From a79d5a6598623b6e31e56106f97b395157a3b2eb Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Wed, 16 Oct 2024 16:43:28 +0100 Subject: [PATCH 01/19] fix: added-json-schema --- src/evm/evm_transaction.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evm/evm_transaction.rs b/src/evm/evm_transaction.rs index a1e1478..16d7656 100644 --- a/src/evm/evm_transaction.rs +++ b/src/evm/evm_transaction.rs @@ -1,12 +1,13 @@ use near_sdk::serde::{Deserialize, Serialize}; use rlp::RlpStream; +use schemars::JsonSchema; use crate::constants::EIP_1559_TYPE; use super::types::{AccessList, Address, Signature}; use super::utils::parse_eth_address; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub struct EVMTransaction { pub chain_id: u64, From 87d7ff2aecc2d7f4db96150e6ef4a9acba8a21f9 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Wed, 16 Oct 2024 18:27:27 +0100 Subject: [PATCH 02/19] fix: json schema for signature --- src/evm/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evm/types.rs b/src/evm/types.rs index c0de528..729defc 100644 --- a/src/evm/types.rs +++ b/src/evm/types.rs @@ -1,10 +1,11 @@ use near_sdk::serde::{Deserialize, Serialize}; +use schemars::JsonSchema; pub type Address = [u8; 20]; pub type AccessList = Vec<(Address, Vec<[u8; 32]>)>; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub struct Signature { pub v: u64, From 18591bfeea6248159c2ea18b5ac0ed4037b1f406 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Wed, 16 Oct 2024 18:32:24 +0100 Subject: [PATCH 03/19] update dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb12d20..9cc2d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ near = [] rlp = "0.6.1" hex = "0.4.3" borsh = { version = "1.0.0", features = ["derive"] } -near-sdk = { version = "5.3.0" } +near-sdk = { version = "5.3.0", features = ["schemars"] } serde-big-array = "0.5.1" bs58 = "0.5.1" serde = "1.0" From 8e2d097f457c198723aaf12f287df62040907c27 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 18 Oct 2024 12:53:00 +0100 Subject: [PATCH 04/19] fix: added json schema to missing structs --- Cargo.toml | 5 +- src/near/near_transaction.rs | 3 +- src/near/types/actions.rs | 145 ++++++++++++++++++++++++++++++++--- src/near/types/block_hash.rs | 7 +- src/near/types/integers.rs | 5 +- src/near/types/public_key.rs | 73 ++++++++++++++++-- 6 files changed, 210 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cc2d6c..1ef4f52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ overflow-checks = true [features] default = ["all"] all = ["near", "bitcoin", "evm"] -bitcoin = ["sha2", "schemars"] +bitcoin = ["sha2"] evm = [] near = [] @@ -29,12 +29,13 @@ rlp = "0.6.1" hex = "0.4.3" borsh = { version = "1.0.0", features = ["derive"] } near-sdk = { version = "5.3.0", features = ["schemars"] } +near-account-id = { version = "1.0.0", features = ["schemars"] } serde-big-array = "0.5.1" bs58 = "0.5.1" serde = "1.0" serde_json = "1.0" +schemars = { version = "0.8" } sha2 = { version = "0.10.8", optional = true } -schemars = { version = "0.8.11", optional = true } [dev-dependencies] # ethereum diff --git a/src/near/near_transaction.rs b/src/near/near_transaction.rs index 23bc461..1d96f69 100644 --- a/src/near/near_transaction.rs +++ b/src/near/near_transaction.rs @@ -1,10 +1,11 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{borsh, AccountId}; +use schemars::JsonSchema; use super::types::{Action, BlockHash, PublicKey, Signature, U64}; -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub struct NearTransaction { /// An account on which behalf transaction is signed diff --git a/src/near/types/actions.rs b/src/near/types/actions.rs index c544848..455743f 100644 --- a/src/near/types/actions.rs +++ b/src/near/types/actions.rs @@ -2,10 +2,21 @@ use crate::near::types::PublicKey; use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::AccountId; +use schemars::JsonSchema; use super::{U128, U64}; -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub enum Action { /// Create an (sub)account using a transaction `receiver_id` as an ID for @@ -22,17 +33,47 @@ pub enum Action { DeleteAccount(DeleteAccountAction), } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct CreateAccountAction {} -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct DeployContractAction { pub code: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct FunctionCallAction { pub method_name: String, @@ -41,13 +82,33 @@ pub struct FunctionCallAction { pub deposit: U128, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct TransferAction { pub deposit: U128, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct StakeAction { /// Amount of tokens to stake. @@ -56,7 +117,17 @@ pub struct StakeAction { pub public_key: PublicKey, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct AddKeyAction { /// A public key which will be associated with an access_key @@ -65,7 +136,17 @@ pub struct AddKeyAction { pub access_key: AccessKey, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct AccessKey { /// Nonce for this access key, used for tx nonce generation. When access key is created, nonce @@ -76,7 +157,17 @@ pub struct AccessKey { pub permission: AccessKeyPermission, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub enum AccessKeyPermission { FunctionCall(FunctionCallPermission), @@ -85,7 +176,17 @@ pub enum AccessKeyPermission { FullAccess, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct FunctionCallPermission { pub allowance: Option, @@ -93,14 +194,34 @@ pub struct FunctionCallPermission { pub method_names: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct DeleteKeyAction { /// A public key associated with the access_key to be deleted. pub public_key: PublicKey, } -#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + JsonSchema, +)] #[serde(crate = "near_sdk::serde")] pub struct DeleteAccountAction { pub beneficiary_id: AccountId, diff --git a/src/near/types/block_hash.rs b/src/near/types/block_hash.rs index 46eef41..aec51b2 100644 --- a/src/near/types/block_hash.rs +++ b/src/near/types/block_hash.rs @@ -1,11 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Deserializer, Serialize}; +use schemars::JsonSchema; use serde::de; -use serde_big_array::BigArray; -#[derive(Serialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -pub struct BlockHash(#[serde(with = "BigArray")] pub [u8; 32]); +#[derive(Serialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq, JsonSchema)] +pub struct BlockHash(pub [u8; 32]); impl From<[u8; 32]> for BlockHash { fn from(data: [u8; 32]) -> Self { diff --git a/src/near/types/integers.rs b/src/near/types/integers.rs index ef59aa1..ce4c74c 100644 --- a/src/near/types/integers.rs +++ b/src/near/types/integers.rs @@ -1,11 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Deserializer, Serialize}; +use schemars::JsonSchema; use std::fmt; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, JsonSchema)] pub struct U64(pub u64); -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, JsonSchema)] pub struct U128(pub u128); impl From for U64 { diff --git a/src/near/types/public_key.rs b/src/near/types/public_key.rs index f3515b4..3d03891 100644 --- a/src/near/types/public_key.rs +++ b/src/near/types/public_key.rs @@ -2,19 +2,19 @@ use crate::constants::{ED25519_PUBLIC_KEY_LENGTH, SECP256K1_PUBLIC_KEY_LENGTH}; use crate::near::utils::PublicKeyStrExt; use borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Deserializer, Serialize}; -use serde::de; -use serde_big_array::BigArray; +use schemars::JsonSchema; +use serde::de::{self}; +use serde::ser::{SerializeTuple, Serializer}; use std::io::{Error, Write}; -#[derive(Serialize, Deserialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -#[serde(crate = "near_sdk::serde")] -pub struct Secp256K1PublicKey(#[serde(with = "BigArray")] pub [u8; SECP256K1_PUBLIC_KEY_LENGTH]); +#[derive(BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct Secp256K1PublicKey(pub [u8; SECP256K1_PUBLIC_KEY_LENGTH]); -#[derive(Serialize, Deserialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +#[derive(Serialize, Deserialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub struct ED25519PublicKey(pub [u8; ED25519_PUBLIC_KEY_LENGTH]); -#[derive(Serialize, PartialEq, Eq, Debug, Clone)] +#[derive(Serialize, PartialEq, Eq, Debug, Clone, JsonSchema)] #[serde(crate = "near_sdk::serde")] pub enum PublicKey { /// 256 bit elliptic curve based public-key. @@ -156,6 +156,65 @@ impl<'de> Deserialize<'de> for PublicKey { } } +// Big Array +impl Serialize for Secp256K1PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_tuple(SECP256K1_PUBLIC_KEY_LENGTH)?; + for byte in &self.0 { + seq.serialize_element(byte)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for Secp256K1PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct Secp256K1PublicKeyVisitor; + + impl<'de> serde::de::Visitor<'de> for Secp256K1PublicKeyVisitor { + type Value = Secp256K1PublicKey; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(&format!( + "an array of {} bytes", + SECP256K1_PUBLIC_KEY_LENGTH + )) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut arr = [0u8; SECP256K1_PUBLIC_KEY_LENGTH]; + for i in 0..SECP256K1_PUBLIC_KEY_LENGTH { + arr[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &self))?; + } + Ok(Secp256K1PublicKey(arr)) + } + } + + deserializer.deserialize_tuple(SECP256K1_PUBLIC_KEY_LENGTH, Secp256K1PublicKeyVisitor) + } +} + +impl JsonSchema for Secp256K1PublicKey { + fn schema_name() -> String { + "Secp256K1PublicKey".to_owned() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + >::json_schema(gen) + } +} + #[cfg(test)] mod tests { use super::*; From 85a1a4ce06a1d6621cc85cb28cb49f56e84674c5 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Thu, 24 Oct 2024 08:31:44 +0100 Subject: [PATCH 05/19] p2wpk works --- tests/bitcoin_integration_test.rs | 243 +++++++++++++++++++++++++++--- 1 file changed, 218 insertions(+), 25 deletions(-) diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 440b4dc..8c2f32c 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -1,11 +1,14 @@ +use bitcoin::consensus::Encodable; // Rust Bitcoin use bitcoin::hashes::{sha256d, Hash}; use bitcoin::script::Builder; use bitcoin::secp256k1::{Message, Secp256k1}; -use bitcoin::Address; -use bitcoin::EcdsaSighashType; -use bitcoin::ScriptBuf; -use bitcoin::Witness; +use bitcoin::sighash::SighashCache; +use bitcoin::{absolute, transaction, Address, Amount, Transaction, TxIn}; +use bitcoin::{EcdsaSighashType, OutPoint}; +use bitcoin::{ScriptBuf, Sequence}; +use bitcoin::{TxOut, Witness}; +use bitcoind::AddressType; // Omni library use omni_transaction::bitcoin::bitcoin_transaction::BitcoinTransaction; use omni_transaction::bitcoin::types::{ @@ -19,6 +22,7 @@ use omni_transaction::transaction_builder::TxBuilder; use omni_transaction::types::BITCOIN; // Testing use eyre::Result; +// use serde::Serialize; use serde_json::json; use std::result::Result::Ok; use tempfile::TempDir; @@ -28,6 +32,7 @@ mod utils; pub use utils::bitcoin_utils::*; const OMNI_SPEND_AMOUNT: OmniAmount = OmniAmount::from_sat(500_000_000); +const BITCOIN_SPEND_AMOUNT: Amount = Amount::from_sat(500_000_000); fn setup_bitcoin_testnet() -> Result { if std::env::var("CI_ENVIRONMENT").is_ok() { @@ -66,9 +71,9 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let mut btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses - let bob = btc_test_context.setup_account().unwrap(); + let bob = btc_test_context.setup_account(AddressType::Legacy).unwrap(); - let alice = btc_test_context.setup_account().unwrap(); + let alice = btc_test_context.setup_account(AddressType::Legacy).unwrap(); // Generate 101 blocks to the address client.generate_to_address(101, &bob.address)?; @@ -176,7 +181,7 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { } #[tokio::test] -async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { +async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); let client = &bitcoind.client; @@ -184,9 +189,9 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let mut btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses - let bob = btc_test_context.setup_account().unwrap(); + let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); - let alice = btc_test_context.setup_account().unwrap(); + let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); // Generate 101 blocks to the address client.generate_to_address(101, &bob.address)?; @@ -213,13 +218,190 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let txin: OmniTxIn = OmniTxIn { previous_output: OmniOutPoint::new(omni_txid, vout as u32), script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. - sequence: OmniSequence::MAX, - witness: OmniWitness::default(), + sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, + witness: OmniWitness::default(), // Filled in after signing. + }; + + let utxo_amount = + OmniAmount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + + // The change output is locked to a key controlled by us. + let change_txout = OmniTxOut { + value: change_amount, + script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), + // script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. }; + // The spend output is locked to a key controlled by the receiver. In this case to Alice. let spend_txout = OmniTxOut { value: OMNI_SPEND_AMOUNT, - script_pubkey: OmniScriptBuf(alice.script_pubkey.as_bytes().to_vec()), + script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), + }; + + let omni_tx: BitcoinTransaction = TransactionBuilder::new::() + .version(OmniVersion::Two) + .lock_time(OmniLockTime::from_height(0).unwrap()) + .inputs(vec![txin]) + .outputs(vec![spend_txout, change_txout]) + .build(); + + println!("OMNI omni_tx: {:?}", omni_tx.serialize()); + + let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) + .p2wpkh_script_code() + .unwrap(); + // let script_pub_key_bytes_bob = script_pubkey_bob.as_bytes().to_vec(); + + println!("script_pub_key_bytes_bob: {:?}", script_pubkey_bob); + + // Prepare the transaction for signing + let sighash_type = OmniSighashType::All; + let input_index = 0; + let encoded_data = omni_tx.build_for_signing_segwit( + sighash_type, + input_index, + &OmniScriptBuf(script_pubkey_bob.into_bytes()), + utxo_amount.to_sat(), + ); + + println!("OMNI sighash_data: {:?}", encoded_data); + + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + + // -------------------------------------------------------------------------- + + // Calculate the sighash with Rust Bitcoin + let input = TxIn { + previous_output: OutPoint::new(bitcoin_txid, vout as u32), + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: BITCOIN_SPEND_AMOUNT, + script_pubkey: alice.address.script_pubkey(), + }; + + let btc_utxo_amount = + Amount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let btc_change_amount: Amount = btc_utxo_amount - BITCOIN_SPEND_AMOUNT - Amount::from_sat(1000); // 1000 satoshis for fee + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: btc_change_amount, + script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + println!("omni_tx: {:?}", omni_tx); + println!("unsigned_tx: {:?}", unsigned_tx); + + let mut buffer = Vec::new(); + unsigned_tx.consensus_encode(&mut buffer).unwrap(); + println!("encoded bitcoin tx {:?}", buffer); + + // Get the sighash to sign. + let script_pubkey = ScriptBuf::new_p2wpkh(&bob.wpkh); + println!("script_pubkey DESPUES: {:?}", script_pubkey); + println!("script_pubkey bytes: {:?}", script_pubkey.as_bytes()); + + let sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + + let mut writer = Vec::new(); + let script_code = script_pubkey.p2wpkh_script_code().unwrap(); + println!("script_code: {:?}", script_code); + + sighasher + .segwit_v0_encode_signing_data_to( + &mut writer, + input_index, + &script_code, + btc_utxo_amount, + sighash_type, + ) + .expect("failed to create sighash"); + + println!("BITCOIN encoded_data: {:?}", writer); + println!("OMNI sighash_data: {:?}", encoded_data); + + let sighash_bitcoin = sighasher + .p2wpkh_signature_hash(input_index, &script_pubkey, btc_utxo_amount, sighash_type) + .expect("failed to create sighash"); + + // let encoded_data = omni_tx.build_for_signing_segwit( + // sighash_type, + // input_index, + // &OmniScriptBuf(alice.address.script_pubkey().into_bytes()).p2wpkh_script_code(), + // utxo_amount.to_sat(), + // ); + + println!("sighash_omni: {:?}", sighash_omni); + println!("sighash_bitcoin: {:?}", sighash_bitcoin); + + // Assert that the sighash is the same + assert_eq!( + sighash_omni.to_byte_array(), + sighash_bitcoin.to_byte_array() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { + let bitcoind = setup_bitcoin_testnet().unwrap(); + let client = &bitcoind.client; + + // Setup testing environment + let mut btc_test_context = BTCTestContext::new(client).unwrap(); + + // Setup Bob and Alice addresses + let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + // Generate 101 blocks to the address + client.generate_to_address(101, &bob.address)?; + + // List UTXOs for Bob + let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); + + // Get the first UTXO + let first_unspent = unspent_utxos_bob + .into_iter() + .next() + .expect("There should be at least one unspent output"); + + let txid_str = first_unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); + + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + + let vout = first_unspent["vout"].as_u64().unwrap(); + + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: OmniSequence::MAX, + witness: OmniWitness::default(), }; let utxo_amount = @@ -227,12 +409,16 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - let script_sig = ScriptBuf::new_p2wpkh(&bob.wpkh); - // The change output is locked to a key controlled by us. let change_txout = OmniTxOut { value: change_amount, - script_pubkey: OmniScriptBuf(script_sig.as_bytes().to_vec()), // Change comes back to us. + script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. + }; + + // The spend output is locked to a key controlled by the receiver. In this case to Alice. + let spend_txout = OmniTxOut { + value: OMNI_SPEND_AMOUNT, + script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), }; let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() @@ -248,7 +434,7 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let encoded_data = omni_tx.build_for_signing_segwit( sighash_type, input_index, - &OmniScriptBuf(alice.script_pubkey.as_bytes().to_vec()), + &OmniScriptBuf(alice.address.script_pubkey().into_bytes()).p2wpkh_script_code(), utxo_amount.to_sat(), ); @@ -274,24 +460,31 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { }; // Create the witness + println!("bob.public_key: {:?}", bob.public_key); let witness = Witness::p2wpkh(&signature, &bob.public_key); + println!("witness: {:?}", witness); - // Assign script_sig to txin let encoded_omni_tx = omni_tx.build_with_witness(0, witness.to_vec(), TransactionType::P2WPKH); // Convert the transaction to a hexadecimal string - let _hex_omni_tx = hex::encode(encoded_omni_tx); + let hex_omni_tx = hex::encode(encoded_omni_tx); - // TODO: Fix broadcasting the transaction - // let raw_tx_result: serde_json::Value = client - // .call("sendrawtransaction", &[json!(hex_omni_tx)]) - // .unwrap(); + let maxfeerate = 0.10; + let maxburnamount = 100.00; - // println!("raw_tx_result: {:?}", raw_tx_result); + // We now deploy to the bitcoin network (regtest mode) + let raw_tx_result: serde_json::Value = client + .call( + "sendrawtransaction", + &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], + ) + .unwrap(); - // client.generate_to_address(101, &bob.address)?; + println!("raw_tx_result: {:?}", raw_tx_result); - // assert_utxos_for_address(client, alice.address, 1); + client.generate_to_address(101, &bob.address)?; + + assert_utxos_for_address(client, alice.address, 1); Ok(()) } From f845ba9883a2932b3844e9a419e94207d06cdd11 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Thu, 24 Oct 2024 15:03:32 +0100 Subject: [PATCH 06/19] wip: except propagation --- tests/bitcoin_integration_test.rs | 240 ++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 8c2f32c..86cec68 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -362,6 +362,246 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> Ok(()) } +#[tokio::test] +async fn test_multiple_p2wpkh_utxos() -> Result<()> { + let bitcoind = setup_bitcoin_testnet().unwrap(); + let client = &bitcoind.client; + + // Setup testing environment + let mut btc_test_context = BTCTestContext::new(client).unwrap(); + + // Setup Bob and Alice addresses + let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + // Generate 101 blocks to the address + client.generate_to_address(101, &bob.address)?; + + // List UTXOs for Bob + let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); + + // Get the first UTXO + let first_unspent = unspent_utxos_bob + .into_iter() + .next() + .expect("There should be at least one unspent output"); + + let txid_str = first_unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); + + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + + let vout = first_unspent["vout"].as_u64().unwrap(); + + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, + witness: OmniWitness::default(), // Filled in after signing. + }; + + let utxo_amount = + OmniAmount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + + println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); + + // The change output is locked to a key controlled by us. + let change_txout = OmniTxOut { + value: change_amount, + script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), + // script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. + }; + + // The spend output is locked to a key controlled by the receiver. In this case to Alice. + let spend_txout = OmniTxOut { + value: OMNI_SPEND_AMOUNT, + script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), + }; + + let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() + .version(OmniVersion::Two) + .lock_time(OmniLockTime::from_height(0).unwrap()) + .inputs(vec![txin]) + .outputs(vec![spend_txout, change_txout]) + .build(); + + // println!("OMNI omni_tx: {:?}", omni_tx.clone().serialize()); + + let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) + .p2wpkh_script_code() + .unwrap(); + // let script_pub_key_bytes_bob = script_pubkey_bob.as_bytes().to_vec(); + + // println!("script_pub_key_bytes_bob: {:?}", script_pubkey_bob); + + // Prepare the transaction for signing + let sighash_type = OmniSighashType::All; + let input_index = 0; + let encoded_data = omni_tx.build_for_signing_segwit( + sighash_type, + input_index, + &OmniScriptBuf(script_pubkey_bob.into_bytes()), + utxo_amount.to_sat(), + ); + + // println!("OMNI sighash_data: {:?}", encoded_data); + + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + + // -------------------------------------------------------------------------- + + // Calculate the sighash with Rust Bitcoin + let input = TxIn { + previous_output: OutPoint::new(bitcoin_txid, vout as u32), + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: BITCOIN_SPEND_AMOUNT, + script_pubkey: alice.address.script_pubkey(), + }; + + let btc_utxo_amount = + Amount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let btc_change_amount: Amount = btc_utxo_amount - BITCOIN_SPEND_AMOUNT - Amount::from_sat(1000); // 1000 satoshis for fee + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: btc_change_amount, + script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + // println!("omni_tx: {:?}", omni_tx.clone()); + // println!("unsigned_tx: {:?}", unsigned_tx); + + let mut buffer = Vec::new(); + unsigned_tx.consensus_encode(&mut buffer).unwrap(); + println!("encoded bitcoin tx {:?}", buffer); + + // Get the sighash to sign. + let script_pubkey = ScriptBuf::new_p2wpkh(&bob.wpkh); + // println!("script_pubkey DESPUES: {:?}", script_pubkey); + println!("script_pubkey bytes: {:?}", script_pubkey.as_bytes()); + + let sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + + let mut writer = Vec::new(); + let script_code = script_pubkey.p2wpkh_script_code().unwrap(); + println!("script_code: {:?}", script_code); + + sighasher + .segwit_v0_encode_signing_data_to( + &mut writer, + input_index, + &script_code, + btc_utxo_amount, + sighash_type, + ) + .expect("failed to create sighash"); + + // println!("BITCOIN encoded_data: {:?}", writer); + // println!("OMNI sighash_data: {:?}", encoded_data); + assert_eq!(writer, encoded_data); + + let sighash_bitcoin = sighasher + .p2wpkh_signature_hash(input_index, &script_pubkey, btc_utxo_amount, sighash_type) + .expect("failed to create sighash"); + + // println!("sighash_omni: {:?}", sighash_omni); + // println!("sighash_bitcoin: {:?}", sighash_bitcoin); + + // Assert that the sighash is the same + assert_eq!( + sighash_omni.to_byte_array(), + sighash_bitcoin.to_byte_array() + ); + + // -------------------------------------------------------------------------- + // PROPAGATE THE TRANSACTION + // -------------------------------------------------------------------------- + + let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + + // Sign the sighash and broadcast the transaction using the Omni library + let secp = Secp256k1::new(); + let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); + + // Verify signature + let is_valid = secp + .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) + .is_ok(); + + assert!(is_valid, "The signature should be valid"); + + // Encode the signature + let signature = bitcoin::ecdsa::Signature { + signature: signature_omni, + sighash_type: EcdsaSighashType::All, + }; + + // Create the witness + println!("bob.public_key: {:?}", bob.public_key); + let witness = Witness::p2wpkh(&signature, &bob.public_key); + println!("witness: {:?}", witness); + + // let wpkh_manual = sha256d::Hash::hash(bob.bitcoin_public_key.as_bytes()); + let encoded_omni_tx = omni_tx.build_with_witness(0, witness.to_vec(), TransactionType::P2WPKH); + println!("encoded_omni_tx with witness: {:?}", encoded_omni_tx); + + // Convert the transaction to a hexadecimal string + let hex_omni_tx = hex::encode(encoded_omni_tx); + + let maxfeerate = 0.10; + let maxburnamount = 100.00; + + // We now deploy to the bitcoin network (regtest mode) + let raw_tx_result: serde_json::Value = client + .call( + "sendrawtransaction", + &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], + ) + .unwrap(); + + println!("raw_tx_result: {:?}", raw_tx_result); + + client.generate_to_address(101, &bob.address)?; + + Ok(()) + + // PERHAPS TO COMPARE ?? + // let msg = Message::from(sighash); + // let signature = secp.sign_ecdsa(&msg, &sk); + + // // Update the witness stack. + // let signature = bitcoin::ecdsa::Signature { signature, sighash_type }; + // let pk = sk.public_key(&secp); + // *sighasher.witness_mut(input_index).unwrap() = Witness::p2wpkh(signature, pk); + + // // Get the signed transaction. + // let tx = sighasher.into_transaction(); +} + #[tokio::test] async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); From 2cc1f50796cc1fd8c431e0f71e584c9c95cc57a5 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Thu, 24 Oct 2024 19:06:36 +0100 Subject: [PATCH 07/19] version que funciona --- tests/bitcoin_integration_test.rs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 86cec68..523c16a 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -415,7 +415,6 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { let change_txout = OmniTxOut { value: change_amount, script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), - // script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. }; // The spend output is locked to a key controlled by the receiver. In this case to Alice. @@ -431,14 +430,9 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { .outputs(vec![spend_txout, change_txout]) .build(); - // println!("OMNI omni_tx: {:?}", omni_tx.clone().serialize()); - let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) .p2wpkh_script_code() .unwrap(); - // let script_pub_key_bytes_bob = script_pubkey_bob.as_bytes().to_vec(); - - // println!("script_pub_key_bytes_bob: {:?}", script_pubkey_bob); // Prepare the transaction for signing let sighash_type = OmniSighashType::All; @@ -450,8 +444,6 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { utxo_amount.to_sat(), ); - // println!("OMNI sighash_data: {:?}", encoded_data); - // Calculate the sighash let sighash_omni = sha256d::Hash::hash(&encoded_data); @@ -491,28 +483,21 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { }; let input_index = 0; - // println!("omni_tx: {:?}", omni_tx.clone()); - // println!("unsigned_tx: {:?}", unsigned_tx); - let mut buffer = Vec::new(); unsigned_tx.consensus_encode(&mut buffer).unwrap(); println!("encoded bitcoin tx {:?}", buffer); // Get the sighash to sign. - let script_pubkey = ScriptBuf::new_p2wpkh(&bob.wpkh); - // println!("script_pubkey DESPUES: {:?}", script_pubkey); - println!("script_pubkey bytes: {:?}", script_pubkey.as_bytes()); - let sighash_type = EcdsaSighashType::All; let mut sighasher = SighashCache::new(&mut unsigned_tx); - - let mut writer = Vec::new(); + let script_pubkey = ScriptBuf::new_p2wpkh(&bob.wpkh); let script_code = script_pubkey.p2wpkh_script_code().unwrap(); - println!("script_code: {:?}", script_code); + + let mut encoded_btc_sighash = Vec::new(); sighasher .segwit_v0_encode_signing_data_to( - &mut writer, + &mut encoded_btc_sighash, input_index, &script_code, btc_utxo_amount, @@ -520,17 +505,12 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { ) .expect("failed to create sighash"); - // println!("BITCOIN encoded_data: {:?}", writer); - // println!("OMNI sighash_data: {:?}", encoded_data); - assert_eq!(writer, encoded_data); + assert_eq!(encoded_btc_sighash, encoded_data); let sighash_bitcoin = sighasher .p2wpkh_signature_hash(input_index, &script_pubkey, btc_utxo_amount, sighash_type) .expect("failed to create sighash"); - // println!("sighash_omni: {:?}", sighash_omni); - // println!("sighash_bitcoin: {:?}", sighash_bitcoin); - // Assert that the sighash is the same assert_eq!( sighash_omni.to_byte_array(), From a26617f0911c38f043a52166c61efed82e094f70 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 25 Oct 2024 16:22:34 +0100 Subject: [PATCH 08/19] wip: managed to do multiple UTXOs --- tests/bitcoin_integration_test.rs | 428 +++++++++++++++++++++++++++++- 1 file changed, 416 insertions(+), 12 deletions(-) diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 523c16a..9c6cf23 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -375,6 +375,420 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + // Generate 101 blocks to the address + client.generate_to_address(101, &bob.address)?; + client.generate_to_address(101, &bob.address)?; + + // List UTXOs for Bob + let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); + + // Get the first two UTXOs + let first_two_unspent: Vec<_> = unspent_utxos_bob.into_iter().take(2).collect(); + assert!( + first_two_unspent.len() == 2, + "There should be at least two unspent outputs" + ); + + let mut inputs = Vec::new(); + let mut total_utxo_amount = 0; + + for unspent in &first_two_unspent { + let txid_str = unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); + + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + + let vout = unspent["vout"].as_u64().unwrap(); + + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, + witness: OmniWitness::default(), // Filled in after signing. + }; + + inputs.push(txin); + + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + total_utxo_amount += utxo_amount.to_sat(); + } + + let change_amount: OmniAmount = + OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + + println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); + + // The change output is locked to a key controlled by us. + let change_txout = OmniTxOut { + value: change_amount, + script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), + }; + + // The spend output is locked to a key controlled by the receiver. In this case to Alice. + let spend_txout = OmniTxOut { + value: OMNI_SPEND_AMOUNT, + script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), + }; + + let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() + .version(OmniVersion::Two) + .lock_time(OmniLockTime::from_height(0).unwrap()) + .inputs(inputs) + .outputs(vec![spend_txout, change_txout]) + .build(); + + let secp = Secp256k1::new(); + + for (i, unspent) in first_two_unspent.iter().enumerate() { + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) + .p2wpkh_script_code() + .unwrap(); + + // Prepare the transaction for signing + let sighash_type = OmniSighashType::All; + let input_index = i; + let encoded_data = omni_tx.build_for_signing_segwit( + sighash_type, + input_index, + &OmniScriptBuf(script_pubkey_bob.into_bytes()), + utxo_amount.to_sat(), + ); + + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + + // Sign the sighash + let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); + + // Verify signature + let is_valid = secp + .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) + .is_ok(); + + assert!(is_valid, "The signature should be valid"); + + println!("SIGNATURES ARE VALID"); + + // Encode the signature + let signature = bitcoin::ecdsa::Signature { + signature: signature_omni, + sighash_type: EcdsaSighashType::All, + }; + + // Create the witness + let witness = Witness::p2wpkh(&signature, &bob.public_key); + omni_tx.input[input_index].witness = + omni_transaction::bitcoin::types::Witness::from_slice(&witness.to_vec()); + } + + // Convert the transaction to a hexadecimal string + let hex_omni_tx = hex::encode(omni_tx.serialize()); + + let decoded_tx: serde_json::Value = + client.call("decoderawtransaction", &[json!(hex_omni_tx.clone())])?; + println!("Decoded transaction: {:?}", decoded_tx); + + println!("HEX OMNI TX ENCODED: {:?}", hex_omni_tx); + + let maxfeerate = 0.10; + let maxburnamount = 100.00; + + // We now deploy to the bitcoin network (regtest mode) + let raw_tx_result: serde_json::Value = client + .call( + "sendrawtransaction", + &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], + ) + .unwrap(); + + println!("raw_tx_result: {:?}", raw_tx_result); + + client.generate_to_address(101, &bob.address)?; + + Ok(()) +} + +#[tokio::test] +async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { + let bitcoind = setup_bitcoin_testnet().unwrap(); + let client = &bitcoind.client; + + // Setup testing environment + let mut btc_test_context = BTCTestContext::new(client).unwrap(); + + // Setup Bob and Alice addresses + let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + // Generate 101 blocks to the address + client.generate_to_address(101, &bob.address)?; + client.generate_to_address(101, &bob.address)?; + + // List UTXOs for Bob + let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); + + // Get the first two UTXOs + let first_two_unspent: Vec<_> = unspent_utxos_bob.into_iter().take(2).collect(); + assert!( + first_two_unspent.len() == 2, + "There should be at least two unspent outputs" + ); + + let mut inputs = Vec::new(); + let mut total_utxo_amount = 0; + let mut bitcoin_txids = Vec::new(); + + for unspent in &first_two_unspent { + let txid_str = unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); + + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + + let vout = unspent["vout"].as_u64().unwrap(); + + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, + witness: OmniWitness::default(), // Filled in after signing. + }; + + let btc_txid = TxIn { + previous_output: OutPoint::new(bitcoin_txid, vout as u32), + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + inputs.push(txin); + bitcoin_txids.push(btc_txid); + + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + println!("UTXO AMOUNT: {:?}", utxo_amount); + + total_utxo_amount += utxo_amount.to_sat(); + } + + let change_amount: OmniAmount = + OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + + println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); + + // The change output is locked to a key controlled by us. + let change_txout = OmniTxOut { + value: change_amount, + script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), + }; + + let btc_change_amount = Amount::from_sat(change_amount.to_sat()); + + // BTC Change Output + let btc_change_txout = TxOut { + value: btc_change_amount, + script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. + }; + + // BTC Spend Output + let btc_spend_txout = TxOut { + value: BITCOIN_SPEND_AMOUNT, + script_pubkey: alice.address.script_pubkey(), + }; + + // The spend output is locked to a key controlled by the receiver. In this case to Alice. + let spend_txout = OmniTxOut { + value: OMNI_SPEND_AMOUNT, + script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), + }; + + let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() + .version(OmniVersion::Two) + .lock_time(OmniLockTime::from_height(0).unwrap()) + .inputs(inputs) + .outputs(vec![spend_txout, change_txout]) + .build(); + + // BTC Transaction + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: bitcoin_txids, // Input goes into index 0. + output: vec![btc_spend_txout, btc_change_txout], // Outputs, order does not matter. + }; + + let mut buffer = Vec::new(); + unsigned_tx.consensus_encode(&mut buffer).unwrap(); + println!("encoded bitcoin tx {:?}", buffer); + + assert_eq!(buffer, omni_tx.serialize()); + + println!("BITCOIN TX AND OMNI TX ARE THE SAME !!!"); + + // -------------------------------------------------------------------------- + // COMPARE SIGHASHES + // -------------------------------------------------------------------------- + + let secp = Secp256k1::new(); + + let mut witness_vec = Vec::new(); + for (i, unspent) in first_two_unspent.iter().enumerate() { + println!("ENTERING LOOP {:?}", i); + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let script_pub_key = ScriptBuf::new_p2wpkh(&bob.wpkh); + let script_pubkey_bob = script_pub_key.p2wpkh_script_code().unwrap(); + + // Prepare the transaction for signing + let sighash_type = OmniSighashType::All; + let input_index = i; + let encoded_data = omni_tx.build_for_signing_segwit( + sighash_type, + input_index, + &OmniScriptBuf(script_pubkey_bob.into_bytes()), + utxo_amount.to_sat(), + ); + + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + + let btc_sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + + let btc_utxo_amount = Amount::from_sat(utxo_amount.to_sat()); + + // TODO: Meter la representacion intermedia para poder ver que es lo que se esta encodeando diferente + let mut encoded_btc_sighash = Vec::new(); + + sighasher + .segwit_v0_encode_signing_data_to( + &mut encoded_btc_sighash, + input_index, + &script_pub_key.p2wpkh_script_code().unwrap(), + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + println!("Encoded data for OMNI: {:?}", encoded_data); + println!("Encoded data for BTC: {:?}", encoded_btc_sighash); + + assert_eq!(encoded_btc_sighash, encoded_data); + + let sighash_bitcoin = sighasher + .p2wpkh_signature_hash( + input_index, + &script_pub_key, + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + assert_eq!( + sighash_omni.to_byte_array(), + sighash_bitcoin.to_byte_array(), + "SIGHASHES ARE NOT THE SAME" + ); + + println!("SIGHASHES ARE THE SAME !!!"); + // Sign the sighash + let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); + + // Verify signature + let is_valid = secp + .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) + .is_ok(); + + assert!(is_valid, "The signature should be valid"); + + println!("SIGNATURES ARE VALID"); + + // Encode the signature + let signature = bitcoin::ecdsa::Signature { + signature: signature_omni, + sighash_type: EcdsaSighashType::All, + }; + + // Create the witness + let witness = Witness::p2wpkh(&signature, &bob.public_key); + + // TODO: Review that the witness information is correct, that is, that the witness is being + // included in the correct input and is in the final transaction + println!("witness: {:?}", witness); + println!("input_index: {:?}", input_index); + + witness_vec.push(omni_transaction::bitcoin::types::Witness::from_slice( + &witness.to_vec(), + )); + + // TODO: Guardar esto aparte para luego reconstruir la transaction + // TODO: O sea, guardar los witness en una structura temporal y al final agregarlos a omni_tx + // y listo + // omni_tx.input[input_index].witness = + // omni_transaction::bitcoin::types::Witness::from_slice(&witness.to_vec()); + } + + // Add the witness to the transaction + for (i, witness) in witness_vec.iter().enumerate() { + omni_tx.input[i].witness = witness.clone(); + } + + let serialized_tx = omni_tx.serialize(); + println!("serialized omni tx: {:?}", serialized_tx); + + // Convert the transaction to a hexadecimal string + let hex_omni_tx = hex::encode(serialized_tx); + println!("HEX OMNI TX ENCODED: {:?}", hex_omni_tx); + + let decoded_tx: serde_json::Value = + client.call("decoderawtransaction", &[json!(hex_omni_tx.clone())])?; + println!("Decoded transaction: {:?}", decoded_tx); + + let maxfeerate = 0.10; + let maxburnamount = 100.00; + + // We now deploy to the bitcoin network (regtest mode) + let raw_tx_result: serde_json::Value = client + .call( + "sendrawtransaction", + &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], + ) + .unwrap(); + + println!("raw_tx_result: {:?}", raw_tx_result); + + client.generate_to_address(101, &bob.address)?; + + Ok(()) +} + +#[tokio::test] +async fn test_p2wpkh_single_utxo() -> Result<()> { + let bitcoind = setup_bitcoin_testnet().unwrap(); + let client = &bitcoind.client; + + // Setup testing environment + let mut btc_test_context = BTCTestContext::new(client).unwrap(); + + // Setup Bob and Alice addresses + let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + + let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); + // Generate 101 blocks to the address client.generate_to_address(101, &bob.address)?; @@ -534,6 +948,8 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { assert!(is_valid, "The signature should be valid"); + println!("SIGNATURES ARE VALID"); + // Encode the signature let signature = bitcoin::ecdsa::Signature { signature: signature_omni, @@ -568,18 +984,6 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { client.generate_to_address(101, &bob.address)?; Ok(()) - - // PERHAPS TO COMPARE ?? - // let msg = Message::from(sighash); - // let signature = secp.sign_ecdsa(&msg, &sk); - - // // Update the witness stack. - // let signature = bitcoin::ecdsa::Signature { signature, sighash_type }; - // let pk = sk.public_key(&secp); - // *sighasher.witness_mut(input_index).unwrap() = Witness::p2wpkh(signature, pk); - - // // Get the signed transaction. - // let tx = sighasher.into_transaction(); } #[tokio::test] From a60302b88d86e6d10cea8094d2d7b6b2262290e1 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 25 Oct 2024 16:41:32 +0100 Subject: [PATCH 09/19] fix: added missing values to sequence --- src/bitcoin/types/tx_in/sequence.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/bitcoin/types/tx_in/sequence.rs b/src/bitcoin/types/tx_in/sequence.rs index dc0eed1..14bc2e1 100644 --- a/src/bitcoin/types/tx_in/sequence.rs +++ b/src/bitcoin/types/tx_in/sequence.rs @@ -34,6 +34,22 @@ impl Sequence { /// /// This sequence number enables replace-by-fee and absolute lock time. pub const ZERO: Self = Self(0); + + /// The sequence number that enables absolute lock time but disables replace-by-fee + /// and relative lock time. + pub const ENABLE_LOCKTIME_NO_RBF: Self = Sequence::MIN_NO_RBF; + /// The sequence number that enables replace-by-fee and absolute lock time but + /// disables relative lock time. + pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD); + + /// The lowest sequence number that does not opt-in for replace-by-fee. + /// + /// A transaction is considered to have opted in to replacement of itself + /// if any of it's inputs have a `Sequence` number less than this value + /// (Explicit Signalling [BIP-125]). + /// + /// [BIP-125]: + const MIN_NO_RBF: Self = Sequence(0xFFFFFFFE); } impl Default for Sequence { From 8d30253b03534e5ec9fd39a0b5944686404bd698 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 25 Oct 2024 16:41:56 +0100 Subject: [PATCH 10/19] extend script --- src/bitcoin/types/script_buf.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bitcoin/types/script_buf.rs b/src/bitcoin/types/script_buf.rs index 3693241..56fff03 100644 --- a/src/bitcoin/types/script_buf.rs +++ b/src/bitcoin/types/script_buf.rs @@ -22,6 +22,12 @@ impl ScriptBuf { pub const fn from_bytes(bytes: Vec) -> Self { Self(bytes) } + + pub fn p2wpkh_script_code(&self) -> Self { + let mut script = vec![0x00, 0x14]; + script.extend_from_slice(&self.0); + Self(script) + } } pub trait FromHex: Sized { From 27a500691bff4f2741e06cc7d4f8c2a88a020a18 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 25 Oct 2024 16:45:10 +0100 Subject: [PATCH 11/19] fix: clean bitcoin utils --- tests/utils/bitcoin_utils.rs | 122 ++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 32 deletions(-) diff --git a/tests/utils/bitcoin_utils.rs b/tests/utils/bitcoin_utils.rs index 893a423..33f2725 100644 --- a/tests/utils/bitcoin_utils.rs +++ b/tests/utils/bitcoin_utils.rs @@ -1,7 +1,7 @@ -use bitcoin::bip32::{ChildNumber, DerivationPath, Xpub}; +use bitcoin::bip32::DerivationPath; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::{bip32::Xpriv, Address, Network, ScriptBuf}; -use bitcoin::{PublicKey as BitcoinPublicKey, WPubkeyHash}; +use bitcoin::{CompressedPublicKey, PublicKey as BitcoinPublicKey, WPubkeyHash}; use bitcoind::AddressType; use serde_json::{json, Value}; use std::str::FromStr as _; @@ -11,42 +11,56 @@ pub struct UserInfo { pub script_pubkey: ScriptBuf, pub private_key: SecretKey, pub public_key: PublicKey, + pub compressed_public_key: CompressedPublicKey, pub bitcoin_public_key: BitcoinPublicKey, pub wpkh: WPubkeyHash, } pub struct BTCTestContext<'a> { client: &'a bitcoind::Client, - master_key: Xpriv, - account_index: u32, + master_key_p2pkh: Xpriv, + master_key_p2wpkh: Xpriv, } impl<'a> BTCTestContext<'a> { pub fn new(client: &'a bitcoind::Client) -> Result> { - let master_key = Self::get_master_key_of_regtest_node(client)?; + let master_key_p2pkh = Self::get_master_key_of_regtest_node_p2pkh(client)?; + let master_key_p2wpkh = Self::get_master_key_of_regtest_node_p2wpkh(client)?; + Ok(BTCTestContext { client, - master_key, - account_index: 0, + master_key_p2pkh, + master_key_p2wpkh, }) } - pub fn setup_account(&mut self) -> Result> { + pub fn setup_account( + &mut self, + address_type: AddressType, + ) -> Result> { let address = self .client - .get_new_address_with_type(AddressType::Legacy) + .get_new_address_with_type(address_type.clone()) .unwrap() .address() .unwrap(); let address = address.require_network(Network::Regtest).unwrap(); - // Get address info for Bob + // Get address info for Account let address_info: Value = self .client .call("getaddressinfo", &[address.to_string().into()])?; - // Extract the scriptPubKey from the result + // Extract the pubkey from the address info + let pubkey_hex = address_info["pubkey"] + .as_str() + .expect("pubkey should be a string"); + + let compressed_pub_key = + CompressedPublicKey::from_str(pubkey_hex).expect("Failed to parse pubkey"); + + // Extract the scriptPubKey from the address info let script_pubkey_hex = address_info["scriptPubKey"] .as_str() .expect("scriptPubKey should be a string"); @@ -58,33 +72,36 @@ impl<'a> BTCTestContext<'a> { let secp = Secp256k1::new(); // Derive child private key using path m/44h/1h/0h - let path = "m/44h/1h/0h".parse::().unwrap(); - let child = self.master_key.derive_priv(&secp, &path).unwrap(); - let xpub = Xpub::from_priv(&secp, &child); - - // Generate P2PKH address at m/0/{account_index} - let zero = ChildNumber::Normal { index: 0 }; - let index = ChildNumber::Normal { - index: self.account_index, + let hd_key_path = address_info["hdkeypath"].as_str().unwrap(); + let path = DerivationPath::from_str(hd_key_path).unwrap(); + + let child = if address_type == AddressType::Bech32 { + self.master_key_p2wpkh.derive_priv(&secp, &path).unwrap() + } else { + self.master_key_p2pkh.derive_priv(&secp, &path).unwrap() }; - let public_key = xpub.derive_pub(&secp, &[zero, index]).unwrap().public_key; - let bitcoin_public_key: BitcoinPublicKey = BitcoinPublicKey::new(public_key); - let derived_address = Address::p2pkh(bitcoin_public_key, Network::Regtest); + let private_key = child.private_key; + let public_key = PublicKey::from_secret_key(&secp, &private_key); + let bitcoin_public_key = BitcoinPublicKey::new(public_key); + + let derived_address = if address_type == AddressType::Bech32 { + Address::p2wpkh(&compressed_pub_key, Network::Regtest) + } else { + Address::p2pkh(&compressed_pub_key, Network::Regtest) + }; + + assert_eq!( + bitcoin_public_key.to_string(), + pubkey_hex, + "Derived public key does not match the one provided by the node" + ); // Verify that the address is the same as the one generated by the client assert_eq!(address, derived_address); - // Get private key for first P2PKH address - let private_key: SecretKey = child - .derive_priv(&secp, &DerivationPath::from(vec![zero, index])) - .unwrap() - .private_key; - let wpkh: WPubkeyHash = bitcoin_public_key .wpubkey_hash() - .expect("key is compressed"); - - self.account_index += 1; + .expect("Failed to compute WPubkeyHash: ensure the key is compressed"); Ok(UserInfo { address, @@ -93,10 +110,11 @@ impl<'a> BTCTestContext<'a> { public_key, bitcoin_public_key, wpkh, + compressed_public_key: compressed_pub_key, }) } - fn get_master_key_of_regtest_node( + fn get_master_key_of_regtest_node_p2pkh( client: &bitcoind::Client, ) -> Result> { let descriptors: Value = client.call("listdescriptors", &[true.into()])?; @@ -117,6 +135,46 @@ impl<'a> BTCTestContext<'a> { Ok(master_key) } + fn get_master_key_of_regtest_node_p2wpkh( + client: &bitcoind::Client, + ) -> Result> { + let descriptors: Value = client.call("listdescriptors", &[true.into()])?; + + let p2wpkh_descriptor = descriptors["descriptors"] + .as_array() + .unwrap() + .iter() + .find(|descriptor| { + let desc = descriptor["desc"].as_str().unwrap(); + desc.contains("wpkh") && !desc.starts_with("tr(") // Exclude descriptors for taproot + }) + .expect("No P2WPKH or nested P2WPKH descriptor found"); + + let desc = p2wpkh_descriptor["desc"].as_str().unwrap(); + + // Extract the xpriv part from the descriptor + let xpriv_part = desc + .split("wpkh(") + .nth(1) + .unwrap() + .split(')') + .next() + .unwrap(); + let parts: Vec<&str> = xpriv_part.split('/').collect(); + let master_key_str = parts[0]; + + // Ensure the key starts with "tprv" for testnet/regtest + let master_key_str = if !master_key_str.starts_with("tprv") { + format!("tprv{}", master_key_str) + } else { + master_key_str.to_string() + }; + + let master_key = Xpriv::from_str(&master_key_str)?; + + Ok(master_key) + } + pub fn get_utxo_for_address( &self, address: &Address, From da0f3d7e7b90cac140303eb10ff333f0e3d9841c Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 25 Oct 2024 17:52:05 +0100 Subject: [PATCH 12/19] fix: clean integration tests --- tests/bitcoin_integration_test.rs | 244 ++++++++++++------------------ 1 file changed, 94 insertions(+), 150 deletions(-) diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 9c6cf23..fd1af54 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -231,7 +231,6 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> let change_txout = OmniTxOut { value: change_amount, script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), - // script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. }; // The spend output is locked to a key controlled by the receiver. In this case to Alice. @@ -247,14 +246,9 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> .outputs(vec![spend_txout, change_txout]) .build(); - println!("OMNI omni_tx: {:?}", omni_tx.serialize()); - let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) .p2wpkh_script_code() .unwrap(); - // let script_pub_key_bytes_bob = script_pubkey_bob.as_bytes().to_vec(); - - println!("script_pub_key_bytes_bob: {:?}", script_pubkey_bob); // Prepare the transaction for signing let sighash_type = OmniSighashType::All; @@ -266,13 +260,9 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> utxo_amount.to_sat(), ); - println!("OMNI sighash_data: {:?}", encoded_data); - // Calculate the sighash let sighash_omni = sha256d::Hash::hash(&encoded_data); - // -------------------------------------------------------------------------- - // Calculate the sighash with Rust Bitcoin let input = TxIn { previous_output: OutPoint::new(bitcoin_txid, vout as u32), @@ -307,24 +297,17 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> }; let input_index = 0; - println!("omni_tx: {:?}", omni_tx); - println!("unsigned_tx: {:?}", unsigned_tx); - let mut buffer = Vec::new(); unsigned_tx.consensus_encode(&mut buffer).unwrap(); - println!("encoded bitcoin tx {:?}", buffer); // Get the sighash to sign. let script_pubkey = ScriptBuf::new_p2wpkh(&bob.wpkh); - println!("script_pubkey DESPUES: {:?}", script_pubkey); - println!("script_pubkey bytes: {:?}", script_pubkey.as_bytes()); let sighash_type = EcdsaSighashType::All; let mut sighasher = SighashCache::new(&mut unsigned_tx); let mut writer = Vec::new(); let script_code = script_pubkey.p2wpkh_script_code().unwrap(); - println!("script_code: {:?}", script_code); sighasher .segwit_v0_encode_signing_data_to( @@ -336,23 +319,10 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> ) .expect("failed to create sighash"); - println!("BITCOIN encoded_data: {:?}", writer); - println!("OMNI sighash_data: {:?}", encoded_data); - let sighash_bitcoin = sighasher .p2wpkh_signature_hash(input_index, &script_pubkey, btc_utxo_amount, sighash_type) .expect("failed to create sighash"); - // let encoded_data = omni_tx.build_for_signing_segwit( - // sighash_type, - // input_index, - // &OmniScriptBuf(alice.address.script_pubkey().into_bytes()).p2wpkh_script_code(), - // utxo_amount.to_sat(), - // ); - - println!("sighash_omni: {:?}", sighash_omni); - println!("sighash_bitcoin: {:?}", sighash_bitcoin); - // Assert that the sighash is the same assert_eq!( sighash_omni.to_byte_array(), @@ -391,6 +361,7 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { let mut inputs = Vec::new(); let mut total_utxo_amount = 0; + let mut bitcoin_txids = Vec::new(); for unspent in &first_two_unspent { let txid_str = unspent["txid"].as_str().unwrap(); @@ -410,24 +381,45 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { witness: OmniWitness::default(), // Filled in after signing. }; + let btc_txid = TxIn { + previous_output: OutPoint::new(bitcoin_txid, vout as u32), + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + inputs.push(txin); + bitcoin_txids.push(btc_txid); let utxo_amount = OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + total_utxo_amount += utxo_amount.to_sat(); } let change_amount: OmniAmount = OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); - // The change output is locked to a key controlled by us. let change_txout = OmniTxOut { value: change_amount, script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), }; + let btc_change_amount = Amount::from_sat(change_amount.to_sat()); + + // BTC Change Output + let btc_change_txout = TxOut { + value: btc_change_amount, + script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. + }; + + // BTC Spend Output + let btc_spend_txout = TxOut { + value: BITCOIN_SPEND_AMOUNT, + script_pubkey: alice.address.script_pubkey(), + }; + // The spend output is locked to a key controlled by the receiver. In this case to Alice. let spend_txout = OmniTxOut { value: OMNI_SPEND_AMOUNT, @@ -441,15 +433,28 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { .outputs(vec![spend_txout, change_txout]) .build(); + // BTC Transaction + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: bitcoin_txids, // Input goes into index 0. + output: vec![btc_spend_txout, btc_change_txout], // Outputs, order does not matter. + }; + + let mut buffer = Vec::new(); + unsigned_tx.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(buffer, omni_tx.serialize()); + let secp = Secp256k1::new(); + let mut witness_vec = Vec::new(); for (i, unspent) in first_two_unspent.iter().enumerate() { let utxo_amount = OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) - .p2wpkh_script_code() - .unwrap(); + let script_pub_key = ScriptBuf::new_p2wpkh(&bob.wpkh); + let script_pubkey_bob = script_pub_key.p2wpkh_script_code().unwrap(); // Prepare the transaction for signing let sighash_type = OmniSighashType::All; @@ -464,6 +469,40 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { // Calculate the sighash let sighash_omni = sha256d::Hash::hash(&encoded_data); + let btc_sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + + let btc_utxo_amount = Amount::from_sat(utxo_amount.to_sat()); + + let mut encoded_btc_sighash = Vec::new(); + + sighasher + .segwit_v0_encode_signing_data_to( + &mut encoded_btc_sighash, + input_index, + &script_pub_key.p2wpkh_script_code().unwrap(), + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + assert_eq!(encoded_btc_sighash, encoded_data); + + let sighash_bitcoin = sighasher + .p2wpkh_signature_hash( + input_index, + &script_pub_key, + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + assert_eq!( + sighash_omni.to_byte_array(), + sighash_bitcoin.to_byte_array(), + "SIGHASHES ARE NOT THE SAME" + ); + // Sign the sighash let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); @@ -475,8 +514,6 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { assert!(is_valid, "The signature should be valid"); - println!("SIGNATURES ARE VALID"); - // Encode the signature let signature = bitcoin::ecdsa::Signature { signature: signature_omni, @@ -485,18 +522,25 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { // Create the witness let witness = Witness::p2wpkh(&signature, &bob.public_key); - omni_tx.input[input_index].witness = - omni_transaction::bitcoin::types::Witness::from_slice(&witness.to_vec()); + + witness_vec.push(omni_transaction::bitcoin::types::Witness::from_slice( + &witness.to_vec(), + )); + } + + // Add the witness to the transaction + for (i, witness) in witness_vec.iter().enumerate() { + omni_tx.input[i].witness = witness.clone(); } + // Serialize the transaction + let serialized_tx = omni_tx.serialize(); + // Convert the transaction to a hexadecimal string - let hex_omni_tx = hex::encode(omni_tx.serialize()); + let hex_omni_tx = hex::encode(serialized_tx); - let decoded_tx: serde_json::Value = + let _decoded_tx: serde_json::Value = client.call("decoderawtransaction", &[json!(hex_omni_tx.clone())])?; - println!("Decoded transaction: {:?}", decoded_tx); - - println!("HEX OMNI TX ENCODED: {:?}", hex_omni_tx); let maxfeerate = 0.10; let maxburnamount = 100.00; @@ -578,16 +622,12 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { let utxo_amount = OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - println!("UTXO AMOUNT: {:?}", utxo_amount); - total_utxo_amount += utxo_amount.to_sat(); } let change_amount: OmniAmount = OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); - // The change output is locked to a key controlled by us. let change_txout = OmniTxOut { value: change_amount, @@ -614,7 +654,7 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), }; - let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() + let omni_tx: BitcoinTransaction = TransactionBuilder::new::() .version(OmniVersion::Two) .lock_time(OmniLockTime::from_height(0).unwrap()) .inputs(inputs) @@ -631,21 +671,10 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { let mut buffer = Vec::new(); unsigned_tx.consensus_encode(&mut buffer).unwrap(); - println!("encoded bitcoin tx {:?}", buffer); assert_eq!(buffer, omni_tx.serialize()); - println!("BITCOIN TX AND OMNI TX ARE THE SAME !!!"); - - // -------------------------------------------------------------------------- - // COMPARE SIGHASHES - // -------------------------------------------------------------------------- - - let secp = Secp256k1::new(); - - let mut witness_vec = Vec::new(); for (i, unspent) in first_two_unspent.iter().enumerate() { - println!("ENTERING LOOP {:?}", i); let utxo_amount = OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); @@ -670,7 +699,6 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { let btc_utxo_amount = Amount::from_sat(utxo_amount.to_sat()); - // TODO: Meter la representacion intermedia para poder ver que es lo que se esta encodeando diferente let mut encoded_btc_sighash = Vec::new(); sighasher @@ -683,9 +711,6 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { ) .expect("failed to create sighash"); - println!("Encoded data for OMNI: {:?}", encoded_data); - println!("Encoded data for BTC: {:?}", encoded_btc_sighash); - assert_eq!(encoded_btc_sighash, encoded_data); let sighash_bitcoin = sighasher @@ -702,77 +727,8 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { sighash_bitcoin.to_byte_array(), "SIGHASHES ARE NOT THE SAME" ); - - println!("SIGHASHES ARE THE SAME !!!"); - // Sign the sighash - let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); - let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); - - // Verify signature - let is_valid = secp - .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) - .is_ok(); - - assert!(is_valid, "The signature should be valid"); - - println!("SIGNATURES ARE VALID"); - - // Encode the signature - let signature = bitcoin::ecdsa::Signature { - signature: signature_omni, - sighash_type: EcdsaSighashType::All, - }; - - // Create the witness - let witness = Witness::p2wpkh(&signature, &bob.public_key); - - // TODO: Review that the witness information is correct, that is, that the witness is being - // included in the correct input and is in the final transaction - println!("witness: {:?}", witness); - println!("input_index: {:?}", input_index); - - witness_vec.push(omni_transaction::bitcoin::types::Witness::from_slice( - &witness.to_vec(), - )); - - // TODO: Guardar esto aparte para luego reconstruir la transaction - // TODO: O sea, guardar los witness en una structura temporal y al final agregarlos a omni_tx - // y listo - // omni_tx.input[input_index].witness = - // omni_transaction::bitcoin::types::Witness::from_slice(&witness.to_vec()); } - // Add the witness to the transaction - for (i, witness) in witness_vec.iter().enumerate() { - omni_tx.input[i].witness = witness.clone(); - } - - let serialized_tx = omni_tx.serialize(); - println!("serialized omni tx: {:?}", serialized_tx); - - // Convert the transaction to a hexadecimal string - let hex_omni_tx = hex::encode(serialized_tx); - println!("HEX OMNI TX ENCODED: {:?}", hex_omni_tx); - - let decoded_tx: serde_json::Value = - client.call("decoderawtransaction", &[json!(hex_omni_tx.clone())])?; - println!("Decoded transaction: {:?}", decoded_tx); - - let maxfeerate = 0.10; - let maxburnamount = 100.00; - - // We now deploy to the bitcoin network (regtest mode) - let raw_tx_result: serde_json::Value = client - .call( - "sendrawtransaction", - &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], - ) - .unwrap(); - - println!("raw_tx_result: {:?}", raw_tx_result); - - client.generate_to_address(101, &bob.address)?; - Ok(()) } @@ -823,8 +779,6 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - println!("BOB PUBLIC KEY HASH: {:?}", bob.wpkh); - // The change output is locked to a key controlled by us. let change_txout = OmniTxOut { value: change_amount, @@ -899,7 +853,6 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { let mut buffer = Vec::new(); unsigned_tx.consensus_encode(&mut buffer).unwrap(); - println!("encoded bitcoin tx {:?}", buffer); // Get the sighash to sign. let sighash_type = EcdsaSighashType::All; @@ -931,10 +884,6 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { sighash_bitcoin.to_byte_array() ); - // -------------------------------------------------------------------------- - // PROPAGATE THE TRANSACTION - // -------------------------------------------------------------------------- - let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); // Sign the sighash and broadcast the transaction using the Omni library @@ -948,8 +897,6 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { assert!(is_valid, "The signature should be valid"); - println!("SIGNATURES ARE VALID"); - // Encode the signature let signature = bitcoin::ecdsa::Signature { signature: signature_omni, @@ -957,13 +904,9 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { }; // Create the witness - println!("bob.public_key: {:?}", bob.public_key); let witness = Witness::p2wpkh(&signature, &bob.public_key); - println!("witness: {:?}", witness); - // let wpkh_manual = sha256d::Hash::hash(bob.bitcoin_public_key.as_bytes()); let encoded_omni_tx = omni_tx.build_with_witness(0, witness.to_vec(), TransactionType::P2WPKH); - println!("encoded_omni_tx with witness: {:?}", encoded_omni_tx); // Convert the transaction to a hexadecimal string let hex_omni_tx = hex::encode(encoded_omni_tx); @@ -1052,13 +995,17 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { .outputs(vec![spend_txout, change_txout]) .build(); + let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) + .p2wpkh_script_code() + .unwrap(); + // Prepare the transaction for signing let sighash_type = OmniSighashType::All; let input_index = 0; let encoded_data = omni_tx.build_for_signing_segwit( sighash_type, input_index, - &OmniScriptBuf(alice.address.script_pubkey().into_bytes()).p2wpkh_script_code(), + &OmniScriptBuf(script_pubkey_bob.into_bytes()), utxo_amount.to_sat(), ); @@ -1084,10 +1031,7 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { }; // Create the witness - println!("bob.public_key: {:?}", bob.public_key); let witness = Witness::p2wpkh(&signature, &bob.public_key); - println!("witness: {:?}", witness); - let encoded_omni_tx = omni_tx.build_with_witness(0, witness.to_vec(), TransactionType::P2WPKH); // Convert the transaction to a hexadecimal string From d9585a94802969aabb6b3d2cfd53d469ff58ef5d Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Mon, 28 Oct 2024 08:31:17 +0000 Subject: [PATCH 13/19] fix: clippy --- src/bitcoin/types/tx_in/sequence.rs | 6 +++--- src/near/types/public_key.rs | 4 ++-- tests/bitcoin_integration_test.rs | 14 +++++++------- tests/utils/bitcoin_utils.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bitcoin/types/tx_in/sequence.rs b/src/bitcoin/types/tx_in/sequence.rs index 14bc2e1..4200342 100644 --- a/src/bitcoin/types/tx_in/sequence.rs +++ b/src/bitcoin/types/tx_in/sequence.rs @@ -37,10 +37,10 @@ impl Sequence { /// The sequence number that enables absolute lock time but disables replace-by-fee /// and relative lock time. - pub const ENABLE_LOCKTIME_NO_RBF: Self = Sequence::MIN_NO_RBF; + pub const ENABLE_LOCKTIME_NO_RBF: Self = Self::MIN_NO_RBF; /// The sequence number that enables replace-by-fee and absolute lock time but /// disables relative lock time. - pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD); + pub const ENABLE_RBF_NO_LOCKTIME: Self = Self(0xFFFFFFFD); /// The lowest sequence number that does not opt-in for replace-by-fee. /// @@ -49,7 +49,7 @@ impl Sequence { /// (Explicit Signalling [BIP-125]). /// /// [BIP-125]: - const MIN_NO_RBF: Self = Sequence(0xFFFFFFFE); + const MIN_NO_RBF: Self = Self(0xFFFFFFFE); } impl Default for Sequence { diff --git a/src/near/types/public_key.rs b/src/near/types/public_key.rs index 3d03891..16c9025 100644 --- a/src/near/types/public_key.rs +++ b/src/near/types/public_key.rs @@ -192,8 +192,8 @@ impl<'de> Deserialize<'de> for Secp256K1PublicKey { A: serde::de::SeqAccess<'de>, { let mut arr = [0u8; SECP256K1_PUBLIC_KEY_LENGTH]; - for i in 0..SECP256K1_PUBLIC_KEY_LENGTH { - arr[i] = seq + for (i, byte) in arr.iter_mut().enumerate().take(SECP256K1_PUBLIC_KEY_LENGTH) { + *byte = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(i, &self))?; } diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index fd1af54..7f66da0 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -68,7 +68,7 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { assert_eq!(0, blockchain_info.blocks); // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Legacy).unwrap(); @@ -186,7 +186,7 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> let client = &bitcoind.client; // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); @@ -338,7 +338,7 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { let client = &bitcoind.client; // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); @@ -540,7 +540,7 @@ async fn test_multiple_p2wpkh_utxos() -> Result<()> { let hex_omni_tx = hex::encode(serialized_tx); let _decoded_tx: serde_json::Value = - client.call("decoderawtransaction", &[json!(hex_omni_tx.clone())])?; + client.call("decoderawtransaction", &[json!(hex_omni_tx)])?; let maxfeerate = 0.10; let maxburnamount = 100.00; @@ -566,7 +566,7 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { let client = &bitcoind.client; // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); @@ -738,7 +738,7 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { let client = &bitcoind.client; // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); @@ -935,7 +935,7 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let client = &bitcoind.client; // Setup testing environment - let mut btc_test_context = BTCTestContext::new(client).unwrap(); + let btc_test_context = BTCTestContext::new(client).unwrap(); // Setup Bob and Alice addresses let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); diff --git a/tests/utils/bitcoin_utils.rs b/tests/utils/bitcoin_utils.rs index 33f2725..9595fc3 100644 --- a/tests/utils/bitcoin_utils.rs +++ b/tests/utils/bitcoin_utils.rs @@ -35,7 +35,7 @@ impl<'a> BTCTestContext<'a> { } pub fn setup_account( - &mut self, + &self, address_type: AddressType, ) -> Result> { let address = self @@ -88,7 +88,7 @@ impl<'a> BTCTestContext<'a> { let derived_address = if address_type == AddressType::Bech32 { Address::p2wpkh(&compressed_pub_key, Network::Regtest) } else { - Address::p2pkh(&compressed_pub_key, Network::Regtest) + Address::p2pkh(compressed_pub_key, Network::Regtest) }; assert_eq!( From e76c4fb14804c3086b9bef87d212df3e9260e25c Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Mon, 28 Oct 2024 15:00:08 +0000 Subject: [PATCH 14/19] fix: removed unused test and btc context dep from local --- Cargo.toml | 5 +- tests/bitcoin_integration_test.rs | 510 +++++++++++------------------- tests/utils/bitcoin_utils.rs | 209 ------------ tests/utils/mod.rs | 1 - 4 files changed, 192 insertions(+), 533 deletions(-) delete mode 100644 tests/utils/bitcoin_utils.rs delete mode 100644 tests/utils/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 1ef4f52..9cbf792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ near = [] rlp = "0.6.1" hex = "0.4.3" borsh = { version = "1.0.0", features = ["derive"] } -near-sdk = { version = "5.3.0", features = ["schemars"] } +near-sdk = { version = "5.3.0", features = ["schemars", "abi"] } near-account-id = { version = "1.0.0", features = ["schemars"] } serde-big-array = "0.5.1" bs58 = "0.5.1" @@ -71,3 +71,6 @@ tokio = { version = "1.38", features = ["full"] } # misc eyre = "0.6" + +# testing +omni-testing-utilities = { git = "https://github.com/Omni-rs/omni-testing-utilities", branch = "main" } diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 7f66da0..5060624 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -10,6 +10,7 @@ use bitcoin::{ScriptBuf, Sequence}; use bitcoin::{TxOut, Witness}; use bitcoind::AddressType; // Omni library +use omni_testing_utilities::BTCTestContext; use omni_transaction::bitcoin::bitcoin_transaction::BitcoinTransaction; use omni_transaction::bitcoin::types::{ Amount as OmniAmount, EcdsaSighashType as OmniSighashType, Hash as OmniHash, @@ -22,50 +23,17 @@ use omni_transaction::transaction_builder::TxBuilder; use omni_transaction::types::BITCOIN; // Testing use eyre::Result; -// use serde::Serialize; use serde_json::json; use std::result::Result::Ok; use tempfile::TempDir; -mod utils; - -pub use utils::bitcoin_utils::*; - const OMNI_SPEND_AMOUNT: OmniAmount = OmniAmount::from_sat(500_000_000); const BITCOIN_SPEND_AMOUNT: Amount = Amount::from_sat(500_000_000); -fn setup_bitcoin_testnet() -> Result { - if std::env::var("CI_ENVIRONMENT").is_ok() { - let curr_dir_path = std::env::current_dir().unwrap(); - - let bitcoind_path = if cfg!(target_os = "macos") { - curr_dir_path.join("tests/bin").join("bitcoind-mac") - } else if cfg!(target_os = "linux") { - curr_dir_path.join("tests/bin").join("bitcoind-linux") - } else { - return Err( - std::io::Error::new(std::io::ErrorKind::Other, "Unsupported platform").into(), - ); - }; - - let temp_dir = TempDir::new().expect("Failed to create temp dir"); - - let mut conf = bitcoind::Conf::default(); - conf.tmpdir = Some(temp_dir.path().to_path_buf()); - let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_path, &conf).unwrap(); - Ok(bitcoind) - } else { - let bitcoind = bitcoind::BitcoinD::from_downloaded().unwrap(); - Ok(bitcoind) - } -} - #[tokio::test] async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); let client = &bitcoind.client; - let blockchain_info = client.get_blockchain_info().unwrap(); - assert_eq!(0, blockchain_info.blocks); // Setup testing environment let btc_test_context = BTCTestContext::new(client).unwrap(); @@ -75,7 +43,7 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let alice = btc_test_context.setup_account(AddressType::Legacy).unwrap(); - // Generate 101 blocks to the address + // Generate 101 blocks to Bob's address client.generate_to_address(101, &bob.address)?; // List UTXOs for Bob @@ -104,6 +72,7 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { witness: OmniWitness::default(), }; + // Create the spend output let txout = OmniTxOut { value: OMNI_SPEND_AMOUNT, script_pubkey: OmniScriptBuf(alice.script_pubkey.as_bytes().to_vec()), @@ -114,6 +83,7 @@ async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + // Create the change output let change_txout = OmniTxOut { value: change_amount, script_pubkey: OmniScriptBuf(bob.script_pubkey.as_bytes().to_vec()), @@ -332,234 +302,6 @@ async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> Ok(()) } -#[tokio::test] -async fn test_multiple_p2wpkh_utxos() -> Result<()> { - let bitcoind = setup_bitcoin_testnet().unwrap(); - let client = &bitcoind.client; - - // Setup testing environment - let btc_test_context = BTCTestContext::new(client).unwrap(); - - // Setup Bob and Alice addresses - let bob = btc_test_context.setup_account(AddressType::Bech32).unwrap(); - - let alice = btc_test_context.setup_account(AddressType::Bech32).unwrap(); - - // Generate 101 blocks to the address - client.generate_to_address(101, &bob.address)?; - client.generate_to_address(101, &bob.address)?; - - // List UTXOs for Bob - let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); - - // Get the first two UTXOs - let first_two_unspent: Vec<_> = unspent_utxos_bob.into_iter().take(2).collect(); - assert!( - first_two_unspent.len() == 2, - "There should be at least two unspent outputs" - ); - - let mut inputs = Vec::new(); - let mut total_utxo_amount = 0; - let mut bitcoin_txids = Vec::new(); - - for unspent in &first_two_unspent { - let txid_str = unspent["txid"].as_str().unwrap(); - let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; - let omni_hash = OmniHash::from_hex(txid_str)?; - let omni_txid = OmniTxid(omni_hash); - - assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); - - let vout = unspent["vout"].as_u64().unwrap(); - - // Create inputs using Omni library - let txin: OmniTxIn = OmniTxIn { - previous_output: OmniOutPoint::new(omni_txid, vout as u32), - script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. - sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, - witness: OmniWitness::default(), // Filled in after signing. - }; - - let btc_txid = TxIn { - previous_output: OutPoint::new(bitcoin_txid, vout as u32), - script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Witness::default(), // Filled in after signing. - }; - - inputs.push(txin); - bitcoin_txids.push(btc_txid); - - let utxo_amount = - OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - - total_utxo_amount += utxo_amount.to_sat(); - } - - let change_amount: OmniAmount = - OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - - // The change output is locked to a key controlled by us. - let change_txout = OmniTxOut { - value: change_amount, - script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), - }; - - let btc_change_amount = Amount::from_sat(change_amount.to_sat()); - - // BTC Change Output - let btc_change_txout = TxOut { - value: btc_change_amount, - script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. - }; - - // BTC Spend Output - let btc_spend_txout = TxOut { - value: BITCOIN_SPEND_AMOUNT, - script_pubkey: alice.address.script_pubkey(), - }; - - // The spend output is locked to a key controlled by the receiver. In this case to Alice. - let spend_txout = OmniTxOut { - value: OMNI_SPEND_AMOUNT, - script_pubkey: OmniScriptBuf(alice.address.script_pubkey().into_bytes()), - }; - - let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() - .version(OmniVersion::Two) - .lock_time(OmniLockTime::from_height(0).unwrap()) - .inputs(inputs) - .outputs(vec![spend_txout, change_txout]) - .build(); - - // BTC Transaction - let mut unsigned_tx = Transaction { - version: transaction::Version::TWO, // Post BIP-68. - lock_time: absolute::LockTime::ZERO, // Ignore the locktime. - input: bitcoin_txids, // Input goes into index 0. - output: vec![btc_spend_txout, btc_change_txout], // Outputs, order does not matter. - }; - - let mut buffer = Vec::new(); - unsigned_tx.consensus_encode(&mut buffer).unwrap(); - - assert_eq!(buffer, omni_tx.serialize()); - - let secp = Secp256k1::new(); - - let mut witness_vec = Vec::new(); - for (i, unspent) in first_two_unspent.iter().enumerate() { - let utxo_amount = - OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - - let script_pub_key = ScriptBuf::new_p2wpkh(&bob.wpkh); - let script_pubkey_bob = script_pub_key.p2wpkh_script_code().unwrap(); - - // Prepare the transaction for signing - let sighash_type = OmniSighashType::All; - let input_index = i; - let encoded_data = omni_tx.build_for_signing_segwit( - sighash_type, - input_index, - &OmniScriptBuf(script_pubkey_bob.into_bytes()), - utxo_amount.to_sat(), - ); - - // Calculate the sighash - let sighash_omni = sha256d::Hash::hash(&encoded_data); - - let btc_sighash_type = EcdsaSighashType::All; - let mut sighasher = SighashCache::new(&mut unsigned_tx); - - let btc_utxo_amount = Amount::from_sat(utxo_amount.to_sat()); - - let mut encoded_btc_sighash = Vec::new(); - - sighasher - .segwit_v0_encode_signing_data_to( - &mut encoded_btc_sighash, - input_index, - &script_pub_key.p2wpkh_script_code().unwrap(), - btc_utxo_amount, - btc_sighash_type, - ) - .expect("failed to create sighash"); - - assert_eq!(encoded_btc_sighash, encoded_data); - - let sighash_bitcoin = sighasher - .p2wpkh_signature_hash( - input_index, - &script_pub_key, - btc_utxo_amount, - btc_sighash_type, - ) - .expect("failed to create sighash"); - - assert_eq!( - sighash_omni.to_byte_array(), - sighash_bitcoin.to_byte_array(), - "SIGHASHES ARE NOT THE SAME" - ); - - // Sign the sighash - let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); - let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); - - // Verify signature - let is_valid = secp - .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) - .is_ok(); - - assert!(is_valid, "The signature should be valid"); - - // Encode the signature - let signature = bitcoin::ecdsa::Signature { - signature: signature_omni, - sighash_type: EcdsaSighashType::All, - }; - - // Create the witness - let witness = Witness::p2wpkh(&signature, &bob.public_key); - - witness_vec.push(omni_transaction::bitcoin::types::Witness::from_slice( - &witness.to_vec(), - )); - } - - // Add the witness to the transaction - for (i, witness) in witness_vec.iter().enumerate() { - omni_tx.input[i].witness = witness.clone(); - } - - // Serialize the transaction - let serialized_tx = omni_tx.serialize(); - - // Convert the transaction to a hexadecimal string - let hex_omni_tx = hex::encode(serialized_tx); - - let _decoded_tx: serde_json::Value = - client.call("decoderawtransaction", &[json!(hex_omni_tx)])?; - - let maxfeerate = 0.10; - let maxburnamount = 100.00; - - // We now deploy to the bitcoin network (regtest mode) - let raw_tx_result: serde_json::Value = client - .call( - "sendrawtransaction", - &[json!(hex_omni_tx), json!(maxfeerate), json!(maxburnamount)], - ) - .unwrap(); - - println!("raw_tx_result: {:?}", raw_tx_result); - - client.generate_to_address(101, &bob.address)?; - - Ok(()) -} - #[tokio::test] async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); @@ -815,8 +557,6 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { // Calculate the sighash let sighash_omni = sha256d::Hash::hash(&encoded_data); - // -------------------------------------------------------------------------- - // Calculate the sighash with Rust Bitcoin let input = TxIn { previous_output: OutPoint::new(bitcoin_txid, vout as u32), @@ -930,7 +670,7 @@ async fn test_p2wpkh_single_utxo() -> Result<()> { } #[tokio::test] -async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { +async fn test_p2wpkh_multiple_utxos() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); let client = &bitcoind.client; @@ -944,42 +684,77 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { // Generate 101 blocks to the address client.generate_to_address(101, &bob.address)?; + client.generate_to_address(101, &bob.address)?; // List UTXOs for Bob let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); - // Get the first UTXO - let first_unspent = unspent_utxos_bob - .into_iter() - .next() - .expect("There should be at least one unspent output"); + // Get the first two UTXOs + let first_two_unspent: Vec<_> = unspent_utxos_bob.into_iter().take(2).collect(); + assert!( + first_two_unspent.len() == 2, + "There should be at least two unspent outputs" + ); - let txid_str = first_unspent["txid"].as_str().unwrap(); - let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; - let omni_hash = OmniHash::from_hex(txid_str)?; - let omni_txid = OmniTxid(omni_hash); + let mut inputs = Vec::new(); + let mut total_utxo_amount = 0; + let mut bitcoin_txids = Vec::new(); - assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + for unspent in &first_two_unspent { + let txid_str = unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); - let vout = first_unspent["vout"].as_u64().unwrap(); + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); - // Create inputs using Omni library - let txin: OmniTxIn = OmniTxIn { - previous_output: OmniOutPoint::new(omni_txid, vout as u32), - script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. - sequence: OmniSequence::MAX, - witness: OmniWitness::default(), - }; + let vout = unspent["vout"].as_u64().unwrap(); - let utxo_amount = - OmniAmount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: OmniSequence::ENABLE_RBF_NO_LOCKTIME, + witness: OmniWitness::default(), // Filled in after signing. + }; - let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + let btc_txid = TxIn { + previous_output: OutPoint::new(bitcoin_txid, vout as u32), + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + inputs.push(txin); + bitcoin_txids.push(btc_txid); + + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + total_utxo_amount += utxo_amount.to_sat(); + } + + let change_amount: OmniAmount = + OmniAmount::from_sat(total_utxo_amount) - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee // The change output is locked to a key controlled by us. let change_txout = OmniTxOut { value: change_amount, - script_pubkey: OmniScriptBuf(bob.address.script_pubkey().into_bytes()), // Change comes back to us. + script_pubkey: OmniScriptBuf(ScriptBuf::new_p2wpkh(&bob.wpkh).into_bytes()), + }; + + let btc_change_amount = Amount::from_sat(change_amount.to_sat()); + + // BTC Change Output + let btc_change_txout = TxOut { + value: btc_change_amount, + script_pubkey: ScriptBuf::new_p2wpkh(&bob.wpkh), // Change comes back to us. + }; + + // BTC Spend Output + let btc_spend_txout = TxOut { + value: BITCOIN_SPEND_AMOUNT, + script_pubkey: alice.address.script_pubkey(), }; // The spend output is locked to a key controlled by the receiver. In this case to Alice. @@ -990,52 +765,119 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() .version(OmniVersion::Two) - .lock_time(OmniLockTime::from_height(1).unwrap()) - .inputs(vec![txin]) + .lock_time(OmniLockTime::from_height(0).unwrap()) + .inputs(inputs) .outputs(vec![spend_txout, change_txout]) .build(); - let script_pubkey_bob = ScriptBuf::new_p2wpkh(&bob.wpkh) - .p2wpkh_script_code() - .unwrap(); + // BTC Transaction + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: bitcoin_txids, // Input goes into index 0. + output: vec![btc_spend_txout, btc_change_txout], // Outputs, order does not matter. + }; - // Prepare the transaction for signing - let sighash_type = OmniSighashType::All; - let input_index = 0; - let encoded_data = omni_tx.build_for_signing_segwit( - sighash_type, - input_index, - &OmniScriptBuf(script_pubkey_bob.into_bytes()), - utxo_amount.to_sat(), - ); + let mut buffer = Vec::new(); + unsigned_tx.consensus_encode(&mut buffer).unwrap(); - // Calculate the sighash - let sighash_omni = sha256d::Hash::hash(&encoded_data); - let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + assert_eq!(buffer, omni_tx.serialize()); - // Sign the sighash and broadcast the transaction using the Omni library let secp = Secp256k1::new(); - let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); - // Verify signature - let is_valid = secp - .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) - .is_ok(); + let mut witness_vec = Vec::new(); + for (i, unspent) in first_two_unspent.iter().enumerate() { + let utxo_amount = + OmniAmount::from_sat((unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - assert!(is_valid, "The signature should be valid"); + let script_pub_key = ScriptBuf::new_p2wpkh(&bob.wpkh); + let script_pubkey_bob = script_pub_key.p2wpkh_script_code().unwrap(); - // Encode the signature - let signature = bitcoin::ecdsa::Signature { - signature: signature_omni, - sighash_type: EcdsaSighashType::All, - }; + // Prepare the transaction for signing + let sighash_type = OmniSighashType::All; + let input_index = i; + let encoded_data = omni_tx.build_for_signing_segwit( + sighash_type, + input_index, + &OmniScriptBuf(script_pubkey_bob.into_bytes()), + utxo_amount.to_sat(), + ); - // Create the witness - let witness = Witness::p2wpkh(&signature, &bob.public_key); - let encoded_omni_tx = omni_tx.build_with_witness(0, witness.to_vec(), TransactionType::P2WPKH); + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + + let btc_sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + + let btc_utxo_amount = Amount::from_sat(utxo_amount.to_sat()); + + let mut encoded_btc_sighash = Vec::new(); + + sighasher + .segwit_v0_encode_signing_data_to( + &mut encoded_btc_sighash, + input_index, + &script_pub_key.p2wpkh_script_code().unwrap(), + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + assert_eq!(encoded_btc_sighash, encoded_data); + + let sighash_bitcoin = sighasher + .p2wpkh_signature_hash( + input_index, + &script_pub_key, + btc_utxo_amount, + btc_sighash_type, + ) + .expect("failed to create sighash"); + + assert_eq!( + sighash_omni.to_byte_array(), + sighash_bitcoin.to_byte_array(), + "SIGHASHES ARE NOT THE SAME" + ); + + // Sign the sighash + let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); + + // Verify signature + let is_valid = secp + .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) + .is_ok(); + + assert!(is_valid, "The signature should be valid"); + + // Encode the signature + let signature = bitcoin::ecdsa::Signature { + signature: signature_omni, + sighash_type: EcdsaSighashType::All, + }; + + // Create the witness + let witness = Witness::p2wpkh(&signature, &bob.public_key); + + witness_vec.push(omni_transaction::bitcoin::types::Witness::from_slice( + &witness.to_vec(), + )); + } + + // Add the witness to the transaction + for (i, witness) in witness_vec.iter().enumerate() { + omni_tx.input[i].witness = witness.clone(); + } + + // Serialize the transaction + let serialized_tx = omni_tx.serialize(); // Convert the transaction to a hexadecimal string - let hex_omni_tx = hex::encode(encoded_omni_tx); + let hex_omni_tx = hex::encode(serialized_tx); + + let _decoded_tx: serde_json::Value = + client.call("decoderawtransaction", &[json!(hex_omni_tx)])?; let maxfeerate = 0.10; let maxburnamount = 100.00; @@ -1052,8 +894,6 @@ async fn test_send_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { client.generate_to_address(101, &bob.address)?; - assert_utxos_for_address(client, alice.address, 1); - Ok(()) } @@ -1084,3 +924,29 @@ fn assert_utxos_for_address(client: &bitcoind::Client, address: Address, number_ unspent_utxos.len() ); } + +fn setup_bitcoin_testnet() -> Result { + if std::env::var("CI_ENVIRONMENT").is_ok() { + let curr_dir_path = std::env::current_dir().unwrap(); + + let bitcoind_path = if cfg!(target_os = "macos") { + curr_dir_path.join("tests/bin").join("bitcoind-mac") + } else if cfg!(target_os = "linux") { + curr_dir_path.join("tests/bin").join("bitcoind-linux") + } else { + return Err( + std::io::Error::new(std::io::ErrorKind::Other, "Unsupported platform").into(), + ); + }; + + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + + let mut conf = bitcoind::Conf::default(); + conf.tmpdir = Some(temp_dir.path().to_path_buf()); + let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_path, &conf).unwrap(); + Ok(bitcoind) + } else { + let bitcoind = bitcoind::BitcoinD::from_downloaded().unwrap(); + Ok(bitcoind) + } +} diff --git a/tests/utils/bitcoin_utils.rs b/tests/utils/bitcoin_utils.rs deleted file mode 100644 index 9595fc3..0000000 --- a/tests/utils/bitcoin_utils.rs +++ /dev/null @@ -1,209 +0,0 @@ -use bitcoin::bip32::DerivationPath; -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use bitcoin::{bip32::Xpriv, Address, Network, ScriptBuf}; -use bitcoin::{CompressedPublicKey, PublicKey as BitcoinPublicKey, WPubkeyHash}; -use bitcoind::AddressType; -use serde_json::{json, Value}; -use std::str::FromStr as _; - -pub struct UserInfo { - pub address: Address, - pub script_pubkey: ScriptBuf, - pub private_key: SecretKey, - pub public_key: PublicKey, - pub compressed_public_key: CompressedPublicKey, - pub bitcoin_public_key: BitcoinPublicKey, - pub wpkh: WPubkeyHash, -} - -pub struct BTCTestContext<'a> { - client: &'a bitcoind::Client, - master_key_p2pkh: Xpriv, - master_key_p2wpkh: Xpriv, -} - -impl<'a> BTCTestContext<'a> { - pub fn new(client: &'a bitcoind::Client) -> Result> { - let master_key_p2pkh = Self::get_master_key_of_regtest_node_p2pkh(client)?; - let master_key_p2wpkh = Self::get_master_key_of_regtest_node_p2wpkh(client)?; - - Ok(BTCTestContext { - client, - master_key_p2pkh, - master_key_p2wpkh, - }) - } - - pub fn setup_account( - &self, - address_type: AddressType, - ) -> Result> { - let address = self - .client - .get_new_address_with_type(address_type.clone()) - .unwrap() - .address() - .unwrap(); - - let address = address.require_network(Network::Regtest).unwrap(); - - // Get address info for Account - let address_info: Value = self - .client - .call("getaddressinfo", &[address.to_string().into()])?; - - // Extract the pubkey from the address info - let pubkey_hex = address_info["pubkey"] - .as_str() - .expect("pubkey should be a string"); - - let compressed_pub_key = - CompressedPublicKey::from_str(pubkey_hex).expect("Failed to parse pubkey"); - - // Extract the scriptPubKey from the address info - let script_pubkey_hex = address_info["scriptPubKey"] - .as_str() - .expect("scriptPubKey should be a string"); - - let script_pubkey = - ScriptBuf::from_hex(script_pubkey_hex).expect("Failed to parse scriptPubKey"); - - // Initialize secp256k1 context - let secp = Secp256k1::new(); - - // Derive child private key using path m/44h/1h/0h - let hd_key_path = address_info["hdkeypath"].as_str().unwrap(); - let path = DerivationPath::from_str(hd_key_path).unwrap(); - - let child = if address_type == AddressType::Bech32 { - self.master_key_p2wpkh.derive_priv(&secp, &path).unwrap() - } else { - self.master_key_p2pkh.derive_priv(&secp, &path).unwrap() - }; - - let private_key = child.private_key; - let public_key = PublicKey::from_secret_key(&secp, &private_key); - let bitcoin_public_key = BitcoinPublicKey::new(public_key); - - let derived_address = if address_type == AddressType::Bech32 { - Address::p2wpkh(&compressed_pub_key, Network::Regtest) - } else { - Address::p2pkh(compressed_pub_key, Network::Regtest) - }; - - assert_eq!( - bitcoin_public_key.to_string(), - pubkey_hex, - "Derived public key does not match the one provided by the node" - ); - // Verify that the address is the same as the one generated by the client - assert_eq!(address, derived_address); - - let wpkh: WPubkeyHash = bitcoin_public_key - .wpubkey_hash() - .expect("Failed to compute WPubkeyHash: ensure the key is compressed"); - - Ok(UserInfo { - address, - script_pubkey, - private_key, - public_key, - bitcoin_public_key, - wpkh, - compressed_public_key: compressed_pub_key, - }) - } - - fn get_master_key_of_regtest_node_p2pkh( - client: &bitcoind::Client, - ) -> Result> { - let descriptors: Value = client.call("listdescriptors", &[true.into()])?; - - let p2pkh_descriptor = descriptors["descriptors"] - .as_array() - .unwrap() - .iter() - .find(|descriptor| descriptor["desc"].as_str().unwrap().contains("pkh")) - .expect("No P2PKH descriptor found"); - - let desc = p2pkh_descriptor["desc"].as_str().unwrap(); - let parts: Vec<&str> = desc.split('/').collect(); - let master_key_str = parts[0].replace("pkh(", "").replace(")", ""); - - let master_key = Xpriv::from_str(&master_key_str).unwrap(); - - Ok(master_key) - } - - fn get_master_key_of_regtest_node_p2wpkh( - client: &bitcoind::Client, - ) -> Result> { - let descriptors: Value = client.call("listdescriptors", &[true.into()])?; - - let p2wpkh_descriptor = descriptors["descriptors"] - .as_array() - .unwrap() - .iter() - .find(|descriptor| { - let desc = descriptor["desc"].as_str().unwrap(); - desc.contains("wpkh") && !desc.starts_with("tr(") // Exclude descriptors for taproot - }) - .expect("No P2WPKH or nested P2WPKH descriptor found"); - - let desc = p2wpkh_descriptor["desc"].as_str().unwrap(); - - // Extract the xpriv part from the descriptor - let xpriv_part = desc - .split("wpkh(") - .nth(1) - .unwrap() - .split(')') - .next() - .unwrap(); - let parts: Vec<&str> = xpriv_part.split('/').collect(); - let master_key_str = parts[0]; - - // Ensure the key starts with "tprv" for testnet/regtest - let master_key_str = if !master_key_str.starts_with("tprv") { - format!("tprv{}", master_key_str) - } else { - master_key_str.to_string() - }; - - let master_key = Xpriv::from_str(&master_key_str)?; - - Ok(master_key) - } - - pub fn get_utxo_for_address( - &self, - address: &Address, - ) -> Result, Box> { - let min_conf = 1; - let max_conf = 9999999; - let include_unsafe = true; - let query_options = json!({}); - - let unspent_utxos: Vec = self.client.call( - "listunspent", - &[ - json!(min_conf), - json!(max_conf), - json!(vec![address.to_string()]), - json!(include_unsafe), - query_options, - ], - )?; - - // Verify UTXO belongs to the address and has the correct amount - for utxo in unspent_utxos.iter() { - assert_eq!( - utxo["address"].as_str().unwrap(), - address.to_string(), - "UTXO doesn't belong to the address" - ); - } - - Ok(unspent_utxos) - } -} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs deleted file mode 100644 index 335e83e..0000000 --- a/tests/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod bitcoin_utils; From a66771d52573cd88013ecab3c289aa43344b746f Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Tue, 29 Oct 2024 16:13:53 +0000 Subject: [PATCH 15/19] fix: update import --- Cargo.toml | 2 +- tests/bitcoin_integration_test.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cbf792..513dbd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,4 +73,4 @@ tokio = { version = "1.38", features = ["full"] } eyre = "0.6" # testing -omni-testing-utilities = { git = "https://github.com/Omni-rs/omni-testing-utilities", branch = "main" } +omni-testing-utilities = { git = "https://github.com/Omni-rs/omni-testing-utilities", branch = "feat/v1" } diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 5060624..48ede80 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -10,7 +10,7 @@ use bitcoin::{ScriptBuf, Sequence}; use bitcoin::{TxOut, Witness}; use bitcoind::AddressType; // Omni library -use omni_testing_utilities::BTCTestContext; +use omni_testing_utilities::bitcoin::BTCTestContext; use omni_transaction::bitcoin::bitcoin_transaction::BitcoinTransaction; use omni_transaction::bitcoin::types::{ Amount as OmniAmount, EcdsaSighashType as OmniSighashType, Hash as OmniHash, From 531288ba6f566327892cc0133bb5d47e87d3387a Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Tue, 29 Oct 2024 19:06:49 +0000 Subject: [PATCH 16/19] fix: moved dependencies to testing utils repo and changed json rpc to point to omni one --- Cargo.toml | 6 +- tests/bitcoin_integration_test.rs | 305 ++++++++++++------------------ 2 files changed, 127 insertions(+), 184 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 513dbd3..5ae3e8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,14 +46,13 @@ alloy-primitives = { version = "0.8.3" } # near near-primitives = { version = "0.25.0" } near-crypto = { version = "0.25.0" } +near-jsonrpc-client = { git = "https://github.com/omni-rs/near-jsonrpc-client-rs", tag = "v0.12.1" } near-workspaces = { version = "0.13.0", features = [ "experimental", "unstable", ] } -near-jsonrpc-client = { git = "https://github.com/EdsonAlcala/near-jsonrpc-client-rs", tag = "v0.12.1" } # bitcoin -tempfile = "3.3.0" bitcoin = { version = "0.32.0", default-features = false, features = [ "std", "serde", @@ -62,9 +61,6 @@ bitcoin = { version = "0.32.0", default-features = false, features = [ "secp-lowmemory", "secp-recovery", ] } -bitcoind = { package = "bitcoind-json-rpc-regtest", version = "0.3.0", features = [ - "26_0", -] } # async tokio = { version = "1.38", features = ["full"] } diff --git a/tests/bitcoin_integration_test.rs b/tests/bitcoin_integration_test.rs index 48ede80..7b9a1c1 100644 --- a/tests/bitcoin_integration_test.rs +++ b/tests/bitcoin_integration_test.rs @@ -1,16 +1,18 @@ -use bitcoin::consensus::Encodable; // Rust Bitcoin +use bitcoin::consensus::Encodable; use bitcoin::hashes::{sha256d, Hash}; use bitcoin::script::Builder; use bitcoin::secp256k1::{Message, Secp256k1}; use bitcoin::sighash::SighashCache; -use bitcoin::{absolute, transaction, Address, Amount, Transaction, TxIn}; +use bitcoin::{absolute, transaction, Amount, Transaction, TxIn}; use bitcoin::{EcdsaSighashType, OutPoint}; use bitcoin::{ScriptBuf, Sequence}; use bitcoin::{TxOut, Witness}; -use bitcoind::AddressType; -// Omni library +// Omni testing utilities +use omni_testing_utilities::bitcoin::setup_bitcoin_testnet; use omni_testing_utilities::bitcoin::BTCTestContext; +use omni_testing_utilities::bitcoind::AddressType; +// Omni library use omni_transaction::bitcoin::bitcoin_transaction::BitcoinTransaction; use omni_transaction::bitcoin::types::{ Amount as OmniAmount, EcdsaSighashType as OmniSighashType, Hash as OmniHash, @@ -25,131 +27,10 @@ use omni_transaction::types::BITCOIN; use eyre::Result; use serde_json::json; use std::result::Result::Ok; -use tempfile::TempDir; const OMNI_SPEND_AMOUNT: OmniAmount = OmniAmount::from_sat(500_000_000); const BITCOIN_SPEND_AMOUNT: Amount = Amount::from_sat(500_000_000); -#[tokio::test] -async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { - let bitcoind = setup_bitcoin_testnet().unwrap(); - let client = &bitcoind.client; - - // Setup testing environment - let btc_test_context = BTCTestContext::new(client).unwrap(); - - // Setup Bob and Alice addresses - let bob = btc_test_context.setup_account(AddressType::Legacy).unwrap(); - - let alice = btc_test_context.setup_account(AddressType::Legacy).unwrap(); - - // Generate 101 blocks to Bob's address - client.generate_to_address(101, &bob.address)?; - - // List UTXOs for Bob - let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); - - // Get the first UTXO - let first_unspent = unspent_utxos_bob - .into_iter() - .next() - .expect("There should be at least one unspent output"); - - let txid_str = first_unspent["txid"].as_str().unwrap(); - let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; - let omni_hash = OmniHash::from_hex(txid_str)?; - let omni_txid = OmniTxid(omni_hash); - - assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); - - let vout = first_unspent["vout"].as_u64().unwrap(); - - // Create inputs using Omni library - let txin: OmniTxIn = OmniTxIn { - previous_output: OmniOutPoint::new(omni_txid, vout as u32), - script_sig: OmniScriptBuf::default(), // Initially empty, will be filled later with the signature - sequence: OmniSequence::MAX, - witness: OmniWitness::default(), - }; - - // Create the spend output - let txout = OmniTxOut { - value: OMNI_SPEND_AMOUNT, - script_pubkey: OmniScriptBuf(alice.script_pubkey.as_bytes().to_vec()), - }; - - let utxo_amount = - OmniAmount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); - - let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee - - // Create the change output - let change_txout = OmniTxOut { - value: change_amount, - script_pubkey: OmniScriptBuf(bob.script_pubkey.as_bytes().to_vec()), - }; - - let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() - .version(OmniVersion::One) - .lock_time(OmniLockTime::from_height(1).unwrap()) - .inputs(vec![txin]) - .outputs(vec![txout, change_txout]) - .build(); - - // Add the script_sig to the transaction - omni_tx.input[0].script_sig = OmniScriptBuf(bob.script_pubkey.as_bytes().to_vec()); - - // Encode the transaction for signing - let sighash_type = OmniSighashType::All; - let encoded_data = omni_tx.build_for_signing_legacy(sighash_type); - - // Calculate the sighash - let sighash_omni = sha256d::Hash::hash(&encoded_data); - let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); - - // Sign the sighash and broadcast the transaction using the Omni library - let secp = Secp256k1::new(); - let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); - - // Verify signature - let is_valid = secp - .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) - .is_ok(); - - assert!(is_valid, "The signature should be valid"); - - // Encode the signature - let signature = bitcoin::ecdsa::Signature { - signature: signature_omni, - sighash_type: EcdsaSighashType::All, - }; - - // Create the script_sig - let script_sig_new = Builder::new() - .push_slice(signature.serialize()) - .push_key(&bob.bitcoin_public_key) - .into_script(); - - // Assign script_sig to txin - let omni_script_sig = OmniScriptBuf(script_sig_new.as_bytes().to_vec()); - let encoded_omni_tx = omni_tx.build_with_script_sig(0, omni_script_sig, TransactionType::P2PKH); - - // Convert the transaction to a hexadecimal string - let hex_omni_tx = hex::encode(encoded_omni_tx); - - let raw_tx_result: serde_json::Value = client - .call("sendrawtransaction", &[json!(hex_omni_tx)]) - .unwrap(); - - println!("raw_tx_result: {:?}", raw_tx_result); - - client.generate_to_address(101, &bob.address)?; - - assert_utxos_for_address(client, alice.address, 1); - - Ok(()) -} - #[tokio::test] async fn test_sighash_p2wpkh_using_rust_bitcoin_and_omni_library() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); @@ -474,6 +355,126 @@ async fn test_sighash_for_multiple_p2wpkh_utxos() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_send_p2pkh_using_rust_bitcoin_and_omni_library() -> Result<()> { + let bitcoind = setup_bitcoin_testnet().unwrap(); + let client = &bitcoind.client; + + // Setup testing environment + let btc_test_context = BTCTestContext::new(client).unwrap(); + + // Setup Bob and Alice addresses + let bob = btc_test_context.setup_account(AddressType::Legacy).unwrap(); + + let alice = btc_test_context.setup_account(AddressType::Legacy).unwrap(); + + // Generate 101 blocks to Bob's address + client.generate_to_address(101, &bob.address)?; + + // List UTXOs for Bob + let unspent_utxos_bob = btc_test_context.get_utxo_for_address(&bob.address).unwrap(); + + // Get the first UTXO + let first_unspent = unspent_utxos_bob + .into_iter() + .next() + .expect("There should be at least one unspent output"); + + let txid_str = first_unspent["txid"].as_str().unwrap(); + let bitcoin_txid: bitcoin::Txid = txid_str.parse()?; + let omni_hash = OmniHash::from_hex(txid_str)?; + let omni_txid = OmniTxid(omni_hash); + + assert_eq!(bitcoin_txid.to_string(), omni_txid.to_string()); + + let vout = first_unspent["vout"].as_u64().unwrap(); + + // Create inputs using Omni library + let txin: OmniTxIn = OmniTxIn { + previous_output: OmniOutPoint::new(omni_txid, vout as u32), + script_sig: OmniScriptBuf::default(), // Initially empty, will be filled later with the signature + sequence: OmniSequence::MAX, + witness: OmniWitness::default(), + }; + + // Create the spend output + let txout = OmniTxOut { + value: OMNI_SPEND_AMOUNT, + script_pubkey: OmniScriptBuf(alice.script_pubkey.as_bytes().to_vec()), + }; + + let utxo_amount = + OmniAmount::from_sat((first_unspent["amount"].as_f64().unwrap() * 100_000_000.0) as u64); + + let change_amount: OmniAmount = utxo_amount - OMNI_SPEND_AMOUNT - OmniAmount::from_sat(1000); // 1000 satoshis for fee + + // Create the change output + let change_txout = OmniTxOut { + value: change_amount, + script_pubkey: OmniScriptBuf(bob.script_pubkey.as_bytes().to_vec()), + }; + + let mut omni_tx: BitcoinTransaction = TransactionBuilder::new::() + .version(OmniVersion::One) + .lock_time(OmniLockTime::from_height(1).unwrap()) + .inputs(vec![txin]) + .outputs(vec![txout, change_txout]) + .build(); + + // Add the script_sig to the transaction + omni_tx.input[0].script_sig = OmniScriptBuf(bob.script_pubkey.as_bytes().to_vec()); + + // Encode the transaction for signing + let sighash_type = OmniSighashType::All; + let encoded_data = omni_tx.build_for_signing_legacy(sighash_type); + + // Calculate the sighash + let sighash_omni = sha256d::Hash::hash(&encoded_data); + let msg_omni = Message::from_digest_slice(sighash_omni.as_byte_array()).unwrap(); + + // Sign the sighash and broadcast the transaction using the Omni library + let secp = Secp256k1::new(); + let signature_omni = secp.sign_ecdsa(&msg_omni, &bob.private_key); + + // Verify signature + let is_valid = secp + .verify_ecdsa(&msg_omni, &signature_omni, &bob.public_key) + .is_ok(); + + assert!(is_valid, "The signature should be valid"); + + // Encode the signature + let signature = bitcoin::ecdsa::Signature { + signature: signature_omni, + sighash_type: EcdsaSighashType::All, + }; + + // Create the script_sig + let script_sig_new = Builder::new() + .push_slice(signature.serialize()) + .push_key(&bob.bitcoin_public_key) + .into_script(); + + // Assign script_sig to txin + let omni_script_sig = OmniScriptBuf(script_sig_new.as_bytes().to_vec()); + let encoded_omni_tx = omni_tx.build_with_script_sig(0, omni_script_sig, TransactionType::P2PKH); + + // Convert the transaction to a hexadecimal string + let hex_omni_tx = hex::encode(encoded_omni_tx); + + let raw_tx_result: serde_json::Value = client + .call("sendrawtransaction", &[json!(hex_omni_tx)]) + .unwrap(); + + println!("raw_tx_result: {:?}", raw_tx_result); + + client.generate_to_address(101, &bob.address)?; + + btc_test_context.assert_utxos_for_address(alice.address, 1); + + Ok(()) +} + #[tokio::test] async fn test_p2wpkh_single_utxo() -> Result<()> { let bitcoind = setup_bitcoin_testnet().unwrap(); @@ -896,57 +897,3 @@ async fn test_p2wpkh_multiple_utxos() -> Result<()> { Ok(()) } - -fn assert_utxos_for_address(client: &bitcoind::Client, address: Address, number_of_utxos: usize) { - let min_conf = 1; - let max_conf = 9999999; - let include_unsafe = true; - let query_options = json!({}); - - let unspent_utxos: Vec = client - .call( - "listunspent", - &[ - json!(min_conf), - json!(max_conf), - json!(vec![address.to_string()]), - json!(include_unsafe), - query_options, - ], - ) - .unwrap(); - - assert!( - unspent_utxos.len() == number_of_utxos, - "Expected {} UTXOs for address {}, but found {}", - number_of_utxos, - address, - unspent_utxos.len() - ); -} - -fn setup_bitcoin_testnet() -> Result { - if std::env::var("CI_ENVIRONMENT").is_ok() { - let curr_dir_path = std::env::current_dir().unwrap(); - - let bitcoind_path = if cfg!(target_os = "macos") { - curr_dir_path.join("tests/bin").join("bitcoind-mac") - } else if cfg!(target_os = "linux") { - curr_dir_path.join("tests/bin").join("bitcoind-linux") - } else { - return Err( - std::io::Error::new(std::io::ErrorKind::Other, "Unsupported platform").into(), - ); - }; - - let temp_dir = TempDir::new().expect("Failed to create temp dir"); - - let mut conf = bitcoind::Conf::default(); - conf.tmpdir = Some(temp_dir.path().to_path_buf()); - let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_path, &conf).unwrap(); - Ok(bitcoind) - } else { - let bitcoind = bitcoind::BitcoinD::from_downloaded().unwrap(); - Ok(bitcoind) - } -} From 83ade029af0872a673b9a5319be08b18894f6942 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Tue, 29 Oct 2024 19:24:02 +0000 Subject: [PATCH 17/19] fix: updated cargo to enable wasm build --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5ae3e8d..3088dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ near = [] rlp = "0.6.1" hex = "0.4.3" borsh = { version = "1.0.0", features = ["derive"] } -near-sdk = { version = "5.3.0", features = ["schemars", "abi"] } +near-sdk = { version = "5.3.0", features = ["schemars"] } near-account-id = { version = "1.0.0", features = ["schemars"] } serde-big-array = "0.5.1" bs58 = "0.5.1" From 51caf0685dd227a13704ef39c0745f8856dbeeb9 Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Tue, 29 Oct 2024 19:28:35 +0000 Subject: [PATCH 18/19] updated dependency of omni testing utilities --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3088dd6..aaaf835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,4 +69,4 @@ tokio = { version = "1.38", features = ["full"] } eyre = "0.6" # testing -omni-testing-utilities = { git = "https://github.com/Omni-rs/omni-testing-utilities", branch = "feat/v1" } +omni-testing-utilities = { git = "https://github.com/Omni-rs/omni-testing-utilities", branch = "main" } From f0b3920cfa78bec76f2a0be6c54395d75bcab7ef Mon Sep 17 00:00:00 2001 From: edsonalcala Date: Fri, 1 Nov 2024 18:10:25 +0000 Subject: [PATCH 19/19] fix: export integers module --- src/near/types/mod.rs | 2 +- tests/near_integration_test.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/near/types/mod.rs b/src/near/types/mod.rs index d5686ab..92bc4e0 100644 --- a/src/near/types/mod.rs +++ b/src/near/types/mod.rs @@ -1,6 +1,6 @@ mod actions; mod block_hash; -mod integers; +pub mod integers; mod public_key; mod signature; diff --git a/tests/near_integration_test.rs b/tests/near_integration_test.rs index 2660271..0085f28 100644 --- a/tests/near_integration_test.rs +++ b/tests/near_integration_test.rs @@ -5,7 +5,7 @@ use near_jsonrpc_client::{methods, JsonRpcClient}; use near_primitives::hash::CryptoHash; use near_workspaces::sandbox; use omni_transaction::near::types::{ - Action, ED25519Signature, Signature as OmniSignature, TransferAction, U128, + Action, ED25519Signature, Signature as OmniSignature, TransferAction, }; use omni_transaction::near::utils::PublicKeyStrExt; use omni_transaction::transaction_builder::{TransactionBuilder, TxBuilder}; @@ -26,7 +26,9 @@ async fn test_send_raw_transaction_created_with_omnitransactionbuilder_for_near( let alice_original_balance = alice.view_account().await?.balance; let bob_original_balance = bob.view_account().await?.balance; - let transfer_action = Action::Transfer(TransferAction { deposit: U128(1) }); + let transfer_action = Action::Transfer(TransferAction { + deposit: omni_transaction::near::types::integers::U128(1), + }); let actions = vec![transfer_action]; // Configure the signer from the first default Sandbox account (Alice).