def verify_nonownership( proof: bytes, script_pubkey: bytes, commitment_data: bytes | None, keychain: Keychain, coin: CoinInfo, ) -> bool: try: r = utils.BufferReader(proof) if r.read_memoryview(4) != _VERSION_MAGIC: raise wire.DataError("Unknown format of proof of ownership") flags = r.get() if flags & 0b1111_1110: raise wire.DataError("Unknown flags in proof of ownership") # Determine whether our ownership ID appears in the proof. id_count = read_bitcoin_varint(r) ownership_id = get_identifier(script_pubkey, keychain) not_owned = True for _ in range(id_count): if utils.consteq(ownership_id, r.read_memoryview(_OWNERSHIP_ID_LEN)): not_owned = False # Verify the BIP-322 SignatureProof. proof_body = proof[:r.offset] sighash = hashlib.sha256(proof_body) sighash.update(script_pubkey) if commitment_data: sighash.update(commitment_data) script_sig, witness = read_bip322_signature_proof(r) # We don't call verifier.ensure_hash_type() to avoid possible compatibility # issues between implementations, because the hash type doesn't influence # the digest and the value to use is not defined in BIP-322. verifier = SignatureVerifier(script_pubkey, script_sig, witness, coin) verifier.verify(sighash.digest()) except (ValueError, EOFError): raise wire.DataError("Invalid proof of ownership") return not_owned
def generate_proof( node: bip32.HDNode, script_type: InputScriptType, multisig: MultisigRedeemScriptType | None, coin: CoinInfo, user_confirmed: bool, ownership_ids: list[bytes], script_pubkey: bytes, commitment_data: bytes, ) -> tuple[bytes, bytes]: flags = 0 if user_confirmed: flags |= _FLAG_USER_CONFIRMED proof = utils.empty_bytearray(4 + 1 + 1 + len(ownership_ids) * _OWNERSHIP_ID_LEN) write_bytes_fixed(proof, _VERSION_MAGIC, 4) write_uint8(proof, flags) write_bitcoin_varint(proof, len(ownership_ids)) for ownership_id in ownership_ids: write_bytes_fixed(proof, ownership_id, _OWNERSHIP_ID_LEN) sighash = HashWriter(sha256(proof)) write_bytes_prefixed(sighash, script_pubkey) write_bytes_prefixed(sighash, commitment_data) if script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): signature = common.ecdsa_sign(node, sighash.get_digest()) elif script_type == InputScriptType.SPENDTAPROOT: signature = common.bip340_sign(node, sighash.get_digest()) else: raise wire.DataError("Unsupported script type.") public_key = node.public_key() write_bip322_signature_proof(proof, script_type, multisig, coin, public_key, signature) return proof, signature
def address_from_public_key(pubkey: bytes) -> str: """Extracts public key from an address Ripple address is in format: <1-byte ripple flag> <20-bytes account id> <4-bytes dSHA-256 checksum> - 1-byte flag is 0x00 which is 'r' (Ripple uses its own base58 alphabet) - 20-bytes account id is a ripemd160(sha256(pubkey)) - checksum is first 4 bytes of double sha256(data) see https://developers.ripple.com/accounts.html#address-encoding """ """Returns the Ripple address created using base58""" h = sha256(pubkey).digest() h = ripemd160(h).digest() address = bytearray() address.append(0x00) # 'r' address.extend(h) return base58_ripple.encode_check(bytes(address))
async def sign_tx(ctx, msg: StellarSignTx): if msg.num_operations == 0: raise ProcessError("Stellar: At least one operation is required") node = await seed.derive_node(ctx, msg.address_n, consts.STELLAR_CURVE) pubkey = seed.remove_ed25519_prefix(node.public_key()) w = bytearray() await _init(ctx, w, pubkey, msg) _timebounds(w, msg.timebounds_start, msg.timebounds_end) await _memo(ctx, w, msg) await _operations(ctx, w, msg.num_operations) await _final(ctx, w, msg) # sign digest = sha256(w).digest() signature = ed25519.sign(node.private_key(), digest) # Add the public key for verification that the right account was used for signing return StellarSignedTx(pubkey, signature)
async def confirm_manage_data_op(ctx: Context, op: StellarManageDataOp) -> None: from trezor.crypto.hashlib import sha256 if op.value: digest = sha256(op.value).digest() await confirm_properties( ctx, "op_data", "Set data", props=(("Key:", op.key), ("Value (SHA-256):", digest)), ) else: await confirm_metadata( ctx, "op_data", "Clear data", "Do you want to clear value key {}?", param=op.key, )
async def sign_tx(ctx, msg): pubkey, seckey = await _get_keys(ctx, msg) transaction = _update_raw_tx(msg.transaction, pubkey) try: await _require_confirm_by_type(ctx, transaction) except AttributeError: raise wire.DataError("The transaction has invalid asset data field") await layout.require_confirm_fee(ctx, transaction.amount, transaction.fee) txbytes = _get_transaction_bytes(transaction) txhash = HashWriter(sha256()) for field in txbytes: txhash.extend(field) digest = txhash.get_digest() signature = ed25519.sign(seckey, digest) return LiskSignedTx(signature=signature)
def from_cred_id(cred_id: bytes, rp_id_hash: bytes) -> Optional["Fido2Credential"]: if len(cred_id ) < _CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: return None key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", cred_id[0:4], b"Encryption key"]).key() iv = cred_id[4:16] ciphertext = cred_id[16:-16] tag = cred_id[-16:] ctx = chacha20poly1305(key, iv) ctx.auth(rp_id_hash) data = ctx.decrypt(ciphertext) if not utils.consteq(ctx.finish(), tag): return None try: data = cbor.decode(data) except Exception: return None if not isinstance(data, dict): return None cred = Fido2Credential() cred.rp_id = data.get(_CRED_ID_RP_ID, None) cred.rp_id_hash = rp_id_hash cred.rp_name = data.get(_CRED_ID_RP_NAME, None) cred.user_id = data.get(_CRED_ID_USER_ID, None) cred.user_name = data.get(_CRED_ID_USER_NAME, None) cred.user_display_name = data.get(_CRED_ID_USER_DISPLAY_NAME, None) cred._creation_time = data.get(_CRED_ID_CREATION_TIME, 0) cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False) cred.id = cred_id if (not cred.check_required_fields() or not cred.check_data_types() or hashlib.sha256(cred.rp_id).digest() != rp_id_hash): return None return cred
async def sign_tx(ctx, msg: StellarSignTx, keychain): await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) pubkey = seed.remove_ed25519_prefix(node.public_key()) if msg.num_operations == 0: raise ProcessError("Stellar: At least one operation is required") w = bytearray() await _init(ctx, w, pubkey, msg) await _timebounds(ctx, w, msg.timebounds_start, msg.timebounds_end) await _memo(ctx, w, msg) await _operations(ctx, w, msg.num_operations) await _final(ctx, w, msg) # sign digest = sha256(w).digest() signature = ed25519.sign(node.private_key(), digest) # Add the public key for verification that the right account was used for signing return StellarSignedTx(public_key=pubkey, signature=signature)
async def sign_tx(ctx, msg: TronSignTx): """Parse and sign TRX transaction""" validate(msg) address_n = msg.address_n or () node = await seed.derive_node(ctx, address_n) seckey = node.private_key() public_key = secp256k1.publickey(seckey, False) address = get_address_from_public_key(public_key[:65]) try: await _require_confirm_by_type(ctx, msg, address) except AttributeError: raise wire.DataError("The transaction has invalid asset data field") raw_data = serialize(msg, address) data_hash = sha256(raw_data).digest() signature = secp256k1.sign(seckey, data_hash, False) signature = signature[1:65] + bytes([~signature[0] & 0x01]) return TronSignedTx(signature=signature, serialized_tx=raw_data)
def __init__(self, signer: Signer, tx: SignTx | PrevTx) -> None: # Checksum of multisig inputs, used to validate change-output. self.multisig_fingerprint = MultisigFingerprintChecker() # Common prefix of input paths, used to validate change-output. self.wallet_path = WalletPathChecker() # h_tx_check is used to make sure that the inputs and outputs streamed in # different steps are the same every time, e.g. the ones streamed for approval # in Steps 1 and 2 and the ones streamed for signing legacy inputs in Step 4. self.h_tx_check = HashWriter(sha256()) # not a real tx hash # The digests of the inputs streamed for approval in Step 1. These are used to # ensure that the inputs streamed for verification in Step 3 are the same as # those in Step 1. self.h_inputs_check: bytes | None = None # BIP-0143 transaction hashing. self.sig_hasher = signer.create_sig_hasher(tx) # The minimum nSequence of all inputs. self.min_sequence = _SEQUENCE_FINAL
def input_derive_script( script_type: EnumTypeInputScriptType, multisig: Optional[MultisigRedeemScriptType], coin: CoinInfo, hash_type: int, pubkey: bytes, signature: bytes, ) -> bytes: if script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type) if script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if multisig is not None: # p2wsh in p2sh pubkeys = multisig_get_pubkeys(multisig) witness_script_hasher = utils.HashWriter(sha256()) write_output_script_multisig(witness_script_hasher, pubkeys, multisig.m) witness_script_hash = witness_script_hasher.get_digest() return input_script_p2wsh_in_p2sh(witness_script_hash) # p2wpkh in p2sh return input_script_p2wpkh_in_p2sh( common.ecdsa_hash_pubkey(pubkey, coin)) elif script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh return input_script_native_p2wpkh_or_p2wsh() elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig assert multisig is not None # checked in sanitize_tx_input signature_index = multisig_pubkey_index(multisig, pubkey) return input_script_multisig(multisig, signature, signature_index, hash_type, coin) else: raise wire.ProcessError("Invalid script type")
def input_derive_script(coin: coininfo.CoinInfo, i: TxInputType, pubkey: bytes, signature: bytes = None) -> bytes: if i.script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh return scripts.input_script_p2pkh_or_p2sh(pubkey, signature, get_hash_type(coin)) if i.script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if i.multisig: # p2wsh in p2sh pubkeys = multisig.multisig_get_pubkeys(i.multisig) witness_script_hasher = utils.HashWriter(sha256()) scripts.output_script_multisig(pubkeys, i.multisig.m, witness_script_hasher) witness_script_hash = witness_script_hasher.get_digest() return scripts.input_script_p2wsh_in_p2sh(witness_script_hash) # p2wpkh in p2sh return scripts.input_script_p2wpkh_in_p2sh( addresses.ecdsa_hash_pubkey(pubkey, coin)) elif i.script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh return scripts.input_script_native_p2wpkh_or_p2wsh() elif i.script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig signature_index = multisig.multisig_pubkey_index(i.multisig, pubkey) return scripts.input_script_multisig(i.multisig, signature, signature_index, get_hash_type(coin), coin) else: raise SigningError(FailureType.ProcessError, "Invalid script type")
async def sign_tx(ctx: wire.Context, msg: EosSignTx, keychain: Keychain) -> EosSignedTx: if msg.chain_id is None: raise wire.DataError("No chain id") if msg.header is None: raise wire.DataError("No header") if msg.num_actions is None or msg.num_actions == 0: raise wire.DataError("No actions") await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) sha = HashWriter(sha256()) await _init(ctx, sha, msg) await _actions(ctx, sha, msg.num_actions) writers.write_variant32(sha, 0) writers.write_bytes_unchecked(sha, bytearray(32)) digest = sha.get_digest() signature = secp256k1.sign(node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS) return EosSignedTx(signature=base58_encode("SIG_", "K1", signature))
async def step3_verify_inputs(self) -> None: # should come out the same as h_inputs, checked before continuing h_check = HashWriter(sha256()) for i in range(self.tx_info.tx.inputs_count): progress.advance() txi = await helpers.request_tx_input(self.tx_req, i, self.coin) writers.write_tx_input_check(h_check, txi) prev_amount, script_pubkey = await self.get_prevtx_output( txi.prev_hash, txi.prev_index) if prev_amount != txi.amount: raise wire.DataError("Invalid amount specified") if i in self.external: await self.verify_external_input(i, txi, script_pubkey) # check that the inputs were the same as those streamed for approval if h_check.get_digest() != self.h_inputs: raise wire.ProcessError("Transaction has changed during signing") # verify the signature of one SIGHASH_ALL input in each original transaction await self.verify_original_txs()
async def sign_tx(ctx, msg: EosSignTx, keychain): if msg.chain_id is None: raise wire.DataError("No chain id") if msg.header is None: raise wire.DataError("No header") if msg.num_actions is None or msg.num_actions == 0: raise wire.DataError("No actions") await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE) node = keychain.derive(msg.address_n) sha = HashWriter(sha256()) await _init(ctx, sha, msg) await _actions(ctx, sha, msg.num_actions) writers.write_variant32(sha, 0) writers.write_bytes(sha, bytearray(32)) digest = sha.get_digest() signature = secp256k1.sign(node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS) return EosSignedTx(signature[0], signature[1:33], signature[33:])
async def verify_message(ctx, msg): message = msg.message address = msg.address signature = msg.signature coin_name = msg.coin_name or 'Bitcoin' coin = coins.by_name(coin_name) await confirm_verify_message(ctx, message) digest = message_digest(coin, message) pubkey = secp256k1.verify_recover(signature, digest) if not pubkey: raise wire.FailureError(ProcessError, 'Invalid signature') raw_address = base58.decode_check(address) _, pkh = address_type.split(coin, raw_address) pkh2 = ripemd160(sha256(pubkey).digest()).digest() if pkh != pkh2: raise wire.FailureError(ProcessError, 'Invalid signature') return Success(message='Message verified')
async def step1_process_inputs(self) -> None: h_external_inputs_check = HashWriter(sha256()) for i in range(self.tx_info.tx.inputs_count): # STAGE_REQUEST_1_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin) script_pubkey = self.input_derive_script(txi) self.tx_info.add_input(txi, script_pubkey) if txi.script_type not in ( InputScriptType.SPENDTAPROOT, InputScriptType.EXTERNAL, ): self.taproot_only = False if input_is_segwit(txi): self.segwit.add(i) if input_is_external(txi): self.external.add(i) writers.write_tx_input_check(h_external_inputs_check, txi) await self.process_external_input(txi) else: await self.process_internal_input(txi) if txi.orig_hash: await self.process_original_input(txi, script_pubkey) self.tx_info.h_inputs_check = self.tx_info.get_tx_check_digest() self.h_external_inputs = h_external_inputs_check.get_digest() # Finalize original inputs. for orig in self.orig_txs: orig.h_inputs_check = orig.get_tx_check_digest() if orig.index != orig.tx.inputs_count: raise wire.ProcessError("Removal of original inputs is not supported.") orig.index = 0 # Reset counter for outputs.
async def process_unknown_action(ctx, w, action): checksum = HashWriter(sha256()) writers.write_variant32(checksum, action.unknown.data_size) checksum.extend(action.unknown.data_chunk) writers.write_bytes(w, action.unknown.data_chunk) bytes_left = action.unknown.data_size - len(action.unknown.data_chunk) while bytes_left != 0: action = await ctx.call(EosTxActionRequest(data_size=bytes_left), EosTxActionAck) if action.unknown is None: raise ValueError("Bad response. Unknown struct expected.") checksum.extend(action.unknown.data_chunk) writers.write_bytes(w, action.unknown.data_chunk) bytes_left -= len(action.unknown.data_chunk) if bytes_left < 0: raise ValueError("Bad response. Buffer overflow.") await layout.confirm_action_unknown(ctx, action.common, checksum.get_digest())
def test_vectors(self): vectors = [ ("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "sample", "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60" ), ("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", "sample", "2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3" ), ("0000000000000000000000000000000000000000000000000000000000000001", "Satoshi Nakamoto", "8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15" ), ("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "Satoshi Nakamoto", "33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90" ), ("f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", "Alan Turing", "525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1" ), ("0000000000000000000000000000000000000000000000000000000000000001", "All those moments will be lost in time, like tears in rain. Time to die...", "38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3" ), ("e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", "1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d" ), ] for key, msg, k in vectors: rng = rfc6979(unhexlify(key), sha256(msg).digest()) self.assertEqual(rng.next(), unhexlify(k))
def input_derive_script( txi: TxInputType, coin: CoinInfo, hash_type: int, pubkey: bytes, signature: Optional[bytes], ) -> bytes: if txi.script_type == InputScriptType.SPENDADDRESS: # p2pkh or p2sh return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type) if txi.script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if txi.multisig: # p2wsh in p2sh pubkeys = multisig_get_pubkeys(txi.multisig) witness_script_hasher = utils.HashWriter(sha256()) write_output_script_multisig(witness_script_hasher, pubkeys, txi.multisig.m) witness_script_hash = witness_script_hasher.get_digest() return input_script_p2wsh_in_p2sh(witness_script_hash) # p2wpkh in p2sh return input_script_p2wpkh_in_p2sh( common.ecdsa_hash_pubkey(pubkey, coin)) elif txi.script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh return input_script_native_p2wpkh_or_p2wsh() elif txi.script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig signature_index = multisig_pubkey_index(txi.multisig, pubkey) return input_script_multisig(txi.multisig, signature, signature_index, hash_type, coin) else: raise wire.ProcessError("Invalid script type")
async def check_tx_fee(tx: SignTx, root: bip32.HDNode): coin = coins.by_name(tx.coin_name) # h_first is used to make sure the inputs and outputs streamed in Phase 1 # are the same as in Phase 2. it is thus not required to fully hash the # tx, as the SignTx info is streamed only once h_first = HashWriter(sha256()) # not a real tx hash if coin.decred: hash143 = DecredPrefixHasher(tx) # pseudo bip143 prefix hashing tx_ser = TxRequestSerializedType() elif tx.overwintered: if tx.version == 3: hash143 = Zip143() # ZIP-0143 transaction hashing elif tx.version == 4: hash143 = Zip243() # ZIP-0243 transaction hashing else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) else: hash143 = Bip143() # BIP-0143 transaction hashing multifp = MultisigFingerprint() # control checksum of multisig inputs weight = TxWeightCalculator(tx.inputs_count, tx.outputs_count) total_in = 0 # sum of input amounts segwit_in = 0 # sum of segwit input amounts total_out = 0 # sum of output amounts change_out = 0 # change output amount wallet_path = [] # common prefix of input paths segwit = {} # dict of booleans stating if input is segwit # output structures txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() for i in range(tx.inputs_count): progress.advance() # STAGE_REQUEST_1_INPUT txi = await request_tx_input(tx_req, i) wallet_path = input_extract_wallet_path(txi, wallet_path) write_tx_input_check(h_first, txi) weight.add_input(txi) hash143.add_prevouts( txi) # all inputs are included (non-segwit as well) hash143.add_sequence(txi) if not address_n_matches_coin(txi.address_n, coin): await confirm_foreign_address(txi.address_n, coin) if txi.multisig: multifp.add(txi.multisig) if txi.script_type in ( InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): if not coin.segwit: raise SigningError(FailureType.DataError, "Segwit not enabled on this coin") if not txi.amount: raise SigningError(FailureType.DataError, "Segwit input without amount") segwit[i] = True segwit_in += txi.amount total_in += txi.amount elif txi.script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, ): if coin.force_bip143 or tx.overwintered: if not txi.amount: raise SigningError(FailureType.DataError, "Expected input with amount") segwit[i] = False segwit_in += txi.amount total_in += txi.amount else: segwit[i] = False total_in += await get_prevtx_output_value( coin, tx_req, txi.prev_hash, txi.prev_index) else: raise SigningError(FailureType.DataError, "Wrong input script type") if coin.decred: w_txi = empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash)) if i == 0: # serializing first input => prepend headers write_bytes(w_txi, get_tx_header(coin, tx)) write_tx_input_decred(w_txi, txi) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser if coin.decred: hash143.add_output_count(tx) for o in range(tx.outputs_count): # STAGE_REQUEST_3_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) weight.add_output(txo_bin.script_pubkey) if change_out == 0 and is_change(txo, wallet_path, segwit_in, multifp): # output is change and does not need confirmation change_out = txo.amount elif not await confirm_output(txo, coin): raise SigningError(FailureType.ActionCancelled, "Output cancelled") if coin.decred: if txo.decred_script_version is not None and txo.decred_script_version != 0: raise SigningError( FailureType.ActionCancelled, "Cannot send to output with script version != 0", ) txo_bin.decred_script_version = txo.decred_script_version w_txo_bin = empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey)) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser hash143.set_last_output_bytes(w_txo_bin) write_tx_output(h_first, txo_bin) hash143.add_output(txo_bin) total_out += txo_bin.amount fee = total_in - total_out if fee < 0: raise SigningError(FailureType.NotEnoughFunds, "Not enough funds") # fee > (coin.maxfee per byte * tx size) if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4): if not await confirm_feeoverthreshold(fee, coin): raise SigningError(FailureType.ActionCancelled, "Signing cancelled") if not await confirm_total(total_in - change_out, fee, coin): raise SigningError(FailureType.ActionCancelled, "Total cancelled") if coin.decred: hash143.add_locktime_expiry(tx) return h_first, hash143, segwit, total_in, wallet_path
async def sign_tx(tx: SignTx, root: bip32.HDNode): tx = sanitize_sign_tx(tx) progress.init(tx.inputs_count, tx.outputs_count) # Phase 1 h_first, hash143, segwit, authorized_in, wallet_path = await check_tx_fee( tx, root) # Phase 2 # - sign inputs # - check that nothing changed coin = coins.by_name(tx.coin_name) tx_ser = TxRequestSerializedType() txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() tx_req.serialized = None if coin.decred: prefix_hash = hash143.prefix_hash() for i_sign in range(tx.inputs_count): progress.advance() txi_sign = None key_sign = None key_sign_pub = None if segwit[i_sign]: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) is_segwit = (txi_sign.script_type == InputScriptType.SPENDWITNESS or txi_sign.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") input_check_wallet_path(txi_sign, wallet_path) key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub) w_txi = empty_bytearray(7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi, get_tx_header(coin, tx, True)) write_tx_input(w_txi, txi_sign) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser elif coin.force_bip143 or tx.overwintered: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) input_check_wallet_path(txi_sign, wallet_path) is_bip143 = (txi_sign.script_type == InputScriptType.SPENDADDRESS or txi_sign.script_type == InputScriptType.SPENDMULTISIG) if not is_bip143 or txi_sign.amount > authorized_in: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") authorized_in -= txi_sign.amount key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) signature = ecdsa_sign(key_sign, hash143_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(coin, tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser elif coin.decred: txi_sign = await request_tx_input(tx_req, i_sign) input_check_wallet_path(txi_sign, wallet_path) key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() if txi_sign.script_type == InputScriptType.SPENDMULTISIG: prev_pkscript = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: prev_pkscript = output_script_p2pkh( ecdsa_hash_pubkey(key_sign_pub, coin)) else: raise ValueError("Unknown input script type") h_witness = HashWriter(blake256()) write_uint32(h_witness, tx.version | DECRED_SERIALIZE_WITNESS_SIGNING) write_varint(h_witness, tx.inputs_count) for ii in range(tx.inputs_count): if ii == i_sign: write_varint(h_witness, len(prev_pkscript)) write_bytes(h_witness, prev_pkscript) else: write_varint(h_witness, 0) witness_hash = get_tx_hash(h_witness, double=coin.sign_hash_double, reverse=False) h_sign = HashWriter(blake256()) write_uint32(h_sign, DECRED_SIGHASHALL) write_bytes(h_sign, prefix_hash) write_bytes(h_sign, witness_hash) sig_hash = get_tx_hash(h_sign, double=coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray( 8 + 4 + len(hash143.get_last_output_bytes()) if i_sign == 0 else 0 + 16 + 4 + len(txi_sign.script_sig)) if i_sign == 0: write_bytes(w_txi_sign, hash143.get_last_output_bytes()) write_uint32(w_txi_sign, tx.lock_time) write_uint32(w_txi_sign, tx.expiry) write_varint(w_txi_sign, tx.inputs_count) write_tx_input_decred_witness(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser else: # hash of what we are signing with this input h_sign = HashWriter(sha256()) # same as h_first, checked before signing the digest h_second = HashWriter(sha256()) if tx.overwintered: write_uint32(h_sign, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(h_sign, tx.version_group_id) # nVersionGroupId else: write_uint32(h_sign, tx.version) # nVersion write_varint(h_sign, tx.inputs_count) for i in range(tx.inputs_count): # STAGE_REQUEST_4_INPUT txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) write_tx_input_check(h_second, txi) if i == i_sign: txi_sign = txi key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() # for the signing process the script_sig is equal # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH) if txi_sign.script_type == InputScriptType.SPENDMULTISIG: txi_sign.script_sig = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: txi_sign.script_sig = output_script_p2pkh( ecdsa_hash_pubkey(key_sign_pub, coin)) if coin.bip115: txi_sign.script_sig += script_replay_protection_bip115( txi_sign.prev_block_hash_bip115, txi_sign.prev_block_height_bip115, ) else: raise SigningError(FailureType.ProcessError, "Unknown transaction type") else: txi.script_sig = bytes() write_tx_input(h_sign, txi) write_varint(h_sign, tx.outputs_count) for o in range(tx.outputs_count): # STAGE_REQUEST_4_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) write_tx_output(h_second, txo_bin) write_tx_output(h_sign, txo_bin) write_uint32(h_sign, tx.lock_time) if tx.overwintered: write_uint32(h_sign, tx.expiry) # expiryHeight write_varint(h_sign, 0) # nJoinSplit write_uint32(h_sign, get_hash_type(coin)) # check the control digests if get_tx_hash(h_first, False) != get_tx_hash(h_second): raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) # compute the signature from the tx digest signature = ecdsa_sign( key_sign, get_tx_hash(h_sign, double=coin.sign_hash_double)) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(coin, tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser if coin.decred: return await request_tx_finish(tx_req) for o in range(tx.outputs_count): progress.advance() # STAGE_REQUEST_5_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) # serialize output w_txo_bin = empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) tx_ser.signature_index = None tx_ser.signature = None tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser any_segwit = True in segwit.values() for i in range(tx.inputs_count): progress.advance() if segwit[i]: # STAGE_REQUEST_SEGWIT_WITNESS txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) is_segwit = (txi.script_type == InputScriptType.SPENDWITNESS or txi.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit or txi.amount > authorized_in: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") authorized_in -= txi.amount key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi, ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) signature = ecdsa_sign(key_sign, hash143_hash) if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig_pubkey_index( txi.multisig, key_sign_pub) witness = witness_p2wsh(txi.multisig, signature, signature_index, get_hash_type(coin)) else: witness = witness_p2wpkh(signature, key_sign_pub, get_hash_type(coin)) tx_ser.serialized_tx = witness tx_ser.signature_index = i tx_ser.signature = signature elif any_segwit: tx_ser.serialized_tx = bytearray( 1) # empty witness for non-segwit inputs tx_ser.signature_index = None tx_ser.signature = None tx_req.serialized = tx_ser write_uint32(tx_ser.serialized_tx, tx.lock_time) if tx.overwintered: if tx.version == 3: write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight write_varint(tx_ser.serialized_tx, 0) # nJoinSplit elif tx.version == 4: write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight write_uint64(tx_ser.serialized_tx, 0) # valueBalance write_varint(tx_ser.serialized_tx, 0) # nShieldedSpend write_varint(tx_ser.serialized_tx, 0) # nShieldedOutput write_varint(tx_ser.serialized_tx, 0) # nJoinSplit else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) await request_tx_finish(tx_req)
async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int) -> int: total_out = 0 # sum of output amounts # STAGE_REQUEST_2_PREV_META tx = await request_tx_meta(tx_req, prev_hash) if coin.decred: txh = HashWriter(blake256()) else: txh = HashWriter(sha256()) if tx.overwintered: write_uint32(txh, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(txh, tx.version_group_id) # nVersionGroupId elif coin.decred: write_uint32(txh, tx.version | DECRED_SERIALIZE_NO_WITNESS) else: write_uint32(txh, tx.version) # nVersion write_varint(txh, tx.inputs_cnt) for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await request_tx_input(tx_req, i, prev_hash) if coin.decred: write_tx_input_decred(txh, txi) else: write_tx_input(txh, txi) write_varint(txh, tx.outputs_cnt) for o in range(tx.outputs_cnt): # STAGE_REQUEST_2_PREV_OUTPUT txo_bin = await request_tx_output(tx_req, o, prev_hash) write_tx_output(txh, txo_bin) if o == prev_index: total_out += txo_bin.amount if (coin.decred and txo_bin.decred_script_version is not None and txo_bin.decred_script_version != 0): raise SigningError( FailureType.ProcessError, "Cannot use utxo that has script_version != 0", ) write_uint32(txh, tx.lock_time) if tx.overwintered or coin.decred: write_uint32(txh, tx.expiry) ofs = 0 while ofs < tx.extra_data_len: size = min(1024, tx.extra_data_len - ofs) data = await request_tx_extra_data(tx_req, ofs, size, prev_hash) write_bytes(txh, data) ofs += len(data) if get_tx_hash(txh, double=coin.sign_hash_double, reverse=True) != prev_hash: raise SigningError(FailureType.ProcessError, "Encountered invalid prev_hash") return total_out
def generate_content_signature(json: bytes, private_key: bytes) -> bytes: msghash = sha256(json).digest() return secp256k1.sign(private_key, msghash)[1:65]
def address_multisig_p2wsh_in_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: if coin.address_type_p2sh is None: raise wire.ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) return address_p2wsh_in_p2sh(witness_script_h.get_digest(), coin)
def sha256_ripemd160_digest(b: bytes) -> bytes: h = sha256(b).digest() h = ripemd160(h).digest() return h
async def get_legacy_tx_digest( self, index: int, tx_info: Union[TxInfo, OriginalTxInfo], script_pubkey: Optional[bytes] = None, ) -> Tuple[bytes, TxInput, Optional[bip32.HDNode]]: tx_hash = tx_info.orig_hash if isinstance(tx_info, OriginalTxInfo) else None # the transaction digest which gets signed for this input h_sign = self.create_hash_writer() # should come out the same as h_tx_check, checked before signing the digest h_check = HashWriter(sha256()) self.write_tx_header(h_sign, tx_info.tx, witness_marker=False) write_bitcoin_varint(h_sign, tx_info.tx.inputs_count) for i in range(tx_info.tx.inputs_count): # STAGE_REQUEST_4_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin, tx_hash) writers.write_tx_input_check(h_check, txi) # Only the previous UTXO's scriptPubKey is included in h_sign. if i == index: txi_sign = txi node = None if not script_pubkey: self.tx_info.check_input(txi) node = self.keychain.derive(txi.address_n) key_sign_pub = node.public_key() if txi.multisig: # Sanity check to ensure we are signing with a key that is included in the multisig. multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) if txi.script_type == InputScriptType.SPENDMULTISIG: assert txi.multisig is not None # checked in sanitize_tx_input script_pubkey = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi.multisig), txi.multisig.m, ) elif txi.script_type == InputScriptType.SPENDADDRESS: script_pubkey = scripts.output_script_p2pkh( addresses.ecdsa_hash_pubkey( key_sign_pub, self.coin)) else: raise wire.ProcessError("Unknown transaction type") self.write_tx_input(h_sign, txi, script_pubkey) else: self.write_tx_input(h_sign, txi, bytes()) write_bitcoin_varint(h_sign, tx_info.tx.outputs_count) for i in range(tx_info.tx.outputs_count): # STAGE_REQUEST_4_OUTPUT in legacy txo = await helpers.request_tx_output(self.tx_req, i, self.coin, tx_hash) script_pubkey = self.output_derive_script(txo) self.write_tx_output(h_check, txo, script_pubkey) self.write_tx_output(h_sign, txo, script_pubkey) writers.write_uint32(h_sign, tx_info.tx.lock_time) writers.write_uint32(h_sign, self.get_sighash_type(txi_sign)) # check that the inputs were the same as those streamed for approval if tx_info.get_tx_check_digest() != h_check.get_digest(): raise wire.ProcessError("Transaction has changed during signing") tx_digest = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) return tx_digest, txi_sign, node
def address_multisig_p2wsh(pubkeys: list[bytes], m: int, hrp: str) -> str: if not hrp: raise wire.ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) return address_p2wsh(witness_script_h.get_digest(), hrp)
def create_hash_writer(self) -> HashWriter: return HashWriter(sha256())
def init_hash143(self) -> None: self.h_prevouts = HashWriter(sha256()) self.h_sequence = HashWriter(sha256()) self.h_outputs = HashWriter(sha256())