1
0
mirror of https://github.com/bitcoin/bips.git synced 2024-11-19 09:50:06 +01:00
bitcoin-bips/bip-0352/bitcoin_utils.py
Sebastian Falbesoner 2a99b8f925
BIP-352: use own ripemd160 for reference implementation (#1616)
On some operating systems, Python doesn't provide the expected ripemd160
implementation anymore, so the reference implementation fails to start.
E.g. in Ubuntu 22.04:

----------------------------------------------------------------------------------------------
$ ./reference.py send_and_receive_test_vectors.json
Simple send: two inputs
Traceback (most recent call last):
  File "/usr/lib/python3.10/hashlib.py", line 160, in __hash_new
    return _hashlib.new(name, data, **kwargs)
ValueError: [digital envelope routines] unsupported

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/thestack/bips/bip-0352/./reference.py", line 228, in <module>
    pubkey = get_pubkey_from_input(vin)
  File "/home/thestack/bips/bip-0352/./reference.py", line 46, in get_pubkey_from_input
    pubkey_hash = hash160(pubkey_bytes)
  File "/home/thestack/bips/bip-0352/bitcoin_utils.py", line 130, in hash160
    return hashlib.new("ripemd160", hashlib.sha256(s).digest()).digest()
  File "/usr/lib/python3.10/hashlib.py", line 166, in __hash_new
    return __get_builtin_constructor(name)(data)
  File "/usr/lib/python3.10/hashlib.py", line 123, in __get_builtin_constructor
    raise ValueError('unsupported hash type ' + name)
ValueError: unsupported hash type ripemd160
----------------------------------------------------------------------------------------------

Fix this by providing a manual implementation, taken from the functional test framework
of Bitcoin Core. See corresponding issue https://github.com/bitcoin/bitcoin/issues/23710 and
PR https://github.com/bitcoin/bitcoin/pull/23716
2024-06-29 07:08:49 -07:00

160 lines
3.9 KiB
Python

import hashlib
import struct
from io import BytesIO
from ripemd160 import ripemd160
from secp256k1 import ECKey
from typing import Union
def from_hex(hex_string):
"""Deserialize from a hex string representation (e.g. from RPC)"""
return BytesIO(bytes.fromhex(hex_string))
def ser_uint32(u: int) -> bytes:
return u.to_bytes(4, "big")
def ser_uint256(u):
return u.to_bytes(32, 'little')
def deser_uint256(f):
return int.from_bytes(f.read(32), 'little')
def deser_txid(txid: str):
# recall that txids are serialized little-endian, but displayed big-endian
# this means when converting from a human readable hex txid, we need to first
# reverse it before deserializing it
dixt = "".join(map(str.__add__, txid[-2::-2], txid[-1::-2]))
return bytes.fromhex(dixt)
def deser_compact_size(f: BytesIO):
view = f.getbuffer()
nbytes = view.nbytes;
view.release()
if (nbytes == 0):
return 0 # end of stream
nit = struct.unpack("<B", f.read(1))[0]
if nit == 253:
nit = struct.unpack("<H", f.read(2))[0]
elif nit == 254:
nit = struct.unpack("<I", f.read(4))[0]
elif nit == 255:
nit = struct.unpack("<Q", f.read(8))[0]
return nit
def deser_string(f: BytesIO):
nit = deser_compact_size(f)
return f.read(nit)
def deser_string_vector(f: BytesIO):
nit = deser_compact_size(f)
r = []
for _ in range(nit):
t = deser_string(f)
r.append(t)
return r
class COutPoint:
__slots__ = ("hash", "n",)
def __init__(self, hash=b"", n=0,):
self.hash = hash
self.n = n
def serialize(self):
r = b""
r += self.hash
r += struct.pack("<I", self.n)
return r
def deserialize(self, f):
self.hash = f.read(32)
self.n = struct.unpack("<I", f.read(4))[0]
class VinInfo:
__slots__ = ("outpoint", "scriptSig", "txinwitness", "prevout", "private_key")
def __init__(self, outpoint=None, scriptSig=b"", txinwitness=None, prevout=b"", private_key=None):
if outpoint is None:
self.outpoint = COutPoint()
else:
self.outpoint = outpoint
if txinwitness is None:
self.txinwitness = CTxInWitness()
else:
self.txinwitness = txinwitness
if private_key is None:
self.private_key = ECKey()
else:
self.private_key = private_key
self.scriptSig = scriptSig
self.prevout = prevout
class CScriptWitness:
__slots__ = ("stack",)
def __init__(self):
# stack is a vector of strings
self.stack = []
def is_null(self):
if self.stack:
return False
return True
class CTxInWitness:
__slots__ = ("scriptWitness",)
def __init__(self):
self.scriptWitness = CScriptWitness()
def deserialize(self, f: BytesIO):
self.scriptWitness.stack = deser_string_vector(f)
return self
def is_null(self):
return self.scriptWitness.is_null()
def hash160(s: Union[bytes, bytearray]) -> bytes:
return ripemd160(hashlib.sha256(s).digest())
def is_p2tr(spk: bytes) -> bool:
if len(spk) != 34:
return False
# OP_1 OP_PUSHBYTES_32 <32 bytes>
return (spk[0] == 0x51) & (spk[1] == 0x20)
def is_p2wpkh(spk: bytes) -> bool:
if len(spk) != 22:
return False
# OP_0 OP_PUSHBYTES_20 <20 bytes>
return (spk[0] == 0x00) & (spk[1] == 0x14)
def is_p2sh(spk: bytes) -> bool:
if len(spk) != 23:
return False
# OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL
return (spk[0] == 0xA9) & (spk[1] == 0x14) & (spk[-1] == 0x87)
def is_p2pkh(spk: bytes) -> bool:
if len(spk) != 25:
return False
# OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
return (spk[0] == 0x76) & (spk[1] == 0xA9) & (spk[2] == 0x14) & (spk[-2] == 0x88) & (spk[-1] == 0xAC)