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 10, 2024
1 parent ec4e605 commit d58daeb
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
26 changes: 26 additions & 0 deletions ironfish-rust/src/transaction/mints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
assets::asset::Asset,
errors::{IronfishError, IronfishErrorKind},
sapling_bls12::SAPLING,
serializing::read_scalar,
transaction::TransactionVersion,
PublicAddress, SaplingKey,
};
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -144,6 +146,30 @@ impl UnsignedMintDescription {

Ok(self.description)
}

pub fn read<R: io::Read>(
mut reader: R,
version: TransactionVersion,
) -> Result<Self, IronfishError> {
let public_key_randomness = read_scalar(&mut reader)?;
let description = MintDescription::read(&mut reader, version)?;

Ok(UnsignedMintDescription {
public_key_randomness,
description,
})
}

pub fn write<W: io::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
Expand Down
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
18 changes: 18 additions & 0 deletions ironfish-rust/src/transaction/spends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -192,6 +193,23 @@ impl UnsignedSpendDescription {

Ok(self.description)
}

pub fn read<R: io::Read>(mut reader: R) -> Result<Self, IronfishError> {
let public_key_randomness = read_scalar(&mut reader)?;
let description = SpendDescription::read(&mut reader)?;

Ok(UnsignedSpendDescription {
public_key_randomness,
description,
})
}

pub fn write<W: io::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
Expand Down
181 changes: 181 additions & 0 deletions ironfish-rust/src/transaction/unsigned.rs
Original file line number Diff line number Diff line change
@@ -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<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(())
}

/// 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 d58daeb

Please sign in to comment.