Skip to content

Commit

Permalink
wip dleq
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jul 23, 2024
1 parent 1cef515 commit 6a59afd
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 65 deletions.
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
1 change: 1 addition & 0 deletions messages/btc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ message BTCSignNextResponse {
uint32 prev_index = 5;
AntiKleptoSignerCommitment anti_klepto_signer_commitment = 6;
bytes generated_output_pkscript = 7;
bytes silent_payment_dleq_proof = 8;
}

message BTCSignInputRequest {
Expand Down
92 changes: 46 additions & 46 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ class BTCSignNextResponse(google.protobuf.message.Message):
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 @@ -370,6 +371,7 @@ 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 @@ -379,9 +381,10 @@ class BTCSignNextResponse(google.protobuf.message.Message):
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","generated_output_pkscript",b"generated_output_pkscript","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
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ add_custom_target(rust-bindgen
--allowlist-function wally_hash160
--allowlist-function wally_sha512
--allowlist-function printf
--allowlist-function bitbox_secp256k1_dleq_prove
--allowlist-function bitbox_secp256k1_dleq_verify
${CMAKE_CURRENT_SOURCE_DIR}/rust/bitbox02-sys/wrapper.h --
-DPB_NO_PACKED_STRUCTS=1 -DPB_FIELD_16BIT=1 -fshort-enums ${RUST_BINDGEN_FLAGS} ${RUST_INCLUDES}
COMMAND
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.

6 changes: 4 additions & 2 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -818,15 +818,17 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result<Response, Error> {
if output_type != pb::BtcOutputType::P2tr {
return Err(Error::InvalidInput);
}
let xonly = silent_payment
let sp_output = silent_payment
.create_output(&output_silent_payment.address)
.map_err(|_| Error::InvalidInput)?;
let payload = common::Payload {
data: xonly.serialize().to_vec(),
data: sp_output.pubkey.serialize().to_vec(),
output_type,
};
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
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ pub struct BtcSignNextResponse {
>,
#[prost(bytes = "vec", tag = "7")]
pub generated_output_pkscript: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "8")]
pub silent_payment_dleq_proof: ::prost::alloc::vec::Vec<u8>,
}
/// Nested message and enum types in `BTCSignNextResponse`.
pub mod btc_sign_next_response {
Expand Down
1 change: 1 addition & 0 deletions src/rust/bitbox02-sys/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <screen.h>
#include <sd.h>
#include <secp256k1_ecdsa_s2c.h>
#include <secp256k1_ecdsa_adaptor.h>
#include <securechip/securechip.h>
#include <system.h>
#include <time.h>
Expand Down
1 change: 1 addition & 0 deletions src/rust/bitbox02/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bitbox02-sys = {path="../bitbox02-sys"}
util = {path = "../util"}
zeroize = { workspace = true }
lazy_static = { workspace = true, optional = true }
bitcoin = { workspace = true }

[dev-dependencies]
hex = { workspace = true }
Expand Down
91 changes: 90 additions & 1 deletion src/rust/bitbox02/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 Shift Crypto AG
// Copyright 2022-2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use bitcoin::secp256k1::ffi::CPtr;

use alloc::vec::Vec;

pub fn ecdsa_anti_exfil_host_commit(rand32: &[u8]) -> Result<Vec<u8>, ()> {
Expand All @@ -27,3 +29,90 @@ pub fn ecdsa_anti_exfil_host_commit(rand32: &[u8]) -> Result<Vec<u8>, ()> {
_ => Err(()),
}
}

pub fn dleq_prove(
sk: &[u8; 32],
gen2: &bitcoin::secp256k1::PublicKey,
p1: &bitcoin::secp256k1::PublicKey,
p2: &bitcoin::secp256k1::PublicKey,
) -> Result<Vec<u8>, ()> {
let mut s = [0u8; 32];
let mut e = [0u8; 32];
let result = unsafe {
bitbox02_sys::bitbox_secp256k1_dleq_prove(
bitbox02_sys::wally_get_secp_context(),
s.as_mut_ptr(),
e.as_mut_ptr(),
sk.as_ptr(),
gen2.as_c_ptr() as _,
p1.as_c_ptr() as _,
p2.as_c_ptr() as _,
)
};
if result == 1 {
let mut result = s.to_vec();
result.extend(&e);
Ok(result)
} else {
Err(())
}
}

pub fn dleq_verify(
proof: [u8; 64],
gen2: &bitcoin::secp256k1::PublicKey,
p1: &bitcoin::secp256k1::PublicKey,
p2: &bitcoin::secp256k1::PublicKey,
) -> Result<(), ()> {
let result = unsafe {
bitbox02_sys::bitbox_secp256k1_dleq_verify(
bitbox02_sys::wally_get_secp_context(),
proof[..32].as_ptr(),
proof[32..].as_ptr(),
p1.as_c_ptr() as _,
gen2.as_c_ptr() as _,
p2.as_c_ptr() as _,
)
};
if result == 1 {
Ok(())
} else {
Err(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};

#[test]
fn test_dleq() {
let secp = Secp256k1::new();
let seckey_bytes = b"\x07\x7e\xb7\x5a\x52\xec\xa2\x4c\xde\xdf\x05\x8c\x92\xf1\xca\x8b\x9d\x48\x41\x77\x1f\xd6\xba\xa3\xd2\x78\x85\xfb\x5b\x49\xfb\xa2";
let seckey = SecretKey::from_slice(seckey_bytes).unwrap();

let pubkey = seckey.public_key(&secp);

let other_base_bytes = b"\x03\x89\x14\x0f\x7b\xb8\x52\xf0\x20\xf1\x54\xe5\x59\x08\xfe\x36\x99\xdc\x9f\x65\x15\x3e\x68\x15\x27\xf0\xd5\x5a\xab\xed\x93\x7f\x4b";
let other_base = PublicKey::from_slice(other_base_bytes).unwrap();

let other_pubkey = other_base;
let other_pubkey = other_pubkey.mul_tweak(&secp, &seckey.into()).unwrap();
let proof = dleq_prove(seckey_bytes, &other_base, &pubkey, &other_pubkey).unwrap();
// Check against fixture so potential upstream changes in the DLEQ implementation get
// caught. Incompatible changes can break BitBox client libraries that rely on this
// specific DLEQ implementation.
assert_eq!(
hex::encode(&proof),
"6c885f825f6ce7565bc6d0bfda90506b11e2682dfe943f5a85badf1c8a96edc5f5e03f5ee2c58bf979646fbada920f9f1c5bd92805fb5b01534b42d26a550f79",
);
dleq_verify(
proof.try_into().unwrap(),
&other_base,
&pubkey,
&other_pubkey,
)
.unwrap();
}
}
3 changes: 2 additions & 1 deletion src/rust/streaming-silent-payments/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ license = "Apache-2.0"

[dependencies]
bitcoin = { workspace = true }
bech32 = { version = "0.11.0", default-features = false }
bech32 = { workspace = true }
bitbox02 = {path = "../bitbox02" }

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand Down
53 changes: 42 additions & 11 deletions src/rust/streaming-silent-payments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ mod hash;

pub use bitcoin;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey};
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey};

use alloc::vec::Vec;

pub struct Output {
pub pubkey: XOnlyPublicKey,
pub dleq_proof: [u8; 33 + 64],
}

pub enum Network {
Btc,
Tbtc,
Expand All @@ -46,7 +51,7 @@ pub struct SilentPayment {
// Done streaming inputs?
inputs_done: bool,
// We only allow one silent payment output for now. This tracks whether we've seen it.
output_checked: bool,
output_created: bool,
}

fn calculate_t_k(ecdh_shared_secret: &PublicKey, k: u32) -> Result<SecretKey, ()> {
Expand Down Expand Up @@ -99,7 +104,7 @@ impl SilentPayment {
smallest_outpoint: None,
a_sum: None,
inputs_done: false,
output_checked: false,
output_created: false,
}
}

Expand Down Expand Up @@ -137,7 +142,7 @@ impl SilentPayment {
match self.a_sum {
None => self.a_sum = Some(negated_key),
Some(ref mut p) => {
*p = p.add_tweak(&negated_key.into()).map_err(|_| ())?;
*p = p.add_tweak(&Scalar::from(negated_key)).map_err(|_| ())?;
}
}

Expand All @@ -147,12 +152,12 @@ impl SilentPayment {
/// Call this for silent payment outputs.
/// `silent_payment_address` is the output address.
/// This returns the SegWit v1 Taproot output key of the created output.
pub fn create_output(&mut self, silent_payment_address: &str) -> Result<XOnlyPublicKey, ()> {
pub fn create_output(&mut self, silent_payment_address: &str) -> Result<Output, ()> {
self.inputs_done = true;
if self.output_checked {
if self.output_created {
return Err(());
}
self.output_checked = true;
self.output_created = true;

let (scan_pubkey, m_pubkey) =
decode_address(silent_payment_address, self.network.sp_hrp())?;
Expand All @@ -170,7 +175,7 @@ impl SilentPayment {
.mul_tweak(&self.secp, &partial_secret.into())
.map_err(|_| ())?;

// If we want to support more than one silent pay4ment output, we need to get this value from
// If we want to support more than one silent payment output, we need to get this value from
// the host per output, and check before signing the tx that for each SP output with the
// same scan pubkey has a different `k` and they are consecutive starting at 0, so the
// recipient is sure to be able to find the output. With only one silent payment output
Expand All @@ -182,7 +187,30 @@ impl SilentPayment {
let res = t_k.public_key(&self.secp);
let reskey = res.combine(&m_pubkey).map_err(|_| ())?;
let (reskey_xonly, _) = reskey.x_only_public_key();
Ok(reskey_xonly)

let dleq_proof: [u8; 33 + 64] = {
#[allow(non_snake_case)]
let C = scan_pubkey
.mul_tweak(&self.secp, &Scalar::from(*a_sum))
.map_err(|_| ())?;

let mut v = C.serialize().to_vec();
let proof = bitbox02::secp256k1::dleq_prove(a_sum.as_ref(), &scan_pubkey, &A_sum, &C)?;
// Sanity check.
bitbox02::secp256k1::dleq_verify(
proof.as_slice().try_into().unwrap(),
&scan_pubkey,
&A_sum,
&C,
)?;
v.extend(&proof);
v.try_into().unwrap()
};

Ok(Output {
pubkey: reskey_xonly,
dleq_proof,
})
}
}

Expand Down Expand Up @@ -232,11 +260,14 @@ mod tests {
"f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16:0"
);

let expected = XOnlyPublicKey::from_str(
let expected_pubkey = XOnlyPublicKey::from_str(
"3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1",
)
.unwrap();
assert_eq!(v.create_output("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv").unwrap(), expected);
let expected_dleq_proof = "02bd6cf6542e272a81a6aba9d35c0140d73758ad74c992d8808c0f0d76a642fe9977ecc511315c7fa44e54af3676ee212ca21031ef4a763dc841a49b59431ef8e4e2ac48d74324d5115602e2720c365c836da738f8c43c513f0022a40d6e71a048";
let output = v.create_output("sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv").unwrap();
assert_eq!(output.pubkey, expected_pubkey);
assert_eq!(hex::encode(output.dleq_proof), expected_dleq_proof);
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/rust/streaming-silent-payments/tests/table_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ fn test_sending() {
} else if pk_script.is_p2sh() {
panic!("tests don't include p2sh - parse for p2sh-p2wpkh if needed")
} else {
panic!("unreconigzed input")
panic!("unrecognized input")
};
v.add_input(
input_type,
Expand All @@ -180,7 +180,7 @@ fn test_sending() {
.unwrap();
}

assert_eq!(v.create_output(sp_address).unwrap(), expected);
assert_eq!(v.create_output(sp_address).unwrap().pubkey, expected);
}
}
}
1 change: 1 addition & 0 deletions test/unit-test/test_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <secp256k1_ecdsa_s2c.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
#include <secp256k1_ecdsa_adaptor.h>
#include <securechip/securechip.h>
#include <util.h>

Expand Down

0 comments on commit 6a59afd

Please sign in to comment.