docs: document consensus rules from 4.3 JoinSplit Descriptions (#3452)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Conrado Gouvea 2022-02-08 06:57:09 -03:00 committed by GitHub
parent 3bbb8f0531
commit e9f1aa60ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 5 deletions

View File

@ -121,25 +121,50 @@ impl<P: ZkSnarkProof> JoinSplit<P> {
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
// # 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::<P> {
// 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)?,

View File

@ -314,6 +314,14 @@ where
type Error = TransactionError;
fn try_from(input: DescriptionWrapper<&T>) -> Result<Self, Self::Error> {
// # 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()))?,

View File

@ -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()?,
));

View File

@ -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::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");