Skip to content

Commit

Permalink
bitcoin: support sending to silent payment addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jul 24, 2024
1 parent 64db23f commit 278c013
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 100 deletions.
10 changes: 10 additions & 0 deletions messages/btc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ message BTCSignInitRequest {
SAT = 1;
}
FormatUnit format_unit = 8;
bool contains_silent_payment_outputs = 9;
}

message BTCSignNextResponse {
Expand All @@ -135,6 +136,8 @@ 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;
bytes generated_output_pkscript = 7;
bytes silent_payment_dleq_proof = 8;
}

message BTCSignInputRequest {
Expand All @@ -158,6 +161,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 @@ -167,6 +174,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.
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.

36 changes: 32 additions & 4 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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 = ...,
Expand All @@ -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):
Expand Down Expand Up @@ -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"""
Expand All @@ -365,6 +370,8 @@ class BTCSignNextResponse(google.protobuf.message.Message):

@property
def anti_klepto_signer_commitment(self) -> antiklepto_pb2.AntiKleptoSignerCommitment: ...
generated_output_pkscript: builtins.bytes
silent_payment_dleq_proof: builtins.bytes
def __init__(self,
*,
type: global___BTCSignNextResponse.Type.ValueType = ...,
Expand All @@ -373,9 +380,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 @@ -418,13 +427,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 @@ -443,6 +464,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.
"""
pass
def __init__(self,
*,
ours: builtins.bool = ...,
Expand All @@ -452,9 +479,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
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ add_custom_target(rust-bindgen
--allowlist-function keystore_lock
--allowlist-function keystore_create_and_store_seed
--allowlist-function keystore_copy_seed
--allowlist-function keystore_secp256k1_get_private_key
--allowlist-function keystore_get_bip39_mnemonic
--allowlist-function keystore_get_bip39_word
--allowlist-function keystore_get_ed25519_seed
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
6 changes: 6 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/rust/Cargo.lock

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

1 change: 1 addition & 0 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"}
hex = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true, optional = true }
Expand Down
Loading

0 comments on commit 278c013

Please sign in to comment.