Skip to main content

Shield Circuit

Shield moves public tokens into the shielded pool and inserts one note commitment into the active tree of the Merkle forest. The deposit itself is public; the proof ties the new commitment to exactly the amount and asset that were paid in.


Why a deposit needs a proof​

A commitment is an opaque hash, Poseidon4(value, asset_id, owner_pk, blinding) (see Notes). Without a proof, nothing on-chain connects the value inside it to the amount actually deposited: a deposit of 1 could insert a note worth 1,000, and spending that note would drain other depositors' funds.

The shield circuit closes that gap. The pallet supplies value and asset_id from the call itself, and the proof shows that the commitment opens to exactly those two values.


What the circuit proves​

Shield is circuit id 3; version 1 is active. It proves one statement:

I know owner_pubkey and blinding such that commitment = Poseidon4(value, asset_id, owner_pubkey, blinding).

The note commitment recomputed from the inputs must equal the public commitment. Nothing else is constrained.


Public inputs​

In circuit order, which is also the order the verifier encodes them in:

#SignalSourceEncoding
0commitmentthe call's commitment32-byte little-endian field element
1valuethe call's amount (u128)little-endian in the low 16 bytes
2asset_idthe call's asset_id (u32)little-endian in the low 4 bytes

The caller never passes these as field elements: the verifier builds them from the extrinsic's own arguments. A key with any other number of inputs fails verification.


Private inputs​

SignalDescription
owner_pubkeyBaby JubJub Ax of the note owner
blindingBlinding factor of the commitment

The wallet already holds both when it builds the note, so proving needs no Merkle path and no spending key.


What it does not bind​

The owner

owner_pubkey is private and unconstrained. A commitment to a key the depositor does not hold only locks the depositor's own funds.

The memo

Shield has no memo_hash input. A shield is a signed transaction: nobody can copy it and swap the memo without paying the deposit themselves.

The value range

There is no range check on value: the pallet sets it from the call's u128 amount. Transfer and unshield range-check it again whenever the note is spent.

Nothing new is made public

The depositor, amount, asset and commitment of a shield are already visible on-chain. The proof adds a binding, not a disclosure.


How the pallet checks a shield​

shield(asset_id, amount, commitment, encrypted_memo, proof, circuit_version) is a signed call. It runs every check that needs no proof first, so a malformed request costs no verification:

StepCheckError
1The asset exists, is verified and is the native asset (0, ORB)InvalidAssetId, AssetNotVerified, AssetNotSupported
2amount is non-zeroInvalidAmount
3The memo is exactly 180 bytesInvalidMemoSize
4The commitment is a canonical field element and non-zeroInvalidPublicSignals
5The commitment is not already in the forestCommitmentAlreadyExists
6The proof verifies under shield circuit_versionProofVerificationFailed; the verifier's UnsupportedCircuitVersion for an unknown or retired version
7amount moves from the depositor to the pool account with KeepAlivebalance error
8The commitment is inserted and the memo storedMerkleTreeFull

KeepAlive means a shield cannot take the depositor's free balance below the existential deposit. Only the native asset moves today because the pool holds the native currency for every operation; a note of any other asset would be backed by ORB. On success the pallet emits Shielded with the depositor, amount, commitment, memo and leaf index.

shield_batch takes 1 to 20 entries, each with its own proof and circuit_version, and runs them in order exactly as shield would. The first failing entry reverts the whole batch; an empty batch fails with EmptyBatch. The app submits single shields.

Through the EVM, the ShieldedPool precompile takes the same proof and version, with the amount in msg.value.


Circuit parameters​

ParameterValue
Circuit id3
Active version1
Constraints736
Public inputs3 (commitment, value, asset_id)
Private inputs2 (owner_pubkey, blinding)
Tree depthNone: the circuit checks the commitment, not its position
Proving schemeGroth16 over BN254
Single-contributor setup

The shield keys come from a single-contributor trusted setup; a multi-party ceremony is planned before mainnet. See Trusted Setup.