From 0b5ed42b988ec4549323a26852ef10bd63b7cd6f Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 9 Jan 2024 14:17:47 -0800 Subject: [PATCH] add unsigned transaction --- ironfish-rust/src/transaction/mod.rs | 1 + ironfish-rust/src/transaction/unsigned.rs | 274 ++++++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 ironfish-rust/src/transaction/unsigned.rs 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/unsigned.rs b/ironfish-rust/src/transaction/unsigned.rs new file mode 100644 index 0000000000..84d8fb526a --- /dev/null +++ b/ironfish-rust/src/transaction/unsigned.rs @@ -0,0 +1,274 @@ +use std::io; + +use byteorder::{ReadBytesExt, LittleEndian}; +use ironfish_zkp::redjubjub::{Signature, self}; + +use crate::{OutputDescription, errors::IronfishError, serializing::read_scalar}; + +use super::{TransactionVersion, spends::UnsignedSpendDescription, mints::UnsignedMintDescription, burns::BurnDescription}; + + +#[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(()) + } + + pub fn coordinator_signing_package( + &self, + native_commitments: HashMap, + ) -> Result<(jubjub::Fr, Vec), IronfishError> { + let mut commitments = BTreeMap::new(); + for (identifier, signing_commitment) in native_commitments { + commitments.insert( + Identifier::deserialize(&hex_to_bytes(&identifier)?).unwrap(), + SigningCommitments::new( + NonceCommitment::deserialize(hex_to_bytes(&signing_commitment.hiding)?) + .unwrap(), + NonceCommitment::deserialize(hex_to_bytes(&signing_commitment.binding)?) + .unwrap(), + ), + ); + } + + // Create the transaction signature hash + let data_to_sign = self.transaction_signature_hash()?; + + // Coordinator generates randomized params on the fly + Ok(( + self.public_key_randomness, + frost::SigningPackage::new(commitments, &data_to_sign) + .serialize() + .unwrap(), + )) + } + + pub fn post_frost_aggregate( + &mut self, + public_key_package: &str, + authorizing_signing_package_str: &str, + authorizing_signature_shares_hashmap: HashMap, + ) -> Result { + let pubkeys = + PublicKeyPackage::deserialize(&hex_to_vec_bytes(public_key_package)?).unwrap(); + let authorizing_signing_package = + SigningPackage::deserialize(&hex_to_vec_bytes(authorizing_signing_package_str)?) + .unwrap(); + let authorizing_signature_shares = BTreeMap::::from_iter( + authorizing_signature_shares_hashmap.iter().map(|(k, v)| { + ( + Identifier::deserialize(&hex_to_bytes(k).unwrap()).unwrap(), + SignatureShare::deserialize(hex_to_bytes(v).unwrap()).unwrap(), + ) + }), + ); + + // Create the transaction signature hash + let data_to_sign = self.transaction_signature_hash()?; + + let randomizer = Randomizer::deserialize(&self.public_key_randomness.to_bytes()).unwrap(); + let randomized_params = + RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer); + + let authorizing_group_signature = aggregate( + &authorizing_signing_package, + &authorizing_signature_shares, + &pubkeys, + &randomized_params, + ) + .unwrap(); + + // Verify the signature with the public keys + let verify_signature = randomized_params + .randomized_verifying_key() + .verify(&data_to_sign, &authorizing_group_signature); + + assert!(verify_signature.is_ok()); + + let signature = { Signature::read(&mut authorizing_group_signature.serialize().as_ref())? }; + + // Sign spends now that we have the data needed to be signed + let mut spend_descriptions = Vec::with_capacity(self.spends.len()); + for spend in self.spends.drain(0..) { + spend_descriptions.push(spend.sign_frost(signature).unwrap()); + } + + // Sign mints now that we have the data needed to be signed + let mut mint_descriptions = Vec::with_capacity(self.mints.len()); + for mint in self.mints.drain(0..) { + mint_descriptions.push(mint.sign_frost(signature).unwrap()); + } + + let transaction = Transaction { + version: self.version, + expiration: self.expiration, + fee: self.fee, + spends: spend_descriptions, + outputs: self.outputs.clone(), + mints: mint_descriptions, + burns: self.burns.clone(), + binding_signature: self.binding_signature, + randomized_public_key: redjubjub::PublicKey(self.randomized_public_key.0), + }; + let transactions = [&transaction]; + + Ok(transaction) + } + + /// 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) + } +}