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
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
2023-08-19 15:47:54 -04:00
from test_framework . messages import COIN
2023-08-23 17:34:43 -04:00
from test_framework . script_util import keys_to_multisig_script
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
2023-08-19 08:54:30 -04:00
self . enable_wallet_if_possible ( )
2018-09-09 14:32:37 -03:00
2023-08-19 15:47:54 -04:00
def create_keys ( self , num_keys ) :
2019-07-16 15:33:35 -04:00
self . pub = [ ]
self . priv = [ ]
2023-08-19 15:47:54 -04:00
for _ in range ( num_keys ) :
2023-05-23 17:38:31 -04:00
privkey , pubkey = generate_keypair ( wif = True )
self . pub . append ( pubkey . hex ( ) )
self . priv . append ( privkey )
2018-04-25 05:44:58 -03:00
2023-08-19 15:47:54 -04:00
def create_wallet ( self , node , wallet_name ) :
node . createwallet ( wallet_name = wallet_name , disable_private_keys = True )
return node . get_wallet_rpc ( wallet_name )
2018-04-25 05:44:58 -03:00
def run_test ( self ) :
2024-08-27 08:14:45 -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
2023-08-19 17:51:06 -04:00
if self . is_wallet_compiled ( ) :
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
2023-08-19 15:47:54 -04:00
wallet_multi = self . create_wallet ( node1 , ' wmulti ' ) if self . _requires_wallet else None
2023-08-21 16:58:46 -04:00
self . create_keys ( 21 ) # max number of allowed keys + 1
m_of_n = [ ( 2 , 3 ) , ( 3 , 3 ) , ( 2 , 5 ) , ( 3 , 5 ) , ( 10 , 15 ) , ( 15 , 15 ) ]
for ( sigs , keys ) in m_of_n :
for output_type in [ " bech32 " , " p2sh-segwit " , " legacy " ] :
self . do_multisig ( keys , sigs , output_type , wallet_multi )
2022-05-25 14:17:12 -04:00
2023-08-21 17:18:00 -04:00
self . test_multisig_script_limit ( wallet_multi )
2023-08-19 18:18:00 -04:00
self . test_mixing_uncompressed_and_compressed_keys ( node0 , wallet_multi )
2023-08-19 18:20:04 -04:00
self . test_sortedmulti_descriptors_bip67 ( )
2019-10-04 16:32:00 -03:00
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 " )
2023-08-23 17:34:43 -04:00
self . log . info ( ' Check correct encoding of multisig script for all n (1..20) ' )
for nkeys in range ( 1 , 20 + 1 ) :
keys = [ self . pub [ 0 ] ] * nkeys
expected_ms_script = keys_to_multisig_script ( keys , k = nkeys ) # simply use n-of-n
# note that the 'legacy' address type fails for n values larger than 15
# due to exceeding the P2SH size limit (520 bytes), so we use 'bech32' instead
# (for the purpose of this encoding test, we don't care about the resulting address)
res = self . nodes [ 0 ] . createmultisig ( nrequired = nkeys , keys = keys , address_type = ' bech32 ' )
assert_equal ( res [ ' redeemScript ' ] , expected_ms_script . hex ( ) )
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 " )
2023-08-21 17:18:00 -04:00
def test_multisig_script_limit ( self , wallet_multi ) :
2023-08-21 16:58:46 -04:00
node1 = self . nodes [ 1 ]
pubkeys = self . pub [ 0 : 20 ]
self . log . info ( ' Test legacy redeem script max size limit ' )
assert_raises_rpc_error ( - 8 , " redeemScript exceeds size limit: 684 > 520 " , node1 . createmultisig , 16 , pubkeys , ' legacy ' )
self . log . info ( ' Test valid 16-20 multisig p2sh-legacy and bech32 (no wallet) ' )
self . do_multisig ( nkeys = 20 , nsigs = 16 , output_type = " p2sh-segwit " , wallet_multi = None )
self . do_multisig ( nkeys = 20 , nsigs = 16 , output_type = " bech32 " , wallet_multi = None )
self . log . info ( ' Test invalid 16-21 multisig p2sh-legacy and bech32 (no wallet) ' )
assert_raises_rpc_error ( - 8 , " Number of keys involved in the multisignature address creation > 20 " , node1 . createmultisig , 16 , self . pub , ' p2sh-segwit ' )
assert_raises_rpc_error ( - 8 , " Number of keys involved in the multisignature address creation > 20 " , node1 . createmultisig , 16 , self . pub , ' bech32 ' )
2023-08-21 17:18:00 -04:00
# Check legacy wallet related command
self . log . info ( ' Test legacy redeem script max size limit (with wallet) ' )
if wallet_multi is not None and not self . options . descriptors :
assert_raises_rpc_error ( - 8 , " redeemScript exceeds size limit: 684 > 520 " , wallet_multi . addmultisigaddress , 16 , pubkeys , ' ' , ' legacy ' )
self . log . info ( ' Test legacy wallet unsupported operation. 16-20 multisig p2sh-legacy and bech32 generation ' )
# Due an internal limitation on legacy wallets, the redeem script limit also applies to p2sh-segwit and bech32 (even when the scripts are valid)
# We take this as a "good thing" to tell users to upgrade to descriptors.
assert_raises_rpc_error ( - 4 , " Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts " , wallet_multi . addmultisigaddress , 16 , pubkeys , ' ' , ' p2sh-segwit ' )
assert_raises_rpc_error ( - 4 , " Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts " , wallet_multi . addmultisigaddress , 16 , pubkeys , ' ' , ' bech32 ' )
2023-08-19 15:47:54 -04:00
def do_multisig ( self , nkeys , nsigs , output_type , wallet_multi ) :
2024-08-27 08:14:45 -04:00
node0 , _node1 , node2 = self . nodes
2023-08-19 15:47:54 -04:00
pub_keys = self . pub [ 0 : nkeys ]
priv_keys = self . priv [ 0 : nkeys ]
2018-04-25 05:44:58 -03:00
2019-03-25 18:00:00 -03:00
# Construct the expected descriptor
2023-08-19 15:47:54 -04:00
desc = ' multi( {} , {} ) ' . format ( nsigs , ' , ' . join ( pub_keys ) )
if output_type == ' legacy ' :
2019-03-25 18:00:00 -03:00
desc = ' sh( {} ) ' . format ( desc )
2023-08-19 15:47:54 -04:00
elif output_type == ' p2sh-segwit ' :
2019-03-25 18:00:00 -03:00
desc = ' sh(wsh( {} )) ' . format ( desc )
2023-08-19 15:47:54 -04:00
elif output_type == ' bech32 ' :
2019-03-25 18:00:00 -03:00
desc = ' wsh( {} ) ' . format ( desc )
desc = descsum_create ( desc )
2023-08-19 15:47:54 -04:00
msig = node2 . createmultisig ( nsigs , pub_keys , 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 ' ] )
2023-08-19 15:47:54 -04:00
if output_type == ' bech32 ' :
2018-04-25 05:44:58 -03:00
assert madd [ 0 : 4 ] == " bcrt " # actually a bech32 address
2023-08-19 15:47:54 -04:00
if wallet_multi is not None :
2022-03-22 05:47:51 -03:00
# compare against addmultisigaddress
2023-08-19 15:47:54 -04:00
msigw = wallet_multi . addmultisigaddress ( nsigs , pub_keys , None , output_type )
2022-03-22 05:47:51 -03:00
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
2023-03-28 12:58:16 -03:00
spk = address_to_scriptpubkey ( madd )
2023-08-21 16:58:46 -04:00
value = decimal . Decimal ( " 0.00004000 " )
2023-08-19 15:47:54 -04:00
tx = self . wallet . send_to ( from_node = self . nodes [ 0 ] , scriptPubKey = spk , amount = int ( value * COIN ) )
prevtxs = [ { " txid " : tx [ " txid " ] , " vout " : tx [ " sent_vout " ] , " scriptPubKey " : spk . hex ( ) , " redeemScript " : mredeem , " amount " : value } ]
2018-04-25 05:44:58 -03:00
2021-08-19 11:10:24 -04:00
self . generate ( node0 , 1 )
2018-04-25 05:44:58 -03:00
2023-08-21 16:58:46 -04:00
outval = value - decimal . Decimal ( " 0.00002000 " ) # deduce fee (must be higher than the min relay fee)
2023-08-19 17:51:06 -04:00
# send coins to node2 when wallet is enabled
node2_balance = node2 . getbalances ( ) [ ' mine ' ] [ ' trusted ' ] if self . is_wallet_compiled ( ) else 0
out_addr = node2 . getnewaddress ( ) if self . is_wallet_compiled ( ) else getnewdestination ( ' bech32 ' ) [ 2 ]
rawtx = node2 . createrawtransaction ( [ { " txid " : tx [ " txid " ] , " vout " : tx [ " sent_vout " ] } ] , [ { out_addr : outval } ] )
2018-04-25 05:44:58 -03:00
2019-06-20 00:02:06 -04:00
prevtx_err = dict ( prevtxs [ 0 ] )
del prevtx_err [ " redeemScript " ]
2023-08-19 15:47:54 -04:00
assert_raises_rpc_error ( - 8 , " Missing redeemScript/witnessScript " , node2 . signrawtransactionwithkey , rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 00:02:06 -04:00
2019-06-20 04:01:23 -04:00
# if witnessScript specified, all ok
prevtx_err [ " witnessScript " ] = prevtxs [ 0 ] [ " redeemScript " ]
2023-08-19 15:47:54 -04:00
node2 . signrawtransactionwithkey ( rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
# both specified, also ok
prevtx_err [ " redeemScript " ] = prevtxs [ 0 ] [ " redeemScript " ]
2023-08-19 15:47:54 -04:00
node2 . signrawtransactionwithkey ( rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
# redeemScript mismatch to witnessScript
prevtx_err [ " redeemScript " ] = " 6a " # OP_RETURN
2023-08-19 15:47:54 -04:00
assert_raises_rpc_error ( - 8 , " redeemScript does not correspond to witnessScript " , node2 . signrawtransactionwithkey , rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
# redeemScript does not match scriptPubKey
del prevtx_err [ " witnessScript " ]
2023-08-19 15:47:54 -04:00
assert_raises_rpc_error ( - 8 , " redeemScript/witnessScript does not match scriptPubKey " , node2 . signrawtransactionwithkey , rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
# witnessScript does not match scriptPubKey
prevtx_err [ " witnessScript " ] = prevtx_err [ " redeemScript " ]
del prevtx_err [ " redeemScript " ]
2023-08-19 15:47:54 -04:00
assert_raises_rpc_error ( - 8 , " redeemScript/witnessScript does not match scriptPubKey " , node2 . signrawtransactionwithkey , rawtx , priv_keys [ 0 : nsigs - 1 ] , [ prevtx_err ] )
2019-06-20 04:01:23 -04:00
2023-08-19 15:47:54 -04:00
rawtx2 = node2 . signrawtransactionwithkey ( rawtx , priv_keys [ 0 : nsigs - 1 ] , prevtxs )
2024-06-10 16:52:46 -04:00
assert_equal ( rawtx2 [ " complete " ] , False )
rawtx3 = node2 . signrawtransactionwithkey ( rawtx , [ priv_keys [ - 1 ] ] , prevtxs )
assert_equal ( rawtx3 [ " complete " ] , False )
assert_raises_rpc_error ( - 22 , " TX decode failed " , node2 . combinerawtransaction , [ rawtx2 [ ' hex ' ] , rawtx3 [ ' hex ' ] + " 00 " ] )
assert_raises_rpc_error ( - 22 , " Missing transactions " , node2 . combinerawtransaction , [ ] )
combined_rawtx = node2 . combinerawtransaction ( [ rawtx2 [ " hex " ] , rawtx3 [ " hex " ] ] )
tx = node0 . sendrawtransaction ( combined_rawtx , 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 " ]
2024-06-10 16:52:46 -04:00
assert_raises_rpc_error ( - 25 , " Input not found or already spent " , node2 . combinerawtransaction , [ rawtx2 [ ' hex ' ] , rawtx3 [ ' hex ' ] ] )
2023-08-19 17:51:06 -04:00
# When the wallet is enabled, assert node2 sees the incoming amount
if self . is_wallet_compiled ( ) :
assert_equal ( node2 . getbalances ( ) [ ' mine ' ] [ ' trusted ' ] , node2_balance + outval )
2018-04-25 05:44:58 -03:00
txinfo = node0 . getrawtransaction ( tx , True , blk )
2023-08-19 15:47:54 -04:00
self . log . info ( " n/m= %d / %d %s size= %d vsize= %d weight= %d " % ( nsigs , nkeys , output_type , txinfo [ " size " ] , txinfo [ " vsize " ] , txinfo [ " weight " ] ) )
2018-04-25 05:44:58 -03:00
2023-08-19 18:18:00 -04:00
def test_mixing_uncompressed_and_compressed_keys ( self , node , wallet_multi ) :
self . log . info ( ' Mixed compressed and uncompressed multisigs are not allowed ' )
pk0 , pk1 , pk2 = [ getnewdestination ( ' bech32 ' ) [ 0 ] . hex ( ) for _ in range ( 3 ) ]
# decompress pk2
pk_obj = ECPubKey ( )
pk_obj . set ( bytes . fromhex ( pk2 ) )
pk_obj . compressed = False
pk2 = pk_obj . get_bytes ( ) . hex ( )
# 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 = node . createmultisig ( 2 , keys , ' legacy ' ) [ ' address ' ]
if wallet_multi is not None :
# 'addmultisigaddress' should return the same address
result = wallet_multi . addmultisigaddress ( 2 , keys , ' ' , ' legacy ' )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert ' warnings ' not in result
# Generate addresses with the segwit types. These should all make legacy addresses
err_msg = [ " Unable to make chosen address type, please ensure no uncompressed public keys are present. " ]
for addr_type in [ ' bech32 ' , ' p2sh-segwit ' ] :
result = self . nodes [ 0 ] . createmultisig ( nrequired = 2 , keys = keys , address_type = addr_type )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert_equal ( result [ ' warnings ' ] , err_msg )
if wallet_multi is not None :
result = wallet_multi . addmultisigaddress ( nrequired = 2 , keys = keys , address_type = addr_type )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert_equal ( result [ ' warnings ' ] , err_msg )
2023-08-19 18:20:04 -04:00
def test_sortedmulti_descriptors_bip67 ( self ) :
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 ' ] )
2023-08-19 18:18:00 -04:00
2019-04-16 13:06:37 -04:00
2018-04-25 05:44:58 -03:00
if __name__ == ' __main__ ' :
2024-07-16 17:05:14 -04:00
RpcCreateMultiSigTest ( __file__ ) . main ( )