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:
parent
3bbb8f0531
commit
e9f1aa60ea
|
|
@ -121,25 +121,50 @@ impl<P: ZkSnarkProof> JoinSplit<P> {
|
||||||
|
|
||||||
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
|
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
|
||||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
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> {
|
Ok(JoinSplit::<P> {
|
||||||
|
// Type is `{0 .. MAX_MONEY}`; see [`NonNegative::valid_range()`].
|
||||||
vpub_old: (&mut reader).zcash_deserialize_into()?,
|
vpub_old: (&mut reader).zcash_deserialize_into()?,
|
||||||
vpub_new: (&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()?),
|
anchor: tree::Root::from(reader.read_32_bytes()?),
|
||||||
|
// Types are `B^{ℓ^{Sprout}_{PRF}}` i.e. 32 bytes.
|
||||||
nullifiers: [
|
nullifiers: [
|
||||||
reader.read_32_bytes()?.into(),
|
reader.read_32_bytes()?.into(),
|
||||||
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: [
|
commitments: [
|
||||||
commitment::NoteCommitment::from(reader.read_32_bytes()?),
|
commitment::NoteCommitment::from(reader.read_32_bytes()?),
|
||||||
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()?),
|
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()?),
|
random_seed: RandomSeed::from(reader.read_32_bytes()?),
|
||||||
|
// Types are `B^{ℓ^{Sprout}_{PRF}}` i.e. 32 bytes.
|
||||||
|
// See [`note::Mac::zcash_deserialize`].
|
||||||
vmacs: [
|
vmacs: [
|
||||||
note::Mac::zcash_deserialize(&mut reader)?,
|
note::Mac::zcash_deserialize(&mut reader)?,
|
||||||
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)?,
|
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: [
|
enc_ciphertexts: [
|
||||||
note::EncryptedNote::zcash_deserialize(&mut reader)?,
|
note::EncryptedNote::zcash_deserialize(&mut reader)?,
|
||||||
note::EncryptedNote::zcash_deserialize(&mut reader)?,
|
note::EncryptedNote::zcash_deserialize(&mut reader)?,
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,14 @@ where
|
||||||
type Error = TransactionError;
|
type Error = TransactionError;
|
||||||
|
|
||||||
fn try_from(input: DescriptionWrapper<&T>) -> Result<Self, Self::Error> {
|
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((
|
Ok(Item::from((
|
||||||
bellman::groth16::Proof::read(&input.0.proof().0[..])
|
bellman::groth16::Proof::read(&input.0.proof().0[..])
|
||||||
.map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?,
|
.map_err(|e| TransactionError::MalformedGroth16(e.to_string()))?,
|
||||||
|
|
|
||||||
|
|
@ -714,16 +714,18 @@ where
|
||||||
|
|
||||||
if let Some(joinsplit_data) = joinsplit_data {
|
if let Some(joinsplit_data) = joinsplit_data {
|
||||||
for joinsplit in joinsplit_data.joinsplits() {
|
for joinsplit in joinsplit_data.joinsplits() {
|
||||||
// Consensus rule: The proof π_ZKSpend MUST be valid given a
|
// # Consensus
|
||||||
// primary input formed from the relevant other fields and h_{Sig}
|
//
|
||||||
|
// > 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
|
// Queue the verification of the Groth16 spend proof
|
||||||
// for each JoinSplit description while adding the
|
// for each JoinSplit description while adding the
|
||||||
// resulting future to our collection of async
|
// resulting future to our collection of async
|
||||||
// checks that (at a minimum) must pass for the
|
// checks that (at a minimum) must pass for the
|
||||||
// transaction to verify.
|
// transaction to verify.
|
||||||
//
|
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
||||||
checks.push(primitives::groth16::JOINSPLIT_VERIFIER.oneshot(
|
checks.push(primitives::groth16::JOINSPLIT_VERIFIER.oneshot(
|
||||||
DescriptionWrapper(&(joinsplit, &joinsplit_data.pub_key)).try_into()?,
|
DescriptionWrapper(&(joinsplit, &joinsplit_data.pub_key)).try_into()?,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,10 @@ pub fn joinsplit_has_vpub_zero(tx: &Transaction) -> Result<(), TransactionError>
|
||||||
.output_values_to_sprout()
|
.output_values_to_sprout()
|
||||||
.zip(tx.input_values_from_sprout());
|
.zip(tx.input_values_from_sprout());
|
||||||
for (vpub_old, vpub_new) in vpub_pairs {
|
for (vpub_old, vpub_new) in vpub_pairs {
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
// > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero.
|
// > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero.
|
||||||
|
//
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
||||||
if *vpub_old != zero && *vpub_new != zero {
|
if *vpub_old != zero && *vpub_new != zero {
|
||||||
return Err(TransactionError::BothVPubsNonZero);
|
return Err(TransactionError::BothVPubsNonZero);
|
||||||
|
|
@ -168,7 +171,10 @@ pub fn disabled_add_to_sprout_pool(
|
||||||
.activation_height(network)
|
.activation_height(network)
|
||||||
.expect("Canopy activation height must be present for both networks");
|
.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
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
||||||
if height >= canopy_activation_height {
|
if height >= canopy_activation_height {
|
||||||
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue