@@ -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
273
|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
+ }
269
287
}
270
288
271
289
/// Parse the hexadecimal Stacks private key
@@ -305,14 +323,52 @@ fn parse_network(network: &str) -> Result<Network, String> {
305
323
306
324
#[ cfg( test) ]
307
325
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 } ;
309
331
310
332
use super :: * ;
311
333
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
+
312
362
#[ test]
313
363
fn test_parse_pox_addr ( ) {
314
364
let tr = "bc1p8vg588hldsnv4a558apet4e9ff3pr4awhqj2hy8gy6x2yxzjpmqsvvpta4" ;
315
365
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
+ ) ;
316
372
match pox_addr {
317
373
PoxAddress :: Addr32 ( _, addr_type, _) => {
318
374
assert_eq ! ( addr_type, PoxAddressType32 :: P2TR ) ;
@@ -322,35 +378,105 @@ mod tests {
322
378
323
379
let legacy = "1N8GMS991YDY1E696e9SB9EsYY5ckSU7hZ" ;
324
380
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
+ ) ;
325
387
match pox_addr {
326
388
PoxAddress :: Standard ( stacks_addr, hash_mode) => {
327
389
assert_eq ! ( stacks_addr. version, 22 ) ;
328
- assert ! ( hash_mode. is_none ( ) ) ;
390
+ assert_eq ! ( hash_mode, Some ( AddressHashMode :: SerializeP2PKH ) ) ;
329
391
}
330
392
_ => panic ! ( "Invalid parsed address" ) ,
331
393
}
332
394
333
395
let p2sh = "33JNgVMNMC9Xm6mJG9oTVf5zWbmt5xi1Mv" ;
334
396
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) ;
335
403
match pox_addr {
336
404
PoxAddress :: Standard ( stacks_addr, hash_mode) => {
337
405
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 ) ) ;
339
423
}
340
424
_ => panic ! ( "Invalid parsed address" ) ,
341
425
}
342
426
343
427
let wsh = "bc1qvnpcphdctvmql5gdw6chtwvvsl6ra9gwa2nehc99np7f24juc4vqrx29cs" ;
344
428
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) ;
345
435
match pox_addr {
346
436
PoxAddress :: Addr32 ( _, addr_type, _) => {
347
437
assert_eq ! ( addr_type, PoxAddressType32 :: P2WSH ) ;
348
438
}
349
439
_ => panic ! ( "Invalid parsed address" ) ,
350
440
}
351
441
352
- let wpkh = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4 " ;
442
+ let wpkh = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 " ;
353
443
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
+ ) ;
354
480
match pox_addr {
355
481
PoxAddress :: Addr20 ( _, addr_type, _) => {
356
482
assert_eq ! ( addr_type, PoxAddressType20 :: P2WPKH ) ;
0 commit comments