@@ -21,7 +21,11 @@ use blockstack_lib::chainstate::stacks::address::PoxAddress;
21
21
use blockstack_lib:: util_lib:: signed_structured_data:: pox4:: Pox4SignatureTopic ;
22
22
use clap:: { Parser , ValueEnum } ;
23
23
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
+ } ;
25
29
use stacks_common:: types:: chainstate:: StacksPrivateKey ;
26
30
27
31
use crate :: config:: Network ;
@@ -260,12 +264,26 @@ fn parse_contract(contract: &str) -> Result<QualifiedContractIdentifier, String>
260
264
QualifiedContractIdentifier :: parse ( contract) . map_err ( |e| format ! ( "Invalid contract: {}" , e) )
261
265
}
262
266
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.
264
270
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 (
266
272
|| Err ( format ! ( "Invalid pox address: {pox_address_literal}" ) ) ,
267
- |pox_address| Ok ( pox_address) ,
268
- )
273
+ Ok ,
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
+ }
269
287
}
270
288
271
289
/// Parse the hexadecimal Stacks private key
@@ -306,13 +324,50 @@ fn parse_network(network: &str) -> Result<Network, String> {
306
324
#[ cfg( test) ]
307
325
mod tests {
308
326
use blockstack_lib:: chainstate:: stacks:: address:: { PoxAddressType20 , PoxAddressType32 } ;
327
+ use blockstack_lib:: util_lib:: signed_structured_data:: pox4:: make_pox_4_signer_key_message_hash;
328
+ use clarity:: consts:: CHAIN_ID_TESTNET ;
329
+ use clarity:: util:: hash:: Sha256Sum ;
309
330
310
331
use super :: * ;
311
332
333
+ /// Helper just to ensure that a the pox address
334
+ /// can be turned into a clarity tuple
335
+ fn make_message_hash ( pox_addr : & PoxAddress ) -> Sha256Sum {
336
+ make_pox_4_signer_key_message_hash (
337
+ pox_addr,
338
+ 0 ,
339
+ & Pox4SignatureTopic :: StackStx ,
340
+ CHAIN_ID_TESTNET ,
341
+ 0 ,
342
+ 0 ,
343
+ 0 ,
344
+ )
345
+ }
346
+
347
+ fn clarity_tuple_version ( pox_addr : & PoxAddress ) -> u8 {
348
+ pox_addr
349
+ . as_clarity_tuple ( )
350
+ . expect ( "Failed to generate clarity tuple for pox address" )
351
+ . get ( "version" )
352
+ . expect ( "Expected version in clarity tuple" )
353
+ . clone ( )
354
+ . expect_buff ( 1 )
355
+ . expect ( "Expected version to be a u128" )
356
+ . get ( 0 )
357
+ . expect ( "Expected version to be a uint" )
358
+ . clone ( )
359
+ }
360
+
312
361
#[ test]
313
362
fn test_parse_pox_addr ( ) {
314
363
let tr = "bc1p8vg588hldsnv4a558apet4e9ff3pr4awhqj2hy8gy6x2yxzjpmqsvvpta4" ;
315
364
let pox_addr = parse_pox_addr ( tr) . expect ( "Failed to parse segwit address" ) ;
365
+ assert_eq ! ( tr, pox_addr. clone( ) . to_b58( ) ) ;
366
+ make_message_hash ( & pox_addr) ;
367
+ assert_eq ! (
368
+ clarity_tuple_version( & pox_addr) ,
369
+ PoxAddressType32 :: P2TR . to_u8( )
370
+ ) ;
316
371
match pox_addr {
317
372
PoxAddress :: Addr32 ( _, addr_type, _) => {
318
373
assert_eq ! ( addr_type, PoxAddressType32 :: P2TR ) ;
@@ -322,35 +377,105 @@ mod tests {
322
377
323
378
let legacy = "1N8GMS991YDY1E696e9SB9EsYY5ckSU7hZ" ;
324
379
let pox_addr = parse_pox_addr ( legacy) . expect ( "Failed to parse legacy address" ) ;
380
+ assert_eq ! ( legacy, pox_addr. clone( ) . to_b58( ) ) ;
381
+ make_message_hash ( & pox_addr) ;
382
+ assert_eq ! (
383
+ clarity_tuple_version( & pox_addr) ,
384
+ AddressHashMode :: SerializeP2PKH as u8
385
+ ) ;
325
386
match pox_addr {
326
387
PoxAddress :: Standard ( stacks_addr, hash_mode) => {
327
388
assert_eq ! ( stacks_addr. version, 22 ) ;
328
- assert ! ( hash_mode. is_none ( ) ) ;
389
+ assert_eq ! ( hash_mode, Some ( AddressHashMode :: SerializeP2PKH ) ) ;
329
390
}
330
391
_ => panic ! ( "Invalid parsed address" ) ,
331
392
}
332
393
333
394
let p2sh = "33JNgVMNMC9Xm6mJG9oTVf5zWbmt5xi1Mv" ;
334
395
let pox_addr = parse_pox_addr ( p2sh) . expect ( "Failed to parse legacy address" ) ;
396
+ assert_eq ! ( p2sh, pox_addr. clone( ) . to_b58( ) ) ;
397
+ assert_eq ! (
398
+ clarity_tuple_version( & pox_addr) ,
399
+ AddressHashMode :: SerializeP2SH as u8
400
+ ) ;
401
+ make_message_hash ( & pox_addr) ;
335
402
match pox_addr {
336
403
PoxAddress :: Standard ( stacks_addr, hash_mode) => {
337
404
assert_eq ! ( stacks_addr. version, 20 ) ;
338
- assert ! ( hash_mode. is_none( ) ) ;
405
+ assert_eq ! ( hash_mode, Some ( AddressHashMode :: SerializeP2SH ) ) ;
406
+ }
407
+ _ => panic ! ( "Invalid parsed address" ) ,
408
+ }
409
+
410
+ let testnet_p2pkh = "mnr5asd1MLSutHLL514WZXNpUNN3L98zBc" ;
411
+ let pox_addr = parse_pox_addr ( testnet_p2pkh) . expect ( "Failed to parse testnet address" ) ;
412
+ assert_eq ! (
413
+ clarity_tuple_version( & pox_addr) ,
414
+ AddressHashMode :: SerializeP2PKH as u8
415
+ ) ;
416
+ assert_eq ! ( testnet_p2pkh, pox_addr. clone( ) . to_b58( ) ) ;
417
+ make_message_hash ( & pox_addr) ;
418
+ match pox_addr {
419
+ PoxAddress :: Standard ( stacks_addr, hash_mode) => {
420
+ assert_eq ! ( stacks_addr. version, C32_ADDRESS_VERSION_TESTNET_SINGLESIG ) ;
421
+ assert_eq ! ( hash_mode, Some ( AddressHashMode :: SerializeP2PKH ) ) ;
339
422
}
340
423
_ => panic ! ( "Invalid parsed address" ) ,
341
424
}
342
425
343
426
let wsh = "bc1qvnpcphdctvmql5gdw6chtwvvsl6ra9gwa2nehc99np7f24juc4vqrx29cs" ;
344
427
let pox_addr = parse_pox_addr ( wsh) . expect ( "Failed to parse segwit address" ) ;
428
+ assert_eq ! (
429
+ clarity_tuple_version( & pox_addr) ,
430
+ PoxAddressType32 :: P2WSH . to_u8( )
431
+ ) ;
432
+ assert_eq ! ( wsh, pox_addr. clone( ) . to_b58( ) ) ;
433
+ make_message_hash ( & pox_addr) ;
345
434
match pox_addr {
346
435
PoxAddress :: Addr32 ( _, addr_type, _) => {
347
436
assert_eq ! ( addr_type, PoxAddressType32 :: P2WSH ) ;
348
437
}
349
438
_ => panic ! ( "Invalid parsed address" ) ,
350
439
}
351
440
352
- let wpkh = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4 " ;
441
+ let wpkh = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 " ;
353
442
let pox_addr = parse_pox_addr ( wpkh) . expect ( "Failed to parse segwit address" ) ;
443
+ assert_eq ! (
444
+ clarity_tuple_version( & pox_addr) ,
445
+ PoxAddressType20 :: P2WPKH . to_u8( )
446
+ ) ;
447
+ assert_eq ! ( wpkh, pox_addr. clone( ) . to_b58( ) ) ;
448
+ make_message_hash ( & pox_addr) ;
449
+ match pox_addr {
450
+ PoxAddress :: Addr20 ( _, addr_type, _) => {
451
+ assert_eq ! ( addr_type, PoxAddressType20 :: P2WPKH ) ;
452
+ }
453
+ _ => panic ! ( "Invalid parsed address" ) ,
454
+ }
455
+
456
+ let testnet_tr = "tb1p46cgptxsfwkqpnnj552rkae3nf6l52wxn4snp4vm6mcrz2585hwq6cdwf2" ;
457
+ let pox_addr = parse_pox_addr ( testnet_tr) . expect ( "Failed to parse testnet address" ) ;
458
+ assert_eq ! ( testnet_tr, pox_addr. clone( ) . to_b58( ) ) ;
459
+ make_message_hash ( & pox_addr) ;
460
+ assert_eq ! (
461
+ clarity_tuple_version( & pox_addr) ,
462
+ PoxAddressType32 :: P2TR . to_u8( )
463
+ ) ;
464
+ match pox_addr {
465
+ PoxAddress :: Addr32 ( _, addr_type, _) => {
466
+ assert_eq ! ( addr_type, PoxAddressType32 :: P2TR ) ;
467
+ }
468
+ _ => panic ! ( "Invalid parsed address" ) ,
469
+ }
470
+
471
+ let testnet_segwit = "tb1q38eleudmqyg4jrm39dnudj23pv6jcjrksa437s" ;
472
+ let pox_addr = parse_pox_addr ( testnet_segwit) . expect ( "Failed to parse testnet address" ) ;
473
+ assert_eq ! ( testnet_segwit, pox_addr. clone( ) . to_b58( ) ) ;
474
+ make_message_hash ( & pox_addr) ;
475
+ assert_eq ! (
476
+ clarity_tuple_version( & pox_addr) ,
477
+ PoxAddressType20 :: P2WPKH . to_u8( )
478
+ ) ;
354
479
match pox_addr {
355
480
PoxAddress :: Addr20 ( _, addr_type, _) => {
356
481
assert_eq ! ( addr_type, PoxAddressType20 :: P2WPKH ) ;
0 commit comments