def parse_witness(vds, txin, full_parse: bool): n = vds.read_compact_size() if n == 0: txin['witness'] = '00' return if n == 0xffffffff: txin['value'] = vds.read_uint64() txin['witness_version'] = vds.read_uint16() n = vds.read_compact_size() # now 'n' is the number of items in the witness w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n)) txin['witness'] = construct_witness(w) if not full_parse: return try: if txin.get('witness_version', 0) != 0: raise UnknownTxinType() if txin['type'] == 'coinbase': pass elif txin['type'] == 'address': pass elif txin['type'] == 'p2wsh-p2sh' or n > 2: witness_script_unsanitized = w[ -1] # for partial multisig txn, this has x_pubkeys try: m, n, x_pubkeys, pubkeys, witness_script = parse_redeemScript_multisig( bfh(witness_script_unsanitized)) except NotRecognizedRedeemScript: raise UnknownTxinType() txin['signatures'] = parse_sig(w[1:-1]) txin['num_sig'] = m txin['x_pubkeys'] = x_pubkeys txin['pubkeys'] = pubkeys txin['witness_script'] = witness_script if not txin.get('scriptSig'): # native segwit script txin['type'] = 'p2wsh' txin['address'] = bitcoin.script_to_p2wsh(witness_script) elif txin['type'] == 'p2wpkh-p2sh' or n == 2: txin['num_sig'] = 1 txin['x_pubkeys'] = [w[1]] txin['pubkeys'] = [safe_parse_pubkey(w[1])] txin['signatures'] = parse_sig([w[0]]) if not txin.get('scriptSig'): # native segwit script txin['type'] = 'p2wpkh' txin['address'] = bitcoin.public_key_to_p2wpkh( bfh(txin['pubkeys'][0])) else: raise UnknownTxinType() except UnknownTxinType: txin['type'] = 'unknown' except BaseException: txin['type'] = 'unknown' _logger.exception(f"failed to parse witness {txin.get('witness')}")
def txid(self): self.deserialize() all_segwit = all(self.is_segwit_input(x) for x in self.inputs()) if not all_segwit and not self.is_complete(): return None ser = self.serialize_to_network(witness=False) return bh2u(sha256d(bfh(ser))[::-1])
def _calc_bip143_shared_txdigest_fields( self) -> BIP143SharedTxDigestFields: inputs = self.inputs() outputs = self.outputs() hashPrevouts = bh2u( sha256d( bfh(''.join(self.serialize_outpoint(txin) for txin in inputs)))) hashSequence = bh2u( sha256d( bfh(''.join( int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) for txin in inputs)))) hashOutputs = bh2u( sha256d(bfh(''.join(self.serialize_output(o) for o in outputs)))) return BIP143SharedTxDigestFields(hashPrevouts=hashPrevouts, hashSequence=hashSequence, hashOutputs=hashOutputs)
def update_signatures(self, signatures: Sequence[str]): """Add new signatures to a transaction `signatures` is expected to be a list of sigs with signatures[i] intended for self._inputs[i]. This is used by the Trezor, KeepKey an Safe-T plugins. """ if self.is_complete(): return if len(self.inputs()) != len(signatures): raise Exception('expected {} signatures; got {}'.format( len(self.inputs()), len(signatures))) for i, txin in enumerate(self.inputs()): pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) sig = signatures[i] if sig in txin.get('signatures'): continue pre_hash = sha256d(bfh(self.serialize_preimage(i))) sig_string = ecc.sig_string_from_der_sig(bfh(sig[:-2])) for recid in range(4): try: public_key = ecc.ECPubkey.from_sig_string( sig_string, recid, pre_hash) except ecc.InvalidECPointException: # the point might not be on the curve for some recid values continue pubkey_hex = public_key.get_public_key_hex(compressed=True) if pubkey_hex in pubkeys: try: public_key.verify_message_hash(sig_string, pre_hash) except Exception: _logger.exception('') continue j = pubkeys.index(pubkey_hex) _logger.info(f"adding sig {i} {j} {pubkey_hex} {sig}") self.add_signature_to_txin(i, j, sig) break # redo raw self.raw = self.serialize()
def tx_from_str(txt: str) -> str: """Sanitizes tx-describing input (json or raw hex or base43) into raw hex transaction.""" assert isinstance(txt, str), f"txt must be str, not {type(txt)}" txt = txt.strip() if not txt: raise ValueError("empty string") # try hex try: bfh(txt) return txt except: pass # try base43 try: return base_decode(txt, length=None, base=43).hex() except: pass # try json import json tx_dict = json.loads(str(txt)) assert "hex" in tx_dict.keys() return tx_dict["hex"]
def sign_txin(self, txin_index, privkey_bytes, *, bip143_shared_txdigest_fields=None) -> str: pre_hash = sha256d( bfh( self.serialize_preimage( txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields ))) privkey = ecc.ECPrivkey(privkey_bytes) sig = privkey.sign_transaction(pre_hash) sig = bh2u(sig) + '01' return sig
def xpubkey_to_address(x_pubkey): if x_pubkey[0:2] == 'fd': address = bitcoin.script_to_address(x_pubkey[2:]) return x_pubkey, address if x_pubkey[0:2] in ['02', '03', '04']: pubkey = x_pubkey elif x_pubkey[0:2] == 'ff': xpub, s = parse_xpubkey(x_pubkey) pubkey = get_pubkey_from_xpub(xpub, s) else: raise BitcoinException("Cannot parse pubkey. prefix: {}".format( x_pubkey[0:2])) if pubkey: address = public_key_to_p2pkh(bfh(pubkey)) return pubkey, address
def get_preimage_script(self, txin): preimage_script = txin.get('preimage_script', None) if preimage_script is not None: return preimage_script pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) if txin['type'] == 'p2pkh': return bitcoin.address_to_script(txin['address']) elif txin['type'] in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: return multisig_script(pubkeys, txin['num_sig']) elif txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: pubkey = pubkeys[0] pkh = bh2u(hash_160(bfh(pubkey))) return '76a9' + push_script(pkh) + '88ac' elif txin['type'] == 'p2pk': pubkey = pubkeys[0] return bitcoin.public_key_to_p2pk_script(pubkey) else: raise TypeError('Unknown txin type', txin['type'])
def parse_xpubkey(pubkey): # type + xpub + derivation assert pubkey[0:2] == 'ff' pk = bfh(pubkey) # xpub: pk = pk[1:] xkey = bitcoin.EncodeBase58Check(pk[0:78]) # derivation: dd = pk[78:] s = [] while dd: # 2 bytes for derivation path index n = int.from_bytes(dd[0:2], byteorder="little") dd = dd[2:] # in case of overflow, drop these 2 bytes; and use next 4 bytes instead if n == 0xffff: n = int.from_bytes(dd[0:4], byteorder="little") dd = dd[4:] s.append(n) assert len(s) == 2 return xkey, s
def deserialize(raw: str, force_full_parse=False) -> dict: raw_bytes = bfh(raw) d = {} if raw_bytes[:5] == PARTIAL_TXN_HEADER_MAGIC: d['partial'] = is_partial = True partial_format_version = raw_bytes[5] if partial_format_version != 0: raise SerializationError( 'unknown tx partial serialization format version: {}'.format( partial_format_version)) raw_bytes = raw_bytes[6:] else: d['partial'] = is_partial = False full_parse = force_full_parse or is_partial vds = BCDataStream() vds.write(raw_bytes) d['version'] = vds.read_int32() n_vin = vds.read_compact_size() is_segwit = (n_vin == 0) if is_segwit: marker = vds.read_bytes(1) if marker != b'\x01': raise ValueError('invalid txn marker byte: {}'.format(marker)) n_vin = vds.read_compact_size() d['segwit_ser'] = is_segwit d['inputs'] = [ parse_input(vds, full_parse=full_parse) for i in range(n_vin) ] n_vout = vds.read_compact_size() d['outputs'] = [parse_output(vds, i) for i in range(n_vout)] if is_segwit: for i in range(n_vin): txin = d['inputs'][i] parse_witness(vds, txin, full_parse=full_parse) d['lockTime'] = vds.read_uint32() if vds.can_read_more(): raise SerializationError('extra junk at the end') return d
def parse_redeemScript_multisig(redeem_script: bytes): try: dec2 = [x for x in script_GetOp(redeem_script)] except MalformedBitcoinScript: raise NotRecognizedRedeemScript() try: m = dec2[0][0] - opcodes.OP_1 + 1 n = dec2[-2][0] - opcodes.OP_1 + 1 except IndexError: raise NotRecognizedRedeemScript() op_m = opcodes.OP_1 + m - 1 op_n = opcodes.OP_1 + n - 1 match_multisig = [op_m] + [OPPushDataGeneric] * n + [ op_n, opcodes.OP_CHECKMULTISIG ] if not match_decoded(dec2, match_multisig): raise NotRecognizedRedeemScript() x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys] redeem_script2 = bfh(multisig_script(x_pubkeys, m)) if redeem_script2 != redeem_script: raise NotRecognizedRedeemScript() redeem_script_sanitized = multisig_script(pubkeys, m) return m, n, x_pubkeys, pubkeys, redeem_script_sanitized
def parse_scriptSig(d, _bytes): try: decoded = [x for x in script_GetOp(_bytes)] except Exception as e: # coinbase transactions raise an exception _logger.info( f"parse_scriptSig: cannot find address in input script (coinbase?) {bh2u(_bytes)}" ) return match = [OPPushDataGeneric] if match_decoded(decoded, match): item = decoded[0][1] if item[0] == 0: # segwit embedded into p2sh # witness version 0 d['address'] = bitcoin.hash160_to_p2sh(hash_160(item)) if len(item) == 22: d['type'] = 'p2wpkh-p2sh' elif len(item) == 34: d['type'] = 'p2wsh-p2sh' else: _logger.info(f"unrecognized txin type {bh2u(item)}") elif opcodes.OP_1 <= item[0] <= opcodes.OP_16: # segwit embedded into p2sh # witness version 1-16 pass else: # assert item[0] == 0x30 # pay-to-pubkey d['type'] = 'p2pk' d['address'] = "(pubkey)" d['signatures'] = [bh2u(item)] d['num_sig'] = 1 d['x_pubkeys'] = ["(pubkey)"] d['pubkeys'] = ["(pubkey)"] return # p2pkh TxIn transactions push a signature # (71-73 bytes) and then their public key # (33 or 65 bytes) onto the stack: match = [OPPushDataGeneric, OPPushDataGeneric] if match_decoded(decoded, match): sig = bh2u(decoded[0][1]) x_pubkey = bh2u(decoded[1][1]) try: signatures = parse_sig([sig]) pubkey, address = xpubkey_to_address(x_pubkey) except: _logger.info( f"parse_scriptSig: cannot find address in input script (p2pkh?) {bh2u(_bytes)}" ) return d['type'] = 'p2pkh' d['signatures'] = signatures d['x_pubkeys'] = [x_pubkey] d['num_sig'] = 1 d['pubkeys'] = [pubkey] d['address'] = address return # p2sh transaction, m of n match = [opcodes.OP_0] + [OPPushDataGeneric] * (len(decoded) - 1) if match_decoded(decoded, match): x_sig = [bh2u(x[1]) for x in decoded[1:-1]] redeem_script_unsanitized = decoded[-1][ 1] # for partial multisig txn, this has x_pubkeys try: m, n, x_pubkeys, pubkeys, redeem_script = parse_redeemScript_multisig( redeem_script_unsanitized) except NotRecognizedRedeemScript: _logger.info( f"parse_scriptSig: cannot find address in input script (p2sh?) {bh2u(_bytes)}" ) # we could still guess: # d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1])) return # write result in d d['type'] = 'p2sh' d['num_sig'] = m d['signatures'] = parse_sig(x_sig) d['x_pubkeys'] = x_pubkeys d['pubkeys'] = pubkeys d['redeem_script'] = redeem_script d['address'] = hash160_to_p2sh(hash_160(bfh(redeem_script))) return # custom partial format for imported addresses match = [opcodes.OP_INVALIDOPCODE, opcodes.OP_0, OPPushDataGeneric] if match_decoded(decoded, match): x_pubkey = bh2u(decoded[2][1]) pubkey, address = xpubkey_to_address(x_pubkey) d['type'] = 'address' d['address'] = address d['num_sig'] = 1 d['x_pubkeys'] = [x_pubkey] d['pubkeys'] = None # get_sorted_pubkeys will populate this d['signatures'] = [None] return _logger.info( f"parse_scriptSig: cannot find address in input script (unknown) {bh2u(_bytes)}" )
def wtxid(self): self.deserialize() if not self.is_complete(): return None ser = self.serialize_to_network(witness=True) return bh2u(sha256d(bfh(ser))[::-1])
def serialize_outpoint(self, txin): return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex( txin['prevout_n'], 4)