Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bitcoin: support sending to silent payment addresses #1220

Merged
merged 2 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- Bitcoin: add support for regtest

### 9.20.0
Expand Down
2 changes: 1 addition & 1 deletion external/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ string(REPLACE "-mfpu=fpv4-sp-d16" "" MODIFIED_C_FLAGS ${MODIFIED_C_FLAGS_TMP})
# wally-core

# configure flags for secp256k1 bundled in libwally core, to reduce memory consumption
set(LIBWALLY_SECP256k1_FLAGS --with-ecmult-window=2 --with-ecmult-gen-precision=2 --enable-ecmult-static-precomputation --enable-module-schnorrsig)
set(LIBWALLY_SECP256k1_FLAGS --with-ecmult-window=2 --with-ecmult-gen-precision=2 --enable-ecmult-static-precomputation --enable-module-schnorrsig --enable-module-ecdsa-adaptor)
set(LIBWALLY_CONFIGURE_FLAGS --enable-static --disable-shared --disable-tests ${LIBWALLY_SECP256k1_FLAGS})
if(SANITIZE_ADDRESS)
set(LIBWALLY_CFLAGS "-fsanitize=address")
Expand Down
11 changes: 11 additions & 0 deletions messages/btc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ message BTCSignInitRequest {
SAT = 1;
}
FormatUnit format_unit = 8;
bool contains_silent_payment_outputs = 9;
}

message BTCSignNextResponse {
Expand All @@ -137,6 +138,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 {
Expand All @@ -160,6 +164,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.
Expand All @@ -169,6 +177,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 {
Expand Down
98 changes: 50 additions & 48 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Large diffs are not rendered by default.

38 changes: 34 additions & 4 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,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]:
Expand All @@ -306,6 +307,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 = ...,
Expand All @@ -315,8 +317,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):
Expand Down Expand Up @@ -356,6 +359,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"""
Expand All @@ -371,6 +376,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 = ...,
Expand All @@ -379,9 +388,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):
Expand Down Expand Up @@ -424,13 +435,25 @@ 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
PAYLOAD_FIELD_NUMBER: builtins.int
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"""
Expand All @@ -449,6 +472,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 = ...,
Expand All @@ -458,9 +487,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

Expand Down
23 changes: 23 additions & 0 deletions src/keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
14 changes: 14 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@ USE_RESULT bool keystore_secp256k1_schnorr_bip86_sign(
const uint8_t* msg32,
uint8_t* sig64_out);

/**
* Get the private key at the keypath.
*
* @param[in] keypath derivation keypath
* @param[in] keypath_len number of elements in keypath
* @param[in] tweak_bip86 if true, the resulting private key is tweaked with the BIP-86 tweak.
* @param[out] key_out resulting private key, must be 32 bytes.
*/
USE_RESULT bool keystore_secp256k1_get_private_key(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc comment for this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

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.
Expand Down
58 changes: 58 additions & 0 deletions src/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ members = [
"bitbox02",
"bitbox02-sys",
"erc20_params",
"streaming-silent-payments",
]

resolver = "2"

[workspace.dependencies]
bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.32.2", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
num-bigint = { version = "0.4.3", default-features = false }
Expand Down
6 changes: 4 additions & 2 deletions src/rust/bitbox02-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -41,8 +42,8 @@ num-bigint = { workspace = true, optional = true }
num-traits = { version = "0.2", default-features = false }
# If you change this, also change src/rust/.cargo/config.toml.
bip32-ed25519 = { git = "https://github.com/BitBoxSwiss/rust-bip32-ed25519", tag = "v0.2.0", optional = true }
bech32 = { version = "0.11.0", default-features = false, features = ["alloc"], optional = true }
blake2 = { version = "0.10.6", default-features = false, features = ["size_opt"], optional = true }
bech32 = { workspace = true, optional = true }
blake2 = { version = "0.10.6", default-features = false, optional = true }
minicbor = { version = "0.24.0", default-features = false, features = ["alloc"], optional = true }
crc = { version = "3.0.1", optional = true }
ed25519-dalek = { version = "2.1.1", default-features = false, features = ["hazmat", "digest"], optional = true }
Expand Down Expand Up @@ -77,6 +78,7 @@ app-ethereum = [
app-bitcoin = [
"dep:bech32",
"dep:miniscript",
"dep:streaming-silent-payments",
"bitbox02/app-bitcoin",
]
app-litecoin = [
Expand Down
Loading