diff --git a/zebra-chain/src/sprout/joinsplit.rs b/zebra-chain/src/sprout/joinsplit.rs index 74ada9b20..32d4bae3d 100644 --- a/zebra-chain/src/sprout/joinsplit.rs +++ b/zebra-chain/src/sprout/joinsplit.rs @@ -121,25 +121,50 @@ impl JoinSplit

{ impl ZcashDeserialize for JoinSplit

{ fn zcash_deserialize(mut reader: R) -> Result { + // # Consensus + // + // > Elements of a JoinSplit description MUST have the types given above + // + // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc + // + // See comments below for each specific type. Ok(JoinSplit::

{ + // Type is `{0 .. MAX_MONEY}`; see [`NonNegative::valid_range()`]. vpub_old: (&mut reader).zcash_deserialize_into()?, vpub_new: (&mut reader).zcash_deserialize_into()?, + // Type is `B^{ℓ^{Sprout}_{Merkle}}` i.e. 32 bytes. anchor: tree::Root::from(reader.read_32_bytes()?), + // Types are `B^{ℓ^{Sprout}_{PRF}}` i.e. 32 bytes. nullifiers: [ reader.read_32_bytes()?.into(), reader.read_32_bytes()?.into(), ], + // Types are `NoteCommit^{Sprout}.Output`, i.e. `B^{ℓ^{Sprout}_{Merkle}}`, + // i.e. 32 bytes. https://zips.z.cash/protocol/protocol.pdf#abstractcommit commitments: [ commitment::NoteCommitment::from(reader.read_32_bytes()?), commitment::NoteCommitment::from(reader.read_32_bytes()?), ], + // Type is `KA^{Sprout}.Public`, i.e. `B^Y^{[32]}`, i.e. 32 bytes. + // https://zips.z.cash/protocol/protocol.pdf#concretesproutkeyagreement ephemeral_key: x25519_dalek::PublicKey::from(reader.read_32_bytes()?), + // Type is `B^{[ℓ_{Seed}]}`, i.e. 32 bytes random_seed: RandomSeed::from(reader.read_32_bytes()?), + // Types are `B^{ℓ^{Sprout}_{PRF}}` i.e. 32 bytes. + // See [`note::Mac::zcash_deserialize`]. vmacs: [ note::Mac::zcash_deserialize(&mut reader)?, note::Mac::zcash_deserialize(&mut reader)?, ], + // Type is described in https://zips.z.cash/protocol/protocol.pdf#grothencoding. + // It is not enforced here; this just reads 192 bytes. + // The type is validated when validating the proof, see + // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead. zkproof: P::zcash_deserialize(&mut reader)?, + // Types are `Sym.C`, i.e. `B^Y^{[N]}`, i.e. arbitrary-sized byte arrays + // https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to + // 601 bytes in https://zips.z.cash/protocol/protocol.pdf#joinsplitencodingandconsensus + // See [`note::EncryptedNote::zcash_deserialize`]. enc_ciphertexts: [ note::EncryptedNote::zcash_deserialize(&mut reader)?, note::EncryptedNote::zcash_deserialize(&mut reader)?, diff --git a/zebra-consensus/src/primitives/groth16.rs b/zebra-consensus/src/primitives/groth16.rs index a1fc3ab91..25651a545 100644 --- a/zebra-consensus/src/primitives/groth16.rs +++ b/zebra-consensus/src/primitives/groth16.rs @@ -314,6 +314,14 @@ where type Error = TransactionError; fn try_from(input: DescriptionWrapper<&T>) -> Result { + // # Consensus + // + // > Elements of a JoinSplit description MUST have the types given above + // + // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc + // + // This validates the 𝜋_{ZKJoinSplit} element. In #3179 we plan to validate + // during deserialization, see [`JoinSplit::zcash_deserialize`]. Ok(Item::from(( bellman::groth16::Proof::read(&input.0.proof().0[..]) .map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?, diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 48f7480d1..c8327dbbb 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -714,16 +714,18 @@ where if let Some(joinsplit_data) = joinsplit_data { for joinsplit in joinsplit_data.joinsplits() { - // Consensus rule: The proof π_ZKSpend MUST be valid given a - // primary input formed from the relevant other fields and h_{Sig} + // # Consensus + // + // > The proof π_ZKJoinSplit MUST be valid given a + // > primary input formed from the relevant other fields and h_{Sig} + // + // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc // // Queue the verification of the Groth16 spend proof // for each JoinSplit description while adding the // resulting future to our collection of async // checks that (at a minimum) must pass for the // transaction to verify. - // - // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc checks.push(primitives::groth16::JOINSPLIT_VERIFIER.oneshot( DescriptionWrapper(&(joinsplit, &joinsplit_data.pub_key)).try_into()?, )); diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 9ea9b245a..cabb29bf8 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -144,7 +144,10 @@ pub fn joinsplit_has_vpub_zero(tx: &Transaction) -> Result<(), TransactionError> .output_values_to_sprout() .zip(tx.input_values_from_sprout()); for (vpub_old, vpub_new) in vpub_pairs { + // # Consensus + // // > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero. + // // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc if *vpub_old != zero && *vpub_new != zero { return Err(TransactionError::BothVPubsNonZero); @@ -168,7 +171,10 @@ pub fn disabled_add_to_sprout_pool( .activation_height(network) .expect("Canopy activation height must be present for both networks"); - // [Canopy onward]: `vpub_old` MUST be zero. + // # Consensus + // + // > [Canopy onward]: `vpub_old` MUST be zero. + // // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc if height >= canopy_activation_height { let zero = Amount::::try_from(0).expect("an amount of 0 is always valid");