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

Transaction Builder #45

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod encoding;
pub mod seed;
pub mod signing;
pub mod spendpolicy;
pub mod transactionbuilder;
pub mod transactions;
pub mod unlock_conditions;

Expand Down
2 changes: 1 addition & 1 deletion src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ mod tests {
};
assert!(key
.public_key()
.verify(&sig_hash.as_ref(), &signature.signature));
.verify(sig_hash.as_ref(), &signature.signature));
}
}
}
152 changes: 152 additions & 0 deletions src/transactionbuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use crate::{
encoding::SerializeError,
signing::SigningState,
transactions::{CoveredFields, SiacoinOutput},
Address, Currency, Hash256,
};
use thiserror::Error;

use crate::{
signing::PrivateKey,
transactions::{SiacoinInput, SiacoinOutputID, Transaction},
unlock_conditions::{UnlockConditions, UnlockKey},
};

#[derive(Error, Debug)]
pub enum Error {
#[error("Transaction serialization error: {0}")]
Serialize(#[from] SerializeError),
}

#[derive(Debug, PartialEq)]
pub struct TransactionBuilder {
transaction: Transaction,
}

// NOTE: instead of using the builder pattern, we could also return a ToSign
// when we add inputs and leave it to the caller to track the ToSign instances
// they want to use for signing later.
pub struct ToSign {
parent_id: Hash256,
public_key_index: u64,
}

/// TODO: Missing features
/// - Add change output
/// - Add FileContract
/// - Add FileContractdRevision
/// - Add StorageProof
/// - Add SiafundInput
/// - Add SiafundOutput
/// - Add MinerFee
/// - Add ArbitraryData
/// - Add Signature

impl Default for TransactionBuilder {
fn default() -> Self {
TransactionBuilder {
transaction: Default::default(),
}
}
}

impl TransactionBuilder {
/// Creates a new transaction builder
pub fn new() -> TransactionBuilder {
Default::default()
}

pub fn add_change_address(self, _address: Address) -> Self {
unimplemented!(
"if inputs exceed outputs, add an output that send the change to the provided address"
)
}

pub fn add_miner_fee(mut self, fee: Currency) -> Self {
self.transaction.miner_fees.push(fee);
self
}

/// Adds a siacoin input with a simple 'spendable by public key' unlock
/// condition
pub fn add_siacoin_input(mut self, parent_id: SiacoinOutputID, public_key: UnlockKey) -> Self {
self.transaction.siacoin_inputs.push(SiacoinInput {
parent_id,
unlock_conditions: UnlockConditions {
public_keys: vec![public_key],
timelock: 0,
signatures_required: 1,
},
});
self
}

/// Adds a siacoin output with the given value to the transaction
pub fn add_siacoin_output(mut self, address: Address, value: Currency) -> Self {
self.transaction
.siacoin_outputs
.push(SiacoinOutput { address, value });
self
}

/// Finalizes the transaction, consuming the builder.
pub fn finalize(self) -> Transaction {
self.transaction
}

/// Signs the whole transaction with the provided private key for each input
/// in to_sign. The public_key_index is the index of the public key within
/// the unlock conditions of the input that we sign for.
pub fn sign(
mut self,
state: &SigningState,
to_sign: &[ToSign],
timelock: u64,
private_key: &PrivateKey,
) -> Result<Self, Error> {
// cover everything
let mut covered_fields = CoveredFields::default();
for i in 0..self.transaction.siacoin_inputs.len() {
covered_fields.siacoin_inputs.push(i);
}
for i in 0..self.transaction.siacoin_outputs.len() {
covered_fields.siacoin_outputs.push(i);
}
for i in 0..self.transaction.file_contracts.len() {
covered_fields.file_contracts.push(i);
}
for i in 0..self.transaction.file_contract_revisions.len() {
covered_fields.file_contract_revisions.push(i);
}
for i in 0..self.transaction.storage_proofs.len() {
covered_fields.storage_proofs.push(i);
}
for i in 0..self.transaction.siafund_inputs.len() {
covered_fields.siafund_inputs.push(i);
}
for i in 0..self.transaction.siafund_outputs.len() {
covered_fields.siafund_outputs.push(i);
}
for i in 0..self.transaction.miner_fees.len() {
covered_fields.miner_fees.push(i);
}
for i in 0..self.transaction.arbitrary_data.len() {
covered_fields.arbitrary_data.push(i);
}
for i in 0..self.transaction.signatures.len() {
covered_fields.signatures.push(i);
}
for ts in to_sign {
let signature = self.transaction.sign(
state,
&covered_fields,
ts.parent_id,
ts.public_key_index,
timelock,
private_key,
)?;
self.transaction.signatures.push(signature);
}
Ok(self)
}
}
10 changes: 10 additions & 0 deletions tests/transactionbuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extern crate sia_core;

use sia_core::transactionbuilder::*;
use sia_core::transactions::Transaction;

#[test]
fn test_transactionbuilder_new() {
let transaction = TransactionBuilder::new().finalize();
assert_eq!(transaction, Transaction::default());
}