Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add unsigned transaction, unsigned mint, unsigned spend serde #4522

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}