Skip to content

feat: add snapshot Merkle inclusion proof verifier (#463)#491

Open
morelucks wants to merge 1 commit into
RevoraOrg:masterfrom
morelucks:feat/snapshot-merkle-proof
Open

feat: add snapshot Merkle inclusion proof verifier (#463)#491
morelucks wants to merge 1 commit into
RevoraOrg:masterfrom
morelucks:feat/snapshot-merkle-proof

Conversation

@morelucks

Copy link
Copy Markdown

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 function verify_snapshot_inclusion


Design

Leaf encoding

Identical to finalize_snapshot (same hash function, same serialisation):

leaf_hash = SHA-256( index_xdr || holder_xdr || shares_bps_xdr )

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:

parent = SHA-256( min(a, b) || max(a, b) )

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

  1. Hash the supplied raw leaf bytes with SHA-256 → current
  2. For each sibling in proof: sort pair, concatenate, SHA-256 → new current
  3. current == entry.content_hashtrue, otherwise false

Security properties

Property Detail
Finalization guard Returns false for unfinalized snapshots (untrusted content_hash)
Missing snapshot Returns false — no error leaked (prevents enumeration)
Fixed-width proof elements Vec<BytesN<32>> — no length-extension attack surface
Collision resistance Forging a valid proof requires breaking SHA-256
Same hash function as finalize_snapshot env.crypto().sha256() throughout

Leaf encoding reference (for off-chain implementors)

// index: slot position (u32), holder: Address, share_bps: u32
let mut raw = Bytes::new(&env);
raw.append(&index.to_xdr(&env));
raw.append(&holder.to_xdr(&env));
raw.append(&share_bps.to_xdr(&env));
let leaf_hash = env.crypto().sha256(&raw).to_bytes(); // BytesN<32>

Pass raw (the pre-hash bytes) as the leaf argument to verify_snapshot_inclusion.

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)
@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

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.

Add snapshot Merkle inclusion proof verification helper for off-chain claim attestation

1 participant