@ -1,4 +1,5 @@
# include <bitcoin/privkey.h>
# include <ccan/array_size/array_size.h>
# include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
# include <ccan/err/err.h>
# include <ccan/noerr/noerr.h>
@ -13,15 +14,19 @@
# include <errno.h>
# include <fcntl.h>
# include <inttypes.h>
# include <limits.h>
# include <sodium.h>
# include <sys/stat.h>
# include <termios.h>
# include <unistd.h>
# include <wally_bip39.h>
# define ERROR_HSM_FILE errno
# define ERROR_USAGE 2
# define ERROR_LIBSODIUM 3
# define ERROR_LIBWALLY 4
# define ERROR_KEYDERIV 5
# define ERROR_LANG_NOT_SUPPORTED 6
static void show_usage ( const char * progname )
{
@ -33,6 +38,7 @@ static void show_usage(const char *progname)
" <path/to/hsm_secret> [hsm_secret password] \n " ) ;
printf ( " - guesstoremote <P2WPKH address> <node id> <tries> "
" <path/to/hsm_secret> [hsm_secret password] \n " ) ;
printf ( " - generatehsm <path/to/new//hsm_secret> \n " ) ;
exit ( 0 ) ;
}
@ -368,6 +374,145 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
return 1 ;
}
static void get_words ( struct words * * words ) {
struct wordlist_lang {
char * abbr ;
char * name ;
} ;
struct wordlist_lang languages [ ] = {
{ " en " , " English " } ,
{ " es " , " Spanish " } ,
{ " fr " , " French " } ,
{ " it " , " Italian " } ,
{ " jp " , " Japanese " } ,
{ " zhs " , " Chinese Simplified " } ,
{ " zht " , " Chinese Traditional " } ,
} ;
printf ( " Select your language: \n " ) ;
for ( size_t i = 0 ; i < ARRAY_SIZE ( languages ) ; i + + ) {
printf ( " %zu) %s (%s) \n " , i , languages [ i ] . name , languages [ i ] . abbr ) ;
}
printf ( " Select [0-%zu]: " , ARRAY_SIZE ( languages ) ) ;
char * selected = NULL ;
size_t size = 0 ;
size_t characters = getline ( & selected , & size , stdin ) ;
if ( characters < 0 )
errx ( ERROR_USAGE , " Could not read line from stdin. " ) ;
/* To distinguish success/failure after call */
errno = 0 ;
char * endptr ;
long val = strtol ( selected , & endptr , 10 ) ;
if ( errno = = ERANGE | | ( errno ! = 0 & & val = = 0 ) | | endptr = = selected | | val < 0 | | val > = ARRAY_SIZE ( languages ) )
errx ( ERROR_USAGE , " Invalid language selection, select one from the list [0-6]. " ) ;
bip39_get_wordlist ( languages [ val ] . abbr , words ) ;
}
static void get_mnemonic ( char * mnemonic ) {
char * line = NULL ;
size_t line_size = 0 ;
printf ( " Introduce your BIP39 word list separated by space: \n " ) ;
size_t characters = getline ( & line , & line_size , stdin ) ;
if ( characters < 0 )
errx ( ERROR_USAGE , " Could not read line from stdin. " ) ;
line [ characters - 1 ] = ' \0 ' ;
strcpy ( mnemonic , line ) ;
free ( line ) ;
}
static void read_mnemonic ( char * mnemonic ) {
/* Get words for the mnemonic language */
struct words * words ;
get_words ( & words ) ;
/* Get mnemonic */
get_mnemonic ( mnemonic ) ;
if ( bip39_mnemonic_validate ( words , mnemonic ) ! = 0 ) {
errx ( ERROR_USAGE , " Invalid mnemonic: \" %s \" " , mnemonic ) ;
}
}
static void read_passphrase ( char * * passphrase ) {
struct termios current_term , temp_term ;
printf ( " Warning: remember that different passphrases yield different "
" bitcoin wallets. \n " ) ;
printf ( " If left empty, no password is used (echo is "
" disabled now). \n " ) ;
printf ( " Enter your passphrase: \n " ) ;
/* Change terminal options so we do not echo the passphrase */
if ( tcgetattr ( fileno ( stdin ) , & current_term ) ! = 0 )
errx ( ERROR_USAGE , " Could not get current terminal options. " ) ;
temp_term = current_term ;
temp_term . c_lflag & = ~ ECHO ;
if ( tcsetattr ( fileno ( stdin ) , TCSAFLUSH , & temp_term ) ! = 0 )
errx ( ERROR_USAGE , " Could not disable passphrase echoing. " ) ;
/* If we don't flush we might end up being buffered and we might seem
* to hang while we wait for the password . */
fflush ( stdout ) ;
size_t passphrase_size = 0 ;
size_t characters = getline ( passphrase , & passphrase_size , stdin ) ;
if ( characters < 0 )
errx ( ERROR_USAGE , " Could not read passphrase from stdin. " ) ;
/* Newline is not part of the valid passphrase */
if ( ( * passphrase ) [ characters - 1 ] = = ' \n ' ) {
( * passphrase ) [ characters - 1 ] = ' \0 ' ;
}
/* If the user did not introduce any password, we want to set passphrase
* to NULL not to ' \0 ' for libwally */
if ( strlen ( * passphrase ) = = 0 ) {
free ( * passphrase ) ;
* passphrase = NULL ;
}
if ( tcsetattr ( fileno ( stdin ) , TCSAFLUSH , & current_term ) ! = 0 )
errx ( ERROR_USAGE , " Could not restore terminal options. " ) ;
}
static int generate_hsm ( const char * hsm_secret_path )
{
char mnemonic [ BIP39_WORDLIST_LEN ] ;
read_mnemonic ( mnemonic ) ;
char * passphrase = NULL ;
read_passphrase ( & passphrase ) ;
u8 bip32_seed [ BIP39_SEED_LEN_512 ] ;
size_t bip32_seed_len ;
if ( bip39_mnemonic_to_seed ( mnemonic , passphrase , bip32_seed , sizeof ( bip32_seed ) , & bip32_seed_len ) ! = WALLY_OK )
errx ( ERROR_LIBWALLY , " Unable to derive BIP32 seed from BIP39 mnemonic " ) ;
int fd = open ( hsm_secret_path , O_CREAT | O_EXCL | O_WRONLY , 0400 ) ;
if ( fd < 0 ) {
errx ( ERROR_USAGE , " Unable to create hsm_secret file " ) ;
}
if ( ! write_all ( fd , bip32_seed , bip32_seed_len ) )
errx ( ERROR_USAGE , " Error writing secret to hsm_secret file " ) ;
if ( fsync ( fd ) ! = 0 )
errx ( ERROR_USAGE , " Error fsyncing hsm_secret file " ) ;
/* This should never fail if fsync succeeded. But paranoia is good, and bugs exist */
if ( close ( fd ) ! = 0 )
errx ( ERROR_USAGE , " Error closing hsm_secret file " ) ;
printf ( " New hsm_secret file created at %s \n " , hsm_secret_path ) ;
printf ( " Use the `encrypt` command to encrypt the BIP32 seed if needed \n " ) ;
free ( passphrase ) ;
return 0 ;
}
int main ( int argc , char * argv [ ] )
{
const char * method ;
@ -413,5 +558,20 @@ int main(int argc, char *argv[])
argv [ 5 ] , argc > = 7 ? argv [ 6 ] : NULL ) ;
}
if ( streq ( method , " generatehsm " ) ) {
if ( argc ! = 3 )
show_usage ( argv [ 0 ] ) ;
char * hsm_secret_path = argv [ 2 ] ;
/* if hsm_secret already exists we abort the process
* we do not want to lose someone else ' s funds */
struct stat st ;
if ( stat ( hsm_secret_path , & st ) = = 0 )
errx ( ERROR_USAGE , " hsm_secret file at %s already exists " , hsm_secret_path ) ;
return generate_hsm ( hsm_secret_path ) ;
}
show_usage ( argv [ 0 ] ) ;
}