Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 23)
define(_CLIENT_VERSION_MINOR, 3)
define(_CLIENT_VERSION_BUILD, 4)
define(_CLIENT_VERSION_RC, 1)
define(_CLIENT_VERSION_RC, 0)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2026)
define(_COPYRIGHT_HOLDERS,[The %s developers])
Expand Down
2 changes: 1 addition & 1 deletion doc/man/elements-cli.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTS-CLI "1" "June 2026" "elements-cli v23.3.4" "User Commands"
.TH ELEMENTS-CLI "1" "September 2026" "elements-cli v23.3.4" "User Commands"
.SH NAME
elements-cli \- manual page for elements-cli v23.3.4
.SH SYNOPSIS
Expand Down
12 changes: 2 additions & 10 deletions doc/man/elements-qt.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTS-QT "1" "June 2026" "elements-qt v23.3.4" "User Commands"
.TH ELEMENTS-QT "1" "September 2026" "elements-qt v23.3.4" "User Commands"
.SH NAME
elements-qt \- manual page for elements-qt v23.3.4
.SH SYNOPSIS
Expand Down Expand Up @@ -115,7 +115,7 @@ Do not keep transactions in the mempool longer than <n> hours (default:
.HP
\fB\-par=\fR<n>
.IP
Set the number of script verification threads (\fB\-16\fR to 15, 0 = auto, <0 =
Set the number of script verification threads (\fB\-24\fR to 15, 0 = auto, <0 =
leave that many cores free, default: 0)
.HP
\fB\-persistmempool\fR
Expand Down Expand Up @@ -318,10 +318,6 @@ created within past week. 0 = no limit (default: 0M). Optional
suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000
base while uppercase is 1024 base
.HP
\fB\-natpmp\fR
.IP
Use NAT\-PMP to map the listening port (default: 0)
.HP
\fB\-networkactive\fR
.IP
Enable all P2P network activity (default: 1). Can be changed by the
Expand Down Expand Up @@ -390,10 +386,6 @@ Tor control port to use if onion listening enabled (default:
.IP
Tor control port password (default: empty)
.HP
\fB\-upnp\fR
.IP
Use UPnP to map the listening port (default: 0)
.HP
\fB\-whitebind=\fR<[permissions@]addr>
.IP
Bind to the given address and add permission flags to the peers
Expand Down
2 changes: 1 addition & 1 deletion doc/man/elements-tx.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTS-TX "1" "June 2026" "elements-tx v23.3.4" "User Commands"
.TH ELEMENTS-TX "1" "September 2026" "elements-tx v23.3.4" "User Commands"
.SH NAME
elements-tx \- manual page for elements-tx v23.3.4
.SH SYNOPSIS
Expand Down
2 changes: 1 addition & 1 deletion doc/man/elements-util.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTS-UTIL "1" "June 2026" "elements-util v23.3.4" "User Commands"
.TH ELEMENTS-UTIL "1" "September 2026" "elements-util v23.3.4" "User Commands"
.SH NAME
elements-util \- manual page for elements-util v23.3.4
.SH SYNOPSIS
Expand Down
2 changes: 1 addition & 1 deletion doc/man/elements-wallet.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTS-WALLET "1" "June 2026" "elements-wallet v23.3.4" "User Commands"
.TH ELEMENTS-WALLET "1" "September 2026" "elements-wallet v23.3.4" "User Commands"
.SH NAME
elements-wallet \- manual page for elements-wallet v23.3.4
.SH DESCRIPTION
Expand Down
12 changes: 2 additions & 10 deletions doc/man/elementsd.1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ELEMENTSD "1" "June 2026" "elementsd v23.3.4" "User Commands"
.TH ELEMENTSD "1" "September 2026" "elementsd v23.3.4" "User Commands"
.SH NAME
elementsd \- manual page for elementsd v23.3.4
.SH SYNOPSIS
Expand Down Expand Up @@ -115,7 +115,7 @@ Do not keep transactions in the mempool longer than <n> hours (default:
.HP
\fB\-par=\fR<n>
.IP
Set the number of script verification threads (\fB\-16\fR to 15, 0 = auto, <0 =
Set the number of script verification threads (\fB\-24\fR to 15, 0 = auto, <0 =
leave that many cores free, default: 0)
.HP
\fB\-persistmempool\fR
Expand Down Expand Up @@ -318,10 +318,6 @@ created within past week. 0 = no limit (default: 0M). Optional
suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000
base while uppercase is 1024 base
.HP
\fB\-natpmp\fR
.IP
Use NAT\-PMP to map the listening port (default: 0)
.HP
\fB\-networkactive\fR
.IP
Enable all P2P network activity (default: 1). Can be changed by the
Expand Down Expand Up @@ -390,10 +386,6 @@ Tor control port to use if onion listening enabled (default:
.IP
Tor control port password (default: empty)
.HP
\fB\-upnp\fR
.IP
Use UPnP to map the listening port (default: 0)
.HP
\fB\-whitebind=\fR<[permissions@]addr>
.IP
Bind to the given address and add permission flags to the peers
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ BITCOIN_TESTS =\
test/serialize_tests.cpp \
test/settings_tests.cpp \
test/sighash_tests.cpp \
test/sigcache_tests.cpp \
test/sigopcount_tests.cpp \
test/skiplist_tests.cpp \
test/sock_tests.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-rangeproofcache", strprintf("Enable the range proof validation cache (default: %u). Use -norangeproofcache to disable.", 1), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-printpriority", strprintf("Log transaction fee rate in " + CURRENCY_UNIT + "/kvB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
Expand Down
103 changes: 79 additions & 24 deletions src/script/sigcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <random.h>
#include <uint256.h>
#include <util/system.h>
#include <hash.h>

#include <cuckoocache.h>

Expand All @@ -26,23 +27,23 @@ namespace {
class CSignatureCache
{
private:
//! Entries are SHA256(nonce || 'E' or 'S' || 31 zero bytes || signature hash || public key || signature):
//! Salted SHA256 midstates, domain-separated by signature or proof type.
CSHA256 m_salted_hasher_ecdsa;
CSHA256 m_salted_hasher_schnorr;
CSHA256 m_salted_hasher_range_proof;
CSHA256 m_salted_hasher_surjection_proof;
CHashWriter m_salted_hasher_range_proof;
CHashWriter m_salted_hasher_surjection_proof;
typedef CuckooCache::cache<uint256, SignatureCacheHasher> map_type;
map_type setValid;
std::shared_mutex cs_sigcache;

public:
CSignatureCache()
CSignatureCache():
m_salted_hasher_range_proof(SER_GETHASH,0),
m_salted_hasher_surjection_proof(SER_GETHASH,0)
{
uint256 nonce = GetRandHash();
// We want the nonce to be 64 bytes long to force the hasher to process
// this chunk, which makes later hash computations more efficient. We
// just write our 32-byte entropy, and then pad with 'E' for ECDSA and
// 'S' for Schnorr (followed by 0 bytes).
// Use 64-byte, type-specific salted midstates so later hash computations
// can start after the first SHA256 chunk.
static constexpr unsigned char PADDING_ECDSA[32] = {'E'};
static constexpr unsigned char PADDING_SCHNORR[32] = {'S'};
static constexpr unsigned char PADDING_RANGE_PROOF[32] = {'r'};
Expand All @@ -51,10 +52,8 @@ class CSignatureCache
m_salted_hasher_ecdsa.Write(PADDING_ECDSA, 32);
m_salted_hasher_schnorr.Write(nonce.begin(), 32);
m_salted_hasher_schnorr.Write(PADDING_SCHNORR, 32);
m_salted_hasher_range_proof.Write(nonce.begin(), 32);
m_salted_hasher_range_proof.Write(PADDING_RANGE_PROOF, 32);
m_salted_hasher_surjection_proof.Write(nonce.begin(), 32);
m_salted_hasher_surjection_proof.Write(PADDING_SURJECTION_PROOF, 32);
m_salted_hasher_range_proof << nonce << PADDING_RANGE_PROOF;
m_salted_hasher_surjection_proof << nonce << PADDING_SURJECTION_PROOF;
}

void
Expand All @@ -72,13 +71,39 @@ class CSignatureCache
}

// ELEMENTS:
void ComputeEntryRangeProof(uint256& entry, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment, const std::vector<unsigned char>& asset_commitment, const CScript& scriptPubKey) {
CSHA256 hasher = m_salted_hasher_range_proof;
hasher.Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Write(asset_commitment.data(), asset_commitment.size()).Write(scriptPubKey.data(), scriptPubKey.size()).Finalize(entry.begin());
void ComputeEntryRangeProof(uint256& entry,
const std::vector<unsigned char>& proof,
const std::vector<unsigned char>& commitment,
const std::vector<unsigned char>& asset_commitment,
const CScript& script_pub_key) const
{
CHashWriter hasher = m_salted_hasher_range_proof;
// We commit to both commitments and the scriptPubKey because these are
// committed to by the rangeproof itself; a change in any of them would
// invalidate the proof. Since these are exactly the arguments to
// CachingRangeProofChecker::VerifyRangeProof (below), there is no
// additional data that could affect the rangeproof's validity.
// Serialization length-prefixes every field, including the variable-length
// proof and script, so distinct argument tuples cannot share an encoding.
hasher << proof << commitment << asset_commitment << script_pub_key;
entry = hasher.GetSHA256();
}
void ComputeEntrySurjectionProof(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment) {
CSHA256 hasher = m_salted_hasher_surjection_proof;
hasher.Write(hash.begin(), 32).Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Finalize(entry.begin());
void ComputeEntrySurjectionProof(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment, const std::vector<secp256k1_generator>& vTags) const {
CHashWriter hasher = m_salted_hasher_surjection_proof;
// We hash all arguments passed to CachingSurjectionProofChecker::VerifySurjectionProof,
// to ensure that any change in the way that the verification function is called will
// trigger a cache miss and explicit verification. However, we note that the `wtxid`
// (hash) commits to all the other data such that we could technically hash only it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 9400096:

I realize it isn't true that just the wtxid and proof are sufficient -- you can imagine the same proof appearing multiple times in the same transaction, such that it's only valid in one place.

Our code correctly avoids this vulnerability by hashing the commitment and tags, it's just the comment saying "y'know..." that's wrong.

// We retain the other data as a defense against future refactorings.
//
// Serialize vTags as a flat byte vector (each secp256k1_generator is 64 bytes).
std::vector<unsigned char> vTagsBytes;
vTagsBytes.reserve(vTags.size() * 64);
for (const auto& tag : vTags) {
vTagsBytes.insert(vTagsBytes.end(), std::begin(tag.data), std::end(tag.data));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 9400096:

Just highlighting that this use of the internal tag.data array is fine, although the libsecp256k1-zkp docs say that the internals of secp256k1_generator are implementation-defined and nonportable. We do not need portability here or any particular property of the bytes, only that they represent the generator in question.

}
hasher << hash << proof << commitment << vTagsBytes;
entry = hasher.GetSHA256();
}

bool
Expand Down Expand Up @@ -154,6 +179,10 @@ bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsig
// To be called once in AppInit2/TestingSetup to initialize the rangeproof cache
void InitRangeproofCache()
{
if (!gArgs.GetBoolArg("-rangeproofcache", true)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 19d7042:

I think we should also disable the surjectionproof cache, throughout. Fine to leave the option name as-is.

LogPrintf("Range proof cache disabled via -norangeproofcache\n");
return;
}
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
// setup_bytes creates the minimum possible cache (2 elements).
size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 4), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
Expand All @@ -175,11 +204,18 @@ void InitSurjectionproofCache()

bool CachingRangeProofChecker::VerifyRangeProof(const std::vector<unsigned char>& vchRangeProof, const std::vector<unsigned char>& vchValueCommitment, const std::vector<unsigned char>& vchAssetCommitment, const CScript& scriptPubKey, const secp256k1_context* secp256k1_ctx_verify_amounts) const
{
// ELEMENTS: NOTE FOR FUTURE EDITORS: every argument to this function that
// carries data (i.e. everything except the secp256k1 context, which is
// stateless) MUST be included in ComputeEntryRangeProof. Omitting any
// argument risks returning a cached positive result for a proof that was
// verified with different inputs.
uint256 entry;
rangeProofCache.ComputeEntryRangeProof(entry, vchRangeProof, vchValueCommitment, vchAssetCommitment, scriptPubKey);

if (rangeProofCache.Get(entry, !store)) {
return true;
const bool useCache = gArgs.GetBoolArg("-rangeproofcache", true);
if (useCache) {
rangeProofCache.ComputeEntryRangeProof(entry, vchRangeProof, vchValueCommitment, vchAssetCommitment, scriptPubKey);
if (rangeProofCache.Get(entry, !store)) {
return true;
}
}

if (vchRangeProof.size() == 0) {
Expand Down Expand Up @@ -208,7 +244,7 @@ bool CachingRangeProofChecker::VerifyRangeProof(const std::vector<unsigned char>
return false;
}

if (store) {
if (useCache && store) {
rangeProofCache.Set(entry);
}

Expand All @@ -227,7 +263,7 @@ bool CachingSurjectionProofChecker::VerifySurjectionProof(secp256k1_surjectionpr
// wtxid commits to all data including surj targets
// we need to specify the proof and output asset point to be unique
uint256 entry;
surjectionProofCache.ComputeEntrySurjectionProof(entry, wtxid, vchproof, std::vector<unsigned char>(std::begin(gen.data), std::end(gen.data)));
surjectionProofCache.ComputeEntrySurjectionProof(entry, wtxid, vchproof, std::vector<unsigned char>(std::begin(gen.data), std::end(gen.data)), vTags);

if (surjectionProofCache.Get(entry, !store)) {
return true;
Expand All @@ -244,5 +280,24 @@ bool CachingSurjectionProofChecker::VerifySurjectionProof(secp256k1_surjectionpr
return true;
}

// Test-only hooks (see sigcache.h). Forward to the anonymous-namespace caches.
void TestComputeEntryRangeProof(uint256& entry,
const std::vector<unsigned char>& proof,
const std::vector<unsigned char>& commitment,
const std::vector<unsigned char>& asset_commitment,
const CScript& script_pub_key)
{
rangeProofCache.ComputeEntryRangeProof(entry, proof, commitment, asset_commitment, script_pub_key);
}

void TestComputeEntrySurjectionProof(uint256& entry,
const uint256& hash,
const std::vector<unsigned char>& proof,
const std::vector<unsigned char>& commitment,
const std::vector<secp256k1_generator>& vTags)
{
surjectionProofCache.ComputeEntrySurjectionProof(entry, hash, proof, commitment, vTags);
}

// END ELEMENTS
//
14 changes: 14 additions & 0 deletions src/script/sigcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ class CachingSurjectionProofChecker
void InitRangeproofCache();
void InitSurjectionproofCache();

// Test-only hooks: expose the (anonymous-namespace) cache-entry computation so
// unit tests can verify collision-resistance and domain separation. These are
// NOT part of the consensus/validation API and are only used by unit tests.
void TestComputeEntryRangeProof(uint256& entry,
const std::vector<unsigned char>& proof,
const std::vector<unsigned char>& commitment,
const std::vector<unsigned char>& asset_commitment,
const CScript& script_pub_key);
void TestComputeEntrySurjectionProof(uint256& entry,
const uint256& hash,
const std::vector<unsigned char>& proof,
const std::vector<unsigned char>& commitment,
const std::vector<secp256k1_generator>& vTags);

// END ELEMENTS
//

Expand Down
Loading
Loading