mirror of
https://github.com/JoinMarket-Org/joinmarket-clientserver.git
synced 2026-08-13 12:33:40 +02:00
Add support for OP_CLTV timelock addresses
The new functions implement creating fund freezing redeem scripts and transactions which spend from such scripts. Also there is a new test function.
This commit is contained in:
parent
d86df334c3
commit
ee70cd793e
3 changed files with 110 additions and 22 deletions
|
|
@ -10,6 +10,7 @@ import struct
|
|||
import random
|
||||
from jmbitcoin.secp256k1_main import *
|
||||
from jmbitcoin.bech32 import *
|
||||
import jmbitcoin as btc
|
||||
|
||||
P2PKH_PRE, P2PKH_POST = b'\x76\xa9\x14', b'\x88\xac'
|
||||
P2SH_P2WPKH_PRE, P2SH_P2WPKH_POST = b'\xa9\x14', b'\x87'
|
||||
|
|
@ -541,7 +542,7 @@ def pubkeys_to_p2wsh_multisig_script(pubs):
|
|||
"""
|
||||
N = len(pubs)
|
||||
script = mk_multisig_script(pubs, N)
|
||||
return P2WSH_PRE + bin_sha256(binascii.unhexlify(script))
|
||||
return redeem_script_to_p2wsh_script(script)
|
||||
|
||||
def pubkeys_to_p2wsh_multisig_address(pubs):
|
||||
""" Given a list of N pubkeys, constructs an N of N
|
||||
|
|
@ -551,6 +552,12 @@ def pubkeys_to_p2wsh_multisig_address(pubs):
|
|||
script = pubkeys_to_p2wsh_multisig_script(pubs)
|
||||
return script_to_address(script)
|
||||
|
||||
def redeem_script_to_p2wsh_script(redeem_script):
|
||||
return P2WSH_PRE + bin_sha256(binascii.unhexlify(redeem_script))
|
||||
|
||||
def redeem_script_to_p2wsh_address(redeem_script, vbyte, witver=0):
|
||||
return script_to_address(redeem_script_to_p2wsh_script(redeem_script), vbyte, witver)
|
||||
|
||||
def deserialize_script(scriptinp):
|
||||
""" Note that this is not used internally, in
|
||||
the jmbitcoin package, to deserialize() transactions;
|
||||
|
|
@ -603,10 +610,14 @@ def deserialize_script(scriptinp):
|
|||
|
||||
def serialize_script_unit(unit):
|
||||
if isinstance(unit, int):
|
||||
if unit < 16:
|
||||
return from_int_to_byte(unit + 80)
|
||||
else:
|
||||
if unit == 0:
|
||||
return from_int_to_byte(unit)
|
||||
elif unit < 16:
|
||||
return from_int_to_byte(unit + 80)
|
||||
elif unit < 256:
|
||||
return from_int_to_byte(unit)
|
||||
else:
|
||||
return b'\x04' + struct.pack(b"<I", unit)
|
||||
elif unit is None:
|
||||
return b'\x00'
|
||||
else:
|
||||
|
|
@ -634,15 +645,30 @@ def serialize_script(script):
|
|||
else:
|
||||
return result
|
||||
|
||||
|
||||
def mk_multisig_script(pubs, k):
|
||||
""" Given a list of pubkeys and an integer k,
|
||||
construct a multisig script for k of N, where N is
|
||||
the length of the list `pubs`; script is returned
|
||||
as hex string.
|
||||
"""
|
||||
return serialize_script([k] + pubs + [len(pubs)]) + 'ae'
|
||||
return serialize_script([k] + pubs + [len(pubs)]) \
|
||||
+ 'ae' #OP_CHECKMULTISIG
|
||||
|
||||
def mk_freeze_script(pub, locktime):
|
||||
"""
|
||||
Given a pubkey and locktime, create a script which can only be spent
|
||||
after the locktime has passed using OP_CHECKLOCKTIMEVERIFY
|
||||
"""
|
||||
if not isinstance(locktime, int):
|
||||
raise TypeError("locktime must be int")
|
||||
if not isinstance(pub, bytes):
|
||||
raise TypeError("pubkey must be in bytes")
|
||||
usehex = False
|
||||
if not is_valid_pubkey(pub, usehex, require_compressed=True):
|
||||
raise ValueError("not a valid public key")
|
||||
scr = [locktime, btc.OP_CHECKLOCKTIMEVERIFY, btc.OP_DROP, pub,
|
||||
btc.OP_CHECKSIG]
|
||||
return binascii.hexlify(serialize_script(scr)).decode()
|
||||
|
||||
# Signing and verifying
|
||||
|
||||
|
|
@ -701,8 +727,8 @@ def sign(tx, i, priv, hashcode=SIGHASH_ALL, usenonce=None, amount=None,
|
|||
`amount` flags whether segwit signing is to be done, and the field
|
||||
`native` flags that native segwit p2wpkh signing is to be done. Note
|
||||
that signing multisig is to be done with the alternative functions
|
||||
multisign or p2wsh_multisign (and non N of N multisig scripthash
|
||||
signing is not currently supported).
|
||||
get_p2sh_signature or get_p2wsh_signature (and non N of N multisig
|
||||
scripthash signing is not currently supported).
|
||||
"""
|
||||
if isinstance(tx, basestring) and not isinstance(tx, bytes):
|
||||
tx = binascii.unhexlify(tx)
|
||||
|
|
@ -762,28 +788,28 @@ def signall(tx, priv):
|
|||
tx = sign(tx, i, priv)
|
||||
return tx
|
||||
|
||||
|
||||
def multisign(tx, i, script, pk, amount=None, hashcode=SIGHASH_ALL):
|
||||
""" Tx is assumed to be serialized. The script passed here is
|
||||
the redeemscript, for example the output of mk_multisig_script.
|
||||
def get_p2sh_signature(tx, i, redeem_script, pk, amount=None, hashcode=SIGHASH_ALL):
|
||||
"""
|
||||
Tx is assumed to be serialized. redeem_script is for example the
|
||||
output of mk_multisig_script.
|
||||
pk is the private key, and must be passed in hex.
|
||||
If amount is not None, the output of p2wsh_multisign is returned.
|
||||
If amount is not None, the output of get_p2wsh_signature is returned.
|
||||
What is returned is a single signature.
|
||||
"""
|
||||
if isinstance(tx, str):
|
||||
tx = binascii.unhexlify(tx)
|
||||
if isinstance(script, str):
|
||||
script = binascii.unhexlify(script)
|
||||
if isinstance(redeem_script, str):
|
||||
redeem_script = binascii.unhexlify(redeem_script)
|
||||
if amount:
|
||||
return p2wsh_multisign(tx, i, script, pk, amount, hashcode)
|
||||
modtx = signature_form(tx, i, script, hashcode)
|
||||
return get_p2wsh_signature(tx, i, redeem_script, pk, amount, hashcode)
|
||||
modtx = signature_form(tx, i, redeem_script, hashcode)
|
||||
return ecdsa_tx_sign(modtx, pk, hashcode)
|
||||
|
||||
def p2wsh_multisign(tx, i, script, pk, amount, hashcode=SIGHASH_ALL):
|
||||
def get_p2wsh_signature(tx, i, redeem_script, pk, amount, hashcode=SIGHASH_ALL):
|
||||
""" See note to multisign for the value to pass in as `script`.
|
||||
Tx is assumed to be serialized.
|
||||
"""
|
||||
modtx = segwit_signature_form(deserialize(tx), i, script, amount,
|
||||
modtx = segwit_signature_form(deserialize(tx), i, redeem_script, amount,
|
||||
hashcode, decoder_func=lambda x: x)
|
||||
return ecdsa_tx_sign(modtx, pk, hashcode)
|
||||
|
||||
|
|
@ -819,6 +845,16 @@ def apply_multisignatures(*args):
|
|||
txobj["ins"][i]["script"] = serialize_script([None] + sigs + [script])
|
||||
return serialize(txobj)
|
||||
|
||||
def apply_freeze_signature(tx, i, redeem_script, sig):
|
||||
if isinstance(redeem_script, str):
|
||||
redeem_script = binascii.unhexlify(redeem_script)
|
||||
if isinstance(sig, str):
|
||||
sig = binascii.unhexlify(sig)
|
||||
txobj = deserialize(tx)
|
||||
txobj["ins"][i]["script"] = ""
|
||||
txobj["ins"][i]["txinwitness"] = [sig, redeem_script]
|
||||
return serialize(txobj)
|
||||
|
||||
def mktx(ins, outs, version=1, locktime=0):
|
||||
""" Given a list of input dicts with key "output"
|
||||
which are txid:n strings in hex, and a list of outputs
|
||||
|
|
|
|||
|
|
@ -287,4 +287,4 @@ ENGINES = {
|
|||
TYPE_P2PKH: BTC_P2PKH,
|
||||
TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH,
|
||||
TYPE_P2WPKH: BTC_P2WPKH
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import time
|
|||
import binascii
|
||||
import struct
|
||||
from commontest import make_wallets, make_sign_and_push
|
||||
from binascii import unhexlify
|
||||
>>>>>>> c158543... only allow pubkey in bytes for mk_freeze_script()
|
||||
|
||||
import jmbitcoin as bitcoin
|
||||
import pytest
|
||||
|
|
@ -190,7 +192,7 @@ def test_spend_p2sh_utxos(setup_tx_creation):
|
|||
tx = bitcoin.mktx(ins, outs)
|
||||
sigs = []
|
||||
for priv in privs[:2]:
|
||||
sigs.append(bitcoin.multisign(tx, 0, script, binascii.hexlify(priv).decode('ascii')))
|
||||
sigs.append(bitcoin.get_p2sh_signature(tx, 0, script, binascii.hexlify(priv).decode('ascii')))
|
||||
tx = bitcoin.apply_multisignatures(tx, 0, script, sigs)
|
||||
txid = jm_single().bc_interface.pushtx(tx)
|
||||
assert txid
|
||||
|
|
@ -268,7 +270,7 @@ def test_spend_p2wsh(setup_tx_creation):
|
|||
sigs = []
|
||||
for priv in privs[i*2:i*2+2]:
|
||||
# sign input j with each of 2 keys
|
||||
sig = bitcoin.multisign(tx, i, redeemScripts[i], priv, amount=amount)
|
||||
sig = bitcoin.get_p2sh_signature(tx, i, redeemScripts[i], priv, amount=amount)
|
||||
sigs.append(sig)
|
||||
# check that verify_tx_input correctly validates;
|
||||
assert bitcoin.verify_tx_input(tx, i, scriptPubKeys[i], sig,
|
||||
|
|
@ -278,6 +280,56 @@ def test_spend_p2wsh(setup_tx_creation):
|
|||
txid = jm_single().bc_interface.pushtx(tx)
|
||||
assert txid
|
||||
|
||||
def ensure_bip65_activated():
|
||||
#on regtest bip65 activates on height 1351
|
||||
#https://github.com/bitcoin/bitcoin/blob/1d1f8bbf57118e01904448108a104e20f50d2544/src/chainparams.cpp#L262
|
||||
BIP65Height = 1351
|
||||
current_height = jm_single().bc_interface.rpc("getblockchaininfo", [])["blocks"]
|
||||
until_bip65_activation = BIP65Height - current_height + 1
|
||||
if until_bip65_activation > 0:
|
||||
jm_single().bc_interface.tick_forward_chain(until_bip65_activation)
|
||||
|
||||
def test_spend_freeze_script(setup_tx_creation):
|
||||
ensure_bip65_activated()
|
||||
|
||||
wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet']
|
||||
wallet_service.sync_wallet(fast=True)
|
||||
|
||||
mediantime = jm_single().bc_interface.rpc("getblockchaininfo", [])["mediantime"]
|
||||
|
||||
timeoffset_success_tests = [(2, False), (-60*60*24*30, True), (60*60*24*30, False)]
|
||||
|
||||
for timeoffset, required_success in timeoffset_success_tests:
|
||||
#generate keypair
|
||||
priv = "aa"*32 + "01"
|
||||
pub = unhexlify(bitcoin.privkey_to_pubkey(priv))
|
||||
addr_locktime = mediantime + timeoffset
|
||||
redeem_script = bitcoin.mk_freeze_script(pub, addr_locktime)
|
||||
script_pub_key = bitcoin.redeem_script_to_p2wsh_script(redeem_script)
|
||||
regtest_vbyte = 100
|
||||
addr = bitcoin.script_to_address(script_pub_key, vbyte=regtest_vbyte)
|
||||
|
||||
#fund frozen funds address
|
||||
amount = 100000000
|
||||
funding_ins_full = wallet_service.select_utxos(0, amount)
|
||||
funding_txid = make_sign_and_push(funding_ins_full, wallet_service, amount, output_addr=addr)
|
||||
assert funding_txid
|
||||
|
||||
#spend frozen funds
|
||||
frozen_in = funding_txid + ":0"
|
||||
output_addr = wallet_service.get_internal_addr(1)
|
||||
miner_fee = 5000
|
||||
outs = [{'value': amount - miner_fee, 'address': output_addr}]
|
||||
tx = bitcoin.mktx([frozen_in], outs, locktime=addr_locktime+1)
|
||||
i = 0
|
||||
sig = bitcoin.get_p2sh_signature(tx, i, redeem_script, priv, amount)
|
||||
|
||||
assert bitcoin.verify_tx_input(tx, i, script_pub_key, sig, pub,
|
||||
scriptCode=redeem_script, amount=amount)
|
||||
tx = bitcoin.apply_freeze_signature(tx, i, redeem_script, sig)
|
||||
push_success = jm_single().bc_interface.pushtx(tx)
|
||||
|
||||
assert push_success == required_success
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def setup_tx_creation():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue