bitcoin: add support for OP_RETURN outputs
What changed, and why it matters
This commit adds the ability to include OP_RETURN outputs when signing Bitcoin transactions on a BitBox02 hardware wallet. OP_RETURN is a special Bitcoin output type used to embed small pieces of data (like text or hashes) on the blockchain. The device now shows the user the data being embedded and asks for confirmation before signing. The commit also enforces that OP_RETURN outputs must carry zero Bitcoin value, so no money can accidentally be burned into them.
Review the implementation to ensure the verify_message confirmation cannot be bypassed and that the OP_RETURN script is constructed exactly as specified. Consider whether the single-push restriction and 0-value enforcement are sufficient for all supported coins. No immediate patch appears necessary based on the diff alone, but a security review of the new signing path is warranted before release.
Security signals we found
New transaction output type introduced into signing path
Value-zero enforcement for OP_RETURN to prevent value destruction
User confirmation required via verify_message flow before signing
Payment requests disallowed for OP_RETURN outputs
Address derivation explicitly returns error for OP_RETURN
Only single data push supported, though protocol could allow multiple
Evidence from the diff
The change extends the BTCOutputType enum with OP_RETURN and wires it through the signing flow. In signtx.rs, OP_RETURN outputs are required to have value=0; otherwise signing aborts with InvalidInput. For non-change outputs, the normal recipient/amount confirmation is skipped and the verify_message workflow is reused to display the payload (ASCII if printable, hex otherwise) under an ‘OP_RETURN’ title. The common.rs Payload type now maps OpReturn to ScriptBuf::new_op_return and returns an error for address() since OP_RETURN has no address. The Python library and demo script are updated to expose and exercise the new output type, with a version gate requiring firmware >= 9.24.0.
Changed components
Bitcoin transaction signing API (signtx.rs)Bitcoin script payload handling (common.rs)Protobuf message definitions (messages/btc.proto)Python client library (py/bitbox02)Device UI confirmation workflow (verify_message)Inspect captured patch +221 / −21
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7e00c3..93fd7ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
- simulator: simulate a Nova device
- Add API call to fetch multiple xpubs at once
- Add the option for the simulator to write its memory to file.
+- Bitcoin: add support for OP_RETURN outputs
### 9.23.1
- EVM: add HyperEVM (HYPE) and SONIC (S) to known networks
diff --git a/messages/btc.proto b/messages/btc.proto
index 24669ff..9793542 100644
--- a/messages/btc.proto
+++ b/messages/btc.proto
@@ -175,6 +175,7 @@ enum BTCOutputType {
P2WPKH = 3;
P2WSH = 4;
P2TR = 5;
+ OP_RETURN = 6;
}
message BTCSignOutputRequest {
diff --git a/py/bitbox02/CHANGELOG.md b/py/bitbox02/CHANGELOG.md
index 7ad876a..e277e2e 100644
--- a/py/bitbox02/CHANGELOG.md
+++ b/py/bitbox02/CHANGELOG.md
@@ -4,6 +4,7 @@
# 7.1.0
- Add `btc_xpubs()` to fetch multiple xpubs at once
+- Bitcoin: add support for OP_RETURN outputs
# 7.0.0
- get_info: add optional device initialized boolean to returned tuple
diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
index 753cde5..5a0c20a 100644
--- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py
+++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
@@ -467,6 +467,16 @@ class BitBox02(BitBoxCommonAPI):
# Attaching output info supported since v9.22.0.
self._require_atleast(semver.VersionInfo(9, 22, 0))
+ if any(
+ map(
+ lambda output: isinstance(output, BTCOutputExternal)
+ and output.type == btc.BTCOutputType.OP_RETURN,
+ outputs,
+ )
+ ):
+ # OP_RETURN supported sice v9.24.0
+ self._require_atleast(semver.VersionInfo(9, 24, 0))
+
supports_antiklepto = self.version >= semver.VersionInfo(9, 4, 0)
sigs: List[Tuple[int, bytes]] = []
diff --git a/py/bitbox02/bitbox02/communication/generated/btc_pb2.py b/py/bitbox02/bitbox02/communication/generated/btc_pb2.py
index 45148fd..251ae66 100644
--- a/py/bitbox02/bitbox02/communication/generated/btc_pb2.py
+++ b/py/bitbox02/bitbox02/communication/generated/btc_pb2.py
@@ -15,7 +15,7 @@ from . import common_pb2 as common__pb2
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\"\xdf\x01\n\x0f\x42TCXpubsRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12\x41\n\txpub_type\x18\x02 \x01(\x0e\x32..shiftcrypto.bitbox02.BTCXpubsRequest.XPubType\x12/\n\x08keypaths\x18\x03 \x03(\x0b\x32\x1d.shiftcrypto.bitbox02.Keypath\"+\n\x08XPubType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04XPUB\x10\x01\x12\x08\n\x04TPUB\x10\x02\"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\"\xb9\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\x12\x36\n\x05xpubs\x18\t \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCXpubsRequestH\x00\x42\t\n\x07request\"\xc4\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\x12\x32\n\x04pubs\x18\x06 \x01(\x0b\x32\".shiftcrypto.bitbox02.PubsResponseH\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\"\xdf\x01\n\x0f\x42TCXpubsRequest\x12+\n\x04\x63oin\x18\x01 \x01(\x0e\x32\x1d.shiftcrypto.bitbox02.BTCCoin\x12\x41\n\txpub_type\x18\x02 \x01(\x0e\x32..shiftcrypto.bitbox02.BTCXpubsRequest.XPubType\x12/\n\x08keypaths\x18\x03 \x03(\x0b\x32\x1d.shiftcrypto.bitbox02.Keypath\"+\n\x08XPubType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04XPUB\x10\x01\x12\x08\n\x04TPUB\x10\x02\"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\"\xb9\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\x12\x36\n\x05xpubs\x18\t \x01(\x0b\x32%.shiftcrypto.bitbox02.BTCXpubsRequestH\x00\x42\t\n\x07request\"\xc4\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\x12\x32\n\x04pubs\x18\x06 \x01(\x0b\x32\".shiftcrypto.bitbox02.PubsResponseH\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*a\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\x12\r\n\tOP_RETURN\x10\x06\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'btc_pb2', globals())
@@ -25,7 +25,7 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_BTCCOIN._serialized_start=5551
_BTCCOIN._serialized_end=5608
_BTCOUTPUTTYPE._serialized_start=5610
- _BTCOUTPUTTYPE._serialized_end=5692
+ _BTCOUTPUTTYPE._serialized_end=5707
_BTCSCRIPTCONFIG._serialized_start=68
_BTCSCRIPTCONFIG._serialized_end=650
_BTCSCRIPTCONFIG_MULTISIG._serialized_start=293
diff --git a/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
index 05022bd..c7f89ac 100644
--- a/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
+++ b/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
@@ -70,6 +70,7 @@ class _BTCOutputTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
P2WPKH: _BTCOutputType.ValueType # 3
P2WSH: _BTCOutputType.ValueType # 4
P2TR: _BTCOutputType.ValueType # 5
+ OP_RETURN: _BTCOutputType.ValueType # 6
class BTCOutputType(_BTCOutputType, metaclass=_BTCOutputTypeEnumTypeWrapper): ...
@@ -79,6 +80,7 @@ P2SH: BTCOutputType.ValueType # 2
P2WPKH: BTCOutputType.ValueType # 3
P2WSH: BTCOutputType.ValueType # 4
P2TR: BTCOutputType.ValueType # 5
+OP_RETURN: BTCOutputType.ValueType # 6
global___BTCOutputType = BTCOutputType
@typing.final
diff --git a/py/send_message.py b/py/send_message.py
index a611c27..dfb74ae 100755
--- a/py/send_message.py
+++ b/py/send_message.py
@@ -788,6 +788,43 @@ class SendMessage:
for input_index, sig in sigs:
print("Signature for input {}: {}".format(input_index, sig.hex()))
+ def _sign_btc_op_return(
+ 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.append(
+ bitbox02.BTCOutputExternal(
+ output_type=bitbox02.btc.OP_RETURN,
+ output_payload=b"hello world",
+ value=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,
+ )
+ for input_index, sig in sigs:
+ print("Signature for input {}: {}".format(input_index, sig.hex()))
+
def _sign_btc_tx_from_raw(self) -> None:
"""
Experiment with testnet transactions.
@@ -896,6 +933,7 @@ class SendMessage:
("Taproot inputs", self._sign_btc_taproot_inputs),
("Taproot output", self._sign_btc_taproot_output),
("Policy", self._sign_btc_policy),
+ ("OP_RETURN", self._sign_btc_op_return),
("From testnet tx ID", self._sign_btc_tx_from_raw),
)
choice = ask_user(choices)
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 6e13d78..86a48e1 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
@@ -211,7 +211,8 @@ impl Payload {
}
}
- /// Converts a payload to an address.
+ /// Converts a payload to an address. Returns an error for invalid input or if an address does
+ /// not exist for the output type (e.g. OP_RETURN).
pub fn address(&self, params: &Params) -> Result<String, ()> {
let payload = self.data.as_slice();
match self.output_type {
@@ -252,6 +253,7 @@ impl Payload {
}
encode_segwit_addr(params.bech32_hrp, 1, payload)
}
+ BtcOutputType::OpReturn => Err(()),
}
}
@@ -289,6 +291,11 @@ impl Payload {
);
ScriptBuf::new_p2tr_tweaked(tweaked)
}
+ BtcOutputType::OpReturn => {
+ let pushbytes: &bitcoin::script::PushBytes =
+ payload.try_into().map_err(|_| Error::InvalidInput)?;
+ ScriptBuf::new_op_return(pushbytes)
+ }
};
Ok(script.into_bytes())
}
@@ -657,6 +664,22 @@ mod tests {
output_type: BtcOutputType::P2tr,
expected_pkscript: "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c",
},
+ Test {
+ payload: "aabbcc",
+ output_type: BtcOutputType::OpReturn,
+ expected_pkscript: "6a03aabbcc",
+ },
+ Test {
+ payload: "",
+ output_type: BtcOutputType::OpReturn,
+ expected_pkscript: "6a00",
+ },
+ Test {
+ // 80 byte payload
+ payload: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ output_type: BtcOutputType::OpReturn,
+ expected_pkscript: "6a4c50aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ },
];
for test in tests {
@@ -667,15 +690,17 @@ mod tests {
};
assert_eq!(
hex::encode(payload.pk_script(params).unwrap()),
- test.expected_pkscript
+ test.expected_pkscript,
);
- // Payload of wrong size
- let payload = Payload {
- data: hex::decode(&test.payload[2..]).unwrap(),
- output_type: test.output_type,
- };
- assert_eq!(payload.pk_script(params), Err(Error::Generic));
+ // Payload of wrong size. Does not apply to OpReturn, almost any size is accepted.
+ if test.output_type != BtcOutputType::OpReturn {
+ let payload = Payload {
+ data: hex::decode(&test.payload[2..]).unwrap(),
+ output_type: test.output_type,
+ };
+ assert_eq!(payload.pk_script(params), Err(Error::Generic));
+ }
}
}
}
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 c7f8d37..11dc376 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -1,4 +1,4 @@
-// Copyright 2022-2024 Shift Crypto AG
+// Copyright 2022-2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -853,12 +853,18 @@ async fn _process(
};
}
- if tx_output.value == 0 {
+ let output_type = pb::BtcOutputType::try_from(tx_output.r#type)?;
+
+ // We don't allow regular outputs to have 0 value.
+ // OP_RETURN outputs however we require to have 0 value.
+ if output_type == pb::BtcOutputType::OpReturn {
+ if tx_output.value != 0 {
+ return Err(Error::InvalidInput);
+ }
+ } else if tx_output.value == 0 {
return Err(Error::InvalidInput);
}
- let output_type = pb::BtcOutputType::try_from(tx_output.r#type)?;
-
// Get payload. If the output is marked ours, we compute the payload from the keystore,
// otherwise it is provided in tx_output.payload.
let payload: common::Payload = if tx_output.ours {
@@ -949,16 +955,21 @@ async fn _process(
if !is_change {
// Verify output if it is not a change output.
// Assemble address to display, get user confirmation.
- let address = if let Some(sp) = tx_output.silent_payment.as_ref() {
- sp.address.clone()
- } else {
- payload.address(coin_params)?
+ let address = || -> Result<String, Error> {
+ if let Some(sp) = tx_output.silent_payment.as_ref() {
+ Ok(sp.address.clone())
+ } else {
+ Ok(payload.address(coin_params)?)
+ }
};
if let Some(output_payment_request_index) = tx_output.payment_request_index {
if output_payment_request_index != 0 {
return Err(Error::InvalidInput);
}
+ if output_type == pb::BtcOutputType::OpReturn {
+ return Err(Error::InvalidInput);
+ }
if payment_request_seen {
return Err(Error::InvalidInput);
}
@@ -970,7 +981,7 @@ async fn _process(
coin_params,
&payment_request,
tx_output.value,
- &address,
+ &address()?,
)
.is_err()
{
@@ -979,6 +990,16 @@ async fn _process(
}
payment_request_seen = true;
+ } else if output_type == pb::BtcOutputType::OpReturn {
+ // OP_RETURN value was validated to be 0 above, so we don't need to show the amount.
+ crate::workflow::verify_message::verify(
+ hal,
+ "OP_RETURN",
+ "OP_RETURN",
+ &tx_output.payload,
+ false,
+ )
+ .await?;
} 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.
@@ -1001,9 +1022,9 @@ async fn _process(
hal.ui()
.verify_recipient(
&(if let Some(prefix) = prefix {
- format!("{}: {}", prefix, address)
+ format!("{}: {}", prefix, address()?)
} else {
- address
+ address()?
}),
&format_amount(coin_params, format_unit, tx_output.value)?,
)
@@ -3683,4 +3704,102 @@ mod tests {
]
);
}
+
+ #[test]
+ fn test_op_return() {
+ let transaction =
+ alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc)));
+
+ // Attach OP_RETURN output
+ {
+ let mut tx = transaction.borrow_mut();
+ tx.outputs.push(pb::BtcSignOutputRequest {
+ r#type: pb::BtcOutputType::OpReturn as _,
+ value: 0,
+ payload: b"hello world".to_vec(),
+ ..Default::default()
+ });
+ }
+
+ mock_host_responder(transaction.clone());
+ mock_unlocked();
+ bitbox02::random::fake_reset();
+ let init_request = transaction.borrow().init_request();
+
+ let mut mock_hal = TestingHal::new();
+ let result = block_on(process(&mut mock_hal, &init_request));
+
+ match result {
+ Ok(Response::BtcSignNext(next)) => {
+ assert!(next.has_signature);
+ assert_eq!(
+ hex::encode(next.signature),
+ "f49c71b89ec3510ebebae9aff9f967ad9bb6cc0c4cddbdf851f97e47e9922646622459e522b0751fa246e49a8e48417344a5384a9f68c1c85cd03804b35e1e1e",
+ );
+ }
+ _ => panic!("wrong result"),
+ }
+
+ assert!(mock_hal.ui.contains_confirm("OP_RETURN", "hello world"));
+ }
+
+ #[test]
+ fn test_op_return_nonascii() {
+ let transaction =
+ alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc)));
+
+ // Attach OP_RETURN output
+ {
+ let mut tx = transaction.borrow_mut();
+ tx.outputs.push(pb::BtcSignOutputRequest {
+ r#type: pb::BtcOutputType::OpReturn as _,
+ value: 0,
+ payload: vec![1, 2, 3, 4, 5],
+ ..Default::default()
+ });
+ }
+
+ mock_host_responder(transaction.clone());
+ mock_unlocked();
+ bitbox02::random::fake_reset();
+ let init_request = transaction.borrow().init_request();
+
+ let mut mock_hal = TestingHal::new();
+ let result = block_on(process(&mut mock_hal, &init_request));
+ assert!(result.is_ok());
+
+ assert!(
+ mock_hal
+ .ui
+ .contains_confirm("OP_RETURN\ndata (hex)", "0102030405")
+ );
+ }
+
+ #[test]
+ fn test_op_return_fail_nonzero_value() {
+ let transaction =
+ alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc)));
+
+ // Attach OP_RETURN output
+ {
+ let mut tx = transaction.borrow_mut();
+ tx.outputs.push(pb::BtcSignOutputRequest {
+ r#type: pb::BtcOutputType::OpReturn as _,
+ value: 100,
+ payload: b"hello world".to_vec(),
+ ..Default::default()
+ });
+ }
+
+ mock_host_responder(transaction.clone());
+ mock_unlocked();
+ bitbox02::random::fake_reset();
+ let init_request = transaction.borrow().init_request();
+
+ let mut mock_hal = TestingHal::new();
+ assert_eq!(
+ block_on(process(&mut mock_hal, &init_request)),
+ Err(Error::InvalidInput)
+ );
+ }
}
diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
index 018adf9..3a5f532 100644
--- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
+++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
@@ -1060,6 +1060,7 @@ pub enum BtcOutputType {
P2wpkh = 3,
P2wsh = 4,
P2tr = 5,
+ OpReturn = 6,
}
impl BtcOutputType {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -1074,6 +1075,7 @@ impl BtcOutputType {
BtcOutputType::P2wpkh => "P2WPKH",
BtcOutputType::P2wsh => "P2WSH",
BtcOutputType::P2tr => "P2TR",
+ BtcOutputType::OpReturn => "OP_RETURN",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -1085,6 +1087,7 @@ impl BtcOutputType {
"P2WPKH" => Some(Self::P2wpkh),
"P2WSH" => Some(Self::P2wsh),
"P2TR" => Some(Self::P2tr),
+ "OP_RETURN" => Some(Self::OpReturn),
_ => None,
}
}
Why this scored 38/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.