Browse Source

bitcoin: implement construct_script and use it

patch-4
SomberNight 4 years ago
parent
commit
4c7a92f39c
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 51
      electrum/bitcoin.py
  2. 9
      electrum/gui/qt/paytoedit.py
  3. 120
      electrum/lnutil.py
  4. 29
      electrum/transaction.py

51
electrum/bitcoin.py

@ -28,7 +28,7 @@ from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence
import enum import enum
from enum import IntEnum, Enum from enum import IntEnum, Enum
from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict, is_hex_str
from . import version from . import version
from . import segwit_addr from . import segwit_addr
from . import constants from . import constants
@ -307,10 +307,30 @@ def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
item = script_num_to_hex(item) item = script_num_to_hex(item)
elif isinstance(item, (bytes, bytearray)): elif isinstance(item, (bytes, bytearray)):
item = bh2u(item) item = bh2u(item)
else:
assert is_hex_str(item)
witness += witness_push(item) witness += witness_push(item)
return witness return witness
def construct_script(items: Sequence[Union[str, int, bytes, opcodes]]) -> str:
"""Constructs bitcoin script from given items."""
script = ''
for item in items:
if isinstance(item, opcodes):
script += item.hex()
elif type(item) is int:
script += add_number_to_script(item).hex()
elif isinstance(item, (bytes, bytearray)):
script += push_script(item.hex())
elif isinstance(item, str):
assert is_hex_str(item)
script += push_script(item)
else:
raise Exception(f'unexpected item for script: {item!r}')
return script
def relayfee(network: 'Network' = None) -> int: def relayfee(network: 'Network' = None) -> int:
"""Returns feerate in sat/kbyte.""" """Returns feerate in sat/kbyte."""
from .simple_config import FEERATE_DEFAULT_RELAY, FEERATE_MAX_RELAY from .simple_config import FEERATE_DEFAULT_RELAY, FEERATE_MAX_RELAY
@ -386,12 +406,12 @@ def script_to_p2wsh(script: str, *, net=None) -> str:
return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net) return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net)
def p2wpkh_nested_script(pubkey: str) -> str: def p2wpkh_nested_script(pubkey: str) -> str:
pkh = bh2u(hash_160(bfh(pubkey))) pkh = hash_160(bfh(pubkey))
return '00' + push_script(pkh) return construct_script([0, pkh])
def p2wsh_nested_script(witness_script: str) -> str: def p2wsh_nested_script(witness_script: str) -> str:
wsh = bh2u(sha256(bfh(witness_script))) wsh = sha256(bfh(witness_script))
return '00' + push_script(wsh) return construct_script([0, wsh])
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str: def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
if net is None: net = constants.net if net is None: net = constants.net
@ -436,16 +456,12 @@ def address_to_script(addr: str, *, net=None) -> str:
if witprog is not None: if witprog is not None:
if not (0 <= witver <= 16): if not (0 <= witver <= 16):
raise BitcoinException(f'impossible witness version: {witver}') raise BitcoinException(f'impossible witness version: {witver}')
script = bh2u(add_number_to_script(witver)) return construct_script([witver, bytes(witprog)])
script += push_script(bh2u(bytes(witprog)))
return script
addrtype, hash_160_ = b58_address_to_hash160(addr) addrtype, hash_160_ = b58_address_to_hash160(addr)
if addrtype == net.ADDRTYPE_P2PKH: if addrtype == net.ADDRTYPE_P2PKH:
script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_)) script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_))
elif addrtype == net.ADDRTYPE_P2SH: elif addrtype == net.ADDRTYPE_P2SH:
script = opcodes.OP_HASH160.hex() script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
script += push_script(bh2u(hash_160_))
script += opcodes.OP_EQUAL.hex()
else: else:
raise BitcoinException(f'unknown address type: {addrtype}') raise BitcoinException(f'unknown address type: {addrtype}')
return script return script
@ -493,13 +509,16 @@ def script_to_scripthash(script: str) -> str:
return bh2u(bytes(reversed(h))) return bh2u(bytes(reversed(h)))
def public_key_to_p2pk_script(pubkey: str) -> str: def public_key_to_p2pk_script(pubkey: str) -> str:
return push_script(pubkey) + opcodes.OP_CHECKSIG.hex() return construct_script([pubkey, opcodes.OP_CHECKSIG])
def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str: def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str:
script = bytes([opcodes.OP_DUP, opcodes.OP_HASH160]).hex() return construct_script([
script += push_script(pubkey_hash160) opcodes.OP_DUP,
script += bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]).hex() opcodes.OP_HASH160,
return script pubkey_hash160,
opcodes.OP_EQUALVERIFY,
opcodes.OP_CHECKSIG
])
__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

9
electrum/gui/qt/paytoedit.py

@ -31,8 +31,8 @@ from PyQt5.QtGui import QFontMetrics, QFont
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh, maybe_extract_bolt11_invoice from electrum.util import bfh, maybe_extract_bolt11_invoice
from electrum.transaction import push_script, PartialTxOutput from electrum.transaction import PartialTxOutput
from electrum.bitcoin import opcodes from electrum.bitcoin import opcodes, construct_script
from electrum.logging import Logger from electrum.logging import Logger
from electrum.lnaddr import LnDecodeException from electrum.lnaddr import LnDecodeException
@ -111,11 +111,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
for word in x.split(): for word in x.split():
if word[0:3] == 'OP_': if word[0:3] == 'OP_':
opcode_int = opcodes[word] opcode_int = opcodes[word]
assert opcode_int < 256 # opcode is single-byte script += construct_script([opcode_int])
script += bitcoin.int_to_hex(opcode_int)
else: else:
bfh(word) # to test it is hex data bfh(word) # to test it is hex data
script += push_script(word) script += construct_script([word])
return script return script
def parse_amount(self, x): def parse_amount(self, x):

120
electrum/lnutil.py

@ -19,7 +19,8 @@ from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOut
PartialTxOutput, opcodes, TxOutput) PartialTxOutput, opcodes, TxOutput)
from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number
from . import ecc, bitcoin, crypto, transaction from . import ecc, bitcoin, crypto, transaction
from .bitcoin import push_script, redeem_script_to_address, address_to_script, construct_witness from .bitcoin import (push_script, redeem_script_to_address, address_to_script,
construct_witness, construct_script)
from . import segwit_addr from . import segwit_addr
from .i18n import _ from .i18n import _
from .lnaddr import lndecode from .lnaddr import lndecode
@ -452,13 +453,17 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela
assert type(local_feerate) is int assert type(local_feerate) is int
assert type(revocationpubkey) is bytes assert type(revocationpubkey) is bytes
assert type(local_delayedpubkey) is bytes assert type(local_delayedpubkey) is bytes
script = bytes([opcodes.OP_IF]) \ script = bfh(construct_script([
+ bfh(push_script(bh2u(revocationpubkey))) \ opcodes.OP_IF,
+ bytes([opcodes.OP_ELSE]) \ revocationpubkey,
+ bitcoin.add_number_to_script(to_self_delay) \ opcodes.OP_ELSE,
+ bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) \ to_self_delay,
+ bfh(push_script(bh2u(local_delayedpubkey))) \ opcodes.OP_CHECKSEQUENCEVERIFY,
+ bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) opcodes.OP_DROP,
local_delayedpubkey,
opcodes.OP_ENDIF,
opcodes.OP_CHECKSIG,
]))
p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script)) p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script))
weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
@ -503,13 +508,35 @@ def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
assert type(remote_htlcpubkey) is bytes assert type(remote_htlcpubkey) is bytes
assert type(local_htlcpubkey) is bytes assert type(local_htlcpubkey) is bytes
assert type(payment_hash) is bytes assert type(payment_hash) is bytes
return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\ script = bfh(construct_script([
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ opcodes.OP_DUP,
+ bfh(push_script(bh2u(remote_htlcpubkey)))\ opcodes.OP_HASH160,
+ bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\ bitcoin.hash_160(revocation_pubkey),
+ bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\ opcodes.OP_EQUAL,
+ bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\ opcodes.OP_IF,
+ bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) opcodes.OP_CHECKSIG,
opcodes.OP_ELSE,
remote_htlcpubkey,
opcodes.OP_SWAP,
opcodes.OP_SIZE,
32,
opcodes.OP_EQUAL,
opcodes.OP_NOTIF,
opcodes.OP_DROP,
2,
opcodes.OP_SWAP,
local_htlcpubkey,
2,
opcodes.OP_CHECKMULTISIG,
opcodes.OP_ELSE,
opcodes.OP_HASH160,
crypto.ripemd(payment_hash),
opcodes.OP_EQUALVERIFY,
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
]))
return script
def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes, def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
local_htlcpubkey: bytes, payment_hash: bytes, cltv_expiry: int) -> bytes: local_htlcpubkey: bytes, payment_hash: bytes, cltv_expiry: int) -> bytes:
@ -517,22 +544,38 @@ def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
assert type(i) is bytes assert type(i) is bytes
assert type(cltv_expiry) is int assert type(cltv_expiry) is int
return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \ script = bfh(construct_script([
+ bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \ opcodes.OP_DUP,
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ opcodes.OP_HASH160,
+ bfh(push_script(bh2u(remote_htlcpubkey))) \ bitcoin.hash_160(revocation_pubkey),
+ bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \ opcodes.OP_EQUAL,
+ bitcoin.add_number_to_script(32) \ opcodes.OP_IF,
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \ opcodes.OP_CHECKSIG,
+ bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \ opcodes.OP_ELSE,
+ bytes([opcodes.OP_EQUALVERIFY]) \ remote_htlcpubkey,
+ bitcoin.add_number_to_script(2) \ opcodes.OP_SWAP,
+ bytes([opcodes.OP_SWAP]) \ opcodes.OP_SIZE,
+ bfh(push_script(bh2u(local_htlcpubkey))) \ 32,
+ bitcoin.add_number_to_script(2) \ opcodes.OP_EQUAL,
+ bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \ opcodes.OP_IF,
+ bitcoin.add_number_to_script(cltv_expiry) \ opcodes.OP_HASH160,
+ bytes([opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) crypto.ripemd(payment_hash),
opcodes.OP_EQUALVERIFY,
2,
opcodes.OP_SWAP,
local_htlcpubkey,
2,
opcodes.OP_CHECKMULTISIG,
opcodes.OP_ELSE,
opcodes.OP_DROP,
cltv_expiry,
opcodes.OP_CHECKLOCKTIMEVERIFY,
opcodes.OP_DROP,
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
]))
return script
def make_htlc_output_witness_script(is_received_htlc: bool, remote_revocation_pubkey: bytes, remote_htlc_pubkey: bytes, def make_htlc_output_witness_script(is_received_htlc: bool, remote_revocation_pubkey: bytes, remote_htlc_pubkey: bytes,
local_htlc_pubkey: bytes, payment_hash: bytes, cltv_expiry: Optional[int]) -> bytes: local_htlc_pubkey: bytes, payment_hash: bytes, cltv_expiry: Optional[int]) -> bytes:
@ -796,9 +839,18 @@ def make_commitment(
def make_commitment_output_to_local_witness_script( def make_commitment_output_to_local_witness_script(
revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> bytes: revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> bytes:
local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + bitcoin.add_number_to_script(to_self_delay) \ script = bfh(construct_script([
+ bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) opcodes.OP_IF,
return local_script revocation_pubkey,
opcodes.OP_ELSE,
to_self_delay,
opcodes.OP_CHECKSEQUENCEVERIFY,
opcodes.OP_DROP,
delayed_pubkey,
opcodes.OP_ENDIF,
opcodes.OP_CHECKSIG,
]))
return script
def make_commitment_output_to_local_address( def make_commitment_output_to_local_address(
revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str: revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:

29
electrum/transaction.py

@ -48,7 +48,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
int_to_hex, push_script, b58_address_to_hash160, int_to_hex, push_script, b58_address_to_hash160,
opcodes, add_number_to_script, base_decode, is_segwit_script_type, opcodes, add_number_to_script, base_decode, is_segwit_script_type,
base_encode, construct_witness) base_encode, construct_witness, construct_script)
from .crypto import sha256d from .crypto import sha256d
from .logging import get_logger from .logging import get_logger
@ -500,10 +500,7 @@ def parse_output(vds: BCDataStream) -> TxOutput:
def multisig_script(public_keys: Sequence[str], m: int) -> str: def multisig_script(public_keys: Sequence[str], m: int) -> str:
n = len(public_keys) n = len(public_keys)
assert 1 <= m <= n <= 15, f'm {m}, n {n}' assert 1 <= m <= n <= 15, f'm {m}, n {n}'
op_m = bh2u(add_number_to_script(m)) return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG])
op_n = bh2u(add_number_to_script(n))
keylist = [push_script(k) for k in public_keys]
return op_m + ''.join(keylist) + op_n + opcodes.OP_CHECKMULTISIG.hex()
@ -639,7 +636,7 @@ class Transaction:
_type = txin.script_type _type = txin.script_type
if not cls.is_segwit_input(txin): if not cls.is_segwit_input(txin):
return '00' return construct_witness([])
if _type in ('address', 'unknown') and estimate_size: if _type in ('address', 'unknown') and estimate_size:
_type = cls.guess_txintype_from_address(txin.address) _type = cls.guess_txintype_from_address(txin.address)
@ -648,9 +645,9 @@ class Transaction:
return construct_witness([sig_list[0], pubkeys[0]]) return construct_witness([sig_list[0], pubkeys[0]])
elif _type in ['p2wsh', 'p2wsh-p2sh']: elif _type in ['p2wsh', 'p2wsh-p2sh']:
witness_script = multisig_script(pubkeys, txin.num_sig) witness_script = multisig_script(pubkeys, txin.num_sig)
return construct_witness([0] + sig_list + [witness_script]) return construct_witness([0, *sig_list, witness_script])
elif _type in ['p2pk', 'p2pkh', 'p2sh']: elif _type in ['p2pk', 'p2pkh', 'p2sh']:
return '00' return construct_witness([])
raise UnknownTxinType(f'cannot construct witness for txin_type: {_type}') raise UnknownTxinType(f'cannot construct witness for txin_type: {_type}')
@classmethod @classmethod
@ -702,38 +699,34 @@ class Transaction:
assert isinstance(txin, PartialTxInput) assert isinstance(txin, PartialTxInput)
if txin.is_p2sh_segwit() and txin.redeem_script: if txin.is_p2sh_segwit() and txin.redeem_script:
return push_script(txin.redeem_script.hex()) return construct_script([txin.redeem_script])
if txin.is_native_segwit(): if txin.is_native_segwit():
return '' return ''
_type = txin.script_type _type = txin.script_type
pubkeys, sig_list = self.get_siglist(txin, estimate_size=estimate_size) pubkeys, sig_list = self.get_siglist(txin, estimate_size=estimate_size)
script = ''.join(push_script(x) for x in sig_list)
if _type in ('address', 'unknown') and estimate_size: if _type in ('address', 'unknown') and estimate_size:
_type = self.guess_txintype_from_address(txin.address) _type = self.guess_txintype_from_address(txin.address)
if _type == 'p2pk': if _type == 'p2pk':
return script return construct_script([sig_list[0]])
elif _type == 'p2sh': elif _type == 'p2sh':
# put op_0 before script # put op_0 before script
script = '00' + script
redeem_script = multisig_script(pubkeys, txin.num_sig) redeem_script = multisig_script(pubkeys, txin.num_sig)
script += push_script(redeem_script) return construct_script([0, *sig_list, redeem_script])
return script
elif _type == 'p2pkh': elif _type == 'p2pkh':
script += push_script(pubkeys[0]) return construct_script([sig_list[0], pubkeys[0]])
return script
elif _type in ['p2wpkh', 'p2wsh']: elif _type in ['p2wpkh', 'p2wsh']:
return '' return ''
elif _type == 'p2wpkh-p2sh': elif _type == 'p2wpkh-p2sh':
redeem_script = bitcoin.p2wpkh_nested_script(pubkeys[0]) redeem_script = bitcoin.p2wpkh_nested_script(pubkeys[0])
return push_script(redeem_script) return construct_script([redeem_script])
elif _type == 'p2wsh-p2sh': elif _type == 'p2wsh-p2sh':
if estimate_size: if estimate_size:
witness_script = '' witness_script = ''
else: else:
witness_script = self.get_preimage_script(txin) witness_script = self.get_preimage_script(txin)
redeem_script = bitcoin.p2wsh_nested_script(witness_script) redeem_script = bitcoin.p2wsh_nested_script(witness_script)
return push_script(redeem_script) return construct_script([redeem_script])
raise UnknownTxinType(f'cannot construct scriptSig for txin_type: {_type}') raise UnknownTxinType(f'cannot construct scriptSig for txin_type: {_type}')
@classmethod @classmethod

Loading…
Cancel
Save