def asm(script): """Turns a script into a symbolic representation""" if isinstance(script, str): script = hex_to_bytes(script) else: script = copy(script) def read(n): nonlocal script data = script[:n] assert data or n == 0, 'EOF' script = script[n:] return data results = [] while script: byte = bytes_to_int(read(1)) op = OP(byte) if byte in range(1, 76): results.append(bytes_to_hex(read(byte))) elif byte == 76: bytes_to_read = bytes_to_int(read(1)) results.append(bytes_to_hex(read(bytes_to_read))) elif byte == 77: bytes_to_read = bytes_to_int(read(2)) results.append(bytes_to_hex(read(bytes_to_read))) elif byte == 78: bytes_to_read = bytes_to_int(read(4)) results.append(bytes_to_hex(read(bytes_to_read))) else: results.append(str(op)) return ' '.join(results)
def sign_schnorr(self, private: PrivateKey, aux: bytes = None) -> Schnorr: """https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#default-signing""" assert len(self.msg) == 32, 'The message must be 32 bytes long' if aux is None: aux = secrets.token_bytes(32) aux = aux.rjust(32, b'\x00') dprime = private.int() assert 0 < dprime < N, "Invalid private key" P = CURVE.G * dprime d = dprime if has_even_y(P) else CURVE.N - dprime t = bytewise_xor(int_to_bytes(d, 32), hashtag(b'BIP0340/aux', aux)) rand = hashtag(tag=b'BIP0340/nonce', x=t + P.compact() + self.msg) kprime = bytes_to_int(rand) % CURVE.N assert kprime != 0, "Zero nonce" R = CURVE.G * kprime k = kprime if has_even_y(R) else CURVE.N - kprime e = hashtag(tag=b'BIP0340/challenge', x=R.compact() + P.compact() + self.msg) e = bytes_to_int(e) % CURVE.N s = (k + e*d) % CURVE.N return Schnorr(R, s)
def __init__(self, inputs, outputs, version=b'\x01\x00\x00\x00', lock_time=b'\x00\x00\x00\x00'): self.inputs = inputs self.outputs = outputs assert len(version) == 4, 'Invalid Version' assert len(lock_time) == 4, 'Invalid lock time' self._version = version[::-1] self.version = bytes_to_int(self._version) self._lock_time = lock_time[::-1] self.lock_time = bytes_to_int(self._lock_time)
def from_seed(cls, seed: Union[bytes, str], addresstype='P2PKH') -> 'Xprv': if isinstance(seed, str): seed = hex_to_bytes(seed) assert 16 <= len(seed) <= 64, 'Seed should be between 128 and 512 bits' I = hmac.new(key=b"Bitcoin seed", msg=seed, digestmod=hashlib.sha512).digest() I_L, I_R = I[:32], I[32:] if bytes_to_int(I_L) == 0 or bytes_to_int(I_L) > CURVE.N: raise KeyDerivationError key, code = PrivateKey(I_L), I_R return cls(key, code, addresstype=addresstype)
def child(self, i: int) -> 'Xprv': hardened = i >= 1 << 31 if hardened: I = hmac.new(key=self.code, msg=self.keydata() + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() else: I = hmac.new(key=self.code, msg=self.key.to_public().encode(compressed=True) + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() I_L, I_R = bytes_to_int(I[:32]), I[32:] key = (I_L + self.key.int()) % CURVE.N if I_L >= CURVE.N or key == 0: return self.child(i + 1) ret_code = I_R if hardened: path = self.path + f'/{i - 2**31}h' else: path = self.path + f'/{i}' return Xprv(PrivateKey.from_int(key), ret_code, depth=self.depth + 1, i=i, parent=self.fingerprint(), path=path, addresstype=self.type.value)
def _verify_schnorr(self, signature: Schnorr, public: PublicKey) -> bool: try: P = Point.from_compact(int_to_bytes(public.x, 32)) except AssertionError as e: return False r = signature.R.x if r >= CURVE.P: return False if P.is_inf(): return None s = signature.s if s >= CURVE.N: return False e = hashtag(tag=b'BIP0340/challenge', x=signature.R.compact() + P.compact() + self.msg) e = bytes_to_int(e) % CURVE.N R = CURVE.G * s - P * e if R.is_inf(): return False if not has_even_y(R): return False if R.x != r: return False return True
def decode(cls, key: bytes) -> 'PublicKey': if len(key) == 32: # compact key with implicit y coordinate key = b'\x02' + key if key.startswith(b'\x04'): # uncompressed key assert len(key) == 65, 'An uncompressed public key must be 65 bytes long' x, y = bytes_to_int(key[1:33]), bytes_to_int(key[33:]) else: # compressed key assert len(key) == 33, 'A compressed public key must be 33 bytes long' x = bytes_to_int(key[1:]) root = modsqrt(CURVE.f(x), P) if key.startswith(b'\x03'): # odd root y = root if root % 2 == 1 else -root % P elif key.startswith(b'\x02'): # even root y = root if root % 2 == 0 else -root % P else: assert False, 'Wrong key format' return cls(Point(x, y))
def encode(bts: bytes) -> str: n = bytes_to_int(bts) leading_zero_bytes = len(bts) - len(bts.lstrip(b'\x00')) int_digits = [] while n: int_digits.append(int(n % BASE)) n //= BASE for _ in range(leading_zero_bytes): int_digits.append(0) return ''.join(ALPHABET[i] for i in reversed(int_digits))
def from_compact(cls, bts: bytes): assert len(bts) == 32, 'A compact Elliptic Curve Point representation is 32 bytes' x = bytes_to_int(bts) assert 0 < x < CURVE.P, "x not in 0..P-1" ysq = CURVE.f(x) y = pow(ysq, (CURVE.P + 1) // 4, CURVE.P) assert pow(y, 2, CURVE.P) == ysq return cls(x, y if y & 1 == 0 else CURVE.P - y)
def read_var_int(): """https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer""" byte = pop(1) if byte == b'\xfd': result = pop(2) elif byte == b'\xfe': result = pop(4) elif byte == b'\xff': result = pop(8) else: result = byte return bytes_to_int(result[::-1])
def verify_hash(self, hash, pubkey): if not (1 <= self.r < CURVE.N and 1 <= self.s < CURVE.N): return False e = bytes_to_int(hash) w = mulinv(self.s, CURVE.N) u1 = (e * w) % CURVE.N u2 = (self.r * w) % CURVE.N point = CURVE.G * u1 + pubkey.point * u2 return self.r % CURVE.N == point.x % CURVE.N
def sign_hash(self, hash): e = hex_to_int(hash) if isinstance(hash, str) else bytes_to_int(hash) r, s = 0, 0 while r == 0 or s == 0: k = secrets.randbelow(N) point = CURVE.G * k r = point.x % N inv_k = mulinv(k, N) s = (inv_k * (e + r * self.int())) % N return Signature(r=r, s=s)
def deserialize(cls, bts: bytes) -> 'ExtendedKey': def read(n): nonlocal bts data, bts = bts[:n], bts[n:] return data net = read(4) is_private = net in network('extended_prv').values() is_public = net in network('extended_pub').values() assert is_public ^ is_private, f'Invalid network bytes : {bytes_to_hex(net)}' address_lookup = { val: key for key, val in (network('extended_prv') if is_private else network('extended_pub')).items() } constructor = Xprv if is_private else Xpub depth = bytes_to_int(read(1)) assert depth in range(256), f'Invalid depth : {depth}' fingerprint = read(4) i = bytes_to_int(read(4)) if depth == 0: i = None path = None else: ih = f'{i}' if i < 2**31 else f"{i - 2**31}h" path = '/'.join([constructor.root_path] + ['x' for _ in range(depth - 1)] + [ih]) code = read(32) key = read(33) key = PrivateKey(key) if is_private else PublicKey.decode(key) assert not bts, 'Leftover bytes' return constructor(key, code, depth=depth, i=i, parent=fingerprint, path=path, addresstype=address_lookup[net])
def decode(cls, bts): data = deque(bts) lead = data.popleft() == 0x30 assert lead, f'Invalid leading byte: 0x{lead:x}' # ASN1 SEQUENCE sequence_length = data.popleft() assert sequence_length <= 70, f'Invalid Sequence length: {sequence_length}' lead = data.popleft() assert lead == 0x02, f'Invalid r leading byte: 0x{lead:x}' # 0x02 byte before r len_r = data.popleft() assert len_r <= 33, f'Invalid r length: {len_r}' bts = bytes(data) r, data = bytes_to_int(bts[:len_r]), deque(bts[len_r:]) lead = data.popleft() assert lead == 0x02, f'Invalid s leading byte: 0x{lead:x}' # 0x02 byte before s len_s = data.popleft() assert len_s <= 33, f'Invalid s length: {len_s}' bts = bytes(data) s, rest = bytes_to_int(bts[:len_s]), bts[len_s:] assert len(rest) == 0, f'{len(rest)} leftover bytes' return cls(r, s)
def __init__(self, output, index, script, sequence=b'\xff\xff\xff\xff', witness=None, referenced_tx=None): # Parameters should be bytes as transmitted i.e reversed assert isinstance(output, bytes) and len(output) == 32 self.output = output[::-1] # referenced tx hash self.index = index if isinstance(index, int) else bytes_to_int(index[::-1]) assert self.index <= 0xffffffff self.script = script self.sequence = sequence[::-1] self._referenced_tx = referenced_tx self._referenced_output = None self.witness = witness self._parent = None self.tx_index = None # index of this input in it's parent tx self.parent_id = None
def _receive(self, value: int): """Creates an output that sends to this address""" addr_type = self.type() output = Output(value=value, script=b'') if addr_type == ADDRESS.P2PKH: address = base58.decode(self.address).rjust(25, b'\x00') keyhash = address[1:-4] output.script = OP.DUP.byte + OP.HASH160.byte + push(keyhash) + OP.EQUALVERIFY.byte + OP.CHECKSIG.byte elif addr_type == ADDRESS.P2SH: address = base58.decode(self.address).rjust(25, b'\x00') scripthash = address[1:-4] output.script = OP.HASH160.byte + push(scripthash) + OP.EQUAL.byte elif addr_type in (ADDRESS.P2WPKH, ADDRESS.P2WSH): witness_version, witness_program = bech32.decode(network('hrp'), self.address) output.script = OP(bytes_to_int(witness_byte(witness_version))).byte + push(bytes(witness_program)) else: raise ValidationError(f"Cannot create output of type {addr_type}") return output
def value(self): return bytes_to_int(self._value)
def __init__(self, bts): assert bytes_to_int(bts) < N, 'Key larger than Curve Order' super().__init__(bts)
def OP_PUSHDATA4(self): bytes_to_push = bytes_to_int(self.read(4)) self.push(self.read(bytes_to_push))
def decode(cls, bts: bytes) -> 'Schnorr': assert len(bts) == 64, 'A Schnorr signature must be 64 bytes long' R = Point.from_compact(bts[:32]) s = bytes_to_int(bts[32:]) return cls(R, s)
def sequence(self): return bytes_to_int(self._sequence)
def index(self): return bytes_to_int(self._index)
def step(self): """Executes one script operation""" byte = bytes_to_int(self.read(1)) opcode = OP(byte) self.op(opcode)