diff --git a/Cargo.toml b/Cargo.toml index 8fc3046..f4d9328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,2 @@ -[package] -name = "blockchain_contracts" -version = "0.3.2" -authors = ["CoBloX developers "] -edition = "2018" -description = "Blockchain contracts used by COMIT-network daemons to execute cryptographic protocols." -homepage = "https://comit.network/" -repository = "https://github.com/comit-network/blockchain-contracts" -keywords = ["atomic-swaps", "blockchain", "cryptocurrencies", "comit", "htlc"] -categories = ["cryptography::cryptocurrencies"] -readme = "./README.md" -license-file = "./LICENSE.md" - -[dependencies] -byteorder = "1.3" -hex = "0.4" -hex-literal = "0.2" -itertools = "0.8.0" -regex = "1.3" -rust_bitcoin = { version = "0.23", package = "bitcoin", features = ["use-serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tiny-keccak = "2.0" - -[dev-dependencies] -failure = "0.1" -lazy_static = "1.4" -log = "0.4" -pretty_env_logger = "0.4" -reqwest = { version = "0.10", features = ["json", "blocking"] } -rust-crypto = "0.2" -spectral = "0.6" -testcontainers = "0.9" -tiny-keccak = { version = "2.0", features = ["keccak"] } -web3 = { version = "0.10", default-features = false, features = ["http"] } - -# These versions need to be changed together with web3, depends on what version of primitive-types ships with web3 -[dev-dependencies.primitive-types] -features = ["rlp"] -version = "0.5.0" -[dev-dependencies.rlp] -version = "0.4.2" - -# This dependency version is set by rust-bitcoin but we need the "recovery" feature on -[dev-dependencies.secp256k1] -features = ["recovery"] -version = "0.17.1" - - -[build-dependencies] -regex = "1.3" +[workspace] +members = ["lib", "print_offsets"] diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 0000000..b58b1a3 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "blockchain_contracts" +version = "0.4.0" +authors = ["CoBloX developers "] +edition = "2018" +description = "Blockchain contracts used by COMIT-network daemons to execute cryptographic protocols." +homepage = "https://comit.network/" +repository = "https://github.com/comit-network/blockchain-contracts" +keywords = ["atomic-swaps", "blockchain", "cryptocurrencies", "comit", "htlc"] +categories = ["cryptography::cryptocurrencies"] +readme = "../README.md" +license-file = "../LICENSE.md" + +[dependencies] +byteorder = "1" +hex-literal = "0.2" +regex = "1" +rust_bitcoin = { version = "0.25", package = "bitcoin" } + +[dev-dependencies] +failure = "0.1" +lazy_static = "1" +log = "0.4" +hex = "0.4" +pretty_env_logger = "0.4" +reqwest = { version = "0.10", features = ["json", "blocking"] } +rust-crypto = "0.2" +spectral = "0.6" +testcontainers = "0.9" +tiny-keccak = { version = "2", features = ["keccak"] } +web3 = { version = "0.10", default-features = false, features = ["http"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +rust_bitcoin = { version = "0.25", package = "bitcoin", features = ["use-serde"] } + +# These versions need to be changed together with web3, depends on what version of primitive-types ships with web3 +[dev-dependencies.primitive-types] +features = ["rlp"] +version = "0.5.0" +[dev-dependencies.rlp] +version = "0.4.2" + +# This dependency version is set by rust-bitcoin but we need the "recovery" feature on +[dev-dependencies.secp256k1] +features = ["recovery"] +version = "0.19" diff --git a/src/bitcoin/bitcoin_htlc.rs b/lib/src/bitcoin/hbit.rs similarity index 92% rename from src/bitcoin/bitcoin_htlc.rs rename to lib/src/bitcoin/hbit.rs index 75fb436..1df3deb 100644 --- a/src/bitcoin/bitcoin_htlc.rs +++ b/lib/src/bitcoin/hbit.rs @@ -14,25 +14,13 @@ use rust_bitcoin::{ // contract template RFC: https://github.com/comit-network/RFCs/blob/master/RFC-005-SWAP-Basic-Bitcoin.adoc#contract pub const CONTRACT_TEMPLATE: [u8;97] = hex!("6382012088a82010000000000000000000000000000000000000000000000000000000000000018876a9143000000000000000000000000000000000000003670420000002b17576a91440000000000000000000000000000000000000046888ac"); -#[derive(Clone, Copy, Debug)] -pub enum UnlockingError { - WrongSecret { - got: SecretHash, - expected: SecretHash, - }, - WrongPubkeyHash { - got: [u8; 20], - expected: [u8; 20], - }, -} - #[derive(Debug)] -pub struct BitcoinHtlc { +pub struct Htlc { script: Vec, expiry: u32, } -impl BitcoinHtlc { +impl Htlc { pub fn new( expiry: u32, refund_identity: hash160::Hash, @@ -45,7 +33,7 @@ impl BitcoinHtlc { BitcoinTimestamp(expiry).fit_into_placeholder_slice(&mut contract[65..69]); refund_identity.fit_into_placeholder_slice(&mut contract[74..94]); - BitcoinHtlc { + Htlc { script: contract, expiry, } @@ -115,7 +103,7 @@ mod tests { #[test] fn compiled_contract_is_same_length_as_template() { - let htlc = BitcoinHtlc::new( + let htlc = Htlc::new( 3_000_000, hash160::Hash::default(), hash160::Hash::default(), @@ -131,7 +119,7 @@ mod tests { #[test] fn given_input_data_when_compiled_should_contain_given_data() { - let htlc = BitcoinHtlc::new( + let htlc = Htlc::new( 2_000_000_000, hash160::Hash::default(), hash160::Hash::default(), diff --git a/lib/src/bitcoin/mod.rs b/lib/src/bitcoin/mod.rs new file mode 100644 index 0000000..b386e82 --- /dev/null +++ b/lib/src/bitcoin/mod.rs @@ -0,0 +1,2 @@ +pub mod hbit; +pub mod witness; diff --git a/src/bitcoin/witness/mod.rs b/lib/src/bitcoin/witness/mod.rs similarity index 96% rename from src/bitcoin/witness/mod.rs rename to lib/src/bitcoin/witness/mod.rs index 8645145..2dfcb2b 100644 --- a/src/bitcoin/witness/mod.rs +++ b/lib/src/bitcoin/witness/mod.rs @@ -8,11 +8,9 @@ pub const SEQUENCE_DISALLOW_NTIMELOCK_NO_RBF: u32 = 0xFFFF_FFFF; mod p2wpkh; mod primed_transaction; -mod pubkey_hash; pub use p2wpkh::UnlockP2wpkh; pub use primed_transaction::{Error, PrimedInput, PrimedTransaction}; -pub use pubkey_hash::PubkeyHash; use rust_bitcoin::{ secp256k1::{PublicKey, SecretKey}, diff --git a/src/bitcoin/witness/p2wpkh.rs b/lib/src/bitcoin/witness/p2wpkh.rs similarity index 87% rename from src/bitcoin/witness/p2wpkh.rs rename to lib/src/bitcoin/witness/p2wpkh.rs index f51aff4..3c935bd 100644 --- a/src/bitcoin/witness/p2wpkh.rs +++ b/lib/src/bitcoin/witness/p2wpkh.rs @@ -1,6 +1,7 @@ -use crate::bitcoin::witness::{PubkeyHash, UnlockParameters, Witness}; +use crate::bitcoin::witness::{UnlockParameters, Witness}; use rust_bitcoin::{ hashes::hash160, + hashes::Hash, secp256k1::{self, PublicKey, SecretKey}, Script, }; @@ -12,9 +13,7 @@ use rust_bitcoin::{ /// 19 76 a9 14 88 ac /// in the unlocking script. See BIP 143. /// This function simply returns the latter as a Script. -fn generate_prev_script(public_key_hash: PubkeyHash) -> Script { - let public_key_hash: hash160::Hash = public_key_hash.into(); - +fn generate_prev_script(public_key_hash: hash160::Hash) -> Script { let mut prev_script = vec![0x76, 0xa9, 0x14]; prev_script.append(&mut public_key_hash[..].to_vec()); @@ -41,7 +40,7 @@ impl UnlockP2wpkh for SecretKey { witness: vec![Witness::Signature(self), Witness::PublicKey(public_key)], sequence: super::SEQUENCE_ALLOW_NTIMELOCK_NO_RBF, locktime: 0, - prev_script: generate_prev_script(public_key.into()), + prev_script: generate_prev_script(hash160::Hash::hash(&public_key.serialize())), } } } diff --git a/src/bitcoin/witness/primed_transaction.rs b/lib/src/bitcoin/witness/primed_transaction.rs similarity index 92% rename from src/bitcoin/witness/primed_transaction.rs rename to lib/src/bitcoin/witness/primed_transaction.rs index 1dd8d63..87d91ce 100644 --- a/src/bitcoin/witness/primed_transaction.rs +++ b/lib/src/bitcoin/witness/primed_transaction.rs @@ -2,9 +2,10 @@ use crate::bitcoin::witness::{UnlockParameters, Witness}; use rust_bitcoin::{ hashes::Hash, secp256k1::{self, Message, Secp256k1}, - util::bip143::SighashComponents, + util::bip143::SigHashCache, Address, Amount, OutPoint, Script, SigHashType, Transaction, TxIn, TxOut, }; +use std::fmt; #[derive(Copy, Clone, Debug, PartialEq)] pub enum Error { @@ -12,6 +13,17 @@ pub enum Error { FeeHigherThanInputValue, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::OverflowingFee => write!(f, "provided fee causes overflow"), + Error::FeeHigherThanInputValue => write!(f, "fee is higher than input value"), + } + } +} + +impl std::error::Error for Error {} + #[derive(Clone, Debug, PartialEq)] pub struct PrimedInput { input_parameters: UnlockParameters, @@ -78,11 +90,12 @@ impl PrimedTransaction { let input_parameters = primed_input.input_parameters; for (j, witness) in input_parameters.witness.iter().enumerate() { if let Witness::Signature(secret_key) = witness { - let sighash_components = SighashComponents::new(transaction); - let hash_to_sign = sighash_components.sighash_all( - &transaction.input[i], + let mut sighash_cache = SigHashCache::new(transaction as &Transaction); + let hash_to_sign = sighash_cache.signature_hash( + i, &input_parameters.prev_script, primed_input.value.as_sat(), + SigHashType::All, ); // `from` should be used instead of `from_slice` once `ThirtyTwoByteHash` is // implemented for Hashes See https://github.com/rust-bitcoin/rust-secp256k1/issues/106 diff --git a/src/ethereum/erc20_htlc.rs b/lib/src/ethereum/herc20.rs similarity index 96% rename from src/ethereum/erc20_htlc.rs rename to lib/src/ethereum/herc20.rs index a26c4dd..7e16e85 100644 --- a/src/ethereum/erc20_htlc.rs +++ b/lib/src/ethereum/herc20.rs @@ -6,15 +6,15 @@ use hex_literal::hex; pub const CONTRACT_TEMPLATE: [u8;411] = hex!("61018c61000f60003961018c6000f3361561007957602036141561004f57602060006000376020602160206000600060026048f17f100000000000000000000000000000000000000000000000000000000000000160215114166100ae575b7f696e76616c69645365637265740000000000000000000000000000000000000060005260206000fd5b426320000002106100f1577f746f6f4561726c7900000000000000000000000000000000000000000000000060005260206000fd5b7f72656465656d656400000000000000000000000000000000000000000000000060206000a1733000000000000000000000000000000000000003602052610134565b7f726566756e64656400000000000000000000000000000000000000000000000060006000a1734000000000000000000000000000000000000004602052610134565b63a9059cbb6000527f5000000000000000000000000000000000000000000000000000000000000005604052602060606044601c6000736000000000000000000000000000000000000006620186a05a03f150602051ff"); #[derive(Debug, Clone)] -pub struct Erc20Htlc(Vec); +pub struct Htlc(Vec); -impl From for Vec { - fn from(htlc: Erc20Htlc) -> Self { +impl From for Vec { + fn from(htlc: Htlc) -> Self { htlc.0 } } -impl Erc20Htlc { +impl Htlc { pub fn new( expiry: u32, refund_identity: Address, @@ -31,7 +31,7 @@ impl Erc20Htlc { token_quantity.fit_into_placeholder_slice(&mut contract[333..365]); token_contract_address.fit_into_placeholder_slice(&mut contract[379..399]); - Erc20Htlc(contract) + Htlc(contract) } pub fn deploy_tx_gas_limit() -> u64 { @@ -89,7 +89,7 @@ mod tests { #[test] fn compiled_contract_is_same_length_as_template() { - let htlc = Erc20Htlc::new( + let htlc = Htlc::new( 3_000_000, Address([0u8; 20]), Address([0u8; 20]), @@ -107,7 +107,7 @@ mod tests { #[test] fn given_input_data_when_compiled_should_contain_given_data() { - let htlc = Erc20Htlc::new( + let htlc = Htlc::new( 2_000_000_000, Address([0u8; 20]), Address([0u8; 20]), @@ -145,7 +145,7 @@ mod tests { .unwrap(); let expiry = 1_552_263_040; - let htlc = Erc20Htlc::new( + let htlc = Htlc::new( expiry, Address(refund_identity), Address(redeem_identity), diff --git a/src/ethereum/ether_htlc.rs b/lib/src/ethereum/heth.rs similarity index 95% rename from src/ethereum/ether_htlc.rs rename to lib/src/ethereum/heth.rs index c213df7..8f7b9d5 100644 --- a/src/ethereum/ether_htlc.rs +++ b/lib/src/ethereum/heth.rs @@ -6,15 +6,15 @@ use hex_literal::hex; pub const CONTRACT_TEMPLATE: [u8;311] = hex!("61012861000f6000396101286000f3361561007957602036141561004f57602060006000376020602160206000600060026048f17f100000000000000000000000000000000000000000000000000000000000000160215114166100ae575b7f696e76616c69645365637265740000000000000000000000000000000000000060005260206000fd5b426320000002106100eb577f746f6f4561726c7900000000000000000000000000000000000000000000000060005260206000fd5b7f72656465656d656400000000000000000000000000000000000000000000000060206000a1733000000000000000000000000000000000000003ff5b7f726566756e64656400000000000000000000000000000000000000000000000060006000a1734000000000000000000000000000000000000004ff"); #[derive(Debug)] -pub struct EtherHtlc(Vec); +pub struct Htlc(Vec); -impl From for Vec { - fn from(htlc: EtherHtlc) -> Self { +impl From for Vec { + fn from(htlc: Htlc) -> Self { htlc.0 } } -impl EtherHtlc { +impl Htlc { pub fn new( expiry: u32, refund_identity: Address, @@ -27,7 +27,7 @@ impl EtherHtlc { redeem_identity.fit_into_placeholder_slice(&mut contract[229..249]); refund_identity.fit_into_placeholder_slice(&mut contract[290..310]); - EtherHtlc(contract) + Htlc(contract) } pub fn deploy_tx_gas_limit() -> u64 { @@ -64,7 +64,7 @@ mod tests { #[test] fn compiled_contract_is_same_length_as_template() { - let htlc = EtherHtlc::new( + let htlc = Htlc::new( 3_000_000, Address([0u8; 20]), Address([0u8; 20]), @@ -80,7 +80,7 @@ mod tests { #[test] fn given_input_data_when_compiled_should_contain_given_data() { - let htlc = EtherHtlc::new( + let htlc = Htlc::new( 2_000_000_000, Address([0u8; 20]), Address([0u8; 20]), @@ -110,7 +110,7 @@ mod tests { .unwrap(); let expiry = 1_552_263_040; - let htlc = EtherHtlc::new( + let htlc = Htlc::new( expiry, Address(refund_identity), Address(redeem_identity), diff --git a/src/ethereum/mod.rs b/lib/src/ethereum/mod.rs similarity index 95% rename from src/ethereum/mod.rs rename to lib/src/ethereum/mod.rs index 9a7c607..f35e25c 100644 --- a/src/ethereum/mod.rs +++ b/lib/src/ethereum/mod.rs @@ -1,8 +1,5 @@ -pub mod erc20_htlc; -pub mod ether_htlc; - -pub use erc20_htlc::*; -pub use ether_htlc::*; +pub mod herc20; +pub mod heth; /// The log message emitted when the HTLC is redeemed. /// diff --git a/src/fit_into_placeholder_slice.rs b/lib/src/fit_into_placeholder_slice.rs similarity index 100% rename from src/fit_into_placeholder_slice.rs rename to lib/src/fit_into_placeholder_slice.rs diff --git a/src/lib.rs b/lib/src/lib.rs similarity index 100% rename from src/lib.rs rename to lib/src/lib.rs diff --git a/tests/bitcoin_helper/mod.rs b/lib/tests/bitcoin_helper/mod.rs similarity index 83% rename from tests/bitcoin_helper/mod.rs rename to lib/tests/bitcoin_helper/mod.rs index c49b300..e42d8a8 100644 --- a/tests/bitcoin_helper/mod.rs +++ b/lib/tests/bitcoin_helper/mod.rs @@ -1,6 +1,7 @@ -#![warn(unused_extern_crates, missing_debug_implementations, rust_2018_idioms)] +#![warn(unused_extern_crates, missing_debug_implementations)] #![forbid(unsafe_code)] +use anyhow::{Context, Result}; use rust_bitcoin::hashes::hex::FromHex; use rust_bitcoin::Txid; use rust_bitcoin::{ @@ -34,10 +35,12 @@ pub struct RpcError { impl std::fmt::Display for RpcError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "RPC failed with: {}", self.message) } } +impl std::error::Error for RpcError {} + impl JsonRpcRequest { fn new(method: &str, params: T) -> Self { Self { @@ -49,8 +52,8 @@ impl JsonRpcRequest { } } -fn serialize(t: T) -> Result { - let value = serde_json::to_value(t).map_err(Error::Serialize)?; +fn serialize(t: T) -> Result { + let value = serde_json::to_value(t).context("failed to serialize value")?; Ok(value) } @@ -61,46 +64,12 @@ pub struct Client { auth: RpcAuth, } -#[derive(Debug)] -pub enum Error { - Address(rust_bitcoin::util::address::Error), - Reqwest(reqwest::Error), - Encode(rust_bitcoin::consensus::encode::Error), - Rpc(RpcError), - Serialize(serde_json::Error), - Hash(rust_bitcoin::hashes::hex::Error), -} - -impl From for Error { - fn from(err: reqwest::Error) -> Self { - Error::Reqwest(err) - } -} - -impl From for Error { - fn from(err: rust_bitcoin::util::address::Error) -> Self { - Error::Address(err) - } -} - -impl From for Error { - fn from(err: rust_bitcoin::consensus::encode::Error) -> Self { - Error::Encode(err) - } -} - -impl From for Error { - fn from(err: rust_bitcoin::hashes::hex::Error) -> Self { - Error::Hash(err) - } -} - impl Client { pub fn new(endpoint: String, auth: RpcAuth) -> Client { Client { endpoint, auth } } - pub fn get_new_address(&self) -> Result { + pub fn get_new_address(&self) -> Result
{ let request = JsonRpcRequest::>::new("getnewaddress", Vec::new()); Ok(reqwest::blocking::Client::new() @@ -113,7 +82,7 @@ impl Client { .expect("getnewaddress response result is null")) } - pub fn generate(&self, num: u32) -> Result<(), Error> { + pub fn generate(&self, num: u32) -> Result<()> { let request = JsonRpcRequest::new("generate", vec![serialize(num)?]); let _ = reqwest::blocking::Client::new() @@ -125,7 +94,7 @@ impl Client { Ok(()) } - pub fn send_raw_transaction(&self, hex: String) -> Result { + pub fn send_raw_transaction(&self, hex: String) -> Result { let request = JsonRpcRequest::new("sendrawtransaction", vec![serialize(hex)?]); let response = reqwest::blocking::Client::new() @@ -139,7 +108,7 @@ impl Client { JsonRpcResponse { result: None, error: Some(error), - } => Err(Error::Rpc(error)), + } => Err(error.into()), JsonRpcResponse { result: Some(result), error: None, @@ -148,7 +117,7 @@ impl Client { } } - pub fn get_raw_transaction(&self, txid: &Txid) -> Result { + pub fn get_raw_transaction(&self, txid: &Txid) -> Result { let request = JsonRpcRequest::new("getrawtransaction", vec![serialize(txid)?]); let response: JsonRpcResponse = reqwest::blocking::Client::new() @@ -165,7 +134,7 @@ impl Client { )?)?) } - pub fn get_blockchain_info(&self) -> Result { + pub fn get_blockchain_info(&self) -> Result { let request = JsonRpcRequest::>::new("getblockchaininfo", vec![]); Ok(reqwest::blocking::Client::new() @@ -178,7 +147,7 @@ impl Client { .expect("getblockchaininfo response result is null")) } - pub fn list_unspent(&self, addresses: Option<&[Address]>) -> Result, Error> { + pub fn list_unspent(&self, addresses: Option<&[Address]>) -> Result> { let request = JsonRpcRequest::new( "listunspent", vec![ @@ -198,7 +167,7 @@ impl Client { .expect("list_unspent response result is null")) } - pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result { + pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result { let request = JsonRpcRequest::new( "sendtoaddress", vec![serialize(address)?, serialize(amount.as_btc())?], @@ -266,7 +235,8 @@ impl Client { key: public_key, }, Network::Regtest, - ); + ) + .unwrap(); let txid = self.send_to_address(&address, amount).unwrap(); diff --git a/tests/bitcoin_witness_p2wpkh.rs b/lib/tests/bitcoin_witness_p2wpkh.rs similarity index 100% rename from tests/bitcoin_witness_p2wpkh.rs rename to lib/tests/bitcoin_witness_p2wpkh.rs diff --git a/tests/bitcoin_witness_sign_with_rate.rs b/lib/tests/bitcoin_witness_sign_with_rate.rs similarity index 100% rename from tests/bitcoin_witness_sign_with_rate.rs rename to lib/tests/bitcoin_witness_sign_with_rate.rs diff --git a/tests/ethereum_helper/mod.rs b/lib/tests/ethereum_helper/mod.rs similarity index 100% rename from tests/ethereum_helper/mod.rs rename to lib/tests/ethereum_helper/mod.rs diff --git a/tests/ethereum_helper/tc_web3_client.rs b/lib/tests/ethereum_helper/tc_web3_client.rs similarity index 100% rename from tests/ethereum_helper/tc_web3_client.rs rename to lib/tests/ethereum_helper/tc_web3_client.rs diff --git a/tests/ethereum_helper/to_ethereum_address.rs b/lib/tests/ethereum_helper/to_ethereum_address.rs similarity index 100% rename from tests/ethereum_helper/to_ethereum_address.rs rename to lib/tests/ethereum_helper/to_ethereum_address.rs diff --git a/tests/ethereum_helper/transaction.rs b/lib/tests/ethereum_helper/transaction.rs similarity index 100% rename from tests/ethereum_helper/transaction.rs rename to lib/tests/ethereum_helper/transaction.rs diff --git a/tests/ethereum_helper/wallet.rs b/lib/tests/ethereum_helper/wallet.rs similarity index 100% rename from tests/ethereum_helper/wallet.rs rename to lib/tests/ethereum_helper/wallet.rs diff --git a/tests/bitcoin_htlc.rs b/lib/tests/hbit.rs similarity index 86% rename from tests/bitcoin_htlc.rs rename to lib/tests/hbit.rs index ee9aa4a..768711b 100644 --- a/tests/bitcoin_htlc.rs +++ b/lib/tests/hbit.rs @@ -13,8 +13,8 @@ use crate::{ }; use bitcoin_helper::new_tc_bitcoincore_client; use blockchain_contracts::bitcoin::{ + hbit::Htlc, witness::{PrimedInput, PrimedTransaction, UnlockParameters, Witness}, - BitcoinHtlc, }; use rust_bitcoin::Txid; use rust_bitcoin::{ @@ -32,10 +32,10 @@ use testcontainers::{clients::Cli, images::coblox_bitcoincore::BitcoinCore, Dock /// except that we want to insert our "CustomSizeSecret" on the witness /// stack. /// -/// [method]: blockchain_contracts::bitcoin::bitcoin_htlc:: +/// [method]: blockchain_contracts::bitcoin::hbit::bitcoin_htlc:: /// BitcoinHtlc#unlock_with_secret fn unlock_with_custom_size_secret( - htlc: BitcoinHtlc, + htlc: Htlc, secret_key: SecretKey, custom_size_secret: CustomSizeSecret, ) -> UnlockParameters { @@ -86,7 +86,7 @@ fn fund_htlc( Txid, OutPoint, Amount, - BitcoinHtlc, + Htlc, Timestamp, SecretKey, SecretKey, @@ -108,7 +108,7 @@ fn fund_htlc( let refund_timestamp = Timestamp::from(current_time).plus(5); let amount = Amount::from_sat(100_000_001); - let htlc = BitcoinHtlc::new( + let htlc = Htlc::new( refund_timestamp.into(), refund_pubkey_hash, redeem_pubkey_hash, @@ -202,18 +202,17 @@ fn refund_htlc() { let error = client .send_raw_transaction(refund_tx_hex.clone()) - .unwrap_err(); - - match error { - bitcoin_helper::Error::Rpc(error) => assert_eq!( - error, - RpcError { - code: -26, - message: "non-final (code 64)".to_string() - } - ), - _ => panic!(format!("Unexpected error received: {:?}", error)), - } + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!( + error, + RpcError { + code: -26, + message: "non-final (code 64)".to_string() + } + ); loop { let time = client.get_blockchain_info().unwrap().mediantime; @@ -269,18 +268,19 @@ fn redeem_htlc_with_long_secret() { let rpc_redeem_txid = client.send_raw_transaction(redeem_tx_hex); - let error = assert_that(&rpc_redeem_txid).is_err().subject; - - match error { - bitcoin_helper::Error::Rpc(error) => assert_eq!( - *error, - RpcError { - code: -26, - message: "non-mandatory-script-verify-flag (Script failed an OP_EQUALVERIFY operation) (code 64)".to_string() - } - ), - _ => panic!(format!("Unexpected error received: {:?}", error)), - } + let error = assert_that(&rpc_redeem_txid) + .is_err() + .subject + .downcast_ref::() + .unwrap(); + + assert_eq!( + *error, + RpcError { + code: -26, + message: "non-mandatory-script-verify-flag (Script failed an OP_EQUALVERIFY operation) (code 64)".to_string() + } + ) } #[test] @@ -315,16 +315,17 @@ fn redeem_htlc_with_short_secret() { let rpc_redeem_txid = client.send_raw_transaction(redeem_tx_hex); - let error = assert_that(&rpc_redeem_txid).is_err().subject; - - match error { - bitcoin_helper::Error::Rpc(error) => assert_eq!( - *error, - RpcError { - code: -26, - message: "non-mandatory-script-verify-flag (Script failed an OP_EQUALVERIFY operation) (code 64)".to_string() - } - ), - _ => panic!(format!("Unexpected error received: {:?}", error)), - } + let error = assert_that(&rpc_redeem_txid) + .is_err() + .subject + .downcast_ref::() + .unwrap(); + + assert_eq!( + *error, + RpcError { + code: -26, + message: "non-mandatory-script-verify-flag (Script failed an OP_EQUALVERIFY operation) (code 64)".to_string() + } + ) } diff --git a/tests/erc20_htlc.rs b/lib/tests/herc20.rs similarity index 95% rename from tests/erc20_htlc.rs rename to lib/tests/herc20.rs index bca588c..5c54de2 100644 --- a/tests/erc20_htlc.rs +++ b/lib/tests/herc20.rs @@ -13,7 +13,7 @@ use crate::{ }; use crate::parity_client::ParityClient; -use blockchain_contracts::ethereum::erc20_htlc::Erc20Htlc; +use blockchain_contracts::ethereum::herc20::Htlc; use blockchain_contracts::ethereum::Address; use blockchain_contracts::ethereum::TokenQuantity; use blockchain_contracts::ethereum::INVALID_SECRET; @@ -50,7 +50,7 @@ fn given_erc20_token_should_deploy_erc20_htlc_and_fund_htlc() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -76,7 +76,7 @@ fn given_erc20_token_should_deploy_erc20_htlc_and_fund_htlc() { let transaction_receipt = client.send_data( htlc_address, Some(Bytes(SECRET.to_vec())), - Erc20Htlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!("used gas ERC20 redeem {:?}", transaction_receipt.gas_used); @@ -108,7 +108,7 @@ fn given_funded_erc20_htlc_when_redeemed_with_secret_then_tokens_are_transferred to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -130,7 +130,7 @@ fn given_funded_erc20_htlc_when_redeemed_with_secret_then_tokens_are_transferred let transaction_receipt = client.send_data( htlc_address, Some(Bytes(SECRET.to_vec())), - U256::from(Erc20Htlc::redeem_tx_gas_limit()), + U256::from(Htlc::redeem_tx_gas_limit()), ); log::debug!("used gas ERC20 redeemed {:?}", transaction_receipt.gas_used); @@ -163,7 +163,7 @@ fn given_deployed_erc20_htlc_when_refunded_after_expiry_time_then_tokens_are_ref to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -184,7 +184,7 @@ fn given_deployed_erc20_htlc_when_refunded_after_expiry_time_then_tokens_are_ref // Wait for the contract to expire sleep_until(harness_params.htlc_refund_timestamp); let transaction_receipt = - client.send_data(htlc_address, None, Erc20Htlc::refund_tx_gas_limit().into()); + client.send_data(htlc_address, None, Htlc::refund_tx_gas_limit().into()); log::debug!("used gas ERC20 refund {:?}", transaction_receipt.gas_used); assert_eq!( @@ -218,7 +218,7 @@ fn given_deployed_erc20_htlc_when_expiry_time_not_yet_reached_should_revert_tx_w to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -238,7 +238,7 @@ fn given_deployed_erc20_htlc_when_expiry_time_not_yet_reached_should_revert_tx_w // Don't wait for the timeout and don't send a secret let transaction_receipt = - client.send_data(htlc_address, None, Erc20Htlc::refund_tx_gas_limit().into()); + client.send_data(htlc_address, None, Htlc::refund_tx_gas_limit().into()); log::debug!( "used gas ERC20 refund too early {:?}", transaction_receipt.gas_used @@ -277,7 +277,7 @@ fn given_not_enough_tokens_when_redeemed_token_balances_dont_change() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -299,7 +299,7 @@ fn given_not_enough_tokens_when_redeemed_token_balances_dont_change() { let transaction_receipt = client.send_data( htlc_address, Some(Bytes(SECRET.to_vec())), - Erc20Htlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!( "used gas ERC20 redeemed not enough token {:?}", @@ -332,7 +332,7 @@ fn given_htlc_and_redeem_should_emit_redeem_log_msg_with_secret() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -380,7 +380,7 @@ fn given_htlc_and_refund_should_emit_refund_log_msg() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -439,7 +439,7 @@ fn given_funded_erc20_htlc_when_redeemed_with_short_secret_should_revert_with_er to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -461,7 +461,7 @@ fn given_funded_erc20_htlc_when_redeemed_with_short_secret_should_revert_with_er let transaction_receipt = client.send_data( htlc_address, Some(Bytes(vec![1u8, 2u8, 3u8, 4u8, 6u8, 6u8, 7u8, 9u8, 10u8])), - Erc20Htlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!( @@ -505,7 +505,7 @@ fn given_correct_zero_secret_htlc_should_redeem() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -527,7 +527,7 @@ fn given_correct_zero_secret_htlc_should_redeem() { let transaction_receipt = client.send_data( htlc_address, Some(Bytes(secret_vec)), - Erc20Htlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!("used gas ERC20 redeem {:?}", transaction_receipt.gas_used); @@ -568,7 +568,7 @@ fn given_short_zero_secret_htlc_should_revert_tx_with_error() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) @@ -593,7 +593,7 @@ fn given_short_zero_secret_htlc_should_revert_tx_with_error() { 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, ])), - Erc20Htlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!( @@ -633,7 +633,7 @@ fn given_invalid_secret_htlc_should_revert_tx_with_error() { to: Some(token_contract), value: U256::from(0), data: Some( - Erc20Htlc::transfer_erc20_tx_payload( + Htlc::transfer_erc20_tx_payload( TokenQuantity(token_amount.into()), Address(htlc_address.into()), ) diff --git a/tests/ether_htlc.rs b/lib/tests/heth.rs similarity index 94% rename from tests/ether_htlc.rs rename to lib/tests/heth.rs index 2fbb180..98201ce 100644 --- a/tests/ether_htlc.rs +++ b/lib/tests/heth.rs @@ -11,7 +11,7 @@ use crate::htlc_harness::{ use blockchain_contracts::ethereum::REDEEMED_LOG_MSG; use blockchain_contracts::ethereum::REFUNDED_LOG_MSG; use blockchain_contracts::ethereum::TOO_EARLY; -use blockchain_contracts::ethereum::{EtherHtlc, INVALID_SECRET}; +use blockchain_contracts::ethereum::{heth::Htlc, INVALID_SECRET}; use parity_client::ParityClient; use serde_json::json; use spectral::prelude::*; @@ -36,7 +36,7 @@ fn given_deployed_htlc_when_redeemed_with_secret_then_money_is_transferred() { let transaction_receipt = client.send_data( htlc, Some(Bytes(SECRET.to_vec())), - U256::from(EtherHtlc::redeem_tx_gas_limit()), + U256::from(Htlc::redeem_tx_gas_limit()), ); log::debug!("used gas ETH redeem {:?}", transaction_receipt.gas_used); @@ -68,8 +68,7 @@ fn given_deployed_htlc_when_refunded_after_expiry_time_then_money_is_refunded() // Wait for the contract to expire sleep_until(harness_params.htlc_refund_timestamp); - let transaction_receipt = - client.send_data(htlc, None, U256::from(EtherHtlc::refund_tx_gas_limit())); + let transaction_receipt = client.send_data(htlc, None, U256::from(Htlc::refund_tx_gas_limit())); log::debug!("used gas ETH refund {:?}", transaction_receipt.gas_used); assert_eq!(client.eth_balance_of(bob), U256::from(0)); @@ -92,8 +91,7 @@ fn given_deployed_htlc_when_refunded_too_early_should_revert_tx_with_error() { ); // Don't wait for the timeout and don't send a secret - let transaction_receipt = - client.send_data(htlc, None, U256::from(EtherHtlc::refund_tx_gas_limit())); + let transaction_receipt = client.send_data(htlc, None, U256::from(Htlc::refund_tx_gas_limit())); log::debug!("used gas ETH too early {:?}", transaction_receipt.gas_used); // Check refund did not happen @@ -115,7 +113,7 @@ fn given_htlc_and_redeem_should_emit_redeem_log_msg_with_secret() { let transaction_receipt = client.send_data( htlc, Some(Bytes(SECRET.to_vec())), - EtherHtlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); log::debug!("used gas ETH redeem {:?}", transaction_receipt.gas_used); } @@ -129,7 +127,7 @@ fn given_htlc_and_refund_should_emit_refund_log_msg() { // Wait for the timelock to expire sleep_until(harness_params.htlc_refund_timestamp); - let transaction_receipt = client.send_data(htlc, None, EtherHtlc::refund_tx_gas_limit().into()); + let transaction_receipt = client.send_data(htlc, None, Htlc::refund_tx_gas_limit().into()); let topic: H256 = REFUNDED_LOG_MSG.parse().unwrap(); let Log { topics, data, .. } = assert_that(&transaction_receipt.logs) @@ -200,7 +198,7 @@ fn given_correct_zero_secret_htlc_should_redeem() { client.send_data( htlc, Some(Bytes(secret_vec)), - EtherHtlc::redeem_tx_gas_limit().into(), + Htlc::redeem_tx_gas_limit().into(), ); assert_eq!( diff --git a/tests/htlc_harness/erc20_harness.rs b/lib/tests/htlc_harness/erc20_harness.rs similarity index 95% rename from tests/htlc_harness/erc20_harness.rs rename to lib/tests/htlc_harness/erc20_harness.rs index 82b4e48..e18dba9 100644 --- a/tests/htlc_harness/erc20_harness.rs +++ b/lib/tests/htlc_harness/erc20_harness.rs @@ -3,7 +3,7 @@ use crate::{ htlc_harness::{new_account, timestamp::Timestamp, SECRET_HASH}, parity_client::ParityClient, }; -use blockchain_contracts::ethereum::Erc20Htlc; +use blockchain_contracts::ethereum::herc20::Htlc; use blockchain_contracts::ethereum::TokenQuantity; use std::sync::Arc; use testcontainers::{images::parity_parity::ParityEthereum, Container, Docker}; @@ -75,7 +75,7 @@ pub fn erc20_harness( alice_client.mint_tokens(token_contract, params.alice_initial_tokens, alice); - let erc20_htlc = Erc20Htlc::new( + let erc20_htlc = Htlc::new( params.htlc_refund_timestamp.into(), blockchain_contracts::ethereum::Address(alice.into()), blockchain_contracts::ethereum::Address(bob.into()), @@ -87,7 +87,7 @@ pub fn erc20_harness( let tx_id = alice_client.deploy_htlc( erc20_htlc.into(), U256::from(0), - Erc20Htlc::deploy_tx_gas_limit().into(), + Htlc::deploy_tx_gas_limit().into(), ); let transaction_receipt = alice_client.receipt(tx_id); diff --git a/tests/htlc_harness/ether_harness.rs b/lib/tests/htlc_harness/ether_harness.rs similarity index 95% rename from tests/htlc_harness/ether_harness.rs rename to lib/tests/htlc_harness/ether_harness.rs index 287712e..35a3900 100644 --- a/tests/htlc_harness/ether_harness.rs +++ b/lib/tests/htlc_harness/ether_harness.rs @@ -3,7 +3,7 @@ use crate::{ htlc_harness::{new_account, timestamp::Timestamp, SECRET_HASH}, parity_client::ParityClient, }; -use blockchain_contracts::ethereum::EtherHtlc; +use blockchain_contracts::ethereum::heth::Htlc; use std::sync::Arc; use testcontainers::{images::parity_parity::ParityEthereum, Container, Docker}; use web3::{ @@ -67,7 +67,7 @@ pub fn ether_harness( alice_client.give_eth_to(alice, params.alice_initial_wei); let tx_id = alice_client.deploy_htlc( - EtherHtlc::new( + Htlc::new( params.htlc_refund_timestamp.into(), blockchain_contracts::ethereum::Address(alice.into()), blockchain_contracts::ethereum::Address(bob.into()), @@ -75,7 +75,7 @@ pub fn ether_harness( ) .into(), params.htlc_wei_value, - EtherHtlc::deploy_tx_gas_limit().into(), + Htlc::deploy_tx_gas_limit().into(), ); let transaction_receipt = alice_client.receipt(tx_id); diff --git a/tests/htlc_harness/mod.rs b/lib/tests/htlc_harness/mod.rs similarity index 100% rename from tests/htlc_harness/mod.rs rename to lib/tests/htlc_harness/mod.rs diff --git a/tests/htlc_harness/timestamp.rs b/lib/tests/htlc_harness/timestamp.rs similarity index 100% rename from tests/htlc_harness/timestamp.rs rename to lib/tests/htlc_harness/timestamp.rs diff --git a/tests/parity_client/erc20_token_contract.asm.hex b/lib/tests/parity_client/erc20_token_contract.asm.hex similarity index 100% rename from tests/parity_client/erc20_token_contract.asm.hex rename to lib/tests/parity_client/erc20_token_contract.asm.hex diff --git a/tests/parity_client/mod.rs b/lib/tests/parity_client/mod.rs similarity index 100% rename from tests/parity_client/mod.rs rename to lib/tests/parity_client/mod.rs diff --git a/tests/parity_client/parity_client.rs b/lib/tests/parity_client/parity_client.rs similarity index 100% rename from tests/parity_client/parity_client.rs rename to lib/tests/parity_client/parity_client.rs diff --git a/print_offsets/Cargo.toml b/print_offsets/Cargo.toml new file mode 100644 index 0000000..4c62a04 --- /dev/null +++ b/print_offsets/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "print_offsets" +version = "0.1.0" +authors = ["CoBloX developers "] +edition = "2018" +description = "Commandline tool for printing the exact bytecode of the COMIT contracts." +readme = "./README.md" +license-file = "./LICENSE.md" + +[dependencies] +blockchain_contracts = { path = "../lib" } +serde_json = "1" +regex = "1" +byteorder = "1" +itertools = "0.8" +serde = { version = "1", features = ["derive"] } +hex = "0.4" +anyhow = "1" diff --git a/src/bin/calculate_offsets/bitcoin/templates/bitcoin/config.json b/print_offsets/hbit_template/config.json similarity index 89% rename from src/bin/calculate_offsets/bitcoin/templates/bitcoin/config.json rename to print_offsets/hbit_template/config.json index acba2ad..76de675 100644 --- a/src/bin/calculate_offsets/bitcoin/templates/bitcoin/config.json +++ b/print_offsets/hbit_template/config.json @@ -1,6 +1,5 @@ { - "ledger_name": "Bitcoin", - "asset_name": "Bitcoin", + "protocol_name": "hbit", "placeholders": [ { "name": "secret_hash", diff --git a/src/bin/calculate_offsets/bitcoin/templates/bitcoin/contract.script b/print_offsets/hbit_template/contract.script similarity index 100% rename from src/bin/calculate_offsets/bitcoin/templates/bitcoin/contract.script rename to print_offsets/hbit_template/contract.script diff --git a/src/bin/calculate_offsets/ethereum/templates/erc20/config.json b/print_offsets/herc20_template/config.json similarity index 93% rename from src/bin/calculate_offsets/ethereum/templates/erc20/config.json rename to print_offsets/herc20_template/config.json index 309c3a1..c56d4a8 100644 --- a/src/bin/calculate_offsets/ethereum/templates/erc20/config.json +++ b/print_offsets/herc20_template/config.json @@ -1,6 +1,5 @@ { - "ledger_name": "Ethereum", - "asset_name": "ERC20", + "protocol_name": "herc20", "placeholders": [ { "name": "secret_hash", diff --git a/src/bin/calculate_offsets/ethereum/templates/erc20/contract.asm b/print_offsets/herc20_template/contract.asm similarity index 100% rename from src/bin/calculate_offsets/ethereum/templates/erc20/contract.asm rename to print_offsets/herc20_template/contract.asm diff --git a/src/bin/calculate_offsets/ethereum/templates/erc20/deploy_header.asm b/print_offsets/herc20_template/deploy_header.asm similarity index 100% rename from src/bin/calculate_offsets/ethereum/templates/erc20/deploy_header.asm rename to print_offsets/herc20_template/deploy_header.asm diff --git a/src/bin/calculate_offsets/ethereum/templates/ether/config.json b/print_offsets/heth_template/config.json similarity index 89% rename from src/bin/calculate_offsets/ethereum/templates/ether/config.json rename to print_offsets/heth_template/config.json index 888ec4c..0400f56 100644 --- a/src/bin/calculate_offsets/ethereum/templates/ether/config.json +++ b/print_offsets/heth_template/config.json @@ -1,6 +1,5 @@ { - "ledger_name": "Ethereum", - "asset_name": "Ether", + "protocol_name": "heth", "placeholders": [ { "name": "secret_hash", diff --git a/src/bin/calculate_offsets/ethereum/templates/ether/contract.asm b/print_offsets/heth_template/contract.asm similarity index 100% rename from src/bin/calculate_offsets/ethereum/templates/ether/contract.asm rename to print_offsets/heth_template/contract.asm diff --git a/src/bin/calculate_offsets/ethereum/templates/ether/deploy_header.asm b/print_offsets/heth_template/deploy_header.asm similarity index 100% rename from src/bin/calculate_offsets/ethereum/templates/ether/deploy_header.asm rename to print_offsets/heth_template/deploy_header.asm diff --git a/src/bin/calculate_offsets/bitcoin/compile_contract.rs b/print_offsets/src/calculate_offsets/bitcoin/compile_contract.rs similarity index 63% rename from src/bin/calculate_offsets/bitcoin/compile_contract.rs rename to print_offsets/src/calculate_offsets/bitcoin/compile_contract.rs index f877176..ae8686d 100644 --- a/src/bin/calculate_offsets/bitcoin/compile_contract.rs +++ b/print_offsets/src/calculate_offsets/bitcoin/compile_contract.rs @@ -1,12 +1,13 @@ -use crate::calculate_offsets::{bitcoin::Error, check_bin_in_path}; +use crate::calculate_offsets::check_bin_in_path; +use anyhow::Context; +use anyhow::Result; use std::{ - ffi::OsStr, io::Write, path::Path, process::{Command, Stdio}, }; -pub fn compile>(file_path: S) -> Result, Error> { +pub fn compile>(file_path: S) -> Result> { check_bin_in_path("docker"); let mut bx = Command::new("docker") .arg("run") @@ -19,11 +20,13 @@ pub fn compile>(file_path: S) -> Result, Error> { .stderr(Stdio::null()) .spawn()?; - let input = std::fs::read(Path::new(&file_path))?; + let file_path = file_path.as_ref(); + let input = std::fs::read(file_path) + .with_context(|| format!("failed to open contract file {}", file_path.display()))?; let input = String::from_utf8(input)?; let input = input.replace("\n", " ").into_bytes(); - let stdin = bx.stdin.as_mut().ok_or(Error::CannotWriteInStdin)?; + let stdin = bx.stdin.as_mut().context("failed to write to stdin")?; stdin.write_all(&input)?; let output = bx.wait_with_output()?; diff --git a/print_offsets/src/calculate_offsets/bitcoin/mod.rs b/print_offsets/src/calculate_offsets/bitcoin/mod.rs new file mode 100644 index 0000000..2cd8f9d --- /dev/null +++ b/print_offsets/src/calculate_offsets/bitcoin/mod.rs @@ -0,0 +1,40 @@ +use crate::calculate_offsets::{ + metadata::Metadata, placeholder_config::PlaceholderConfig, Contract, +}; +use anyhow::Result; +use std::path::Path; + +mod compile_contract; + +pub struct BitcoinScript { + bytes: Vec, + placeholder_config: PlaceholderConfig, +} + +impl Contract for BitcoinScript { + fn compile>(template_folder: S) -> Result { + let bytes = compile_contract::compile(template_folder.as_ref().join("contract.script"))?; + let placeholder_config = + PlaceholderConfig::from_file(template_folder.as_ref().join("config.json"))?; + + Ok(Self { + bytes, + placeholder_config, + }) + } + + fn metadata(&self) -> Metadata { + Metadata { + protocol_name: self.placeholder_config.protocol_name.clone(), + contract: self.bytes.to_owned(), + } + } + + fn placeholder_config(&self) -> &PlaceholderConfig { + &self.placeholder_config + } + + fn bytes(&self) -> &[u8] { + self.bytes.as_slice() + } +} diff --git a/src/bin/calculate_offsets/ethereum/compile_contract.rs b/print_offsets/src/calculate_offsets/ethereum/compile_contract.rs similarity index 72% rename from src/bin/calculate_offsets/ethereum/compile_contract.rs rename to print_offsets/src/calculate_offsets/ethereum/compile_contract.rs index 9e8225f..c920676 100644 --- a/src/bin/calculate_offsets/ethereum/compile_contract.rs +++ b/print_offsets/src/calculate_offsets/ethereum/compile_contract.rs @@ -1,12 +1,13 @@ -use crate::calculate_offsets::{check_bin_in_path, ethereum::Error}; +use crate::calculate_offsets::check_bin_in_path; +use anyhow::{Context, Result}; use regex::Regex; +use std::path::Path; use std::{ env::var, - ffi::OsStr, process::{Command, Stdio}, }; -pub fn compile>(file_path: S) -> Result, Error> { +pub fn compile>(file_path: S) -> Result> { let solc_bin = var("SOLC_BIN"); let mut solc = match solc_bin { @@ -33,7 +34,9 @@ pub fn compile>(file_path: S) -> Result, Error> { } }; - let mut file = ::std::fs::File::open(OsStr::new(&file_path))?; + let file_path = file_path.as_ref(); + let mut file = ::std::fs::File::open(file_path) + .with_context(|| format!("failed to open contract file {}", file_path.display()))?; ::std::io::copy(&mut file, solc.stdin.as_mut().unwrap())?; @@ -45,7 +48,9 @@ pub fn compile>(file_path: S) -> Result, Error> { .captures(stdout.as_str()) .expect("Regex didn't match!"); - let hexcode = captures.name("hexcode").ok_or(Error::CaptureSolcBytecode)?; + let hexcode = captures + .name("hexcode") + .context("failed to find hex in solc output")?; let bytes = hex::decode(hexcode.as_str())?; Ok(bytes) diff --git a/src/bin/calculate_offsets/ethereum/contract.rs b/print_offsets/src/calculate_offsets/ethereum/contract.rs similarity index 72% rename from src/bin/calculate_offsets/ethereum/contract.rs rename to print_offsets/src/calculate_offsets/ethereum/contract.rs index 1eed520..24e1d91 100644 --- a/src/bin/calculate_offsets/ethereum/contract.rs +++ b/print_offsets/src/calculate_offsets/ethereum/contract.rs @@ -1,12 +1,14 @@ use crate::calculate_offsets::{ - calc_offset, concat_path, - ethereum::{compile_contract::compile, Error}, + calc_offset, + ethereum::compile_contract::compile, metadata::Metadata, placeholder_config::{Placeholder, PlaceholderConfig}, Contract, }; +use anyhow::Result; use byteorder::{BigEndian, ByteOrder}; -use std::{convert::TryFrom, ffi::OsStr}; +use std::convert::TryFrom; +use std::path::Path; pub struct EthereumContract { bytes: Vec, @@ -14,10 +16,7 @@ pub struct EthereumContract { } impl EthereumContract { - fn replace_contract_offset_parameters_in_header( - header: &mut [u8], - body: &[u8], - ) -> Result<(), Error> { + fn replace_contract_offset_parameters_in_header(header: &mut [u8], body: &[u8]) -> Result<()> { let body_length = body.len(); let header_length = header.len(); @@ -48,7 +47,7 @@ impl EthereumContract { name: &str, value: usize, header: &mut [u8], - ) -> Result<(), Error> { + ) -> Result<()> { let header_placeholder = Placeholder { name: name.into(), replace_pattern: replace_pattern.into(), @@ -66,18 +65,16 @@ impl EthereumContract { } impl Contract for EthereumContract { - type Error = crate::calculate_offsets::ethereum::Error; - - fn compile>(template_folder: S) -> Result { - let mut bytes = compile(concat_path(&template_folder, "deploy_header.asm"))?; - let mut contract_body = compile(concat_path(&template_folder, "contract.asm"))?; + fn compile>(template_folder: S) -> Result { + let mut bytes = compile(template_folder.as_ref().join("deploy_header.asm"))?; + let mut contract_body = compile(template_folder.as_ref().join("contract.asm"))?; Self::replace_contract_offset_parameters_in_header(&mut bytes, &contract_body)?; bytes.append(&mut contract_body); let placeholder_config = - PlaceholderConfig::from_file(concat_path(&template_folder, "config.json"))?; + PlaceholderConfig::from_file(template_folder.as_ref().join("config.json"))?; Ok(Self { bytes, @@ -87,8 +84,7 @@ impl Contract for EthereumContract { fn metadata(&self) -> Metadata { Metadata { - ledger_name: self.placeholder_config.ledger_name.to_owned(), - asset_name: self.placeholder_config.asset_name.to_owned(), + protocol_name: self.placeholder_config.protocol_name.clone(), contract: self.bytes.to_owned(), } } diff --git a/print_offsets/src/calculate_offsets/ethereum/mod.rs b/print_offsets/src/calculate_offsets/ethereum/mod.rs new file mode 100644 index 0000000..ceaf81f --- /dev/null +++ b/print_offsets/src/calculate_offsets/ethereum/mod.rs @@ -0,0 +1,4 @@ +mod compile_contract; +mod contract; + +pub use contract::EthereumContract; diff --git a/src/bin/calculate_offsets/metadata.rs b/print_offsets/src/calculate_offsets/metadata.rs similarity index 51% rename from src/bin/calculate_offsets/metadata.rs rename to print_offsets/src/calculate_offsets/metadata.rs index b6d5f37..1edd8b8 100644 --- a/src/bin/calculate_offsets/metadata.rs +++ b/print_offsets/src/calculate_offsets/metadata.rs @@ -1,15 +1,13 @@ pub struct Metadata { - pub ledger_name: String, - pub asset_name: String, + pub protocol_name: String, pub contract: Vec, } impl Metadata { pub fn to_markdown(&self) -> String { format!( - "** {} on {} **\nContract template:\n {}", - self.asset_name, - self.ledger_name, + "** {} **\nContract template:\n {}", + self.protocol_name, hex::encode(&self.contract) ) } diff --git a/src/bin/calculate_offsets/mod.rs b/print_offsets/src/calculate_offsets/mod.rs similarity index 71% rename from src/bin/calculate_offsets/mod.rs rename to print_offsets/src/calculate_offsets/mod.rs index f7c3d90..dd491bb 100644 --- a/src/bin/calculate_offsets/mod.rs +++ b/print_offsets/src/calculate_offsets/mod.rs @@ -3,7 +3,9 @@ use crate::calculate_offsets::{ offset::Offset, placeholder_config::{Placeholder, PlaceholderConfig}, }; -use std::{ffi::OsStr, path::PathBuf, process::Command}; +use anyhow::{Context, Result}; +use std::path::Path; +use std::process::Command; pub mod bitcoin; pub mod ethereum; @@ -11,28 +13,14 @@ pub mod metadata; pub mod offset; pub mod placeholder_config; -#[derive(Debug)] -pub enum Error { - PlaceholderNotFound(String), - Hex(hex::FromHexError), -} - -impl From for Error { - fn from(e: hex::FromHexError) -> Self { - Error::Hex(e) - } -} - pub trait Contract: std::marker::Sized { - type Error: From; - - fn compile>(template_folder: S) -> Result; + fn compile>(template_folder: S) -> Result; fn metadata(&self) -> Metadata; fn placeholder_config(&self) -> &PlaceholderConfig; fn bytes(&self) -> &[u8]; } -pub fn placeholder_offsets(contract: C) -> Result, Error> { +pub fn placeholder_offsets(contract: C) -> Result> { contract .placeholder_config() .placeholders @@ -41,14 +29,10 @@ pub fn placeholder_offsets(contract: C) -> Result, Erro .collect() } -fn concat_path>(folder: S, file: &str) -> PathBuf { - [OsStr::new(&folder), OsStr::new(file)].iter().collect() -} - -fn calc_offset(placeholder: &Placeholder, contract: &[u8]) -> Result { +fn calc_offset(placeholder: &Placeholder, contract: &[u8]) -> Result { let decoded_placeholder = hex::decode(placeholder.replace_pattern.as_str())?; let start_pos = find_subsequence(&contract[..], &decoded_placeholder[..]) - .ok_or_else(|| Error::PlaceholderNotFound(hex::encode(&decoded_placeholder)))?; + .with_context(|| format!("failed to find placeholder {}", placeholder.name))?; let end_pos = start_pos + decoded_placeholder.len(); Ok(Offset { diff --git a/src/bin/calculate_offsets/offset.rs b/print_offsets/src/calculate_offsets/offset.rs similarity index 100% rename from src/bin/calculate_offsets/offset.rs rename to print_offsets/src/calculate_offsets/offset.rs diff --git a/print_offsets/src/calculate_offsets/placeholder_config.rs b/print_offsets/src/calculate_offsets/placeholder_config.rs new file mode 100644 index 0000000..6d70dab --- /dev/null +++ b/print_offsets/src/calculate_offsets/placeholder_config.rs @@ -0,0 +1,34 @@ +use anyhow::{Context, Result}; +use serde::Deserialize; +use std::path::Path; +use std::{fs::File, io::BufReader}; + +#[derive(Debug, Deserialize)] +pub struct PlaceholderConfig { + pub protocol_name: String, + pub placeholders: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct Placeholder { + pub name: String, + pub replace_pattern: String, +} + +impl PlaceholderConfig { + pub fn from_file>(file_path: S) -> Result { + let file_path = file_path.as_ref(); + let file = File::open(file_path).with_context(|| { + format!( + "failed to open placeholder config at {}", + file_path.display() + ) + })?; + let reader = BufReader::new(file); + + let config = + serde_json::from_reader(reader).context("failed to deserialize placeholder config")?; + + Ok(config) + } +} diff --git a/print_offsets/src/main.rs b/print_offsets/src/main.rs new file mode 100644 index 0000000..fa9543d --- /dev/null +++ b/print_offsets/src/main.rs @@ -0,0 +1,84 @@ +#![warn( + unused_extern_crates, + missing_debug_implementations, + missing_copy_implementations, + rust_2018_idioms, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::fallible_impl_from, + clippy::cast_precision_loss, + clippy::cast_possible_wrap, + clippy::print_stdout, + clippy::dbg_macro +)] +#![forbid(unsafe_code)] + +mod calculate_offsets; + +use crate::calculate_offsets::placeholder_offsets; +use crate::calculate_offsets::{ + bitcoin::BitcoinScript, ethereum::EthereumContract, offset::to_markdown, Contract, +}; +use anyhow::Result; +use std::path::Path; + +const HETH_TEMPLATE_FOLDER: &str = "./print_offsets/heth_template/"; +const HERC20_TEMPLATE_FOLDER: &str = "./print_offsets/herc20_template/"; +const HBIT_TEMPLATE_FOLDER: &str = "./print_offsets/hbit_template/"; + +#[allow(clippy::print_stdout)] +fn main() -> Result<()> { + println!( + "{}", + generate_markdown::(HBIT_TEMPLATE_FOLDER)? + ); + println!( + "{}", + generate_markdown::(HETH_TEMPLATE_FOLDER)? + ); + println!( + "{}", + generate_markdown::(HERC20_TEMPLATE_FOLDER)? + ); + + Ok(()) +} + +fn generate_markdown>(template_folder: S) -> Result { + let contract = C::compile(template_folder)?; + + let metadata = contract.metadata(); + let offsets = placeholder_offsets(contract)?; + + Ok(format!( + "{}\n{}", + metadata.to_markdown(), + to_markdown(offsets) + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use blockchain_contracts::ethereum::{herc20, heth}; + + #[test] + fn heth_contract_template_matches_template_in_calculate_offsets() -> Result<()> { + let contract = EthereumContract::compile(Path::new("..").join(HETH_TEMPLATE_FOLDER))?; + assert_eq!( + heth::CONTRACT_TEMPLATE.to_vec(), + contract.metadata().contract, + ); + Ok(()) + } + + #[test] + fn herc20_contract_template_matches_template_in_calculate_offsets() -> Result<()> { + let contract = EthereumContract::compile(Path::new("..").join(HERC20_TEMPLATE_FOLDER))?; + assert_eq!( + herc20::CONTRACT_TEMPLATE.to_vec(), + contract.metadata().contract, + ); + Ok(()) + } +} diff --git a/src/bin/calculate_offsets/bitcoin/mod.rs b/src/bin/calculate_offsets/bitcoin/mod.rs deleted file mode 100644 index 5d8cf46..0000000 --- a/src/bin/calculate_offsets/bitcoin/mod.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::calculate_offsets::{ - self, concat_path, - metadata::Metadata, - placeholder_config::{self, PlaceholderConfig}, - Contract, -}; -use std::{ffi::OsStr, path::Path, string::FromUtf8Error}; - -mod compile_contract; - -pub struct BitcoinScript { - bytes: Vec, - placeholder_config: PlaceholderConfig, -} - -#[derive(Debug)] -pub enum Error { - CalculateOffset(calculate_offsets::Error), - PlaceholderConfig(placeholder_config::Error), - Hex(hex::FromHexError), - IO(std::io::Error), - MalformedRegex(regex::Error), - CannotWriteInStdin, - MalformedInput(FromUtf8Error), -} - -impl From for Error { - fn from(err: calculate_offsets::Error) -> Self { - Error::CalculateOffset(err) - } -} - -impl From for Error { - fn from(err: placeholder_config::Error) -> Self { - Error::PlaceholderConfig(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Error::IO(err) - } -} - -impl From for Error { - fn from(e: regex::Error) -> Self { - Error::MalformedRegex(e) - } -} - -impl From for Error { - fn from(e: hex::FromHexError) -> Self { - Error::Hex(e) - } -} - -impl From for Error { - fn from(err: FromUtf8Error) -> Self { - Error::MalformedInput(err) - } -} - -impl Contract for BitcoinScript { - type Error = Error; - - fn compile>(template_folder: S) -> Result { - let bytes = compile_contract::compile(Path::new(&template_folder).join("contract.script"))?; - let placeholder_config = - PlaceholderConfig::from_file(concat_path(&template_folder, "config.json"))?; - - Ok(Self { - bytes, - placeholder_config, - }) - } - - fn metadata(&self) -> Metadata { - Metadata { - ledger_name: self.placeholder_config.ledger_name.to_owned(), - asset_name: self.placeholder_config.asset_name.to_owned(), - contract: self.bytes.to_owned(), - } - } - - fn placeholder_config(&self) -> &PlaceholderConfig { - &self.placeholder_config - } - - fn bytes(&self) -> &[u8] { - self.bytes.as_slice() - } -} diff --git a/src/bin/calculate_offsets/ethereum/mod.rs b/src/bin/calculate_offsets/ethereum/mod.rs deleted file mode 100644 index 8214a6b..0000000 --- a/src/bin/calculate_offsets/ethereum/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::calculate_offsets::{self, placeholder_config}; - -mod compile_contract; -pub mod contract; - -#[derive(Debug)] -pub enum Error { - IO(std::io::Error), - Hex(hex::FromHexError), - PlaceholderNotFound(String), - MalformedConfig(serde_json::Error), - CaptureSolcBytecode, - MalformedRegex(regex::Error), - NumberConversionFailed(std::num::TryFromIntError), -} - -impl From for Error { - fn from(e: hex::FromHexError) -> Self { - Error::Hex(e) - } -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IO(e) - } -} - -impl From for Error { - fn from(e: regex::Error) -> Self { - Error::MalformedRegex(e) - } -} - -impl From for Error { - fn from(e: std::num::TryFromIntError) -> Self { - Error::NumberConversionFailed(e) - } -} - -impl From for Error { - fn from(err: calculate_offsets::Error) -> Self { - match err { - calculate_offsets::Error::PlaceholderNotFound(placeholder) => { - Error::PlaceholderNotFound(placeholder) - } - calculate_offsets::Error::Hex(err) => Error::Hex(err), - } - } -} - -impl From for Error { - fn from(err: placeholder_config::Error) -> Self { - match err { - placeholder_config::Error::IO(err) => Error::IO(err), - placeholder_config::Error::MalformedConfig(err) => Error::MalformedConfig(err), - } - } -} diff --git a/src/bin/calculate_offsets/ethereum/templates/.gitignore b/src/bin/calculate_offsets/ethereum/templates/.gitignore deleted file mode 100644 index 89f9ac0..0000000 --- a/src/bin/calculate_offsets/ethereum/templates/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/src/bin/calculate_offsets/placeholder_config.rs b/src/bin/calculate_offsets/placeholder_config.rs deleted file mode 100644 index 5b98364..0000000 --- a/src/bin/calculate_offsets/placeholder_config.rs +++ /dev/null @@ -1,44 +0,0 @@ -use serde::Deserialize; -use std::{ffi::OsStr, fs::File, io::BufReader}; - -#[derive(Debug, Deserialize)] -pub struct PlaceholderConfig { - pub ledger_name: String, - pub asset_name: String, - pub placeholders: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct Placeholder { - pub name: String, - pub replace_pattern: String, -} - -#[derive(Debug)] -pub enum Error { - IO(std::io::Error), - MalformedConfig(serde_json::Error), -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IO(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Error::MalformedConfig(e) - } -} - -impl PlaceholderConfig { - pub fn from_file>(file_path: S) -> Result { - let file = File::open(OsStr::new(&file_path))?; - let reader = BufReader::new(file); - - let config = serde_json::from_reader(reader)?; - - Ok(config) - } -} diff --git a/src/bin/print_offsets.rs b/src/bin/print_offsets.rs deleted file mode 100644 index 95be9b9..0000000 --- a/src/bin/print_offsets.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![warn( - unused_extern_crates, - missing_debug_implementations, - missing_copy_implementations, - rust_2018_idioms, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::fallible_impl_from, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::print_stdout, - clippy::dbg_macro -)] -#![forbid(unsafe_code)] - -mod calculate_offsets; - -use self::calculate_offsets::{ - bitcoin::BitcoinScript, ethereum::contract::EthereumContract, offset::to_markdown, Contract, -}; -use crate::calculate_offsets::placeholder_offsets; -use std::ffi::OsStr; - -const ETHER_TEMPLATE_FOLDER: &str = "./src/bin/calculate_offsets/ethereum/templates/ether/"; -const ERC20_TEMPLATE_FOLDER: &str = "./src/bin/calculate_offsets/ethereum/templates/erc20/"; -const BITCOIN_TEMPLATE_FOLDER: &str = "./src/bin/calculate_offsets/bitcoin/templates/bitcoin/"; - -#[allow(clippy::print_stdout)] -fn main() -> Result<(), Error> { - println!("### RFC003 ###"); - - println!( - "{}", - generate_markdown::(BITCOIN_TEMPLATE_FOLDER)? - ); - println!( - "{}", - generate_markdown::(ETHER_TEMPLATE_FOLDER)? - ); - println!( - "{}", - generate_markdown::(ERC20_TEMPLATE_FOLDER)? - ); - - Ok(()) -} - -fn generate_markdown>(template_folder: S) -> Result { - let contract = C::compile(template_folder)?; - - let metadata = contract.metadata(); - let offsets = placeholder_offsets(contract)?; - - Ok(format!( - "{}\n{}", - metadata.to_markdown(), - to_markdown(offsets) - )) -} - -#[derive(Debug)] -enum Error { - BitcoinScript(self::calculate_offsets::bitcoin::Error), - EthereumContract(self::calculate_offsets::ethereum::Error), -} - -impl From for Error { - fn from(err: self::calculate_offsets::bitcoin::Error) -> Self { - Error::BitcoinScript(err) - } -} - -impl From for Error { - fn from(err: self::calculate_offsets::ethereum::Error) -> Self { - Error::EthereumContract(err) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use blockchain_contracts::ethereum::{erc20_htlc, ether_htlc}; - - #[test] - fn ether_contract_template_matches_template_in_calculate_offsets() -> Result<(), Error> { - let contract = EthereumContract::compile(ETHER_TEMPLATE_FOLDER)?; - assert_eq!( - ether_htlc::CONTRACT_TEMPLATE.to_vec(), - contract.metadata().contract, - ); - Ok(()) - } - - #[test] - fn erc20_contract_template_matches_template_in_calculate_offsets() -> Result<(), Error> { - let contract = EthereumContract::compile(ERC20_TEMPLATE_FOLDER)?; - assert_eq!( - erc20_htlc::CONTRACT_TEMPLATE.to_vec(), - contract.metadata().contract, - ); - Ok(()) - } -} diff --git a/src/bitcoin/mod.rs b/src/bitcoin/mod.rs deleted file mode 100644 index 7c852d8..0000000 --- a/src/bitcoin/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod bitcoin_htlc; -pub mod witness; - -pub use bitcoin_htlc::*; diff --git a/src/bitcoin/witness/pubkey_hash.rs b/src/bitcoin/witness/pubkey_hash.rs deleted file mode 100644 index 70fe292..0000000 --- a/src/bitcoin/witness/pubkey_hash.rs +++ /dev/null @@ -1,169 +0,0 @@ -use hex::{self, FromHex}; -use rust_bitcoin::{ - hashes::{hash160, Hash}, - secp256k1::{self, PublicKey, Secp256k1, SecretKey}, -}; -use serde::{ - de::{self, Deserialize, Deserializer}, - ser::{Serialize, Serializer}, -}; -use std::{ - convert::TryFrom, - fmt::{self, Display}, -}; - -#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct PubkeyHash(hash160::Hash); - -impl PubkeyHash { - #[allow(dead_code)] // Only used in tests at the moment - fn new(secp: &Secp256k1, secret_key: &SecretKey) -> Self { - secp256k1::PublicKey::from_secret_key(secp, secret_key).into() - } -} - -impl From for PubkeyHash { - fn from(hash: hash160::Hash) -> PubkeyHash { - PubkeyHash(hash) - } -} - -impl From for PubkeyHash { - fn from(public_key: PublicKey) -> PubkeyHash { - PubkeyHash( - ::hash( - &public_key.serialize(), - ), - ) - } -} - -impl<'a> TryFrom<&'a [u8]> for PubkeyHash { - type Error = rust_bitcoin::hashes::error::Error; - - fn try_from(value: &[u8]) -> Result { - Ok(PubkeyHash(hash160::Hash::from_slice(value)?)) - } -} - -#[derive(Clone, Copy, Debug)] -pub enum FromHexError { - HexConversion(hex::FromHexError), - HashConversion(rust_bitcoin::hashes::error::Error), -} - -impl From for FromHexError { - fn from(err: hex::FromHexError) -> Self { - FromHexError::HexConversion(err) - } -} - -impl From for FromHexError { - fn from(err: rust_bitcoin::hashes::error::Error) -> Self { - FromHexError::HashConversion(err) - } -} - -impl Display for FromHexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{:?}", &self) - } -} - -impl FromHex for PubkeyHash { - type Error = FromHexError; - - fn from_hex>(hex: T) -> Result { - Ok(PubkeyHash::try_from(hex::decode(hex)?.as_ref())?) - } -} - -impl AsRef<[u8]> for PubkeyHash { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} - -impl Into for PubkeyHash { - fn into(self) -> hash160::Hash { - self.0 - } -} - -impl fmt::LowerHex for PubkeyHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(format!("{:?}", self.0).as_str()) - } -} - -impl<'de> Deserialize<'de> for PubkeyHash { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - struct Visitor; - - impl<'vde> de::Visitor<'vde> for Visitor { - type Value = PubkeyHash; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - formatter.write_str("A hex-encoded compressed SECP256k1 public key") - } - - fn visit_str(self, hex_pubkey: &str) -> Result - where - E: de::Error, - { - PubkeyHash::from_hex(hex_pubkey).map_err(E::custom) - } - } - - deserializer.deserialize_str(Visitor) - } -} - -impl Serialize for PubkeyHash { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer, - { - serializer.serialize_str(hex::encode(self.0.into_inner()).as_str()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use rust_bitcoin::PrivateKey; - use std::str::FromStr; - - #[test] - fn correct_pubkeyhash_from_private_key() { - let secp = Secp256k1::signing_only(); - - let private_key = - PrivateKey::from_str("L253jooDhCtNXJ7nVKy7ijtns7vU4nY49bYWqUH8R9qUAUZt87of").unwrap(); - let pubkey_hash = PubkeyHash::new(&secp, &private_key.key); - - assert_eq!( - pubkey_hash, - PubkeyHash::try_from( - &hex::decode("8bc513e458372a3b3bb05818d09550295ce15949").unwrap()[..] - ) - .unwrap() - ) - } - - #[test] - fn roundtrip_serialization_of_pubkeyhash() { - let public_key = PublicKey::from_str( - "02c2a8efce029526d364c2cf39d89e3cdda05e5df7b2cbfc098b4e3d02b70b5275", - ) - .unwrap(); - let pubkey_hash: PubkeyHash = public_key.into(); - let serialized = serde_json::to_string(&pubkey_hash).unwrap(); - assert_eq!(serialized, "\"ac2db2f2615c81b83fe9366450799b4992931575\""); - let deserialized = serde_json::from_str::(serialized.as_str()).unwrap(); - assert_eq!(deserialized, pubkey_hash); - } -}