2018-04-25 05:44:58 -03:00
#!/usr/bin/env python3
2022-12-24 20:49:50 -03:00
# Copyright (c) 2015-2022 The Bitcoin Core developers
2018-04-25 05:44:58 -03:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2019-04-16 13:06:37 -04:00
""" Test multisig RPCs """
2020-06-10 11:56:44 -04:00
import decimal
import itertools
import json
import os
2018-04-25 05:44:58 -03:00
2023-03-28 12:58:16 -03:00
from test_framework . address import address_to_scriptpubkey
2021-05-17 10:38:19 -04:00
from test_framework . blocktools import COINBASE_MATURITY
2019-07-16 15:33:35 -04:00
from test_framework . authproxy import JSONRPCException
2019-03-25 18:00:00 -03:00
from test_framework . descriptors import descsum_create , drop_origins
2023-05-23 17:38:31 -04:00
from test_framework . key import ECPubKey
2018-04-25 05:44:58 -03:00
from test_framework . test_framework import BitcoinTestFramework
2019-04-16 13:12:54 -04:00
from test_framework . util import (
assert_raises_rpc_error ,
2019-05-15 00:21:11 -04:00
assert_equal ,
2019-04-16 13:12:54 -04:00
)
2023-05-23 17:38:31 -04:00
from test_framework . wallet_util import generate_keypair
2022-03-22 05:47:51 -03:00
from test_framework . wallet import (
MiniWallet ,
getnewdestination ,
)
2019-04-16 13:06:37 -04:00
2018-04-25 05:44:58 -03:00
class RpcCreateMultiSigTest ( BitcoinTestFramework ) :
2022-11-09 08:53:13 -03:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2018-04-25 05:44:58 -03:00
def set_test_params ( self ) :
self . setup_clean_chain = True
self . num_nodes = 3
2019-12-06 11:37:49 -03:00
self . supports_cli = False
2018-09-09 14:32:37 -03:00
2018-04-25 05:44:58 -03:00
def get_keys ( self ) :
2019-07-16 15:33:35 -04:00
self . pub = [ ]
self . priv = [ ]
2018-09-09 14:32:37 -03:00
node0 , node1 , node2 = self . nodes
2019-07-16 15:33:35 -04:00
for _ in range ( self . nkeys ) :
2023-05-23 17:38:31 -04:00
privkey , pubkey = generate_keypair ( wif = True )
self . pub . append ( pubkey . hex ( ) )
self . priv . append ( privkey )
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
self . final = node2 . getnewaddress ( )
else :
2022-06-06 19:47:54 -04:00
self . final = getnewdestination ( ' bech32 ' ) [ 2 ]
2018-04-25 05:44:58 -03:00
def run_test ( self ) :
2019-04-16 13:06:37 -04:00
node0 , node1 , node2 = self . nodes
2022-03-22 05:47:51 -03:00
self . wallet = MiniWallet ( test_node = node0 )
2018-04-25 05:44:58 -03:00
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
2022-11-10 06:12:39 -03:00
self . import_deterministic_coinbase_privkeys ( )
2022-03-22 05:47:51 -03:00
self . check_addmultisigaddress_errors ( )
2019-04-16 13:12:54 -04:00
self . log . info ( ' Generating blocks ... ' )
2022-03-22 05:47:51 -03:00
self . generate ( self . wallet , 149 )
2018-04-25 05:44:58 -03:00
self . moved = 0
2019-04-16 13:06:37 -04:00
for self . nkeys in [ 3 , 5 ] :
for self . nsigs in [ 2 , 3 ] :
2018-04-25 05:44:58 -03:00
for self . output_type in [ " bech32 " , " p2sh-segwit " , " legacy " ] :
self . get_keys ( )
self . do_multisig ( )
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
self . checkbalances ( )
2018-04-25 05:44:58 -03:00
2019-05-15 00:21:11 -04:00
# Test mixed compressed and uncompressed pubkeys
self . log . info ( ' Mixed compressed and uncompressed multisigs are not allowed ' )
2022-06-06 19:47:54 -04:00
pk0 , pk1 , pk2 = [ getnewdestination ( ' bech32 ' ) [ 0 ] . hex ( ) for _ in range ( 3 ) ]
2019-05-15 00:21:11 -04:00
# decompress pk2
pk_obj = ECPubKey ( )
2021-08-04 14:59:06 -04:00
pk_obj . set ( bytes . fromhex ( pk2 ) )
2019-05-15 00:21:11 -04:00
pk_obj . compressed = False
2021-08-04 14:59:06 -04:00
pk2 = pk_obj . get_bytes ( ) . hex ( )
2019-05-15 00:21:11 -04:00
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
node0 . createwallet ( wallet_name = ' wmulti0 ' , disable_private_keys = True )
wmulti0 = node0 . get_wallet_rpc ( ' wmulti0 ' )
2019-07-16 15:33:35 -04:00
2019-05-15 00:21:11 -04:00
# Check all permutations of keys because order matters apparently
for keys in itertools . permutations ( [ pk0 , pk1 , pk2 ] ) :
# Results should be the same as this legacy one
legacy_addr = node0 . createmultisig ( 2 , keys , ' legacy ' ) [ ' address ' ]
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
result = wmulti0 . addmultisigaddress ( 2 , keys , ' ' , ' legacy ' )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert ' warnings ' not in result
2019-05-15 00:21:11 -04:00
# Generate addresses with the segwit types. These should all make legacy addresses
2022-05-25 14:17:12 -04:00
err_msg = [ " Unable to make chosen address type, please ensure no uncompressed public keys are present. " ]
2021-09-27 23:08:13 -03:00
for addr_type in [ ' bech32 ' , ' p2sh-segwit ' ] :
2022-05-25 14:17:12 -04:00
result = self . nodes [ 0 ] . createmultisig ( nrequired = 2 , keys = keys , address_type = addr_type )
2021-09-27 23:08:13 -03:00
assert_equal ( legacy_addr , result [ ' address ' ] )
2022-05-25 14:17:12 -04:00
assert_equal ( result [ ' warnings ' ] , err_msg )
2021-09-27 23:08:13 -03:00
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
2022-05-25 14:17:12 -04:00
result = wmulti0 . addmultisigaddress ( nrequired = 2 , keys = keys , address_type = addr_type )
2022-03-22 05:47:51 -03:00
assert_equal ( legacy_addr , result [ ' address ' ] )
2022-05-25 14:17:12 -04:00
assert_equal ( result [ ' warnings ' ] , err_msg )
2019-05-15 00:21:11 -04:00
2019-10-04 16:32:00 -03:00
self . log . info ( ' Testing sortedmulti descriptors with BIP 67 test vectors ' )
with open ( os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , ' data/rpc_bip67.json ' ) , encoding = ' utf-8 ' ) as f :
vectors = json . load ( f )
for t in vectors :
key_str = ' , ' . join ( t [ ' keys ' ] )
desc = descsum_create ( ' sh(sortedmulti(2, {} )) ' . format ( key_str ) )
assert_equal ( self . nodes [ 0 ] . deriveaddresses ( desc ) [ 0 ] , t [ ' address ' ] )
sorted_key_str = ' , ' . join ( t [ ' sorted_keys ' ] )
sorted_key_desc = descsum_create ( ' sh(multi(2, {} )) ' . format ( sorted_key_str ) )
assert_equal ( self . nodes [ 0 ] . deriveaddresses ( sorted_key_desc ) [ 0 ] , t [ ' address ' ] )
2021-06-04 17:35:47 -04:00
# Check that bech32m is currently not allowed
assert_raises_rpc_error ( - 5 , " createmultisig cannot create bech32m multisig addresses " , self . nodes [ 0 ] . createmultisig , 2 , self . pub , " bech32m " )
2019-04-16 13:12:54 -04:00
def check_addmultisigaddress_errors ( self ) :
2019-07-16 15:33:35 -04:00
if self . options . descriptors :
return
2019-04-16 13:12:54 -04:00
self . log . info ( ' Check that addmultisigaddress fails when the private keys are missing ' )
addresses = [ self . nodes [ 1 ] . getnewaddress ( address_type = ' legacy ' ) for _ in range ( 2 ) ]
assert_raises_rpc_error ( - 5 , ' no full public key for address ' , lambda : self . nodes [ 0 ] . addmultisigaddress ( nrequired = 1 , keys = addresses ) )
for a in addresses :
# Importing all addresses should not change the result
self . nodes [ 0 ] . importaddress ( a )
assert_raises_rpc_error ( - 5 , ' no full public key for address ' , lambda : self . nodes [ 0 ] . addmultisigaddress ( nrequired = 1 , keys = addresses ) )
2021-06-04 17:35:47 -04:00
# Bech32m address type is disallowed for legacy wallets
pubs = [ self . nodes [ 1 ] . getaddressinfo ( addr ) [ " pubkey " ] for addr in addresses ]
assert_raises_rpc_error ( - 5 , " Bech32m multisig addresses cannot be created with legacy wallets " , self . nodes [ 0 ] . addmultisigaddress , 2 , pubs , " " , " bech32m " )
2018-04-25 05:44:58 -03:00
def checkbalances ( self ) :
2019-04-16 13:06:37 -04:00
node0 , node1 , node2 = self . nodes
2021-08-19 11:10:24 -04:00
self . generate ( node0 , COINBASE_MATURITY )
2018-04-25 05:44:58 -03:00
bal0 = node0 . getbalance ( )
bal1 = node1 . getbalance ( )
bal2 = node2 . getbalance ( )
2022-03-22 05:47:51 -03:00
balw = self . wallet . get_balance ( )
2018-04-25 05:44:58 -03:00
height = node0 . getblockchaininfo ( ) [ " blocks " ]
assert 150 < height < 350
2019-04-16 13:06:37 -04:00
total = 149 * 50 + ( height - 149 - 100 ) * 25
2018-04-25 05:44:58 -03:00
assert bal1 == 0
assert bal2 == self . moved
2022-03-22 05:47:51 -03:00
assert_equal ( bal0 + bal1 + bal2 + balw , total )
2018-04-25 05:44:58 -03:00
def do_multisig ( self ) :
2019-04-16 13:06:37 -04:00
node0 , node1 , node2 = self . nodes
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
if ' wmulti ' not in node1 . listwallets ( ) :
try :
node1 . loadwallet ( ' wmulti ' )
except JSONRPCException as e :
2023-06-14 07:00:11 -04:00
path = self . nodes [ 1 ] . wallets_path / " wmulti "
2022-03-22 05:47:51 -03:00
if e . error [ ' code ' ] == - 18 and " Wallet file verification failed. Failed to load database path ' {} ' . Path does not exist. " . format ( path ) in e . error [ ' message ' ] :
node1 . createwallet ( wallet_name = ' wmulti ' , disable_private_keys = True )
else :
raise
wmulti = node1 . get_wallet_rpc ( ' wmulti ' )
2018-04-25 05:44:58 -03:00
2019-03-25 18:00:00 -03:00
# Construct the expected descriptor
desc = ' multi( {} , {} ) ' . format ( self . nsigs , ' , ' . join ( self . pub ) )
if self . output_type == ' legacy ' :
desc = ' sh( {} ) ' . format ( desc )
elif self . output_type == ' p2sh-segwit ' :
desc = ' sh(wsh( {} )) ' . format ( desc )
elif self . output_type == ' bech32 ' :
desc = ' wsh( {} ) ' . format ( desc )
desc = descsum_create ( desc )
2018-04-25 05:44:58 -03:00
msig = node2 . createmultisig ( self . nsigs , self . pub , self . output_type )
2022-05-25 14:17:12 -04:00
assert ' warnings ' not in msig
2018-04-25 05:44:58 -03:00
madd = msig [ " address " ]
mredeem = msig [ " redeemScript " ]
2019-03-25 18:00:00 -03:00
assert_equal ( desc , msig [ ' descriptor ' ] )
2018-04-25 05:44:58 -03:00
if self . output_type == ' bech32 ' :
assert madd [ 0 : 4 ] == " bcrt " # actually a bech32 address
2022-03-22 05:47:51 -03:00
if self . is_bdb_compiled ( ) :
# compare against addmultisigaddress
msigw = wmulti . addmultisigaddress ( self . nsigs , self . pub , None , self . output_type )
maddw = msigw [ " address " ]
mredeemw = msigw [ " redeemScript " ]
assert_equal ( desc , drop_origins ( msigw [ ' descriptor ' ] ) )
# addmultisigiaddress and createmultisig work the same
assert maddw == madd
assert mredeemw == mredeem
wmulti . unloadwallet ( )
2023-03-28 12:58:16 -03:00
spk = address_to_scriptpubkey ( madd )
2023-05-12 09:27:05 -04:00
txid = self . wallet . send_to ( from_node = self . nodes [ 0 ] , scriptPubKey = spk , amount = 1300 ) [ " txid " ]
2018-04-25 05:44:58 -03:00
tx = node0 . getrawtransaction ( txid , True )
2021-02-01 12:52:07 -03:00
vout = [ v [ " n " ] for v in tx [ " vout " ] if madd == v [ " scriptPubKey " ] [ " address " ] ]
2018-04-25 05:44:58 -03:00
assert len ( vout ) == 1
vout = vout [ 0 ]
scriptPubKey = tx [ " vout " ] [ vout ] [ " scriptPubKey " ] [ " hex " ]
value = tx [ " vout " ] [ vout ] [ " value " ]
prevtxs = [ { " txid " : txid , " vout " : vout , " scriptPubKey " : scriptPubKey , " redeemScript " : mredeem , " amount " : value } ]
2021-08-19 11:10:24 -04:00
self . generate ( node0 , 1 )
2018-04-25 05:44:58 -03:00
outval = value - decimal . Decimal ( " 0.00001000 " )
rawtx = node2 . createrawtransaction ( [ { " txid " : txid , " vout " : vout } ] , [ { self . final : outval } ] )
2019-06-20 00:02:06 -04:00
prevtx_err = dict ( prevtxs [ 0 ] )
del prevtx_err [ " redeemScript " ]
assert_raises_rpc_error ( - 8 , " Missing redeemScript/witnessScript " , node2 . signrawtransactionwithkey , rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
# if witnessScript specified, all ok
prevtx_err [ " witnessScript " ] = prevtxs [ 0 ] [ " redeemScript " ]
node2 . signrawtransactionwithkey ( rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
# both specified, also ok
prevtx_err [ " redeemScript " ] = prevtxs [ 0 ] [ " redeemScript " ]
node2 . signrawtransactionwithkey ( rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
# redeemScript mismatch to witnessScript
prevtx_err [ " redeemScript " ] = " 6a " # OP_RETURN
assert_raises_rpc_error ( - 8 , " redeemScript does not correspond to witnessScript " , node2 . signrawtransactionwithkey , rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
# redeemScript does not match scriptPubKey
del prevtx_err [ " witnessScript " ]
assert_raises_rpc_error ( - 8 , " redeemScript/witnessScript does not match scriptPubKey " , node2 . signrawtransactionwithkey , rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
# witnessScript does not match scriptPubKey
prevtx_err [ " witnessScript " ] = prevtx_err [ " redeemScript " ]
del prevtx_err [ " redeemScript " ]
assert_raises_rpc_error ( - 8 , " redeemScript/witnessScript does not match scriptPubKey " , node2 . signrawtransactionwithkey , rawtx , self . priv [ 0 : self . nsigs - 1 ] , [ prevtx_err ] )
2019-04-16 13:06:37 -04:00
rawtx2 = node2 . signrawtransactionwithkey ( rawtx , self . priv [ 0 : self . nsigs - 1 ] , prevtxs )
2018-04-25 05:44:58 -03:00
rawtx3 = node2 . signrawtransactionwithkey ( rawtx2 [ " hex " ] , [ self . priv [ - 1 ] ] , prevtxs )
self . moved + = outval
2018-06-27 04:21:07 -04:00
tx = node0 . sendrawtransaction ( rawtx3 [ " hex " ] , 0 )
2021-08-19 11:10:24 -04:00
blk = self . generate ( node0 , 1 ) [ 0 ]
2018-04-25 05:44:58 -03:00
assert tx in node0 . getblock ( blk ) [ " tx " ]
txinfo = node0 . getrawtransaction ( tx , True , blk )
self . log . info ( " n/m= %d / %d %s size= %d vsize= %d weight= %d " % ( self . nsigs , self . nkeys , self . output_type , txinfo [ " size " ] , txinfo [ " vsize " ] , txinfo [ " weight " ] ) )
2019-04-16 13:06:37 -04:00
2018-04-25 05:44:58 -03:00
if __name__ == ' __main__ ' :
RpcCreateMultiSigTest ( ) . main ( )