Skip to content

sigcache: harden range proof cache keys and add -norangeproofcache option - #1600

Merged
psgreco merged 5 commits into
ElementsProject:elements-23.3.xfrom
delta1:elements-23.3.x
Sep 8, 2026
Merged

sigcache: harden range proof cache keys and add -norangeproofcache option#1600
psgreco merged 5 commits into
ElementsProject:elements-23.3.xfrom
delta1:elements-23.3.x

Conversation

@delta1

@delta1 delta1 commented Sep 8, 2026

Copy link
Copy Markdown
Member

Target branch is elements-23.3.x

Switch the range-proof and surjection-proof cache hashers from raw
CSHA256 concatenation to CHashWriter . This length-prefixes
every field, so distinct argument tuples with byte-identical raw
concatenations no longer collide to the same cache key.

Both caches retain per-process salted midstates with distinct domain
separators ('r' / 's').

Also adds a -norangeproofcache startup option to
disable the range proof cache without recompiling.

Unit tests cover field-boundary collisions, script sensitivity, domain
separation, determinism, and vTags sensitivity.

Bump version to 23.3.4 and updates manpages.

Switch range-proof and surjection-proof cache hashers from raw CSHA256
concatenation to CHashWriter (SER_GETHASH). This serializes each field
with a length prefix, so distinct argument tuples with byte-identical
raw concatenations no longer collide to the same cache key.

A cache entry is a positive verification result; a collision would let
an attacker bypass verification. The fix affects two caches:

- ComputeEntryRangeProof: proof, commitment, asset_commitment, scriptPubKey
- ComputeEntrySurjectionProof: add vTags to key (was missing entirely)

Both caches retain the per-process 64-byte salted midstate
(nonce || PADDING_RANGE_PROOF / PADDING_SURJECTION_PROOF) for domain
separation between the two proof types.

Expose thin test-only hooks (TestComputeEntryRangeProof /
TestComputeEntrySurjectionProof) so unit tests can reach the
anonymous-namespace cache internals.
Test the field-boundary, script-sensitivity, domain-separation,
determinism, and vTags-sensitivity properties of the range-proof and
surjection-proof cache entry computation.
Add -norangeproofcache startup option. InitRangeproofCache()
early-returns (allocating nothing) when the flag is set. VerifyRangeProof
reads the flag on each call and bypasses both the Get and Set paths,
so cache behaviour is fully skipped without restarting.
@delta1

delta1 commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

ARM CI task failure is because Debian 11 is EOL

https://www.debian.org/News/2026/20260831

@l0rinc

l0rinc commented Sep 8, 2026

Copy link
Copy Markdown

A fuzz oracle to test the combinations with and without cache could help catching these problems next time.

Comment thread src/script/sigcache.cpp
// 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.

Comment thread src/script/sigcache.cpp
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.

Comment thread src/script/sigcache.cpp
// 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.

@apoelstra

Copy link
Copy Markdown
Member

Done reviewing 1bfa1ee. Just two nits.

@psgreco

psgreco commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

ACK 1bfa1ee

@psgreco
psgreco merged commit ca17280 into ElementsProject:elements-23.3.x Sep 8, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants