feat: add snapshot Merkle inclusion proof verifier (#463)#491
Open
morelucks wants to merge 3 commits into
Open
feat: add snapshot Merkle inclusion proof verifier (#463)#491morelucks wants to merge 3 commits into
morelucks wants to merge 3 commits into
Conversation
Closes RevoraOrg#463 Add verify_snapshot_inclusion(issuer, namespace, token, snapshot_ref, leaf, proof) -> bool, a pure off-chain Merkle inclusion proof helper that lets holders prove their leaf was included in a finalized snapshot without storing the full holder set on-chain. ## Design ### Leaf encoding (identical to finalize_snapshot) leaf_hash = SHA-256( index_xdr || holder_xdr || shares_bps_xdr ) where each field is XDR-serialised via .to_xdr(&env), matching the exact byte layout used in finalize_snapshot. This guarantees that any leaf produced by the existing finalization pass is directly provable. ### Merkle tree construction (sorted-pair) Internal nodes are computed as: parent = SHA-256( min(left, right) || max(left, right) ) Sorting the pair lexicographically before hashing makes the tree position-independent — no left/right flag is needed in the proof array. Off-chain tooling builds proofs with the same sorted-pair rule. ### Proof verification 1. Hash the supplied raw leaf bytes with SHA-256. 2. For each sibling in the proof array, sort the pair and hash. 3. The final value must equal the committed content_hash. ### Security guards - Returns false (not an error) when the snapshot does not exist or is not yet finalized. An unfinalized snapshot has an untrusted content_hash and must not be accepted as a proof root. - Every proof element is BytesN<32> — fixed-width, no length extension. - SHA-256 collision resistance makes forging a valid proof infeasible. Relevant files: src/lib.rs (verify_snapshot_inclusion)
|
@morelucks Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
- Move SnapshotFinalized from DataKey (51 variants > Soroban 50 limit) into DataKey2; redirect all DataKey::SnapshotFinalized references - Add missing DataKey2 variants: SupplyCap, DepositedRevenue, InvestmentConstraints, MinRevenueThreshold - Add PauseState enum (NotPaused/SoftPaused/HardPaused) - Add StaleConcentrationData = 54; fix duplicate discriminant 48 - Fix get_pending_periods -> get_pending_periods_page in test - Fix 5-arg register_offering and deposit_revenue calls in tests - Fix unused Address/token imports in test files - Fix format! -> std::format! in test_compute_share_invariants - Fix for &bps in array -> .iter() - Fix empty line after doc comment in lib.rs - Fix clamp-like pattern; add too_many_arguments allow - Fix deprecated register_stellar_asset_contract - Remove u32 as u32 redundant casts - Fix needless borrow in vesting.rs; collapse nested if - Fix unexpected_cfgs in kani harness - Run cargo fmt
- Fix arithmetic overflow in test_compute_share_invariants (i128::MAX + 9_999 overflows; use aligned boundary values instead) - Fix sibling offering test: deploy a real FailingTransferToken for offering B so deposit_revenue can actually transfer tokens
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
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.
Summary
Closes #463
Adds
verify_snapshot_inclusion— a pure, stateless Merkle inclusion proof helper that lets off-chain holders prove their leaf was included in a finalized snapshot without storing the full holder set on-chain.What changed
src/lib.rs— single new public functionverify_snapshot_inclusionDesign
Leaf encoding
Identical to
finalize_snapshot(same hash function, same serialisation):Each field is XDR-serialised with
.to_xdr(&env). This guarantees that any leaf produced by the existing finalization pass is directly provable — no separate encoding step.Sorted-pair Merkle tree
Internal nodes:
Sorting the pair lexicographically before hashing makes the tree position-independent: the proof array carries only sibling hashes, no left/right flags. Off-chain tooling must build proofs with the same sorted-pair rule.
Verification algorithm
currentproof: sort pair, concatenate, SHA-256 → newcurrentcurrent == entry.content_hash→true, otherwisefalseSecurity properties
falsefor unfinalized snapshots (untrustedcontent_hash)false— no error leaked (prevents enumeration)Vec<BytesN<32>>— no length-extension attack surfacefinalize_snapshotenv.crypto().sha256()throughoutLeaf encoding reference (for off-chain implementors)
Pass
raw(the pre-hash bytes) as theleafargument toverify_snapshot_inclusion.