mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-28 16:01:30 +00:00
bitcoin: implement construct_script and use it
This commit is contained in:
parent
89bd520185
commit
4c7a92f39c
4 changed files with 136 additions and 73 deletions
|
@ -28,7 +28,7 @@ from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence
|
|||
import 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 segwit_addr
|
||||
from . import constants
|
||||
|
@ -307,10 +307,30 @@ def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
|
|||
item = script_num_to_hex(item)
|
||||
elif isinstance(item, (bytes, bytearray)):
|
||||
item = bh2u(item)
|
||||
else:
|
||||
assert is_hex_str(item)
|
||||
witness += witness_push(item)
|
||||
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:
|
||||
"""Returns feerate in sat/kbyte."""
|
||||
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)
|
||||
|
||||
def p2wpkh_nested_script(pubkey: str) -> str:
|
||||
pkh = bh2u(hash_160(bfh(pubkey)))
|
||||
return '00' + push_script(pkh)
|
||||
pkh = hash_160(bfh(pubkey))
|
||||
return construct_script([0, pkh])
|
||||
|
||||
def p2wsh_nested_script(witness_script: str) -> str:
|
||||
wsh = bh2u(sha256(bfh(witness_script)))
|
||||
return '00' + push_script(wsh)
|
||||
wsh = sha256(bfh(witness_script))
|
||||
return construct_script([0, wsh])
|
||||
|
||||
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
|
||||
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 not (0 <= witver <= 16):
|
||||
raise BitcoinException(f'impossible witness version: {witver}')
|
||||
script = bh2u(add_number_to_script(witver))
|
||||
script += push_script(bh2u(bytes(witprog)))
|
||||
return script
|
||||
return construct_script([witver, bytes(witprog)])
|
||||
addrtype, hash_160_ = b58_address_to_hash160(addr)
|
||||
if addrtype == net.ADDRTYPE_P2PKH:
|
||||
script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_))
|
||||
elif addrtype == net.ADDRTYPE_P2SH:
|
||||
script = opcodes.OP_HASH160.hex()
|
||||
script += push_script(bh2u(hash_160_))
|
||||
script += opcodes.OP_EQUAL.hex()
|
||||
script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
|
||||
else:
|
||||
raise BitcoinException(f'unknown address type: {addrtype}')
|
||||
return script
|
||||
|
@ -493,13 +509,16 @@ def script_to_scripthash(script: str) -> str:
|
|||
return bh2u(bytes(reversed(h)))
|
||||
|
||||
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:
|
||||
script = bytes([opcodes.OP_DUP, opcodes.OP_HASH160]).hex()
|
||||
script += push_script(pubkey_hash160)
|
||||
script += bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]).hex()
|
||||
return script
|
||||
return construct_script([
|
||||
opcodes.OP_DUP,
|
||||
opcodes.OP_HASH160,
|
||||
pubkey_hash160,
|
||||
opcodes.OP_EQUALVERIFY,
|
||||
opcodes.OP_CHECKSIG
|
||||
])
|
||||
|
||||
|
||||
__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
|
|
@ -31,8 +31,8 @@ from PyQt5.QtGui import QFontMetrics, QFont
|
|||
|
||||
from electrum import bitcoin
|
||||
from electrum.util import bfh, maybe_extract_bolt11_invoice
|
||||
from electrum.transaction import push_script, PartialTxOutput
|
||||
from electrum.bitcoin import opcodes
|
||||
from electrum.transaction import PartialTxOutput
|
||||
from electrum.bitcoin import opcodes, construct_script
|
||||
from electrum.logging import Logger
|
||||
from electrum.lnaddr import LnDecodeException
|
||||
|
||||
|
@ -111,11 +111,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||
for word in x.split():
|
||||
if word[0:3] == 'OP_':
|
||||
opcode_int = opcodes[word]
|
||||
assert opcode_int < 256 # opcode is single-byte
|
||||
script += bitcoin.int_to_hex(opcode_int)
|
||||
script += construct_script([opcode_int])
|
||||
else:
|
||||
bfh(word) # to test it is hex data
|
||||
script += push_script(word)
|
||||
script += construct_script([word])
|
||||
return script
|
||||
|
||||
def parse_amount(self, x):
|
||||
|
|
|
@ -19,7 +19,8 @@ from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOut
|
|||
PartialTxOutput, opcodes, TxOutput)
|
||||
from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number
|
||||
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 .i18n import _
|
||||
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(revocationpubkey) is bytes
|
||||
assert type(local_delayedpubkey) is bytes
|
||||
script = bytes([opcodes.OP_IF]) \
|
||||
+ bfh(push_script(bh2u(revocationpubkey))) \
|
||||
+ bytes([opcodes.OP_ELSE]) \
|
||||
+ bitcoin.add_number_to_script(to_self_delay) \
|
||||
+ bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) \
|
||||
+ bfh(push_script(bh2u(local_delayedpubkey))) \
|
||||
+ bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG])
|
||||
script = bfh(construct_script([
|
||||
opcodes.OP_IF,
|
||||
revocationpubkey,
|
||||
opcodes.OP_ELSE,
|
||||
to_self_delay,
|
||||
opcodes.OP_CHECKSEQUENCEVERIFY,
|
||||
opcodes.OP_DROP,
|
||||
local_delayedpubkey,
|
||||
opcodes.OP_ENDIF,
|
||||
opcodes.OP_CHECKSIG,
|
||||
]))
|
||||
|
||||
p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script))
|
||||
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(local_htlcpubkey) 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))))\
|
||||
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \
|
||||
+ bfh(push_script(bh2u(remote_htlcpubkey)))\
|
||||
+ bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\
|
||||
+ bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\
|
||||
+ bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\
|
||||
+ bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
|
||||
script = bfh(construct_script([
|
||||
opcodes.OP_DUP,
|
||||
opcodes.OP_HASH160,
|
||||
bitcoin.hash_160(revocation_pubkey),
|
||||
opcodes.OP_EQUAL,
|
||||
opcodes.OP_IF,
|
||||
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,
|
||||
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(cltv_expiry) is int
|
||||
|
||||
return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \
|
||||
+ bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \
|
||||
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \
|
||||
+ bfh(push_script(bh2u(remote_htlcpubkey))) \
|
||||
+ bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \
|
||||
+ bitcoin.add_number_to_script(32) \
|
||||
+ bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \
|
||||
+ bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \
|
||||
+ bytes([opcodes.OP_EQUALVERIFY]) \
|
||||
+ bitcoin.add_number_to_script(2) \
|
||||
+ bytes([opcodes.OP_SWAP]) \
|
||||
+ bfh(push_script(bh2u(local_htlcpubkey))) \
|
||||
+ bitcoin.add_number_to_script(2) \
|
||||
+ bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \
|
||||
+ bitcoin.add_number_to_script(cltv_expiry) \
|
||||
+ bytes([opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
|
||||
script = bfh(construct_script([
|
||||
opcodes.OP_DUP,
|
||||
opcodes.OP_HASH160,
|
||||
bitcoin.hash_160(revocation_pubkey),
|
||||
opcodes.OP_EQUAL,
|
||||
opcodes.OP_IF,
|
||||
opcodes.OP_CHECKSIG,
|
||||
opcodes.OP_ELSE,
|
||||
remote_htlcpubkey,
|
||||
opcodes.OP_SWAP,
|
||||
opcodes.OP_SIZE,
|
||||
32,
|
||||
opcodes.OP_EQUAL,
|
||||
opcodes.OP_IF,
|
||||
opcodes.OP_HASH160,
|
||||
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,
|
||||
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(
|
||||
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) \
|
||||
+ bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG])
|
||||
return local_script
|
||||
script = bfh(construct_script([
|
||||
opcodes.OP_IF,
|
||||
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(
|
||||
revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:
|
||||
|
|
|
@ -48,7 +48,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
|||
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
|
||||
int_to_hex, push_script, b58_address_to_hash160,
|
||||
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 .logging import get_logger
|
||||
|
||||
|
@ -500,10 +500,7 @@ def parse_output(vds: BCDataStream) -> TxOutput:
|
|||
def multisig_script(public_keys: Sequence[str], m: int) -> str:
|
||||
n = len(public_keys)
|
||||
assert 1 <= m <= n <= 15, f'm {m}, n {n}'
|
||||
op_m = bh2u(add_number_to_script(m))
|
||||
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()
|
||||
return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG])
|
||||
|
||||
|
||||
|
||||
|
@ -639,7 +636,7 @@ class Transaction:
|
|||
|
||||
_type = txin.script_type
|
||||
if not cls.is_segwit_input(txin):
|
||||
return '00'
|
||||
return construct_witness([])
|
||||
|
||||
if _type in ('address', 'unknown') and estimate_size:
|
||||
_type = cls.guess_txintype_from_address(txin.address)
|
||||
|
@ -648,9 +645,9 @@ class Transaction:
|
|||
return construct_witness([sig_list[0], pubkeys[0]])
|
||||
elif _type in ['p2wsh', 'p2wsh-p2sh']:
|
||||
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']:
|
||||
return '00'
|
||||
return construct_witness([])
|
||||
raise UnknownTxinType(f'cannot construct witness for txin_type: {_type}')
|
||||
|
||||
@classmethod
|
||||
|
@ -702,38 +699,34 @@ class Transaction:
|
|||
assert isinstance(txin, PartialTxInput)
|
||||
|
||||
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():
|
||||
return ''
|
||||
|
||||
_type = txin.script_type
|
||||
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:
|
||||
_type = self.guess_txintype_from_address(txin.address)
|
||||
if _type == 'p2pk':
|
||||
return script
|
||||
return construct_script([sig_list[0]])
|
||||
elif _type == 'p2sh':
|
||||
# put op_0 before script
|
||||
script = '00' + script
|
||||
redeem_script = multisig_script(pubkeys, txin.num_sig)
|
||||
script += push_script(redeem_script)
|
||||
return script
|
||||
return construct_script([0, *sig_list, redeem_script])
|
||||
elif _type == 'p2pkh':
|
||||
script += push_script(pubkeys[0])
|
||||
return script
|
||||
return construct_script([sig_list[0], pubkeys[0]])
|
||||
elif _type in ['p2wpkh', 'p2wsh']:
|
||||
return ''
|
||||
elif _type == 'p2wpkh-p2sh':
|
||||
redeem_script = bitcoin.p2wpkh_nested_script(pubkeys[0])
|
||||
return push_script(redeem_script)
|
||||
return construct_script([redeem_script])
|
||||
elif _type == 'p2wsh-p2sh':
|
||||
if estimate_size:
|
||||
witness_script = ''
|
||||
else:
|
||||
witness_script = self.get_preimage_script(txin)
|
||||
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}')
|
||||
|
||||
@classmethod
|
||||
|
|
Loading…
Add table
Reference in a new issue