@@ -6,7 +6,7 @@ use crate::{
6
6
use std:: {
7
7
collections:: HashMap ,
8
8
convert:: TryFrom ,
9
- fmt, fs,
9
+ fmt, fs, iter :: FromIterator ,
10
10
io:: { self , Read , Write } ,
11
11
net:: SocketAddr ,
12
12
path:: { Path , PathBuf } ,
@@ -384,6 +384,91 @@ impl fmt::Display for DatadirError {
384
384
385
385
impl std:: error:: Error for DatadirError { }
386
386
387
+ #[ derive( Debug ) ]
388
+ pub enum ChecksumError {
389
+ Checksum ( String ) ,
390
+ }
391
+
392
+ impl fmt:: Display for ChecksumError {
393
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
394
+ match self {
395
+ Self :: Checksum ( e) => {
396
+ write ! ( f, "Error computing checksum: {}" , e)
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ impl std:: error:: Error for ChecksumError { }
403
+
404
+ const INPUT_CHARSET : & str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\" \\ " ;
405
+ const CHECKSUM_CHARSET : & str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" ;
406
+
407
+ fn poly_mod ( mut c : u64 , val : u64 ) -> u64 {
408
+ let c0 = c >> 35 ;
409
+
410
+ c = ( ( c & 0x7ffffffff ) << 5 ) ^ val;
411
+ if c0 & 1 > 0 {
412
+ c ^= 0xf5dee51989
413
+ } ;
414
+ if c0 & 2 > 0 {
415
+ c ^= 0xa9fdca3312
416
+ } ;
417
+ if c0 & 4 > 0 {
418
+ c ^= 0x1bab10e32d
419
+ } ;
420
+ if c0 & 8 > 0 {
421
+ c ^= 0x3706b1677a
422
+ } ;
423
+ if c0 & 16 > 0 {
424
+ c ^= 0x644d626ffd
425
+ } ;
426
+
427
+ c
428
+ }
429
+
430
+ /// Compute the checksum of a descriptor
431
+ /// Note that this function does not check if the
432
+ /// descriptor string is syntactically correct or not.
433
+ /// This only computes the checksum
434
+ pub fn desc_checksum ( desc : & str ) -> Result < String , ChecksumError > {
435
+ let mut c = 1 ;
436
+ let mut cls = 0 ;
437
+ let mut clscount = 0 ;
438
+
439
+ for ch in desc. chars ( ) {
440
+ let pos = INPUT_CHARSET . find ( ch) . ok_or ( ChecksumError :: Checksum ( format ! (
441
+ "Invalid character in checksum: '{}'" ,
442
+ ch
443
+ ) ) ) ? as u64 ;
444
+ c = poly_mod ( c, pos & 31 ) ;
445
+ cls = cls * 3 + ( pos >> 5 ) ;
446
+ clscount += 1 ;
447
+ if clscount == 3 {
448
+ c = poly_mod ( c, cls) ;
449
+ cls = 0 ;
450
+ clscount = 0 ;
451
+ }
452
+ }
453
+ if clscount > 0 {
454
+ c = poly_mod ( c, cls) ;
455
+ }
456
+ ( 0 ..8 ) . for_each ( |_| c = poly_mod ( c, 0 ) ) ;
457
+ c ^= 1 ;
458
+
459
+ let mut chars = Vec :: with_capacity ( 8 ) ;
460
+ for j in 0 ..8 {
461
+ chars. push (
462
+ CHECKSUM_CHARSET
463
+ . chars ( )
464
+ . nth ( ( ( c >> ( 5 * ( 7 - j) ) ) & 31 ) as usize )
465
+ . unwrap ( ) ,
466
+ ) ;
467
+ }
468
+
469
+ Ok ( String :: from_iter ( chars) )
470
+ }
471
+
387
472
impl RevaultD {
388
473
/// Creates our global state by consuming the static configuration
389
474
pub fn from_config ( config : Config ) -> Result < RevaultD , StartupError > {
@@ -517,6 +602,13 @@ impl RevaultD {
517
602
NoisePubKey ( curve25519:: scalarmult_base ( & scalar) . 0 )
518
603
}
519
604
605
+ /// vault (deposit) address descriptor with checksum in canonical form (e.g.
606
+ /// 'addr(ADDRESS)#CHECKSUM') for importing with bitcoind
607
+ pub fn vault_desc ( & self , child_number : ChildNumber ) -> Result < String , ChecksumError > {
608
+ let addr_desc = format ! ( "addr({})" , self . vault_address( child_number) ) ;
609
+ Ok ( format ! ( "{}#{}" , addr_desc, desc_checksum( & addr_desc) ?) )
610
+ }
611
+
520
612
pub fn vault_address ( & self , child_number : ChildNumber ) -> Address {
521
613
self . deposit_descriptor
522
614
. derive ( child_number, & self . secp_ctx )
@@ -525,6 +617,13 @@ impl RevaultD {
525
617
. expect ( "deposit_descriptor is a wsh" )
526
618
}
527
619
620
+ /// unvault address descriptor with checksum in canonical form (e.g.
621
+ /// 'addr(ADDRESS)#CHECKSUM') for importing with bitcoind
622
+ pub fn unvault_desc ( & self , child_number : ChildNumber ) -> Result < String , ChecksumError > {
623
+ let addr_desc = format ! ( "addr({})" , self . unvault_address( child_number) ) ;
624
+ Ok ( format ! ( "{}#{}" , addr_desc, desc_checksum( & addr_desc) ?) )
625
+ }
626
+
528
627
pub fn unvault_address ( & self , child_number : ChildNumber ) -> Address {
529
628
self . unvault_descriptor
530
629
. derive ( child_number, & self . secp_ctx )
@@ -601,38 +700,47 @@ impl RevaultD {
601
700
self . vault_address ( self . current_unused_index )
602
701
}
603
702
703
+ pub fn last_deposit_desc ( & self ) -> Result < String , ChecksumError > {
704
+ let raw_index: u32 = self . current_unused_index . into ( ) ;
705
+ // FIXME: this should fail instead of creating a hardened index
706
+ self . vault_desc ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
707
+ }
708
+
604
709
pub fn last_deposit_address ( & self ) -> Address {
605
710
let raw_index: u32 = self . current_unused_index . into ( ) ;
606
711
// FIXME: this should fail instead of creating a hardened index
607
712
self . vault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
608
713
}
609
714
715
+ pub fn last_unvault_desc ( & self ) -> Result < String , ChecksumError > {
716
+ let raw_index: u32 = self . current_unused_index . into ( ) ;
717
+ // FIXME: this should fail instead of creating a hardened index
718
+ self . unvault_desc ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
719
+ }
720
+
610
721
pub fn last_unvault_address ( & self ) -> Address {
611
722
let raw_index: u32 = self . current_unused_index . into ( ) ;
612
723
// FIXME: this should fail instead of creating a hardened index
613
724
self . unvault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
614
725
}
615
726
616
- /// All deposit addresses as strings up to the gap limit (100)
617
- pub fn all_deposit_addresses ( & mut self ) -> Vec < String > {
727
+ /// All deposit address descriptors as strings up to the gap limit (100)
728
+ pub fn all_deposit_descriptors ( & mut self ) -> Vec < String > {
618
729
self . derivation_index_map
619
- . keys ( )
620
- . map ( |s| {
621
- Address :: from_script ( s, self . bitcoind_config . network )
622
- . expect ( "Created from P2WSH address" )
623
- . to_string ( )
730
+ . values ( )
731
+ . map ( |child_num| {
732
+ self . vault_desc ( ChildNumber :: from ( * child_num) ) . expect ( "Failed checksum computation" )
624
733
} )
625
734
. collect ( )
626
735
}
627
736
628
- /// All unvault addresses as strings up to the gap limit (100)
629
- pub fn all_unvault_addresses ( & mut self ) -> Vec < String > {
737
+ /// All unvault address descriptors as strings up to the gap limit (100)
738
+ pub fn all_unvault_descriptors ( & mut self ) -> Vec < String > {
630
739
let raw_index: u32 = self . current_unused_index . into ( ) ;
631
740
( 0 ..raw_index + self . gap_limit ( ) )
632
741
. map ( |raw_index| {
633
742
// FIXME: this should fail instead of creating a hardened index
634
- self . unvault_address ( ChildNumber :: from ( raw_index) )
635
- . to_string ( )
743
+ self . unvault_desc ( ChildNumber :: from ( raw_index) ) . expect ( "Failed to comput checksum" )
636
744
} )
637
745
. collect ( )
638
746
}
0 commit comments