Hash function
Poseidon2 over BN254
Nullis uses Poseidon / Poseidon2 on the BN254 curve — the Stellar Protocol 25/26 host functions. No other hash is substituted anywhere in the system.
The canonical structures
What each one does
commitment
commitment
Poseidon(credential_secret). The public commitment to a private secret. The issuer adds commitments to the approved-root Merkle tree; the circuit proves membership without revealing the secret.policy_hash
policy_hash
Binds every consensus-critical policy parameter into one hash, registered on-chain. Any change to the asset, limit, root, or expiry changes the hash — you cannot quietly alter a published policy.
context_hash
context_hash
Binds a proof to one exact action — network, both contracts, the policy version, and every field of the payment. The circuit binds to it; the contract recomputes it from the submitted action and checks the match.
action_id
action_id
One application-created authorization intent, carrying the
intent_nonce. Consumed on-chain to prevent replay.nullifier
nullifier
Poseidon(credential_secret, policy_id, app_domain, action_id). Domain-separated and cross-app unlinkable. Consumed on-chain so the same proof can never be spent twice. The app_domain term is what makes it unlinkable across apps.Replay semantics
Theintent_nonce is bound into action_id and consumed on-chain — it is never a free value feeding only the nullifier. A genuinely new payment requires a new intent nonce and a new proof.
The cross-implementation guarantee
The load-bearing property of the whole system: the same canonical hashes are produced on-chain (Rust /soroban-poseidon), off-chain (TypeScript / @nullis/core), and in the Noir circuit. A single golden-vector file (test-vectors.json) is asserted by both Rust and TypeScript, and nargo execute cross-validates that Noir’s Poseidon2 is identical.
Because all three implementations agree byte-for-byte, the ZK layer cannot silently diverge from the contract. See Evidence → tests.