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