optiga: reduce number of events on init
What changed, and why it matters
This commit is a small internal optimization in the BitBox02 hardware wallet firmware. When a user creates or restores a password, the device previously performed two separate operations on the secure chip (Optiga/ATECC): one to initialize the new password and another to stretch it. The commit combines these into a single step, reducing the number of secure chip 'events' (internal counter operations). There is no direct security vulnerability being fixed; it is a performance and resource-usage improvement.
No security action required. Treat as a normal code-quality/optimization change. Reviewers may want to confirm that the stretched output is correctly zeroized in all Rust paths and that error handling in init_new_password properly aborts the workflow if stretching fails.
Security signals we found
Reduces secure chip event counter usage during password initialization workflows
No change to cryptographic algorithms, key derivation logic, or password stretching formulas
Refactoring only: moves existing stretch computation into init_new_password and reuses result
Test assertions updated to reflect fewer secure chip operations
Evidence from the diff
The change refactors securechip_init_new_password() so that it returns the stretched password output directly, instead of requiring a subsequent call to securechip_stretch_password(). The Rust keystore code now calls init_new_password() once and uses its returned secret, rather than calling init_new_password() followed by stretch_password(). The C implementations for ATECC and Optiga were updated to compute and return the stretched value inside init_new_password(). Test expectations for secure chip event counters were reduced accordingly (e.g., change_password from 14 to 10, restore from 9 to 5, set_password from 10 to 6, encrypt_and_store_seed from 8 to 4). A helper _v1_combine() was extracted in optiga.c to avoid code duplication between init and stretch paths.
Changed components
src/optiga/optiga.csrc/atecc/atecc.csrc/securechip/securechip.csrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02/src/securechip.rstest/hardware-fakes/src/fake_securechip.cInspect captured patch +69 / −45
diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c
index 6942d47..905d6d0 100644
--- a/src/atecc/atecc.c
+++ b/src/atecc/atecc.c
@@ -575,7 +575,8 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
int atecc_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo)
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
(void)password;
if (password_stretch_algo != MEMORY_PASSWORD_STRETCH_ALGO_V0) {
@@ -584,7 +585,7 @@ int atecc_init_new_password(
if (!atecc_reset_keys()) {
return SC_ATECC_ERR_RESET_KEYS;
}
- return 0;
+ return atecc_stretch_password(password, password_stretch_algo, stretched_out);
}
int atecc_stretch_password(
diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h
index 8b4e0f8..93185e6 100644
--- a/src/atecc/atecc.h
+++ b/src/atecc/atecc.h
@@ -17,7 +17,8 @@ USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs);
USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
USE_RESULT int atecc_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo);
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
USE_RESULT int atecc_stretch_password(
const char* password,
memory_password_stretch_algo_t password_stretch_algo,
diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c
index 52dcfc4..3836ec2 100644
--- a/src/optiga/optiga.c
+++ b/src/optiga/optiga.c
@@ -1320,9 +1320,31 @@ static int _v1_get_auth_password(
return 0;
}
+static int _v1_combine(
+ const char* password,
+ const uint8_t* auth_password,
+ const uint8_t* password_secret,
+ uint8_t* stretched_out)
+{
+ rust_hmac_sha256(password_secret, 32, auth_password, 32, stretched_out);
+
+ uint8_t password_salted_hashed[32] = {0};
+ if (!salt_hash_data(
+ (const uint8_t*)password,
+ strlen(password),
+ "optiga_password_stretch_out",
+ password_salted_hashed)) {
+ return SC_ERR_SALT;
+ }
+ rust_hmac_sha256(
+ password_salted_hashed, sizeof(password_salted_hashed), stretched_out, 32, stretched_out);
+ return 0;
+}
+
int optiga_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo)
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
if (password_stretch_algo != MEMORY_PASSWORD_STRETCH_ALGO_V1) {
// New passwords must use the latest algo.
@@ -1383,7 +1405,7 @@ int optiga_init_new_password(
return res;
}
- return 0;
+ return _v1_combine(password, auth_password, password_secret, stretched_out);
}
bool optiga_reset_keys(void)
@@ -1394,7 +1416,8 @@ bool optiga_reset_keys(void)
// for the purpose of resetting the keys.
// We reset using V1, the latest algorithm. It covers resetting everything from V0 as well.
- return optiga_init_new_password("", MEMORY_PASSWORD_STRETCH_ALGO_V1) == 0;
+ uint8_t stretched[32];
+ return optiga_init_new_password("", MEMORY_PASSWORD_STRETCH_ALGO_V1, stretched) == 0;
}
static int _optiga_verify_password_v0(const char* password, uint8_t* password_secret_out)
@@ -1769,19 +1792,7 @@ static int _stretch_password_v1(const char* password, uint8_t* stretched_out)
return res;
}
- rust_hmac_sha256(password_secret, sizeof(password_secret), stretched_out, 32, stretched_out);
-
- uint8_t password_salted_hashed[32] = {0};
- if (!salt_hash_data(
- (const uint8_t*)password,
- strlen(password),
- "optiga_password_stretch_out",
- password_salted_hashed)) {
- return SC_ERR_SALT;
- }
- rust_hmac_sha256(
- password_salted_hashed, sizeof(password_salted_hashed), stretched_out, 32, stretched_out);
- return 0;
+ return _v1_combine(password, auth_password, password_secret, stretched_out);
}
int optiga_stretch_password(
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 2d9d7ff..54a86dd 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -69,7 +69,8 @@ USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs);
USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out);
USE_RESULT int optiga_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo);
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
USE_RESULT int optiga_stretch_password(
const char* password,
memory_password_stretch_algo_t password_stretch_algo,
diff --git a/src/rust/bitbox02-rust/src/hal.rs b/src/rust/bitbox02-rust/src/hal.rs
index f3ee968..18f9f8b 100644
--- a/src/rust/bitbox02-rust/src/hal.rs
+++ b/src/rust/bitbox02-rust/src/hal.rs
@@ -31,7 +31,7 @@ pub trait SecureChip {
&mut self,
password: &str,
password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
- ) -> Result<(), bitbox02::securechip::Error>;
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error>;
fn stretch_password(
&mut self,
password: &str,
@@ -155,7 +155,7 @@ impl SecureChip for BitBox02SecureChip {
&mut self,
password: &str,
password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
- ) -> Result<(), bitbox02::securechip::Error> {
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
bitbox02::securechip::init_new_password(password, password_stretch_algo)
}
@@ -509,11 +509,18 @@ pub mod testing {
impl super::SecureChip for TestingSecureChip {
fn init_new_password(
&mut self,
- _password: &str,
+ password: &str,
_password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
- ) -> Result<(), bitbox02::securechip::Error> {
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
self.event_counter += 3;
- Ok(())
+
+ use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
+ let mut engine = HmacEngine::<sha256::Hash>::new(b"unit-test");
+ engine.input(password.as_bytes());
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ Ok(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array().to_vec(),
+ ))
}
fn stretch_password(
diff --git a/src/rust/bitbox02-rust/src/hww/api/change_password.rs b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
index 4e3b584..09045a6 100644
--- a/src/rust/bitbox02-rust/src/hww/api/change_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
@@ -104,7 +104,7 @@ mod tests {
// We expect 14 secure chip events. This is intentionally brittle to catch
// unintended changes in the number of securechip operations during password change.
// If this fails after a legitimate change, update the expected count.
- assert_eq!(hal.securechip.get_event_counter(), 14);
+ assert_eq!(hal.securechip.get_event_counter(), 10);
// check that the old password is no longer valid
keystore::lock();
diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs
index 43c5f99..30535d1 100644
--- a/src/rust/bitbox02-rust/src/hww/api/restore.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs
@@ -190,7 +190,7 @@ mod tests {
)),
Ok(Response::Success(pb::Success {}))
);
- assert_eq!(mock_hal.securechip.get_event_counter(), 9);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 5);
assert!(!crate::keystore::is_locked());
assert!(mock_hal.memory.is_initialized());
diff --git a/src/rust/bitbox02-rust/src/hww/api/set_password.rs b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
index 09ace53..5b127dd 100644
--- a/src/rust/bitbox02-rust/src/hww/api/set_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
@@ -69,7 +69,7 @@ mod tests {
)),
Ok(Response::Success(pb::Success {}))
);
- assert_eq!(mock_hal.securechip.get_event_counter(), 10);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 6);
assert!(!keystore::is_locked());
assert!(keystore::copy_seed(&mut mock_hal).unwrap().len() == 32);
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 6083005..2637076 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -242,12 +242,9 @@ fn encrypt_and_store_seed_internal(
let password_stretch_algo = default_password_stretch_algo(hal)?;
- hal.securechip()
- .init_new_password(password, password_stretch_algo)?;
-
let secret = hal
.securechip()
- .stretch_password(password, password_stretch_algo)?;
+ .init_new_password(password, password_stretch_algo)?;
let iv_rand = hal.random().random_32_bytes();
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
@@ -1785,7 +1782,7 @@ mod tests {
mock_hal.securechip.event_counter_reset();
assert!(encrypt_and_store_seed(&mut mock_hal, seed, "foo").is_ok());
- assert_eq!(mock_hal.securechip.get_event_counter(), 8);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 4);
assert!(is_locked());
diff --git a/src/rust/bitbox02/src/securechip.rs b/src/rust/bitbox02/src/securechip.rs
index f44815a..a4f0ed5 100644
--- a/src/rust/bitbox02/src/securechip.rs
+++ b/src/rust/bitbox02/src/securechip.rs
@@ -86,14 +86,19 @@ pub fn reset_keys() -> Result<(), ()> {
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<(), Error> {
+) -> Result<Zeroizing<Vec<u8>>, Error> {
let password = crate::util::str_to_cstr_vec_zeroizing(password)
.map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
+ let mut stretched = Zeroizing::new(vec![0u8; 32]);
let status = unsafe {
- bitbox02_sys::securechip_init_new_password(password.as_ptr().cast(), password_stretch_algo)
+ bitbox02_sys::securechip_init_new_password(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
};
if status == 0 {
- Ok(())
+ Ok(stretched)
} else {
Err(Error::from_status(status))
}
diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c
index abfd094..9a5d2dc 100644
--- a/src/securechip/securechip.c
+++ b/src/securechip/securechip.c
@@ -13,7 +13,8 @@ typedef struct {
int (*kdf)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out);
int (*init_new_password)(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo);
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
int (*stretch_password)(
const char* password,
memory_password_stretch_algo_t password_stretch_algo,
@@ -100,10 +101,11 @@ int securechip_kdf(const uint8_t* msg, size_t msg_len, uint8_t* mac_out)
int securechip_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo)
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
ABORT_IF_NULL(init_new_password);
- return _fns.init_new_password(password, password_stretch_algo);
+ return _fns.init_new_password(password, password_stretch_algo, stretched_out);
}
int securechip_stretch_password(
diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h
index 533b8db..141614f 100644
--- a/src/securechip/securechip.h
+++ b/src/securechip/securechip.h
@@ -98,7 +98,8 @@ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
*/
USE_RESULT int securechip_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo);
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
/**
* Stretch password using secrets in the secure chip.
diff --git a/test/hardware-fakes/src/fake_securechip.c b/test/hardware-fakes/src/fake_securechip.c
index 7f8f0dc..e24272e 100644
--- a/test/hardware-fakes/src/fake_securechip.c
+++ b/test/hardware-fakes/src/fake_securechip.c
@@ -21,16 +21,14 @@ int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
int securechip_init_new_password(
const char* password,
- memory_password_stretch_algo_t password_stretch_algo)
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
if (password_stretch_algo != MEMORY_PASSWORD_STRETCH_ALGO_V1) {
// New passwords must use the latest algo.
return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
}
-
- (void)password;
- (void)password_stretch_algo;
- return 0;
+ return securechip_stretch_password(password, password_stretch_algo, stretched_out);
}
int securechip_stretch_password(
const char* password,
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.