build: consolidate secp256k1 into new crate bitbox-secp256k1
What changed, and why it matters
This commit is a build-system refactor: it moves the secp256k1 cryptographic library dependency into a new Rust crate called bitbox-secp256k1 so it can be built with normal Cargo tooling instead of CMake. The actual cryptographic functions (signing, nonce commitments, DLEQ proofs, etc.) are copied almost unchanged from existing modules into the new crate. There is no direct evidence in the commit that this fixes a security vulnerability or introduces a new exploit.
No immediate security action required. Treat as routine build refactor. If reviewing for security, verify that the new build.rs preserves the same secp256k1-zkp source tree, compiler flags, and module enables as the old CMake build, and that the moved FFI bindings remain correct.
Security signals we found
Refactor only: cryptographic code relocated, not modified
Build configuration constants preserved (ECMULT_WINDOW_SIZE=2, ECMULT_GEN_PREC_BITS=2, same module enables)
No new unsafe blocks introduced beyond existing FFI bindings
No mention of vulnerability, CVE, bug, or security fix in commit message or diff
No changes to input validation or memory handling visible in the moved functions
Evidence from the diff
The change consolidates secp256k1-zkp into a cargo-first crate, bitbox-secp256k1, with a build.rs that compiles the C dependency via cc. It removes the old CMake-based secp256k1 build, removes secp256k1 bindings from bitbox02-sys, deletes bitbox02::secp256k1, and makes bitbox02-rust::secp256k1 re-export the new crate. The same secp256k1_zkp configuration options (ECMULT_WINDOW_SIZE=2, ECMULT_GEN_PREC_BITS=2, recovery/extrakeys/schnorrsig/ecdsa-adaptor/s2c modules) are preserved. The diff shows code relocation, not logic changes, and adds unit tests for the moved functions.
Changed components
src/rust/bitbox-secp256k1 (new crate)src/rust/bitbox02/src/secp256k1.rs (removed)src/rust/bitbox02-rust/src/secp256k1.rs (now re-exports new crate)src/rust/bitbox02-sys/build.rs (secp256k1 bindings removed)external/CMakeLists.txt (secp256k1 build removed)src/CMakeLists.txt and test simulators (linking updated)Inspect captured patch +547 / −438
diff --git a/.gitmodules b/.gitmodules
index 1f05a5c..1fd61b4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,6 +10,6 @@
[submodule "external/embedded-swd"]
path = external/embedded-swd
url = https://github.com/BitBoxSwiss/embedded-swd
-[submodule "external/secp256k1-zkp"]
- path = external/secp256k1-zkp
+[submodule "src/rust/bitbox-secp256k1/depend/secp256k1-zkp"]
+ path = src/rust/bitbox-secp256k1/depend/secp256k1-zkp
url = https://github.com/BitBoxSwiss/secp256k1-zkp.git
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 6caaa3d..c8ab86d 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -17,25 +17,6 @@ endif()
string(REPLACE "-mfloat-abi=softfp" "" MODIFIED_C_FLAGS_TMP ${CMAKE_C_FLAGS})
string(REPLACE "-mfpu=fpv4-sp-d16" "" MODIFIED_C_FLAGS ${MODIFIED_C_FLAGS_TMP})
-#----------------------
-
-## secp256k1-zkp
-
-# Override
-set(SECP256K1_ENABLE_MODULE_RECOVERY ON) # needed only in Rust unit tests.
-set(SECP256K1_ECMULT_WINDOW_SIZE 2 CACHE STRING "Window size for ecmult precomputation for verification, specified as integer in range [2..24]. \"AUTO\" is a reasonable setting for desktop machines (currently 15). [default=AUTO]" FORCE)
-set(SECP256K1_ECMULT_GEN_PREC_BITS 2 CACHE STRING "Precision bits to tune the precomputed table size for signing, specified as integer 2, 4 or 8. \"AUTO\" is a reasonable setting for desktop machines (currently 4). [default=AUTO]" FORCE)
-set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "Build benchmarks.")
-set(SECP256K1_BUILD_TESTS OFF CACHE BOOL "Build tests.")
-set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE BOOL "Build exhaustive tests.")
-set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE BOOL "Build constant-time tests.")
-# Suppress all warnings in this directory, we don't have control over them.
-set_directory_properties(PROPERTIES
- COMPILE_OPTIONS "-w"
- DIRECTORY secp256k1-zkp
-)
-add_subdirectory(secp256k1-zkp)
-
if(CMAKE_CROSSCOMPILING)
# Cortex Microcontroller Software Interface Standard
add_library(CMSIS INTERFACE)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6214877..1d73ddb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -220,6 +220,7 @@ endif()
# Use libsecp256k1 that we link ourselves.
# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
+# This is replicated in .cargo/config.toml because RUSTFLAGS here override the rustflags there.
string(JOIN " " RUSTFLAGS "${RUSTFLAGS}" "--cfg=rust_secp_no_symbol_renaming")
if(CMAKE_CROSSCOMPILING)
@@ -266,7 +267,7 @@ add_custom_target(rust-cbindgen
# Test rust crates that contain business logic. Avoid testing crates that depend on hardware.
if(NOT CMAKE_CROSSCOMPILING)
- set(RUSTFLAGS_TESTS "${RUSTFLAGS} -L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lsecp256k1 -lstatic=fatfs")
+ set(RUSTFLAGS_TESTS "${RUSTFLAGS} -L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lstatic=fatfs")
# Since we build with all features we need to use a separate build directory.
# Otherwise we invalidate the result from the normal compilation that uses a
@@ -293,7 +294,7 @@ if(NOT CMAKE_CROSSCOMPILING)
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/rust/rust.h
)
- add_dependencies(rust-test secp256k1 generate-protobufs fatfs)
+ add_dependencies(rust-test generate-protobufs fatfs)
add_custom_target(rust-clippy
COMMAND
@@ -556,7 +557,6 @@ if(CMAKE_CROSSCOMPILING)
# left-to-right.
target_link_libraries(${elf} PRIVATE ${firmware}_rust_c)
- target_link_libraries(${elf} PRIVATE secp256k1)
target_link_libraries(${elf} PRIVATE ${QTOUCHLIB_A} ${QTOUCHLIB_B} ${QTOUCHLIB_T})
# Select the smaller version of libc called nano.
diff --git a/src/rust/.cargo/config.toml b/src/rust/.cargo/config.toml
index 1c2583b..66f912f 100644
--- a/src/rust/.cargo/config.toml
+++ b/src/rust/.cargo/config.toml
@@ -18,3 +18,10 @@ replace-with = "vendored-sources"
[source.vendored-sources]
directory = "../../external/vendor"
+
+[build]
+# Use our own copy of libsecp256k1-zpk instead of the one bundled with secp256k1-sys, also in `cargo
+# test` and other cargo-based builds. This is replicated in src/CMakeLists.txt,
+# test/simulator-graphical*/CMakeLists.txt for CMake-based builds.
+# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
+rustflags = ["--cfg", "rust_secp_no_symbol_renaming"]
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 84d4f11..96b4d2f 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -106,6 +106,16 @@ dependencies = [
"util",
]
+[[package]]
+name = "bitbox-secp256k1"
+version = "0.1.0"
+dependencies = [
+ "bitcoin",
+ "cc",
+ "hex",
+ "hex_lit",
+]
+
[[package]]
name = "bitbox02"
version = "0.1.0"
@@ -114,8 +124,6 @@ dependencies = [
"bitbox-aes",
"bitbox02-rust",
"bitbox02-sys",
- "bitcoin",
- "hex",
"hex_lit",
"util",
"zeroize",
@@ -138,6 +146,7 @@ dependencies = [
"bip32-ed25519",
"bip39",
"bitbox-aes",
+ "bitbox-secp256k1",
"bitbox02",
"bitbox02-noise",
"bitcoin",
@@ -1046,7 +1055,7 @@ dependencies = [
name = "streaming-silent-payments"
version = "0.1.0"
dependencies = [
- "bitbox02",
+ "bitbox-secp256k1",
"bitcoin",
"hex",
"serde",
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 7f5910c..48fd6d6 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -9,6 +9,7 @@ members = [
"util",
"bitbox02-noise",
"bitbox02",
+ "bitbox-secp256k1",
"bitbox02-sys",
"erc20_params",
"streaming-silent-payments",
diff --git a/src/rust/bitbox-secp256k1/Cargo.toml b/src/rust/bitbox-secp256k1/Cargo.toml
new file mode 100644
index 0000000..0e0215b
--- /dev/null
+++ b/src/rust/bitbox-secp256k1/Cargo.toml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "bitbox-secp256k1"
+version = "0.1.0"
+authors = ["Shift Crypto AG <support@bitbox.swiss>"]
+edition = "2024"
+description = "secp256k1-zkp build and wrappers for BitBox"
+license = "Apache-2.0"
+
+[lib]
+doctest = false
+
+[dependencies]
+bitcoin = { workspace = true }
+
+[dev-dependencies]
+hex = { workspace = true }
+hex_lit = { workspace = true, features = ["rust_v_1_46"] }
+
+[build-dependencies]
+cc = { version = "1.2", features = ["parallel"] }
+
+[features]
+testing = []
diff --git a/src/rust/bitbox-secp256k1/build.rs b/src/rust/bitbox-secp256k1/build.rs
new file mode 100644
index 0000000..2552cfd
--- /dev/null
+++ b/src/rust/bitbox-secp256k1/build.rs
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::path::PathBuf;
+
+fn main() {
+ let secp_dir = PathBuf::from("depend/secp256k1-zkp");
+
+ println!(
+ "cargo::rerun-if-changed={}",
+ secp_dir.join("include").display()
+ );
+ println!("cargo::rerun-if-changed={}", secp_dir.join("src").display());
+
+ let mut build = cc::Build::new();
+ build
+ .file(secp_dir.join("src/secp256k1.c"))
+ .file(secp_dir.join("src/precomputed_ecmult.c"))
+ .file(secp_dir.join("src/precomputed_ecmult_gen.c"))
+ .include(secp_dir.join("include"))
+ // Suppress all warnings in this dependency, we don't have control over them.
+ .flag_if_supported("-w")
+ .define("ECMULT_WINDOW_SIZE", Some("2"))
+ .define("ECMULT_GEN_PREC_BITS", Some("2"))
+ .define("ENABLE_MODULE_RECOVERY", Some("1")) // needed only in Rust unit tests.
+ .define("ENABLE_MODULE_EXTRAKEYS", Some("1"))
+ .define("ENABLE_MODULE_SCHNORRSIG", Some("1"))
+ .define("ENABLE_MODULE_ECDSA_ADAPTOR", Some("1"))
+ .define("ENABLE_MODULE_ECDSA_S2C", Some("1"));
+
+ build.compile("secp256k1");
+}
diff --git a/src/rust/bitbox-secp256k1/src/lib.rs b/src/rust/bitbox-secp256k1/src/lib.rs
new file mode 100644
index 0000000..36b48ca
--- /dev/null
+++ b/src/rust/bitbox-secp256k1/src/lib.rs
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+
+extern crate alloc;
+
+pub use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
+use bitcoin::secp256k1::ffi::CPtr;
+use bitcoin::secp256k1::{All, Secp256k1};
+
+use alloc::vec::Vec;
+use core::cell::OnceCell;
+use core::ffi::c_int;
+use core::mem::MaybeUninit;
+use core::ops::Deref;
+
+mod ffi {
+ use bitcoin::secp256k1::ffi::{Context, PublicKey, Signature};
+ use core::ffi::{c_int, c_uchar};
+
+ #[allow(non_camel_case_types)]
+ #[repr(C)]
+ pub struct secp256k1_ecdsa_s2c_opening {
+ pub data: [c_uchar; 64],
+ }
+
+ unsafe extern "C" {
+ pub fn secp256k1_anti_exfil_sign(
+ ctx: *const Context,
+ sig: *mut Signature,
+ msg32: *const c_uchar,
+ seckey: *const c_uchar,
+ host_data32: *const c_uchar,
+ recid: *mut c_int,
+ ) -> c_int;
+
+ #[cfg(feature = "testing")]
+ pub fn secp256k1_anti_exfil_host_verify(
+ ctx: *const Context,
+ sig: *const Signature,
+ msg32: *const c_uchar,
+ pubkey: *const PublicKey,
+ host_data32: *const c_uchar,
+ opening: *const secp256k1_ecdsa_s2c_opening,
+ ) -> c_int;
+
+ pub fn secp256k1_ecdsa_anti_exfil_host_commit(
+ ctx: *const Context,
+ rand_commitment32: *mut c_uchar,
+ rand32: *const c_uchar,
+ ) -> c_int;
+
+ pub fn secp256k1_ecdsa_anti_exfil_signer_commit(
+ ctx: *const Context,
+ s2c_opening: *mut secp256k1_ecdsa_s2c_opening,
+ msg32: *const c_uchar,
+ seckey32: *const c_uchar,
+ rand_commitment32: *const c_uchar,
+ ) -> c_int;
+
+ #[cfg(feature = "testing")]
+ pub fn secp256k1_ecdsa_s2c_opening_parse(
+ ctx: *const Context,
+ opening: *mut secp256k1_ecdsa_s2c_opening,
+ input33: *const c_uchar,
+ ) -> c_int;
+
+ pub fn secp256k1_ecdsa_s2c_opening_serialize(
+ ctx: *const Context,
+ output33: *mut c_uchar,
+ opening: *const secp256k1_ecdsa_s2c_opening,
+ ) -> c_int;
+
+ pub fn secp256k1_ecdsa_signature_serialize_compact(
+ ctx: *const Context,
+ output64: *mut c_uchar,
+ sig: *const Signature,
+ ) -> c_int;
+
+ pub fn bitbox_secp256k1_dleq_prove(
+ ctx: *const Context,
+ s: *mut c_uchar,
+ e: *mut c_uchar,
+ sk: *const c_uchar,
+ gen2: *const PublicKey,
+ p1: *const PublicKey,
+ p2: *const PublicKey,
+ ) -> c_int;
+
+ pub fn bitbox_secp256k1_dleq_verify(
+ ctx: *const Context,
+ s: *const c_uchar,
+ e: *const c_uchar,
+ p1: *const PublicKey,
+ gen2: *const PublicKey,
+ p2: *const PublicKey,
+ ) -> c_int;
+ }
+}
+
+pub struct SignResult {
+ pub signature: [u8; 64],
+ pub recid: u8,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct GlobalContext {
+ __private: (), // prevents direct init
+}
+
+/// Global context, initialized once.
+///
+/// Port of https://docs.rs/secp256k1/latest/secp256k1/global/struct.GlobalContext.html to no_std.
+pub static SECP256K1: &GlobalContext = &GlobalContext { __private: () };
+
+struct SyncWrapper(OnceCell<Secp256k1<All>>);
+
+// SAFETY: Embedded single-threaded use only, can't use from an interrupt context.
+unsafe impl Sync for SyncWrapper {}
+
+impl Deref for GlobalContext {
+ type Target = Secp256k1<All>;
+
+ fn deref(&self) -> &Self::Target {
+ static CONTEXT: SyncWrapper = SyncWrapper(OnceCell::new());
+
+ CONTEXT.0.get_or_init(|| {
+ // Initialized on first access
+ Secp256k1::new()
+ })
+ }
+}
+
+/// Sign message with private key using the given private key.
+///
+/// Details about `host_nonce`, the host nonce contribution. Instead of using plain rfc6979 to
+/// generate the nonce in this signature, the following formula is used:
+///
+/// r = rfc6979(..., additional_data=Hash_d(host_nonce))
+/// R = r * G (pubkey to secret r)
+/// nonce = r + Hash_p(R, host_nonce)
+/// `Hash_d(msg)` and `Hash_p(msg)` are tagged hashes: `sha256(sha256(tag)||sha256(tag)||msg)`.
+/// Tag for `Hash_d`: "s2c/ecdsa/data".
+/// Tag for `Hash_p`: "s2c/ecdsa/point".
+/// This is part of the ECDSA Anti-Klepto protocol, preventing this function to leak any secrets via
+/// the signatures (see the ecdsa-s2c module in secp256k1-zpk for more details).
+///
+/// # Arguments
+/// * `private_key` - 32 byte private key
+/// * `msg` - 32 byte message to sign
+/// * `host_nonce` - 32 byte nonce contribution. Cannot be NULL.
+/// Intended to be a contribution by the host. If there is none available, use 32 zero bytes.
+///
+/// # Returns
+/// * `Ok(SignResult)` containing signature in compact format and recoverable id on success
+/// * `Err(())` on error.
+pub fn secp256k1_sign(
+ private_key: &[u8; 32],
+ msg: &[u8; 32],
+ host_nonce: &[u8; 32],
+) -> Result<SignResult, ()> {
+ let mut sig = MaybeUninit::<bitcoin::secp256k1::ffi::Signature>::uninit();
+ let mut recid: c_int = 0;
+ if unsafe {
+ ffi::secp256k1_anti_exfil_sign(
+ SECP256K1.ctx().as_ptr(),
+ sig.as_mut_ptr(),
+ msg.as_ptr(),
+ private_key.as_ptr(),
+ host_nonce.as_ptr(),
+ &mut recid,
+ )
+ } != 1
+ {
+ return Err(());
+ }
+
+ let mut signature = [0u8; 64];
+ if unsafe {
+ ffi::secp256k1_ecdsa_signature_serialize_compact(
+ SECP256K1.ctx().as_ptr(),
+ signature.as_mut_ptr(),
+ sig.as_ptr(),
+ )
+ } != 1
+ {
+ return Err(());
+ }
+ Ok(SignResult {
+ signature,
+ recid: recid.try_into().unwrap(),
+ })
+}
+
+/// Get a commitment to the original nonce before tweaking it with the host nonce. This is part of
+/// the ECDSA Anti-Klepto Protocol. For more details, check the docs of
+/// `secp256k1_ecdsa_anti_exfil_signer_commit`.
+///
+/// # Arguments
+/// * `private_key` - 32 byte private key
+/// * `msg` - 32 byte message which will be signed by `secp256k1_sign`
+/// * `host_commitment` - must be `sha256(sha256(tag)||sha256(tag)||host_nonce)` where
+/// host_nonce is passed to `secp256k1_sign()`. See `secp256k1_ecdsa_anti_exfil_host_commit()`.
+///
+/// # Returns
+/// * `Ok([u8; PUBLIC_KEY_SIZE])` - PUBLIC_KEY_SIZE bytes compressed signer nonce pubkey on success
+/// * `Err(())` on failure
+pub fn secp256k1_nonce_commit(
+ private_key: &[u8; 32],
+ msg: &[u8; 32],
+ host_commitment: &[u8; 32],
+) -> Result<[u8; PUBLIC_KEY_SIZE], ()> {
+ let mut signer_commitment = MaybeUninit::<ffi::secp256k1_ecdsa_s2c_opening>::uninit();
+ if unsafe {
+ ffi::secp256k1_ecdsa_anti_exfil_signer_commit(
+ SECP256K1.ctx().as_ptr(),
+ signer_commitment.as_mut_ptr(),
+ msg.as_ptr(),
+ private_key.as_ptr(),
+ host_commitment.as_ptr(),
+ )
+ } != 1
+ {
+ return Err(());
+ }
+
+ let mut out = [0u8; PUBLIC_KEY_SIZE];
+ if unsafe {
+ ffi::secp256k1_ecdsa_s2c_opening_serialize(
+ SECP256K1.ctx().as_ptr(),
+ out.as_mut_ptr(),
+ signer_commitment.as_ptr(),
+ )
+ } != 1
+ {
+ return Err(());
+ }
+ Ok(out)
+}
+
+pub fn ecdsa_anti_exfil_host_commit(secp: &Secp256k1<All>, rand32: &[u8]) -> Result<Vec<u8>, ()> {
+ let mut out = [0u8; 32];
+ match unsafe {
+ ffi::secp256k1_ecdsa_anti_exfil_host_commit(
+ secp.ctx().as_ptr(),
+ out.as_mut_ptr(),
+ rand32.as_ptr(),
+ )
+ } {
+ 1 => Ok(out.to_vec()),
+ _ => Err(()),
+ }
+}
+
+#[cfg(feature = "testing")]
+pub fn anti_exfil_host_verify(
+ secp: &Secp256k1<All>,
+ signature: &bitcoin::secp256k1::ecdsa::Signature,
+ msg: &[u8; 32],
+ pubkey: &bitcoin::secp256k1::PublicKey,
+ host_nonce: &[u8; 32],
+ signer_commitment: &[u8; 33],
+) -> Result<(), ()> {
+ let mut opening = MaybeUninit::<ffi::secp256k1_ecdsa_s2c_opening>::uninit();
+ let parse_res = unsafe {
+ ffi::secp256k1_ecdsa_s2c_opening_parse(
+ secp.ctx().as_ptr(),
+ opening.as_mut_ptr(),
+ signer_commitment.as_ptr(),
+ )
+ };
+ if parse_res != 1 {
+ return Err(());
+ }
+ let opening = unsafe { opening.assume_init() };
+ let verify_res = unsafe {
+ ffi::secp256k1_anti_exfil_host_verify(
+ secp.ctx().as_ptr(),
+ signature.as_c_ptr(),
+ msg.as_ptr(),
+ pubkey.as_c_ptr(),
+ host_nonce.as_ptr(),
+ &opening,
+ )
+ };
+ if verify_res == 1 { Ok(()) } else { Err(()) }
+}
+
+pub fn dleq_prove(
+ secp: &Secp256k1<All>,
+ 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 {
+ ffi::bitbox_secp256k1_dleq_prove(
+ secp.ctx().as_ptr(),
+ s.as_mut_ptr(),
+ e.as_mut_ptr(),
+ sk.as_ptr(),
+ gen2.as_c_ptr(),
+ p1.as_c_ptr(),
+ p2.as_c_ptr(),
+ )
+ };
+ if result == 1 {
+ let mut result = s.to_vec();
+ result.extend(&e);
+ Ok(result)
+ } else {
+ Err(())
+ }
+}
+
+pub fn dleq_verify(
+ secp: &Secp256k1<All>,
+ proof: [u8; 64],
+ gen2: &bitcoin::secp256k1::PublicKey,
+ p1: &bitcoin::secp256k1::PublicKey,
+ p2: &bitcoin::secp256k1::PublicKey,
+) -> Result<(), ()> {
+ let result = unsafe {
+ ffi::bitbox_secp256k1_dleq_verify(
+ secp.ctx().as_ptr(),
+ proof[..32].as_ptr(),
+ proof[32..].as_ptr(),
+ p1.as_c_ptr(),
+ gen2.as_c_ptr(),
+ p2.as_c_ptr(),
+ )
+ };
+ if result == 1 { Ok(()) } else { Err(()) }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use hex_lit::hex;
+
+ use bitcoin::secp256k1;
+ 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(&secp, 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(
+ &secp,
+ proof.try_into().unwrap(),
+ &other_base,
+ &pubkey,
+ &other_pubkey,
+ )
+ .unwrap();
+ }
+
+ #[test]
+ fn test_secp256k1_sign() {
+ let private_key = hex!("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c");
+ let msg = [0x88u8; 32];
+ let host_nonce = [0x56u8; 32];
+
+ let sign_result = secp256k1_sign(&private_key, &msg, &host_nonce).unwrap();
+
+ // Verify signature against expected pubkey.
+
+ let expected_pubkey = {
+ let pubkey = hex!("023ffb4a4e41444d40e4e1e4c6cc329bcba2be50d0ef380aea19d490c373be58fb");
+ secp256k1::PublicKey::from_slice(&pubkey).unwrap()
+ };
+ let msg = secp256k1::Message::from_digest_slice(&msg).unwrap();
+ // Test recid by recovering the public key from the signature and checking against the
+ // expected public key.
+ let recoverable_sig = secp256k1::ecdsa::RecoverableSignature::from_compact(
+ &sign_result.signature,
+ secp256k1::ecdsa::RecoveryId::from_i32(sign_result.recid as i32).unwrap(),
+ )
+ .unwrap();
+
+ let recovered_pubkey = SECP256K1.recover_ecdsa(&msg, &recoverable_sig).unwrap();
+ assert_eq!(recovered_pubkey, expected_pubkey);
+
+ // Verify signature.
+ assert!(
+ SECP256K1
+ .verify_ecdsa(&msg, &recoverable_sig.to_standard(), &expected_pubkey)
+ .is_ok()
+ );
+ }
+
+ #[test]
+ fn test_secp256k1_nonce_commit() {
+ let private_key = hex!("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c");
+ let msg = [0x88u8; 32];
+ let host_commitment = [0xabu8; 32];
+
+ let client_commitment =
+ secp256k1_nonce_commit(&private_key, &msg, &host_commitment).unwrap();
+ assert_eq!(
+ hex::encode(client_commitment),
+ "0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",
+ );
+ }
+}
diff --git a/src/rust/bitbox02-rust/Cargo.toml b/src/rust/bitbox02-rust/Cargo.toml
index ddf1f6e..b175b78 100644
--- a/src/rust/bitbox02-rust/Cargo.toml
+++ b/src/rust/bitbox02-rust/Cargo.toml
@@ -15,6 +15,7 @@ doctest = false
[dependencies]
bitbox02 = {path = "../bitbox02"}
+bitbox-secp256k1 = { path = "../bitbox-secp256k1" }
util = { path = "../util" }
erc20_params = { path = "../erc20_params", optional = true }
binascii = { version = "0.1.4", default-features = false, features = ["encode"] }
@@ -89,6 +90,7 @@ app-cardano = [
testing = [
"bitbox02/testing",
+ "bitbox-secp256k1/testing",
"util/testing"
]
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 37283a3..f3cad9d 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -2697,7 +2697,7 @@ mod tests {
// tested in keystore::tests::test_secp256k1_antiklepto_protocol. That the host nonce was included in the sig is
// tested by the signature fixture test below.
let host_nonce_commitment = pb::AntiKleptoHostNonceCommitment {
- commitment: bitbox02::secp256k1::ecdsa_anti_exfil_host_commit(SECP256K1, &host_nonce)
+ commitment: bitbox_secp256k1::ecdsa_anti_exfil_host_commit(SECP256K1, &host_nonce)
.unwrap(),
};
transaction.borrow_mut().inputs[1].host_nonce = Some(host_nonce.to_vec());
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 85e694a..0dba157 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -1939,7 +1939,7 @@ mod tests {
// Protocol step 1.
let host_commitment_vec =
- bitbox02::secp256k1::ecdsa_anti_exfil_host_commit(SECP256K1, &host_nonce).unwrap();
+ bitbox_secp256k1::ecdsa_anti_exfil_host_commit(SECP256K1, &host_nonce).unwrap();
let host_commitment: [u8; 32] = host_commitment_vec.try_into().unwrap();
// Get pubkey at keypath.
@@ -1963,7 +1963,7 @@ mod tests {
let signature =
secp256k1::ecdsa::Signature::from_compact(&sign_result.signature).unwrap();
// Protocol step 5: host verification.
- bitbox02::secp256k1::anti_exfil_host_verify(
+ bitbox_secp256k1::anti_exfil_host_verify(
SECP256K1,
&signature,
&msg,
diff --git a/src/rust/bitbox02-rust/src/secp256k1.rs b/src/rust/bitbox02-rust/src/secp256k1.rs
index 8a9217d..414d57f 100644
--- a/src/rust/bitbox02-rust/src/secp256k1.rs
+++ b/src/rust/bitbox02-rust/src/secp256k1.rs
@@ -1,146 +1,3 @@
// SPDX-License-Identifier: Apache-2.0
-pub use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
-use bitcoin::secp256k1::{All, Secp256k1};
-
-use core::cell::OnceCell;
-use core::ops::Deref;
-
-pub use bitbox02::secp256k1::SignResult;
-
-#[derive(Debug, Copy, Clone)]
-pub struct GlobalContext {
- __private: (), // prevents direct init
-}
-
-/// Global context, initialized once.
-///
-/// Port of https://docs.rs/secp256k1/latest/secp256k1/global/struct.GlobalContext.html to no_std.
-pub static SECP256K1: &GlobalContext = &GlobalContext { __private: () };
-
-struct SyncWrapper(OnceCell<Secp256k1<All>>);
-
-// SAFETY: Embedded single-threaded use only, can't use from an interrupt context.
-unsafe impl Sync for SyncWrapper {}
-
-impl Deref for GlobalContext {
- type Target = Secp256k1<All>;
-
- fn deref(&self) -> &Self::Target {
- static CONTEXT: SyncWrapper = SyncWrapper(OnceCell::new());
-
- CONTEXT.0.get_or_init(|| {
- // Initialized on first access
- Secp256k1::new()
- })
- }
-}
-
-/// Sign message with private key using the given private key.
-///
-/// Details about `host_nonce`, the host nonce contribution. Instead of using plain rfc6979 to
-/// generate the nonce in this signature, the following formula is used:
-///
-/// r = rfc6979(..., additional_data=Hash_d(host_nonce))
-/// R = r * G (pubkey to secret r)
-/// nonce = r + Hash_p(R, host_nonce)
-/// `Hash_d(msg)` and `Hash_p(msg)` are tagged hashes: `sha256(sha256(tag)||sha256(tag)||msg)`.
-/// Tag for `Hash_d`: "s2c/ecdsa/data".
-/// Tag for `Hash_p`: "s2c/ecdsa/point".
-/// This is part of the ECDSA Anti-Klepto protocol, preventing this function to leak any secrets via
-/// the signatures (see the ecdsa-s2c module in secp256k1-zpk for more details).
-///
-/// # Arguments
-/// * `private_key` - 32 byte private key
-/// * `msg` - 32 byte message to sign
-/// * `host_nonce` - 32 byte nonce contribution. Cannot be NULL.
-/// Intended to be a contribution by the host. If there is none available, use 32 zero bytes.
-///
-/// # Returns
-/// * `Ok(SignResult)` containing signature in compact format and recoverable id on success
-/// * `Err(())` on error.
-pub fn secp256k1_sign(
- private_key: &[u8; 32],
- msg: &[u8; 32],
- host_nonce: &[u8; 32],
-) -> Result<SignResult, ()> {
- bitbox02::secp256k1::_secp256k1_sign(SECP256K1, private_key, msg, host_nonce)
-}
-
-/// Get a commitment to the original nonce before tweaking it with the host nonce. This is part of
-/// the ECDSA Anti-Klepto Protocol. For more details, check the docs of
-/// `secp256k1_ecdsa_anti_exfil_signer_commit`.
-///
-/// # Arguments
-/// * `private_key` - 32 byte private key
-/// * `msg` - 32 byte message which will be signed by `secp256k1_sign`
-/// * `host_commitment` - must be `sha256(sha256(tag)||sha256(tag)||host_nonce)` where
-/// host_nonce is passed to `secp256k1_sign()`. See `secp256k1_ecdsa_anti_exfil_host_commit()`.
-///
-/// # Returns
-/// * `Ok([u8; PUBLIC_KEY_SIZE])` - PUBLIC_KEY_SIZE bytes compressed signer nonce pubkey on success
-/// * `Err(())` on failure
-pub fn secp256k1_nonce_commit(
- private_key: &[u8; 32],
- msg: &[u8; 32],
- host_commitment: &[u8; 32],
-) -> Result<[u8; PUBLIC_KEY_SIZE], ()> {
- bitbox02::secp256k1::_secp256k1_nonce_commit(SECP256K1, private_key, msg, host_commitment)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use hex_lit::hex;
-
- use bitcoin::secp256k1;
-
- #[test]
- fn test_secp256k1_sign() {
- let private_key = hex!("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c");
- let msg = [0x88u8; 32];
- let host_nonce = [0x56u8; 32];
-
- let sign_result = secp256k1_sign(&private_key, &msg, &host_nonce).unwrap();
-
- // Verify signature against expected pubkey.
-
- let expected_pubkey = {
- let pubkey = hex!("023ffb4a4e41444d40e4e1e4c6cc329bcba2be50d0ef380aea19d490c373be58fb");
- secp256k1::PublicKey::from_slice(&pubkey).unwrap()
- };
- let msg = secp256k1::Message::from_digest_slice(&msg).unwrap();
- // Test recid by recovering the public key from the signature and checking against the
- // expected public key.
- let recoverable_sig = secp256k1::ecdsa::RecoverableSignature::from_compact(
- &sign_result.signature,
- secp256k1::ecdsa::RecoveryId::from_i32(sign_result.recid as i32).unwrap(),
- )
- .unwrap();
-
- let recovered_pubkey = SECP256K1.recover_ecdsa(&msg, &recoverable_sig).unwrap();
- assert_eq!(recovered_pubkey, expected_pubkey);
-
- // Verify signature.
- assert!(
- SECP256K1
- .verify_ecdsa(&msg, &recoverable_sig.to_standard(), &expected_pubkey)
- .is_ok()
- );
- }
-
- #[test]
- fn test_secp256k1_nonce_commit() {
- let private_key = hex!("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c");
- let msg = [0x88u8; 32];
- let host_commitment = [0xabu8; 32];
-
- let client_commitment =
- secp256k1_nonce_commit(&private_key, &msg, &host_commitment).unwrap();
- assert_eq!(
- hex::encode(client_commitment),
- "0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",
- );
- }
-}
+pub use bitbox_secp256k1::*;
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 861284b..313896d 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -42,9 +42,6 @@ const ALLOWLIST_TYPES: &[&str] = &[
"delay_t",
"event_slider_data_t",
"event_types",
- "secp256k1_ecdsa_s2c_opening",
- "secp256k1_ecdsa_signature",
- "secp256k1_pubkey",
"securechip_error_t",
"trinary_input_string_params_t",
"UG_COLOR",
@@ -57,8 +54,6 @@ const ALLOWLIST_FNS: &[&str] = &[
"bitbox02_smarteeprom_get_unlock_attempts",
"bitbox02_smarteeprom_increment_unlock_attempts",
"bitbox02_smarteeprom_reset_unlock_attempts",
- "bitbox_secp256k1_dleq_prove",
- "bitbox_secp256k1_dleq_verify",
"bitbox02_smarteeprom_init",
"confirm_create",
"confirm_transaction_address_create",
@@ -147,13 +142,6 @@ const ALLOWLIST_FNS: &[&str] = &[
"sd_load_bin",
"sd_write_bin",
"sdcard_create",
- "secp256k1_anti_exfil_sign",
- "secp256k1_anti_exfil_host_verify",
- "secp256k1_ecdsa_anti_exfil_signer_commit",
- "secp256k1_ecdsa_anti_exfil_host_commit",
- "secp256k1_ecdsa_s2c_opening_serialize",
- "secp256k1_ecdsa_s2c_opening_parse",
- "secp256k1_ecdsa_signature_serialize_compact",
"securechip_attestation_sign",
"securechip_init_new_password",
"securechip_kdf",
@@ -328,8 +316,6 @@ pub fn main() -> Result<(), &'static str> {
"../../usb/class/hid",
"../../usb/class/hid/hww",
"../../usb/class/hid/u2f",
- // $SECP256k1_INCLUDES
- "../../../external/secp256k1-zkp/include",
// ASF4 headers allowed in unit tests
"../../../external/asf4-drivers/hal/utils/include",
// fatfs
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index 98e2ca2..f3415b9 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -11,8 +11,6 @@
#include <reset.h>
#include <screen.h>
#include <sd.h>
-#include <secp256k1_ecdsa_adaptor.h>
-#include <secp256k1_ecdsa_s2c.h>
#include <securechip/securechip.h>
#include <system.h>
#include <time.h>
diff --git a/src/rust/bitbox02/Cargo.toml b/src/rust/bitbox02/Cargo.toml
index b05c713..f8d903b 100644
--- a/src/rust/bitbox02/Cargo.toml
+++ b/src/rust/bitbox02/Cargo.toml
@@ -12,12 +12,9 @@ license = "Apache-2.0"
bitbox02-sys = {path="../bitbox02-sys"}
util = {path = "../util"}
zeroize = { workspace = true }
-bitcoin = { workspace = true }
bip39 = { workspace = true }
-hex = { workspace = true }
[dev-dependencies]
-hex = { workspace = true }
bitbox-aes = { path = "../bitbox-aes" }
bitbox02-rust = { path = "../bitbox02-rust" }
hex_lit = { workspace = true }
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index f59d1ff..9d9228f 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -35,7 +35,6 @@ pub mod random;
pub mod screen;
pub mod screen_saver;
pub mod sd;
-pub mod secp256k1;
pub mod securechip;
pub mod smarteeprom;
pub mod spi_mem;
diff --git a/src/rust/bitbox02/src/secp256k1.rs b/src/rust/bitbox02/src/secp256k1.rs
deleted file mode 100644
index b9d164a..0000000
--- a/src/rust/bitbox02/src/secp256k1.rs
+++ /dev/null
@@ -1,221 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use bitcoin::secp256k1::ffi::CPtr;
-
-pub use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
-use bitcoin::secp256k1::{All, Secp256k1};
-
-use alloc::vec::Vec;
-use core::mem::MaybeUninit;
-
-pub struct SignResult {
- pub signature: [u8; 64],
- pub recid: u8,
-}
-
-pub fn _secp256k1_sign(
- secp: &Secp256k1<All>,
- private_key: &[u8; 32],
- msg: &[u8; 32],
- host_nonce: &[u8; 32],
-) -> Result<SignResult, ()> {
- let mut sig = MaybeUninit::<bitbox02_sys::secp256k1_ecdsa_signature>::uninit();
- let mut recid: core::ffi::c_int = 0;
- if unsafe {
- bitbox02_sys::secp256k1_anti_exfil_sign(
- secp.ctx().as_ptr().cast(),
- sig.as_mut_ptr(),
- msg.as_ptr(),
- private_key.as_ptr(),
- host_nonce.as_ptr(),
- &mut recid,
- )
- } != 1
- {
- return Err(());
- }
-
- let mut signature = [0u8; 64];
- if unsafe {
- bitbox02_sys::secp256k1_ecdsa_signature_serialize_compact(
- secp.ctx().as_ptr().cast(),
- signature.as_mut_ptr(),
- sig.as_ptr(),
- )
- } != 1
- {
- return Err(());
- }
- Ok(SignResult {
- signature,
- recid: recid.try_into().unwrap(),
- })
-}
-
-pub fn _secp256k1_nonce_commit(
- secp: &Secp256k1<All>,
- private_key: &[u8; 32],
- msg: &[u8; 32],
- host_commitment: &[u8; 32],
-) -> Result<[u8; PUBLIC_KEY_SIZE], ()> {
- let mut signer_commitment = MaybeUninit::<bitbox02_sys::secp256k1_ecdsa_s2c_opening>::uninit();
- if unsafe {
- bitbox02_sys::secp256k1_ecdsa_anti_exfil_signer_commit(
- secp.ctx().as_ptr().cast(),
- signer_commitment.as_mut_ptr(),
- msg.as_ptr(),
- private_key.as_ptr(),
- host_commitment.as_ptr(),
- )
- } != 1
- {
- return Err(());
- }
-
- let mut out = [0u8; PUBLIC_KEY_SIZE];
- if unsafe {
- bitbox02_sys::secp256k1_ecdsa_s2c_opening_serialize(
- secp.ctx().as_ptr().cast(),
- out.as_mut_ptr(),
- signer_commitment.as_ptr(),
- )
- } != 1
- {
- return Err(());
- }
- Ok(out)
-}
-
-pub fn ecdsa_anti_exfil_host_commit(secp: &Secp256k1<All>, rand32: &[u8]) -> Result<Vec<u8>, ()> {
- let mut out = [0u8; 32];
- match unsafe {
- bitbox02_sys::secp256k1_ecdsa_anti_exfil_host_commit(
- secp.ctx().as_ptr().cast(),
- out.as_mut_ptr(),
- rand32.as_ptr(),
- )
- } {
- 1 => Ok(out.to_vec()),
- _ => Err(()),
- }
-}
-
-#[cfg(feature = "testing")]
-pub fn anti_exfil_host_verify(
- secp: &Secp256k1<All>,
- signature: &bitcoin::secp256k1::ecdsa::Signature,
- msg: &[u8; 32],
- pubkey: &bitcoin::secp256k1::PublicKey,
- host_nonce: &[u8; 32],
- signer_commitment: &[u8; 33],
-) -> Result<(), ()> {
- let mut opening = core::mem::MaybeUninit::<bitbox02_sys::secp256k1_ecdsa_s2c_opening>::uninit();
- let parse_res = unsafe {
- bitbox02_sys::secp256k1_ecdsa_s2c_opening_parse(
- secp.ctx().as_ptr().cast(),
- opening.as_mut_ptr(),
- signer_commitment.as_ptr(),
- )
- };
- if parse_res != 1 {
- return Err(());
- }
- let opening = unsafe { opening.assume_init() };
- let verify_res = unsafe {
- bitbox02_sys::secp256k1_anti_exfil_host_verify(
- secp.ctx().as_ptr().cast(),
- signature.as_c_ptr() as *const bitbox02_sys::secp256k1_ecdsa_signature,
- msg.as_ptr(),
- pubkey.as_c_ptr() as *const bitbox02_sys::secp256k1_pubkey,
- host_nonce.as_ptr(),
- &opening,
- )
- };
- if verify_res == 1 { Ok(()) } else { Err(()) }
-}
-
-pub fn dleq_prove(
- secp: &Secp256k1<All>,
- 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(
- secp.ctx().as_ptr().cast(),
- 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(
- secp: &Secp256k1<All>,
- 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(
- secp.ctx().as_ptr().cast(),
- 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(&secp, 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(
- &secp,
- proof.try_into().unwrap(),
- &other_base,
- &pubkey,
- &other_pubkey,
- )
- .unwrap();
- }
-}
diff --git a/src/rust/streaming-silent-payments/Cargo.toml b/src/rust/streaming-silent-payments/Cargo.toml
index 2a7ccb6..3ee5f4e 100644
--- a/src/rust/streaming-silent-payments/Cargo.toml
+++ b/src/rust/streaming-silent-payments/Cargo.toml
@@ -9,7 +9,7 @@ license = "Apache-2.0"
[dependencies]
bitcoin = { workspace = true }
-bitbox02 = { path = "../bitbox02" }
+bitbox-secp256k1 = { path = "../bitbox-secp256k1" }
[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
diff --git a/src/rust/streaming-silent-payments/src/lib.rs b/src/rust/streaming-silent-payments/src/lib.rs
index 13ffe24..16b7dbc 100644
--- a/src/rust/streaming-silent-payments/src/lib.rs
+++ b/src/rust/streaming-silent-payments/src/lib.rs
@@ -130,15 +130,10 @@ fn create_dleq_proof(
.mul_tweak(secp, &Scalar::from(*a_sum))
.map_err(|_| ())?;
- let proof = bitbox02::secp256k1::dleq_prove(
- secp,
- a_sum.as_ref(),
- scan_pubkey,
- a_sum_pubkey,
- &c_pubkey,
- )?;
+ let proof =
+ bitbox_secp256k1::dleq_prove(secp, a_sum.as_ref(), scan_pubkey, a_sum_pubkey, &c_pubkey)?;
// Sanity check.
- bitbox02::secp256k1::dleq_verify(
+ bitbox_secp256k1::dleq_verify(
secp,
proof.as_slice().try_into().unwrap(),
scan_pubkey,
diff --git a/test/simulator-graphical-bb03/CMakeLists.txt b/test/simulator-graphical-bb03/CMakeLists.txt
index 7d45281..1cf7313 100644
--- a/test/simulator-graphical-bb03/CMakeLists.txt
+++ b/test/simulator-graphical-bb03/CMakeLists.txt
@@ -6,7 +6,10 @@
set(RUST_BINARY_DIR ${CMAKE_BINARY_DIR}/src/rust)
-set(RUSTFLAGS "-L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lstatic=fatfs -lstatic=secp256k1 --cfg=rust_secp_no_symbol_renaming")
+# Use libsecp256k1 that we link ourselves.
+# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
+# This is replicated in .cargo/config.toml because RUSTFLAGS here override the rustflags there.
+set(RUSTFLAGS "-L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lstatic=fatfs --cfg=rust_secp_no_symbol_renaming")
if(SANITIZE_ADDRESS OR SANITIZE_UNDEFINED)
string(APPEND RUSTFLAGS " -Zexternal-clangrt -Cdefault-linker-libraries=y")
@@ -44,7 +47,7 @@ add_custom_command(
${CMAKE_COMMAND} -E copy
${RUST_BINARY_DIR}/${LLVM_HOST_TUPLE}/release/simulator-graphical-bb03
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/simulator-graphical-bb03
- DEPENDS rust-cbindgen secp256k1 fatfs
+ DEPENDS rust-cbindgen fatfs
WORKING_DIRECTORY ${RUST_BINARY_DIR}
)
diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock
index b09ab5a..881403b 100644
--- a/test/simulator-graphical-bb03/Cargo.lock
+++ b/test/simulator-graphical-bb03/Cargo.lock
@@ -331,14 +331,20 @@ dependencies = [
"util",
]
+[[package]]
+name = "bitbox-secp256k1"
+version = "0.1.0"
+dependencies = [
+ "bitcoin",
+ "cc",
+]
+
[[package]]
name = "bitbox02"
version = "0.1.0"
dependencies = [
"bip39",
"bitbox02-sys",
- "bitcoin",
- "hex",
"util",
"zeroize",
]
@@ -360,6 +366,7 @@ dependencies = [
"bip32-ed25519",
"bip39",
"bitbox-aes",
+ "bitbox-secp256k1",
"bitbox02",
"bitbox02-noise",
"bitcoin",
@@ -2874,7 +2881,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
name = "streaming-silent-payments"
version = "0.1.0"
dependencies = [
- "bitbox02",
+ "bitbox-secp256k1",
"bitcoin",
]
diff --git a/test/simulator-graphical/CMakeLists.txt b/test/simulator-graphical/CMakeLists.txt
index fa5fd4a..fd43f0d 100644
--- a/test/simulator-graphical/CMakeLists.txt
+++ b/test/simulator-graphical/CMakeLists.txt
@@ -6,7 +6,10 @@
set(RUST_BINARY_DIR ${CMAKE_BINARY_DIR}/src/rust)
-set(RUSTFLAGS "-L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lstatic=fatfs -lstatic=secp256k1 --cfg=rust_secp_no_symbol_renaming")
+# Use libsecp256k1 that we link ourselves.
+# See https://github.com/rust-bitcoin/rust-secp256k1/tree/7c8270a8506e31731e540fab7ee1abde1f48314e/secp256k1-sys#linking-to-external-symbols
+# This is replicated in .cargo/config.toml because RUSTFLAGS here override the rustflags there.
+set(RUSTFLAGS "-L${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} -lstatic=fatfs --cfg=rust_secp_no_symbol_renaming")
if(SANITIZE_ADDRESS OR SANITIZE_UNDEFINED)
string(APPEND RUSTFLAGS " -Zexternal-clangrt -Cdefault-linker-libraries=y")
@@ -44,7 +47,7 @@ add_custom_command(
${CMAKE_COMMAND} -E copy
${RUST_BINARY_DIR}/${LLVM_HOST_TUPLE}/release/simulator-graphical
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/simulator-graphical
- DEPENDS rust-cbindgen secp256k1 fatfs
+ DEPENDS rust-cbindgen fatfs
WORKING_DIRECTORY ${RUST_BINARY_DIR}
)
diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock
index 22aebd1..42f6d66 100644
--- a/test/simulator-graphical/Cargo.lock
+++ b/test/simulator-graphical/Cargo.lock
@@ -293,14 +293,20 @@ dependencies = [
"util",
]
+[[package]]
+name = "bitbox-secp256k1"
+version = "0.1.0"
+dependencies = [
+ "bitcoin",
+ "cc",
+]
+
[[package]]
name = "bitbox02"
version = "0.1.0"
dependencies = [
"bip39",
"bitbox02-sys",
- "bitcoin",
- "hex",
"util",
"zeroize",
]
@@ -322,6 +328,7 @@ dependencies = [
"bip32-ed25519",
"bip39",
"bitbox-aes",
+ "bitbox-secp256k1",
"bitbox02",
"bitbox02-noise",
"bitcoin",
@@ -2818,7 +2825,7 @@ dependencies = [
name = "streaming-silent-payments"
version = "0.1.0"
dependencies = [
- "bitbox02",
+ "bitbox-secp256k1",
"bitcoin",
]
diff --git a/test/simulator/CMakeLists.txt b/test/simulator/CMakeLists.txt
index 6dcbd76..3325ac4 100644
--- a/test/simulator/CMakeLists.txt
+++ b/test/simulator/CMakeLists.txt
@@ -10,7 +10,6 @@ target_link_libraries(simulator PRIVATE
c-unit-tests_rust_c
fatfs
$<$<NOT:$<PLATFORM_ID:Darwin>>:-Wl,--end-group>
- secp256k1
)
add_custom_command(
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index cdc75ea..da824c4 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -68,7 +68,6 @@ else()
mocks
fatfs
-Wl,--end-group
- secp256k1
${TEST_LINK_ARGS}
)
target_include_directories(${EXE} PRIVATE
@@ -143,7 +142,6 @@ foreach(TEST_NAME ${U2F_TESTS})
fatfs
$<$<NOT:$<PLATFORM_ID:Darwin>>:-Wl,--end-group>
u2f-util
- secp256k1
)
add_test(NAME test_${TEST_NAME} COMMAND ${EXE})
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.