Skip to main content

Threat Model and Audits

1. Why this chapter exists

A new contributor must know, before touching any cryptographic code, which attacks the existing code defends against and which are out of scope. This chapter is the single place where that table lives. Read it once before writing your first non-trivial behavioural patch.

The table is opinionated about scope: a property the Zcash Protocol Specification defines and this crate implements is in scope; consensus state (double-spend prevention across history) is out of scope because this crate does not maintain a chain state.

2. Definitions

Definition 15.1 (in scope). A property is in scope for this crate iff the Zcash Protocol Specification defines it and this crate implements at least one mechanism that participates in enforcing it.

Definition 15.2 (out of scope). A property is out of scope iff its enforcement requires state this crate does not maintain (e.g. the global nullifier set), a layer this crate does not own (network, OS, ceremony), or a separate ZIP this crate does not implement.

Definition 15.3 (defended by reduction). A property "defended by cryptographic reduction" is one whose breaking implies breaking a stated cryptographic assumption (discrete log on Jubjub, qq-power knowledge of exponent on BLS12-381, BLAKE2 collision resistance). For each row in section 3.1, the reduction is named or cited.

Definition 15.4 (defended by code-review invariant). A property "defended by code-review invariant" is one whose absence is not caught by any existing test in the workspace and is preserved only because reviewers actively look for its violation. These rows are the most fragile and the best candidates for new regression tests.

3. Threat-model table

3.1 Defended by cryptographic reduction

Adversary capabilityFormal goalDefence in this crateRegression test
Forge a valid Spend without knowing the spending keyKnowledge soundness of RSpendR_{\mathsf{Spend}}Groth16 verifier in verifier/single.rs::check_spend; reduction is Theorem 11.3circuit::test_input_circuit_with_bls12_381
Spend a note twiceNullifier collision resistance (PRF^nf one-way)Faerie-gold mixing: spec::mixing_pedersen_hash plus spec::prf_nfcircuit::test_input_circuit_with_bls12_381 (asserts in-circuit nf matches out-of-circuit note.nf)
Inflate the shielded supplyBalance preservation (Theorem 9.4)Binding signature over bvk derived from cv_sum - [valueBalance] G_{cv,v} (verifier::SaplingVerificationContextInner::final_check)value::tests::bsk_consistent_with_bvk
Forge a spend authorisation signatureExistential unforgeability of RedJubjubSignature verification in verifier/single.rs::check_spend; the redjubjub crate handles the cryptographykeys::tests::spend_auth_sig_test_vectors
Bind a note to multiple ivk-decryptable plaintexts via a small-order epkSubgroup discipline (Invariant 3.4)epk.is_small_order() check in SaplingVerificationContextInner::check_output lines 108-110; g_d.assert_not_small_order in the Output circuitcircuit::test_output_circuit_with_bls12_381
Bind a spend to a small-order rkSubgroup disciplinerk.is_small_order() check in SaplingVerificationContextInner::check_spend lines 47-51None automated in this workspace; caught by audit only
Slip a non-canonical cmu encoding past consensusCanonical encoding rule (Invariant 5.5)ExtractedNoteCommitment::from_bytes requires Scalar::from_reprNone automated; caught by integration tests in downstream crates (zcash_primitives)

2.2 Defended by side-channel discipline (constant-time)

Adversary capabilityDefenceRegression test
Timing side channel on secret-dependent comparisonsubtle::ConstantTimeEq impls: Nullifier, ExtractedNoteCommitment, EphemeralSecretKeyNone; constant-time is a code-review invariant, not an automated test
Timing side channel on conditional branch over Optionsubtle::CtOption everywhere a public-key-derived branch happens: see SpendAuthorizingKey::from_bytesNone automated

2.3 Out of scope for this crate

Adversary capabilityWhy out of scopeWhere it is enforced
Replay a nullifier (already-seen across chain)This crate computes nullifiers; it does not maintain the seen-setzebrad / zcashd consensus state
Double-spend across forksSame as abovezebrad / zcashd consensus state
Network-level deanonymisation (timing, topology)Out of scope for any crypto crateTor / Heartbeats, zcashd peer-discipline
Memory disclosure of secrets at runtimeThis crate uses Zeroize selectively (e.g. through redjubjub); a process-level memory dump is out of scopeOS-level mitigations
Trusted setup compromise (Sapling MPC)The trusted-setup ceremony is one-time and externalSapling MPC, 2017-2018

4. Where audits live

The crate has been through multiple third-party audits as part of its history under librustzcash. Public audit reports:

  • QED-it Sapling review (the issue thread; reports linked from comments). Test exercises from this review are tracked in issue #94, which is open and a reasonable contribution target.
  • NCC Group review of bellman is not specific to sapling-crypto but covers the underlying Groth16 backend.

The crate maintains its own supply-chain audit infrastructure via cargo-vet in supply-chain/audits.toml. Read it before adding any dependency.

5. Failure modes (this chapter's own)

  • Treating the table as definitive. This table summarises the defence-in-depth surface as of 0.7.0. Future versions will add rows (new attacks, new mitigations) and remove some (an attack found to be subsumed by another). Treat it as a snapshot and check the source first. Caught by: this very disclaimer.

6. Spec pointers

7. Exercises

  1. Locate the small-order check. Without using grep, identify from this table the file and line range that rejects a small-order rk in a spend description. Open the file and confirm the check is exactly what the table says.
  2. Add a regression test for the small-order rk check. Construct a SpendDescription whose rk is one of the eight small-order Jubjub points (the trick: [rJ/8]G[r_J/8] G for a prime-order generator GG would be the identity; cofactor points are explicitly enumerated in the Jubjub paper). Call SaplingVerificationContext::check_spend. Confirm the verifier returns false. Add this as a test.
  3. Find a missing test. This table marks "non-canonical cmu encoding" as having no automated test in this workspace. Write one. Hint: construct a 32-byte buffer corresponding to a value above the field modulus, pass it to ExtractedNoteCommitment::from_bytes, and assert the result is None. This is the kind of test issue #145 exists to attract.

Answers in the code. For exercise 1, the small-order rk check is at src/verifier.rs lines 47-51. For exercise 3, the canonical bls12_381::Scalar modulus is q=0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; any 32-byte value above this is non-canonical.