Skip to content

Commit

Permalink
add unsigned transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
jowparks committed Jan 9, 2024
1 parent e3d54ed commit 0b5ed42
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 0 deletions.
1 change: 1 addition & 0 deletions ironfish-rust/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub mod burns;
pub mod mints;
pub mod outputs;
pub mod spends;
pub mod unsigned;

mod utils;
mod value_balances;
Expand Down
274 changes: 274 additions & 0 deletions ironfish-rust/src/transaction/unsigned.rs
Original file line number Diff line number Diff line change
@@ -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<UnsignedSpendDescription>,

/// List of outputs, or output notes that have been created.
outputs: Vec<OutputDescription>,

/// List of mint descriptions
mints: Vec<UnsignedMintDescription>,

/// List of burn descriptions
burns: Vec<BurnDescription>,

/// 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<R: io::Read>(mut reader: R) -> Result<Self, IronfishError> {
let version = TransactionVersion::read(&mut reader)?;
let num_spends = reader.read_u64::<LittleEndian>()?;
let num_outputs = reader.read_u64::<LittleEndian>()?;
let num_mints = reader.read_u64::<LittleEndian>()?;
let num_burns = reader.read_u64::<LittleEndian>()?;
let fee = reader.read_i64::<LittleEndian>()?;
let expiration = reader.read_u32::<LittleEndian>()?;
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<W: io::Write>(&self, mut writer: W) -> Result<(), IronfishError> {
self.version.write(&mut writer)?;
writer.write_u64::<LittleEndian>(self.spends.len() as u64)?;
writer.write_u64::<LittleEndian>(self.outputs.len() as u64)?;
writer.write_u64::<LittleEndian>(self.mints.len() as u64)?;
writer.write_u64::<LittleEndian>(self.burns.len() as u64)?;
writer.write_i64::<LittleEndian>(self.fee)?;
writer.write_u32::<LittleEndian>(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<String, SigningCommitment>,
) -> Result<(jubjub::Fr, Vec<u8>), 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<String, String>,
) -> Result<Transaction, IronfishError> {
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::<Identifier, SignatureShare>::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::<LittleEndian>(self.expiration)?;
hasher.write_i64::<LittleEndian>(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)
}
}

0 comments on commit 0b5ed42

Please sign in to comment.