Skip to content

Commit c3aad65

Browse files
benma's agentbenma
authored andcommitted
port salt.c to Rust
test_salt.c unit tests is replicated in Rust. The C function body calls the Rust function so we can also verify the C unit tests still pass (to be removed in the next commit).
1 parent 00cf523 commit c3aad65

File tree

3 files changed

+135
-19
lines changed

3 files changed

+135
-19
lines changed

src/rust/bitbox02-rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub mod hal;
3636
pub mod hash;
3737
pub mod hww;
3838
pub mod keystore;
39+
pub mod salt;
3940
pub mod secp256k1;
4041
#[cfg(feature = "app-u2f")]
4142
mod u2f;

src/rust/bitbox02-rust/src/salt.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use alloc::vec::Vec;
16+
use core::ffi::c_char;
17+
18+
use bitbox02::memory;
19+
use sha2::Digest;
20+
use util::bytes::{Bytes, BytesMut};
21+
use zeroize::Zeroizing;
22+
23+
/// Creates `SHA256(salt_root || purpose || data)`, where `salt_root` is a persisted value that
24+
/// remains unchanged until the device is reset. The `purpose` string namespaces individual uses of
25+
/// the salt, and the provided `data` slice is hashed alongside it.
26+
///
27+
/// Returns `Err(())` if the salt root cannot be retrieved from persistent storage.
28+
pub fn hash_data(data: &[u8], purpose: &str) -> Result<Zeroizing<Vec<u8>>, ()> {
29+
let salt_root = memory::get_salt_root()?;
30+
31+
let mut hasher = sha2::Sha256::new();
32+
hasher.update(salt_root.as_slice());
33+
hasher.update(purpose.as_bytes());
34+
hasher.update(data);
35+
36+
Ok(Zeroizing::new(hasher.finalize().to_vec()))
37+
}
38+
39+
/// # Safety
40+
///
41+
/// `purpose` must be a valid, null-terminated UTF-8 string pointer.
42+
#[unsafe(no_mangle)]
43+
pub unsafe extern "C" fn rust_salt_hash_data(
44+
data: Bytes,
45+
purpose: *const c_char,
46+
mut hash_out: BytesMut,
47+
) -> bool {
48+
let purpose_str = match unsafe { bitbox02::util::str_from_null_terminated_ptr(purpose) } {
49+
Ok(purpose) => purpose,
50+
Err(()) => return false,
51+
};
52+
match hash_data(data.as_ref(), purpose_str) {
53+
Ok(hash) => {
54+
hash_out.as_mut()[..32].copy_from_slice(&hash);
55+
true
56+
}
57+
Err(()) => false,
58+
}
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
use bitbox02::testing::mock_memory;
65+
use core::convert::TryInto;
66+
use core::ptr;
67+
use hex_lit::hex;
68+
69+
const MOCK_SALT_ROOT: [u8; 32] =
70+
hex!("0000000000000000111111111111111122222222222222223333333333333333");
71+
72+
#[test]
73+
fn test_hash_data() {
74+
mock_memory();
75+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
76+
77+
let data = hex!("001122334455667788");
78+
let expected = hex!("62db8dcd47ddf8e81809c377ed96643855d3052bb73237100ca81f0f5a7611e6");
79+
80+
let hash = hash_data(&data, "test purpose").unwrap();
81+
assert_eq!(hash.as_slice(), &expected);
82+
}
83+
84+
#[test]
85+
fn test_hash_data_empty_inputs() {
86+
mock_memory();
87+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
88+
89+
let expected = hex!("2dbb05dd73d94edba6946611aaca367f76c809e96f20499ad674e596050f9833");
90+
91+
let hash = hash_data(&[], "").unwrap();
92+
assert_eq!(hash.as_slice(), &expected);
93+
}
94+
95+
#[test]
96+
fn test_rust_salt_hash_data() {
97+
mock_memory();
98+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
99+
100+
let data = hex!("001122334455667788");
101+
let expected = hex!("62db8dcd47ddf8e81809c377ed96643855d3052bb73237100ca81f0f5a7611e6");
102+
103+
let mut hash_out = [0u8; 32];
104+
let purpose = c"test purpose";
105+
assert!(unsafe {
106+
rust_salt_hash_data(
107+
util::bytes::rust_util_bytes(data.as_ptr(), data.len()),
108+
purpose.as_ptr(),
109+
util::bytes::rust_util_bytes_mut(hash_out.as_mut_ptr(), hash_out.len()),
110+
)
111+
});
112+
assert_eq!(hash_out, expected);
113+
}
114+
115+
#[test]
116+
fn test_rust_salt_hash_data_empty_inputs() {
117+
mock_memory();
118+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
119+
120+
let expected = hex!("2dbb05dd73d94edba6946611aaca367f76c809e96f20499ad674e596050f9833");
121+
let mut hash_out = [0u8; 32];
122+
assert!(unsafe {
123+
rust_salt_hash_data(
124+
util::bytes::rust_util_bytes(ptr::null(), 0),
125+
c"".as_ptr(),
126+
util::bytes::rust_util_bytes_mut(hash_out.as_mut_ptr(), hash_out.len()),
127+
)
128+
});
129+
assert_eq!(hash_out, expected);
130+
}
131+
}

src/salt.c

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,9 @@
2121

2222
bool salt_hash_data(const uint8_t* data, size_t data_len, const char* purpose, uint8_t* hash_out)
2323
{
24-
if (data_len > 0 && data == NULL) {
24+
if ((data_len > 0 && data == NULL) || purpose == NULL || hash_out == NULL) {
2525
return false;
2626
}
27-
if (!purpose || !hash_out) {
28-
return false;
29-
}
30-
31-
uint8_t salt_root[32];
32-
UTIL_CLEANUP_32(salt_root);
33-
if (!memory_get_salt_root(salt_root)) {
34-
return false;
35-
}
36-
37-
void* ctx = rust_sha256_new();
38-
rust_sha256_update(ctx, salt_root, sizeof(salt_root));
39-
rust_sha256_update(ctx, purpose, strlen(purpose));
40-
if (data != NULL) {
41-
rust_sha256_update(ctx, data, data_len);
42-
}
43-
rust_sha256_finish(&ctx, hash_out);
44-
return true;
27+
return rust_salt_hash_data(
28+
rust_util_bytes(data, data_len), purpose, rust_util_bytes_mut(hash_out, 32));
4529
}

0 commit comments

Comments
 (0)