From cd47a1f963d82e93ecb532bd3e5584a968150b64 Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Thu, 15 Aug 2024 23:16:46 -0700 Subject: [PATCH] move transaction signing to signing state --- src/common.rs | 1 + src/lib.rs | 1 - src/seed.rs | 2 +- src/signing.rs | 333 +++++++++++++----- src/spendpolicy.rs | 810 -------------------------------------------- src/transactions.rs | 421 +---------------------- 6 files changed, 270 insertions(+), 1298 deletions(-) delete mode 100644 src/spendpolicy.rs diff --git a/src/common.rs b/src/common.rs index 0005376..7502641 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,6 +3,7 @@ use core::fmt; use serde::{Deserialize, Serialize}; use std::fmt::Debug; +#[derive(Serialize, Deserialize, PartialEq)] pub struct ChainIndex { pub height: u64, pub id: [u8; 32], diff --git a/src/lib.rs b/src/lib.rs index aaa7dfb..0296965 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ pub mod currency; pub mod encoding; pub mod seed; pub mod signing; -pub mod spendpolicy; pub mod transactions; pub mod unlock_conditions; diff --git a/src/seed.rs b/src/seed.rs index 610c25e..5981edd 100644 --- a/src/seed.rs +++ b/src/seed.rs @@ -160,7 +160,7 @@ mod tests { let seed = Seed::from_mnemonic(PHRASE).unwrap(); for (i, expected) in test_addresses { let pk = seed.private_key(i).public_key(); - assert_eq!(pk.as_ref(), expected[32..].as_ref(), "index {}", i); + assert_eq!(pk.to_string(), format!("ed25519:{}", hex::encode(expected[32..].as_ref())), "index {}", i); } } } diff --git a/src/signing.rs b/src/signing.rs index 5137517..4ab5dee 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -1,57 +1,15 @@ use core::fmt; use std::time::SystemTime; -use crate::{ChainIndex, Hash256, HexParseError}; +use crate::encoding::{to_writer, SerializeError}; +use crate::ImplHashID; +use crate::{transactions::{CoveredFields, Transaction}, ChainIndex, Hash256, HexParseError}; use base64::prelude::*; +use blake2b_simd::Params; use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey}; use serde::{de::Error, Deserialize, Serialize}; -/// An ed25519 public key that can be used to verify a signature -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct PublicKey([u8; 32]); - -impl PublicKey { - const PREFIX: &'static str = "ed25519:"; -} - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize( - &format!("{}{}", Self::PREFIX, &self.to_string()), - serializer, - ) - } else { - <[u8; 32]>::serialize(&self.0, serializer) - } - } -} - -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - let s = s.strip_prefix(Self::PREFIX).ok_or(Error::custom(format!( - "key must have prefix '{}'", - Self::PREFIX - )))?; - let mut pk = [0; 32]; - hex::decode_to_slice(s, &mut pk).map_err(|e| Error::custom(format!("{:?}", e)))?; - Ok(Self::new(pk)) - } else { - Ok(PublicKey(<[u8; 32]>::deserialize(deserializer)?)) - } - } -} - -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - hex::encode(self.0).fmt(f) - } -} +ImplHashID!(PublicKey, "ed25519"); impl PublicKey { pub fn new(buf: [u8; 32]) -> Self { @@ -65,12 +23,6 @@ impl PublicKey { } } -impl AsRef<[u8]> for PublicKey { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - /// An ed25519 private key that can be used to sign a hash #[derive(Debug, PartialEq, Clone)] pub struct PrivateKey([u8; 64]); @@ -121,6 +73,14 @@ impl Drop for PrivateKey { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature([u8; 64]); +impl TryFrom<&[u8]> for Signature { + type Error = core::array::TryFromSliceError; + + fn try_from(buf: &[u8]) -> Result { + <[u8; 64]>::try_from(buf).map(Signature) + } +} + impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { @@ -198,7 +158,7 @@ impl fmt::Display for Signature { write!(f, "sig:{}", hex::encode(self.0)) } } -#[derive(Default, Debug)] +#[derive(Default, Debug, Serialize, Deserialize)] pub struct NetworkHardforks { pub asic_height: u64, @@ -208,6 +168,7 @@ pub struct NetworkHardforks { pub v2_require_height: u64, } +#[derive(Serialize, Deserialize)] pub struct SigningState { pub index: ChainIndex, pub median_timestamp: SystemTime, @@ -227,7 +188,7 @@ impl SigningState { } } - pub fn replay_prefix(&self) -> &[u8] { + fn replay_prefix(&self) -> &[u8] { if self.index.height >= self.hardforks.v2_allow_height { return &[2]; } else if self.index.height >= self.hardforks.foundation_height { @@ -237,6 +198,175 @@ impl SigningState { } &[] } + + fn v1_partial_sig_hash(&self, txn: &Transaction, covered_fields: &CoveredFields) -> Result { + let mut state = Params::new().hash_length(32).to_state(); + + for &i in covered_fields.siacoin_inputs.iter() { + if i >= txn.siacoin_inputs.len() { + return Err(SerializeError::Custom( + "siacoin_inputs index out of bounds".to_string(), + )); + } + state.update(self.replay_prefix()); + to_writer(&mut state, &txn.siacoin_inputs[i])?; + } + + for &i in covered_fields.siacoin_outputs.iter() { + if i >= txn.siacoin_outputs.len() { + return Err(SerializeError::Custom( + "siacoin_outputs index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.siacoin_outputs[i])?; + } + + for &i in covered_fields.file_contracts.iter() { + if i >= txn.file_contracts.len() { + return Err(SerializeError::Custom( + "file_contracts index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.file_contracts[i])?; + } + + for &i in covered_fields.file_contract_revisions.iter() { + if i >= txn.file_contract_revisions.len() { + return Err(SerializeError::Custom( + "file_contract_revisions index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.file_contract_revisions[i])?; + } + + for &i in covered_fields.storage_proofs.iter() { + if i >= txn.storage_proofs.len() { + return Err(SerializeError::Custom( + "storage_proofs index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.storage_proofs[i])?; + } + + for &i in covered_fields.siafund_inputs.iter() { + if i >= txn.siafund_inputs.len() { + return Err(SerializeError::Custom( + "siafund_inputs index out of bounds".to_string(), + )); + } + state.update(self.replay_prefix()); + to_writer(&mut state, &txn.siafund_inputs[i])?; + } + + for &i in covered_fields.siafund_outputs.iter() { + if i >= txn.siafund_outputs.len() { + return Err(SerializeError::Custom( + "siafund_outputs index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.siafund_outputs[i])?; + } + + for &i in covered_fields.miner_fees.iter() { + if i >= txn.miner_fees.len() { + return Err(SerializeError::Custom( + "miner_fees index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.miner_fees[i])?; + } + + for &i in covered_fields.arbitrary_data.iter() { + if i >= txn.arbitrary_data.len() { + return Err(SerializeError::Custom( + "arbitrary_data index out of bounds".to_string(), + )); + } + state.update(&(txn.arbitrary_data[i].len() as u64).to_le_bytes()); + state.update(&txn.arbitrary_data[i]); + } + + for &i in covered_fields.signatures.iter() { + if i >= txn.signatures.len() { + return Err(SerializeError::Custom( + "signatures index out of bounds".to_string(), + )); + } + to_writer(&mut state, &txn.signatures[i])?; + } + + Ok(state.finalize().into()) + } + + fn v1_whole_sig_hash(&self, txn: &Transaction, parent_id: &Hash256, public_key_index: u64, timelock: u64) -> Result { + let mut state = Params::new().hash_length(32).to_state(); + + state.update(&(txn.siacoin_inputs.len() as u64).to_le_bytes()); + for input in txn.siacoin_inputs.iter() { + state.update(self.replay_prefix()); + to_writer(&mut state, input)?; + } + + state.update(&(txn.siacoin_outputs.len() as u64).to_le_bytes()); + for output in txn.siacoin_outputs.iter() { + to_writer(&mut state, output)?; + } + + state.update(&(txn.file_contracts.len() as u64).to_le_bytes()); + for file_contract in txn.file_contracts.iter() { + to_writer(&mut state, file_contract)?; + } + + state.update(&(txn.file_contract_revisions.len() as u64).to_le_bytes()); + for file_contract_revision in txn.file_contract_revisions.iter() { + to_writer(&mut state, file_contract_revision)?; + } + + state.update(&(txn.storage_proofs.len() as u64).to_le_bytes()); + for storage_proof in txn.storage_proofs.iter() { + to_writer(&mut state, storage_proof).unwrap(); + } + + state.update(&(txn.siafund_inputs.len() as u64).to_le_bytes()); + for input in txn.siafund_inputs.iter() { + state.update(self.replay_prefix()); + to_writer(&mut state, input).unwrap(); + } + + state.update(&(txn.siafund_outputs.len() as u64).to_le_bytes()); + for output in txn.siafund_outputs.iter() { + to_writer(&mut state, output)?; + } + + state.update(&(txn.miner_fees.len() as u64).to_le_bytes()); + for fee in txn.miner_fees.iter() { + to_writer(&mut state, &fee)?; + } + + state.update(&(txn.arbitrary_data.len() as u64).to_le_bytes()); + for data in txn.arbitrary_data.iter() { + state.update(&(data.len() as u64).to_le_bytes()); + state.update(data); + } + + to_writer(&mut state, parent_id)?; + state.update(&public_key_index.to_le_bytes()); + state.update(&timelock.to_le_bytes()); + + Ok(state.finalize().into()) + } + + pub fn v1_transaction_sig_hash(&self, txn: &Transaction, sig_index: usize) -> Result { + if txn.signatures.len() <= sig_index { + return Err(SerializeError::Custom("signature index out of bounds".to_string())); + } + + let sig = &txn.signatures[sig_index]; + if sig.covered_fields.whole_transaction { + return self.v1_whole_sig_hash(txn, &sig.parent_id, sig.public_key_index, sig.timelock); + } + self.v1_partial_sig_hash(txn, &sig.covered_fields) + } } #[cfg(test)] @@ -247,7 +377,7 @@ mod tests { encoding::{from_reader, to_bytes}, transactions::{ CoveredFields, FileContract, FileContractID, FileContractRevision, SiacoinInput, - SiacoinOutput, SiafundInput, SiafundOutput, StorageProof, Transaction, + SiacoinOutput, SiafundInput, SiafundOutput, SiacoinOutputID, StorageProof, Transaction, TransactionSignature, }, unlock_conditions::UnlockConditions, @@ -279,9 +409,72 @@ mod tests { assert_eq!(public_key_deserialized, public_key); } + #[test] + fn test_v1_whole_sig_hash() { + let state = SigningState { + index: ChainIndex { + height: 0, + id: [0; 32], + }, + median_timestamp: SystemTime::now(), + hardforks: NetworkHardforks { + asic_height: 0, + foundation_height: 0, + v2_allow_height: 1000, + v2_require_height: 1000, + }, + }; + let pk = PrivateKey::from_seed(&[ + 136, 215, 58, 248, 45, 30, 78, 97, 128, 111, 82, 204, 43, 233, 223, 111, 110, 29, 73, + 157, 52, 25, 242, 96, 131, 16, 187, 22, 232, 107, 17, 205, + ]); + let test_cases = vec![ + ( + Transaction { + siacoin_inputs: vec![ + SiacoinInput{ + parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), + unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), + } + ], + siacoin_outputs: vec![ + SiacoinOutput{ + value: Currency::new(67856467336433871), + address: Address::parse_string("addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69").unwrap(), + } + ], + file_contracts: Vec::new(), + file_contract_revisions: Vec::new(), + storage_proofs: Vec::new(), + siafund_inputs: Vec::new(), + siafund_outputs: Vec::new(), + miner_fees: Vec::new(), + arbitrary_data: Vec::new(), + signatures: vec![ + TransactionSignature{ + parent_id: Hash256::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), + public_key_index: 0, + timelock: 0, + covered_fields: CoveredFields::whole_transaction(), + signature: Signature::default(), + } + ], + }, + "h:a4b1855c546db7ec902237f730717faae96187db8ce9fe139504323a639f731e" + ) + ]; + + for (txn, expected) in test_cases { + let sig_hash = state.v1_transaction_sig_hash(&txn, 0) + .expect("expect tranasction to hash"); + + assert_eq!(sig_hash.to_string(), expected) + } + } + #[test] - fn test_transaction_sign_verify() { - let unsigned_transaction = Transaction { + fn test_v1_transaction_sign_verify() { + let mut unsigned_transaction = Transaction { siacoin_inputs: vec![SiacoinInput { parent_id: Default::default(), unlock_conditions: UnlockConditions { @@ -481,7 +674,7 @@ mod tests { }, ]; - for tc in test_cases { + for (i, tc) in test_cases.iter().enumerate() { // update state state.index.height = tc.height; @@ -508,24 +701,10 @@ mod tests { }; // sign and check signature - let signature = unsigned_transaction - .sign(&state, &covered_fields, Hash256::default(), 1, 100, &key) - .unwrap(); - assert_eq!(signature.signature, tc.signature); - - // manually build the sig_hash and check the signature - let sig_hash = if tc.whole_transaction { - unsigned_transaction - .whole_sig_hash(&state, &Hash256::default(), 1, 100, &Vec::new()) - .unwrap() - } else { - unsigned_transaction - .partial_sig_hash(&state, &covered_fields) - .unwrap() - }; - assert!(key - .public_key() - .verify(&sig_hash.as_ref(), &signature.signature)); + unsigned_transaction.signatures[0].covered_fields = covered_fields; + let sig_hash = state.v1_transaction_sig_hash(&unsigned_transaction, 0).unwrap(); + let signature = key.sign(sig_hash.as_ref()); + assert_eq!(signature, tc.signature, "test case {}", i); } } } diff --git a/src/spendpolicy.rs b/src/spendpolicy.rs deleted file mode 100644 index 2262fea..0000000 --- a/src/spendpolicy.rs +++ /dev/null @@ -1,810 +0,0 @@ -use crate::encoding::to_writer; -use crate::signing::{PublicKey, Signature, SigningState}; -#[allow(deprecated)] -use crate::unlock_conditions::UnlockConditions; -use crate::{Address, Hash256}; -use blake2b_simd::Params; -use core::{fmt, slice::Iter}; -use serde::ser::SerializeTuple; -use serde::Serialize; -use sha2::{Digest, Sha256}; -use std::time::{self, SystemTime}; -use thiserror::Error; - -#[derive(Debug, PartialEq, Error)] -pub enum ValidationError { - #[error("opaque policy")] - OpaquePolicy, - #[error("invalid policy")] - InvalidPolicy, - #[error("invalid signature")] - InvalidSignature, - #[error("invalid preimage")] - InvalidPreimage, - #[error("invalid height")] - InvalidHeight, - #[error("invalid timestamp")] - InvalidTimestamp, - #[error("missing signature")] - MissingSignature, - #[error("missing preimage")] - MissingPreimage, - #[error("threshold not met")] - ThresholdNotMet, -} - -/// A spend policy is a condition or set of conditions that must be met in -/// order to spend a UTXO. -#[derive(Debug, PartialEq, Clone)] -pub enum SpendPolicy { - /// A policy that is only valid after a block height - Above(u64), - /// A policy that is only valid after a timestamp - After(SystemTime), - /// A policy that requires a valid signature from an ed25519 key pair - PublicKey(PublicKey), - /// A policy that requires a valid SHA256 hash preimage - Hash([u8; 32]), - /// A threshold policy that requires n-of-m sub-policies to be met - Threshold(u8, Vec), - /// An opaque policy that is not directly spendable - Opaque(Address), - - /// A set of v1 unlock conditions for compatibility with v1 transactions - #[deprecated] - UnlockConditions(UnlockConditions), -} - -impl SpendPolicy { - fn type_prefix(&self) -> u8 { - match self { - SpendPolicy::Above(_) => 1, - SpendPolicy::After(_) => 2, - SpendPolicy::PublicKey(_) => 3, - SpendPolicy::Hash(_) => 4, - SpendPolicy::Threshold(_, _) => 5, - SpendPolicy::Opaque(_) => 6, - #[allow(deprecated)] - SpendPolicy::UnlockConditions(_) => 7, - } - } - - /// Create a policy that is only valid after a certain block height - pub fn above(height: u64) -> Self { - Self::Above(height) - } - - /// Create a policy that is only valid after a certain timestamp - pub fn after(timestamp: SystemTime) -> Self { - Self::After(timestamp) - } - - /// Create a policy that requires a valid signature from a public key - pub fn public_key(pk: PublicKey) -> Self { - Self::PublicKey(pk) - } - - /// Create a policy that requires a hash preimage - pub fn hash(hash: [u8; 32]) -> Self { - Self::Hash(hash) - } - - /// Create a threshold policy with n-of-m sub-policies - pub fn threshold(n: u8, policies: Vec) -> Self { - for policy in policies.iter() { - #[allow(deprecated)] - if let SpendPolicy::UnlockConditions(_) = policy { - panic!("UnlockConditions are not allowed in a threshold policy"); - } - } - Self::Threshold(n, policies) - } - - /// Create a v1 unlock conditions policy for compatibility with v1 - /// transactions. - #[deprecated] - pub fn unlock_conditions(uc: UnlockConditions) -> Self { - #[allow(deprecated)] - Self::UnlockConditions(uc) - } - - /// Returns the address of the policy. This is a hash of the policy that - /// can be used to receive funds. - pub fn address(&self) -> Address { - #[allow(deprecated)] - if let SpendPolicy::UnlockConditions(uc) = self { - return uc.address(); - } else if let SpendPolicy::Opaque(addr) = self { - return addr.clone(); - } - - let mut state = Params::new().hash_length(32).to_state(); - - state.update("sia/address|".as_bytes()); - - if let SpendPolicy::Threshold(n, of) = self { - let mut opaque = Vec::with_capacity(of.len()); - for policy in of { - opaque.push(SpendPolicy::Opaque(policy.address())) - } - to_writer(&mut state, &SpendPolicy::Threshold(*n, opaque)).unwrap(); - } else { - to_writer(&mut state, self).unwrap(); - } - Address::from(state.finalize().as_bytes()) - } - - /// Verify that the policy is satisfied by the given parameters. - pub fn verify( - &self, - signing_state: &SigningState, - hash: &Hash256, - signatures: &mut Iter<'_, Signature>, - preimages: &mut Iter<'_, Vec>, - ) -> Result<(), ValidationError> { - match self { - SpendPolicy::Above(height) => { - if *height > signing_state.index.height { - Err(ValidationError::InvalidHeight) - } else { - Ok(()) - } - } - SpendPolicy::After(time) => { - if *time > signing_state.median_timestamp { - Err(ValidationError::InvalidTimestamp) - } else { - Ok(()) - } - } - SpendPolicy::PublicKey(pk) => signatures - .next() - .ok_or(ValidationError::MissingSignature) - .and_then(|sig| { - pk.verify(hash.as_ref(), sig) - .then_some(()) - .ok_or(ValidationError::InvalidSignature) - }), - SpendPolicy::Hash(hash) => { - let preimage = preimages.next().ok_or(ValidationError::MissingPreimage)?; - - let mut hasher = Sha256::new(); - hasher.update(preimage); - - let res: [u8; 32] = hasher.finalize().into(); - if res == *hash { - Ok(()) - } else { - Err(ValidationError::InvalidPreimage) - } - } - SpendPolicy::Threshold(n, ref policies) => { - let mut remaining = *n; - for policy in policies { - #[allow(deprecated)] - if let SpendPolicy::UnlockConditions(_) = policy { - return Err(ValidationError::InvalidPolicy); - } - - if policy - .verify(signing_state, hash, signatures, preimages) - .is_err() - { - continue; - } - - remaining -= 1; - if remaining == 0 { - break; - } - } - if remaining == 0 { - Ok(()) - } else { - Err(ValidationError::ThresholdNotMet) - } - } - #[allow(deprecated)] - SpendPolicy::UnlockConditions(uc) => { - if uc.timelock > signing_state.index.height { - return Err(ValidationError::InvalidHeight); - } else if uc.signatures_required > 255 { - return Err(ValidationError::InvalidPolicy); - } - - let mut remaining = uc.signatures_required; - for pk in uc.public_keys.iter() { - let sig = signatures.next().ok_or(ValidationError::MissingSignature)?; - if pk.public_key().verify(hash.as_ref(), sig) { - remaining -= 1; - if remaining == 0 { - break; - } - } else { - return Err(ValidationError::InvalidSignature); - } - } - - if remaining == 0 { - return Ok(()); - } - Err(ValidationError::ThresholdNotMet) - } - SpendPolicy::Opaque(_) => Err(ValidationError::OpaquePolicy), - } - } - - /// Encode the policy to a writer. This is used to handle recursive - /// threshold policies. The version byte is only written for the top-level - /// policy. - fn serialize_policy(&self, s: &mut S) -> Result<(), S::Error> { - s.serialize_element(&self.type_prefix())?; // type prefix - match self { - SpendPolicy::Above(height) => s.serialize_element(height), - SpendPolicy::After(time) => { - s.serialize_element(&time.duration_since(time::UNIX_EPOCH).unwrap().as_secs()) - } - SpendPolicy::PublicKey(pk) => { - let mut arr: [u8; 32] = [0; 32]; - arr.copy_from_slice(pk.as_ref()); - s.serialize_element(&arr) - } - SpendPolicy::Hash(hash) => s.serialize_element(hash), - SpendPolicy::Threshold(n, policies) => { - let prefix = [*n, policies.len() as u8]; - s.serialize_element(&prefix)?; - for policy in policies { - policy.serialize_policy(s)?; - } - Ok(()) - } - SpendPolicy::Opaque(addr) => s.serialize_element(addr), - #[allow(deprecated)] - SpendPolicy::UnlockConditions(uc) => s.serialize_element(uc), - } - } -} - -impl Serialize for SpendPolicy { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - // unknown length since policie are recursive and need custom - // serialize/deserialize implementations anyway. - let mut s = serializer.serialize_tuple(0)?; - s.serialize_element(&1u8)?; // version - self.serialize_policy(&mut s)?; - s.end() - } - } -} - -impl fmt::Display for SpendPolicy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SpendPolicy::Above(height) => write!(f, "above({})", height), - SpendPolicy::After(time) => { - let duration = time - .duration_since(time::UNIX_EPOCH) - .map_err(|_| fmt::Error)?; - write!(f, "after({})", duration.as_secs()) - } - SpendPolicy::PublicKey(pk) => write!(f, "pk(0x{})", hex::encode(pk.as_ref())), - SpendPolicy::Hash(hash) => write!(f, "h(0x{})", hex::encode(hash)), - SpendPolicy::Threshold(n, policies) => { - write!(f, "thresh({},[", n)?; - for (i, policy) in policies.iter().enumerate() { - if i > 0 { - write!(f, ",")?; - } - write!(f, "{}", policy)?; - } - write!(f, "])") - } - SpendPolicy::Opaque(addr) => write!(f, "opaque(0x{})", hex::encode(addr)), - #[allow(deprecated)] - SpendPolicy::UnlockConditions(uc) => { - write!(f, "uc({},{},[", uc.timelock, uc.signatures_required)?; - for (i, pk) in uc.public_keys.iter().enumerate() { - if i > 0 { - write!(f, ",")?; - } - write!(f, "0x{}", hex::encode(pk.public_key().as_ref()))?; - } - write!(f, "])") - } - } - } -} - -/// A policy that has been satisfied by a set of preimages and signatures. -pub struct SatisfiedPolicy { - pub policy: SpendPolicy, - pub preimages: Vec>, - pub signatures: Vec, -} - -impl SatisfiedPolicy { - /// Create a new satisfied policy from a policy, preimages, and signatures. - pub fn new(policy: SpendPolicy, preimages: Vec>, signatures: Vec) -> Self { - Self { - policy, - preimages, - signatures, - } - } - - /// Verify that the policy is satisfied by the given parameters. - /// This is a convenience method that calls `verify` on the policy. - pub fn verify(&self, state: &SigningState, sig_hash: &Hash256) -> Result<(), ValidationError> { - self.policy.verify( - state, - sig_hash, - &mut self.signatures.iter(), - &mut self.preimages.iter(), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::signing::{NetworkHardforks, PrivateKey}; - use crate::ChainIndex; - use rand::prelude::*; - use std::time::Duration; - - #[test] - fn test_address() { - let test_cases = vec![ - ( - SpendPolicy::PublicKey(PublicKey::new([ - 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ])), - "addr:55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1", - ), - ( - SpendPolicy::Above(100), - "addr:c2fba9b9607c800e80d9284ed0fb9a55737ba1bbd67311d0d9242dd6376bed0c6ee355e814fa", - ), - ( - SpendPolicy::After(time::UNIX_EPOCH + Duration::from_secs(1433600000)), - "addr:5bdb96e33ffdf72619ad38bee57ad4db9eb242aeb2ee32020ba16179af5d46d501bd2011806b", - ), - ( - SpendPolicy::Hash([ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]), - "addr:1cc0fc4cde659333cf7e61971cc5025c5a6b4759c9d1c1d438227c3eb57d841512d4cd4ce620", - ), - ( - SpendPolicy::Threshold( - 2, - vec![ - SpendPolicy::PublicKey(PublicKey::new([ - 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - ])), - SpendPolicy::Above(100), - SpendPolicy::Threshold( - 2, - vec![ - SpendPolicy::PublicKey(PublicKey::new([0; 32])), - SpendPolicy::After( - time::UNIX_EPOCH + Duration::from_secs(1433600000), - ), - ], - ), - ], - ), - "addr:30f516630280059c25ae92f3bf3c451be258ecd3249c43906e3d9dd9e86f2dc00ef5eeffc2c4", - ), - ]; - - for (policy, expected) in test_cases { - assert_eq!(policy.address().to_string(), expected); - } - } - - #[test] - fn test_verify() { - struct PolicyTest { - policy: SpendPolicy, - state: SigningState, - hash: Hash256, - signatures: Vec, - preimages: Vec>, - result: Result<(), ValidationError>, - } - let test_cases = vec![ - PolicyTest { - policy: SpendPolicy::Above(100), - state: SigningState { - index: ChainIndex { - height: 99, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(99), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Err(ValidationError::InvalidHeight), - }, - PolicyTest { - policy: SpendPolicy::Above(100), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(99), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Ok(()), - }, - PolicyTest { - policy: SpendPolicy::After(time::UNIX_EPOCH + Duration::from_secs(100)), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(99), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Err(ValidationError::InvalidTimestamp), - }, - PolicyTest { - policy: SpendPolicy::After(time::UNIX_EPOCH + Duration::from_secs(100)), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Ok(()), - }, - PolicyTest { - policy: SpendPolicy::PublicKey(PublicKey::new([0; 32])), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Err(ValidationError::MissingSignature), - }, - PolicyTest { - policy: SpendPolicy::PublicKey(PublicKey::new([0; 32])), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![Signature::new([0; 64])], - preimages: vec![], - result: Err(ValidationError::InvalidSignature), - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::PublicKey(pk.public_key()), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_ref())], - preimages: vec![], - result: Ok(()), - } - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::Threshold( - 2, - vec![ - SpendPolicy::PublicKey(pk.public_key()), - SpendPolicy::Above(100), - ], - ), - state: SigningState { - index: ChainIndex { - height: 99, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_ref())], - preimages: vec![], - result: Err(ValidationError::ThresholdNotMet), - } - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::Threshold( - 2, - vec![ - SpendPolicy::PublicKey(pk.public_key()), - SpendPolicy::Above(100), - ], - ), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![Signature::new([0; 64])], - preimages: vec![], - result: Err(ValidationError::ThresholdNotMet), - } - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::Threshold( - 2, - vec![ - SpendPolicy::PublicKey(pk.public_key()), - SpendPolicy::Above(100), - ], - ), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_ref())], - preimages: vec![], - result: Ok(()), - } - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::Threshold( - 1, - vec![ - SpendPolicy::PublicKey(pk.public_key()), - SpendPolicy::Opaque(Address::new([0; 32])), - ], - ), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_ref())], - preimages: vec![], - result: Ok(()), - } - }, - { - let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::from(random::<[u8; 32]>()); - - PolicyTest { - policy: SpendPolicy::Threshold( - 1, - vec![ - SpendPolicy::PublicKey(pk.public_key()), - SpendPolicy::Opaque(Address::new([0; 32])), - ], - ), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: sig_hash, - signatures: vec![], - preimages: vec![], - result: Err(ValidationError::ThresholdNotMet), - } - }, - { - let mut preimage = [0; 64]; - thread_rng().fill(&mut preimage); - - let mut hasher = Sha256::new(); - hasher.update(preimage); - let h: [u8; 32] = hasher.finalize().into(); - - PolicyTest { - policy: SpendPolicy::Hash(h), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![], - result: Err(ValidationError::MissingPreimage), - } - }, - { - let mut preimage = [0; 64]; - thread_rng().fill(&mut preimage); - - let mut hasher = Sha256::new(); - hasher.update(preimage); - let h: [u8; 32] = hasher.finalize().into(); - - PolicyTest { - policy: SpendPolicy::Hash(h), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![[0; 64].to_vec()], - result: Err(ValidationError::InvalidPreimage), - } - }, - { - let mut preimage = [0; 64]; - thread_rng().fill(&mut preimage); - - let mut hasher = Sha256::new(); - hasher.update(preimage); - let h: [u8; 32] = hasher.finalize().into(); - - PolicyTest { - policy: SpendPolicy::Hash(h), - state: SigningState { - index: ChainIndex { - height: 100, - id: [0; 32], - }, - median_timestamp: time::UNIX_EPOCH + Duration::from_secs(100), - hardforks: NetworkHardforks::default(), - }, - hash: Hash256::default(), - signatures: vec![], - preimages: vec![preimage.to_vec()], - result: Ok(()), - } - }, - ]; - - for test in test_cases { - let result = test.policy.verify( - &test.state, - &test.hash, - &mut test.signatures.iter(), - &mut test.preimages.iter(), - ); - assert_eq!(result, test.result, "{}", test.policy); - } - } - - #[test] - fn test_opaque_policy() { - let test_cases = vec![ - SpendPolicy::above(100), - SpendPolicy::after(time::UNIX_EPOCH + Duration::from_secs(100)), - SpendPolicy::public_key(PublicKey::new([0; 32])), - SpendPolicy::hash([0; 32]), - SpendPolicy::threshold( - 2, - vec![ - SpendPolicy::public_key(PublicKey::new([0; 32])), - SpendPolicy::above(100), - ], - ), - SpendPolicy::threshold( - 2, - vec![ - SpendPolicy::public_key(PublicKey::new([0; 32])), - SpendPolicy::above(100), - SpendPolicy::threshold( - 2, - vec![ - SpendPolicy::public_key(PublicKey::new([0; 32])), - SpendPolicy::after(time::UNIX_EPOCH + Duration::from_secs(100)), - ], - ), - SpendPolicy::PublicKey(PublicKey::new([1; 32])), - ], - ), - ]; - - for (i, policy) in test_cases.into_iter().enumerate() { - let policy = policy.clone(); - let address = policy.address(); - let expected_address = address.to_string(); - let opaque = SpendPolicy::Opaque(address); - assert_eq!( - opaque.address().to_string(), - expected_address, - "test case {}", - i - ); - - if let SpendPolicy::Threshold(n, of) = policy { - // test that the address of opaque threshold policies is the - // same as the address of normal threshold policies - for j in 0..of.len() { - let mut of = of.clone(); - of[j] = SpendPolicy::Opaque(of[j].address()); - let opaque_policy = SpendPolicy::threshold(n, of); - - assert_eq!( - opaque_policy.address().to_string(), - expected_address, - "test case {}-{}", - i, - j - ); - } - } - } - } -} diff --git a/src/transactions.rs b/src/transactions.rs index 74cafd9..f2eae1c 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -1,5 +1,5 @@ -use crate::encoding::{to_writer, SerializeError}; -use crate::signing::{PrivateKey, Signature, SigningState}; +use crate::encoding::to_writer; +use crate::signing::Signature; use crate::specifier::{specifier, Specifier}; use crate::unlock_conditions::UnlockConditions; use crate::{Address, Currency, ImplHashID, Leaf}; @@ -144,58 +144,7 @@ impl Transaction { const SIACOIN_OUTPUT_ID_PREFIX: Specifier = specifier!("siacoin output"); const SIAFUND_OUTPUT_ID_PREFIX: Specifier = specifier!("siafund output"); - pub fn encode_no_sigs(&self) -> Vec { - let mut buf = Vec::new(); - - buf.extend_from_slice(&(self.siacoin_inputs.len() as u64).to_le_bytes()); - for input in &self.siacoin_inputs { - to_writer(&mut buf, input).unwrap(); - } - - buf.extend_from_slice(&(self.siacoin_outputs.len() as u64).to_le_bytes()); - for output in &self.siacoin_outputs { - to_writer(&mut buf, output).unwrap(); - } - - buf.extend_from_slice(&(self.file_contracts.len() as u64).to_le_bytes()); - for file_contract in &self.file_contracts { - to_writer(&mut buf, file_contract).unwrap(); - } - - buf.extend_from_slice(&(self.file_contract_revisions.len() as u64).to_le_bytes()); - for file_contract_revision in &self.file_contract_revisions { - to_writer(&mut buf, file_contract_revision).unwrap(); - } - - buf.extend_from_slice(&(self.storage_proofs.len() as u64).to_le_bytes()); - for storage_proof in &self.storage_proofs { - to_writer(&mut buf, storage_proof).unwrap(); - } - - buf.extend_from_slice(&(self.siafund_inputs.len() as u64).to_le_bytes()); - for input in &self.siafund_inputs { - to_writer(&mut buf, input).unwrap(); - } - - buf.extend_from_slice(&(self.siafund_outputs.len() as u64).to_le_bytes()); - for output in &self.siafund_outputs { - to_writer(&mut buf, output).unwrap(); - } - - buf.extend_from_slice(&(self.miner_fees.len() as u64).to_le_bytes()); - for fee in &self.miner_fees { - to_writer(&mut buf, fee).unwrap(); - } - - buf.extend_from_slice(&(self.arbitrary_data.len() as u64).to_le_bytes()); - for data in &self.arbitrary_data { - buf.extend_from_slice(&(data.len() as u64).to_le_bytes()); - buf.extend_from_slice(data); - } - buf - } - - pub fn hash_no_sigs(&self, state: &mut State) { + fn hash_no_sigs(&self, state: &mut State) { state.update(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in self.siacoin_inputs.iter() { to_writer(state, input).unwrap(); @@ -243,213 +192,6 @@ impl Transaction { } } - pub(crate) fn whole_sig_hash( - &self, - chain: &SigningState, - parent_id: &Hash256, - public_key_index: u64, - timelock: u64, - covered_sigs: &Vec, - ) -> Result { - let mut state = Params::new().hash_length(32).to_state(); - - state.update(&(self.siacoin_inputs.len() as u64).to_le_bytes()); - for input in self.siacoin_inputs.iter() { - state.update(chain.replay_prefix()); - to_writer(&mut state, input)?; - } - - state.update(&(self.siacoin_outputs.len() as u64).to_le_bytes()); - for output in self.siacoin_outputs.iter() { - to_writer(&mut state, output)?; - } - - state.update(&(self.file_contracts.len() as u64).to_le_bytes()); - for file_contract in self.file_contracts.iter() { - to_writer(&mut state, file_contract)?; - } - - state.update(&(self.file_contract_revisions.len() as u64).to_le_bytes()); - for file_contract_revision in self.file_contract_revisions.iter() { - to_writer(&mut state, file_contract_revision)?; - } - - state.update(&(self.storage_proofs.len() as u64).to_le_bytes()); - for storage_proof in self.storage_proofs.iter() { - to_writer(&mut state, storage_proof).unwrap(); - } - - state.update(&(self.siafund_inputs.len() as u64).to_le_bytes()); - for input in self.siafund_inputs.iter() { - state.update(chain.replay_prefix()); - to_writer(&mut state, input).unwrap(); - } - - state.update(&(self.siafund_outputs.len() as u64).to_le_bytes()); - for output in self.siafund_outputs.iter() { - to_writer(&mut state, output)?; - } - - state.update(&(self.miner_fees.len() as u64).to_le_bytes()); - for fee in self.miner_fees.iter() { - to_writer(&mut state, &fee)?; - } - - state.update(&(self.arbitrary_data.len() as u64).to_le_bytes()); - for data in self.arbitrary_data.iter() { - state.update(&(data.len() as u64).to_le_bytes()); - state.update(data); - } - - to_writer(&mut state, parent_id)?; - state.update(&public_key_index.to_le_bytes()); - state.update(&timelock.to_le_bytes()); - - for &i in covered_sigs { - if i >= self.signatures.len() { - return Err(SerializeError::Custom( - "signature index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.signatures[i])?; - } - - Ok(state.finalize().into()) - } - - pub(crate) fn partial_sig_hash( - &self, - chain: &SigningState, - covered_fields: &CoveredFields, - ) -> Result { - let mut state = Params::new().hash_length(32).to_state(); - - for &i in covered_fields.siacoin_inputs.iter() { - if i >= self.siacoin_inputs.len() { - return Err(SerializeError::Custom( - "siacoin_inputs index out of bounds".to_string(), - )); - } - state.update(chain.replay_prefix()); - to_writer(&mut state, &self.siacoin_inputs[i])?; - } - - for &i in covered_fields.siacoin_outputs.iter() { - if i >= self.siacoin_outputs.len() { - return Err(SerializeError::Custom( - "siacoin_outputs index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.siacoin_outputs[i])?; - } - - for &i in covered_fields.file_contracts.iter() { - if i >= self.file_contracts.len() { - return Err(SerializeError::Custom( - "file_contracts index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.file_contracts[i])?; - } - - for &i in covered_fields.file_contract_revisions.iter() { - if i >= self.file_contract_revisions.len() { - return Err(SerializeError::Custom( - "file_contract_revisions index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.file_contract_revisions[i])?; - } - - for &i in covered_fields.storage_proofs.iter() { - if i >= self.storage_proofs.len() { - return Err(SerializeError::Custom( - "storage_proofs index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.storage_proofs[i])?; - } - - for &i in covered_fields.siafund_inputs.iter() { - if i >= self.siafund_inputs.len() { - return Err(SerializeError::Custom( - "siafund_inputs index out of bounds".to_string(), - )); - } - state.update(chain.replay_prefix()); - to_writer(&mut state, &self.siafund_inputs[i])?; - } - - for &i in covered_fields.siafund_outputs.iter() { - if i >= self.siafund_outputs.len() { - return Err(SerializeError::Custom( - "siafund_outputs index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.siafund_outputs[i])?; - } - - for &i in covered_fields.miner_fees.iter() { - if i >= self.miner_fees.len() { - return Err(SerializeError::Custom( - "miner_fees index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.miner_fees[i])?; - } - - for &i in covered_fields.arbitrary_data.iter() { - if i >= self.arbitrary_data.len() { - return Err(SerializeError::Custom( - "arbitrary_data index out of bounds".to_string(), - )); - } - state.update(&(self.arbitrary_data[i].len() as u64).to_le_bytes()); - state.update(&self.arbitrary_data[i]); - } - - for &i in covered_fields.signatures.iter() { - if i >= self.signatures.len() { - return Err(SerializeError::Custom( - "signatures index out of bounds".to_string(), - )); - } - to_writer(&mut state, &self.signatures[i])?; - } - - Ok(state.finalize().into()) - } - - pub fn sign( - &self, - state: &SigningState, - covered_fields: &CoveredFields, - parent_id: Hash256, - public_key_index: u64, - timelock: u64, - private_key: &PrivateKey, - ) -> Result { - let sig_hash = if covered_fields.whole_transaction { - self.whole_sig_hash( - state, - &parent_id, - public_key_index, - timelock, - &covered_fields.signatures, - ) - } else { - self.partial_sig_hash(state, covered_fields) - }?; - - Ok(TransactionSignature { - parent_id, - public_key_index, - timelock, - covered_fields: covered_fields.clone(), - signature: private_key.sign(sig_hash.as_ref()), - }) - } - pub fn id(&self) -> TransactionID { let mut state = Params::new().hash_length(32).to_state(); self.hash_no_sigs(&mut state); @@ -483,14 +225,18 @@ impl Transaction { #[cfg(test)] mod tests { use crate::encoding::{from_reader, to_bytes}; - use crate::signing::{NetworkHardforks, PublicKey}; + use crate::signing::PublicKey; use crate::unlock_conditions::{Algorithm, UnlockKey}; - use crate::ChainIndex; - use std::time::SystemTime; use std::vec; use super::*; + #[test] + fn test_json_serialize_unlock_conditions() { + let uc = UnlockConditions::new(100, vec![UnlockKey::new(Algorithm::ed25519(), PublicKey::new([0; 32]))], 1); + assert_eq!(serde_json::to_string(&uc).unwrap(), "{\"timelock\":100,\"publicKeys\":[\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\"],\"signaturesRequired\":1}"); + } + #[test] fn test_json_serialize_covered_fields() { let mut cf = CoveredFields::default(); @@ -936,156 +682,13 @@ mod tests { #[test] fn test_transaction_id() { let txn = Transaction::default(); - let h = Params::new() - .hash_length(32) - .to_state() - .update(&txn.encode_no_sigs()) - .finalize(); - + let id = txn.id(); assert_eq!( - txn.encode_no_sigs(), - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - let buf = h.as_bytes(); - assert_eq!( - hex::encode(buf), + id.to_string(), "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24" ); } - #[test] - fn test_whole_sig_hash() { - let state = SigningState { - index: ChainIndex { - height: 0, - id: [0; 32], - }, - median_timestamp: SystemTime::now(), - hardforks: NetworkHardforks { - asic_height: 0, - foundation_height: 0, - v2_allow_height: 1000, - v2_require_height: 1000, - }, - }; - let pk = PrivateKey::from_seed(&[ - 136, 215, 58, 248, 45, 30, 78, 97, 128, 111, 82, 204, 43, 233, 223, 111, 110, 29, 73, - 157, 52, 25, 242, 96, 131, 16, 187, 22, 232, 107, 17, 205, - ]); - let test_cases = vec![ - ( - Transaction { - siacoin_inputs: vec![ - SiacoinInput{ - parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), - unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), - } - ], - siacoin_outputs: vec![ - SiacoinOutput{ - value: Currency::new(67856467336433871), - address: Address::parse_string("addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69").unwrap(), - } - ], - file_contracts: Vec::new(), - file_contract_revisions: Vec::new(), - storage_proofs: Vec::new(), - siafund_inputs: Vec::new(), - siafund_outputs: Vec::new(), - miner_fees: Vec::new(), - arbitrary_data: Vec::new(), - signatures: Vec::new(), - }, - "h:a4b1855c546db7ec902237f730717faae96187db8ce9fe139504323a639f731e" - ) - ]; - - for (txn, expected) in test_cases { - let sig_hash = txn - .whole_sig_hash( - &state, - &Hash256::from(>::into( - txn.siacoin_inputs[0].parent_id, - )), - 0, - 0, - &vec![], - ) - .expect("expect tranasction to hash"); - - assert_eq!(sig_hash.to_string(), expected) - } - } - - #[test] - fn test_transaction_sign() { - let state = SigningState { - index: ChainIndex { - height: 0, - id: [0; 32], - }, - median_timestamp: SystemTime::now(), - hardforks: NetworkHardforks { - asic_height: 0, - foundation_height: 0, - v2_allow_height: 1000, - v2_require_height: 1000, - }, - }; - let pk = PrivateKey::from_seed(&[ - 136, 215, 58, 248, 45, 30, 78, 97, 128, 111, 82, 204, 43, 233, 223, 111, 110, 29, 73, - 157, 52, 25, 242, 96, 131, 16, 187, 22, 232, 107, 17, 205, - ]); - let test_cases = vec![ - ( - Transaction { - siacoin_inputs: vec![ - SiacoinInput{ - parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), - unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), - } - ], - siacoin_outputs: vec![ - SiacoinOutput{ - value: Currency::new(67856467336433871), - address: Address::parse_string("addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69").unwrap(), - } - ], - file_contracts: Vec::new(), - file_contract_revisions: Vec::new(), - storage_proofs: Vec::new(), - siafund_inputs: Vec::new(), - siafund_outputs: Vec::new(), - miner_fees: Vec::new(), - arbitrary_data: Vec::new(), - signatures: Vec::new(), - }, - "sig:7a5db98318b5ecad2954d41ba2084c908823ebf4000b95543f352478066a0d04bf4829e3e6d086b42ff9d943f68981c479798fd42bf6f63dac254f4294a37609" - ) - ]; - - for (txn, expected) in test_cases { - let sig = txn - .sign( - &state, - &CoveredFields::whole_transaction(), - Hash256::from(>::into( - txn.siacoin_inputs[0].parent_id, - )), - 0, - 0, - &pk, - ) - .expect(""); - - assert_eq!(sig.signature.to_string(), expected) - } - } - #[test] fn test_serialize_transaction() { let transaction = Transaction {