From 083f835cf08e64ad119f25d5830a97b7847362cd Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 30 Jan 2025 10:17:20 +0100 Subject: [PATCH] bitcoin: display account name when sending to self Before, we did this only when sending to the same account. This commit extends this to work when sending to an address of a *different* account of the same keystore. The bitcoin transaction script_configs field is used for inputs and change outputs. We add a similar list, `output_script_configs`, which allows to attach script configs of different accounts to outputs, with `output_script_config_index` to reference them in the output. To ensure backwards compatibility, `output_script_config_index` is added as a new field, which takes precedence over `script_config_index` when set. The output script configs are validated, but since one can send to any address of the same keystore, the output script config validation allows any combination of script configs, unlike the input script configs. To not duplicate lots of code, a few refactors are done: - validate_script_configs is now used for generic validation and is used to validate the output script configs as well as the input script configs - validate_input_script_configs performs additional validation as before (can't mix multisig and single sig inputs, can't mix inputs from different accounts, etc). - the multisig/policy name is stored in ValidatedScriptConfig, so we don't have to fetch it twice The Python lib version was downgraded from 8.0.0 to 7.0.0, it seems it was bumped before in error - the previous release tag and release is 6.3.0. --- CHANGELOG.md | 1 + messages/btc.proto | 12 +- py/bitbox02/CHANGELOG.md | 7 +- py/bitbox02/bitbox02/bitbox02/__init__.py | 2 +- py/bitbox02/bitbox02/bitbox02/bitbox02.py | 23 +- .../communication/generated/btc_pb2.py | 100 ++--- .../communication/generated/btc_pb2.pyi | 31 +- .../communication/generated/cardano_pb2.pyi | 4 + py/send_message.py | 49 ++- src/rust/bitbox02-rust/src/hww/api/bitcoin.rs | 5 +- .../src/hww/api/bitcoin/common.rs | 6 +- .../src/hww/api/bitcoin/policies.rs | 9 +- .../src/hww/api/bitcoin/script_configs.rs | 126 +++++- .../src/hww/api/bitcoin/signtx.rs | 369 +++++++++++++----- .../bitbox02-rust/src/shiftcrypto.bitbox02.rs | 16 +- 15 files changed, 581 insertions(+), 179 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af749713d..4220ce2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately. - Ethereum: remove deprecated Goerli network - SD card: solve backup bug when sd card is re-inserted - Cardano: allow serialization using 258-tagged sets +- Bitcoin: identify outputs belonging to different accounts of the same keystore ### 9.21.0 - Bitcoin: add support for sending to silent payment (BIP-352) addresses diff --git a/messages/btc.proto b/messages/btc.proto index 6310343b3..7a49fe49e 100644 --- a/messages/btc.proto +++ b/messages/btc.proto @@ -115,6 +115,9 @@ message BTCSignInitRequest { } FormatUnit format_unit = 8; bool contains_silent_payment_outputs = 9; + // used script configs for outputs that send to an address of the same keystore, but not + // necessarily the same account (as defined by `script_configs` above). + repeated BTCScriptConfigWithKeypath output_script_configs = 10; } message BTCSignNextResponse { @@ -174,12 +177,19 @@ message BTCSignOutputRequest { uint64 value = 3; bytes payload = 4; // if ours is false. Renamed from `hash`. repeated uint32 keypath = 5; // if ours is true - // If ours is true. References a script config from BTCSignInitRequest + // If ours is true and `output_script_config_index` is absent. References a script config from + // BTCSignInitRequest. This allows change output identification and allows us to identify + // non-change outputs to the same account, so we can display this info to the user. uint32 script_config_index = 6; optional uint32 payment_request_index = 7; // If provided, `type` and `payload` is ignored. The generated output pkScript is returned in // BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true. SilentPayment silent_payment = 8; + // If ours is true. If set, `script_config_index` is ignored. References an output script config + // from BTCSignInitRequest. This enables verification that an output belongs to the same keystore, + // even if it is from a different account than we spend from, allowing us to display this info to + // the user. + optional uint32 output_script_config_index = 9; } message BTCScriptConfigRegistration { diff --git a/py/bitbox02/CHANGELOG.md b/py/bitbox02/CHANGELOG.md index caab10cba..af7c1f341 100644 --- a/py/bitbox02/CHANGELOG.md +++ b/py/bitbox02/CHANGELOG.md @@ -2,13 +2,12 @@ ## [Unreleased] -# 8.0.0 -- SD card: Remove API to prompt removal of the microSD card from the device -- Add support for regtest (supported from firmware version v9.21.0) - # 7.0.0 - get_info: add optional device initialized boolean to returned tuple - eth_sign: add address_case field, which should be initialized by the client +- SD card: Remove API to prompt removal of the microSD card from the device +- Add support for regtest (supported from firmware version v9.21.0) +- btc_sign: allow identifying outputs belonging to different accounts of the same keystore # 6.3.0 - Allow infering product and version via API call instead of via USB descriptor diff --git a/py/bitbox02/bitbox02/bitbox02/__init__.py b/py/bitbox02/bitbox02/bitbox02/__init__.py index 50d5ede42..368e59ca4 100644 --- a/py/bitbox02/bitbox02/bitbox02/__init__.py +++ b/py/bitbox02/bitbox02/bitbox02/__init__.py @@ -16,7 +16,7 @@ from __future__ import print_function import sys -__version__ = "8.0.0" +__version__ = "7.0.0" if sys.version_info.major != 3 or sys.version_info.minor < 6: print( diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py index 768a657e7..f7ceaf2bd 100644 --- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py +++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py @@ -117,13 +117,20 @@ class BTCInputType(TypedDict): class BTCOutputInternal: # TODO: Use NamedTuple, but not playing well with protobuf types. - def __init__(self, keypath: Sequence[int], value: int, script_config_index: int): + def __init__( + self, + keypath: Sequence[int], + value: int, + script_config_index: int, + output_script_config_index: Optional[int] = None, + ): """ keypath: keypath to the change output. """ self.keypath = keypath self.value = value self.script_config_index = script_config_index + self.output_script_config_index = output_script_config_index class BTCOutputExternal: @@ -390,13 +397,14 @@ def btc_sign( version: int = 1, locktime: int = 0, format_unit: "btc.BTCSignInitRequest.FormatUnit.V" = btc.BTCSignInitRequest.FormatUnit.DEFAULT, + output_script_configs: Optional[Sequence[btc.BTCScriptConfigWithKeypath]] = None, ) -> Sequence[Tuple[int, bytes]]: """ coin: the first element of all provided keypaths must match the coin: - BTC: 0 + HARDENED - Testnets: 1 + HARDENED - LTC: 2 + HARDENED - script_configs: types of all inputs and change outputs. The first element of all provided + script_configs: types of all inputs and outputs belonging to the same account (change or non-change). The first element of all provided keypaths must match this type: - SCRIPT_P2PKH: 44 + HARDENED - SCRIPT_P2WPKH_P2SH: 49 + HARDENED @@ -407,6 +415,8 @@ def btc_sign( outputs: transaction outputs. Can be an external output (BTCOutputExternal) or an internal output for change (BTCOutputInternal). version, locktime: reserved for future use. + format_unit: defines in which unit amounts will be displayed + output_script_configs: script types for outputs belonging to the same keystore Returns: list of (input index, signature) tuples. Raises Bitbox02Exception with ERR_USER_ABORT on user abort. """ @@ -418,6 +428,10 @@ def btc_sign( if any(map(is_taproot, script_configs)): self._require_atleast(semver.VersionInfo(9, 10, 0)) + if output_script_configs: + # Attaching output info supported since v9.22.0. + self._require_atleast(semver.VersionInfo(9, 22, 0)) + supports_antiklepto = self.version >= semver.VersionInfo(9, 4, 0) sigs: List[Tuple[int, bytes]] = [] @@ -433,6 +447,7 @@ def btc_sign( num_outputs=len(outputs), locktime=locktime, format_unit=format_unit, + output_script_configs=output_script_configs, ) ) next_response = self._msg_query(request, expected_response="btc_sign_next").btc_sign_next @@ -552,12 +567,16 @@ def btc_sign( request = hww.Request() if isinstance(tx_output, BTCOutputInternal): + if tx_output.output_script_config_index is not None: + # Attaching output info supported since v9.22.0. + self._require_atleast(semver.VersionInfo(9, 22, 0)) request.btc_sign_output.CopyFrom( btc.BTCSignOutputRequest( ours=True, value=tx_output.value, keypath=tx_output.keypath, script_config_index=tx_output.script_config_index, + output_script_config_index=tx_output.output_script_config_index, ) ) elif isinstance(tx_output, BTCOutputExternal): diff --git a/py/bitbox02/bitbox02/communication/generated/btc_pb2.py b/py/bitbox02/bitbox02/communication/generated/btc_pb2.py index 11aeec626..111d577b4 100644 --- a/py/bitbox02/bitbox02/communication/generated/btc_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/btc_pb2.py @@ -15,17 +15,17 @@ from . import antiklepto_pb2 as antiklepto__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tbtc.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x10\x61ntiklepto.proto\"\xc6\x04\n\x0f\x42TCScriptConfig\x12G\n\x0bsimple_type\x18\x01 \x01(\x0e\x32\x30.shiftcrypto.bitbox02.BTCScriptConfig.SimpleTypeH\x00\x12\x42\n\x08multisig\x18\x02 \x01(\x0b\x32..shiftcrypto.bitbox02.BTCScriptConfig.MultisigH\x00\x12>\n\x06policy\x18\x03 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCScriptConfig.PolicyH\x00\x1a\xd9\x01\n\x08Multisig\x12\x11\n\tthreshold\x18\x01 \x01(\r\x12)\n\x05xpubs\x18\x02 \x03(\x0b\x32\x1a.shiftcrypto.bitbox02.XPub\x12\x16\n\x0eour_xpub_index\x18\x03 \x01(\r\x12N\n\x0bscript_type\x18\x04 \x01(\x0e\x32\x39.shiftcrypto.bitbox02.BTCScriptConfig.Multisig.ScriptType\"\'\n\nScriptType\x12\t\n\x05P2WSH\x10\x00\x12\x0e\n\nP2WSH_P2SH\x10\x01\x1aK\n\x06Policy\x12\x0e\n\x06policy\x18\x01 \x01(\t\x12\x31\n\x04keys\x18\x02 \x03(\x0b\x32#.shiftcrypto.bitbox02.KeyOriginInfo\"3\n\nSimpleType\x12\x0f\n\x0bP2WPKH_P2SH\x10\x00\x12\n\n\x06P2WPKH\x10\x01\x12\x08\n\x04P2TR\x10\x02\x42\x08\n\x06\x63onfig\"\xfc\x02\n\rBTCPubRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12\x0f\n\x07keypath\x18\x02 \x03(\r\x12\x41\n\txpub_type\x18\x03 \x01(\x0e\x32,.shiftcrypto.bitbox02.BTCPubRequest.XPubTypeH\x00\x12>\n\rscript_config\x18\x04 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfigH\x00\x12\x0f\n\x07\x64isplay\x18\x05 \x01(\x08\"\x8e\x01\n\x08XPubType\x12\x08\n\x04TPUB\x10\x00\x12\x08\n\x04XPUB\x10\x01\x12\x08\n\x04YPUB\x10\x02\x12\x08\n\x04ZPUB\x10\x03\x12\x08\n\x04VPUB\x10\x04\x12\x08\n\x04UPUB\x10\x05\x12\x10\n\x0c\x43\x41PITAL_VPUB\x10\x06\x12\x10\n\x0c\x43\x41PITAL_ZPUB\x10\x07\x12\x10\n\x0c\x43\x41PITAL_UPUB\x10\x08\x12\x10\n\x0c\x43\x41PITAL_YPUB\x10\tB\x08\n\x06output\"k\n\x1a\x42TCScriptConfigWithKeypath\x12<\n\rscript_config\x18\x02 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfig\x12\x0f\n\x07keypath\x18\x03 \x03(\r\"\xee\x02\n\x12\x42TCSignInitRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12H\n\x0escript_configs\x18\x02 \x03(\x0b\x32\x30.shiftcrypto.bitbox02.BTCScriptConfigWithKeypath\x12\x0f\n\x07version\x18\x04 \x01(\r\x12\x12\n\nnum_inputs\x18\x05 \x01(\r\x12\x13\n\x0bnum_outputs\x18\x06 \x01(\r\x12\x10\n\x08locktime\x18\x07 \x01(\r\x12H\n\x0b\x66ormat_unit\x18\x08 \x01(\x0e\x32\x33.shiftcrypto.bitbox02.BTCSignInitRequest.FormatUnit\x12\'\n\x1f\x63ontains_silent_payment_outputs\x18\t \x01(\x08\"\"\n\nFormatUnit\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x07\n\x03SAT\x10\x01\"\xc4\x03\n\x13\x42TCSignNextResponse\x12<\n\x04type\x18\x01 \x01(\x0e\x32..shiftcrypto.bitbox02.BTCSignNextResponse.Type\x12\r\n\x05index\x18\x02 \x01(\r\x12\x15\n\rhas_signature\x18\x03 \x01(\x08\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x12\n\nprev_index\x18\x05 \x01(\r\x12W\n\x1d\x61nti_klepto_signer_commitment\x18\x06 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignerCommitment\x12!\n\x19generated_output_pkscript\x18\x07 \x01(\x0c\x12!\n\x19silent_payment_dleq_proof\x18\x08 \x01(\x0c\"\x82\x01\n\x04Type\x12\t\n\x05INPUT\x10\x00\x12\n\n\x06OUTPUT\x10\x01\x12\x08\n\x04\x44ONE\x10\x02\x12\x0f\n\x0bPREVTX_INIT\x10\x03\x12\x10\n\x0cPREVTX_INPUT\x10\x04\x12\x11\n\rPREVTX_OUTPUT\x10\x05\x12\x0e\n\nHOST_NONCE\x10\x06\x12\x13\n\x0fPAYMENT_REQUEST\x10\x07\"\xea\x01\n\x13\x42TCSignInputRequest\x12\x13\n\x0bprevOutHash\x18\x01 \x01(\x0c\x12\x14\n\x0cprevOutIndex\x18\x02 \x01(\r\x12\x14\n\x0cprevOutValue\x18\x03 \x01(\x04\x12\x10\n\x08sequence\x18\x04 \x01(\r\x12\x0f\n\x07keypath\x18\x06 \x03(\r\x12\x1b\n\x13script_config_index\x18\x07 \x01(\r\x12R\n\x15host_nonce_commitment\x18\x08 \x01(\x0b\x32\x33.shiftcrypto.bitbox02.AntiKleptoHostNonceCommitment\"\xd7\x02\n\x14\x42TCSignOutputRequest\x12\x0c\n\x04ours\x18\x01 \x01(\x08\x12\x31\n\x04type\x18\x02 \x01(\x0e\x32#.shiftcrypto.bitbox02.BTCOutputType\x12\r\n\x05value\x18\x03 \x01(\x04\x12\x0f\n\x07payload\x18\x04 \x01(\x0c\x12\x0f\n\x07keypath\x18\x05 \x03(\r\x12\x1b\n\x13script_config_index\x18\x06 \x01(\r\x12\"\n\x15payment_request_index\x18\x07 \x01(\rH\x00\x88\x01\x01\x12P\n\x0esilent_payment\x18\x08 \x01(\x0b\x32\x38.shiftcrypto.bitbox02.BTCSignOutputRequest.SilentPayment\x1a \n\rSilentPayment\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\tB\x18\n\x16_payment_request_index\"\x99\x01\n\x1b\x42TCScriptConfigRegistration\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12<\n\rscript_config\x18\x02 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfig\x12\x0f\n\x07keypath\x18\x03 \x03(\r\"\x0c\n\nBTCSuccess\"m\n\"BTCIsScriptConfigRegisteredRequest\x12G\n\x0cregistration\x18\x01 \x01(\x0b\x32\x31.shiftcrypto.bitbox02.BTCScriptConfigRegistration\"<\n#BTCIsScriptConfigRegisteredResponse\x12\x15\n\ris_registered\x18\x01 \x01(\x08\"\xfc\x01\n\x1e\x42TCRegisterScriptConfigRequest\x12G\n\x0cregistration\x18\x01 \x01(\x0b\x32\x31.shiftcrypto.bitbox02.BTCScriptConfigRegistration\x12\x0c\n\x04name\x18\x02 \x01(\t\x12P\n\txpub_type\x18\x03 \x01(\x0e\x32=.shiftcrypto.bitbox02.BTCRegisterScriptConfigRequest.XPubType\"1\n\x08XPubType\x12\x11\n\rAUTO_ELECTRUM\x10\x00\x12\x12\n\x0e\x41UTO_XPUB_TPUB\x10\x01\"b\n\x14\x42TCPrevTxInitRequest\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x12\n\nnum_inputs\x18\x02 \x01(\r\x12\x13\n\x0bnum_outputs\x18\x03 \x01(\r\x12\x10\n\x08locktime\x18\x04 \x01(\r\"r\n\x15\x42TCPrevTxInputRequest\x12\x15\n\rprev_out_hash\x18\x01 \x01(\x0c\x12\x16\n\x0eprev_out_index\x18\x02 \x01(\r\x12\x18\n\x10signature_script\x18\x03 \x01(\x0c\x12\x10\n\x08sequence\x18\x04 \x01(\r\">\n\x16\x42TCPrevTxOutputRequest\x12\r\n\x05value\x18\x01 \x01(\x04\x12\x15\n\rpubkey_script\x18\x02 \x01(\x0c\"\xab\x02\n\x18\x42TCPaymentRequestRequest\x12\x16\n\x0erecipient_name\x18\x01 \x01(\t\x12\x42\n\x05memos\x18\x02 \x03(\x0b\x32\x33.shiftcrypto.bitbox02.BTCPaymentRequestRequest.Memo\x12\r\n\x05nonce\x18\x03 \x01(\x0c\x12\x14\n\x0ctotal_amount\x18\x04 \x01(\x04\x12\x11\n\tsignature\x18\x05 \x01(\x0c\x1a{\n\x04Memo\x12Q\n\ttext_memo\x18\x01 \x01(\x0b\x32<.shiftcrypto.bitbox02.BTCPaymentRequestRequest.Memo.TextMemoH\x00\x1a\x18\n\x08TextMemo\x12\x0c\n\x04note\x18\x01 \x01(\tB\x06\n\x04memo\"\xee\x01\n\x15\x42TCSignMessageRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12G\n\rscript_config\x18\x02 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.BTCScriptConfigWithKeypath\x12\x0b\n\x03msg\x18\x03 \x01(\x0c\x12R\n\x15host_nonce_commitment\x18\x04 \x01(\x0b\x32\x33.shiftcrypto.bitbox02.AntiKleptoHostNonceCommitment\"+\n\x16\x42TCSignMessageResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"\x81\x05\n\nBTCRequest\x12_\n\x1bis_script_config_registered\x18\x01 \x01(\x0b\x32\x38.shiftcrypto.bitbox02.BTCIsScriptConfigRegisteredRequestH\x00\x12V\n\x16register_script_config\x18\x02 \x01(\x0b\x32\x34.shiftcrypto.bitbox02.BTCRegisterScriptConfigRequestH\x00\x12\x41\n\x0bprevtx_init\x18\x03 \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCPrevTxInitRequestH\x00\x12\x43\n\x0cprevtx_input\x18\x04 \x01(\x0b\x32+.shiftcrypto.bitbox02.BTCPrevTxInputRequestH\x00\x12\x45\n\rprevtx_output\x18\x05 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCPrevTxOutputRequestH\x00\x12\x43\n\x0csign_message\x18\x06 \x01(\x0b\x32+.shiftcrypto.bitbox02.BTCSignMessageRequestH\x00\x12P\n\x14\x61ntiklepto_signature\x18\x07 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignatureRequestH\x00\x12I\n\x0fpayment_request\x18\x08 \x01(\x0b\x32..shiftcrypto.bitbox02.BTCPaymentRequestRequestH\x00\x42\t\n\x07request\"\x90\x03\n\x0b\x42TCResponse\x12\x33\n\x07success\x18\x01 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCSuccessH\x00\x12`\n\x1bis_script_config_registered\x18\x02 \x01(\x0b\x32\x39.shiftcrypto.bitbox02.BTCIsScriptConfigRegisteredResponseH\x00\x12>\n\tsign_next\x18\x03 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x44\n\x0csign_message\x18\x04 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCSignMessageResponseH\x00\x12X\n\x1c\x61ntiklepto_signer_commitment\x18\x05 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignerCommitmentH\x00\x42\n\n\x08response*9\n\x07\x42TCCoin\x12\x07\n\x03\x42TC\x10\x00\x12\x08\n\x04TBTC\x10\x01\x12\x07\n\x03LTC\x10\x02\x12\x08\n\x04TLTC\x10\x03\x12\x08\n\x04RBTC\x10\x04*R\n\rBTCOutputType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05P2PKH\x10\x01\x12\x08\n\x04P2SH\x10\x02\x12\n\n\x06P2WPKH\x10\x03\x12\t\n\x05P2WSH\x10\x04\x12\x08\n\x04P2TR\x10\x05\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tbtc.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x10\x61ntiklepto.proto\"\xc6\x04\n\x0f\x42TCScriptConfig\x12G\n\x0bsimple_type\x18\x01 \x01(\x0e\x32\x30.shiftcrypto.bitbox02.BTCScriptConfig.SimpleTypeH\x00\x12\x42\n\x08multisig\x18\x02 \x01(\x0b\x32..shiftcrypto.bitbox02.BTCScriptConfig.MultisigH\x00\x12>\n\x06policy\x18\x03 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCScriptConfig.PolicyH\x00\x1a\xd9\x01\n\x08Multisig\x12\x11\n\tthreshold\x18\x01 \x01(\r\x12)\n\x05xpubs\x18\x02 \x03(\x0b\x32\x1a.shiftcrypto.bitbox02.XPub\x12\x16\n\x0eour_xpub_index\x18\x03 \x01(\r\x12N\n\x0bscript_type\x18\x04 \x01(\x0e\x32\x39.shiftcrypto.bitbox02.BTCScriptConfig.Multisig.ScriptType\"\'\n\nScriptType\x12\t\n\x05P2WSH\x10\x00\x12\x0e\n\nP2WSH_P2SH\x10\x01\x1aK\n\x06Policy\x12\x0e\n\x06policy\x18\x01 \x01(\t\x12\x31\n\x04keys\x18\x02 \x03(\x0b\x32#.shiftcrypto.bitbox02.KeyOriginInfo\"3\n\nSimpleType\x12\x0f\n\x0bP2WPKH_P2SH\x10\x00\x12\n\n\x06P2WPKH\x10\x01\x12\x08\n\x04P2TR\x10\x02\x42\x08\n\x06\x63onfig\"\xfc\x02\n\rBTCPubRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12\x0f\n\x07keypath\x18\x02 \x03(\r\x12\x41\n\txpub_type\x18\x03 \x01(\x0e\x32,.shiftcrypto.bitbox02.BTCPubRequest.XPubTypeH\x00\x12>\n\rscript_config\x18\x04 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfigH\x00\x12\x0f\n\x07\x64isplay\x18\x05 \x01(\x08\"\x8e\x01\n\x08XPubType\x12\x08\n\x04TPUB\x10\x00\x12\x08\n\x04XPUB\x10\x01\x12\x08\n\x04YPUB\x10\x02\x12\x08\n\x04ZPUB\x10\x03\x12\x08\n\x04VPUB\x10\x04\x12\x08\n\x04UPUB\x10\x05\x12\x10\n\x0c\x43\x41PITAL_VPUB\x10\x06\x12\x10\n\x0c\x43\x41PITAL_ZPUB\x10\x07\x12\x10\n\x0c\x43\x41PITAL_UPUB\x10\x08\x12\x10\n\x0c\x43\x41PITAL_YPUB\x10\tB\x08\n\x06output\"k\n\x1a\x42TCScriptConfigWithKeypath\x12<\n\rscript_config\x18\x02 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfig\x12\x0f\n\x07keypath\x18\x03 \x03(\r\"\xbf\x03\n\x12\x42TCSignInitRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12H\n\x0escript_configs\x18\x02 \x03(\x0b\x32\x30.shiftcrypto.bitbox02.BTCScriptConfigWithKeypath\x12\x0f\n\x07version\x18\x04 \x01(\r\x12\x12\n\nnum_inputs\x18\x05 \x01(\r\x12\x13\n\x0bnum_outputs\x18\x06 \x01(\r\x12\x10\n\x08locktime\x18\x07 \x01(\r\x12H\n\x0b\x66ormat_unit\x18\x08 \x01(\x0e\x32\x33.shiftcrypto.bitbox02.BTCSignInitRequest.FormatUnit\x12\'\n\x1f\x63ontains_silent_payment_outputs\x18\t \x01(\x08\x12O\n\x15output_script_configs\x18\n \x03(\x0b\x32\x30.shiftcrypto.bitbox02.BTCScriptConfigWithKeypath\"\"\n\nFormatUnit\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x07\n\x03SAT\x10\x01\"\xc4\x03\n\x13\x42TCSignNextResponse\x12<\n\x04type\x18\x01 \x01(\x0e\x32..shiftcrypto.bitbox02.BTCSignNextResponse.Type\x12\r\n\x05index\x18\x02 \x01(\r\x12\x15\n\rhas_signature\x18\x03 \x01(\x08\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x12\n\nprev_index\x18\x05 \x01(\r\x12W\n\x1d\x61nti_klepto_signer_commitment\x18\x06 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignerCommitment\x12!\n\x19generated_output_pkscript\x18\x07 \x01(\x0c\x12!\n\x19silent_payment_dleq_proof\x18\x08 \x01(\x0c\"\x82\x01\n\x04Type\x12\t\n\x05INPUT\x10\x00\x12\n\n\x06OUTPUT\x10\x01\x12\x08\n\x04\x44ONE\x10\x02\x12\x0f\n\x0bPREVTX_INIT\x10\x03\x12\x10\n\x0cPREVTX_INPUT\x10\x04\x12\x11\n\rPREVTX_OUTPUT\x10\x05\x12\x0e\n\nHOST_NONCE\x10\x06\x12\x13\n\x0fPAYMENT_REQUEST\x10\x07\"\xea\x01\n\x13\x42TCSignInputRequest\x12\x13\n\x0bprevOutHash\x18\x01 \x01(\x0c\x12\x14\n\x0cprevOutIndex\x18\x02 \x01(\r\x12\x14\n\x0cprevOutValue\x18\x03 \x01(\x04\x12\x10\n\x08sequence\x18\x04 \x01(\r\x12\x0f\n\x07keypath\x18\x06 \x03(\r\x12\x1b\n\x13script_config_index\x18\x07 \x01(\r\x12R\n\x15host_nonce_commitment\x18\x08 \x01(\x0b\x32\x33.shiftcrypto.bitbox02.AntiKleptoHostNonceCommitment\"\x9f\x03\n\x14\x42TCSignOutputRequest\x12\x0c\n\x04ours\x18\x01 \x01(\x08\x12\x31\n\x04type\x18\x02 \x01(\x0e\x32#.shiftcrypto.bitbox02.BTCOutputType\x12\r\n\x05value\x18\x03 \x01(\x04\x12\x0f\n\x07payload\x18\x04 \x01(\x0c\x12\x0f\n\x07keypath\x18\x05 \x03(\r\x12\x1b\n\x13script_config_index\x18\x06 \x01(\r\x12\"\n\x15payment_request_index\x18\x07 \x01(\rH\x00\x88\x01\x01\x12P\n\x0esilent_payment\x18\x08 \x01(\x0b\x32\x38.shiftcrypto.bitbox02.BTCSignOutputRequest.SilentPayment\x12\'\n\x1aoutput_script_config_index\x18\t \x01(\rH\x01\x88\x01\x01\x1a \n\rSilentPayment\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\tB\x18\n\x16_payment_request_indexB\x1d\n\x1b_output_script_config_index\"\x99\x01\n\x1b\x42TCScriptConfigRegistration\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12<\n\rscript_config\x18\x02 \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCScriptConfig\x12\x0f\n\x07keypath\x18\x03 \x03(\r\"\x0c\n\nBTCSuccess\"m\n\"BTCIsScriptConfigRegisteredRequest\x12G\n\x0cregistration\x18\x01 \x01(\x0b\x32\x31.shiftcrypto.bitbox02.BTCScriptConfigRegistration\"<\n#BTCIsScriptConfigRegisteredResponse\x12\x15\n\ris_registered\x18\x01 \x01(\x08\"\xfc\x01\n\x1e\x42TCRegisterScriptConfigRequest\x12G\n\x0cregistration\x18\x01 \x01(\x0b\x32\x31.shiftcrypto.bitbox02.BTCScriptConfigRegistration\x12\x0c\n\x04name\x18\x02 \x01(\t\x12P\n\txpub_type\x18\x03 \x01(\x0e\x32=.shiftcrypto.bitbox02.BTCRegisterScriptConfigRequest.XPubType\"1\n\x08XPubType\x12\x11\n\rAUTO_ELECTRUM\x10\x00\x12\x12\n\x0e\x41UTO_XPUB_TPUB\x10\x01\"b\n\x14\x42TCPrevTxInitRequest\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x12\n\nnum_inputs\x18\x02 \x01(\r\x12\x13\n\x0bnum_outputs\x18\x03 \x01(\r\x12\x10\n\x08locktime\x18\x04 \x01(\r\"r\n\x15\x42TCPrevTxInputRequest\x12\x15\n\rprev_out_hash\x18\x01 \x01(\x0c\x12\x16\n\x0eprev_out_index\x18\x02 \x01(\r\x12\x18\n\x10signature_script\x18\x03 \x01(\x0c\x12\x10\n\x08sequence\x18\x04 \x01(\r\">\n\x16\x42TCPrevTxOutputRequest\x12\r\n\x05value\x18\x01 \x01(\x04\x12\x15\n\rpubkey_script\x18\x02 \x01(\x0c\"\xab\x02\n\x18\x42TCPaymentRequestRequest\x12\x16\n\x0erecipient_name\x18\x01 \x01(\t\x12\x42\n\x05memos\x18\x02 \x03(\x0b\x32\x33.shiftcrypto.bitbox02.BTCPaymentRequestRequest.Memo\x12\r\n\x05nonce\x18\x03 \x01(\x0c\x12\x14\n\x0ctotal_amount\x18\x04 \x01(\x04\x12\x11\n\tsignature\x18\x05 \x01(\x0c\x1a{\n\x04Memo\x12Q\n\ttext_memo\x18\x01 \x01(\x0b\x32<.shiftcrypto.bitbox02.BTCPaymentRequestRequest.Memo.TextMemoH\x00\x1a\x18\n\x08TextMemo\x12\x0c\n\x04note\x18\x01 \x01(\tB\x06\n\x04memo\"\xee\x01\n\x15\x42TCSignMessageRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12G\n\rscript_config\x18\x02 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.BTCScriptConfigWithKeypath\x12\x0b\n\x03msg\x18\x03 \x01(\x0c\x12R\n\x15host_nonce_commitment\x18\x04 \x01(\x0b\x32\x33.shiftcrypto.bitbox02.AntiKleptoHostNonceCommitment\"+\n\x16\x42TCSignMessageResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"\x81\x05\n\nBTCRequest\x12_\n\x1bis_script_config_registered\x18\x01 \x01(\x0b\x32\x38.shiftcrypto.bitbox02.BTCIsScriptConfigRegisteredRequestH\x00\x12V\n\x16register_script_config\x18\x02 \x01(\x0b\x32\x34.shiftcrypto.bitbox02.BTCRegisterScriptConfigRequestH\x00\x12\x41\n\x0bprevtx_init\x18\x03 \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCPrevTxInitRequestH\x00\x12\x43\n\x0cprevtx_input\x18\x04 \x01(\x0b\x32+.shiftcrypto.bitbox02.BTCPrevTxInputRequestH\x00\x12\x45\n\rprevtx_output\x18\x05 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCPrevTxOutputRequestH\x00\x12\x43\n\x0csign_message\x18\x06 \x01(\x0b\x32+.shiftcrypto.bitbox02.BTCSignMessageRequestH\x00\x12P\n\x14\x61ntiklepto_signature\x18\x07 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignatureRequestH\x00\x12I\n\x0fpayment_request\x18\x08 \x01(\x0b\x32..shiftcrypto.bitbox02.BTCPaymentRequestRequestH\x00\x42\t\n\x07request\"\x90\x03\n\x0b\x42TCResponse\x12\x33\n\x07success\x18\x01 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCSuccessH\x00\x12`\n\x1bis_script_config_registered\x18\x02 \x01(\x0b\x32\x39.shiftcrypto.bitbox02.BTCIsScriptConfigRegisteredResponseH\x00\x12>\n\tsign_next\x18\x03 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x44\n\x0csign_message\x18\x04 \x01(\x0b\x32,.shiftcrypto.bitbox02.BTCSignMessageResponseH\x00\x12X\n\x1c\x61ntiklepto_signer_commitment\x18\x05 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.AntiKleptoSignerCommitmentH\x00\x42\n\n\x08response*9\n\x07\x42TCCoin\x12\x07\n\x03\x42TC\x10\x00\x12\x08\n\x04TBTC\x10\x01\x12\x07\n\x03LTC\x10\x02\x12\x08\n\x04TLTC\x10\x03\x12\x08\n\x04RBTC\x10\x04*R\n\rBTCOutputType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05P2PKH\x10\x01\x12\x08\n\x04P2SH\x10\x02\x12\n\n\x06P2WPKH\x10\x03\x12\t\n\x05P2WSH\x10\x04\x12\x08\n\x04P2TR\x10\x05\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'btc_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _BTCCOIN._serialized_start=5064 - _BTCCOIN._serialized_end=5121 - _BTCOUTPUTTYPE._serialized_start=5123 - _BTCOUTPUTTYPE._serialized_end=5205 + _BTCCOIN._serialized_start=5217 + _BTCCOIN._serialized_end=5274 + _BTCOUTPUTTYPE._serialized_start=5276 + _BTCOUTPUTTYPE._serialized_end=5358 _BTCSCRIPTCONFIG._serialized_start=68 _BTCSCRIPTCONFIG._serialized_end=650 _BTCSCRIPTCONFIG_MULTISIG._serialized_start=293 @@ -43,49 +43,49 @@ _BTCSCRIPTCONFIGWITHKEYPATH._serialized_start=1035 _BTCSCRIPTCONFIGWITHKEYPATH._serialized_end=1142 _BTCSIGNINITREQUEST._serialized_start=1145 - _BTCSIGNINITREQUEST._serialized_end=1511 - _BTCSIGNINITREQUEST_FORMATUNIT._serialized_start=1477 - _BTCSIGNINITREQUEST_FORMATUNIT._serialized_end=1511 - _BTCSIGNNEXTRESPONSE._serialized_start=1514 - _BTCSIGNNEXTRESPONSE._serialized_end=1966 - _BTCSIGNNEXTRESPONSE_TYPE._serialized_start=1836 - _BTCSIGNNEXTRESPONSE_TYPE._serialized_end=1966 - _BTCSIGNINPUTREQUEST._serialized_start=1969 - _BTCSIGNINPUTREQUEST._serialized_end=2203 - _BTCSIGNOUTPUTREQUEST._serialized_start=2206 - _BTCSIGNOUTPUTREQUEST._serialized_end=2549 - _BTCSIGNOUTPUTREQUEST_SILENTPAYMENT._serialized_start=2491 - _BTCSIGNOUTPUTREQUEST_SILENTPAYMENT._serialized_end=2523 - _BTCSCRIPTCONFIGREGISTRATION._serialized_start=2552 - _BTCSCRIPTCONFIGREGISTRATION._serialized_end=2705 - _BTCSUCCESS._serialized_start=2707 - _BTCSUCCESS._serialized_end=2719 - _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_start=2721 - _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_end=2830 - _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_start=2832 - _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_end=2892 - _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_start=2895 - _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_end=3147 - _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_start=3098 - _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_end=3147 - _BTCPREVTXINITREQUEST._serialized_start=3149 - _BTCPREVTXINITREQUEST._serialized_end=3247 - _BTCPREVTXINPUTREQUEST._serialized_start=3249 - _BTCPREVTXINPUTREQUEST._serialized_end=3363 - _BTCPREVTXOUTPUTREQUEST._serialized_start=3365 - _BTCPREVTXOUTPUTREQUEST._serialized_end=3427 - _BTCPAYMENTREQUESTREQUEST._serialized_start=3430 - _BTCPAYMENTREQUESTREQUEST._serialized_end=3729 - _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_start=3606 - _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_end=3729 - _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_start=3697 - _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_end=3721 - _BTCSIGNMESSAGEREQUEST._serialized_start=3732 - _BTCSIGNMESSAGEREQUEST._serialized_end=3970 - _BTCSIGNMESSAGERESPONSE._serialized_start=3972 - _BTCSIGNMESSAGERESPONSE._serialized_end=4015 - _BTCREQUEST._serialized_start=4018 - _BTCREQUEST._serialized_end=4659 - _BTCRESPONSE._serialized_start=4662 - _BTCRESPONSE._serialized_end=5062 + _BTCSIGNINITREQUEST._serialized_end=1592 + _BTCSIGNINITREQUEST_FORMATUNIT._serialized_start=1558 + _BTCSIGNINITREQUEST_FORMATUNIT._serialized_end=1592 + _BTCSIGNNEXTRESPONSE._serialized_start=1595 + _BTCSIGNNEXTRESPONSE._serialized_end=2047 + _BTCSIGNNEXTRESPONSE_TYPE._serialized_start=1917 + _BTCSIGNNEXTRESPONSE_TYPE._serialized_end=2047 + _BTCSIGNINPUTREQUEST._serialized_start=2050 + _BTCSIGNINPUTREQUEST._serialized_end=2284 + _BTCSIGNOUTPUTREQUEST._serialized_start=2287 + _BTCSIGNOUTPUTREQUEST._serialized_end=2702 + _BTCSIGNOUTPUTREQUEST_SILENTPAYMENT._serialized_start=2613 + _BTCSIGNOUTPUTREQUEST_SILENTPAYMENT._serialized_end=2645 + _BTCSCRIPTCONFIGREGISTRATION._serialized_start=2705 + _BTCSCRIPTCONFIGREGISTRATION._serialized_end=2858 + _BTCSUCCESS._serialized_start=2860 + _BTCSUCCESS._serialized_end=2872 + _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_start=2874 + _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_end=2983 + _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_start=2985 + _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_end=3045 + _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_start=3048 + _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_end=3300 + _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_start=3251 + _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_end=3300 + _BTCPREVTXINITREQUEST._serialized_start=3302 + _BTCPREVTXINITREQUEST._serialized_end=3400 + _BTCPREVTXINPUTREQUEST._serialized_start=3402 + _BTCPREVTXINPUTREQUEST._serialized_end=3516 + _BTCPREVTXOUTPUTREQUEST._serialized_start=3518 + _BTCPREVTXOUTPUTREQUEST._serialized_end=3580 + _BTCPAYMENTREQUESTREQUEST._serialized_start=3583 + _BTCPAYMENTREQUESTREQUEST._serialized_end=3882 + _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_start=3759 + _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_end=3882 + _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_start=3850 + _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_end=3874 + _BTCSIGNMESSAGEREQUEST._serialized_start=3885 + _BTCSIGNMESSAGEREQUEST._serialized_end=4123 + _BTCSIGNMESSAGERESPONSE._serialized_start=4125 + _BTCSIGNMESSAGERESPONSE._serialized_end=4168 + _BTCREQUEST._serialized_start=4171 + _BTCREQUEST._serialized_end=4812 + _BTCRESPONSE._serialized_start=4815 + _BTCRESPONSE._serialized_end=5215 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi index 220cf9460..1f01933d3 100644 --- a/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi @@ -293,6 +293,7 @@ class BTCSignInitRequest(google.protobuf.message.Message): LOCKTIME_FIELD_NUMBER: builtins.int FORMAT_UNIT_FIELD_NUMBER: builtins.int CONTAINS_SILENT_PAYMENT_OUTPUTS_FIELD_NUMBER: builtins.int + OUTPUT_SCRIPT_CONFIGS_FIELD_NUMBER: builtins.int coin: global___BTCCoin.ValueType @property def script_configs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BTCScriptConfigWithKeypath]: @@ -308,6 +309,12 @@ class BTCSignInitRequest(google.protobuf.message.Message): format_unit: global___BTCSignInitRequest.FormatUnit.ValueType contains_silent_payment_outputs: builtins.bool + @property + def output_script_configs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BTCScriptConfigWithKeypath]: + """used script configs for outputs that send to an address of the same keystore, but not + necessarily the same account (as defined by `script_configs` above). + """ + pass def __init__(self, *, coin: global___BTCCoin.ValueType = ..., @@ -318,8 +325,9 @@ class BTCSignInitRequest(google.protobuf.message.Message): locktime: builtins.int = ..., format_unit: global___BTCSignInitRequest.FormatUnit.ValueType = ..., contains_silent_payment_outputs: builtins.bool = ..., + output_script_configs: typing.Optional[typing.Iterable[global___BTCScriptConfigWithKeypath]] = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["coin",b"coin","contains_silent_payment_outputs",b"contains_silent_payment_outputs","format_unit",b"format_unit","locktime",b"locktime","num_inputs",b"num_inputs","num_outputs",b"num_outputs","script_configs",b"script_configs","version",b"version"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["coin",b"coin","contains_silent_payment_outputs",b"contains_silent_payment_outputs","format_unit",b"format_unit","locktime",b"locktime","num_inputs",b"num_inputs","num_outputs",b"num_outputs","output_script_configs",b"output_script_configs","script_configs",b"script_configs","version",b"version"]) -> None: ... global___BTCSignInitRequest = BTCSignInitRequest class BTCSignNextResponse(google.protobuf.message.Message): @@ -454,6 +462,7 @@ class BTCSignOutputRequest(google.protobuf.message.Message): SCRIPT_CONFIG_INDEX_FIELD_NUMBER: builtins.int PAYMENT_REQUEST_INDEX_FIELD_NUMBER: builtins.int SILENT_PAYMENT_FIELD_NUMBER: builtins.int + OUTPUT_SCRIPT_CONFIG_INDEX_FIELD_NUMBER: builtins.int ours: builtins.bool type: global___BTCOutputType.ValueType """if ours is false""" @@ -469,7 +478,10 @@ class BTCSignOutputRequest(google.protobuf.message.Message): """if ours is true""" pass script_config_index: builtins.int - """If ours is true. References a script config from BTCSignInitRequest""" + """If ours is true and `output_script_config_index` is absent. References a script config from + BTCSignInitRequest. This allows change output identification and allows us to identify + non-change outputs to the same account, so we can display this info to the user. + """ payment_request_index: builtins.int @property @@ -478,6 +490,13 @@ class BTCSignOutputRequest(google.protobuf.message.Message): BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true. """ pass + output_script_config_index: builtins.int + """If ours is true. If set, `script_config_index` is ignored. References an output script config + from BTCSignInitRequest. This enables verification that an output belongs to the same keystore, + even if it is from a different account than we spend from, allowing us to display this info to + the user. + """ + def __init__(self, *, ours: builtins.bool = ..., @@ -488,9 +507,13 @@ class BTCSignOutputRequest(google.protobuf.message.Message): script_config_index: builtins.int = ..., payment_request_index: typing.Optional[builtins.int] = ..., silent_payment: typing.Optional[global___BTCSignOutputRequest.SilentPayment] = ..., + output_script_config_index: typing.Optional[builtins.int] = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","payment_request_index",b"payment_request_index","silent_payment",b"silent_payment"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","keypath",b"keypath","ours",b"ours","payload",b"payload","payment_request_index",b"payment_request_index","script_config_index",b"script_config_index","silent_payment",b"silent_payment","type",b"type","value",b"value"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_output_script_config_index",b"_output_script_config_index","_payment_request_index",b"_payment_request_index","output_script_config_index",b"output_script_config_index","payment_request_index",b"payment_request_index","silent_payment",b"silent_payment"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_output_script_config_index",b"_output_script_config_index","_payment_request_index",b"_payment_request_index","keypath",b"keypath","ours",b"ours","output_script_config_index",b"output_script_config_index","payload",b"payload","payment_request_index",b"payment_request_index","script_config_index",b"script_config_index","silent_payment",b"silent_payment","type",b"type","value",b"value"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_output_script_config_index",b"_output_script_config_index"]) -> typing.Optional[typing_extensions.Literal["output_script_config_index"]]: ... + @typing.overload def WhichOneof(self, oneof_group: typing_extensions.Literal["_payment_request_index",b"_payment_request_index"]) -> typing.Optional[typing_extensions.Literal["payment_request_index"]]: ... global___BTCSignOutputRequest = BTCSignOutputRequest diff --git a/py/bitbox02/bitbox02/communication/generated/cardano_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/cardano_pb2.pyi index a500b863a..c08098dcf 100644 --- a/py/bitbox02/bitbox02/communication/generated/cardano_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/cardano_pb2.pyi @@ -299,6 +299,10 @@ class CardanoSignTransactionRequest(google.protobuf.message.Message): """include ttl even if it is zero""" tag_cbor_sets: builtins.bool + """Tag arrays in the transaction serialization with the 258 tag. + See https://github.com/IntersectMBO/cardano-ledger/blob/6e2d37cc0f47bd02e89b4ce9f78b59c35c958e96/eras/conway/impl/cddl-files/extra.cddl#L5 + """ + def __init__(self, *, network: global___CardanoNetwork.ValueType = ..., diff --git a/py/send_message.py b/py/send_message.py index 89821c534..4f4e082f9 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -526,7 +526,7 @@ def _sign_btc_normal( for input_index, sig in sigs: print("Signature for input {}: {}".format(input_index, sig.hex())) - def _sign_btc_send_to_self( + def _sign_btc_send_to_self_same_account( self, format_unit: "bitbox02.btc.BTCSignInitRequest.FormatUnit.V" = bitbox02.btc.BTCSignInitRequest.FormatUnit.DEFAULT, ) -> None: @@ -561,6 +561,50 @@ def _sign_btc_send_to_self( for input_index, sig in sigs: print("Signature for input {}: {}".format(input_index, sig.hex())) + def _sign_btc_send_to_self_different_account( + self, + format_unit: "bitbox02.btc.BTCSignInitRequest.FormatUnit.V" = bitbox02.btc.BTCSignInitRequest.FormatUnit.DEFAULT, + ) -> None: + # pylint: disable=no-member + bip44_account: int = 0 + HARDENED + inputs, outputs = _btc_demo_inputs_outputs(bip44_account) + outputs[1] = bitbox02.BTCOutputInternal( + keypath=[84 + HARDENED, 0 + HARDENED, 1 + HARDENED, 0, 0], + value=int(1e8 * 0.2), + script_config_index=0, + output_script_config_index=0, + ) + sigs = self._device.btc_sign( + bitbox02.btc.BTC, + [ + bitbox02.btc.BTCScriptConfigWithKeypath( + script_config=bitbox02.btc.BTCScriptConfig( + simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH + ), + keypath=[84 + HARDENED, 0 + HARDENED, bip44_account], + ), + bitbox02.btc.BTCScriptConfigWithKeypath( + script_config=bitbox02.btc.BTCScriptConfig( + simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH + ), + keypath=[49 + HARDENED, 0 + HARDENED, bip44_account], + ), + ], + inputs=inputs, + outputs=outputs, + format_unit=format_unit, + output_script_configs=[ + bitbox02.btc.BTCScriptConfigWithKeypath( + script_config=bitbox02.btc.BTCScriptConfig( + simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH + ), + keypath=[84 + HARDENED, 0 + HARDENED, 1 + HARDENED], + ), + ], + ) + for input_index, sig in sigs: + print("Signature for input {}: {}".format(input_index, sig.hex())) + def _sign_btc_high_fee(self) -> None: # pylint: disable=no-member bip44_account: int = 0 + HARDENED @@ -832,7 +876,8 @@ def _sign_btc_tx(self) -> None: format_unit=bitbox02.btc.BTCSignInitRequest.FormatUnit.SAT ), ), - ("Send to self", self._sign_btc_send_to_self), + ("Send to self (same account)", self._sign_btc_send_to_self_same_account), + ("Send to self (different account)", self._sign_btc_send_to_self_different_account), ("High fee warning", self._sign_btc_high_fee), ("Multiple change outputs", self._sign_btc_multiple_changes), ("Locktime/RBF", self._sign_btc_locktime_rbf), diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs index 4cafe7b96..4a2bc512f 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs @@ -241,10 +241,7 @@ async fn address_policy( let parsed = policies::parse(policy, coin)?; - let name = match policies::get_name(coin, policy)? { - Some(name) => name, - None => return Err(Error::InvalidInput), - }; + let name = parsed.name(coin_params)?.ok_or(Error::InvalidInput)?; let title = "Receive to"; diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs index 83bc074e2..e1ffede0e 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs @@ -191,13 +191,15 @@ impl Payload { ValidatedScriptConfig::SimpleType(simple_type) => { Self::from_simple(xpub_cache, params, *simple_type, keypath) } - ValidatedScriptConfig::Multisig(multisig) => Self::from_multisig( + ValidatedScriptConfig::Multisig { multisig, .. } => Self::from_multisig( params, multisig, keypath[keypath.len() - 2], keypath[keypath.len() - 1], ), - ValidatedScriptConfig::Policy(policy) => Self::from_policy(params, policy, keypath), + ValidatedScriptConfig::Policy { parsed_policy, .. } => { + Self::from_policy(params, parsed_policy, keypath) + } } } diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs index ee14865e1..e4c799da6 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs @@ -272,6 +272,13 @@ pub struct ParsedPolicy<'a> { } impl ParsedPolicy<'_> { + /// Get the name of a registered policy account. + /// + /// Returns the name of the registered policy account if it exists or None otherwise. + pub fn name(&self, params: &Params) -> Result, ()> { + get_name(params.coin, self.policy) + } + /// Iterates over the placeholder keys in this descriptor. For tr() descriptors, this covers the /// internal key and every key in every leaf script. /// This iterates the keys "left-to-right" in the descriptor. @@ -739,7 +746,7 @@ pub fn get_hash(coin: BtcCoin, policy: &Policy) -> Result, ()> { Ok(hasher.finalize().as_slice().into()) } -/// Get the name of a registered policy account. The poliy is not validated, it must be +/// Get the name of a registered policy account. The policy is not validated, it must be /// pre-validated! /// /// Returns the name of the registered policy account if it exists or None otherwise. diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs index ea0485733..d5cc6eece 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs @@ -12,16 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +use alloc::string::String; + use super::pb; +use super::Error; use pb::btc_script_config::{Multisig, SimpleType}; use super::policies::ParsedPolicy; +use util::bip32::HARDENED; + /// Parsed and validated form of `pb::BtcScriptConfig`. pub enum ValidatedScriptConfig<'a> { SimpleType(SimpleType), - Multisig(&'a Multisig), - Policy(ParsedPolicy<'a>), + Multisig { + name: String, + multisig: &'a Multisig, + }, + Policy { + name: String, + parsed_policy: ParsedPolicy<'a>, + }, } /// Parsed and validated form of `pb::BtcScriptConfigWithKeypath`. @@ -29,3 +40,114 @@ pub struct ValidatedScriptConfigWithKeypath<'a> { pub keypath: &'a [u32], pub config: ValidatedScriptConfig<'a>, } + +impl ValidatedScriptConfigWithKeypath<'_> { + /// Get a string representation of the account script config to show to the user when they send + /// coins to an address belonging to the same keystore. + pub fn self_transfer_representation(&self) -> Result { + match self { + ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::SimpleType(_), + } => { + let keypath_account_element = keypath.get(2).ok_or(Error::Generic)?; + Ok(format!( + "This BitBox (account #{})", + keypath_account_element + .checked_sub(HARDENED) + .ok_or(Error::Generic)? + + 1 + )) + } + ValidatedScriptConfigWithKeypath { + config: ValidatedScriptConfig::Multisig { name, .. }, + .. + } + | ValidatedScriptConfigWithKeypath { + config: ValidatedScriptConfig::Policy { name, .. }, + .. + } => Ok(format!("This BitBox - {}", name)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bip32::parse_xpub; + use bitbox02::testing::{mock_memory, mock_unlocked}; + + #[test] + fn test_self_transfer_representation_simple_type() { + // Same text repr for all simple types. + for simple_type in [SimpleType::P2wpkhP2sh, SimpleType::P2wpkh, SimpleType::P2tr] { + let config = ValidatedScriptConfigWithKeypath { + keypath: &[84 + HARDENED, 0 + HARDENED, 10 + HARDENED], + config: ValidatedScriptConfig::SimpleType(simple_type), + }; + assert_eq!( + config.self_transfer_representation().unwrap(), + "This BitBox (account #11)" + ) + } + } + + #[test] + fn test_self_transfer_representation_multisig() { + let xpub1 = parse_xpub("xpub6EMfjyGVUvwhpc3WKN1zXhMFGKJGMaSBPqbja4tbGoYvRBSXeTBCaqrRDjcuGTcaY95JrrAnQvDG3pdQPdtnYUCugjeksHSbyZT7rq38VQF").unwrap(); + let xpub2 = parse_xpub("xpub6ERxBysTYfQyY4USv6c6J1HNVv9hpZFN9LHVPu47Ac4rK8fLy6NnAeeAHyEsMvG4G66ay5aFZii2VM7wT3KxLKX8Q8keZPd67kRGmrD1WJj").unwrap(); + let multisig = pb::btc_script_config::Multisig { + threshold: 1, + xpubs: vec![xpub1, xpub2], + our_xpub_index: 0, + script_type: pb::btc_script_config::multisig::ScriptType::P2wsh as _, + }; + let keypath = &[48 + HARDENED, 0 + HARDENED, 0 + HARDENED, 2 + HARDENED]; + let config = ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::Multisig { + name: "test multisig account name".into(), + multisig: &multisig, + }, + }; + + assert_eq!( + config.self_transfer_representation().unwrap().as_str(), + "This BitBox - test multisig account name", + ) + } + + #[test] + fn test_self_transfer_representation_policy() { + let keypath = &[48 + HARDENED, 1 + HARDENED, 0 + HARDENED, 3 + HARDENED]; + let policy = pb::btc_script_config::Policy { + policy: "wsh(multi(2,@0/**,@1/**))".into(), + keys: vec![ + pb::KeyOriginInfo { + root_fingerprint: crate::keystore::root_fingerprint().unwrap(), + keypath: keypath.to_vec(), + xpub: Some(crate::keystore::get_xpub(keypath).unwrap().into()), + }, + pb::KeyOriginInfo { + root_fingerprint: vec![], + keypath: vec![], + xpub: Some(parse_xpub("tpubDFGkUYFfEhAALSXQ9VNssUq71HWYLWLK7sAEqFyqJBQxQ4uGSBW1RSBkoVfijE6iEHZFs2kZrVzzV1nZCSEXYKudtsfEWcWKVXvjjLeRyd8").unwrap()), + }, + ], + }; + + let parsed_policy = super::super::policies::parse(&policy, pb::BtcCoin::Btc).unwrap(); + let config = ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::Policy { + name: "test policy account name".into(), + parsed_policy, + }, + }; + + assert_eq!( + config.self_transfer_representation().unwrap().as_str(), + "This BitBox - test policy account name", + ) + } +} diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs index 71842214b..b184501e8 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs @@ -25,6 +25,7 @@ use super::{bip143, bip341, common, keypath}; use crate::workflow::{confirm, status, transaction}; use crate::xpubcache::Bip32XpubCache; +use alloc::string::String; use alloc::vec::Vec; use pb::request::Request; @@ -217,7 +218,7 @@ fn validate_keypath( ) .or(Err(Error::InvalidInput))?; } - ValidatedScriptConfig::Multisig(_) | ValidatedScriptConfig::Policy(_) => { + ValidatedScriptConfig::Multisig { .. } | ValidatedScriptConfig::Policy { .. } => { keypath::validate_address_policy(keypath, mode).or(Err(Error::InvalidInput))?; } } @@ -251,10 +252,13 @@ fn is_taproot(script_config_account: &ValidatedScriptConfigWithKeypath) -> bool matches!( script_config_account.config, ValidatedScriptConfig::SimpleType(SimpleType::P2tr) - | ValidatedScriptConfig::Policy(super::policies::ParsedPolicy { - descriptor: super::policies::Descriptor::Tr(_), + | ValidatedScriptConfig::Policy { + parsed_policy: super::policies::ParsedPolicy { + descriptor: super::policies::Descriptor::Tr(_), + .. + }, .. - }) + } ) } @@ -286,7 +290,7 @@ fn sighash_script( } } ValidatedScriptConfigWithKeypath { - config: ValidatedScriptConfig::Multisig(multisig), + config: ValidatedScriptConfig::Multisig { multisig, .. }, .. } => Ok(super::multisig::pkscript( multisig, @@ -294,9 +298,9 @@ fn sighash_script( keypath[keypath.len() - 1], )?), ValidatedScriptConfigWithKeypath { - config: ValidatedScriptConfig::Policy(policy), + config: ValidatedScriptConfig::Policy { parsed_policy, .. }, .. - } => match policy.derive_at_keypath(keypath)? { + } => match parsed_policy.derive_at_keypath(keypath)? { super::policies::Descriptor::Wsh(wsh) => Ok(wsh.witness_script()), // This function is only called for SegWit v0 inputs. _ => Err(Error::Generic), @@ -374,7 +378,81 @@ async fn handle_prevtx( Ok(()) } -async fn validate_script_configs<'a>( +fn validate_script_config<'a>( + script_config: &'a pb::BtcScriptConfigWithKeypath, + coin_params: &super::params::Params, +) -> Result, Error> { + match script_config { + pb::BtcScriptConfigWithKeypath { + script_config: + Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::Multisig(multisig)), + }), + keypath, + } => { + super::multisig::validate(multisig, keypath)?; + let name = super::multisig::get_name(coin_params.coin, multisig, keypath)? + .ok_or(Error::InvalidInput)?; + Ok(ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::Multisig { name, multisig }, + }) + } + pb::BtcScriptConfigWithKeypath { + script_config: + Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::Policy(policy)), + }), + keypath, + } => { + let parsed_policy = super::policies::parse(policy, coin_params.coin)?; + let name = parsed_policy + .name(coin_params)? + .ok_or(Error::InvalidInput)?; + Ok(ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::Policy { + name, + parsed_policy, + }, + }) + } + pb::BtcScriptConfigWithKeypath { + script_config: + Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::SimpleType(simple_type)), + }), + keypath, + } => { + let simple_type = SimpleType::try_from(*simple_type)?; + keypath::validate_account_simple( + keypath, + coin_params.bip44_coin, + simple_type, + coin_params.taproot_support, + ) + .or(Err(Error::InvalidInput))?; + Ok(ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::SimpleType(simple_type), + }) + } + _ => Err(Error::InvalidInput), + } +} + +fn validate_script_configs<'a>( + coin_params: &super::params::Params, + script_configs: &'a [pb::BtcScriptConfigWithKeypath], +) -> Result>, Error> { + let validated: Vec = script_configs + .iter() + .map(|config| validate_script_config(config, coin_params)) + .collect::, Error>>()?; + Ok(validated) +} + +async fn validate_input_script_configs<'a>( coin_params: &super::params::Params, script_configs: &'a [pb::BtcScriptConfigWithKeypath], ) -> Result>, Error> { @@ -382,44 +460,34 @@ async fn validate_script_configs<'a>( return Err(Error::InvalidInput); } + let script_configs = validate_script_configs(coin_params, script_configs)?; + // If there are multiple script configs, only SimpleType (single sig, no additional inputs) // configs are allowed, so e.g. mixing p2wpkh and pw2wpkh-p2sh is okay, but mixing p2wpkh with // multisig-pw2sh is not. // We get multisig out of the way first. - if let [pb::BtcScriptConfigWithKeypath { - script_config: - Some(pb::BtcScriptConfig { - config: Some(pb::btc_script_config::Config::Multisig(multisig)), - }), - keypath, - }] = script_configs + if let [ValidatedScriptConfigWithKeypath { + config: ValidatedScriptConfig::Multisig { name, multisig }, + .. + }] = script_configs.as_slice() { - super::multisig::validate(multisig, keypath)?; - let name = super::multisig::get_name(coin_params.coin, multisig, keypath)? - .ok_or(Error::InvalidInput)?; - super::multisig::confirm("Spend from", coin_params, &name, multisig).await?; - return Ok(vec![ValidatedScriptConfigWithKeypath { - keypath, - config: ValidatedScriptConfig::Multisig(multisig), - }]); + super::multisig::confirm("Spend from", coin_params, name, multisig).await?; + return Ok(script_configs); } // Then we get policies out of the way. - if let [pb::BtcScriptConfigWithKeypath { - script_config: - Some(pb::BtcScriptConfig { - config: Some(pb::btc_script_config::Config::Policy(policy)), - }), - keypath, - }] = script_configs + if let [ValidatedScriptConfigWithKeypath { + config: + ValidatedScriptConfig::Policy { + name, + parsed_policy, + }, + .. + }] = script_configs.as_slice() { - let parsed_policy = super::policies::parse(policy, coin_params.coin)?; - let name = - super::policies::get_name(coin_params.coin, policy)?.ok_or(Error::InvalidInput)?; - // We could check here that the account keypath matches one of our keys in the policy and // abort early, but we don't have to - if the keypath does not match we will fail when // processing the first input, where it is checked that the account keypath is a prefix of @@ -430,54 +498,32 @@ async fn validate_script_configs<'a>( .confirm( "Spend from", coin_params, - &name, + name, super::policies::Mode::Basic, ) .await?; - return Ok(vec![ValidatedScriptConfigWithKeypath { - keypath, - config: ValidatedScriptConfig::Policy(parsed_policy), - }]); + return Ok(script_configs); } - let validated: Vec = script_configs - .iter() - .map(|script_config| { - // Only allow simple single sig configs here. - match script_config { - pb::BtcScriptConfigWithKeypath { - script_config: - Some(pb::BtcScriptConfig { - config: Some(pb::btc_script_config::Config::SimpleType(simple_type)), - }), - keypath, - } => { - let simple_type = SimpleType::try_from(*simple_type)?; - keypath::validate_account_simple( - keypath, - coin_params.bip44_coin, - simple_type, - coin_params.taproot_support, - ) - .or(Err(Error::InvalidInput))?; - - // Check that the bip44 account is the same for all. While we allow mixing input - // types (bip44 purpose), we do not allow mixing accounts. - - if keypath[2] != script_configs[0].keypath[2] { - return Err(Error::InvalidInput); - } - Ok(ValidatedScriptConfigWithKeypath { - keypath, - config: ValidatedScriptConfig::SimpleType(simple_type), - }) + // Only allow simple single sig configs here. + for script_config in script_configs.iter() { + match script_config { + ValidatedScriptConfigWithKeypath { + keypath, + config: ValidatedScriptConfig::SimpleType(_), + } => { + // Check that the bip44 account is the same for all. While we allow mixing input + // types (bip44 purpose), we do not allow mixing accounts. + + if keypath[2] != script_configs[0].keypath[2] { + return Err(Error::InvalidInput); } - _ => Err(Error::InvalidInput), } - }) - .collect::, Error>>()?; - Ok(validated) + _ => return Err(Error::InvalidInput), + } + } + Ok(script_configs) } // We configure the xpub cache to cache up to change/receive level. If e.g. there is an input/change @@ -625,7 +671,9 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { return Err(Error::InvalidInput); } let validated_script_configs = - validate_script_configs(coin_params, &request.script_configs).await?; + validate_input_script_configs(coin_params, &request.script_configs).await?; + let validated_output_script_configs = + validate_script_configs(coin_params, &request.output_script_configs)?; let mut xpub_cache = Bip32XpubCache::new(); setup_xpub_cache(&mut xpub_cache, &request.script_configs); @@ -807,9 +855,16 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { // otherwise it is provided in tx_output.payload. let payload: common::Payload = if tx_output.ours { // Compute the payload from the keystore. - let script_config_account = validated_script_configs - .get(tx_output.script_config_index as usize) - .ok_or(Error::InvalidInput)?; + let script_config_account = + if let Some(output_script_config_index) = tx_output.output_script_config_index { + validated_output_script_configs + .get(output_script_config_index as usize) + .ok_or(Error::InvalidInput)? + } else { + validated_script_configs + .get(tx_output.script_config_index as usize) + .ok_or(Error::InvalidInput)? + }; validate_keypath( coin_params, @@ -854,15 +909,15 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { } }; - let is_change = if tx_output.ours { + let is_change = if tx_output.ours && tx_output.output_script_config_index.is_none() { let script_config_account = validated_script_configs .get(tx_output.script_config_index as usize) .ok_or(Error::InvalidInput)?; match &script_config_account.config { // Policy. - ValidatedScriptConfig::Policy(policy) => { - policy.is_change_keypath(&tx_output.keypath)? + ValidatedScriptConfig::Policy { parsed_policy, .. } => { + parsed_policy.is_change_keypath(&tx_output.keypath)? } // Everything else. _ => { @@ -916,9 +971,27 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { payment_request_seen = true; } else { + // When sending coins back to the same account (non-change), or another account of + // the same keystore (change or non-change), we show a prefix to let the user know. + let prefix: Option = if tx_output.ours { + if let Some(output_script_config_index) = tx_output.output_script_config_index { + // Any address belonging to any account of the same keystore. + let output_script_config = validated_output_script_configs + .get(output_script_config_index as usize) + .ok_or(Error::InvalidInput)?; + Some(output_script_config.self_transfer_representation()?) + } else { + // Non-change output of the same account. + Some("This BitBox (same account)".into()) + } + } else { + // Regular outgoing payment, no prefix. + None + }; + transaction::verify_recipient( - &(if tx_output.ours { - format!("This BitBox02: {}", address) + &(if let Some(prefix) = prefix { + format!("{}: {}", prefix, address) } else { address }), @@ -1060,13 +1133,13 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { None, )) } - ValidatedScriptConfig::Policy(policy) => { + ValidatedScriptConfig::Policy { parsed_policy, .. } => { // Get the Taproot tweak based on whether we spend using the internal key (key // path spend) or if we spend using a leaf script. For key path spends, we must // first tweak the private key to match the Taproot output key. For leaf // scripts, we do not tweak. - policy.taproot_spend_info(&mut xpub_cache, &tx_input.keypath)? + parsed_policy.taproot_spend_info(&mut xpub_cache, &tx_input.keypath)? } _ => return Err(Error::Generic), }; @@ -1464,6 +1537,7 @@ mod tests { 10 + HARDENED, ], }], + output_script_configs: vec![], version: self.version, num_inputs: self.inputs.len() as _, num_outputs: self.outputs.len() as _, @@ -1489,6 +1563,7 @@ mod tests { }), keypath: keypath_account.to_vec(), }], + output_script_configs: vec![], version: self.version, num_inputs: self.inputs.len() as _, num_outputs: self.outputs.len() as _, @@ -1582,6 +1657,7 @@ mod tests { }), keypath: vec![84 + HARDENED, 0 + HARDENED, 10 + HARDENED], }], + output_script_configs: vec![], version: 1, num_inputs: 1, num_outputs: 1, @@ -1702,6 +1778,37 @@ mod tests { { // can't mix simple type (singlesig) and multisig configs in one tx + + mock_unlocked_using_mnemonic( + "sudden tenant fault inject concert weather maid people chunk youth stumble grit", + "", + ); + mock_memory(); + + let params = super::super::params::get(pb::BtcCoin::Btc); + let keypath = &[48 + HARDENED, params.bip44_coin, 0 + HARDENED, 2 + HARDENED]; + let multisig = pb::btc_script_config::Multisig { + threshold: 1, + xpubs: vec![ + crate::keystore::get_xpub(keypath).unwrap().into(), + parse_xpub("xpub6ERxBysTYfQyY4USv6c6J1HNVv9hpZFN9LHVPu47Ac4rK8fLy6NnAeeAHyEsMvG4G66ay5aFZii2VM7wT3KxLKX8Q8keZPd67kRGmrD1WJj").unwrap(), + ], + our_xpub_index: 0, + script_type: pb::btc_script_config::multisig::ScriptType::P2wsh + as _, + }; + // Register multisig. + let hash = super::super::multisig::get_hash( + params.coin, + &multisig, + super::super::multisig::SortXpubs::Yes, + keypath, + ) + .unwrap(); + bitbox02::memory::multisig_set_by_hash(&hash, "test name").unwrap(); + + assert!(super::super::multisig::validate(&multisig, keypath).is_ok()); + let mut init_req_invalid = init_req_valid.clone(); init_req_invalid.script_configs = vec![ pb::BtcScriptConfigWithKeypath { @@ -1714,24 +1821,9 @@ mod tests { }, pb::BtcScriptConfigWithKeypath { script_config: Some(pb::BtcScriptConfig { - config: Some(pb::btc_script_config::Config::Multisig( - pb::btc_script_config::Multisig { - threshold: 1, - xpubs: vec![ - pb::XPub { - ..Default::default() - }, - pb::XPub { - ..Default::default() - }, - ], - our_xpub_index: 0, - script_type: pb::btc_script_config::multisig::ScriptType::P2wsh - as _, - }, - )), + config: Some(pb::btc_script_config::Config::Multisig(multisig)), }), - keypath: vec![49 + HARDENED, 0 + HARDENED, 0 + HARDENED], + keypath: keypath.to_vec(), }, ]; assert_eq!( @@ -1752,6 +1844,7 @@ mod tests { }), keypath: vec![84 + HARDENED, 2 + HARDENED, 10 + HARDENED], }], + output_script_configs: vec![], version: 1, num_inputs: 1, num_outputs: 1, @@ -1762,6 +1855,22 @@ mod tests { Err(Error::InvalidInput) ); } + { + // invalid output script config (invalid keypath) + let mut init_req_invalid = init_req_valid.clone(); + init_req_invalid.output_script_configs = vec![pb::BtcScriptConfigWithKeypath { + script_config: Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::SimpleType( + SimpleType::P2wpkh as _, + )), + }), + keypath: vec![], + }]; + assert_eq!( + block_on(process(&init_req_invalid)), + Err(Error::InvalidInput) + ); + } } #[test] @@ -2539,9 +2648,9 @@ mod tests { assert!(unsafe { UI_COUNTER >= 1 }); } - // Test an output that is marked ours but is not a change output by keypath. + // Test an output that is sending to the same account, but is not a change output by keypath. #[test] - fn test_our_non_change_output() { + fn test_self_send_non_change_output_same_account() { let transaction = alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc))); transaction.borrow_mut().outputs[5].keypath[3] = 0; @@ -2554,7 +2663,7 @@ mod tests { if UI_COUNTER == 5 { assert_eq!( address, - "This BitBox02: bc1qnu4x8dlrx6dety47gehf4uhk5tj3q7yhywgry6" + "This BitBox (same account): bc1qnu4x8dlrx6dety47gehf4uhk5tj3q7yhywgry6" ); assert_eq!(amount, "0.00000100 BTC"); } @@ -2584,6 +2693,52 @@ mod tests { } } + // Test an output that is sending to another account of our keystore. + #[test] + fn test_self_send_different_account() { + let transaction = + alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc))); + const DIFFERENT_ACCOUNT: u32 = 20 + HARDENED; + transaction.borrow_mut().outputs[5].keypath[2] = DIFFERENT_ACCOUNT; + transaction.borrow_mut().outputs[5].keypath[3] = 0; + transaction.borrow_mut().outputs[5].output_script_config_index = Some(0); + mock_host_responder(transaction.clone()); + static mut UI_COUNTER: u32 = 0; + mock(Data { + ui_transaction_address_create: Some(Box::new(|amount, address| unsafe { + UI_COUNTER += 1; + if UI_COUNTER == 5 { + assert_eq!( + address, + "This BitBox (account #21): bc1qr9t2u35gzrtznzv6n99f2dj37j9msfffv78cv2" + ); + assert_eq!(amount, "0.00000100 BTC"); + } + true + })), + ui_transaction_fee_create: Some(Box::new(|_total, _fee, _longtouch| true)), + ui_confirm_create: Some(Box::new(move |_params| true)), + ..Default::default() + }); + mock_unlocked(); + let tx = transaction.borrow(); + let mut init_request = tx.init_request(); + init_request.output_script_configs = vec![pb::BtcScriptConfigWithKeypath { + script_config: Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::SimpleType( + SimpleType::P2wpkh as _, + )), + }), + keypath: vec![ + 84 + HARDENED, + super::super::params::get(tx.coin).bip44_coin, + DIFFERENT_ACCOUNT, + ], + }]; + assert!(block_on(process(&init_request)).is_ok()); + assert!(unsafe { UI_COUNTER >= 5 }); + } + /// Exercise the antiklepto protocol #[test] fn test_antiklepto() { @@ -2866,6 +3021,7 @@ mod tests { 2 + HARDENED, ], }], + output_script_configs: vec![], version: tx.version, num_inputs: tx.inputs.len() as _, num_outputs: tx.outputs.len() as _, @@ -2928,6 +3084,7 @@ mod tests { 2 + HARDENED, ], }], + output_script_configs: vec![], version: tx.version, num_inputs: tx.inputs.len() as _, num_outputs: tx.outputs.len() as _, @@ -2995,6 +3152,7 @@ mod tests { 1 + HARDENED, ], }], + output_script_configs: vec![], version: tx.version, num_inputs: tx.inputs.len() as _, num_outputs: tx.outputs.len() as _, @@ -3072,6 +3230,7 @@ mod tests { 2 + HARDENED, ], }], + output_script_configs: vec![], version: tx.version, num_inputs: tx.inputs.len() as _, num_outputs: tx.outputs.len() as _, diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs index b30b7eaf1..8f116b2d6 100644 --- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs +++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs @@ -456,6 +456,10 @@ pub struct BtcSignInitRequest { pub format_unit: i32, #[prost(bool, tag = "9")] pub contains_silent_payment_outputs: bool, + /// used script configs for outputs that send to an address of the same keystore, but not + /// necessarily the same account (as defined by `script_configs` above). + #[prost(message, repeated, tag = "10")] + pub output_script_configs: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `BTCSignInitRequest`. pub mod btc_sign_init_request { @@ -621,7 +625,9 @@ pub struct BtcSignOutputRequest { /// if ours is true #[prost(uint32, repeated, tag = "5")] pub keypath: ::prost::alloc::vec::Vec, - /// If ours is true. References a script config from BTCSignInitRequest + /// If ours is true and `output_script_config_index` is absent. References a script config from + /// BTCSignInitRequest. This allows change output identification and allows us to identify + /// non-change outputs to the same account, so we can display this info to the user. #[prost(uint32, tag = "6")] pub script_config_index: u32, #[prost(uint32, optional, tag = "7")] @@ -630,6 +636,12 @@ pub struct BtcSignOutputRequest { /// BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true. #[prost(message, optional, tag = "8")] pub silent_payment: ::core::option::Option, + /// If ours is true. If set, `script_config_index` is ignored. References an output script config + /// from BTCSignInitRequest. This enables verification that an output belongs to the same keystore, + /// even if it is from a different account than we spend from, allowing us to display this info to + /// the user. + #[prost(uint32, optional, tag = "9")] + pub output_script_config_index: ::core::option::Option, } /// Nested message and enum types in `BTCSignOutputRequest`. pub mod btc_sign_output_request { @@ -1019,6 +1031,8 @@ pub struct CardanoSignTransactionRequest { /// include ttl even if it is zero #[prost(bool, tag = "9")] pub allow_zero_ttl: bool, + /// Tag arrays in the transaction serialization with the 258 tag. + /// See #[prost(bool, tag = "10")] pub tag_cbor_sets: bool, }