From c1b1d58e03281cd2da3586e5cac0cdc1fd77b3af Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Tue, 2 Apr 2024 20:14:24 -0700 Subject: [PATCH] fix transaction encoding in 32 bit environments, simplify parse string --- src/address.rs | 184 ++++++++++++---------------- src/consensus.rs | 2 +- src/currency.rs | 23 ++-- src/lib.rs | 29 ++++- src/signing.rs | 22 ++-- src/transactions.rs | 293 +++++++++++++++++++++++++++++++++----------- 6 files changed, 350 insertions(+), 203 deletions(-) diff --git a/src/address.rs b/src/address.rs index 336e94b..4d62c46 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,11 +1,10 @@ +use core::fmt; + use crate::blake2b::Accumulator; use blake2b_simd::Params; use crate::blake2b::LEAF_HASH_PREFIX; -use std::fmt; -use crate::SiaEncodable; +use crate::{SiaEncodable, HexParseError}; use ed25519_dalek::{SigningKey, Signer, VerifyingKey, Verifier, Signature}; -use hex::{encode, decode, FromHexError}; - /// An ed25519 public key that can be used to verify a signature #[derive(Debug, PartialEq, Clone, Copy)] @@ -53,19 +52,6 @@ impl Into for PrivateKey { } } -#[derive(Debug, PartialEq)] -pub enum AddressParseError { - InvalidLength, - InvalidChecksum, - FromHexError, -} - -impl From for AddressParseError { - fn from(_: FromHexError) -> Self { - AddressParseError::FromHexError - } -} - /// An address that can be used to receive UTXOs #[derive(Debug, PartialEq, Clone, Copy)] pub struct Address([u8;32]); @@ -79,40 +65,36 @@ impl Address { self.0 } - pub fn parse_string(s: &str) -> Result { - let mut s = s; - if s.starts_with("addr:") { - s = &s[5..]; - } else if s.len() != 76 { - return Err(AddressParseError::InvalidLength); + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 76 { + return Err(HexParseError::InvalidLength); } - let bytes = decode(s)?; + let mut data = [0u8; 38]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + let h = Params::new() .hash_length(32) .to_state() - .update(&bytes[..32]) + .update(&data[..32]) .finalize(); let checksum = h.as_bytes(); - if checksum[..6] != bytes[32..] { - return Err(AddressParseError::InvalidChecksum); + if checksum[..6] != data[32..] { + return Err(HexParseError::InvalidChecksum); } - let mut buf = [0u8;32]; - buf.copy_from_slice(&bytes[..32]); - Ok(Self(buf)) + Ok(Address(data[..32].try_into().unwrap())) } } impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "addr:{}", encode(self.0)) - } -} - -impl Into for Address { - fn into(self) -> String { let mut buf = [0u8;32+6]; buf[..32].copy_from_slice(&self.0); @@ -123,7 +105,7 @@ impl Into for Address { .finalize(); buf[32..].copy_from_slice(&h.as_bytes()[..6]); - hex::encode(buf) + write!(f, "addr:{}", hex::encode(buf)) } } @@ -139,11 +121,11 @@ pub enum Algorithm { } impl fmt::Display for Algorithm { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Algorithm::ED25519 => write!(f, "ed25519"), - } - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Algorithm::ED25519 => write!(f, "ed25519"), + } + } } impl SiaEncodable for Algorithm { @@ -159,20 +141,6 @@ impl SiaEncodable for Algorithm { } } -#[derive(Debug, PartialEq)] -pub enum ParseUnlockKeyError { - MissingPrefix, - InvalidLength, - UnkownAlgorithm, - FromHexError, -} - -impl From for ParseUnlockKeyError { - fn from(_: FromHexError) -> Self { - ParseUnlockKeyError::FromHexError - } -} - /// A generic public key that can be used to spend a utxo or revise a file /// contract /// @@ -194,33 +162,30 @@ impl UnlockKey { /// Parses an UnlockKey from a string /// The string should be in the format "algorithm:public_key" - pub fn parse_string(s: &str) -> Result { - let parts: Vec<&str> = s.split(':').collect(); - if parts.len() != 2 { - return Err(ParseUnlockKeyError::MissingPrefix); - } - - let algorithm = match parts[0] { + pub fn parse_string(s: &str) -> Result { + let (prefix, key_str) = s.split_once(":").ok_or(HexParseError::MissingPrefix)?; + let algorithm = match prefix { "ed25519" => Algorithm::ED25519, - _ => return Err(ParseUnlockKeyError::UnkownAlgorithm), + _ => return Err(HexParseError::InvalidPrefix), }; - - let key = hex::decode(parts[1])?; - if key.len() != 32 { - return Err(ParseUnlockKeyError::InvalidLength); - } - - Ok(Self::new(algorithm, PublicKey(key.try_into().unwrap()))) + + let mut data = [0u8; 32]; + hex::decode_to_slice(key_str, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(UnlockKey{ + algorithm, + public_key: PublicKey(data), + }) } // Returns the public key of the UnlockKey pub fn public_key(&self) -> PublicKey { self.public_key } +} - /// Returns the UnlockKey as a string - pub fn as_string(&self) -> String { - format!("{}:{}", self.algorithm, encode(self.public_key.0)) +impl fmt::Display for UnlockKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.algorithm, hex::encode(&self.public_key.0)) } } @@ -234,7 +199,7 @@ impl SiaEncodable for UnlockKey { } // specifies the conditions for spending an output or revising a file contract. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct UnlockConditions { pub timelock: u64, pub public_keys: Vec, @@ -244,11 +209,11 @@ pub struct UnlockConditions { impl SiaEncodable for UnlockConditions { fn encode(&self, buf: &mut Vec) { buf.extend_from_slice(&self.timelock.to_le_bytes()); - buf.extend_from_slice(&self.public_keys.len().to_le_bytes()); + buf.extend_from_slice(&(self.public_keys.len() as u64).to_le_bytes()); for key in &self.public_keys { key.encode(buf); } - buf.extend_from_slice(&self.required_signatures.to_le_bytes()); + buf.extend_from_slice(&(self.required_signatures as u64).to_le_bytes()); } } @@ -319,38 +284,47 @@ mod tests { #[test] fn test_standard_unlockhash() { let test_cases = vec![ - ( Address::parse_string("80f637df83a93a6916d1b5c8bdbb061f967fb9fe8fe51ef4d97eeec73c6bfc394771e4a04f42").unwrap(), decode("ad08d551ab7116b8c2285de81ffa528ef3679f9e242c3f551b560a60ab9763db").unwrap() ), - ( Address::parse_string("99a27a168bdde2e9c59bc967f6c662e3db0b2cf13da26ddae26004fa19c61d3db017dca7d0d3").unwrap(), decode("18ac9c05b0c5e7c62859812b943572429cda178aa3df92697569b8984c603b4c").unwrap() ), - ( Address::parse_string("128151658b256d0185f3f91504758349a96e73c1a68a39c7ff7bf9d0e416997c964d773858ce").unwrap(), decode("2b36cc860796f2e8a1990b437f46a4b905840e6ba41ba5f68fe2b8ebe23626af").unwrap() ), - ( Address::parse_string("1f47d453cfd7369bce4034d3ab461feb2a4d073bf59c959225993d00e38d71a8fea7c57cd3f1").unwrap(), decode("a3e3c2f3493a079d3dfe69681bf878c59337e3d1c79d17a34e3da81f062bbe21").unwrap() ), - ( Address::parse_string("e03c56f8d95894cea875711e2f909c68c07dd37142a8253813ad09abceb2b6e5dd89992c9638").unwrap(), decode("a03d3b27db7e143cb8b39a1eb9234bffad59d6f50adf4f0ee916afd510a939a0").unwrap() ), - ( Address::parse_string("68b6dd2e50f12e2deef2efd6b7baa660d87950ea16c5a8402a6db5873e062bcdd5246940b44e").unwrap(), decode("52e4438ca9b6eb2d33953f97255e410130d55749432094fe9963f4fc65167ce5").unwrap() ), - ( Address::parse_string("c766e0a5ef49b7bab6c2e9c6a0b699e87eb3580e08f3fe77648dd93b66795a8606787cc5e29e").unwrap(), decode("4110f8b0ade1cca7aa40008a9b9911655393288eaacc3948fecd13edd3f092ec").unwrap() ), - ( Address::parse_string("b455cf3c22de0d84ab8599499b0c2056d4916ab4c642b6b716148487f83ca5a85ad199b7a454").unwrap(), decode("861d50c4ee90b0a6a5544a3820978dad1fd9391c4813ede9e4963f0d6bec010a").unwrap() ), - ( Address::parse_string("5274e9f3db1acfe8bb2d67dbbb5b6f2cc20769a0b42c8a9187ae827bf637f06e62ecada30f9f").unwrap(), decode("a5329c135951f3505d9a26d2833cb6c1aebb875fbada80f38b09bd3314f26802").unwrap() ), - ( Address::parse_string("1540f42840b0479b238ec0143984a784c58240a8ca5e21da4b66be89a2f54353c99739938947").unwrap(), decode("e11589e1857b7a0d2fc3526fbdfdc4d4708dfbf251184be1118138df4ef2d47e").unwrap() ), - ( Address::parse_string("21592f041e6f6861f199d54a26fe6bdfc5d629bb5dda12058d3ce28549c4aeffdbbdb67c2b95").unwrap(), decode("d57887af5b838ea2d20a582f659b3e36ca47d33b253364e3b774a1f4feb8415b").unwrap() ), - ( Address::parse_string("f34b1e0b74a695f8bc82a97bab3b9d1ebe420956cbb3f7611c349c9659ba13fa362a417b1fd2").unwrap(), decode("5da4058d2f95e3c547aab8b5c70817ed3795856aa7988676f266cb429d75ce06").unwrap() ), - ( Address::parse_string("3549a1680fcc093347e2674f4e89c84200965e1b779e2b3c05e4b21645a0b2fd5ac86923ef7a").unwrap(), decode("98ced26430f3be35b29ca76d3f65ea616f89e2510a3c1307856522e23057d958").unwrap() ), - ( Address::parse_string("86fc291f7f53def33f2f7566f5ff08763bf5ada158f97c87fc240d1dcb04aa2a7b289018e33e").unwrap(), decode("e715d5dc3bd8edecb453c59f85998591d7c14fd08057a0605cb416f6751eaad9").unwrap() ), - ( Address::parse_string("46e60abc3acbff858e382783f0739a8b2f2ba4c51b26941d979e60cb5292f11df1112b7016c0").unwrap(), decode("359eee8d1ef18ed647bbd63cb4b2be85061f8e3fd67318e13924ddbc1beb815f").unwrap() ), - ( Address::parse_string("015b4b0759b0adee6c01de051bdacefe1f30eb571c83fa6c37607008696a9fa7f85273061e72").unwrap(), decode("cf5cd07f31ca3aa3b7d2947da7e92c42ec5f981eff80ff1b438e59fd456465fb").unwrap() ), - ( Address::parse_string("7435604655772ca5ff011127c83692e40945187954da3bc7c01102d59701c7351aadbdc9ac8b").unwrap(), decode("7f6a73aeb6de28f1d3935941caa8cab286d13d8c74f2352b5b717c3d743db9c1").unwrap() ), - ( Address::parse_string("c554d56a2eaffd8426006fb6d987cc615fb4ec05b1b15e793ab9d9127d79cf323787817467e6").unwrap(), decode("14b98855c4f22295fcf3e2ec5d5fdfbb877979639c963bf6e226a0fb71902baf").unwrap() ), - ( Address::parse_string("addr:c4850dbcddb9dfac6f44007ec58fe824bc58e3de2432de478f3e53f7965c2afd7ea651b6c2bf").unwrap(), decode("6f5c23f8797f93d3d3c689fe1a3f5d9a1fbf326a7a6ea51fecbeaa9aba46f180").unwrap() ), - ( Address::parse_string("addr:6a8f4f1d5a7405aa24cb1fb2a3c1dcaae74175c712002627289b5cd9dd887088afe605460abd").unwrap(), decode("45f12760f6005a93cece248f5ec78adf15f9d29dafe397c8c28fefc72781d6fb").unwrap() ), - ( Address::parse_string("addr:e464b9b1c9282d8edeed5832b95405761db6dacf6a156fc9119a396bdc8f8892815c7dce20fd").unwrap(), decode("1c12d17a2a8b2c25950872f312d5d0758f07d8357c98897fc472565a44b3d1f1").unwrap() ), - ( Address::parse_string("addr:9ae839af434aa13de6e8baa280541716811dcbaa33165fea5e9bad0c33998c10f16fcac4f214").unwrap(), decode("686d28bf7e4b4cadf759994caed1e52092e12c11cef257a265b50402dbd70c3b").unwrap() ), - ( Address::parse_string("addr:e92722d80103af9574f19a6cf72aab424335927eb7da022455f53314e3587dc8ece40d254981").unwrap(), decode("b2e9ddef40897219a997ae7af277a5550cc10c54e793b6d2146de94df3bd552b").unwrap() ), - ( Address::parse_string("addr:e2a02510f242f35e46b8840d8da42c087ea906b09d8e454c734663650236977da0362dd2ab43").unwrap(), decode("4f756e475a706cdcec8eb1c02b21a591e0c0450cc0408ae8aec82ae97f634ecf").unwrap() ), - ( Address::parse_string("addr:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0").unwrap(), decode("cd46b523d2ee92f205a00726d8544094bb4fe58142ecffd20ea32b37b6e6bfc3").unwrap() ), + ( "80f637df83a93a6916d1b5c8bdbb061f967fb9fe8fe51ef4d97eeec73c6bfc394771e4a04f42", hex::decode("ad08d551ab7116b8c2285de81ffa528ef3679f9e242c3f551b560a60ab9763db").unwrap() ), + ( "99a27a168bdde2e9c59bc967f6c662e3db0b2cf13da26ddae26004fa19c61d3db017dca7d0d3", hex::decode("18ac9c05b0c5e7c62859812b943572429cda178aa3df92697569b8984c603b4c").unwrap() ), + ( "128151658b256d0185f3f91504758349a96e73c1a68a39c7ff7bf9d0e416997c964d773858ce", hex::decode("2b36cc860796f2e8a1990b437f46a4b905840e6ba41ba5f68fe2b8ebe23626af").unwrap() ), + ( "1f47d453cfd7369bce4034d3ab461feb2a4d073bf59c959225993d00e38d71a8fea7c57cd3f1", hex::decode("a3e3c2f3493a079d3dfe69681bf878c59337e3d1c79d17a34e3da81f062bbe21").unwrap() ), + ( "e03c56f8d95894cea875711e2f909c68c07dd37142a8253813ad09abceb2b6e5dd89992c9638", hex::decode("a03d3b27db7e143cb8b39a1eb9234bffad59d6f50adf4f0ee916afd510a939a0").unwrap() ), + ( "68b6dd2e50f12e2deef2efd6b7baa660d87950ea16c5a8402a6db5873e062bcdd5246940b44e", hex::decode("52e4438ca9b6eb2d33953f97255e410130d55749432094fe9963f4fc65167ce5").unwrap() ), + ( "c766e0a5ef49b7bab6c2e9c6a0b699e87eb3580e08f3fe77648dd93b66795a8606787cc5e29e", hex::decode("4110f8b0ade1cca7aa40008a9b9911655393288eaacc3948fecd13edd3f092ec").unwrap() ), + ( "b455cf3c22de0d84ab8599499b0c2056d4916ab4c642b6b716148487f83ca5a85ad199b7a454", hex::decode("861d50c4ee90b0a6a5544a3820978dad1fd9391c4813ede9e4963f0d6bec010a").unwrap() ), + ( "5274e9f3db1acfe8bb2d67dbbb5b6f2cc20769a0b42c8a9187ae827bf637f06e62ecada30f9f", hex::decode("a5329c135951f3505d9a26d2833cb6c1aebb875fbada80f38b09bd3314f26802").unwrap() ), + ( "1540f42840b0479b238ec0143984a784c58240a8ca5e21da4b66be89a2f54353c99739938947", hex::decode("e11589e1857b7a0d2fc3526fbdfdc4d4708dfbf251184be1118138df4ef2d47e").unwrap() ), + ( "21592f041e6f6861f199d54a26fe6bdfc5d629bb5dda12058d3ce28549c4aeffdbbdb67c2b95", hex::decode("d57887af5b838ea2d20a582f659b3e36ca47d33b253364e3b774a1f4feb8415b").unwrap() ), + ( "f34b1e0b74a695f8bc82a97bab3b9d1ebe420956cbb3f7611c349c9659ba13fa362a417b1fd2", hex::decode("5da4058d2f95e3c547aab8b5c70817ed3795856aa7988676f266cb429d75ce06").unwrap() ), + ( "3549a1680fcc093347e2674f4e89c84200965e1b779e2b3c05e4b21645a0b2fd5ac86923ef7a", hex::decode("98ced26430f3be35b29ca76d3f65ea616f89e2510a3c1307856522e23057d958").unwrap() ), + ( "86fc291f7f53def33f2f7566f5ff08763bf5ada158f97c87fc240d1dcb04aa2a7b289018e33e", hex::decode("e715d5dc3bd8edecb453c59f85998591d7c14fd08057a0605cb416f6751eaad9").unwrap() ), + ( "46e60abc3acbff858e382783f0739a8b2f2ba4c51b26941d979e60cb5292f11df1112b7016c0", hex::decode("359eee8d1ef18ed647bbd63cb4b2be85061f8e3fd67318e13924ddbc1beb815f").unwrap() ), + ( "015b4b0759b0adee6c01de051bdacefe1f30eb571c83fa6c37607008696a9fa7f85273061e72", hex::decode("cf5cd07f31ca3aa3b7d2947da7e92c42ec5f981eff80ff1b438e59fd456465fb").unwrap() ), + ( "7435604655772ca5ff011127c83692e40945187954da3bc7c01102d59701c7351aadbdc9ac8b", hex::decode("7f6a73aeb6de28f1d3935941caa8cab286d13d8c74f2352b5b717c3d743db9c1").unwrap() ), + ( "c554d56a2eaffd8426006fb6d987cc615fb4ec05b1b15e793ab9d9127d79cf323787817467e6", hex::decode("14b98855c4f22295fcf3e2ec5d5fdfbb877979639c963bf6e226a0fb71902baf").unwrap() ), + ( "addr:c4850dbcddb9dfac6f44007ec58fe824bc58e3de2432de478f3e53f7965c2afd7ea651b6c2bf", hex::decode("6f5c23f8797f93d3d3c689fe1a3f5d9a1fbf326a7a6ea51fecbeaa9aba46f180").unwrap() ), + ( "addr:6a8f4f1d5a7405aa24cb1fb2a3c1dcaae74175c712002627289b5cd9dd887088afe605460abd", hex::decode("45f12760f6005a93cece248f5ec78adf15f9d29dafe397c8c28fefc72781d6fb").unwrap() ), + ( "addr:e464b9b1c9282d8edeed5832b95405761db6dacf6a156fc9119a396bdc8f8892815c7dce20fd", hex::decode("1c12d17a2a8b2c25950872f312d5d0758f07d8357c98897fc472565a44b3d1f1").unwrap() ), + ( "addr:9ae839af434aa13de6e8baa280541716811dcbaa33165fea5e9bad0c33998c10f16fcac4f214", hex::decode("686d28bf7e4b4cadf759994caed1e52092e12c11cef257a265b50402dbd70c3b").unwrap() ), + ( "addr:e92722d80103af9574f19a6cf72aab424335927eb7da022455f53314e3587dc8ece40d254981", hex::decode("b2e9ddef40897219a997ae7af277a5550cc10c54e793b6d2146de94df3bd552b").unwrap() ), + ( "addr:e2a02510f242f35e46b8840d8da42c087ea906b09d8e454c734663650236977da0362dd2ab43", hex::decode("4f756e475a706cdcec8eb1c02b21a591e0c0450cc0408ae8aec82ae97f634ecf").unwrap() ), + ( "addr:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0", hex::decode("cd46b523d2ee92f205a00726d8544094bb4fe58142ecffd20ea32b37b6e6bfc3").unwrap() ), ]; - for (expected, public_key) in test_cases { + for (expected_str, public_key) in test_cases { + let expected = Address::parse_string(expected_str).unwrap(); + let public_key = PublicKey(public_key.as_slice().try_into().unwrap()); let uc = UnlockConditions::standard_unlock_conditions(public_key); - - assert_eq!(uc.address(), expected) + let addr = uc.address(); + + assert_eq!(addr, expected); + // test string round-trip + if !expected_str.starts_with("addr:") { + assert_eq!(addr.to_string(), "addr:".to_string()+expected_str) + } else { + assert_eq!(addr.to_string(), expected_str) + } } } } \ No newline at end of file diff --git a/src/consensus.rs b/src/consensus.rs index c7d32d5..217ea80 100644 --- a/src/consensus.rs +++ b/src/consensus.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; pub struct ChainIndex { pub height: u64, diff --git a/src/currency.rs b/src/currency.rs index d6a8b23..4f0a6f9 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -1,7 +1,5 @@ -use std::ops::{Add, Sub, Mul, Div}; -use std::num::ParseIntError; -use std::str::FromStr; -use std::fmt; +use core::ops::{Add, Sub, Mul, Div}; +use core::num::ParseIntError; use crate::SiaEncodable; @@ -22,6 +20,7 @@ impl Currency { pub fn parse_string(s: &str) -> Result { let i = s.find(|c: char| !c.is_digit(10) && c != '.').unwrap_or(s.len()); let (value, unit) = s.split_at(i); + let value = value.trim(); let unit = unit.trim(); if unit.is_empty() || unit == "H" { @@ -139,7 +138,7 @@ impl SiaEncodable for Currency { .find(|&(_index, &value)| value != 0) .map_or(16, |(index, _value)| index); // 16 if all bytes are 0 - buf.extend_from_slice(¤cy_buf[i..].len().to_le_bytes()); + buf.extend_from_slice(&(currency_buf[i..].len() as u64).to_le_bytes()); buf.extend_from_slice(¤cy_buf[i..]); } } @@ -157,7 +156,7 @@ impl From for CurrencyParseError { } } -impl fmt::Display for Currency { +/*impl fmt::Display for Currency { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.0 == 0 { return f.write_str("0 SC"); @@ -237,13 +236,13 @@ impl FromStr for Currency { Ok(Currency::new(integer+fraction)) } -} +}*/ #[cfg(test)] mod tests { use super::*; - #[test] +/* #[test] fn test_display() { let test_cases = vec![ (Currency::new(1), "1 H"), @@ -279,7 +278,7 @@ mod tests { for (currency, expected) in test_cases { assert_eq!(currency.to_string(), expected); } - } + }*/ #[test] fn test_from_str() { @@ -297,12 +296,12 @@ mod tests { ("1 KS", Currency::siacoins(1000)), ("10 KS", Currency::siacoins(10000)), ("65.535 KS", Currency::siacoins(u16::MAX as u64)), - ("100 KS", Currency::siacoins(100000)), + ("100KS", Currency::siacoins(100000)), ("1 MS", Currency::siacoins(1000000)), ("10 MS", Currency::siacoins(10000000)), ("100 MS", Currency::siacoins(100000000)), ("1 GS", Currency::siacoins(1000000000)), - ("4.294967295 GS", Currency::siacoins(u32::MAX as u64)), + ("4.294967295GS", Currency::siacoins(u32::MAX as u64)), ("10 GS", Currency::siacoins(10000000000)), ("100 GS", Currency::siacoins(100000000000)), ("1 TS", Currency::siacoins(1000000000000)), @@ -314,7 +313,7 @@ mod tests { ("340.282366920938463463374607431768211455 TS", Currency::new(u128::MAX)), ]; for (input, expected) in test_cases { - assert_eq!(input.parse::().unwrap(), expected); + assert_eq!(Currency::parse_string(input).unwrap(), expected); } } diff --git a/src/lib.rs b/src/lib.rs index a07f090..7a683bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; pub trait SiaEncodable { fn encode(&self, buf : &mut Vec); @@ -20,6 +20,18 @@ pub use address::*; pub use transactions::*; pub use signing::*; +/// encapsulates the various errors that can occur when parsing a Sia object +/// from a string +#[derive(Debug, PartialEq)] +pub enum HexParseError { + MissingPrefix, + InvalidLength, + InvalidPrefix, + InvalidChecksum, // not every object has a checksum + HexError(hex::FromHexError), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Hash256([u8;32]); impl Hash256 { @@ -30,6 +42,21 @@ impl Hash256 { pub fn as_bytes(&self) -> [u8;32] { self.0 } + + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(Hash256(data)) + } } impl fmt::Display for Hash256 { diff --git a/src/signing.rs b/src/signing.rs index 7dc6e91..bb5bc72 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -49,7 +49,7 @@ impl SigningState { .to_state(); let mut buf = Vec::new(); - state.update(&txn.siacoin_inputs.len().to_le_bytes()); + state.update(&(txn.siacoin_inputs.len() as u64).to_le_bytes()); for input in txn.siacoin_inputs.iter() { buf.clear(); state.update(self.replay_prefix()); @@ -57,35 +57,35 @@ impl SigningState { state.update(&buf); } - state.update(&txn.siacoin_outputs.len().to_le_bytes()); + state.update(&(txn.siacoin_outputs.len() as u64).to_le_bytes()); for output in txn.siacoin_outputs.iter() { buf.clear(); output.encode(&mut buf); state.update(&buf); } - state.update(&txn.file_contracts.len().to_le_bytes()); + state.update(&(txn.file_contracts.len() as u64).to_le_bytes()); for file_contract in txn.file_contracts.iter() { buf.clear(); file_contract.encode(&mut buf); state.update(&buf); } - state.update(&txn.file_contract_revisions.len().to_le_bytes()); + state.update(&(txn.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in txn.file_contract_revisions.iter() { buf.clear(); file_contract_revision.encode(&mut buf); state.update(&buf); } - state.update(&txn.storage_proofs.len().to_le_bytes()); + state.update(&(txn.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in txn.storage_proofs.iter() { buf.clear(); storage_proof.encode(&mut buf); state.update(&buf); } - state.update(&txn.siafund_inputs.len().to_le_bytes()); + state.update(&(txn.siafund_inputs.len() as u64).to_le_bytes()); for input in txn.siafund_inputs.iter() { buf.clear(); state.update(self.replay_prefix()); @@ -93,23 +93,23 @@ impl SigningState { state.update(&buf); } - state.update(&txn.siafund_outputs.len().to_le_bytes()); + state.update(&(txn.siafund_outputs.len() as u64).to_le_bytes()); for output in txn.siafund_outputs.iter() { buf.clear(); output.encode(&mut buf); state.update(&buf); } - state.update(&txn.miner_fees.len().to_le_bytes()); + state.update(&(txn.miner_fees.len() as u64).to_le_bytes()); for fee in txn.miner_fees.iter() { buf.clear(); fee.encode(&mut buf); state.update(&buf); } - state.update(&txn.arbitrary_data.len().to_le_bytes()); + state.update(&(txn.arbitrary_data.len() as u64).to_le_bytes()); for data in txn.arbitrary_data.iter() { - state.update(&data.len().to_le_bytes()); + state.update(&(data.len() as u64).to_le_bytes()); state.update(&data); } @@ -184,7 +184,7 @@ impl SigningState { } for i in covered_fields.arbitrary_data.into_iter() { - state.update(&txn.arbitrary_data[i as usize].len().to_le_bytes()); + state.update(&(txn.arbitrary_data[i as usize].len() as u64).to_le_bytes()); state.update(&txn.arbitrary_data[i as usize]); } diff --git a/src/transactions.rs b/src/transactions.rs index ce714b5..bd18fd3 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -2,12 +2,13 @@ use core::fmt; use crate::currency::Currency; use crate::address::{Address, UnlockConditions}; -use crate::{Hash256, SiaEncodable}; +use crate::{HexParseError, Hash256, SiaEncodable}; use blake2b_simd::{Params, State}; const SIACOIN_OUTPUT_ID_PREFIX : [u8;16] = [b's', b'i', b'a', b'c', b'o', b'i', b'n', b' ', b'o', b'u', b't', b'p', b'u', b't', 0, 0]; const SIAFUND_OUTPUT_ID_PREFIX : [u8;16] = [b's', b'i', b'a', b'f', b'u', b'n', b'd', b' ', b'o', b'u', b't', b'p', b'u', b't', 0, 0]; +#[derive(Debug, Clone, Copy)] pub struct SiacoinOutputID([u8;32]); impl SiacoinOutputID { @@ -18,6 +19,27 @@ impl SiacoinOutputID { pub fn as_bytes(&self) -> [u8;32] { self.0 } + + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(SiacoinOutputID(data)) + } +} + +impl SiaEncodable for SiacoinOutputID { + fn encode(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.0); + } } impl fmt::Display for SiacoinOutputID { @@ -26,6 +48,7 @@ impl fmt::Display for SiacoinOutputID { } } +#[derive(Debug, Clone)] pub struct SiacoinInput { pub parent_id: SiacoinOutputID, pub unlock_conditions: UnlockConditions, @@ -33,11 +56,12 @@ pub struct SiacoinInput { impl SiaEncodable for SiacoinInput { fn encode(&self, buf: &mut Vec) { - buf.extend_from_slice(&self.parent_id.as_bytes()); + self.parent_id.encode(buf); self.unlock_conditions.encode(buf); } } +#[derive(Debug, Clone, Copy)] pub struct SiacoinOutput { pub address: Address, pub value: Currency, @@ -50,12 +74,28 @@ impl SiaEncodable for SiacoinOutput { } } +#[derive(Debug, Clone, Copy)] pub struct SiafundOutputID([u8;32]); impl SiafundOutputID { pub fn as_bytes(&self) -> [u8;32] { self.0 } + + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(SiafundOutputID(data)) + } } impl fmt::Display for SiafundOutputID { @@ -64,20 +104,28 @@ impl fmt::Display for SiafundOutputID { } } +impl SiaEncodable for SiafundOutputID { + fn encode(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.0); + } +} + +#[derive(Debug, Clone)] pub struct SiafundInput { - pub parent_id: [u8;32], + pub parent_id: SiafundOutputID, pub unlock_conditions: UnlockConditions, pub claim_address: Address, } impl SiaEncodable for SiafundInput { fn encode(&self, buf: &mut Vec) { - buf.extend_from_slice(&self.parent_id); + self.parent_id.encode(buf); self.unlock_conditions.encode(buf); self.claim_address.encode(buf); } } +#[derive(Debug, Clone, Copy)] pub struct SiafundOutput { pub address: Address, pub value: Currency, @@ -92,12 +140,28 @@ impl SiaEncodable for SiafundOutput { } } +#[derive(Debug, Clone, Copy)] pub struct FileContractID([u8;32]); impl FileContractID { pub fn as_bytes(&self) -> [u8;32] { self.0 } + + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(FileContractID(data)) + } } impl fmt::Display for FileContractID { @@ -106,6 +170,7 @@ impl fmt::Display for FileContractID { } } +#[derive(Debug, Clone)] pub struct FileContract { pub file_size: u64, pub file_merkle_root: Hash256, @@ -125,11 +190,11 @@ impl SiaEncodable for FileContract { buf.extend_from_slice(&self.window_start.to_le_bytes()); buf.extend_from_slice(&self.window_end.to_le_bytes()); self.payout.encode(buf); - buf.extend_from_slice(&self.valid_proof_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.valid_proof_outputs.len() as u64).to_le_bytes()); for output in &self.valid_proof_outputs { output.encode(buf); } - buf.extend_from_slice(&self.missed_proof_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.missed_proof_outputs.len() as u64).to_le_bytes()); for output in &self.missed_proof_outputs { output.encode(buf); } @@ -138,6 +203,7 @@ impl SiaEncodable for FileContract { } } +#[derive(Debug, Clone)] pub struct FileContractRevision { pub parent_id: FileContractID, pub unlock_conditions: UnlockConditions, @@ -160,11 +226,11 @@ impl SiaEncodable for FileContractRevision { buf.extend_from_slice(&self.file_merkle_root.as_bytes()); buf.extend_from_slice(&self.window_start.to_le_bytes()); buf.extend_from_slice(&self.window_end.to_le_bytes()); - buf.extend_from_slice(&self.valid_proof_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.valid_proof_outputs.len() as u64).to_le_bytes()); for output in &self.valid_proof_outputs { output.encode(buf); } - buf.extend_from_slice(&self.missed_proof_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.missed_proof_outputs.len() as u64).to_le_bytes()); for output in &self.missed_proof_outputs { output.encode(buf); } @@ -172,6 +238,7 @@ impl SiaEncodable for FileContractRevision { } } +#[derive(Debug, Clone)] pub struct StorageProof { pub parent_id: FileContractID, pub leaf: [u8;64], @@ -182,13 +249,14 @@ impl SiaEncodable for StorageProof { fn encode(&self, buf: &mut Vec) { buf.extend_from_slice(&self.parent_id.as_bytes()); buf.extend_from_slice(&self.leaf); - buf.extend_from_slice(&self.proof.len().to_le_bytes()); + buf.extend_from_slice(&(self.proof.len() as u64).to_le_bytes()); for proof in &self.proof { buf.extend_from_slice(&proof.as_bytes()); } } } +#[derive(Debug, Clone)] pub struct CoveredFields { pub whole_transaction: bool, pub siacoin_inputs: Vec, @@ -206,49 +274,50 @@ pub struct CoveredFields { impl SiaEncodable for CoveredFields { fn encode(&self, buf: &mut Vec) { buf.push(self.whole_transaction as u8); - buf.extend_from_slice(&self.siacoin_inputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in &self.siacoin_inputs { buf.extend_from_slice(&input.to_le_bytes()); } - buf.extend_from_slice(&self.siacoin_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siacoin_outputs.len() as u64).to_le_bytes()); for output in &self.siacoin_outputs { buf.extend_from_slice(&output.to_le_bytes()); } - buf.extend_from_slice(&self.siafund_inputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in &self.siafund_inputs { buf.extend_from_slice(&input.to_le_bytes()); } - buf.extend_from_slice(&self.siafund_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siafund_outputs.len() as u64).to_le_bytes()); for output in &self.siafund_outputs { buf.extend_from_slice(&output.to_le_bytes()); } - buf.extend_from_slice(&self.file_contracts.len().to_le_bytes()); + buf.extend_from_slice(&(self.file_contracts.len() as u64).to_le_bytes()); for file_contract in &self.file_contracts { buf.extend_from_slice(&file_contract.to_le_bytes()); } - buf.extend_from_slice(&self.file_contract_revisions.len().to_le_bytes()); + buf.extend_from_slice(&(self.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in &self.file_contract_revisions { buf.extend_from_slice(&file_contract_revision.to_le_bytes()); } - buf.extend_from_slice(&self.storage_proofs.len().to_le_bytes()); + buf.extend_from_slice(&(self.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in &self.storage_proofs { buf.extend_from_slice(&storage_proof.to_le_bytes()); } - buf.extend_from_slice(&self.miner_fees.len().to_le_bytes()); + buf.extend_from_slice(&(self.miner_fees.len() as u64).to_le_bytes()); for miner_fee in &self.miner_fees { buf.extend_from_slice(&miner_fee.to_le_bytes()); } - buf.extend_from_slice(&self.arbitrary_data.len().to_le_bytes()); + buf.extend_from_slice(&(self.arbitrary_data.len() as u64).to_le_bytes()); for arbitrary_data in &self.arbitrary_data { buf.extend_from_slice(&arbitrary_data.to_le_bytes()); } - buf.extend_from_slice(&self.signatures.len().to_le_bytes()); + buf.extend_from_slice(&(self.signatures.len() as u64).to_le_bytes()); for signature in &self.signatures { buf.extend_from_slice(&signature.to_le_bytes()); } } } +#[derive(Debug, Clone)] pub struct Signature { pub parent_id: Hash256, pub public_key_index: u64, @@ -267,6 +336,33 @@ impl SiaEncodable for Signature { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TransactionID([u8;32]); + +impl TransactionID { + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(":"){ + Some((_prefix, suffix)) => suffix, + None => s + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(|err| HexParseError::HexError(err))?; + Ok(TransactionID(data)) + } +} + +impl fmt::Display for TransactionID { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "txn:{}", hex::encode(&self.0)) + } +} + +#[derive(Default, Debug, Clone)] pub struct Transaction { pub miner_fees: Vec, pub siacoin_inputs: Vec, @@ -281,8 +377,58 @@ pub struct Transaction { } impl Transaction { - fn hash_no_sigs(&self, state: &mut State) { - state.update(&self.siacoin_inputs.len().to_le_bytes()); + 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 { + input.encode(&mut buf); + } + + buf.extend_from_slice(&(self.siacoin_outputs.len() as u64).to_le_bytes()); + for output in &self.siacoin_outputs { + output.encode(&mut buf); + } + + buf.extend_from_slice(&(self.file_contracts.len() as u64).to_le_bytes()); + for file_contract in &self.file_contracts { + file_contract.encode(&mut buf); + } + + buf.extend_from_slice(&(self.file_contract_revisions.len() as u64).to_le_bytes()); + for file_contract_revision in &self.file_contract_revisions { + file_contract_revision.encode(&mut buf); + } + + buf.extend_from_slice(&(self.storage_proofs.len() as u64).to_le_bytes()); + for storage_proof in &self.storage_proofs { + storage_proof.encode(&mut buf); + } + + buf.extend_from_slice(&(self.siafund_inputs.len() as u64).to_le_bytes()); + for input in &self.siafund_inputs { + input.encode(&mut buf); + } + + buf.extend_from_slice(&(self.siafund_outputs.len() as u64).to_le_bytes()); + for output in &self.siafund_outputs { + output.encode(&mut buf); + } + + buf.extend_from_slice(&(self.miner_fees.len() as u64).to_le_bytes()); + for fee in &self.miner_fees { + fee.encode(&mut buf); + } + + 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); + } + return buf; + } + pub fn hash_no_sigs(&self, state: &mut State) { + state.update(&(self.siacoin_inputs.len() as u64).to_le_bytes()); let mut buf = Vec::new(); for input in self.siacoin_inputs.iter() { buf.clear(); @@ -290,145 +436,146 @@ impl Transaction { state.update(&buf); } - state.update(&self.siacoin_outputs.len().to_le_bytes()); + state.update(&(self.siacoin_outputs.len() as u64).to_le_bytes()); for output in self.siacoin_outputs.iter() { buf.clear(); output.encode(&mut buf); state.update(&buf); } - state.update(&self.file_contracts.len().to_le_bytes()); + state.update(&(self.file_contracts.len() as u64).to_le_bytes()); for file_contract in self.file_contracts.iter() { buf.clear(); file_contract.encode(&mut buf); state.update(&buf); } - state.update(&self.file_contract_revisions.len().to_le_bytes()); + state.update(&(self.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in self.file_contract_revisions.iter() { buf.clear(); file_contract_revision.encode(&mut buf); state.update(&buf); } - state.update(&self.storage_proofs.len().to_le_bytes()); + state.update(&(self.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in self.storage_proofs.iter() { buf.clear(); storage_proof.encode(&mut buf); state.update(&buf); } - state.update(&self.siafund_inputs.len().to_le_bytes()); + state.update(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in self.siafund_inputs.iter() { buf.clear(); input.encode(&mut buf); state.update(&buf); } - state.update(&self.siafund_outputs.len().to_le_bytes()); + state.update(&(self.siafund_outputs.len() as u64).to_le_bytes()); for output in self.siafund_outputs.iter() { buf.clear(); output.encode(&mut buf); state.update(&buf); } - state.update(&self.miner_fees.len().to_le_bytes()); + state.update(&(self.miner_fees.len() as u64).to_le_bytes()); for fee in self.miner_fees.iter() { buf.clear(); fee.encode(&mut buf); state.update(&buf); } - state.update(&self.arbitrary_data.len().to_le_bytes()); + state.update(&(self.arbitrary_data.len() as u64).to_le_bytes()); for data in self.arbitrary_data.iter() { - state.update(&data.len().to_le_bytes()); + state.update(&(data.len() as u64).to_le_bytes()); state.update(&data); } } - pub fn id(&self) -> [u8;32] { + pub fn id(&self) -> TransactionID { let mut state = Params::new() .hash_length(32) .to_state(); - self.hash_no_sigs(&mut state); - state.finalize() - .as_bytes() + let hash = state.finalize(); + let buf = hash.as_bytes(); + + TransactionID(buf .try_into() - .unwrap() + .unwrap()) } - pub fn siacoin_output_id(&self, i: usize) -> [u8;32] { + pub fn siacoin_output_id(&self, i: usize) -> SiacoinOutputID { let mut state = Params::new() .hash_length(32) .to_state(); state.update(&SIACOIN_OUTPUT_ID_PREFIX); self.hash_no_sigs(&mut state); - state.update(&i.to_le_bytes()) - .finalize(); - let mut output_id = [0;32]; - output_id.copy_from_slice(&state.finalize().as_bytes()[..32]); - return output_id; + SiacoinOutputID(state.update(&i.to_le_bytes()) + .finalize() + .as_bytes() + .try_into() + .unwrap()) } - pub fn siafund_output_id(&self, i: usize) -> [u8;32] { + pub fn siafund_output_id(&self, i: usize) -> SiafundOutputID { let mut state = Params::new() .hash_length(32) .to_state(); state.update(&SIAFUND_OUTPUT_ID_PREFIX); self.hash_no_sigs(&mut state); - state.update(&i.to_le_bytes()) - .finalize(); - let mut output_id = [0;32]; - output_id.copy_from_slice(&state.finalize().as_bytes()[..32]); - return output_id; + SiafundOutputID(state.update(&i.to_le_bytes()) + .finalize() + .as_bytes() + .try_into() + .unwrap()) } } impl SiaEncodable for Transaction { fn encode(&self, buf: &mut Vec) { - buf.extend_from_slice(&self.siacoin_inputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in &self.siacoin_inputs { input.encode(buf); } - buf.extend_from_slice(&self.siacoin_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siacoin_outputs.len() as u64).to_le_bytes()); for output in &self.siacoin_outputs { output.encode(buf); } - buf.extend_from_slice(&self.file_contracts.len().to_le_bytes()); + buf.extend_from_slice(&(self.file_contracts.len() as u64).to_le_bytes()); for file_contract in &self.file_contracts { file_contract.encode(buf); } - buf.extend_from_slice(&self.file_contract_revisions.len().to_le_bytes()); + buf.extend_from_slice(&(self.file_contract_revisions.len() as u64).to_le_bytes()); for file_contract_revision in &self.file_contract_revisions { file_contract_revision.encode(buf); } - buf.extend_from_slice(&self.storage_proofs.len().to_le_bytes()); + buf.extend_from_slice(&(self.storage_proofs.len() as u64).to_le_bytes()); for storage_proof in &self.storage_proofs { storage_proof.encode(buf); } - buf.extend_from_slice(&self.siafund_inputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in &self.siafund_inputs { input.encode(buf); } - buf.extend_from_slice(&self.siafund_outputs.len().to_le_bytes()); + buf.extend_from_slice(&(self.siafund_outputs.len() as u64).to_le_bytes()); for output in &self.siafund_outputs { output.encode(buf); } - buf.extend_from_slice(&self.miner_fees.len().to_le_bytes()); + buf.extend_from_slice(&(self.miner_fees.len() as u64).to_le_bytes()); for fee in &self.miner_fees { fee.encode(buf); } - buf.extend_from_slice(&self.arbitrary_data.len().to_le_bytes()); + 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().to_le_bytes()); + buf.extend_from_slice(&(data.len() as u64).to_le_bytes()); buf.extend_from_slice(data); } - buf.extend_from_slice(&self.signatures.len().to_le_bytes()); + buf.extend_from_slice(&(self.signatures.len() as u64).to_le_bytes()); for signature in &self.signatures { signature.encode(buf); } @@ -441,21 +588,21 @@ mod tests { #[test] fn test_transaction_id() { - let txn = Transaction{ - miner_fees: vec![], - siacoin_inputs: vec![], - siacoin_outputs: vec![], - siafund_inputs: vec![], - siafund_outputs: vec![], - file_contracts: vec![], - file_contract_revisions: vec![], - storage_proofs: vec![], - signatures: vec![], - arbitrary_data: vec![], - }; - - let id = txn.id(); + let txn = Transaction::default(); + assert_eq!(txn.id().to_string(), "txn:b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24") + } - assert_eq!(id, hex::decode("b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24").unwrap().as_slice()); + #[test] + fn test_txn_id() { + let txn = Transaction::default(); + let h = Params::new() + .hash_length(32) + .to_state() + .update(&txn.encode_no_sigs()) + .finalize(); + + 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), "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24"); } } \ No newline at end of file