factorysetup: speed up certificate setup
What changed, and why it matters
This commit is a performance optimization in the BitBox02 factory setup process. It changes how the device picks which trusted root public key to use when verifying an attestation certificate. Previously, the device tried verifying the signature against every known root key until one worked, which could take up to about 2.4 seconds. Now it first looks up the correct root key using a short identifier (a hash of the public key) and then verifies the signature only once, taking less than 0.1 seconds. The security properties remain the same: the signature is still checked, just faster.
No security action required. Treat as a normal performance improvement. If desired, verify that the identifier lookup cannot be manipulated by malformed input (the identifier is read from a fixed offset and fixed length, and the loop bounds are derived from a compile-time array size).
Security signals we found
No removal of cryptographic verification: rust_secp256k1_verify is still performed after key selection.
No change to accepted inputs: any certificate accepted before is still accepted, and any rejected before is still rejected.
Identifier comparison uses MEMEQ over the full 32-byte SHA-256 digest, so collision resistance is standard.
The optimization is purely in the factory setup code path, not in runtime wallet operations.
No buffer size changes or new memory allocations; only control flow is reordered.
Evidence from the diff
In src/factorysetup.c, the certificate installation path is refactored from a trial-and-verify loop over all root public keys to an identifier-based key selection followed by a single secp256k1 signature verification. The input already contains a root_pubkey_identifier. The new code computes the SHA-256 of each candidate root public key, compares it to the supplied identifier, selects the matching key, and then calls rust_secp256k1_verify once. The prior behavior verified the signature against each root key until a match was found. The change is functionally equivalent for valid certificates and rejects invalid ones in the same way, but reduces worst-case runtime from ~2.4 s to <0.1 s.
Changed components
src/factorysetup.cDevice attestation certificate setup flowInspect captured patch +10 / −8
### src/factorysetup.c
@@ -953,21 +953,23 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_
const size_t certificate_size = 64;
const uint8_t* root_pubkey_identifier = input + 1 + pubkey_size + certificate_size;
- // Verify sig
+ // Select the signing key first so that only one signature verification is needed.
uint8_t msg32[32] = {0};
_attestation_sighash(attestation_device_pubkey, msg32);
- bool matches_a_root_pubkey = false;
+ const uint8_t* root_pubkey = NULL;
for (size_t pubkey_idx = 0; pubkey_idx < sizeof(_root_pubkey_bytes) / ROOT_PUBKEY_SIZE;
pubkey_idx++) {
- if (rust_secp256k1_verify(
- rust_util_bytes(certificate, certificate_size),
- rust_util_bytes(msg32, sizeof(msg32)),
- rust_util_bytes(_root_pubkey_bytes[pubkey_idx], ROOT_PUBKEY_SIZE))) {
- matches_a_root_pubkey = true;
+ uint8_t candidate_identifier[32];
+ rust_sha256(_root_pubkey_bytes[pubkey_idx], ROOT_PUBKEY_SIZE, candidate_identifier);
+ if (MEMEQ(root_pubkey_identifier, candidate_identifier, sizeof(candidate_identifier))) {
+ root_pubkey = _root_pubkey_bytes[pubkey_idx];
break;
}
}
- if (!matches_a_root_pubkey) {
+ if (root_pubkey == NULL || !rust_secp256k1_verify(
+ rust_util_bytes(certificate, certificate_size),
+ rust_util_bytes(msg32, sizeof(msg32)),
+ rust_util_bytes(root_pubkey, ROOT_PUBKEY_SIZE))) {
screen_print_debug("setting certificate\nfailed: sig", 0);
result = ERR_INVALID_INPUT;
break;Why this scored 18/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.