-
Notifications
You must be signed in to change notification settings - Fork 24
feat: verify range proofs #1117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
demosdemon
wants to merge
98
commits into
main
Choose a base branch
from
brandon.leblanc/range-proofs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cda5ced
to
3b39383
Compare
This was referenced Jul 23, 2025
demosdemon
added a commit
that referenced
this pull request
Jul 23, 2025
In #1117, I have a need to reuse this verification method. I have refactored it out into separate steps to make that easier.
demosdemon
added a commit
that referenced
this pull request
Jul 23, 2025
…ections (#1121) In a future PR, there will be several cleanups that take advantage of proof being able to switch back and forth from immutable and mutable containers. Particularly in testing, but a few other cases as well. See #1117 for a raw view. The `EmptyProofCollection` is primarily for tests.
623ae71
to
880b0bf
Compare
00ca21b
to
ede7b8e
Compare
ede7b8e
to
474a1c0
Compare
0c9ed1a
to
ab87372
Compare
ab87372
to
0011733
Compare
64df1e6
to
31da116
Compare
This is the final PR in the series to refactor the FFI layer to be more rust and also a bit more Gooey. This replaces the serial atomic id for proposals with a pointer to the actual structures. The wrapper that includes the db handle is only necessary in order to do the cached view shenanigans. I will come back to that later.
29cff9f
to
7dee974
Compare
b396f52
to
2808bf2
Compare
…leblanc/range-proofs
…lized-range-proofs
…eft-exclusion-proof
We were incorrectly checking the length of the last node's full path against the length of the key being proven. This meant that if the last node's full path was the same _length_ as the key being proven, but the key was different, we would incorrectly verify the proof by expecting a value when there should be none.
…leblanc/fix-verify-exclusion-proof
…leblanc/fix-verify-exclusion-proof
…leblanc/fix-verify-exclusion-proof
…eblanc/ffi-refactor
…sion-proof' into brandon.leblanc/ffi-refactor
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Important
This is a work in progress and not yet complete.
Concepts
This section briefly covers some concepts related to Firewood, Ethereum, Key Proofs, and Range Proofs that provide additional context for this change. Some
concepts apply to Change Proofs as well, but this document isn't focused on Change Proofs.
Branching Factor
The branching factor for the trie determines how we split the path when constructing trie nodes as well as how many children are possible for each node.
The default in firewood is a branching factor of 16 and additionally supports a branching factor of 256 with the
branch_factor_256
cargo feature. This means each node can have up to 16 or 256 children, respectively.Path and Path Components
Generically, a trie path is represented by a sequence of path components in big-endian order where a path component is every$\log_2(F)$ bits where $F$ is the branching factor.
Branch Factor 16
With a branching factor of 16, each path component is a 4-bit nibble and a path is a big-endian sequence of 4-bit nibbles. E.g., the hex key
0x1234567890abcdef
is broken down into a sequence of nibbles:Conveniently, hexadecimal is encoded the same way and makes it easy to visually follow a hex encoded key through the trie.
Branch Factor 256
With a branching factor of 256, each path component is a full 8-bit byte. E.g., the hex key
0x1234567890abcdef
is broken down into a sequence of bytes:Trie Bitmap
A trie bitmap is the map of the child's next path component to the child. In rust, we have this represented with an array type alias:
Where
Children
is a type alias for the array of optionalT
values.MAX_CHILDREN
is defined by the branching factor. The default branching factor of 16 contains 16 children and when branching factor of 256 is enabled, the children array is for 256 elements.The array itself is generic over the contents. This is typically the child node (e.g.,
Box<Self>
) which forms a very simple trie structure:However, the element in the children array can be more complex and form a graph edge with additional information. For example, we can encode the merklization of the trie by including the hash of the child:
Key Proofs
Key proofs within the Firewood PMT are represented by a sequence of nodes that follow the path from the root to the target key. If the key is included in the trie, the path terminates on the respective node with a value. If the key is not included in the trie, the path terminates on the node that would be the parent of the node for the key, if it were present. Additionally, each node along the path includes its partial path, value 1, and a trie bitmap of hashes corresponding to each child.
For example, an inclusion proof for the for the key
89d6aaba59f247bcd526ea4631d82646939dea4f2f39bcf57a1c5f60ec4ca03f
in some theoretical trie would be the following sequence of nodes.Diagraming this trie looks like:
Like inclusion proofs, an exclusion proof includes every node in the trie along the path to the disproven key. The absence of a node where there would be if the child existed is the necessary proof that the key does not exist (along with a valid hash of that node).
Using the same proof from above as an example, it includes all the necessary information to disprove the existance of any key in the trie beginning with the following prefixes (exhaustive) https://gist.github.com/demosdemon/f2593327d34075aa904c89e784b6f122#file-excluded_keys-txt.
A reference python script to generate an exhaustive list of excluded key prefixes is also included in the gist.
Non Exhaustive:
Range Proof
A range proof includes the necessary information to prove the existence or absence of every key in the range from start to end. This currently consists of a key proof for the start and end of the range as well as every key and value 2 within the range.
For the totally unbounded range 3, the range proof is the trie itself. In practice, a range proof over the total range is unlikely to yield the total trie due to enforced limits on payload sizes.
For bounded ranges, a key proof is provided for start and end range. As a special note, bounded ranges may have an unbounded start and bounded end, in which case no proof is provided for start. This is because the proof of an unbounded start is simply the proof of the empty key which is automatically included in every proof as it will always be a single proof node over the root node. The converse is true as well; a bounded range may consist of a bounded start and unbounded end where no proof is provided for the end.
For truncated ranges, the end proof is the final key in the truncated range and not the end of the desired range. A proof beyond the final provided key is only valid if there are no keys in the range from the final key to the proven key (in which case, the range was not truncated).
Footnotes
Or the digest of the value; depending on the trie hashing algorithm. The MerkleDB compatible hash includes only the sha256 digest of the value if the value is 32 bytes or longer. However, the Ethereum compatible hash includes the full value of the node. In other words, the proof node includes that value that is part of the preimage. ↩
Unlike proof nodes, range nodes include the complete value every time. ↩
Using total ordering, a totally unbounded range for the PMT is a range beginning with the empty key and ending with a key of infinite length. ↩