2018-04-25 10:44:58 +02:00
#!/usr/bin/env python3
2021-07-28 13:57:16 +02:00
# Copyright (c) 2015-2021 The Bitcoin Core developers
2018-04-25 10:44:58 +02:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2019-04-16 19:06:37 +02:00
""" Test multisig RPCs """
2020-06-10 17:56:44 +02:00
import decimal
import itertools
import json
import os
2018-04-25 10:44:58 +02:00
2021-05-17 16:38:19 +02:00
from test_framework . blocktools import COINBASE_MATURITY
2019-07-16 21:33:35 +02:00
from test_framework . authproxy import JSONRPCException
2019-03-25 22:00:00 +01:00
from test_framework . descriptors import descsum_create , drop_origins
2020-06-10 18:10:02 +02:00
from test_framework . key import ECPubKey , ECKey
2018-04-25 10:44:58 +02:00
from test_framework . test_framework import BitcoinTestFramework
2019-04-16 19:12:54 +02:00
from test_framework . util import (
assert_raises_rpc_error ,
2019-05-15 06:21:11 +02:00
assert_equal ,
2019-04-16 19:12:54 +02:00
)
2020-06-10 18:10:02 +02:00
from test_framework . wallet_util import bytes_to_wif
2019-04-16 19:06:37 +02:00
2018-04-25 10:44:58 +02:00
class RpcCreateMultiSigTest ( BitcoinTestFramework ) :
def set_test_params ( self ) :
self . setup_clean_chain = True
self . num_nodes = 3
2019-12-06 15:37:49 +01:00
self . supports_cli = False
2018-04-25 10:44:58 +02:00
2018-09-09 19:32:37 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2018-04-25 10:44:58 +02:00
def get_keys ( self ) :
2019-07-16 21:33:35 +02:00
self . pub = [ ]
self . priv = [ ]
2018-09-09 19:32:37 +02:00
node0 , node1 , node2 = self . nodes
2019-07-16 21:33:35 +02:00
for _ in range ( self . nkeys ) :
k = ECKey ( )
k . generate ( )
self . pub . append ( k . get_pubkey ( ) . get_bytes ( ) . hex ( ) )
self . priv . append ( bytes_to_wif ( k . get_bytes ( ) , k . is_compressed ) )
2018-04-25 10:44:58 +02:00
self . final = node2 . getnewaddress ( )
def run_test ( self ) :
2019-04-16 19:06:37 +02:00
node0 , node1 , node2 = self . nodes
2018-04-25 10:44:58 +02:00
2019-04-16 19:12:54 +02:00
self . check_addmultisigaddress_errors ( )
self . log . info ( ' Generating blocks ... ' )
2021-08-19 17:10:24 +02:00
self . generate ( node0 , 149 )
2018-04-25 10:44:58 +02:00
self . moved = 0
2019-04-16 19:06:37 +02:00
for self . nkeys in [ 3 , 5 ] :
for self . nsigs in [ 2 , 3 ] :
2018-04-25 10:44:58 +02:00
for self . output_type in [ " bech32 " , " p2sh-segwit " , " legacy " ] :
self . get_keys ( )
self . do_multisig ( )
self . checkbalances ( )
2019-05-15 06:21:11 +02:00
# Test mixed compressed and uncompressed pubkeys
self . log . info ( ' Mixed compressed and uncompressed multisigs are not allowed ' )
pk0 = node0 . getaddressinfo ( node0 . getnewaddress ( ) ) [ ' pubkey ' ]
pk1 = node1 . getaddressinfo ( node1 . getnewaddress ( ) ) [ ' pubkey ' ]
pk2 = node2 . getaddressinfo ( node2 . getnewaddress ( ) ) [ ' pubkey ' ]
# decompress pk2
pk_obj = ECPubKey ( )
2021-08-04 20:59:06 +02:00
pk_obj . set ( bytes . fromhex ( pk2 ) )
2019-05-15 06:21:11 +02:00
pk_obj . compressed = False
2021-08-04 20:59:06 +02:00
pk2 = pk_obj . get_bytes ( ) . hex ( )
2019-05-15 06:21:11 +02:00
2019-07-16 21:33:35 +02:00
node0 . createwallet ( wallet_name = ' wmulti0 ' , disable_private_keys = True )
wmulti0 = node0 . get_wallet_rpc ( ' wmulti0 ' )
2019-05-15 06:21:11 +02: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 ' ]
2021-12-08 05:05:16 +01:00
result = wmulti0 . addmultisigaddress ( 2 , keys , ' ' , ' legacy ' )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert ' warnings ' not in result
2019-05-15 06:21:11 +02:00
# Generate addresses with the segwit types. These should all make legacy addresses
2021-09-28 04:08:13 +02:00
for addr_type in [ ' bech32 ' , ' p2sh-segwit ' ] :
result = wmulti0 . createmultisig ( 2 , keys , addr_type )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert_equal ( result [ ' warnings ' ] , [ " Unable to make chosen address type, please ensure no uncompressed public keys are present. " ] )
2021-12-08 05:05:16 +01:00
result = wmulti0 . addmultisigaddress ( 2 , keys , ' ' , addr_type )
assert_equal ( legacy_addr , result [ ' address ' ] )
assert_equal ( result [ ' warnings ' ] , [ " Unable to make chosen address type, please ensure no uncompressed public keys are present. " ] )
2019-05-15 06:21:11 +02:00
2019-10-04 21:32:00 +02: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 23:35:47 +02: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 19:12:54 +02:00
def check_addmultisigaddress_errors ( self ) :
2019-07-16 21:33:35 +02:00
if self . options . descriptors :
return
2019-04-16 19:12:54 +02: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 23:35:47 +02: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 10:44:58 +02:00
def checkbalances ( self ) :
2019-04-16 19:06:37 +02:00
node0 , node1 , node2 = self . nodes
2021-08-19 17:10:24 +02:00
self . generate ( node0 , COINBASE_MATURITY )
2018-04-25 10:44:58 +02:00
bal0 = node0 . getbalance ( )
bal1 = node1 . getbalance ( )
bal2 = node2 . getbalance ( )
height = node0 . getblockchaininfo ( ) [ " blocks " ]
assert 150 < height < 350
2019-04-16 19:06:37 +02:00
total = 149 * 50 + ( height - 149 - 100 ) * 25
2018-04-25 10:44:58 +02:00
assert bal1 == 0
assert bal2 == self . moved
2019-04-16 19:06:37 +02:00
assert bal0 + bal1 + bal2 == total
2018-04-25 10:44:58 +02:00
def do_multisig ( self ) :
2019-04-16 19:06:37 +02:00
node0 , node1 , node2 = self . nodes
2019-07-16 21:33:35 +02:00
if ' wmulti ' not in node1 . listwallets ( ) :
try :
node1 . loadwallet ( ' wmulti ' )
except JSONRPCException as e :
2020-08-05 02:58:43 +02:00
path = os . path . join ( self . options . tmpdir , " node1 " , " regtest " , " wallets " , " wmulti " )
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 ' ] :
2019-07-16 21:33:35 +02:00
node1 . createwallet ( wallet_name = ' wmulti ' , disable_private_keys = True )
else :
raise
wmulti = node1 . get_wallet_rpc ( ' wmulti ' )
2018-04-25 10:44:58 +02:00
2019-03-25 22:00:00 +01: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 10:44:58 +02:00
msig = node2 . createmultisig ( self . nsigs , self . pub , self . output_type )
madd = msig [ " address " ]
mredeem = msig [ " redeemScript " ]
2019-03-25 22:00:00 +01:00
assert_equal ( desc , msig [ ' descriptor ' ] )
2018-04-25 10:44:58 +02:00
if self . output_type == ' bech32 ' :
assert madd [ 0 : 4 ] == " bcrt " # actually a bech32 address
# compare against addmultisigaddress
2019-07-16 21:33:35 +02:00
msigw = wmulti . addmultisigaddress ( self . nsigs , self . pub , None , self . output_type )
2018-04-25 10:44:58 +02:00
maddw = msigw [ " address " ]
mredeemw = msigw [ " redeemScript " ]
2019-03-25 22:00:00 +01:00
assert_equal ( desc , drop_origins ( msigw [ ' descriptor ' ] ) )
2018-04-25 10:44:58 +02:00
# addmultisigiaddress and createmultisig work the same
assert maddw == madd
assert mredeemw == mredeem
txid = node0 . sendtoaddress ( madd , 40 )
tx = node0 . getrawtransaction ( txid , True )
2021-02-01 16:52:07 +01:00
vout = [ v [ " n " ] for v in tx [ " vout " ] if madd == v [ " scriptPubKey " ] [ " address " ] ]
2018-04-25 10:44:58 +02: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 17:10:24 +02:00
self . generate ( node0 , 1 )
2018-04-25 10:44:58 +02:00
outval = value - decimal . Decimal ( " 0.00001000 " )
rawtx = node2 . createrawtransaction ( [ { " txid " : txid , " vout " : vout } ] , [ { self . final : outval } ] )
2019-06-20 06:02:06 +02: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 10:01:23 +02: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 19:06:37 +02:00
rawtx2 = node2 . signrawtransactionwithkey ( rawtx , self . priv [ 0 : self . nsigs - 1 ] , prevtxs )
2018-04-25 10:44:58 +02:00
rawtx3 = node2 . signrawtransactionwithkey ( rawtx2 [ " hex " ] , [ self . priv [ - 1 ] ] , prevtxs )
self . moved + = outval
2018-06-27 10:21:07 +02:00
tx = node0 . sendrawtransaction ( rawtx3 [ " hex " ] , 0 )
2021-08-19 17:10:24 +02:00
blk = self . generate ( node0 , 1 ) [ 0 ]
2018-04-25 10:44:58 +02: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-07-16 21:33:35 +02:00
wmulti . unloadwallet ( )
2019-04-16 19:06:37 +02:00
2018-04-25 10:44:58 +02:00
if __name__ == ' __main__ ' :
RpcCreateMultiSigTest ( ) . main ( )