@@ -8,6 +8,7 @@ use std::{
88 convert:: TryFrom ,
99 fmt, fs,
1010 io:: { self , Read , Write } ,
11+ iter:: FromIterator ,
1112 net:: SocketAddr ,
1213 path:: { Path , PathBuf } ,
1314 str:: FromStr ,
@@ -384,6 +385,93 @@ impl fmt::Display for DatadirError {
384385
385386impl std:: error:: Error for DatadirError { }
386387
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+
387475impl RevaultD {
388476 /// Creates our global state by consuming the static configuration
389477 pub fn from_config ( config : Config ) -> Result < RevaultD , StartupError > {
@@ -517,6 +605,13 @@ impl RevaultD {
517605 NoisePubKey ( curve25519:: scalarmult_base ( & scalar) . 0 )
518606 }
519607
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+
520615 pub fn vault_address ( & self , child_number : ChildNumber ) -> Address {
521616 self . deposit_descriptor
522617 . derive ( child_number, & self . secp_ctx )
@@ -525,6 +620,13 @@ impl RevaultD {
525620 . expect ( "deposit_descriptor is a wsh" )
526621 }
527622
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+
528630 pub fn unvault_address ( & self , child_number : ChildNumber ) -> Address {
529631 self . unvault_descriptor
530632 . derive ( child_number, & self . secp_ctx )
@@ -601,38 +703,49 @@ impl RevaultD {
601703 self . vault_address ( self . current_unused_index )
602704 }
603705
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+
604712 pub fn last_deposit_address ( & self ) -> Address {
605713 let raw_index: u32 = self . current_unused_index . into ( ) ;
606714 // FIXME: this should fail instead of creating a hardened index
607715 self . vault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
608716 }
609717
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+
610724 pub fn last_unvault_address ( & self ) -> Address {
611725 let raw_index: u32 = self . current_unused_index . into ( ) ;
612726 // FIXME: this should fail instead of creating a hardened index
613727 self . unvault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
614728 }
615729
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 > {
618732 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" )
624737 } )
625738 . collect ( )
626739 }
627740
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 > {
630743 let raw_index: u32 = self . current_unused_index . into ( ) ;
631744 ( 0 ..raw_index + self . gap_limit ( ) )
632745 . map ( |raw_index| {
633746 // 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" )
636749 } )
637750 . collect ( )
638751 }
0 commit comments