diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ffbb385..a84e32b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately. ## Firmware ### [Unreleased] +- Bitcoin: add support for sending to silent payment (BIP-352) addresses ### 9.20.0 - Bitcoin: UX improvements for payment request confirmations diff --git a/messages/btc.proto b/messages/btc.proto index b37c06b67..67db4dbcd 100644 --- a/messages/btc.proto +++ b/messages/btc.proto @@ -112,6 +112,7 @@ message BTCSignInitRequest { SAT = 1; } FormatUnit format_unit = 8; + bool contains_silent_payment_outputs = 9; } message BTCSignNextResponse { @@ -135,6 +136,9 @@ message BTCSignNextResponse { // Previous tx's input/output index in case of PREV_INPUT or PREV_OUTPUT, for the input at `index`. uint32 prev_index = 5; AntiKleptoSignerCommitment anti_klepto_signer_commitment = 6; + // Generated output. The host *must* verify its correctness using silent_payment_dleq_proof. + bytes generated_output_pkscript = 7; + bytes silent_payment_dleq_proof = 8; } message BTCSignInputRequest { @@ -158,6 +162,10 @@ enum BTCOutputType { } message BTCSignOutputRequest { + // https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki + message SilentPayment { + string address = 1; + } bool ours = 1; BTCOutputType type = 2; // if ours is false // 20 bytes for p2pkh, p2sh, pw2wpkh. 32 bytes for p2wsh. @@ -167,6 +175,9 @@ message BTCSignOutputRequest { // If ours is true. References a script config from BTCSignInitRequest 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; } message BTCScriptConfigRegistration { diff --git a/py/bitbox02/bitbox02/communication/generated/btc_pb2.py b/py/bitbox02/bitbox02/communication/generated/btc_pb2.py index 7ac22c05c..a01f2c1f2 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\"\xc5\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\"\"\n\nFormatUnit\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x07\n\x03SAT\x10\x01\"\xfe\x02\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\"\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\"\xe3\x01\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\x42\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*/\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*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\"\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*/\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*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=4837 - _BTCCOIN._serialized_end=4884 - _BTCOUTPUTTYPE._serialized_start=4886 - _BTCOUTPUTTYPE._serialized_end=4968 + _BTCCOIN._serialized_start=5064 + _BTCCOIN._serialized_end=5111 + _BTCOUTPUTTYPE._serialized_start=5113 + _BTCOUTPUTTYPE._serialized_end=5195 _BTCSCRIPTCONFIG._serialized_start=68 _BTCSCRIPTCONFIG._serialized_end=650 _BTCSCRIPTCONFIG_MULTISIG._serialized_start=293 @@ -43,47 +43,49 @@ _BTCSCRIPTCONFIGWITHKEYPATH._serialized_start=1035 _BTCSCRIPTCONFIGWITHKEYPATH._serialized_end=1142 _BTCSIGNINITREQUEST._serialized_start=1145 - _BTCSIGNINITREQUEST._serialized_end=1470 - _BTCSIGNINITREQUEST_FORMATUNIT._serialized_start=1436 - _BTCSIGNINITREQUEST_FORMATUNIT._serialized_end=1470 - _BTCSIGNNEXTRESPONSE._serialized_start=1473 - _BTCSIGNNEXTRESPONSE._serialized_end=1855 - _BTCSIGNNEXTRESPONSE_TYPE._serialized_start=1725 - _BTCSIGNNEXTRESPONSE_TYPE._serialized_end=1855 - _BTCSIGNINPUTREQUEST._serialized_start=1858 - _BTCSIGNINPUTREQUEST._serialized_end=2092 - _BTCSIGNOUTPUTREQUEST._serialized_start=2095 - _BTCSIGNOUTPUTREQUEST._serialized_end=2322 - _BTCSCRIPTCONFIGREGISTRATION._serialized_start=2325 - _BTCSCRIPTCONFIGREGISTRATION._serialized_end=2478 - _BTCSUCCESS._serialized_start=2480 - _BTCSUCCESS._serialized_end=2492 - _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_start=2494 - _BTCISSCRIPTCONFIGREGISTEREDREQUEST._serialized_end=2603 - _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_start=2605 - _BTCISSCRIPTCONFIGREGISTEREDRESPONSE._serialized_end=2665 - _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_start=2668 - _BTCREGISTERSCRIPTCONFIGREQUEST._serialized_end=2920 - _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_start=2871 - _BTCREGISTERSCRIPTCONFIGREQUEST_XPUBTYPE._serialized_end=2920 - _BTCPREVTXINITREQUEST._serialized_start=2922 - _BTCPREVTXINITREQUEST._serialized_end=3020 - _BTCPREVTXINPUTREQUEST._serialized_start=3022 - _BTCPREVTXINPUTREQUEST._serialized_end=3136 - _BTCPREVTXOUTPUTREQUEST._serialized_start=3138 - _BTCPREVTXOUTPUTREQUEST._serialized_end=3200 - _BTCPAYMENTREQUESTREQUEST._serialized_start=3203 - _BTCPAYMENTREQUESTREQUEST._serialized_end=3502 - _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_start=3379 - _BTCPAYMENTREQUESTREQUEST_MEMO._serialized_end=3502 - _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_start=3470 - _BTCPAYMENTREQUESTREQUEST_MEMO_TEXTMEMO._serialized_end=3494 - _BTCSIGNMESSAGEREQUEST._serialized_start=3505 - _BTCSIGNMESSAGEREQUEST._serialized_end=3743 - _BTCSIGNMESSAGERESPONSE._serialized_start=3745 - _BTCSIGNMESSAGERESPONSE._serialized_end=3788 - _BTCREQUEST._serialized_start=3791 - _BTCREQUEST._serialized_end=4432 - _BTCRESPONSE._serialized_start=4435 - _BTCRESPONSE._serialized_end=4835 + _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 # @@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 afcc61803..97f2a0dcc 100644 --- a/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi @@ -286,6 +286,7 @@ class BTCSignInitRequest(google.protobuf.message.Message): NUM_OUTPUTS_FIELD_NUMBER: builtins.int LOCKTIME_FIELD_NUMBER: builtins.int FORMAT_UNIT_FIELD_NUMBER: builtins.int + CONTAINS_SILENT_PAYMENT_OUTPUTS_FIELD_NUMBER: builtins.int coin: global___BTCCoin.ValueType @property def script_configs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BTCScriptConfigWithKeypath]: @@ -300,6 +301,7 @@ class BTCSignInitRequest(google.protobuf.message.Message): """must be <500000000""" format_unit: global___BTCSignInitRequest.FormatUnit.ValueType + contains_silent_payment_outputs: builtins.bool def __init__(self, *, coin: global___BTCCoin.ValueType = ..., @@ -309,8 +311,9 @@ class BTCSignInitRequest(google.protobuf.message.Message): num_outputs: builtins.int = ..., locktime: builtins.int = ..., format_unit: global___BTCSignInitRequest.FormatUnit.ValueType = ..., + contains_silent_payment_outputs: builtins.bool = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["coin",b"coin","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","script_configs",b"script_configs","version",b"version"]) -> None: ... global___BTCSignInitRequest = BTCSignInitRequest class BTCSignNextResponse(google.protobuf.message.Message): @@ -350,6 +353,8 @@ class BTCSignNextResponse(google.protobuf.message.Message): SIGNATURE_FIELD_NUMBER: builtins.int PREV_INDEX_FIELD_NUMBER: builtins.int ANTI_KLEPTO_SIGNER_COMMITMENT_FIELD_NUMBER: builtins.int + GENERATED_OUTPUT_PKSCRIPT_FIELD_NUMBER: builtins.int + SILENT_PAYMENT_DLEQ_PROOF_FIELD_NUMBER: builtins.int type: global___BTCSignNextResponse.Type.ValueType index: builtins.int """index of the current input or output""" @@ -365,6 +370,10 @@ class BTCSignNextResponse(google.protobuf.message.Message): @property def anti_klepto_signer_commitment(self) -> antiklepto_pb2.AntiKleptoSignerCommitment: ... + generated_output_pkscript: builtins.bytes + """Generated output. The host *must* verify its correctness using silent_payment_dleq_proof.""" + + silent_payment_dleq_proof: builtins.bytes def __init__(self, *, type: global___BTCSignNextResponse.Type.ValueType = ..., @@ -373,9 +382,11 @@ class BTCSignNextResponse(google.protobuf.message.Message): signature: builtins.bytes = ..., prev_index: builtins.int = ..., anti_klepto_signer_commitment: typing.Optional[antiklepto_pb2.AntiKleptoSignerCommitment] = ..., + generated_output_pkscript: builtins.bytes = ..., + silent_payment_dleq_proof: builtins.bytes = ..., ) -> None: ... def HasField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment","has_signature",b"has_signature","index",b"index","prev_index",b"prev_index","signature",b"signature","type",b"type"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment","generated_output_pkscript",b"generated_output_pkscript","has_signature",b"has_signature","index",b"index","prev_index",b"prev_index","signature",b"signature","silent_payment_dleq_proof",b"silent_payment_dleq_proof","type",b"type"]) -> None: ... global___BTCSignNextResponse = BTCSignNextResponse class BTCSignInputRequest(google.protobuf.message.Message): @@ -418,6 +429,17 @@ global___BTCSignInputRequest = BTCSignInputRequest class BTCSignOutputRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + class SilentPayment(google.protobuf.message.Message): + """https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + ADDRESS_FIELD_NUMBER: builtins.int + address: typing.Text + def __init__(self, + *, + address: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["address",b"address"]) -> None: ... + OURS_FIELD_NUMBER: builtins.int TYPE_FIELD_NUMBER: builtins.int VALUE_FIELD_NUMBER: builtins.int @@ -425,6 +447,7 @@ class BTCSignOutputRequest(google.protobuf.message.Message): KEYPATH_FIELD_NUMBER: builtins.int SCRIPT_CONFIG_INDEX_FIELD_NUMBER: builtins.int PAYMENT_REQUEST_INDEX_FIELD_NUMBER: builtins.int + SILENT_PAYMENT_FIELD_NUMBER: builtins.int ours: builtins.bool type: global___BTCOutputType.ValueType """if ours is false""" @@ -443,6 +466,12 @@ class BTCSignOutputRequest(google.protobuf.message.Message): """If ours is true. References a script config from BTCSignInitRequest""" payment_request_index: builtins.int + @property + def silent_payment(self) -> global___BTCSignOutputRequest.SilentPayment: + """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. + """ + pass def __init__(self, *, ours: builtins.bool = ..., @@ -452,9 +481,10 @@ class BTCSignOutputRequest(google.protobuf.message.Message): keypath: typing.Optional[typing.Iterable[builtins.int]] = ..., script_config_index: builtins.int = ..., payment_request_index: typing.Optional[builtins.int] = ..., + silent_payment: typing.Optional[global___BTCSignOutputRequest.SilentPayment] = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","payment_request_index",b"payment_request_index"]) -> 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","type",b"type","value",b"value"]) -> 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 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/src/keystore.c b/src/keystore.c index e09420e15..8d695fcc2 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -1000,6 +1000,29 @@ bool keystore_secp256k1_schnorr_bip86_sign( return secp256k1_schnorrsig_verify(ctx, sig64_out, msg32, 32, &pubkey) == 1; } +bool keystore_secp256k1_get_private_key( + const uint32_t* keypath, + const size_t keypath_len, + bool tweak_bip86, + uint8_t* key_out) +{ + if (tweak_bip86) { + secp256k1_keypair __attribute__((__cleanup__(_cleanup_keypair))) keypair = {0}; + secp256k1_xonly_pubkey pubkey = {0}; + if (!_schnorr_bip86_keypair(keypath, keypath_len, &keypair, &pubkey)) { + return false; + } + const secp256k1_context* ctx = wally_get_secp_context(); + return secp256k1_keypair_sec(ctx, key_out, &keypair) == 1; + } + struct ext_key xprv __attribute__((__cleanup__(keystore_zero_xkey))) = {0}; + if (!_get_xprv_twice(keypath, keypath_len, &xprv)) { + return false; + } + memcpy(key_out, xprv.priv_key + 1, 32); + return true; +} + #ifdef TESTING void keystore_mock_unlocked(const uint8_t* seed, size_t seed_len, const uint8_t* bip39_seed) { diff --git a/src/keystore.h b/src/keystore.h index e3624ee08..b8150a33d 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -303,6 +303,12 @@ USE_RESULT bool keystore_secp256k1_schnorr_bip86_sign( const uint8_t* msg32, uint8_t* sig64_out); +USE_RESULT bool keystore_secp256k1_get_private_key( + const uint32_t* keypath, + size_t keypath_len, + bool tweak_bip86, + uint8_t* key_out); + #ifdef TESTING /** * convenience to mock the keystore state (locked, seed) in tests. diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 5b734b049..040bdbc1d 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -108,6 +108,7 @@ dependencies = [ "prost", "sha2", "sha3", + "streaming-silent-payments", "util", "zeroize", ] diff --git a/src/rust/bitbox02-rust/Cargo.toml b/src/rust/bitbox02-rust/Cargo.toml index 24ec5c12f..b8f7eba5e 100644 --- a/src/rust/bitbox02-rust/Cargo.toml +++ b/src/rust/bitbox02-rust/Cargo.toml @@ -32,6 +32,7 @@ util = { path = "../util" } erc20_params = { path = "../erc20_params", optional = true } binascii = { version = "0.1.4", default-features = false, features = ["encode"] } bitbox02-noise = {path = "../bitbox02-noise"} +streaming-silent-payments = { path = "../streaming-silent-payments", optional = true } hex = { workspace = true } sha2 = { workspace = true } sha3 = { workspace = true, optional = true } @@ -82,6 +83,7 @@ app-bitcoin = [ "bech32", "miniscript", "bitcoin", + "streaming-silent-payments", # enable this feature in the deps "bitbox02/app-bitcoin", ] 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 fc92b827d..3023c039a 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs @@ -34,6 +34,10 @@ use pb::btc_sign_init_request::FormatUnit; use pb::btc_sign_next_response::Type as NextType; use sha2::{Digest, Sha256}; +use bitcoin::hashes::Hash; + +use streaming_silent_payments::SilentPayment; + /// After each request from the host, we send a `BtcSignNextResponse` response back to the host, /// containing information which request we want next, and containing additional metadata if /// available (e.g. a signature after signing an input). @@ -80,14 +84,7 @@ async fn get_request( response.next.prev_index = prev_index; } let request = crate::hww::next_request(response.to_protobuf()).await?; - response.next = pb::BtcSignNextResponse { - r#type: 0, - index: 0, - has_signature: false, - signature: vec![], - prev_index: 0, - anti_klepto_signer_commitment: None, - }; + response.next = Default::default(); Ok(request) } @@ -511,6 +508,50 @@ fn setup_xpub_cache(cache: &mut Bip32XpubCache, script_configs: &[pb::BtcScriptC } } +impl TryFrom for streaming_silent_payments::Network { + type Error = Error; + fn try_from(value: pb::BtcCoin) -> Result { + match value { + pb::BtcCoin::Btc => Ok(streaming_silent_payments::Network::Btc), + pb::BtcCoin::Tbtc => Ok(streaming_silent_payments::Network::Tbtc), + _ => Err(Error::InvalidInput), + } + } +} + +impl From<&pb::btc_script_config::SimpleType> for streaming_silent_payments::InputType { + fn from(value: &pb::btc_script_config::SimpleType) -> streaming_silent_payments::InputType { + match value { + pb::btc_script_config::SimpleType::P2wpkhP2sh => { + streaming_silent_payments::InputType::P2wpkhP2sh + } + pb::btc_script_config::SimpleType::P2wpkh => { + streaming_silent_payments::InputType::P2wpkh + } + pb::btc_script_config::SimpleType::P2tr => { + streaming_silent_payments::InputType::P2trKeypathSpend + } + } + } +} + +impl<'a> TryFrom<&'a ValidatedScriptConfigWithKeypath<'a>> + for streaming_silent_payments::InputType +{ + type Error = Error; + fn try_from( + value: &'a ValidatedScriptConfigWithKeypath, + ) -> Result { + match value { + ValidatedScriptConfigWithKeypath { + config: ValidatedScriptConfig::SimpleType(simple_type), + .. + } => Ok(simple_type.into()), + _ => Err(Error::InvalidInput), + } + } +} + /// Singing flow: /// /// init @@ -594,14 +635,7 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { }; let mut next_response = NextResponse { - next: pb::BtcSignNextResponse { - r#type: 0, - index: 0, - has_signature: false, - signature: vec![], - prev_index: 0, - anti_klepto_signer_commitment: None, - }, + next: Default::default(), wrap: false, }; @@ -619,6 +653,12 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { // Are all inputs taproot? let taproot_only = validated_script_configs.iter().all(is_taproot); + let mut silent_payment = if request.contains_silent_payment_outputs { + Some(SilentPayment::new(coin.try_into()?)) + } else { + None + }; + for input_index in 0..request.num_inputs { // Update progress. bitbox02::ui::progress_set( @@ -678,6 +718,28 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { ) .await?; } + + if let Some(ref mut silent_payment) = silent_payment { + let private_key = bitcoin::secp256k1::SecretKey::from_slice( + &bitbox02::keystore::secp256k1_get_private_key( + &tx_input.keypath, + is_taproot(script_config_account), + )?, + ) + .map_err(|_| Error::Generic)?; + + silent_payment + .add_input( + script_config_account.try_into()?, + &private_key, + bitcoin::OutPoint::new( + bitcoin::Txid::from_slice(&tx_input.prev_out_hash) + .map_err(|_| Error::InvalidInput)?, + tx_input.prev_out_index, + ), + ) + .map_err(|_| Error::InvalidInput)?; + } } // The progress for loading the inputs is 100%. @@ -722,6 +784,8 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { 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 { @@ -745,9 +809,31 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { )? } else { // Take payload from provided output. - common::Payload { - data: tx_output.payload.clone(), - output_type: pb::BtcOutputType::try_from(tx_output.r#type)?, + + // Create silent payment output. + if let Some(output_silent_payment) = tx_output.silent_payment.as_ref() { + match silent_payment { + None => return Err(Error::InvalidInput), + Some(ref mut silent_payment) => { + let sp_output = silent_payment + .create_output(&output_silent_payment.address) + .map_err(|_| Error::InvalidInput)?; + let payload = common::Payload { + data: sp_output.pubkey.serialize().to_vec(), + output_type: pb::BtcOutputType::P2tr, + }; + next_response.next.generated_output_pkscript = + payload.pk_script(coin_params)?; + next_response.next.silent_payment_dleq_proof = + sp_output.dleq_proof.to_vec(); + payload + } + } + } else { + common::Payload { + data: tx_output.payload.clone(), + output_type, + } } }; @@ -776,10 +862,18 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result { return Err(Error::InvalidInput); } + if is_change && tx_output.silent_payment.is_some() { + return Err(Error::InvalidInput); + } + if !is_change { // Verify output if it is not a change output. // Assemble address to display, get user confirmation. - let address = payload.address(coin_params)?; + let address = if let Some(sp) = tx_output.silent_payment.as_ref() { + sp.address.clone() + } else { + payload.address(coin_params)? + }; if let Some(output_payment_request_index) = tx_output.payment_request_index { if output_payment_request_index != 0 { @@ -1171,9 +1265,7 @@ mod tests { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, ], - keypath: vec![], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { ours: false, @@ -1183,9 +1275,7 @@ mod tests { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, ], - keypath: vec![], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { ours: false, @@ -1195,9 +1285,7 @@ mod tests { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, ], - keypath: vec![], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { ours: false, @@ -1208,29 +1296,23 @@ mod tests { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, ], - keypath: vec![], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { // change ours: true, r#type: 0, value: 690000000, // btc 6.9 - payload: vec![], keypath: vec![84 + HARDENED, bip44_coin, 10 + HARDENED, 1, 3], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { // change #2 ours: true, r#type: 0, value: 100, - payload: vec![], keypath: vec![84 + HARDENED, bip44_coin, 10 + HARDENED, 1, 30], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, ], locktime: 0, @@ -1283,10 +1365,8 @@ mod tests { ours: true, r#type: pb::BtcOutputType::Unknown as _, value: 9825, // btc 0.00009825 - payload: vec![], keypath: vec![48 + HARDENED, bip44_coin, 0 + HARDENED, 2 + HARDENED, 1, 0], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, pb::BtcSignOutputRequest { ours: false, @@ -1297,9 +1377,7 @@ mod tests { 0x83, 0xe7, 0x57, 0x84, 0x67, 0x25, 0xa3, 0xf6, 0x23, 0xae, 0xc2, 0x09, 0x76, 0xd3, 0x0e, 0x29, 0xb0, 0xd4, 0xb3, 0x5b, ], - keypath: vec![], - script_config_index: 0, - payment_request_index: None, + ..Default::default() }, ], locktime: 1663289, @@ -1339,6 +1417,10 @@ mod tests { num_outputs: self.outputs.len() as _, locktime: self.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: self + .outputs + .iter() + .any(|output| output.silent_payment.is_some()), } } @@ -1360,6 +1442,7 @@ mod tests { num_outputs: self.outputs.len() as _, locktime: self.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, } } @@ -1452,6 +1535,7 @@ mod tests { num_outputs: 1, locktime: 0, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, }; { @@ -1621,6 +1705,7 @@ mod tests { num_outputs: 1, locktime: 0, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, })), Err(Error::InvalidInput) ); @@ -2338,6 +2423,70 @@ mod tests { } } + #[test] + fn test_silent_payment_output() { + let transaction = + alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new(pb::BtcCoin::Btc))); + + // Make an input a P2TR input to verify the right (tweaked) private key is used in the + // derivation of the silent payment output. + transaction.borrow_mut().inputs[0].input.script_config_index = 1; + transaction.borrow_mut().inputs[0].input.keypath[0] = 86 + HARDENED; + + // Make first output a silent payment output. type and payload + // are ignored. + transaction.borrow_mut().outputs[0].r#type = pb::BtcOutputType::Unknown as _; + transaction.borrow_mut().outputs[0].payload = vec![]; + transaction.borrow_mut().outputs[0].silent_payment = + Some(pb::btc_sign_output_request::SilentPayment { + address: "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv".into(), + }); + let tx = transaction.clone(); + *crate::hww::MOCK_NEXT_REQUEST.0.borrow_mut() = Some(Box::new( + move |response: Response| { + let next = extract_next(&response); + + if NextType::try_from(next.r#type).unwrap() == NextType::Output && next.index == 1 { + assert_eq!(next.generated_output_pkscript.as_slice(), b"\x51\x20\x7b\x91\x01\xd6\x0c\x64\x61\xff\x3e\x18\xf0\x83\x2e\x7f\x1e\x95\x20\x84\x20\x50\x62\xd7\xe0\xb7\xb0\x88\x12\xc2\x64\xcf\xe7\x13"); + } + Ok(tx.borrow().make_host_request(response)) + }, + )); + + static mut UI_COUNTER: u32 = 0; + mock(Data { + ui_transaction_address_create: Some(Box::new(|amount, address| unsafe { + UI_COUNTER += 1; + if UI_COUNTER == 1 { + assert_eq!( + address, + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ); + assert_eq!(amount, "1.00000000 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 mut init_request = transaction.borrow().init_request(); + init_request + .script_configs + .push(pb::BtcScriptConfigWithKeypath { + script_config: Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::SimpleType( + pb::btc_script_config::SimpleType::P2tr as _, + )), + }), + keypath: vec![86 + HARDENED, 0 + HARDENED, 10 + HARDENED], + }); + assert!(block_on(process(&init_request)).is_ok()); + assert!(unsafe { UI_COUNTER >= 1 }); + } + // Test an output that is marked ours but is not a change output by keypath. #[test] fn test_our_non_change_output() { @@ -2670,6 +2819,7 @@ mod tests { num_outputs: tx.outputs.len() as _, locktime: tx.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, } }; let result = block_on(process(&init_request)); @@ -2731,6 +2881,7 @@ mod tests { num_outputs: tx.outputs.len() as _, locktime: tx.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, } }; assert_eq!(block_on(process(&init_request)), Err(Error::InvalidInput)); @@ -2797,6 +2948,7 @@ mod tests { num_outputs: tx.outputs.len() as _, locktime: tx.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, } }; let result = block_on(process(&init_request)); @@ -2873,6 +3025,7 @@ mod tests { num_outputs: tx.outputs.len() as _, locktime: tx.locktime, format_unit: FormatUnit::Default as _, + contains_silent_payment_outputs: false, } }; let result = block_on(process(&init_request)); diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs index dfcf61dc3..35fa2a90c 100644 --- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs +++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs @@ -454,6 +454,8 @@ pub struct BtcSignInitRequest { pub locktime: u32, #[prost(enumeration = "btc_sign_init_request::FormatUnit", tag = "8")] pub format_unit: i32, + #[prost(bool, tag = "9")] + pub contains_silent_payment_outputs: bool, } /// Nested message and enum types in `BTCSignInitRequest`. pub mod btc_sign_init_request { @@ -517,6 +519,11 @@ pub struct BtcSignNextResponse { pub anti_klepto_signer_commitment: ::core::option::Option< AntiKleptoSignerCommitment, >, + /// Generated output. The host *must* verify its correctness using silent_payment_dleq_proof. + #[prost(bytes = "vec", tag = "7")] + pub generated_output_pkscript: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "8")] + pub silent_payment_dleq_proof: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `BTCSignNextResponse`. pub mod btc_sign_next_response { @@ -619,6 +626,20 @@ pub struct BtcSignOutputRequest { pub script_config_index: u32, #[prost(uint32, optional, tag = "7")] pub payment_request_index: ::core::option::Option, + /// 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. + #[prost(message, optional, tag = "8")] + pub silent_payment: ::core::option::Option, +} +/// Nested message and enum types in `BTCSignOutputRequest`. +pub mod btc_sign_output_request { + /// + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct SilentPayment { + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 158e71aa9..cc3b4e445 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -140,6 +140,7 @@ pub fn main() -> Result<(), &'static str> { .args(["--allowlist-function", "keystore_lock"]) .args(["--allowlist-function", "keystore_create_and_store_seed"]) .args(["--allowlist-function", "keystore_copy_seed"]) + .args(["--allowlist-function", "keystore_secp256k1_get_private_key"]) .args(["--allowlist-function", "keystore_get_bip39_mnemonic"]) .args(["--allowlist-function", "keystore_get_bip39_word"]) .args(["--allowlist-function", "keystore_get_ed25519_seed"]) diff --git a/src/rust/bitbox02/Cargo.toml b/src/rust/bitbox02/Cargo.toml index 394a50923..56fe75b34 100644 --- a/src/rust/bitbox02/Cargo.toml +++ b/src/rust/bitbox02/Cargo.toml @@ -28,6 +28,9 @@ zeroize = { workspace = true } bitcoin = { workspace = true } hex = { workspace = true } +[dev-dependencies] +hex = { workspace = true } + [features] # Only to be enabled in unit tests. testing = [] diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs index 7e58b052e..bcabef8f1 100644 --- a/src/rust/bitbox02/src/keystore.rs +++ b/src/rust/bitbox02/src/keystore.rs @@ -224,6 +224,24 @@ pub fn encode_xpub_at_keypath(keypath: &[u32]) -> Result, ()> { } } +pub fn secp256k1_get_private_key( + keypath: &[u32], + tweak_bip86: bool, +) -> Result>, ()> { + let mut key = zeroize::Zeroizing::new(vec![0u8; 32]); + match unsafe { + bitbox02_sys::keystore_secp256k1_get_private_key( + keypath.as_ptr(), + keypath.len() as _, + tweak_bip86, + key.as_mut_ptr(), + ) + } { + true => Ok(key), + false => Err(()), + } +} + pub struct SignResult { pub signature: [u8; 64], pub recid: u8, @@ -361,6 +379,7 @@ pub fn secp256k1_schnorr_bip86_pubkey(pubkey33: &[u8]) -> Result<[u8; 32], ()> { mod tests { use super::*; use crate::testing::{mock_unlocked, mock_unlocked_using_mnemonic, TEST_MNEMONIC}; + use util::bip32::HARDENED; #[test] fn test_bip39_mnemonic_to_seed() { @@ -490,7 +509,7 @@ mod tests { "income soft level reunion height pony crane use unfold win keen satisfy", ); assert_eq!( - bip85_bip39(12, util::bip32::HARDENED - 1).unwrap().as_ref() as &str, + bip85_bip39(12, HARDENED - 1).unwrap().as_ref() as &str, "carry build nerve market domain energy mistake script puzzle replace mixture idea", ); assert_eq!( @@ -505,7 +524,7 @@ mod tests { // Invalid number of words. assert!(bip85_bip39(10, 0).is_err()); // Index too high. - assert!(bip85_bip39(12, util::bip32::HARDENED).is_err()); + assert!(bip85_bip39(12, HARDENED).is_err()); } #[test] @@ -527,11 +546,39 @@ mod tests { b"\xe7\xd9\xce\x75\xf8\xcb\x17\x57\x0e\x66\x54\x17\xb4\x7f\xa0\xbe", ); assert_eq!( - bip85_ln(util::bip32::HARDENED - 1).unwrap().as_slice(), + bip85_ln(HARDENED - 1).unwrap().as_slice(), b"\x1f\x3b\x75\xea\x25\x27\x49\x70\x0a\x1e\x45\x34\x69\x14\x8c\xa6", ); // Index too high. - assert!(bip85_ln(util::bip32::HARDENED).is_err()); + assert!(bip85_ln(HARDENED).is_err()); + } + + #[test] + fn test_secp256k1_get_private_key() { + lock(); + let keypath = &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]; + assert!(secp256k1_get_private_key(keypath, false).is_err()); + + mock_unlocked_using_mnemonic( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "", + ); + + assert_eq!( + hex::encode(secp256k1_get_private_key(keypath, false).unwrap()), + "4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3" + ); + + // See first test vector in + // https://github.com/bitcoin/bips/blob/edffe529056f6dfd33d8f716fb871467c3c09263/bip-0086.mediawiki#test-vectors + // The below privte key's public key is: a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c. + assert_eq!( + hex::encode( + secp256k1_get_private_key(&[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0], true) + .unwrap() + ), + "eaac016f36e8c18347fbacf05ab7966708fbfce7ce3bf1dc32a09dd0645db038", + ); } }