diff --git a/Cargo.lock b/Cargo.lock index 5376ac3182..81183c6f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,28 +498,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bindgen" -version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 1.0.109", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -632,15 +610,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "0.1.10" @@ -692,17 +661,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "2.34.0" @@ -1500,12 +1458,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "gloo-timers" version = "0.2.6" @@ -1926,12 +1878,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.153" @@ -1958,16 +1904,6 @@ dependencies = [ "rle-decode-fast", ] -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys 0.48.0", -] - [[package]] name = "libredox" version = "0.0.1" @@ -2090,12 +2026,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2195,16 +2125,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2288,7 +2208,6 @@ version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a64d160b891178fb9d43d1a58ddcafb6502daeb54d810e5e92a7c3c9bfacc07" dependencies = [ - "bindgen", "bitvec", "bs58 0.4.0", "cc", @@ -2337,12 +2256,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2978,12 +2891,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -3345,12 +3252,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signature" version = "2.2.0" @@ -4471,18 +4372,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.31", -] - [[package]] name = "winapi" version = "0.2.8" diff --git a/stacks-signer/src/cli.rs b/stacks-signer/src/cli.rs index 0bed4038e4..24f27cb58c 100644 --- a/stacks-signer/src/cli.rs +++ b/stacks-signer/src/cli.rs @@ -21,7 +21,11 @@ use blockstack_lib::chainstate::stacks::address::PoxAddress; use blockstack_lib::util_lib::signed_structured_data::pox4::Pox4SignatureTopic; use clap::{Parser, ValueEnum}; use clarity::vm::types::QualifiedContractIdentifier; -use stacks_common::address::b58; +use stacks_common::address::{ + b58, AddressHashMode, C32_ADDRESS_VERSION_MAINNET_MULTISIG, + C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_MULTISIG, + C32_ADDRESS_VERSION_TESTNET_SINGLESIG, +}; use stacks_common::types::chainstate::StacksPrivateKey; use crate::config::Network; @@ -260,12 +264,26 @@ fn parse_contract(contract: &str) -> Result QualifiedContractIdentifier::parse(contract).map_err(|e| format!("Invalid contract: {}", e)) } -/// Parse a BTC address argument and return a `PoxAddress` +/// Parse a BTC address argument and return a `PoxAddress`. +/// This function behaves similarly to `PoxAddress::from_b58`, but also handles +/// addresses where the parsed AddressHashMode is None. pub fn parse_pox_addr(pox_address_literal: &str) -> Result { - PoxAddress::from_b58(pox_address_literal).map_or_else( + let parsed_addr = PoxAddress::from_b58(pox_address_literal).map_or_else( || Err(format!("Invalid pox address: {pox_address_literal}")), - |pox_address| Ok(pox_address), - ) + Ok, + ); + match parsed_addr { + Ok(PoxAddress::Standard(addr, None)) => match addr.version { + C32_ADDRESS_VERSION_MAINNET_MULTISIG | C32_ADDRESS_VERSION_TESTNET_MULTISIG => Ok( + PoxAddress::Standard(addr, Some(AddressHashMode::SerializeP2SH)), + ), + C32_ADDRESS_VERSION_MAINNET_SINGLESIG | C32_ADDRESS_VERSION_TESTNET_SINGLESIG => Ok( + PoxAddress::Standard(addr, Some(AddressHashMode::SerializeP2PKH)), + ), + _ => Err(format!("Invalid address version: {}", addr.version)), + }, + _ => parsed_addr, + } } /// Parse the hexadecimal Stacks private key @@ -306,13 +324,50 @@ fn parse_network(network: &str) -> Result { #[cfg(test)] mod tests { use blockstack_lib::chainstate::stacks::address::{PoxAddressType20, PoxAddressType32}; + use blockstack_lib::util_lib::signed_structured_data::pox4::make_pox_4_signer_key_message_hash; + use clarity::consts::CHAIN_ID_TESTNET; + use clarity::util::hash::Sha256Sum; use super::*; + /// Helper just to ensure that a the pox address + /// can be turned into a clarity tuple + fn make_message_hash(pox_addr: &PoxAddress) -> Sha256Sum { + make_pox_4_signer_key_message_hash( + pox_addr, + 0, + &Pox4SignatureTopic::StackStx, + CHAIN_ID_TESTNET, + 0, + 0, + 0, + ) + } + + fn clarity_tuple_version(pox_addr: &PoxAddress) -> u8 { + pox_addr + .as_clarity_tuple() + .expect("Failed to generate clarity tuple for pox address") + .get("version") + .expect("Expected version in clarity tuple") + .clone() + .expect_buff(1) + .expect("Expected version to be a u128") + .get(0) + .expect("Expected version to be a uint") + .clone() + } + #[test] fn test_parse_pox_addr() { let tr = "bc1p8vg588hldsnv4a558apet4e9ff3pr4awhqj2hy8gy6x2yxzjpmqsvvpta4"; let pox_addr = parse_pox_addr(tr).expect("Failed to parse segwit address"); + assert_eq!(tr, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + assert_eq!( + clarity_tuple_version(&pox_addr), + PoxAddressType32::P2TR.to_u8() + ); match pox_addr { PoxAddress::Addr32(_, addr_type, _) => { assert_eq!(addr_type, PoxAddressType32::P2TR); @@ -322,26 +377,60 @@ mod tests { let legacy = "1N8GMS991YDY1E696e9SB9EsYY5ckSU7hZ"; let pox_addr = parse_pox_addr(legacy).expect("Failed to parse legacy address"); + assert_eq!(legacy, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + assert_eq!( + clarity_tuple_version(&pox_addr), + AddressHashMode::SerializeP2PKH as u8 + ); match pox_addr { PoxAddress::Standard(stacks_addr, hash_mode) => { assert_eq!(stacks_addr.version, 22); - assert!(hash_mode.is_none()); + assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2PKH)); } _ => panic!("Invalid parsed address"), } let p2sh = "33JNgVMNMC9Xm6mJG9oTVf5zWbmt5xi1Mv"; let pox_addr = parse_pox_addr(p2sh).expect("Failed to parse legacy address"); + assert_eq!(p2sh, pox_addr.clone().to_b58()); + assert_eq!( + clarity_tuple_version(&pox_addr), + AddressHashMode::SerializeP2SH as u8 + ); + make_message_hash(&pox_addr); match pox_addr { PoxAddress::Standard(stacks_addr, hash_mode) => { assert_eq!(stacks_addr.version, 20); - assert!(hash_mode.is_none()); + assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2SH)); + } + _ => panic!("Invalid parsed address"), + } + + let testnet_p2pkh = "mnr5asd1MLSutHLL514WZXNpUNN3L98zBc"; + let pox_addr = parse_pox_addr(testnet_p2pkh).expect("Failed to parse testnet address"); + assert_eq!( + clarity_tuple_version(&pox_addr), + AddressHashMode::SerializeP2PKH as u8 + ); + assert_eq!(testnet_p2pkh, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + match pox_addr { + PoxAddress::Standard(stacks_addr, hash_mode) => { + assert_eq!(stacks_addr.version, C32_ADDRESS_VERSION_TESTNET_SINGLESIG); + assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2PKH)); } _ => panic!("Invalid parsed address"), } let wsh = "bc1qvnpcphdctvmql5gdw6chtwvvsl6ra9gwa2nehc99np7f24juc4vqrx29cs"; let pox_addr = parse_pox_addr(wsh).expect("Failed to parse segwit address"); + assert_eq!( + clarity_tuple_version(&pox_addr), + PoxAddressType32::P2WSH.to_u8() + ); + assert_eq!(wsh, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); match pox_addr { PoxAddress::Addr32(_, addr_type, _) => { assert_eq!(addr_type, PoxAddressType32::P2WSH); @@ -349,8 +438,44 @@ mod tests { _ => panic!("Invalid parsed address"), } - let wpkh = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"; + let wpkh = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; let pox_addr = parse_pox_addr(wpkh).expect("Failed to parse segwit address"); + assert_eq!( + clarity_tuple_version(&pox_addr), + PoxAddressType20::P2WPKH.to_u8() + ); + assert_eq!(wpkh, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + match pox_addr { + PoxAddress::Addr20(_, addr_type, _) => { + assert_eq!(addr_type, PoxAddressType20::P2WPKH); + } + _ => panic!("Invalid parsed address"), + } + + let testnet_tr = "tb1p46cgptxsfwkqpnnj552rkae3nf6l52wxn4snp4vm6mcrz2585hwq6cdwf2"; + let pox_addr = parse_pox_addr(testnet_tr).expect("Failed to parse testnet address"); + assert_eq!(testnet_tr, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + assert_eq!( + clarity_tuple_version(&pox_addr), + PoxAddressType32::P2TR.to_u8() + ); + match pox_addr { + PoxAddress::Addr32(_, addr_type, _) => { + assert_eq!(addr_type, PoxAddressType32::P2TR); + } + _ => panic!("Invalid parsed address"), + } + + let testnet_segwit = "tb1q38eleudmqyg4jrm39dnudj23pv6jcjrksa437s"; + let pox_addr = parse_pox_addr(testnet_segwit).expect("Failed to parse testnet address"); + assert_eq!(testnet_segwit, pox_addr.clone().to_b58()); + make_message_hash(&pox_addr); + assert_eq!( + clarity_tuple_version(&pox_addr), + PoxAddressType20::P2WPKH.to_u8() + ); match pox_addr { PoxAddress::Addr20(_, addr_type, _) => { assert_eq!(addr_type, PoxAddressType20::P2WPKH); diff --git a/stackslib/src/util_lib/signed_structured_data.rs b/stackslib/src/util_lib/signed_structured_data.rs index 9cc0eaa0f1..566a3da701 100644 --- a/stackslib/src/util_lib/signed_structured_data.rs +++ b/stackslib/src/util_lib/signed_structured_data.rs @@ -106,7 +106,11 @@ pub mod pox4 { TupleData::from_data(vec![ ( "pox-addr".into(), - pox_addr.clone().as_clarity_tuple().unwrap().into(), + pox_addr + .clone() + .as_clarity_tuple() + .expect("Error creating signature hash - invalid PoX Address") + .into(), ), ("reward-cycle".into(), Value::UInt(reward_cycle)), ("period".into(), Value::UInt(period)), @@ -117,7 +121,7 @@ pub mod pox4 { ("auth-id".into(), Value::UInt(auth_id)), ("max-amount".into(), Value::UInt(max_amount)), ]) - .unwrap(), + .expect("Error creating signature hash"), ); structured_data_message_hash(data_tuple, domain_tuple) }