Skip to content

Commit 75f1fb7

Browse files
committed
bitcoin/signtx: add support for payment requests
We implement a subset of the payment requests protocol described in SLIP-24: https://github.com/satoshilabs/slips/blob/master/slip-0024.md Subset means, unlike the full SLIP-24 proposal, this commit does not implement: - support for multiple outputs per payment request - support for multiple payment requests per transaction - memo types other than text memo - non-empty nonces The subset is sufficient to cover the first application: selling Bitcoin via PocketBitcoin. The protobuf messages and code has been designed so that support for the above missing features can be added in a backwards compatible way. This is why there is a `payment_request_index` in the output, which has to be `0` if present, and where at most one output can attach to a payment request. To allow a payment request to request payout to multiple addresses in the future, multiple outputs can have the same index, and the sighash computation (the outputs hash part of it) would need to be streamed and validation done at the end. To allow multiple payment requests per transaction, the payment_request_index can be incremented. SLIP-24 does not go much into how the trusted party is identified. For now we identify them by the recipient name with one hardcoded pubkey per recipient. We might want to extend this with a certificate chain (root key signs signing keys, possibly with expiry dates, which sign payment requests) - this is postponed until we finalize the design.
1 parent 43967c8 commit 75f1fb7

File tree

8 files changed

+700
-53
lines changed

8 files changed

+700
-53
lines changed

messages/btc.proto

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ message BTCSignNextResponse {
124124
PREVTX_INPUT = 4;
125125
PREVTX_OUTPUT = 5;
126126
HOST_NONCE = 6;
127+
PAYMENT_REQUEST = 7;
127128
}
128129
Type type = 1;
129130
// index of the current input or output
@@ -165,6 +166,7 @@ message BTCSignOutputRequest {
165166
repeated uint32 keypath = 5; // if ours is true
166167
// If ours is true. References a script config from BTCSignInitRequest
167168
uint32 script_config_index = 6;
169+
optional uint32 payment_request_index = 7;
168170
}
169171

170172
message BTCScriptConfigRegistration {
@@ -217,6 +219,23 @@ message BTCPrevTxOutputRequest {
217219
bytes pubkey_script = 2;
218220
}
219221

222+
message BTCPaymentRequestRequest {
223+
message Memo {
224+
message TextMemo {
225+
string note = 1;
226+
}
227+
oneof memo {
228+
TextMemo text_memo = 1;
229+
}
230+
}
231+
232+
string recipient_name = 1;
233+
repeated Memo memos = 2;
234+
bytes nonce = 3;
235+
uint64 total_amount = 4;
236+
bytes signature = 5;
237+
}
238+
220239
message BTCSignMessageRequest {
221240
BTCCoin coin = 1;
222241
BTCScriptConfigWithKeypath script_config = 2;
@@ -238,6 +257,7 @@ message BTCRequest {
238257
BTCPrevTxOutputRequest prevtx_output = 5;
239258
BTCSignMessageRequest sign_message = 6;
240259
AntiKleptoSignatureRequest antiklepto_signature = 7;
260+
BTCPaymentRequestRequest payment_request = 8;
241261
}
242262
}
243263

py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Lines changed: 44 additions & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ class BTCSignNextResponse(google.protobuf.message.Message):
329329
PREVTX_INPUT: BTCSignNextResponse._Type.ValueType # 4
330330
PREVTX_OUTPUT: BTCSignNextResponse._Type.ValueType # 5
331331
HOST_NONCE: BTCSignNextResponse._Type.ValueType # 6
332+
PAYMENT_REQUEST: BTCSignNextResponse._Type.ValueType # 7
332333
class Type(_Type, metaclass=_TypeEnumTypeWrapper):
333334
pass
334335

@@ -341,6 +342,7 @@ class BTCSignNextResponse(google.protobuf.message.Message):
341342
PREVTX_INPUT: BTCSignNextResponse.Type.ValueType # 4
342343
PREVTX_OUTPUT: BTCSignNextResponse.Type.ValueType # 5
343344
HOST_NONCE: BTCSignNextResponse.Type.ValueType # 6
345+
PAYMENT_REQUEST: BTCSignNextResponse.Type.ValueType # 7
344346

345347
TYPE_FIELD_NUMBER: builtins.int
346348
INDEX_FIELD_NUMBER: builtins.int
@@ -422,6 +424,7 @@ class BTCSignOutputRequest(google.protobuf.message.Message):
422424
PAYLOAD_FIELD_NUMBER: builtins.int
423425
KEYPATH_FIELD_NUMBER: builtins.int
424426
SCRIPT_CONFIG_INDEX_FIELD_NUMBER: builtins.int
427+
PAYMENT_REQUEST_INDEX_FIELD_NUMBER: builtins.int
425428
ours: builtins.bool
426429
type: global___BTCOutputType.ValueType
427430
"""if ours is false"""
@@ -439,6 +442,7 @@ class BTCSignOutputRequest(google.protobuf.message.Message):
439442
script_config_index: builtins.int
440443
"""If ours is true. References a script config from BTCSignInitRequest"""
441444

445+
payment_request_index: builtins.int
442446
def __init__(self,
443447
*,
444448
ours: builtins.bool = ...,
@@ -447,8 +451,11 @@ class BTCSignOutputRequest(google.protobuf.message.Message):
447451
payload: builtins.bytes = ...,
448452
keypath: typing.Optional[typing.Iterable[builtins.int]] = ...,
449453
script_config_index: builtins.int = ...,
454+
payment_request_index: typing.Optional[builtins.int] = ...,
450455
) -> None: ...
451-
def ClearField(self, field_name: typing_extensions.Literal["keypath",b"keypath","ours",b"ours","payload",b"payload","script_config_index",b"script_config_index","type",b"type","value",b"value"]) -> None: ...
456+
def HasField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","payment_request_index",b"payment_request_index"]) -> builtins.bool: ...
457+
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: ...
458+
def WhichOneof(self, oneof_group: typing_extensions.Literal["_payment_request_index",b"_payment_request_index"]) -> typing.Optional[typing_extensions.Literal["payment_request_index"]]: ...
452459
global___BTCSignOutputRequest = BTCSignOutputRequest
453460

454461
class BTCScriptConfigRegistration(google.protobuf.message.Message):
@@ -599,6 +606,53 @@ class BTCPrevTxOutputRequest(google.protobuf.message.Message):
599606
def ClearField(self, field_name: typing_extensions.Literal["pubkey_script",b"pubkey_script","value",b"value"]) -> None: ...
600607
global___BTCPrevTxOutputRequest = BTCPrevTxOutputRequest
601608

609+
class BTCPaymentRequestRequest(google.protobuf.message.Message):
610+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
611+
class Memo(google.protobuf.message.Message):
612+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
613+
class TextMemo(google.protobuf.message.Message):
614+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
615+
NOTE_FIELD_NUMBER: builtins.int
616+
note: typing.Text
617+
def __init__(self,
618+
*,
619+
note: typing.Text = ...,
620+
) -> None: ...
621+
def ClearField(self, field_name: typing_extensions.Literal["note",b"note"]) -> None: ...
622+
623+
TEXT_MEMO_FIELD_NUMBER: builtins.int
624+
@property
625+
def text_memo(self) -> global___BTCPaymentRequestRequest.Memo.TextMemo: ...
626+
def __init__(self,
627+
*,
628+
text_memo: typing.Optional[global___BTCPaymentRequestRequest.Memo.TextMemo] = ...,
629+
) -> None: ...
630+
def HasField(self, field_name: typing_extensions.Literal["memo",b"memo","text_memo",b"text_memo"]) -> builtins.bool: ...
631+
def ClearField(self, field_name: typing_extensions.Literal["memo",b"memo","text_memo",b"text_memo"]) -> None: ...
632+
def WhichOneof(self, oneof_group: typing_extensions.Literal["memo",b"memo"]) -> typing.Optional[typing_extensions.Literal["text_memo"]]: ...
633+
634+
RECIPIENT_NAME_FIELD_NUMBER: builtins.int
635+
MEMOS_FIELD_NUMBER: builtins.int
636+
NONCE_FIELD_NUMBER: builtins.int
637+
TOTAL_AMOUNT_FIELD_NUMBER: builtins.int
638+
SIGNATURE_FIELD_NUMBER: builtins.int
639+
recipient_name: typing.Text
640+
@property
641+
def memos(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BTCPaymentRequestRequest.Memo]: ...
642+
nonce: builtins.bytes
643+
total_amount: builtins.int
644+
signature: builtins.bytes
645+
def __init__(self,
646+
*,
647+
recipient_name: typing.Text = ...,
648+
memos: typing.Optional[typing.Iterable[global___BTCPaymentRequestRequest.Memo]] = ...,
649+
nonce: builtins.bytes = ...,
650+
total_amount: builtins.int = ...,
651+
signature: builtins.bytes = ...,
652+
) -> None: ...
653+
def ClearField(self, field_name: typing_extensions.Literal["memos",b"memos","nonce",b"nonce","recipient_name",b"recipient_name","signature",b"signature","total_amount",b"total_amount"]) -> None: ...
654+
global___BTCPaymentRequestRequest = BTCPaymentRequestRequest
655+
602656
class BTCSignMessageRequest(google.protobuf.message.Message):
603657
DESCRIPTOR: google.protobuf.descriptor.Descriptor
604658
COIN_FIELD_NUMBER: builtins.int
@@ -644,6 +698,7 @@ class BTCRequest(google.protobuf.message.Message):
644698
PREVTX_OUTPUT_FIELD_NUMBER: builtins.int
645699
SIGN_MESSAGE_FIELD_NUMBER: builtins.int
646700
ANTIKLEPTO_SIGNATURE_FIELD_NUMBER: builtins.int
701+
PAYMENT_REQUEST_FIELD_NUMBER: builtins.int
647702
@property
648703
def is_script_config_registered(self) -> global___BTCIsScriptConfigRegisteredRequest: ...
649704
@property
@@ -658,6 +713,8 @@ class BTCRequest(google.protobuf.message.Message):
658713
def sign_message(self) -> global___BTCSignMessageRequest: ...
659714
@property
660715
def antiklepto_signature(self) -> antiklepto_pb2.AntiKleptoSignatureRequest: ...
716+
@property
717+
def payment_request(self) -> global___BTCPaymentRequestRequest: ...
661718
def __init__(self,
662719
*,
663720
is_script_config_registered: typing.Optional[global___BTCIsScriptConfigRegisteredRequest] = ...,
@@ -667,10 +724,11 @@ class BTCRequest(google.protobuf.message.Message):
667724
prevtx_output: typing.Optional[global___BTCPrevTxOutputRequest] = ...,
668725
sign_message: typing.Optional[global___BTCSignMessageRequest] = ...,
669726
antiklepto_signature: typing.Optional[antiklepto_pb2.AntiKleptoSignatureRequest] = ...,
727+
payment_request: typing.Optional[global___BTCPaymentRequestRequest] = ...,
670728
) -> None: ...
671-
def HasField(self, field_name: typing_extensions.Literal["antiklepto_signature",b"antiklepto_signature","is_script_config_registered",b"is_script_config_registered","prevtx_init",b"prevtx_init","prevtx_input",b"prevtx_input","prevtx_output",b"prevtx_output","register_script_config",b"register_script_config","request",b"request","sign_message",b"sign_message"]) -> builtins.bool: ...
672-
def ClearField(self, field_name: typing_extensions.Literal["antiklepto_signature",b"antiklepto_signature","is_script_config_registered",b"is_script_config_registered","prevtx_init",b"prevtx_init","prevtx_input",b"prevtx_input","prevtx_output",b"prevtx_output","register_script_config",b"register_script_config","request",b"request","sign_message",b"sign_message"]) -> None: ...
673-
def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["is_script_config_registered","register_script_config","prevtx_init","prevtx_input","prevtx_output","sign_message","antiklepto_signature"]]: ...
729+
def HasField(self, field_name: typing_extensions.Literal["antiklepto_signature",b"antiklepto_signature","is_script_config_registered",b"is_script_config_registered","payment_request",b"payment_request","prevtx_init",b"prevtx_init","prevtx_input",b"prevtx_input","prevtx_output",b"prevtx_output","register_script_config",b"register_script_config","request",b"request","sign_message",b"sign_message"]) -> builtins.bool: ...
730+
def ClearField(self, field_name: typing_extensions.Literal["antiklepto_signature",b"antiklepto_signature","is_script_config_registered",b"is_script_config_registered","payment_request",b"payment_request","prevtx_init",b"prevtx_init","prevtx_input",b"prevtx_input","prevtx_output",b"prevtx_output","register_script_config",b"register_script_config","request",b"request","sign_message",b"sign_message"]) -> None: ...
731+
def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["is_script_config_registered","register_script_config","prevtx_init","prevtx_input","prevtx_output","sign_message","antiklepto_signature","payment_request"]]: ...
674732
global___BTCRequest = BTCRequest
675733

676734
class BTCResponse(google.protobuf.message.Message):

src/rust/bitbox02-rust/src/hww/api/bitcoin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod common;
2121
pub mod keypath;
2222
mod multisig;
2323
pub mod params;
24+
mod payment_request;
2425
mod policies;
2526
mod registration;
2627
mod script;
@@ -305,7 +306,8 @@ pub async fn process_api(request: &Request) -> Result<pb::btc_response::Response
305306
Request::PrevtxInit(_)
306307
| Request::PrevtxInput(_)
307308
| Request::PrevtxOutput(_)
308-
| Request::AntikleptoSignature(_) => Err(Error::InvalidState),
309+
| Request::AntikleptoSignature(_)
310+
| Request::PaymentRequest(_) => Err(Error::InvalidState),
309311
}
310312
}
311313

src/rust/bitbox02-rust/src/hww/api/bitcoin/params.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ pub struct Params {
3131
pub taproot_support: bool,
3232
}
3333

34+
impl Params {
35+
/// Returns the SLIP44 coin type:
36+
/// https://github.com/satoshilabs/slips/blob/master/slip-0044.md
37+
pub fn slip44(&self) -> u32 {
38+
self.bip44_coin - HARDENED
39+
}
40+
}
41+
3442
const PARAMS_BTC: Params = Params {
3543
coin: BtcCoin::Btc,
3644
bip44_coin: 0 + HARDENED,

0 commit comments

Comments
 (0)