rust/keystore: wrap secp256k1_nonce_commit() in bitbox02-rust
What changed, and why it matters
This commit is a routine internal code reorganization in the BitBox02 firmware. It moves a cryptographic helper function, used in the anti-klepto signing protocol, from a low-level C-wrapping module into a higher-level Rust keystore module. The actual behavior of the function does not change; callers are simply updated to use the new location. There is no indication this fixes a security vulnerability.
No security action required. Treat as normal maintenance/refactoring commit. Continue standard review and testing.
Security signals we found
Refactoring only: no functional change to cryptographic operations
Anti-Klepto protocol helper relocated, not modified
No boundary/length/validation changes observed
No vendor security disclosure or advisory referenced
Evidence from the diff
The change wraps secp256k1_nonce_commit() inside bitbox02-rust::keystore, renaming the original C-binding function to _secp256k1_nonce_commit() so the compiler will flag any remaining direct callers. Call sites in Bitcoin and Ethereum signing code are updated to use crate::keystore::secp256k1_nonce_commit() and no longer pass the SECP256K1 context explicitly. The underlying implementation still delegates to the same C function with the same arguments. A unit test is moved to the new module with identical inputs and expected output.
Changed components
bitbox02-rust/src/keystore.rsbitbox02/src/keystore.rsBitcoin message signingBitcoin transaction signingEthereum transaction/message signingInspect captured patch +61 / −52
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
index 20692df..be1a668 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
@@ -24,10 +24,9 @@ use pb::btc_script_config::{Config, SimpleType};
use pb::btc_response::Response;
-use bitbox02::keystore;
+use crate::keystore;
use crate::hal::Ui;
-use crate::secp256k1::SECP256K1;
use crate::workflow::{confirm, verify_message};
const MAX_MESSAGE_SIZE: usize = 1024;
@@ -99,8 +98,7 @@ pub async fn process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = keystore::secp256k1_nonce_commit(
- SECP256K1,
- crate::keystore::secp256k1_get_private_key(keypath)?
+ keystore::secp256k1_get_private_key(keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -119,8 +117,8 @@ pub async fn process(
None => [0; 32],
};
- let sign_result = crate::keystore::secp256k1_sign(
- crate::keystore::secp256k1_get_private_key(keypath)?
+ let sign_result = keystore::secp256k1_sign(
+ keystore::secp256k1_get_private_key(keypath)?
.as_slice()
.try_into()
.unwrap(),
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 1f69d56..629f386 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -1230,8 +1230,7 @@ async fn _process(
// Engage in the Anti-Klepto protocol if the host sends a host nonce commitment.
let host_nonce: [u8; 32] = match tx_input.host_nonce_commitment {
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
- let signer_commitment = bitbox02::keystore::secp256k1_nonce_commit(
- SECP256K1,
+ let signer_commitment = crate::keystore::secp256k1_nonce_commit(
private_key.as_slice().try_into().unwrap(),
&sighash,
commitment
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
index 4d33243..319ce8d 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -17,10 +17,9 @@ use super::amount::{Amount, calculate_percentage};
use super::params::Params;
use super::pb;
-use bitbox02::keystore;
+use crate::keystore;
use crate::hal::Ui;
-use crate::secp256k1::SECP256K1;
use crate::workflow::{confirm, transaction};
use alloc::vec::Vec;
@@ -390,8 +389,7 @@ pub async fn _process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { commitment }) => {
let signer_commitment = keystore::secp256k1_nonce_commit(
- SECP256K1,
- &crate::keystore::secp256k1_get_private_key(request.keypath())?
+ &keystore::secp256k1_get_private_key(request.keypath())?
.as_slice()
.try_into()
.unwrap(),
@@ -409,8 +407,8 @@ pub async fn _process(
// Return signature directly without the anti-klepto protocol, for backwards compatibility.
None => [0; 32],
};
- let sign_result = crate::keystore::secp256k1_sign(
- &crate::keystore::secp256k1_get_private_key(request.keypath())?
+ let sign_result = keystore::secp256k1_sign(
+ &keystore::secp256k1_get_private_key(request.keypath())?
.as_slice()
.try_into()
.unwrap(),
@@ -1176,7 +1174,7 @@ mod tests {
{
// Keystore locked.
- crate::keystore::lock();
+ keystore::lock();
assert_eq!(
block_on(process(
&mut TestingHal::new(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
index 4386cd8..01ec872 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
@@ -23,9 +23,8 @@ use super::Error;
use super::pb;
use crate::hal::Ui;
-use crate::secp256k1::SECP256K1;
+use crate::keystore;
use crate::workflow::confirm;
-use bitbox02::keystore;
use pb::eth_request::Request;
use pb::eth_response::Response;
@@ -564,8 +563,7 @@ pub async fn process(
let host_nonce = match request.host_nonce_commitment {
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = keystore::secp256k1_nonce_commit(
- SECP256K1,
- crate::keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(&request.keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -583,8 +581,8 @@ pub async fn process(
_ => return Err(Error::InvalidInput),
};
- let sign_result = crate::keystore::secp256k1_sign(
- crate::keystore::secp256k1_get_private_key(&request.keypath)?
+ let sign_result = keystore::secp256k1_sign(
+ keystore::secp256k1_get_private_key(&request.keypath)?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
index ec3fd51..32ea16c 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
@@ -15,9 +15,8 @@
use super::Error;
use super::pb;
-use bitbox02::keystore;
+use crate::keystore;
-use crate::secp256k1::SECP256K1;
use crate::workflow::verify_message;
use pb::eth_response::Response;
@@ -68,8 +67,7 @@ pub async fn process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = keystore::secp256k1_nonce_commit(
- SECP256K1,
- crate::keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(&request.keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -88,8 +86,8 @@ pub async fn process(
None => [0; 32],
};
- let sign_result = crate::keystore::secp256k1_sign(
- crate::keystore::secp256k1_get_private_key(&request.keypath)?
+ let sign_result = keystore::secp256k1_sign(
+ keystore::secp256k1_get_private_key(&request.keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -266,7 +264,7 @@ mod tests {
);
// Keystore locked.
- crate::keystore::lock();
+ keystore::lock();
assert_eq!(
block_on(process(
&mut TestingHal::new(),
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 1438b50..e325f46 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -28,6 +28,9 @@ use crate::secp256k1::SECP256K1;
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256, sha512};
+/// Length of a compressed secp256k1 pubkey.
+const EC_PUBLIC_KEY_LEN: usize = 33;
+
/// Locks the keystore (resets to state before `keystore::unlock()`).
pub fn lock() {
keystore::_lock();
@@ -289,6 +292,27 @@ pub fn secp256k1_sign(
keystore::_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; EC_PUBLIC_KEY_LEN])` - EC_PUBLIC_KEY_LEN 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; EC_PUBLIC_KEY_LEN], ()> {
+ keystore::_secp256k1_nonce_commit(SECP256K1, private_key, msg, host_commitment)
+}
+
/// Sign a message using the private key at the keypath, which is optionally tweaked with the given
/// tweak.
pub fn secp256k1_schnorr_sign(
@@ -884,6 +908,23 @@ mod tests {
);
}
+ #[test]
+ fn test_secp256k1_nonce_commit() {
+ let private_key =
+ hex::decode("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c")
+ .unwrap();
+ let msg = [0x88u8; 32];
+ let host_commitment = [0xabu8; 32];
+
+ let client_commitment =
+ secp256k1_nonce_commit(&private_key.try_into().unwrap(), &msg, &host_commitment)
+ .unwrap();
+ assert_eq!(
+ hex::encode(client_commitment),
+ "0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",
+ );
+ }
+
#[test]
fn test_secp256k1_schnorr_sign() {
mock_unlocked_using_mnemonic(
diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs
index a139d6a..8d2b46f 100644
--- a/src/rust/bitbox02/src/keystore.rs
+++ b/src/rust/bitbox02/src/keystore.rs
@@ -249,7 +249,7 @@ pub fn _secp256k1_sign(
}
}
-pub fn secp256k1_nonce_commit(
+pub fn _secp256k1_nonce_commit(
secp: &Secp256k1<All>,
private_key: &[u8; 32],
msg: &[u8; 32],
@@ -301,29 +301,6 @@ mod tests {
use crate::testing::mock_memory;
use util::bb02_async::block_on;
- #[test]
- fn test_secp256k1_nonce_commit() {
- let secp = secp256k1::Secp256k1::new();
-
- let private_key =
- hex::decode("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c")
- .unwrap();
- let msg = [0x88u8; 32];
- let host_commitment = [0xabu8; 32];
-
- let client_commitment = secp256k1_nonce_commit(
- &secp,
- &private_key.try_into().unwrap(),
- &msg,
- &host_commitment,
- )
- .unwrap();
- assert_eq!(
- hex::encode(client_commitment),
- "0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",
- );
- }
-
#[test]
fn test_bip39_mnemonic_to_seed() {
assert!(bip39_mnemonic_to_seed("invalid").is_err());
Why this scored 13/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.