From b3332db3e969c86eb699164c286b61f205c434fd Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Nov 2019 12:12:45 +0000 Subject: [PATCH] Add TZEs to transaction format --- zcash_primitives/Cargo.toml | 1 + zcash_primitives/src/serialize.rs | 6 +- .../src/transaction/components.rs | 75 +++++++++++++++++ zcash_primitives/src/transaction/mod.rs | 84 +++++++++++++++---- 4 files changed, 149 insertions(+), 17 deletions(-) diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 37e58969a..40a69ff75 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -36,6 +36,7 @@ ripemd160 = { version = "0.9", optional = true } secp256k1 = { version = "0.19", optional = true } sha2 = "0.9" subtle = "2.2.1" +zcash_extensions_api = { version = "0.0", path = "../zcash_extensions_api" } [dev-dependencies] criterion = "0.3" diff --git a/zcash_primitives/src/serialize.rs b/zcash_primitives/src/serialize.rs index 4e0fb9338..5e56fbb3d 100644 --- a/zcash_primitives/src/serialize.rs +++ b/zcash_primitives/src/serialize.rs @@ -3,10 +3,10 @@ use std::io::{self, Read, Write}; const MAX_SIZE: usize = 0x02000000; -struct CompactSize; +pub(crate) struct CompactSize; impl CompactSize { - fn read(mut reader: R) -> io::Result { + pub(crate) fn read(mut reader: R) -> io::Result { let flag = reader.read_u8()?; match if flag < 253 { Ok(flag as usize) @@ -43,7 +43,7 @@ impl CompactSize { } } - fn write(mut writer: W, size: usize) -> io::Result<()> { + pub(crate) fn write(mut writer: W, size: usize) -> io::Result<()> { match size { s if s < 253 => writer.write_u8(s as u8), s if s <= 0xFFFF => { diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 0929eda51..ad0219a8f 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -4,9 +4,11 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::PrimeField; use group::GroupEncoding; use std::io::{self, Read, Write}; +use zcash_extensions_api::transparent as tze; use crate::legacy::Script; use crate::redjubjub::{PublicKey, Signature}; +use crate::serialize::{CompactSize, Vector}; pub mod amount; pub use self::amount::Amount; @@ -116,6 +118,79 @@ impl TxOut { } } +#[derive(Debug)] +pub struct TzeIn { + pub prevout: OutPoint, + pub witness: tze::Witness, +} + +impl TzeIn { + pub fn read(mut reader: &mut R) -> io::Result { + let prevout = OutPoint::read(&mut reader)?; + + let extension_id = CompactSize::read(&mut reader)?; + let mode = CompactSize::read(&mut reader)?; + let payload = Vector::read(&mut reader, |r| r.read_u8())?; + + Ok(TzeIn { + prevout, + witness: tze::Witness { + extension_id, + mode, + payload, + }, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.prevout.write(&mut writer)?; + + CompactSize::write(&mut writer, self.witness.extension_id)?; + CompactSize::write(&mut writer, self.witness.mode)?; + Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b)) + } +} + +#[derive(Debug)] +pub struct TzeOut { + pub value: Amount, + pub precondition: tze::Precondition, +} + +impl TzeOut { + pub fn read(mut reader: &mut R) -> io::Result { + let value = { + let mut tmp = [0; 8]; + reader.read_exact(&mut tmp)?; + Amount::from_nonnegative_i64_le_bytes(tmp) + } + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?; + + let extension_id = CompactSize::read(&mut reader)?; + let mode = CompactSize::read(&mut reader)?; + let payload = Vector::read(&mut reader, |r| r.read_u8())?; + + Ok(TzeOut { + value, + precondition: tze::Precondition { + extension_id, + mode, + payload, + }, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.value.to_i64_le_bytes())?; + + CompactSize::write(&mut writer, self.precondition.extension_id)?; + CompactSize::write(&mut writer, self.precondition.mode)?; + Vector::write(&mut writer, &self.precondition.payload, |w, b| { + w.write_u8(*b) + }) + } +} + pub struct SpendDescription { pub cv: jubjub::ExtendedPoint, pub anchor: bls12_381::Scalar, diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 24b812703..42cf8fc31 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -19,12 +19,16 @@ mod tests; pub use self::sighash::{signature_hash, signature_hash_data, SIGHASH_ALL}; -use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut}; +use self::components::{ + Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut, +}; const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270; const OVERWINTER_TX_VERSION: u32 = 3; const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085; const SAPLING_TX_VERSION: u32 = 4; +const FUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF; +const FUTURE_TX_VERSION: u32 = 0x0000FFFF; #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct TxId(pub [u8; 32]); @@ -64,6 +68,8 @@ pub struct TransactionData { pub version_group_id: u32, pub vin: Vec, pub vout: Vec, + pub tze_inputs: Vec, + pub tze_outputs: Vec, pub lock_time: u32, pub expiry_height: u32, pub value_balance: Amount, @@ -85,6 +91,8 @@ impl std::fmt::Debug for TransactionData { version_group_id = {:?}, vin = {:?}, vout = {:?}, + tze_inputs = {:?}, + tze_outputs = {:?}, lock_time = {:?}, expiry_height = {:?}, value_balance = {:?}, @@ -98,6 +106,8 @@ impl std::fmt::Debug for TransactionData { self.version_group_id, self.vin, self.vout, + self.tze_inputs, + self.tze_outputs, self.lock_time, self.expiry_height, self.value_balance, @@ -118,6 +128,29 @@ impl TransactionData { version_group_id: SAPLING_VERSION_GROUP_ID, vin: vec![], vout: vec![], + tze_inputs: vec![], + tze_outputs: vec![], + lock_time: 0, + expiry_height: 0, + value_balance: Amount::zero(), + shielded_spends: vec![], + shielded_outputs: vec![], + joinsplits: vec![], + joinsplit_pubkey: None, + joinsplit_sig: None, + binding_sig: None, + } + } + + pub fn nu4() -> Self { + TransactionData { + overwintered: true, + version: FUTURE_TX_VERSION, + version_group_id: FUTURE_VERSION_GROUP_ID, + vin: vec![], + vout: vec![], + tze_inputs: vec![], + tze_outputs: vec![], lock_time: 0, expiry_height: 0, value_balance: Amount::zero(), @@ -178,7 +211,11 @@ impl Transaction { let is_sapling_v4 = overwintered && version_group_id == SAPLING_VERSION_GROUP_ID && version == SAPLING_TX_VERSION; - if overwintered && !(is_overwinter_v3 || is_sapling_v4) { + let has_tze = overwintered + && version_group_id == FUTURE_VERSION_GROUP_ID + && version == FUTURE_TX_VERSION; + + if overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Unknown transaction format", @@ -187,14 +224,21 @@ impl Transaction { let vin = Vector::read(&mut reader, TxIn::read)?; let vout = Vector::read(&mut reader, TxOut::read)?; + let (tze_inputs, tze_outputs) = if has_tze { + let wi = Vector::read(&mut reader, TzeIn::read)?; + let wo = Vector::read(&mut reader, TzeOut::read)?; + (wi, wo) + } else { + (vec![], vec![]) + }; let lock_time = reader.read_u32::()?; - let expiry_height = if is_overwinter_v3 || is_sapling_v4 { + let expiry_height = if is_overwinter_v3 || is_sapling_v4 || has_tze { reader.read_u32::()? } else { 0 }; - let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 { + let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 || has_tze { let vb = { let mut tmp = [0; 8]; reader.read_exact(&mut tmp)?; @@ -226,12 +270,13 @@ impl Transaction { (vec![], None, None) }; - let binding_sig = - if is_sapling_v4 && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) { - Some(Signature::read(&mut reader)?) - } else { - None - }; + let binding_sig = if (is_sapling_v4 || has_tze) + && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) + { + Some(Signature::read(&mut reader)?) + } else { + None + }; Transaction::from_data(TransactionData { overwintered, @@ -239,6 +284,8 @@ impl Transaction { version_group_id, vin, vout, + tze_inputs, + tze_outputs, lock_time, expiry_height, value_balance, @@ -263,7 +310,10 @@ impl Transaction { let is_sapling_v4 = self.overwintered && self.version_group_id == SAPLING_VERSION_GROUP_ID && self.version == SAPLING_TX_VERSION; - if self.overwintered && !(is_overwinter_v3 || is_sapling_v4) { + let is_nu4_v5 = self.overwintered + && self.version_group_id == FUTURE_VERSION_GROUP_ID + && self.version == FUTURE_TX_VERSION; + if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || is_nu4_v5) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Unknown transaction format", @@ -272,12 +322,16 @@ impl Transaction { Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?; Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?; + if is_nu4_v5 { + Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?; + Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?; + } writer.write_u32::(self.lock_time)?; - if is_overwinter_v3 || is_sapling_v4 { + if is_overwinter_v3 || is_sapling_v4 || is_nu4_v5 { writer.write_u32::(self.expiry_height)?; } - if is_sapling_v4 { + if is_sapling_v4 || is_nu4_v5 { writer.write_all(&self.value_balance.to_i64_le_bytes())?; Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?; Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?; @@ -322,7 +376,9 @@ impl Transaction { } } - if is_sapling_v4 && !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty()) { + if (is_sapling_v4 || is_nu4_v5) + && !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty()) + { match self.binding_sig { Some(sig) => sig.write(&mut writer)?, None => {