@@ -6,7 +6,7 @@ use crate::{
66use std:: {
77 collections:: HashMap ,
88 convert:: TryFrom ,
9- fmt, fs,
9+ fmt, fs, iter :: FromIterator ,
1010 io:: { self , Read , Write } ,
1111 net:: SocketAddr ,
1212 path:: { Path , PathBuf } ,
@@ -384,6 +384,91 @@ impl fmt::Display for DatadirError {
384384
385385impl std:: error:: Error for DatadirError { }
386386
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+
387472impl RevaultD {
388473 /// Creates our global state by consuming the static configuration
389474 pub fn from_config ( config : Config ) -> Result < RevaultD , StartupError > {
@@ -517,6 +602,13 @@ impl RevaultD {
517602 NoisePubKey ( curve25519:: scalarmult_base ( & scalar) . 0 )
518603 }
519604
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+
520612 pub fn vault_address ( & self , child_number : ChildNumber ) -> Address {
521613 self . deposit_descriptor
522614 . derive ( child_number, & self . secp_ctx )
@@ -525,6 +617,13 @@ impl RevaultD {
525617 . expect ( "deposit_descriptor is a wsh" )
526618 }
527619
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+
528627 pub fn unvault_address ( & self , child_number : ChildNumber ) -> Address {
529628 self . unvault_descriptor
530629 . derive ( child_number, & self . secp_ctx )
@@ -601,38 +700,47 @@ impl RevaultD {
601700 self . vault_address ( self . current_unused_index )
602701 }
603702
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+
604709 pub fn last_deposit_address ( & self ) -> Address {
605710 let raw_index: u32 = self . current_unused_index . into ( ) ;
606711 // FIXME: this should fail instead of creating a hardened index
607712 self . vault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
608713 }
609714
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+
610721 pub fn last_unvault_address ( & self ) -> Address {
611722 let raw_index: u32 = self . current_unused_index . into ( ) ;
612723 // FIXME: this should fail instead of creating a hardened index
613724 self . unvault_address ( ChildNumber :: from ( raw_index + self . gap_limit ( ) ) )
614725 }
615726
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 > {
618729 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" )
624733 } )
625734 . collect ( )
626735 }
627736
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 > {
630739 let raw_index: u32 = self . current_unused_index . into ( ) ;
631740 ( 0 ..raw_index + self . gap_limit ( ) )
632741 . map ( |raw_index| {
633742 // 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" )
636744 } )
637745 . collect ( )
638746 }
0 commit comments