Repository navigation
feat(shielded-pool)!: private transfer across two trees, spend hardening (circuit v3, spec 18 / tx 6) - #160
Merged
Merged
Conversation
…ing (circuit v3, spec 18 / tx 6)
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
Spec 18 (unreleased) gains private transfers across two trees: transfer circuit v3 proves each input against its own Merkle root, so a note in a sealed tree can be spent together with one in the active tree, with no consolidation step. The spend pipeline is hardened along the way.
transaction_version5 → 6.Pairs with
@orbinum/circuits0.17.1 (transfer v3, unshield v3, release ceremony).Changes
orbinum-zk-verifier3.0.0 (primitives)InputLayout::CrossTree: a transfer key of 9 public inputs. Unshield and shield keys of that arity are still refused.max_public_inputs(circuit_id): the single source for each circuit's widest arity, used by the compile-time cap and the verification weight.PROOF_BYTES(128) long, andMAX_PROOF_BYTESis removed. The deserializer ignored trailing bytes, so a padded proof was a second encoding of the same proof.pallet-zk-verifier0.15.0TransferStatement.merkle_roots: [[u8; 32]; 2].encode_transfer/encode_unshieldreturnOption.Nonemeans the key cannot attest to the statement (two different roots under a v1/v2 key), and the proof fails without a pairing.verification_weightcovers 9 inputs.pallet-shielded-pool0.22.0private_transfertakesmerkle_roots: [Hash; 2].TransferRequest::ensure_roots: every root must be known, and a dummy input's root must repeat the real input's (InvalidPublicSignals, pool code 13). The circuit leaves a dummy's root free.transfer_op_hashbinds both roots, in order.MaxLeavesPerTree < 2^depth. Production (2^20) was not affected.private_transferbenchmark now spends from two sealed trees (worst-case root lookups).pallet-evm-precompile-shielded-pool0.9.0privateTransfer(bytes,bytes32[],bytes32[],bytes32[],bytes[],uint32,uint256,uint32), selector0x63d0b9a0, with exactly two roots.0x66ed2cd4is gone. The fee stays in slot 6.Relay RPC, runtime, scripts
transaction_version: 6, andRUNTIME_VERSIONS.mdspec 18 with crate versions and the activation steps.rotate-dev.sh: expected arity per (circuit, version), plus a--circuitsflag.Security
The circuits side carries a critical fix: transfer v1/v2 and unshield v1/v2 accepted non-canonical spending keys, so one note admitted several nullifiers. Transfer v3 and unshield v3 (circuits 0.17.1) constrain the key to be canonical. No runtime code is needed, because unshield v3 keeps v2's 8-input layout. Transfer v2 and unshield v2 must be retired as soon as v3 is active.
The spend pipeline (relay RPC → precompile → pool admission and dispatch → verifier) was attacked adversarially. Covered by tests:
x + rtwins of nullifier, commitment, change and root;Test plan
cargo test: primitives 82, pallet-zk-verifier 137, pallet-shielded-pool 406 (416 withruntime-benchmarks), precompile 97,fc-rpcrelay 65, runtime 46cargo fmt --check,taplo fmt --check,make clippypallet_shielded_poolandpallet_zk_verifiergenerate (private_transfer:SealedRootIndex r:2)e2e-cross-tree-live.cjs: 42/42e2e-spend-hardening-live.cjs: 78/78, including the relay RPC flow with a two-tree spend (commit, fee to the committed relayer, no double relay, refusals before any claim)