Merge remote-tracking branch 'agent/benma-agent/document-zero-s2c-fallback'
What changed, and why it matters
This commit only adds documentation comments and does not change any code behavior. It explains that when a host computer does not provide an anti-klepto nonce commitment during Bitcoin or Ethereum signing, the device falls back to a historical signing mode that lacks anti-klepto protection. The actual fallback behavior already existed; this change just documents it more clearly.
No immediate action is required for this commit because it is documentation-only. However, users and integrators should ensure they provide `host_nonce_commitment` in signing requests to benefit from anti-klepto protection. Developers should consider whether the documented fallback behavior should be deprecated or removed in a future firmware version.
Security signals we found
Documents existing fallback to deterministic zero-contribution S2C signing when anti-klepto host nonce commitment is absent
Explicitly notes fallback lacks anti-klepto protection
No functional code changes; behavior was already present
Affects Bitcoin transaction signing, Bitcoin message signing, Ethereum transaction signing (legacy and EIP-1559), Ethereum message signing, and Ethereum typed data signing
Evidence from the diff
The commit updates protobuf definitions and generated Rust structs to add comments describing the behavior when host_nonce_commitment is omitted in BTC/ETH signing and message-signing requests. It also updates inline code comments in signmsg.rs, signtx.rs, ethereum/sign.rs, and ethereum/signmsg.rs. No functional code paths were modified. The fallback uses a deterministic zero-contribution S2C (signer-to-counterparty) signature instead of the anti-klepto protocol. This differs from plain RFC6979 and does not provide anti-klepto protection against a potentially malicious device leaking private key material through nonce bias.
Changed components
messages/btc.protomessages/eth.protosrc/rust/bitbox-proto/src/generated/shiftcrypto.bitbox02.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rssrc/rust/bitbox02-rust/src/hww/api/ethereum/sign.rssrc/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rsInspect captured patch +34 / −4
### messages/btc.proto
@@ -152,6 +152,8 @@ message BTCSignInputRequest {
repeated uint32 keypath = 6; // all inputs must be ours.
// References a script config from BTCSignInitRequest
uint32 script_config_index = 7;
+ // If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ // This differs from plain RFC6979 and does not provide anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 8;
}
@@ -281,6 +283,8 @@ message BTCSignMessageRequest {
BTCCoin coin = 1;
BTCScriptConfigWithKeypath script_config = 2;
bytes msg = 3;
+ // If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ // This differs from plain RFC6979 and does not provide anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 4;
}
### messages/eth.proto
@@ -48,6 +48,8 @@ message ETHSignRequest {
bytes recipient = 6; // 20 byte recipient
bytes value = 7; // smallest big endian serialization, max. 32 bytes
bytes data = 8;
+ // If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ // This differs from plain RFC6979 and does not provide anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 9;
// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
uint64 chain_id = 10;
@@ -67,6 +69,8 @@ message ETHSignEIP1559Request {
bytes recipient = 7; // 20 byte recipient
bytes value = 8; // smallest big endian serialization, max. 32 bytes
bytes data = 9;
+ // If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ // This differs from plain RFC6979 and does not provide anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 10;
ETHAddressCase address_case = 11;
// For streaming: if non-zero, data field should be empty and data will be requested in chunks
@@ -88,6 +92,8 @@ message ETHSignMessageRequest {
ETHCoin coin = 1;
repeated uint32 keypath = 2;
bytes msg = 3;
+ // If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ // This differs from plain RFC6979 and does not provide anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 4;
// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
uint64 chain_id = 5;
@@ -131,6 +137,7 @@ message ETHSignTypedMessageRequest {
repeated uint32 keypath = 2;
repeated StructType types = 3;
string primary_type = 4;
+ // If omitted, plain RFC6979 signing is used without anti-klepto protection.
AntiKleptoHostNonceCommitment host_nonce_commitment = 5;
}
### src/rust/bitbox-proto/src/generated/shiftcrypto.bitbox02.rs
@@ -804,6 +804,8 @@ pub struct BtcSignInputRequest {
/// References a script config from BTCSignInitRequest
#[prost(uint32, tag = "7")]
pub script_config_index: u32,
+ /// If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ /// This differs from plain RFC6979 and does not provide anti-klepto protection.
#[prost(message, optional, tag = "8")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
}
@@ -1047,6 +1049,8 @@ pub struct BtcSignMessageRequest {
pub script_config: ::core::option::Option<BtcScriptConfigWithKeypath>,
#[prost(bytes = "vec", tag = "3")]
pub msg: ::prost::alloc::vec::Vec<u8>,
+ /// If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ /// This differs from plain RFC6979 and does not provide anti-klepto protection.
#[prost(message, optional, tag = "4")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
}
@@ -1564,6 +1568,8 @@ pub struct EthSignRequest {
pub value: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "8")]
pub data: ::prost::alloc::vec::Vec<u8>,
+ /// If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ /// This differs from plain RFC6979 and does not provide anti-klepto protection.
#[prost(message, optional, tag = "9")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
/// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
@@ -1603,6 +1609,8 @@ pub struct EthSignEip1559Request {
pub value: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "9")]
pub data: ::prost::alloc::vec::Vec<u8>,
+ /// If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ /// This differs from plain RFC6979 and does not provide anti-klepto protection.
#[prost(message, optional, tag = "10")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
#[prost(enumeration = "EthAddressCase", tag = "11")]
@@ -1637,6 +1645,8 @@ pub struct EthSignMessageRequest {
pub keypath: ::prost::alloc::vec::Vec<u32>,
#[prost(bytes = "vec", tag = "3")]
pub msg: ::prost::alloc::vec::Vec<u8>,
+ /// If omitted, the signature uses the historical deterministic zero-contribution S2C fallback.
+ /// This differs from plain RFC6979 and does not provide anti-klepto protection.
#[prost(message, optional, tag = "4")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
/// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
@@ -1661,6 +1671,7 @@ pub struct EthSignTypedMessageRequest {
pub types: ::prost::alloc::vec::Vec<eth_sign_typed_message_request::StructType>,
#[prost(string, tag = "4")]
pub primary_type: ::prost::alloc::string::String,
+ /// If omitted, plain RFC6979 signing is used without anti-klepto protection.
#[prost(message, optional, tag = "5")]
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
}
### src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
@@ -112,7 +112,9 @@ pub async fn process(
super::antiklepto_get_host_nonce(signer_commitment).await?
}
- // Return signature directly without the anti-klepto protocol, for backwards compatibility.
+ // Return the signature directly without the anti-klepto protocol for backwards
+ // compatibility. Preserve the historical zero-contribution S2C signature; this differs
+ // from plain RFC6979 and does not provide anti-klepto protection.
None => [0; 32],
};
### src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -1314,7 +1314,9 @@ async fn _process(
.try_into()
.or(Err(Error::InvalidInput))?
}
- // Return signature directly without the anti-klepto protocol, for backwards compatibility.
+ // Return the signature directly without the anti-klepto protocol for backwards
+ // compatibility. Preserve the historical zero-contribution S2C signature; this
+ // differs from plain RFC6979 and does not provide anti-klepto protection.
None => [0; 32],
};
### src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -614,7 +614,9 @@ pub async fn _process(
super::antiklepto_get_host_nonce(signer_commitment).await?
}
- // Return signature directly without the anti-klepto protocol, for backwards compatibility.
+ // Return the signature directly without the anti-klepto protocol for backwards
+ // compatibility. Preserve the historical zero-contribution S2C signature; this differs
+ // from plain RFC6979 and does not provide anti-klepto protection.
None => [0; 32],
};
let sign_result = crate::secp256k1::secp256k1_sign(
### src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
@@ -71,7 +71,9 @@ pub async fn process(
super::antiklepto_get_host_nonce(signer_commitment).await?
}
- // Return signature directly without the anti-klepto protocol, for backwards compatibility.
+ // Return the signature directly without the anti-klepto protocol for backwards
+ // compatibility. Preserve the historical zero-contribution S2C signature; this differs
+ // from plain RFC6979 and does not provide anti-klepto protection.
None => [0; 32],
};
Why this scored 23/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.