Orchard data in V5 parsing (#2116)
* initialize the work on parsing orchard data in V5 * add the rest of orchard serialization * fix serialization according to spec * fix arbitrary for Signature<SpendAuth> * move deserialization of AuthorizedAction to shielded_data module * use `from_bits_truncate` to generate valid arbitrary flags * change panic message * fix serialization/deserialization when nActionsOrchard is empty * fix Halo2Proof deserialization * implement ZcashSerialize and ZcashDeserialize for flags * implement ZcashSerialize and ZcashDeserialize for orchard::tree::Root * use ZcashSerialize and ZcashDeserialize for binding_sig * implement from_parts() * implement Arbitrary for Signature<Binding> * add trusted preallocate with tests * fix Arbitrary for orchard Nullifier * Use zcash_serialize_bytes instead of write_compactsize Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
40d06657b3
commit
a57c09a3b8
|
@ -18,6 +18,9 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
|
|
||||||
pub mod amount;
|
pub mod amount;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
|
|
@ -9,8 +9,11 @@ mod arbitrary;
|
||||||
mod commitment;
|
mod commitment;
|
||||||
mod note;
|
mod note;
|
||||||
mod sinsemilla;
|
mod sinsemilla;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
|
pub mod shielded_data;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
|
||||||
pub use action::Action;
|
pub use action::Action;
|
||||||
|
@ -18,3 +21,4 @@ pub use address::Address;
|
||||||
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
||||||
pub use keys::Diversifier;
|
pub use keys::Diversifier;
|
||||||
pub use note::{EncryptedNote, Note, Nullifier};
|
pub use note::{EncryptedNote, Note, Nullifier};
|
||||||
|
pub use shielded_data::{AuthorizedAction, Flags, ShieldedData};
|
||||||
|
|
|
@ -2,7 +2,11 @@ use group::prime::PrimeCurveAffine;
|
||||||
use halo2::pasta::pallas;
|
use halo2::pasta::pallas;
|
||||||
use proptest::{arbitrary::any, array, prelude::*};
|
use proptest::{arbitrary::any, array, prelude::*};
|
||||||
|
|
||||||
use super::{keys, note, Action, NoteCommitment, ValueCommitment};
|
use crate::primitives::redpallas::{Signature, SpendAuth, VerificationKeyBytes};
|
||||||
|
|
||||||
|
use super::{keys, note, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment};
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
impl Arbitrary for Action {
|
impl Arbitrary for Action {
|
||||||
type Parameters = ();
|
type Parameters = ();
|
||||||
|
@ -18,7 +22,7 @@ impl Arbitrary for Action {
|
||||||
|(nullifier, rpk_bytes, enc_ciphertext, out_ciphertext)| Self {
|
|(nullifier, rpk_bytes, enc_ciphertext, out_ciphertext)| Self {
|
||||||
cv: ValueCommitment(pallas::Affine::identity()),
|
cv: ValueCommitment(pallas::Affine::identity()),
|
||||||
nullifier,
|
nullifier,
|
||||||
rk: crate::primitives::redpallas::VerificationKeyBytes::from(rpk_bytes),
|
rk: VerificationKeyBytes::from(rpk_bytes),
|
||||||
cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
|
cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
|
||||||
ephemeral_key: keys::EphemeralPublicKey(pallas::Affine::identity()),
|
ephemeral_key: keys::EphemeralPublicKey(pallas::Affine::identity()),
|
||||||
enc_ciphertext,
|
enc_ciphertext,
|
||||||
|
@ -35,7 +39,52 @@ impl Arbitrary for note::Nullifier {
|
||||||
type Parameters = ();
|
type Parameters = ();
|
||||||
|
|
||||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
array::uniform32(any::<u8>()).prop_map(Self::from).boxed()
|
use halo2::arithmetic::FieldExt;
|
||||||
|
|
||||||
|
(any::<u64>())
|
||||||
|
.prop_map(|number| Self::from(pallas::Scalar::from_u64(number).to_bytes()))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for AuthorizedAction {
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
(any::<Action>(), any::<Signature<SpendAuth>>())
|
||||||
|
.prop_map(|(action, spend_auth_sig)| Self {
|
||||||
|
action,
|
||||||
|
spend_auth_sig,
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Signature<SpendAuth> {
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
(array::uniform32(any::<u8>()), array::uniform32(any::<u8>()))
|
||||||
|
.prop_map(|(r_bytes, s_bytes)| Self {
|
||||||
|
r_bytes,
|
||||||
|
s_bytes,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Flags {
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
(any::<u8>()).prop_map(Self::from_bits_truncate).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy = BoxedStrategy<Self>;
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
//! Orchard shielded data for `V5` `Transaction`s.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
amount::Amount,
|
||||||
|
block::MAX_BLOCK_BYTES,
|
||||||
|
orchard::{tree, Action},
|
||||||
|
primitives::{
|
||||||
|
redpallas::{Binding, Signature, SpendAuth},
|
||||||
|
Halo2Proof,
|
||||||
|
},
|
||||||
|
serialization::{
|
||||||
|
AtLeastOne, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cmp::{Eq, PartialEq},
|
||||||
|
fmt::Debug,
|
||||||
|
io,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A bundle of [`Action`] descriptions and signature data.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub struct ShieldedData {
|
||||||
|
/// The orchard flags for this transaction.
|
||||||
|
pub flags: Flags,
|
||||||
|
/// The net value of Orchard spends minus outputs.
|
||||||
|
pub value_balance: Amount,
|
||||||
|
/// The shared anchor for all `Spend`s in this transaction.
|
||||||
|
pub shared_anchor: tree::Root,
|
||||||
|
/// The aggregated zk-SNARK proof for all the actions in this transaction.
|
||||||
|
pub proof: Halo2Proof,
|
||||||
|
/// The Orchard Actions.
|
||||||
|
pub actions: AtLeastOne<AuthorizedAction>,
|
||||||
|
/// A signature on the transaction `sighash`.
|
||||||
|
pub binding_sig: Signature<Binding>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An authorized action description.
|
||||||
|
///
|
||||||
|
/// Every authorized Orchard `Action` must have a corresponding `SpendAuth` signature.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub struct AuthorizedAction {
|
||||||
|
/// The action description of this Action.
|
||||||
|
pub action: Action,
|
||||||
|
/// The spend signature.
|
||||||
|
pub spend_auth_sig: Signature<SpendAuth>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthorizedAction {
|
||||||
|
/// Split out the action and the signature for V5 transaction
|
||||||
|
/// serialization.
|
||||||
|
pub fn into_parts(self) -> (Action, Signature<SpendAuth>) {
|
||||||
|
(self.action, self.spend_auth_sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the action and the spend auth sig from V5 transaction
|
||||||
|
/// deserialization.
|
||||||
|
pub fn from_parts(action: Action, spend_auth_sig: Signature<SpendAuth>) -> AuthorizedAction {
|
||||||
|
AuthorizedAction {
|
||||||
|
action,
|
||||||
|
spend_auth_sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The size of a single Action
|
||||||
|
///
|
||||||
|
/// Actions are 5 * 32 + 580 + 80 bytes so the total size of each Action is 820 bytes.
|
||||||
|
/// [7.5 Action Description Encoding and Consensus][ps]
|
||||||
|
///
|
||||||
|
/// [ps] https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus
|
||||||
|
pub const ACTION_SIZE: u64 = 5 * 32 + 580 + 80;
|
||||||
|
|
||||||
|
/// The size of a single Signature<SpendAuth>
|
||||||
|
///
|
||||||
|
/// Each Signature is 64 bytes.
|
||||||
|
/// [7.1 Transaction Encoding and Consensus][ps]
|
||||||
|
///
|
||||||
|
/// [ps] https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus
|
||||||
|
pub const SPEND_AUTH_SIG_SIZE: u64 = 64;
|
||||||
|
|
||||||
|
/// The size of a single AuthorizedAction
|
||||||
|
///
|
||||||
|
/// Each serialized `Action` has a corresponding `Signature<SpendAuth>`.
|
||||||
|
pub const AUTHORIZED_ACTION_SIZE: u64 = ACTION_SIZE + SPEND_AUTH_SIG_SIZE;
|
||||||
|
|
||||||
|
/// The maximum number of orchard actions in a valid Zcash on-chain transaction V5.
|
||||||
|
///
|
||||||
|
/// If a transaction contains more actions than can fit in maximally large block, it might be
|
||||||
|
/// valid on the network and in the mempool, but it can never be mined into a block. So
|
||||||
|
/// rejecting these large edge-case transactions can never break consensus.
|
||||||
|
impl TrustedPreallocate for Action {
|
||||||
|
fn max_allocation() -> u64 {
|
||||||
|
// Since a serialized Vec<AuthorizedAction> uses at least one byte for its length,
|
||||||
|
// and the signature is required,
|
||||||
|
// a valid max allocation can never exceed this size
|
||||||
|
(MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrustedPreallocate for Signature<SpendAuth> {
|
||||||
|
fn max_allocation() -> u64 {
|
||||||
|
// Each signature must have a corresponding action.
|
||||||
|
Action::max_allocation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Per-Transaction flags for Orchard.
|
||||||
|
///
|
||||||
|
/// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
|
||||||
|
/// the relevant note spending and creation consensus rules.
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct Flags: u8 {
|
||||||
|
/// Enable spending non-zero valued Orchard notes.
|
||||||
|
///
|
||||||
|
/// "the `enableSpendsOrchard` flag, if present, MUST be 0 for coinbase transactions"
|
||||||
|
const ENABLE_SPENDS = 0b00000001;
|
||||||
|
/// Enable creating new non-zero valued Orchard notes.
|
||||||
|
const ENABLE_OUTPUTS = 0b00000010;
|
||||||
|
// Reserved, zeros (bits 2 .. 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZcashSerialize for Flags {
|
||||||
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
writer.write_u8(self.bits())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZcashDeserialize for Flags {
|
||||||
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
|
let flags = Flags::from_bits(reader.read_u8()?).unwrap();
|
||||||
|
|
||||||
|
Ok(flags)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
mod preallocate;
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! Tests for trusted preallocation during deserialization.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
block::MAX_BLOCK_BYTES,
|
||||||
|
orchard::{
|
||||||
|
shielded_data::{ACTION_SIZE, AUTHORIZED_ACTION_SIZE},
|
||||||
|
Action, AuthorizedAction,
|
||||||
|
},
|
||||||
|
primitives::redpallas::{Signature, SpendAuth},
|
||||||
|
serialization::{tests::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize},
|
||||||
|
};
|
||||||
|
|
||||||
|
use proptest::{prelude::*, proptest};
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
/// Confirm that each `AuthorizedAction` takes exactly AUTHORIZED_ACTION_SIZE
|
||||||
|
/// bytes when serialized.
|
||||||
|
#[test]
|
||||||
|
fn authorized_action_size_is_small_enough(authorized_action in <AuthorizedAction>::arbitrary_with(())) {
|
||||||
|
let (action, spend_auth_sig) = authorized_action.into_parts();
|
||||||
|
let mut serialized_len = action.zcash_serialize_to_vec().expect("Serialization to vec must succeed").len();
|
||||||
|
serialized_len += spend_auth_sig.zcash_serialize_to_vec().expect("Serialization to vec must succeed").len();
|
||||||
|
prop_assert!(serialized_len as u64 == AUTHORIZED_ACTION_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify trusted preallocation for `AuthorizedAction` and its split fields
|
||||||
|
#[test]
|
||||||
|
fn authorized_action_max_allocation_is_big_enough(authorized_action in <AuthorizedAction>::arbitrary_with(())) {
|
||||||
|
let (action, spend_auth_sig) = authorized_action.into_parts();
|
||||||
|
|
||||||
|
let (
|
||||||
|
smallest_disallowed_vec_len,
|
||||||
|
smallest_disallowed_serialized_len,
|
||||||
|
largest_allowed_vec_len,
|
||||||
|
largest_allowed_serialized_len,
|
||||||
|
) = max_allocation_is_big_enough(action);
|
||||||
|
|
||||||
|
// Calculate the actual size of all required Action fields
|
||||||
|
prop_assert!((smallest_disallowed_serialized_len as u64)/ACTION_SIZE*AUTHORIZED_ACTION_SIZE >= MAX_BLOCK_BYTES);
|
||||||
|
prop_assert!((largest_allowed_serialized_len as u64)/ACTION_SIZE*AUTHORIZED_ACTION_SIZE <= MAX_BLOCK_BYTES);
|
||||||
|
|
||||||
|
// Check the serialization limits for `Action`
|
||||||
|
prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Action::max_allocation());
|
||||||
|
prop_assert!((largest_allowed_vec_len as u64) == Action::max_allocation());
|
||||||
|
prop_assert!((largest_allowed_serialized_len as u64) <= MAX_BLOCK_BYTES);
|
||||||
|
|
||||||
|
let (
|
||||||
|
smallest_disallowed_vec_len,
|
||||||
|
_smallest_disallowed_serialized_len,
|
||||||
|
largest_allowed_vec_len,
|
||||||
|
largest_allowed_serialized_len,
|
||||||
|
) = max_allocation_is_big_enough(spend_auth_sig);
|
||||||
|
|
||||||
|
// Check the serialization limits for `Signature::<SpendAuth>`
|
||||||
|
prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Signature::<SpendAuth>::max_allocation());
|
||||||
|
prop_assert!((largest_allowed_vec_len as u64) == Signature::<SpendAuth>::max_allocation());
|
||||||
|
prop_assert!((largest_allowed_serialized_len as u64) <= MAX_BLOCK_BYTES);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::{collections::VecDeque, fmt};
|
use std::{collections::VecDeque, fmt, io};
|
||||||
|
|
||||||
use bitvec::prelude::*;
|
use bitvec::prelude::*;
|
||||||
use halo2::arithmetic::FieldExt;
|
use halo2::arithmetic::FieldExt;
|
||||||
|
@ -23,6 +23,8 @@ use proptest_derive::Arbitrary;
|
||||||
|
|
||||||
use super::{commitment::NoteCommitment, sinsemilla::*};
|
use super::{commitment::NoteCommitment, sinsemilla::*};
|
||||||
|
|
||||||
|
use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize};
|
||||||
|
|
||||||
const MERKLE_DEPTH: usize = 32;
|
const MERKLE_DEPTH: usize = 32;
|
||||||
|
|
||||||
/// MerkleCRH^Orchard Hash Function
|
/// MerkleCRH^Orchard Hash Function
|
||||||
|
@ -88,6 +90,34 @@ impl fmt::Debug for Root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for Root {
|
||||||
|
fn from(bytes: [u8; 32]) -> Root {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Root> for [u8; 32] {
|
||||||
|
fn from(root: Root) -> Self {
|
||||||
|
root.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZcashSerialize for Root {
|
||||||
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
writer.write_all(&<[u8; 32]>::from(*self)[..])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZcashDeserialize for Root {
|
||||||
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
|
let anchor = reader.read_32_bytes()?.into();
|
||||||
|
|
||||||
|
Ok(anchor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Orchard Note Commitment Tree
|
/// Orchard Note Commitment Tree
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
struct NoteCommitmentTree {
|
struct NoteCommitmentTree {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
|
use crate::serialization::{
|
||||||
|
zcash_serialize_bytes, SerializationError, ZcashDeserialize, ZcashSerialize,
|
||||||
|
};
|
||||||
|
|
||||||
/// An encoding of a Halo2 proof, as used in [Zcash][halo2].
|
/// An encoding of a Halo2 proof, as used in [Zcash][halo2].
|
||||||
///
|
///
|
||||||
|
@ -21,18 +23,16 @@ impl fmt::Debug for Halo2Proof {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZcashSerialize for Halo2Proof {
|
impl ZcashSerialize for Halo2Proof {
|
||||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
|
||||||
writer.write_all(&self.0[..])?;
|
zcash_serialize_bytes(&self.0, writer)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZcashDeserialize for Halo2Proof {
|
impl ZcashDeserialize for Halo2Proof {
|
||||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
let mut bytes = Vec::new();
|
let proof = Vec::zcash_deserialize(&mut reader)?;
|
||||||
reader.read_to_end(&mut bytes)?;
|
|
||||||
|
|
||||||
Ok(Self(bytes))
|
Ok(Self(proof))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
|
|
@ -45,12 +45,12 @@ pub type Randomizer = pallas::Scalar;
|
||||||
pub trait SigType: private::Sealed {}
|
pub trait SigType: private::Sealed {}
|
||||||
|
|
||||||
/// A type variable corresponding to Zcash's `BindingSig`.
|
/// A type variable corresponding to Zcash's `BindingSig`.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
pub enum Binding {}
|
pub enum Binding {}
|
||||||
impl SigType for Binding {}
|
impl SigType for Binding {}
|
||||||
|
|
||||||
/// A type variable corresponding to Zcash's `SpendAuthSig`.
|
/// A type variable corresponding to Zcash's `SpendAuthSig`.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
pub enum SpendAuth {}
|
pub enum SpendAuth {}
|
||||||
impl SigType for SpendAuth {}
|
impl SigType for SpendAuth {}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||||
// - Deirdre Connolly <deirdre@zfnd.org>
|
// - Deirdre Connolly <deirdre@zfnd.org>
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::{io, marker::PhantomData};
|
||||||
|
|
||||||
use super::SigType;
|
use super::SigType;
|
||||||
|
|
||||||
|
use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize};
|
||||||
|
|
||||||
/// A RedPallas signature.
|
/// A RedPallas signature.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct Signature<T: SigType> {
|
pub struct Signature<T: SigType> {
|
||||||
pub(crate) r_bytes: [u8; 32],
|
pub(crate) r_bytes: [u8; 32],
|
||||||
pub(crate) s_bytes: [u8; 32],
|
pub(crate) s_bytes: [u8; 32],
|
||||||
|
@ -43,3 +44,16 @@ impl<T: SigType> From<Signature<T>> for [u8; 64] {
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: SigType> ZcashSerialize for Signature<T> {
|
||||||
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
writer.write_all(&<[u8; 64]>::from(*self)[..])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SigType> ZcashDeserialize for Signature<T> {
|
||||||
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
|
Ok(reader.read_64_bytes()?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,11 +14,11 @@ use crate::{
|
||||||
},
|
},
|
||||||
PerSpendAnchor, SharedAnchor,
|
PerSpendAnchor, SharedAnchor,
|
||||||
},
|
},
|
||||||
serialization::{TrustedPreallocate, ZcashSerialize},
|
serialization::{tests::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize},
|
||||||
};
|
};
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use std::{cmp::max, convert::TryInto};
|
use std::cmp::max;
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
/// Confirm that each `Spend<PerSpendAnchor>` takes exactly
|
/// Confirm that each `Spend<PerSpendAnchor>` takes exactly
|
||||||
|
@ -208,37 +208,3 @@ proptest! {
|
||||||
prop_assert!((largest_allowed_vec_len as u64) <= max(SpendPrefixInTransactionV5::max_allocation(), OutputPrefixInTransactionV5::max_allocation()));
|
prop_assert!((largest_allowed_vec_len as u64) <= max(SpendPrefixInTransactionV5::max_allocation(), OutputPrefixInTransactionV5::max_allocation()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the following calculations on `item`:
|
|
||||||
/// smallest_disallowed_vec_len
|
|
||||||
/// smallest_disallowed_serialized_len
|
|
||||||
/// largest_allowed_vec_len
|
|
||||||
/// largest_allowed_serialized_len
|
|
||||||
fn max_allocation_is_big_enough<T>(item: T) -> (usize, usize, usize, usize)
|
|
||||||
where
|
|
||||||
T: TrustedPreallocate + ZcashSerialize + Clone,
|
|
||||||
{
|
|
||||||
let max_allocation: usize = T::max_allocation().try_into().unwrap();
|
|
||||||
let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1);
|
|
||||||
for _ in 0..(max_allocation + 1) {
|
|
||||||
smallest_disallowed_vec.push(item.clone());
|
|
||||||
}
|
|
||||||
let smallest_disallowed_serialized = smallest_disallowed_vec
|
|
||||||
.zcash_serialize_to_vec()
|
|
||||||
.expect("Serialization to vec must succeed");
|
|
||||||
let smallest_disallowed_vec_len = smallest_disallowed_vec.len();
|
|
||||||
|
|
||||||
// Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency)
|
|
||||||
smallest_disallowed_vec.pop();
|
|
||||||
let largest_allowed_vec = smallest_disallowed_vec;
|
|
||||||
let largest_allowed_serialized = largest_allowed_vec
|
|
||||||
.zcash_serialize_to_vec()
|
|
||||||
.expect("Serialization to vec must succeed");
|
|
||||||
|
|
||||||
(
|
|
||||||
smallest_disallowed_vec_len,
|
|
||||||
smallest_disallowed_serialized.len(),
|
|
||||||
largest_allowed_vec.len(),
|
|
||||||
largest_allowed_serialized.len(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ pub use zcash_deserialize::{
|
||||||
ZcashDeserialize, ZcashDeserializeInto,
|
ZcashDeserialize, ZcashDeserializeInto,
|
||||||
};
|
};
|
||||||
pub use zcash_serialize::{
|
pub use zcash_serialize::{
|
||||||
zcash_serialize_bytes_external_count, zcash_serialize_external_count, ZcashSerialize,
|
zcash_serialize_bytes, zcash_serialize_bytes_external_count, zcash_serialize_external_count,
|
||||||
MAX_PROTOCOL_MESSAGE_LEN,
|
ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
pub mod tests;
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
mod preallocate;
|
mod preallocate;
|
||||||
mod prop;
|
mod prop;
|
||||||
|
|
||||||
|
pub use preallocate::max_allocation_is_big_enough;
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
use proptest::{collection::size_range, prelude::*};
|
use proptest::{collection::size_range, prelude::*};
|
||||||
|
|
||||||
use std::matches;
|
use std::{convert::TryInto, matches};
|
||||||
|
|
||||||
use crate::serialization::{
|
use crate::serialization::{
|
||||||
zcash_deserialize::MAX_U8_ALLOCATION, SerializationError, ZcashDeserialize, ZcashSerialize,
|
zcash_deserialize::MAX_U8_ALLOCATION, SerializationError, TrustedPreallocate, ZcashDeserialize,
|
||||||
MAX_PROTOCOL_MESSAGE_LEN,
|
ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow direct serialization of Vec<u8> for these tests. We don't usually
|
// Allow direct serialization of Vec<u8> for these tests. We don't usually
|
||||||
|
@ -19,6 +19,40 @@ impl ZcashSerialize for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the following calculations on `item`:
|
||||||
|
/// smallest_disallowed_vec_len
|
||||||
|
/// smallest_disallowed_serialized_len
|
||||||
|
/// largest_allowed_vec_len
|
||||||
|
/// largest_allowed_serialized_len
|
||||||
|
pub fn max_allocation_is_big_enough<T>(item: T) -> (usize, usize, usize, usize)
|
||||||
|
where
|
||||||
|
T: TrustedPreallocate + ZcashSerialize + Clone,
|
||||||
|
{
|
||||||
|
let max_allocation: usize = T::max_allocation().try_into().unwrap();
|
||||||
|
let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1);
|
||||||
|
for _ in 0..(max_allocation + 1) {
|
||||||
|
smallest_disallowed_vec.push(item.clone());
|
||||||
|
}
|
||||||
|
let smallest_disallowed_serialized = smallest_disallowed_vec
|
||||||
|
.zcash_serialize_to_vec()
|
||||||
|
.expect("Serialization to vec must succeed");
|
||||||
|
let smallest_disallowed_vec_len = smallest_disallowed_vec.len();
|
||||||
|
|
||||||
|
// Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency)
|
||||||
|
smallest_disallowed_vec.pop();
|
||||||
|
let largest_allowed_vec = smallest_disallowed_vec;
|
||||||
|
let largest_allowed_serialized = largest_allowed_vec
|
||||||
|
.zcash_serialize_to_vec()
|
||||||
|
.expect("Serialization to vec must succeed");
|
||||||
|
|
||||||
|
(
|
||||||
|
smallest_disallowed_vec_len,
|
||||||
|
smallest_disallowed_serialized.len(),
|
||||||
|
largest_allowed_vec.len(),
|
||||||
|
largest_allowed_serialized.len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
#![proptest_config(ProptestConfig::with_cases(4))]
|
#![proptest_config(ProptestConfig::with_cases(4))]
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub use sapling::FieldNotPresent;
|
||||||
pub use sighash::HashType;
|
pub use sighash::HashType;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block,
|
block, orchard,
|
||||||
parameters::NetworkUpgrade,
|
parameters::NetworkUpgrade,
|
||||||
primitives::{Bctv14Proof, Groth16Proof},
|
primitives::{Bctv14Proof, Groth16Proof},
|
||||||
sapling, sprout, transparent,
|
sapling, sprout, transparent,
|
||||||
|
@ -112,7 +112,8 @@ pub enum Transaction {
|
||||||
outputs: Vec<transparent::Output>,
|
outputs: Vec<transparent::Output>,
|
||||||
/// The sapling shielded data for this transaction, if any.
|
/// The sapling shielded data for this transaction, if any.
|
||||||
sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
||||||
// TODO: The orchard data for this transaction, if any.
|
// The orchard data for this transaction, if any.
|
||||||
|
orchard_shielded_data: Option<orchard::ShieldedData>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,12 @@ use proptest::{arbitrary::any, array, collection::vec, option, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::Amount,
|
amount::Amount,
|
||||||
block,
|
block, orchard,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
primitives::{Bctv14Proof, Groth16Proof, ZkSnarkProof},
|
primitives::{
|
||||||
|
redpallas::{Binding, Signature},
|
||||||
|
Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof,
|
||||||
|
},
|
||||||
sapling, sprout, transparent, LedgerState,
|
sapling, sprout, transparent, LedgerState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,8 +82,8 @@ impl Transaction {
|
||||||
vec(any::<transparent::Output>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
any::<LockTime>(),
|
any::<LockTime>(),
|
||||||
any::<block::Height>(),
|
any::<block::Height>(),
|
||||||
option::of(any::<sapling::ShieldedData<sapling::PerSpendAnchor>>()),
|
|
||||||
option::of(any::<JoinSplitData<Groth16Proof>>()),
|
option::of(any::<JoinSplitData<Groth16Proof>>()),
|
||||||
|
option::of(any::<sapling::ShieldedData<sapling::PerSpendAnchor>>()),
|
||||||
)
|
)
|
||||||
.prop_map(
|
.prop_map(
|
||||||
|(
|
|(
|
||||||
|
@ -88,15 +91,15 @@ impl Transaction {
|
||||||
outputs,
|
outputs,
|
||||||
lock_time,
|
lock_time,
|
||||||
expiry_height,
|
expiry_height,
|
||||||
sapling_shielded_data,
|
|
||||||
joinsplit_data,
|
joinsplit_data,
|
||||||
|
sapling_shielded_data,
|
||||||
)| Transaction::V4 {
|
)| Transaction::V4 {
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
lock_time,
|
lock_time,
|
||||||
expiry_height,
|
expiry_height,
|
||||||
sapling_shielded_data,
|
|
||||||
joinsplit_data,
|
joinsplit_data,
|
||||||
|
sapling_shielded_data,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
|
@ -111,6 +114,7 @@ impl Transaction {
|
||||||
transparent::Input::vec_strategy(ledger_state, 10),
|
transparent::Input::vec_strategy(ledger_state, 10),
|
||||||
vec(any::<transparent::Output>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()),
|
option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()),
|
||||||
|
option::of(any::<orchard::ShieldedData>()),
|
||||||
)
|
)
|
||||||
.prop_map(
|
.prop_map(
|
||||||
|(
|
|(
|
||||||
|
@ -120,6 +124,7 @@ impl Transaction {
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
sapling_shielded_data,
|
sapling_shielded_data,
|
||||||
|
orchard_shielded_data,
|
||||||
)| {
|
)| {
|
||||||
Transaction::V5 {
|
Transaction::V5 {
|
||||||
network_upgrade,
|
network_upgrade,
|
||||||
|
@ -128,6 +133,7 @@ impl Transaction {
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
sapling_shielded_data,
|
sapling_shielded_data,
|
||||||
|
orchard_shielded_data,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -323,6 +329,58 @@ impl Arbitrary for sapling::TransferData<SharedAnchor> {
|
||||||
type Strategy = BoxedStrategy<Self>;
|
type Strategy = BoxedStrategy<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for orchard::ShieldedData {
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
(
|
||||||
|
any::<orchard::shielded_data::Flags>(),
|
||||||
|
any::<Amount>(),
|
||||||
|
any::<orchard::tree::Root>(),
|
||||||
|
any::<Halo2Proof>(),
|
||||||
|
vec(any::<orchard::shielded_data::AuthorizedAction>(), 1..10),
|
||||||
|
any::<Signature<Binding>>(),
|
||||||
|
)
|
||||||
|
.prop_map(
|
||||||
|
|(flags, value_balance, shared_anchor, proof, actions, binding_sig)| Self {
|
||||||
|
flags,
|
||||||
|
value_balance,
|
||||||
|
shared_anchor,
|
||||||
|
proof,
|
||||||
|
actions: actions
|
||||||
|
.try_into()
|
||||||
|
.expect("arbitrary vector size range produces at least one action"),
|
||||||
|
binding_sig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Signature<Binding> {
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
(vec(any::<u8>(), 64))
|
||||||
|
.prop_filter_map(
|
||||||
|
"zero Signature::<Binding> values are invalid",
|
||||||
|
|sig_bytes| {
|
||||||
|
let mut b = [0u8; 64];
|
||||||
|
b.copy_from_slice(sig_bytes.as_slice());
|
||||||
|
if b == [0u8; 64] {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Signature::<Binding>::from(b))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
impl Arbitrary for Transaction {
|
impl Arbitrary for Transaction {
|
||||||
type Parameters = LedgerState;
|
type Parameters = LedgerState;
|
||||||
|
|
||||||
|
@ -372,6 +430,7 @@ pub fn transaction_to_fake_v5(
|
||||||
lock_time: *lock_time,
|
lock_time: *lock_time,
|
||||||
expiry_height: block::Height(0),
|
expiry_height: block::Height(0),
|
||||||
sapling_shielded_data: None,
|
sapling_shielded_data: None,
|
||||||
|
orchard_shielded_data: None,
|
||||||
},
|
},
|
||||||
V2 {
|
V2 {
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -385,6 +444,7 @@ pub fn transaction_to_fake_v5(
|
||||||
lock_time: *lock_time,
|
lock_time: *lock_time,
|
||||||
expiry_height: block::Height(0),
|
expiry_height: block::Height(0),
|
||||||
sapling_shielded_data: None,
|
sapling_shielded_data: None,
|
||||||
|
orchard_shielded_data: None,
|
||||||
},
|
},
|
||||||
V3 {
|
V3 {
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -399,6 +459,7 @@ pub fn transaction_to_fake_v5(
|
||||||
lock_time: *lock_time,
|
lock_time: *lock_time,
|
||||||
expiry_height: *expiry_height,
|
expiry_height: *expiry_height,
|
||||||
sapling_shielded_data: None,
|
sapling_shielded_data: None,
|
||||||
|
orchard_shielded_data: None,
|
||||||
},
|
},
|
||||||
V4 {
|
V4 {
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -417,6 +478,7 @@ pub fn transaction_to_fake_v5(
|
||||||
.clone()
|
.clone()
|
||||||
.map(sapling_shielded_v4_to_fake_v5)
|
.map(sapling_shielded_v4_to_fake_v5)
|
||||||
.flatten(),
|
.flatten(),
|
||||||
|
orchard_shielded_data: None,
|
||||||
},
|
},
|
||||||
v5 @ V5 { .. } => v5.clone(),
|
v5 @ V5 { .. } => v5.clone(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,15 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
amount,
|
||||||
block::MAX_BLOCK_BYTES,
|
block::MAX_BLOCK_BYTES,
|
||||||
parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID},
|
parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID},
|
||||||
primitives::{Groth16Proof, ZkSnarkProof},
|
primitives::{
|
||||||
|
redpallas::{Binding, Signature, SpendAuth},
|
||||||
|
Groth16Proof, Halo2Proof, ZkSnarkProof,
|
||||||
|
},
|
||||||
serialization::{
|
serialization::{
|
||||||
zcash_deserialize_external_count, zcash_serialize_external_count, ReadZcashExt,
|
zcash_deserialize_external_count, zcash_serialize_external_count, AtLeastOne, ReadZcashExt,
|
||||||
SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize,
|
SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize,
|
||||||
ZcashDeserializeInto, ZcashSerialize,
|
ZcashDeserializeInto, ZcashSerialize,
|
||||||
},
|
},
|
||||||
|
@ -245,6 +249,115 @@ impl ZcashDeserialize for Option<sapling::ShieldedData<SharedAnchor>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ZcashSerialize for Option<orchard::ShieldedData> {
|
||||||
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
match self {
|
||||||
|
None => {
|
||||||
|
// nActionsOrchard
|
||||||
|
writer.write_compactsize(0)?;
|
||||||
|
// We don't need to write anything else here.
|
||||||
|
// "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
|
||||||
|
// proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
|
||||||
|
// https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus notes of the second
|
||||||
|
// table, section sign.
|
||||||
|
}
|
||||||
|
Some(orchard_shielded_data) => {
|
||||||
|
orchard_shielded_data.zcash_serialize(&mut writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZcashSerialize for orchard::ShieldedData {
|
||||||
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
// Split the AuthorizedAction
|
||||||
|
let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
|
||||||
|
.actions
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(orchard::AuthorizedAction::into_parts)
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
// nActionsOrchard and vActionsOrchard
|
||||||
|
actions.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
// flagsOrchard
|
||||||
|
self.flags.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
// valueBalanceOrchard
|
||||||
|
self.value_balance.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
// anchorOrchard
|
||||||
|
self.shared_anchor.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
// sizeProofsOrchard and proofsOrchard
|
||||||
|
self.proof.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
// vSpendAuthSigsOrchard
|
||||||
|
zcash_serialize_external_count(&sigs, &mut writer)?;
|
||||||
|
|
||||||
|
// bindingSigOrchard
|
||||||
|
self.binding_sig.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't split ShieldedData out of Option<ShieldedData> deserialization,
|
||||||
|
// because the counts are read along with the arrays.
|
||||||
|
impl ZcashDeserialize for Option<orchard::ShieldedData> {
|
||||||
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
|
// nActionsOrchard and vActionsOrchard
|
||||||
|
let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// "sizeProofsOrchard ... [is] present if and only if nActionsOrchard > 0"
|
||||||
|
// https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
|
||||||
|
if actions.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flagsOrchard
|
||||||
|
let flags: orchard::Flags = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// valueBalanceOrchard
|
||||||
|
let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// anchorOrchard
|
||||||
|
let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// sizeProofsOrchard and proofsOrchard
|
||||||
|
let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// vSpendAuthSigsOrchard
|
||||||
|
let sigs: Vec<Signature<SpendAuth>> =
|
||||||
|
zcash_deserialize_external_count(actions.len(), &mut reader)?;
|
||||||
|
|
||||||
|
// bindingSigOrchard
|
||||||
|
let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
|
// Create the AuthorizedAction from deserialized parts
|
||||||
|
let authorized_actions: Vec<orchard::AuthorizedAction> = actions
|
||||||
|
.into_iter()
|
||||||
|
.zip(sigs.into_iter())
|
||||||
|
.map(|(action, spend_auth_sig)| {
|
||||||
|
orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
|
||||||
|
|
||||||
|
Ok(Some(orchard::ShieldedData {
|
||||||
|
flags,
|
||||||
|
value_balance,
|
||||||
|
shared_anchor,
|
||||||
|
proof,
|
||||||
|
actions,
|
||||||
|
binding_sig,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZcashSerialize for Transaction {
|
impl ZcashSerialize for Transaction {
|
||||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
// Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES.
|
// Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES.
|
||||||
|
@ -374,6 +487,7 @@ impl ZcashSerialize for Transaction {
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
sapling_shielded_data,
|
sapling_shielded_data,
|
||||||
|
orchard_shielded_data,
|
||||||
} => {
|
} => {
|
||||||
// Transaction V5 spec:
|
// Transaction V5 spec:
|
||||||
// https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
|
// https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
|
||||||
|
@ -401,10 +515,7 @@ impl ZcashSerialize for Transaction {
|
||||||
sapling_shielded_data.zcash_serialize(&mut writer)?;
|
sapling_shielded_data.zcash_serialize(&mut writer)?;
|
||||||
|
|
||||||
// orchard
|
// orchard
|
||||||
// TODO: Parse orchard into structs
|
orchard_shielded_data.zcash_serialize(&mut writer)?;
|
||||||
// In the meantime, to keep the transaction valid, we add `0`
|
|
||||||
// as the nActionsOrchard field
|
|
||||||
writer.write_compactsize(0)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -544,14 +655,7 @@ impl ZcashDeserialize for Transaction {
|
||||||
let sapling_shielded_data = (&mut reader).zcash_deserialize_into()?;
|
let sapling_shielded_data = (&mut reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
// orchard
|
// orchard
|
||||||
// TODO: Parse orchard into structs
|
let orchard_shielded_data = (&mut reader).zcash_deserialize_into()?;
|
||||||
// In the meantime, check the orchard section is just `0`
|
|
||||||
let empty_orchard_section = reader.read_compactsize()?;
|
|
||||||
if empty_orchard_section != 0 {
|
|
||||||
return Err(SerializationError::Parse(
|
|
||||||
"expected orchard section to be just a zero",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Transaction::V5 {
|
Ok(Transaction::V5 {
|
||||||
network_upgrade,
|
network_upgrade,
|
||||||
|
@ -560,6 +664,7 @@ impl ZcashDeserialize for Transaction {
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
sapling_shielded_data,
|
sapling_shielded_data,
|
||||||
|
orchard_shielded_data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
(_, _) => Err(SerializationError::Parse("bad tx header")),
|
(_, _) => Err(SerializationError::Parse("bad tx header")),
|
||||||
|
|
|
@ -109,6 +109,7 @@ fn empty_v5_round_trip() {
|
||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
outputs: Vec::new(),
|
outputs: Vec::new(),
|
||||||
sapling_shielded_data: None,
|
sapling_shielded_data: None,
|
||||||
|
orchard_shielded_data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = tx.zcash_serialize_to_vec().expect("tx should serialize");
|
let data = tx.zcash_serialize_to_vec().expect("tx should serialize");
|
||||||
|
|
Loading…
Reference in New Issue