Skip to content

Commit c9d16d0

Browse files
committed
fix: generating stacking signatures from b58 addresses
1 parent d0df9d1 commit c9d16d0

File tree

2 files changed

+140
-10
lines changed

2 files changed

+140
-10
lines changed

stacks-signer/src/cli.rs

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ use blockstack_lib::chainstate::stacks::address::PoxAddress;
2121
use blockstack_lib::util_lib::signed_structured_data::pox4::Pox4SignatureTopic;
2222
use clap::{Parser, ValueEnum};
2323
use clarity::vm::types::QualifiedContractIdentifier;
24-
use stacks_common::address::b58;
24+
use stacks_common::address::{
25+
b58, AddressHashMode, C32_ADDRESS_VERSION_MAINNET_MULTISIG,
26+
C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_MULTISIG,
27+
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
28+
};
2529
use stacks_common::types::chainstate::StacksPrivateKey;
2630

2731
use crate::config::Network;
@@ -260,12 +264,26 @@ fn parse_contract(contract: &str) -> Result<QualifiedContractIdentifier, String>
260264
QualifiedContractIdentifier::parse(contract).map_err(|e| format!("Invalid contract: {}", e))
261265
}
262266

263-
/// Parse a BTC address argument and return a `PoxAddress`
267+
/// Parse a BTC address argument and return a `PoxAddress`.
268+
/// This function behaves similarly to `PoxAddress::from_b58`, but also handles
269+
/// addresses where the parsed AddressHashMode is None.
264270
pub fn parse_pox_addr(pox_address_literal: &str) -> Result<PoxAddress, String> {
265-
PoxAddress::from_b58(pox_address_literal).map_or_else(
271+
let parsed_addr = PoxAddress::from_b58(pox_address_literal).map_or_else(
266272
|| Err(format!("Invalid pox address: {pox_address_literal}")),
267273
|pox_address| Ok(pox_address),
268-
)
274+
);
275+
match parsed_addr {
276+
Ok(PoxAddress::Standard(addr, None)) => match addr.version {
277+
C32_ADDRESS_VERSION_MAINNET_MULTISIG | C32_ADDRESS_VERSION_TESTNET_MULTISIG => Ok(
278+
PoxAddress::Standard(addr, Some(AddressHashMode::SerializeP2SH)),
279+
),
280+
C32_ADDRESS_VERSION_MAINNET_SINGLESIG | C32_ADDRESS_VERSION_TESTNET_SINGLESIG => Ok(
281+
PoxAddress::Standard(addr, Some(AddressHashMode::SerializeP2PKH)),
282+
),
283+
_ => Err(format!("Invalid address version: {}", addr.version)),
284+
},
285+
_ => parsed_addr,
286+
}
269287
}
270288

271289
/// Parse the hexadecimal Stacks private key
@@ -305,14 +323,52 @@ fn parse_network(network: &str) -> Result<Network, String> {
305323

306324
#[cfg(test)]
307325
mod tests {
308-
use blockstack_lib::chainstate::stacks::address::{PoxAddressType20, PoxAddressType32};
326+
use blockstack_lib::{
327+
chainstate::stacks::address::{PoxAddressType20, PoxAddressType32},
328+
util_lib::signed_structured_data::pox4::make_pox_4_signer_key_message_hash,
329+
};
330+
use clarity::{consts::CHAIN_ID_TESTNET, util::hash::Sha256Sum};
309331

310332
use super::*;
311333

334+
/// Helper just to ensure that a the pox address
335+
/// can be turned into a clarity tuple
336+
fn make_message_hash(pox_addr: &PoxAddress) -> Sha256Sum {
337+
make_pox_4_signer_key_message_hash(
338+
pox_addr,
339+
0,
340+
&Pox4SignatureTopic::StackStx,
341+
CHAIN_ID_TESTNET,
342+
0,
343+
0,
344+
0,
345+
)
346+
}
347+
348+
fn clarity_tuple_version(pox_addr: &PoxAddress) -> u8 {
349+
pox_addr
350+
.as_clarity_tuple()
351+
.expect("Failed to generate clarity tuple for pox address")
352+
.get("version")
353+
.expect("Expected version in clarity tuple")
354+
.clone()
355+
.expect_buff(1)
356+
.expect("Expected version to be a u128")
357+
.get(0)
358+
.expect("Expected version to be a uint")
359+
.clone()
360+
}
361+
312362
#[test]
313363
fn test_parse_pox_addr() {
314364
let tr = "bc1p8vg588hldsnv4a558apet4e9ff3pr4awhqj2hy8gy6x2yxzjpmqsvvpta4";
315365
let pox_addr = parse_pox_addr(tr).expect("Failed to parse segwit address");
366+
assert_eq!(tr, pox_addr.clone().to_b58());
367+
make_message_hash(&pox_addr);
368+
assert_eq!(
369+
clarity_tuple_version(&pox_addr),
370+
PoxAddressType32::P2TR.to_u8()
371+
);
316372
match pox_addr {
317373
PoxAddress::Addr32(_, addr_type, _) => {
318374
assert_eq!(addr_type, PoxAddressType32::P2TR);
@@ -322,35 +378,105 @@ mod tests {
322378

323379
let legacy = "1N8GMS991YDY1E696e9SB9EsYY5ckSU7hZ";
324380
let pox_addr = parse_pox_addr(legacy).expect("Failed to parse legacy address");
381+
assert_eq!(legacy, pox_addr.clone().to_b58());
382+
make_message_hash(&pox_addr);
383+
assert_eq!(
384+
clarity_tuple_version(&pox_addr),
385+
AddressHashMode::SerializeP2PKH as u8
386+
);
325387
match pox_addr {
326388
PoxAddress::Standard(stacks_addr, hash_mode) => {
327389
assert_eq!(stacks_addr.version, 22);
328-
assert!(hash_mode.is_none());
390+
assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2PKH));
329391
}
330392
_ => panic!("Invalid parsed address"),
331393
}
332394

333395
let p2sh = "33JNgVMNMC9Xm6mJG9oTVf5zWbmt5xi1Mv";
334396
let pox_addr = parse_pox_addr(p2sh).expect("Failed to parse legacy address");
397+
assert_eq!(p2sh, pox_addr.clone().to_b58());
398+
assert_eq!(
399+
clarity_tuple_version(&pox_addr),
400+
AddressHashMode::SerializeP2SH as u8
401+
);
402+
make_message_hash(&pox_addr);
335403
match pox_addr {
336404
PoxAddress::Standard(stacks_addr, hash_mode) => {
337405
assert_eq!(stacks_addr.version, 20);
338-
assert!(hash_mode.is_none());
406+
assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2SH));
407+
}
408+
_ => panic!("Invalid parsed address"),
409+
}
410+
411+
let testnet_p2pkh = "mnr5asd1MLSutHLL514WZXNpUNN3L98zBc";
412+
let pox_addr = parse_pox_addr(testnet_p2pkh).expect("Failed to parse testnet address");
413+
assert_eq!(
414+
clarity_tuple_version(&pox_addr),
415+
AddressHashMode::SerializeP2PKH as u8
416+
);
417+
assert_eq!(testnet_p2pkh, pox_addr.clone().to_b58());
418+
make_message_hash(&pox_addr);
419+
match pox_addr {
420+
PoxAddress::Standard(stacks_addr, hash_mode) => {
421+
assert_eq!(stacks_addr.version, C32_ADDRESS_VERSION_TESTNET_SINGLESIG);
422+
assert_eq!(hash_mode, Some(AddressHashMode::SerializeP2PKH));
339423
}
340424
_ => panic!("Invalid parsed address"),
341425
}
342426

343427
let wsh = "bc1qvnpcphdctvmql5gdw6chtwvvsl6ra9gwa2nehc99np7f24juc4vqrx29cs";
344428
let pox_addr = parse_pox_addr(wsh).expect("Failed to parse segwit address");
429+
assert_eq!(
430+
clarity_tuple_version(&pox_addr),
431+
PoxAddressType32::P2WSH.to_u8()
432+
);
433+
assert_eq!(wsh, pox_addr.clone().to_b58());
434+
make_message_hash(&pox_addr);
345435
match pox_addr {
346436
PoxAddress::Addr32(_, addr_type, _) => {
347437
assert_eq!(addr_type, PoxAddressType32::P2WSH);
348438
}
349439
_ => panic!("Invalid parsed address"),
350440
}
351441

352-
let wpkh = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
442+
let wpkh = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
353443
let pox_addr = parse_pox_addr(wpkh).expect("Failed to parse segwit address");
444+
assert_eq!(
445+
clarity_tuple_version(&pox_addr),
446+
PoxAddressType20::P2WPKH.to_u8()
447+
);
448+
assert_eq!(wpkh, pox_addr.clone().to_b58());
449+
make_message_hash(&pox_addr);
450+
match pox_addr {
451+
PoxAddress::Addr20(_, addr_type, _) => {
452+
assert_eq!(addr_type, PoxAddressType20::P2WPKH);
453+
}
454+
_ => panic!("Invalid parsed address"),
455+
}
456+
457+
let testnet_tr = "tb1p46cgptxsfwkqpnnj552rkae3nf6l52wxn4snp4vm6mcrz2585hwq6cdwf2";
458+
let pox_addr = parse_pox_addr(testnet_tr).expect("Failed to parse testnet address");
459+
assert_eq!(testnet_tr, pox_addr.clone().to_b58());
460+
make_message_hash(&pox_addr);
461+
assert_eq!(
462+
clarity_tuple_version(&pox_addr),
463+
PoxAddressType32::P2TR.to_u8()
464+
);
465+
match pox_addr {
466+
PoxAddress::Addr32(_, addr_type, _) => {
467+
assert_eq!(addr_type, PoxAddressType32::P2TR);
468+
}
469+
_ => panic!("Invalid parsed address"),
470+
}
471+
472+
let testnet_segwit = "tb1q38eleudmqyg4jrm39dnudj23pv6jcjrksa437s";
473+
let pox_addr = parse_pox_addr(testnet_segwit).expect("Failed to parse testnet address");
474+
assert_eq!(testnet_segwit, pox_addr.clone().to_b58());
475+
make_message_hash(&pox_addr);
476+
assert_eq!(
477+
clarity_tuple_version(&pox_addr),
478+
PoxAddressType20::P2WPKH.to_u8()
479+
);
354480
match pox_addr {
355481
PoxAddress::Addr20(_, addr_type, _) => {
356482
assert_eq!(addr_type, PoxAddressType20::P2WPKH);

stackslib/src/util_lib/signed_structured_data.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ pub mod pox4 {
106106
TupleData::from_data(vec![
107107
(
108108
"pox-addr".into(),
109-
pox_addr.clone().as_clarity_tuple().unwrap().into(),
109+
pox_addr
110+
.clone()
111+
.as_clarity_tuple()
112+
.expect("Error creating signature hash - invalid PoX Address")
113+
.into(),
110114
),
111115
("reward-cycle".into(), Value::UInt(reward_cycle)),
112116
("period".into(), Value::UInt(period)),
@@ -117,7 +121,7 @@ pub mod pox4 {
117121
("auth-id".into(), Value::UInt(auth_id)),
118122
("max-amount".into(), Value::UInt(max_amount)),
119123
])
120-
.unwrap(),
124+
.expect("Error creating signature hash"),
121125
);
122126
structured_data_message_hash(data_tuple, domain_tuple)
123127
}

0 commit comments

Comments
 (0)