2017-06-07 12:32:45 -07:00
#!/usr/bin/env python3
2022-12-24 23:49:50 +00:00
# Copyright (c) 2016-2022 The Bitcoin Core developers
2017-06-07 12:32:45 -07:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" Test Wallet encryption """
import time
2023-10-24 18:57:16 -04:00
import subprocess
2017-06-07 12:32:45 -07:00
2023-10-24 18:57:16 -04:00
from test_framework . messages import hash256
2017-08-16 08:52:24 -07:00
from test_framework . test_framework import BitcoinTestFramework
2017-06-07 12:32:45 -07:00
from test_framework . util import (
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ,
2022-08-19 13:51:39 -04:00
assert_equal ,
2017-06-07 12:32:45 -07:00
)
2023-10-09 15:30:40 +03:00
from test_framework . wallet_util import WalletUnlock
2017-06-07 12:32:45 -07:00
2020-07-01 20:02:05 -04:00
2017-06-07 12:32:45 -07:00
class WalletEncryptionTest ( BitcoinTestFramework ) :
2022-11-09 12:53:13 +01:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2017-06-09 18:21:21 -04:00
def set_test_params ( self ) :
2017-06-07 12:32:45 -07:00
self . setup_clean_chain = True
self . num_nodes = 1
2018-09-09 13:32:37 -04:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2017-06-07 12:32:45 -07:00
def run_test ( self ) :
passphrase = " WalletPassphrase "
passphrase2 = " SecondWalletPassphrase "
# Make sure the wallet isn't encrypted first
2019-07-22 14:47:17 -04:00
msg = " test message "
address = self . nodes [ 0 ] . getnewaddress ( address_type = ' legacy ' )
sig = self . nodes [ 0 ] . signmessage ( address , msg )
assert self . nodes [ 0 ] . verifymessage ( address , sig , msg )
2018-11-26 16:19:06 -05:00
assert_raises_rpc_error ( - 15 , " Error: running with an unencrypted wallet, but walletpassphrase was called " , self . nodes [ 0 ] . walletpassphrase , ' ff ' , 1 )
assert_raises_rpc_error ( - 15 , " Error: running with an unencrypted wallet, but walletpassphrasechange was called. " , self . nodes [ 0 ] . walletpassphrasechange , ' ff ' , ' ff ' )
2017-06-07 12:32:45 -07:00
# Encrypt the wallet
2022-02-17 12:54:39 +01:00
assert_raises_rpc_error ( - 8 , " passphrase cannot be empty " , self . nodes [ 0 ] . encryptwallet , ' ' )
2018-02-20 16:09:51 -05:00
self . nodes [ 0 ] . encryptwallet ( passphrase )
2017-06-07 12:32:45 -07:00
# Test that the wallet is encrypted
2019-07-22 14:47:17 -04:00
assert_raises_rpc_error ( - 13 , " Please enter the wallet passphrase with walletpassphrase first " , self . nodes [ 0 ] . signmessage , address , msg )
2018-11-26 16:19:06 -05:00
assert_raises_rpc_error ( - 15 , " Error: running with an encrypted wallet, but encryptwallet was called. " , self . nodes [ 0 ] . encryptwallet , ' ff ' )
2022-02-17 12:54:39 +01:00
assert_raises_rpc_error ( - 8 , " passphrase cannot be empty " , self . nodes [ 0 ] . walletpassphrase , ' ' , 1 )
assert_raises_rpc_error ( - 8 , " passphrase cannot be empty " , self . nodes [ 0 ] . walletpassphrasechange , ' ' , ' ff ' )
2017-06-07 12:32:45 -07:00
# Check that walletpassphrase works
self . nodes [ 0 ] . walletpassphrase ( passphrase , 2 )
2019-07-22 14:47:17 -04:00
sig = self . nodes [ 0 ] . signmessage ( address , msg )
assert self . nodes [ 0 ] . verifymessage ( address , sig , msg )
2017-06-07 12:32:45 -07:00
# Check that the timeout is right
2019-07-18 22:12:51 +02:00
time . sleep ( 3 )
2019-07-22 14:47:17 -04:00
assert_raises_rpc_error ( - 13 , " Please enter the wallet passphrase with walletpassphrase first " , self . nodes [ 0 ] . signmessage , address , msg )
2017-06-07 12:32:45 -07:00
# Test wrong passphrase
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 14 , " wallet passphrase entered was incorrect " , self . nodes [ 0 ] . walletpassphrase , passphrase + " wrong " , 10 )
2017-06-07 12:32:45 -07:00
# Test walletlock
2023-10-09 15:30:40 +03:00
with WalletUnlock ( self . nodes [ 0 ] , passphrase ) :
sig = self . nodes [ 0 ] . signmessage ( address , msg )
assert self . nodes [ 0 ] . verifymessage ( address , sig , msg )
2019-07-22 14:47:17 -04:00
assert_raises_rpc_error ( - 13 , " Please enter the wallet passphrase with walletpassphrase first " , self . nodes [ 0 ] . signmessage , address , msg )
2017-06-07 12:32:45 -07:00
# Test passphrase changes
self . nodes [ 0 ] . walletpassphrasechange ( passphrase , passphrase2 )
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 14 , " wallet passphrase entered was incorrect " , self . nodes [ 0 ] . walletpassphrase , passphrase , 10 )
2023-10-09 15:30:40 +03:00
with WalletUnlock ( self . nodes [ 0 ] , passphrase2 ) :
sig = self . nodes [ 0 ] . signmessage ( address , msg )
assert self . nodes [ 0 ] . verifymessage ( address , sig , msg )
2018-01-06 02:08:57 -05:00
# Test timeout bounds
assert_raises_rpc_error ( - 8 , " Timeout cannot be negative. " , self . nodes [ 0 ] . walletpassphrase , passphrase2 , - 10 )
2020-07-01 20:02:05 -04:00
self . log . info ( ' Check a timeout less than the limit ' )
2018-04-06 11:54:52 -04:00
MAX_VALUE = 100000000
2022-08-19 13:51:39 -04:00
now = int ( time . time ( ) )
self . nodes [ 0 ] . setmocktime ( now )
expected_time = now + MAX_VALUE - 600
2018-04-06 11:54:52 -04:00
self . nodes [ 0 ] . walletpassphrase ( passphrase2 , MAX_VALUE - 600 )
2018-01-06 02:08:57 -05:00
actual_time = self . nodes [ 0 ] . getwalletinfo ( ) [ ' unlocked_until ' ]
2022-08-19 13:51:39 -04:00
assert_equal ( actual_time , expected_time )
2020-07-01 20:02:05 -04:00
self . log . info ( ' Check a timeout greater than the limit ' )
2022-08-19 13:51:39 -04:00
expected_time = now + MAX_VALUE
2018-04-06 11:54:52 -04:00
self . nodes [ 0 ] . walletpassphrase ( passphrase2 , MAX_VALUE + 1000 )
2018-01-06 02:08:57 -05:00
actual_time = self . nodes [ 0 ] . getwalletinfo ( ) [ ' unlocked_until ' ]
2022-08-19 13:51:39 -04:00
assert_equal ( actual_time , expected_time )
2023-02-09 12:11:02 -05:00
self . nodes [ 0 ] . walletlock ( )
# Test passphrase with null characters
passphrase_with_nulls = " Phrase \0 With \0 Nulls "
self . nodes [ 0 ] . walletpassphrasechange ( passphrase2 , passphrase_with_nulls )
# walletpassphrasechange should not stop at null characters
assert_raises_rpc_error ( - 14 , " wallet passphrase entered was incorrect " , self . nodes [ 0 ] . walletpassphrase , passphrase_with_nulls . partition ( " \0 " ) [ 0 ] , 10 )
2023-10-09 15:30:40 +03:00
with WalletUnlock ( self . nodes [ 0 ] , passphrase_with_nulls ) :
sig = self . nodes [ 0 ] . signmessage ( address , msg )
assert self . nodes [ 0 ] . verifymessage ( address , sig , msg )
2020-07-01 20:02:05 -04:00
2023-10-24 18:57:16 -04:00
self . log . info ( " Test that wallets without private keys cannot be encrypted " )
self . nodes [ 0 ] . createwallet ( wallet_name = " noprivs " , disable_private_keys = True )
noprivs_wallet = self . nodes [ 0 ] . get_wallet_rpc ( " noprivs " )
assert_raises_rpc_error ( - 16 , " Error: wallet does not contain private keys, nothing to encrypt. " , noprivs_wallet . encryptwallet , " pass " )
if self . is_wallet_tool_compiled ( ) :
self . log . info ( " Test that encryption keys in wallets without privkeys are removed " )
def do_wallet_tool ( * args ) :
proc = subprocess . Popen (
[ self . options . bitcoinwallet , f " -datadir= { self . nodes [ 0 ] . datadir_path } " , f " -chain= { self . chain } " ] + list ( args ) ,
stdin = subprocess . PIPE ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ,
text = True
)
stdout , stderr = proc . communicate ( )
assert_equal ( proc . poll ( ) , 0 )
assert_equal ( stderr , " " )
# Since it is no longer possible to encrypt a wallet without privkeys, we need to force one into the wallet
# 1. Make a dump of the wallet
# 2. Add mkey record to the dump
# 3. Create a new wallet from the dump
# Make the dump
noprivs_wallet . unloadwallet ( )
dumpfile_path = self . nodes [ 0 ] . datadir_path / " noprivs.dump "
do_wallet_tool ( " -wallet=noprivs " , f " -dumpfile= { dumpfile_path } " , " dump " )
# Modify the dump
with open ( dumpfile_path , " r " , encoding = " utf-8 " ) as f :
dump_content = f . readlines ( )
# Drop the checksum line
dump_content = dump_content [ : - 1 ]
# Insert a valid mkey line. This corresponds to a passphrase of "pass".
dump_content . append ( " 046d6b657901000000,300dc926f3b3887aad3d5d5f5a0fc1b1a4a1722f9284bd5c6ff93b64a83902765953939c58fe144013c8b819f42cf698b208e9911e5f0c544fa300000000cc52050000 \n " )
with open ( dumpfile_path , " w " , encoding = " utf-8 " ) as f :
contents = " " . join ( dump_content )
f . write ( contents )
checksum = hash256 ( contents . encode ( ) )
f . write ( f " checksum, { checksum . hex ( ) } \n " )
# Load the dump into a new wallet
do_wallet_tool ( " -wallet=noprivs_enc " , f " -dumpfile= { dumpfile_path } " , " createfromdump " )
# Load the wallet and make sure it is no longer encrypted
with self . nodes [ 0 ] . assert_debug_log ( [ " Detected extraneous encryption keys in this wallet without private keys. Removing extraneous encryption keys. " ] ) :
self . nodes [ 0 ] . loadwallet ( " noprivs_enc " )
noprivs_wallet = self . nodes [ 0 ] . get_wallet_rpc ( " noprivs_enc " )
assert_raises_rpc_error ( - 15 , " Error: running with an unencrypted wallet, but walletpassphrase was called. " , noprivs_wallet . walletpassphrase , " pass " , 1 )
noprivs_wallet . unloadwallet ( )
# Make a new dump and check that there are no mkeys
dumpfile_path = self . nodes [ 0 ] . datadir_path / " noprivs_enc.dump "
do_wallet_tool ( " -wallet=noprivs_enc " , f " -dumpfile= { dumpfile_path } " , " dump " )
with open ( dumpfile_path , " r " , encoding = " utf-8 " ) as f :
2025-01-16 17:29:12 +02:00
# Check there's nothing with an 'mkey' prefix
2023-10-24 18:57:16 -04:00
assert_equal ( all ( [ not line . startswith ( " 046d6b6579 " ) for line in f ] ) , True )
2017-06-07 12:32:45 -07:00
if __name__ == ' __main__ ' :
2024-07-16 22:05:14 +01:00
WalletEncryptionTest ( __file__ ) . main ( )