From 41021c65ad8275c11a1a988a95f83e20e4685636 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Fri, 20 Dec 2019 16:16:20 -0800 Subject: [PATCH] Implement transaction (de)serialization. --- zebra-chain/src/transaction/serialize.rs | 457 ++++++++++++++++++++++- 1 file changed, 450 insertions(+), 7 deletions(-) diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 02c245b8..534d4dc0 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -1,20 +1,463 @@ //! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the //! transaction types, so that all of the serialization logic is in one place. -use std::io; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{self, Read}; -use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}; +use crate::proofs::ZkSnarkProof; +use crate::serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, +}; +use crate::types::Script; -use super::Transaction; +use super::*; + +const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270; +const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085; + +impl ZcashSerialize for OutPoint { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_all(&self.hash.0[..])?; + writer.write_u32::(self.index)?; + Ok(()) + } +} + +impl ZcashDeserialize for OutPoint { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(OutPoint { + hash: TransactionHash(reader.read_32_bytes()?), + index: reader.read_u32::()?, + }) + } +} + +impl ZcashSerialize for TransparentInput { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + self.previous_output.zcash_serialize(&mut writer)?; + self.signature_script.zcash_serialize(&mut writer)?; + writer.write_u32::(self.sequence)?; + Ok(()) + } +} + +impl ZcashDeserialize for TransparentInput { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(TransparentInput { + previous_output: OutPoint::zcash_deserialize(&mut reader)?, + signature_script: Script::zcash_deserialize(&mut reader)?, + sequence: reader.read_u32::()?, + }) + } +} + +impl ZcashSerialize for TransparentOutput { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_u64::(self.value)?; + self.pk_script.zcash_serialize(&mut writer)?; + Ok(()) + } +} + +impl ZcashDeserialize for TransparentOutput { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(TransparentOutput { + value: reader.read_u64::()?, + pk_script: Script::zcash_deserialize(&mut reader)?, + }) + } +} + +impl ZcashSerialize for JoinSplit

{ + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_u64::(self.vpub_old)?; + writer.write_u64::(self.vpub_new)?; + writer.write_all(&self.anchor[..])?; + writer.write_all(&self.nullifiers[0][..])?; + writer.write_all(&self.nullifiers[1][..])?; + writer.write_all(&self.commitments[0][..])?; + writer.write_all(&self.commitments[1][..])?; + writer.write_all(&self.ephemeral_key[..])?; + writer.write_all(&self.random_seed[..])?; + writer.write_all(&self.vmacs[0][..])?; + writer.write_all(&self.vmacs[1][..])?; + self.zkproof.zcash_serialize(&mut writer)?; + assert_eq!(self.enc_ciphertexts[0].len(), 601); // XXX remove when type is refined + writer.write_all(&self.enc_ciphertexts[0][..])?; + assert_eq!(self.enc_ciphertexts[1].len(), 601); // XXX remove when type is refined + writer.write_all(&self.enc_ciphertexts[1][..])?; + Ok(()) + } +} + +impl ZcashDeserialize for JoinSplit

{ + fn zcash_deserialize(mut reader: R) -> Result { + Ok(JoinSplit::

{ + vpub_old: reader.read_u64::()?, + vpub_new: reader.read_u64::()?, + anchor: reader.read_32_bytes()?, + nullifiers: [reader.read_32_bytes()?, reader.read_32_bytes()?], + commitments: [reader.read_32_bytes()?, reader.read_32_bytes()?], + ephemeral_key: reader.read_32_bytes()?, + random_seed: reader.read_32_bytes()?, + vmacs: [reader.read_32_bytes()?, reader.read_32_bytes()?], + zkproof: P::zcash_deserialize(&mut reader)?, + // XXX this is a little ugly but will disappear when we refine types + enc_ciphertexts: [ + { + let mut bytes = Vec::new(); + (&mut reader).take(601).read_to_end(&mut bytes)?; + bytes + }, + { + let mut bytes = Vec::new(); + (&mut reader).take(601).read_to_end(&mut bytes)?; + bytes + }, + ], + }) + } +} + +impl ZcashSerialize for JoinSplitData

{ + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_compactsize(self.joinsplits().count() as u64)?; + for joinsplit in self.joinsplits() { + joinsplit.zcash_serialize(&mut writer)?; + } + writer.write_all(&self.pub_key[..])?; + // XXX very ugly, this happens because we used a [u64; 8] instead of + // [u8; 64] to get trait impls and it will disappear when we refine to + // Zcash-flavored Ed25519. + writer.write_all( + &{ + use byteorder::ByteOrder; + let mut bytes = [0u8; 64]; + LittleEndian::write_u64_into(&self.sig[..], &mut bytes); + bytes + }[..], + )?; + Ok(()) + } +} + +impl ZcashDeserialize for Option> { + fn zcash_deserialize(mut reader: R) -> Result { + let num_joinsplits = reader.read_compactsize()?; + match num_joinsplits { + 0 => Ok(None), + n => { + let first = JoinSplit::zcash_deserialize(&mut reader)?; + let mut rest = Vec::with_capacity((n - 1) as usize); + for _ in 0..(n - 1) { + rest.push(JoinSplit::zcash_deserialize(&mut reader)?); + } + let pub_key = reader.read_32_bytes()?; + // XXX this is horrible, see above, will be removed with type refinement + let sig = { + use byteorder::ByteOrder; + let mut bytes = [0u8; 64]; + reader.read_exact(&mut bytes[..])?; + let mut u64s = [0u64; 8]; + LittleEndian::read_u64_into(&bytes, &mut u64s[..]); + u64s + }; + Ok(Some(JoinSplitData { + first, + rest, + pub_key, + sig, + })) + } + } + } +} + +impl ZcashSerialize for SpendDescription { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_all(&self.cv[..])?; + writer.write_all(&self.anchor.0[..])?; + writer.write_all(&self.nullifier[..])?; + writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; + self.zkproof.zcash_serialize(&mut writer)?; + writer.write_all(&<[u8; 64]>::from(self.spend_auth_sig)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for SpendDescription { + fn zcash_deserialize(mut reader: R) -> Result { + use crate::note_commitment_tree::SaplingNoteTreeRootHash; + Ok(SpendDescription { + cv: reader.read_32_bytes()?, + anchor: SaplingNoteTreeRootHash(reader.read_32_bytes()?), + nullifier: reader.read_32_bytes()?, + rk: reader.read_32_bytes()?.into(), + zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, + spend_auth_sig: reader.read_64_bytes()?.into(), + }) + } +} + +impl ZcashSerialize for OutputDescription { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_all(&self.cv[..])?; + writer.write_all(&self.cmu[..])?; + writer.write_all(&self.ephemeral_key[..])?; + // XXX remove this assertion when types are refined + assert_eq!(self.enc_ciphertext.len(), 580); + writer.write_all(&self.enc_ciphertext[..])?; + // XXX remove this assertion when types are refined + assert_eq!(self.out_ciphertext.len(), 80); + // XXX very ugly, this happens because we used a [u64; 10] instead of + // [u8; 80] to get trait impls and it will disappear when we refine to + // a note ciphertext type + writer.write_all( + &{ + use byteorder::ByteOrder; + let mut bytes = [0u8; 80]; + LittleEndian::write_u64_into(&self.out_ciphertext[..], &mut bytes); + bytes + }[..], + )?; + self.zkproof.zcash_serialize(&mut writer)?; + Ok(()) + } +} + +impl ZcashDeserialize for OutputDescription { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(OutputDescription { + cv: reader.read_32_bytes()?, + cmu: reader.read_32_bytes()?, + ephemeral_key: reader.read_32_bytes()?, + enc_ciphertext: { + // XXX this will disappear when we refine types + let mut bytes = Vec::new(); + (&mut reader).take(580).read_to_end(&mut bytes)?; + bytes + }, + out_ciphertext: { + // XXX this is horrible, see above, will be removed with type refinement + use byteorder::ByteOrder; + let mut bytes = [0u8; 80]; + reader.read_exact(&mut bytes[..])?; + let mut u64s = [0u64; 10]; + LittleEndian::read_u64_into(&bytes, &mut u64s[..]); + u64s + }, + zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, + }) + } +} impl ZcashSerialize for Transaction { - fn zcash_serialize(&self, _writer: W) -> Result<(), SerializationError> { - unimplemented!(); + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + match self { + Transaction::V1 { + inputs, + outputs, + lock_time, + } => { + writer.write_u32::(1)?; + inputs.zcash_serialize(&mut writer)?; + outputs.zcash_serialize(&mut writer)?; + lock_time.zcash_serialize(&mut writer)?; + } + Transaction::V2 { + inputs, + outputs, + lock_time, + joinsplit_data, + } => { + writer.write_u32::(2)?; + inputs.zcash_serialize(&mut writer)?; + outputs.zcash_serialize(&mut writer)?; + lock_time.zcash_serialize(&mut writer)?; + match joinsplit_data { + // Write 0 for nJoinSplits to signal no JoinSplitData. + None => writer.write_compactsize(0)?, + Some(jsd) => jsd.zcash_serialize(&mut writer)?, + } + } + Transaction::V3 { + inputs, + outputs, + lock_time, + expiry_height, + joinsplit_data, + } => { + // Write version 3 and set the fOverwintered bit. + writer.write_u32::(3 | (1 << 31))?; + writer.write_u32::(OVERWINTER_VERSION_GROUP_ID)?; + inputs.zcash_serialize(&mut writer)?; + outputs.zcash_serialize(&mut writer)?; + lock_time.zcash_serialize(&mut writer)?; + writer.write_u32::(expiry_height.0)?; + match joinsplit_data { + // Write 0 for nJoinSplits to signal no JoinSplitData. + None => writer.write_compactsize(0)?, + Some(jsd) => jsd.zcash_serialize(&mut writer)?, + } + } + Transaction::V4 { + inputs, + outputs, + lock_time, + expiry_height, + value_balance, + shielded_data, + joinsplit_data, + } => { + // Write version 4 and set the fOverwintered bit. + writer.write_u32::(4 | (1 << 31))?; + writer.write_u32::(SAPLING_VERSION_GROUP_ID)?; + inputs.zcash_serialize(&mut writer)?; + outputs.zcash_serialize(&mut writer)?; + lock_time.zcash_serialize(&mut writer)?; + writer.write_u32::(expiry_height.0)?; + writer.write_i64::(*value_balance)?; + + // The previous match arms serialize in one go, because the + // internal structure happens to nicely line up with the + // serialized structure. However, this is not possible for + // version 4 transactions, as the binding_sig for the + // ShieldedData is placed at the end of the transaction. So + // instead we have to interleave serialization of the + // ShieldedData and the JoinSplitData. + + match shielded_data { + None => { + // Signal no shielded spends and no shielded outputs. + writer.write_compactsize(0)?; + writer.write_compactsize(0)?; + } + Some(shielded_data) => { + writer.write_compactsize(shielded_data.spends().count() as u64)?; + for spend in shielded_data.spends() { + spend.zcash_serialize(&mut writer)?; + } + writer.write_compactsize(shielded_data.outputs().count() as u64)?; + for output in shielded_data.outputs() { + output.zcash_serialize(&mut writer)?; + } + } + } + + match joinsplit_data { + None => writer.write_compactsize(0)?, + Some(jsd) => jsd.zcash_serialize(&mut writer)?, + } + + match shielded_data { + Some(sd) => writer.write_all(&<[u8; 64]>::from(sd.binding_sig)[..])?, + None => {} + } + } + } + Ok(()) } } impl ZcashDeserialize for Transaction { - fn zcash_deserialize(_reader: R) -> Result { - unimplemented!(); + fn zcash_deserialize(mut reader: R) -> Result { + let (version, overwintered) = { + const LOW_31_BITS: u32 = (1 << 31) - 1; + let header = reader.read_u32::()?; + (header & LOW_31_BITS, header >> 31 != 0) + }; + + // The overwintered flag MUST NOT be set for version 1 and 2 transactions. + match (version, overwintered) { + (1, false) => Ok(Transaction::V1 { + inputs: Vec::zcash_deserialize(&mut reader)?, + outputs: Vec::zcash_deserialize(&mut reader)?, + lock_time: LockTime::zcash_deserialize(&mut reader)?, + }), + (2, false) => { + // Version 2 transactions use Sprout-on-BCTV14. + type OptV2JSD = Option>; + Ok(Transaction::V2 { + inputs: Vec::zcash_deserialize(&mut reader)?, + outputs: Vec::zcash_deserialize(&mut reader)?, + lock_time: LockTime::zcash_deserialize(&mut reader)?, + joinsplit_data: OptV2JSD::zcash_deserialize(&mut reader)?, + }) + } + (3, true) => { + let id = reader.read_u32::()?; + if id != OVERWINTER_VERSION_GROUP_ID { + return Err(SerializationError::Parse( + "expected OVERWINTER_VERSION_GROUP_ID", + )); + } + // Version 3 transactions use Sprout-on-BCTV14. + type OptV3JSD = Option>; + Ok(Transaction::V3 { + inputs: Vec::zcash_deserialize(&mut reader)?, + outputs: Vec::zcash_deserialize(&mut reader)?, + lock_time: LockTime::zcash_deserialize(&mut reader)?, + expiry_height: BlockHeight(reader.read_u32::()?), + joinsplit_data: OptV3JSD::zcash_deserialize(&mut reader)?, + }) + } + (4, true) => { + let id = reader.read_u32::()?; + if id != SAPLING_VERSION_GROUP_ID { + return Err(SerializationError::Parse( + "expected SAPLING_VERSION_GROUP_ID", + )); + } + // Version 4 transactions use Sprout-on-Groth16. + type OptV4JSD = Option>; + + // The previous match arms deserialize in one go, because the + // internal structure happens to nicely line up with the + // serialized structure. However, this is not possible for + // version 4 transactions, as the binding_sig for the + // ShieldedData is placed at the end of the transaction. So + // instead we have to pull the component parts out manually and + // then assemble them. + + let inputs = Vec::zcash_deserialize(&mut reader)?; + let outputs = Vec::zcash_deserialize(&mut reader)?; + let lock_time = LockTime::zcash_deserialize(&mut reader)?; + let expiry_height = BlockHeight(reader.read_u32::()?); + let value_balance = reader.read_i64::()?; + let mut shielded_spends = Vec::zcash_deserialize(&mut reader)?; + let mut shielded_outputs = Vec::zcash_deserialize(&mut reader)?; + let joinsplit_data = OptV4JSD::zcash_deserialize(&mut reader)?; + + use futures::future::Either::*; + let shielded_data = if shielded_spends.len() > 0 { + Some(ShieldedData { + first: Left(shielded_spends.remove(0)), + rest_spends: shielded_spends, + rest_outputs: shielded_outputs, + binding_sig: reader.read_64_bytes()?.into(), + }) + } else if shielded_outputs.len() > 0 { + Some(ShieldedData { + first: Right(shielded_outputs.remove(0)), + rest_spends: shielded_spends, + rest_outputs: shielded_outputs, + binding_sig: reader.read_64_bytes()?.into(), + }) + } else { + None + }; + + Ok(Transaction::V4 { + inputs, + outputs, + lock_time, + expiry_height, + value_balance, + shielded_data, + joinsplit_data, + }) + } + (_, _) => Err(SerializationError::Parse("bad tx header")), + } } }