diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 7dc715ea18..e87f76545e 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -21,6 +21,7 @@ use crate::{ assets::asset::Asset, errors::{IronfishError, IronfishErrorKind}, sapling_bls12::SAPLING, + serializing::read_scalar, transaction::TransactionVersion, PublicAddress, SaplingKey, }; @@ -98,6 +99,7 @@ impl MintBuilder { /// The publicly visible values of a mint description in a transaction. /// These fields get serialized when computing the transaction hash and are used /// to prove that the creator has knowledge of these values. +#[derive(Clone)] pub struct UnsignedMintDescription { /// Used to add randomness to signature generation. Referred to as `ar` in /// the literature. @@ -144,6 +146,30 @@ impl UnsignedMintDescription { Ok(self.description) } + + pub fn read( + mut reader: R, + version: TransactionVersion, + ) -> Result { + let public_key_randomness = read_scalar(&mut reader)?; + let description = MintDescription::read(&mut reader, version)?; + + Ok(UnsignedMintDescription { + public_key_randomness, + description, + }) + } + + pub fn write( + &self, + mut writer: W, + version: TransactionVersion, + ) -> Result<(), IronfishError> { + writer.write_all(&self.public_key_randomness.to_bytes())?; + self.description.write(&mut writer, version)?; + + Ok(()) + } } /// This description represents an action to increase the supply of an existing diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index 3cab77f43f..85dee442fd 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -51,6 +51,7 @@ pub mod burns; pub mod mints; pub mod outputs; pub mod spends; +pub mod unsigned; mod utils; mod value_balances; diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 3e646851c0..c15a5a98e2 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -145,6 +145,7 @@ impl SpendBuilder { } } +#[derive(Clone)] pub struct UnsignedSpendDescription { /// Used to add randomness to signature generation without leaking the /// key. Referred to as `ar` in the literature. @@ -192,6 +193,23 @@ impl UnsignedSpendDescription { Ok(self.description) } + + pub fn read(mut reader: R) -> Result { + let public_key_randomness = read_scalar(&mut reader)?; + let description = SpendDescription::read(&mut reader)?; + + Ok(UnsignedSpendDescription { + public_key_randomness, + description, + }) + } + + pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { + writer.write_all(&self.public_key_randomness.to_bytes())?; + self.description.write(&mut writer)?; + + Ok(()) + } } /// The publicly visible value of a spent note. These get serialized to prove diff --git a/ironfish-rust/src/transaction/unsigned.rs b/ironfish-rust/src/transaction/unsigned.rs new file mode 100644 index 0000000000..c516bf7de7 --- /dev/null +++ b/ironfish-rust/src/transaction/unsigned.rs @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use group::GroupEncoding; +use ironfish_zkp::redjubjub::{self, Signature}; +use std::io::{self, Write}; + +use crate::{ + errors::IronfishError, serializing::read_scalar, transaction::Blake2b, OutputDescription, +}; + +use super::{ + burns::BurnDescription, mints::UnsignedMintDescription, spends::UnsignedSpendDescription, + TransactionVersion, SIGNATURE_HASH_PERSONALIZATION, TRANSACTION_SIGNATURE_VERSION, +}; + +#[derive(Clone)] +pub struct UnsignedTransaction { + /// The transaction serialization version. This can be incremented when + /// changes need to be made to the transaction format + version: TransactionVersion, + + /// List of spends, or input notes, that have been destroyed. + spends: Vec, + + /// List of outputs, or output notes that have been created. + outputs: Vec, + + /// List of mint descriptions + mints: Vec, + + /// List of burn descriptions + burns: Vec, + + /// Signature calculated from accumulating randomness with all the spends + /// and outputs when the transaction was created. + binding_signature: Signature, + + /// This is the sequence in the chain the transaction will expire at and be + /// removed from the mempool. A value of 0 indicates the transaction will + /// not expire. + expiration: u32, + + /// Randomized public key of the sender of the Transaction + /// currently this value is the same for all spends[].owner and outputs[].sender + /// This is used during verification of SpendDescriptions and OutputDescriptions, as + /// well as signing of the SpendDescriptions. Referred to as + /// `rk` in the literature Calculated from the authorizing key and + /// the public_key_randomness. + randomized_public_key: redjubjub::PublicKey, + + // TODO: Verify if this is actually okay to store on the unsigned transaction + public_key_randomness: jubjub::Fr, + + /// The balance of total spends - outputs, which is the amount that the miner gets to keep + fee: i64, +} + +impl UnsignedTransaction { + /// Load a Transaction from a Read implementation (e.g: socket, file) + /// This is the main entry-point when reconstructing a serialized transaction + /// for verifying. + pub fn read(mut reader: R) -> Result { + let version = TransactionVersion::read(&mut reader)?; + let num_spends = reader.read_u64::()?; + let num_outputs = reader.read_u64::()?; + let num_mints = reader.read_u64::()?; + let num_burns = reader.read_u64::()?; + let fee = reader.read_i64::()?; + let expiration = reader.read_u32::()?; + let randomized_public_key = redjubjub::PublicKey::read(&mut reader)?; + let public_key_randomness = read_scalar(&mut reader)?; + + let mut spends = Vec::with_capacity(num_spends as usize); + for _ in 0..num_spends { + spends.push(UnsignedSpendDescription::read(&mut reader)?); + } + + let mut outputs = Vec::with_capacity(num_outputs as usize); + for _ in 0..num_outputs { + outputs.push(OutputDescription::read(&mut reader)?); + } + + let mut mints = Vec::with_capacity(num_mints as usize); + for _ in 0..num_mints { + mints.push(UnsignedMintDescription::read(&mut reader, version)?); + } + + let mut burns = Vec::with_capacity(num_burns as usize); + for _ in 0..num_burns { + burns.push(BurnDescription::read(&mut reader)?); + } + + let binding_signature = Signature::read(&mut reader)?; + + Ok(UnsignedTransaction { + version, + fee, + spends, + outputs, + mints, + burns, + binding_signature, + expiration, + randomized_public_key, + public_key_randomness, + }) + } + + /// Store the bytes of this transaction in the given writer. This is used + /// to serialize transactions to file or network + pub fn write(&self, mut writer: W) -> Result<(), IronfishError> { + self.version.write(&mut writer)?; + writer.write_u64::(self.spends.len() as u64)?; + writer.write_u64::(self.outputs.len() as u64)?; + writer.write_u64::(self.mints.len() as u64)?; + writer.write_u64::(self.burns.len() as u64)?; + writer.write_i64::(self.fee)?; + writer.write_u32::(self.expiration)?; + writer.write_all(&self.randomized_public_key.0.to_bytes())?; + writer.write_all(&self.public_key_randomness.to_bytes())?; + + for spend in self.spends.iter() { + spend.write(&mut writer)?; + } + + for output in self.outputs.iter() { + output.write(&mut writer)?; + } + + for mints in self.mints.iter() { + mints.write(&mut writer, self.version)?; + } + + for burns in self.burns.iter() { + burns.write(&mut writer)?; + } + + self.binding_signature.write(&mut writer)?; + + Ok(()) + } + + /// Calculate a hash of the transaction data. This hash was signed by the + /// private keys when the transaction was constructed, and will now be + /// reconstructed to verify the signature. + pub fn transaction_signature_hash(&self) -> Result<[u8; 32], IronfishError> { + let mut hasher = Blake2b::new() + .hash_length(32) + .personal(SIGNATURE_HASH_PERSONALIZATION) + .to_state(); + hasher.update(TRANSACTION_SIGNATURE_VERSION); + self.version.write(&mut hasher)?; + hasher.write_u32::(self.expiration)?; + hasher.write_i64::(self.fee)?; + hasher.write_all(&self.randomized_public_key.0.to_bytes())?; + + for spend in self.spends.iter() { + spend.description.serialize_signature_fields(&mut hasher)?; + } + + for output in self.outputs.iter() { + output.serialize_signature_fields(&mut hasher)?; + } + + for mint in self.mints.iter() { + mint.description + .serialize_signature_fields(&mut hasher, self.version)?; + } + + for burn in self.burns.iter() { + burn.serialize_signature_fields(&mut hasher)?; + } + + let mut hash_result = [0; 32]; + hash_result[..].clone_from_slice(hasher.finalize().as_ref()); + Ok(hash_result) + } +}