use std::sync::atomic::{AtomicUsize, Ordering};
use rand::RngCore;
use zeroize::Zeroize;
const ARRAY_SIZE: usize = 4096;
const ARRAY_COUNT: usize = 128;
const NUM_SHARES: usize = 4;
const USIZES_PER_SHARE: usize = 32 / std::mem::size_of::<usize>();
const SHARE_ENTRIES: usize = NUM_SHARES * USIZES_PER_SHARE;
const LANE_COUNT: usize = 16;
const LANE_WIDTH: usize = ARRAY_SIZE / LANE_COUNT;
static VAULTS: [[AtomicUsize; ARRAY_SIZE]; ARRAY_COUNT] = {
const ZERO: AtomicUsize = AtomicUsize::new(0);
const ROW: [AtomicUsize; ARRAY_SIZE] = [ZERO; ARRAY_SIZE];
[ROW; ARRAY_COUNT]
};
fn ensure_vaults() {
if VAULTS[0][0].load(Ordering::Relaxed) == 0 {
let mut rng = rand::rngs::OsRng;
for row in VAULTS.iter() {
for slot in row.iter() {
let mut val = rng.next_u64() as usize;
if val == 0 { val = 1; }
let _ = slot.compare_exchange(0, val, Ordering::SeqCst, Ordering::Relaxed);
}
}
}
}
#[derive(Clone, Copy, PartialEq)]
#[cfg_attr(test, derive(Debug))]
struct VaultPos {
array: usize,
slot: usize,
}
#[inline]
fn mix_iterations(instance_addr: usize) -> usize {
let base = VAULTS.as_ptr() as usize;
let h = (instance_addr ^ base).wrapping_mul(instance_addr | 1);
4096 + ((h >> 7) & 4095)
}
#[inline]
fn addr_mix(mut h: u64, m1: u64, m2: u64, iterations: usize) -> u64 {
for _ in 0..iterations {
h ^= h >> 33;
h = h.wrapping_mul(m1);
h ^= h >> 29;
h = h.wrapping_mul(m2);
h ^= h >> 31;
}
h
}
#[inline]
fn lane_slot(raw: usize, lane: usize) -> usize {
(lane << LANE_WIDTH.trailing_zeros()) | (raw & (LANE_WIDTH - 1))
}
fn config_positions(instance_addr: usize, lane: usize) -> (VaultPos, VaultPos, VaultPos) {
let base = VAULTS.as_ptr() as usize;
let m1 = (instance_addr as u64) | 1;
let m2 = (base as u64) | 1;
let iters = mix_iterations(instance_addr);
let mut h = addr_mix((instance_addr as u64) ^ (base as u64).rotate_left(19), m1, m2, iters);
let mut positions = [VaultPos { array: 0, slot: 0 }; 3];
for i in 0..3 {
loop {
let candidate = VaultPos {
array: ((h >> 32) as usize) & (ARRAY_COUNT - 1),
slot: lane_slot(h as usize, lane),
};
if !positions[..i].contains(&candidate) {
positions[i] = candidate;
h = addr_mix(h, m1, m2, iters);
break;
}
h = addr_mix(h, m1, m2, iters);
}
}
(positions[0], positions[1], positions[2])
}
fn share_positions(instance_addr: usize, lane: usize) -> [VaultPos; SHARE_ENTRIES] {
let (seed_pos, mul1_pos, mul2_pos) = config_positions(instance_addr, lane);
let seed = VAULTS[seed_pos.array][seed_pos.slot].load(Ordering::Relaxed) as u64;
let mul1 = VAULTS[mul1_pos.array][mul1_pos.slot].load(Ordering::Relaxed) as u64;
let mul2 = VAULTS[mul2_pos.array][mul2_pos.slot].load(Ordering::Relaxed) as u64;
let mut h = seed ^ (instance_addr as u64).rotate_left(23);
let mut positions = [VaultPos { array: 0, slot: 0 }; SHARE_ENTRIES];
let config = [seed_pos, mul1_pos, mul2_pos];
for i in 0..SHARE_ENTRIES {
loop {
h ^= h >> 17;
h = h.wrapping_mul(mul1 | 1);
h ^= h >> 13;
h = h.wrapping_mul(mul2 | 1);
h ^= h >> 16;
let candidate = VaultPos {
array: ((h >> 32) as usize) & (ARRAY_COUNT - 1),
slot: lane_slot(h as usize, lane),
};
if !config.contains(&candidate) && !positions[..i].contains(&candidate) {
positions[i] = candidate;
break;
}
}
}
positions
}
fn write_decoys(protected: &[VaultPos]) {
let mut rng = rand::rngs::OsRng;
for (array_idx, row) in VAULTS.iter().enumerate() {
for _ in 0..SHARE_ENTRIES {
let mut slot = (rng.next_u64() as usize) & (ARRAY_SIZE - 1);
while protected.iter().any(|p| p.array == array_idx && p.slot == slot) {
slot = (rng.next_u64() as usize) & (ARRAY_SIZE - 1);
}
let mut val = rng.next_u64() as usize;
if val == 0 { val = 1; }
row[slot].store(val, Ordering::Release);
}
}
}
pub struct GuardedKey {
active: AtomicUsize,
}
impl GuardedKey {
pub const fn empty() -> Self {
Self { active: AtomicUsize::new(0) }
}
#[inline]
fn instance_addr(&self) -> usize {
&self.active as *const _ as usize
}
#[inline]
fn lane_of(marker: usize) -> usize {
marker & (LANE_COUNT - 1)
}
#[inline]
fn lane(&self) -> usize {
Self::lane_of(self.active.load(Ordering::Acquire))
}
fn pick_marker(&self, others: &[&GuardedKey], rng: &mut rand::rngs::OsRng) -> usize {
let mut taken = [false; LANE_COUNT];
for &key in others {
if std::ptr::eq(key, self) || !key.has_key() { continue; }
taken[key.lane()] = true;
}
loop {
let mut marker = rng.next_u64() as usize;
if marker == 0 { marker = 1; }
if !taken[Self::lane_of(marker)] {
return marker;
}
}
}
fn collect_other_protected(&self, others: &[&GuardedKey]) -> ([VaultPos; (3 + SHARE_ENTRIES) * LANE_COUNT], usize) {
let mut buf = [VaultPos { array: 0, slot: 0 }; (3 + SHARE_ENTRIES) * LANE_COUNT];
let mut n = 0;
for &key in others {
if std::ptr::eq(key, self) || !key.has_key() { continue; }
if n + 3 + SHARE_ENTRIES > buf.len() { break; }
let addr = key.instance_addr();
let lane = key.lane();
let (s, m1, m2) = config_positions(addr, lane);
buf[n] = s; n += 1;
buf[n] = m1; n += 1;
buf[n] = m2; n += 1;
for &pos in share_positions(addr, lane).iter() {
buf[n] = pos;
n += 1;
}
}
(buf, n)
}
#[inline]
pub fn store_from_keys(&self, keys: &nostr_sdk::Keys, others: &[&GuardedKey]) {
let mut sk_bytes = keys.secret_key().secret_bytes();
self.set(sk_bytes, others);
sk_bytes.zeroize();
}
pub fn set(&self, mut key: [u8; 32], others: &[&GuardedKey]) {
let mut rng = rand::rngs::OsRng;
ensure_vaults();
let marker = self.pick_marker(others, &mut rng);
let lane = Self::lane_of(marker);
let old_marker = self.active.load(Ordering::Acquire);
if old_marker != 0 {
let old_lane = Self::lane_of(old_marker);
if old_lane != lane {
for pos in share_positions(self.instance_addr(), old_lane).iter() {
let mut val = rng.next_u64() as usize;
if val == 0 { val = 1; }
VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
}
}
}
let (protected, pcount) = self.collect_other_protected(others);
write_decoys(&protected[..pcount]);
let (_, mul1_pos, mul2_pos) = config_positions(self.instance_addr(), lane);
let v = VAULTS[mul1_pos.array][mul1_pos.slot].load(Ordering::Relaxed);
VAULTS[mul1_pos.array][mul1_pos.slot].store(v | 1, Ordering::Relaxed);
let v = VAULTS[mul2_pos.array][mul2_pos.slot].load(Ordering::Relaxed);
VAULTS[mul2_pos.array][mul2_pos.slot].store(v | 1, Ordering::Relaxed);
let mut shares = [[0u8; 32]; NUM_SHARES];
for share in shares.iter_mut().take(NUM_SHARES - 1) {
rng.fill_bytes(share);
}
shares[NUM_SHARES - 1] = key;
for i in 0..NUM_SHARES - 1 {
for j in 0..32 {
shares[NUM_SHARES - 1][j] ^= shares[i][j];
}
}
key.zeroize();
let positions = share_positions(self.instance_addr(), lane);
for (share_idx, share) in shares.iter().enumerate() {
for u_idx in 0..USIZES_PER_SHARE {
let byte_off = u_idx * std::mem::size_of::<usize>();
let val = usize::from_ne_bytes(
share[byte_off..byte_off + std::mem::size_of::<usize>()]
.try_into().unwrap()
);
let pos = positions[share_idx * USIZES_PER_SHARE + u_idx];
VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
}
}
for share in shares.iter_mut() { share.zeroize(); }
self.active.store(marker, Ordering::Release);
}
pub fn get(&self) -> Option<[u8; 32]> {
let marker = self.active.load(Ordering::Acquire);
if marker == 0 {
return None;
}
let positions = share_positions(self.instance_addr(), Self::lane_of(marker));
let mut key = [0u8; 32];
for share_idx in 0..NUM_SHARES {
let mut share = [0u8; 32];
for u_idx in 0..USIZES_PER_SHARE {
let pos = positions[share_idx * USIZES_PER_SHARE + u_idx];
let val = VAULTS[pos.array][pos.slot].load(Ordering::Acquire);
let byte_off = u_idx * std::mem::size_of::<usize>();
share[byte_off..byte_off + std::mem::size_of::<usize>()]
.copy_from_slice(&val.to_ne_bytes());
}
for (a, b) in key.iter_mut().zip(share.iter()) {
*a ^= *b;
}
}
Some(key)
}
pub fn clear(&self, others: &[&GuardedKey]) {
let old_marker = self.active.swap(0, Ordering::SeqCst);
if old_marker != 0 {
let mut rng = rand::rngs::OsRng;
let positions = share_positions(self.instance_addr(), Self::lane_of(old_marker));
for pos in &positions {
let mut val = rng.next_u64() as usize;
if val == 0 { val = 1; }
VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
}
let (protected, pcount) = self.collect_other_protected(others);
write_decoys(&protected[..pcount]);
}
}
pub fn has_key(&self) -> bool {
self.active.load(Ordering::Acquire) != 0
}
pub fn to_keys(&self) -> Option<nostr_sdk::Keys> {
let mut bytes = self.get()?;
let result = nostr_sdk::SecretKey::from_slice(&bytes);
bytes.zeroize();
Some(nostr_sdk::Keys::new(result.ok()?))
}
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_KEY_A: GuardedKey = GuardedKey::empty();
static TEST_KEY_B: GuardedKey = GuardedKey::empty();
fn reset() {
TEST_KEY_A.active.store(0, Ordering::SeqCst);
TEST_KEY_B.active.store(0, Ordering::SeqCst);
ensure_vaults();
}
fn others_for_a() -> [&'static GuardedKey; 1] {
[&TEST_KEY_B]
}
fn others_for_b() -> [&'static GuardedKey; 1] {
[&TEST_KEY_A]
}
fn test_key(seed: u8) -> [u8; 32] {
let mut k = [0u8; 32];
for (i, b) in k.iter_mut().enumerate() {
*b = seed.wrapping_add(i as u8).wrapping_mul(37).wrapping_add(7);
}
k
}
#[test]
fn set_get_roundtrip() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let key = test_key(42);
TEST_KEY_A.set(key, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(key));
}
#[test]
fn set_get_1000_iterations() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
for i in 0..1000u16 {
reset();
let key = test_key((i ^ (i >> 3)) as u8);
TEST_KEY_A.set(key, &others_for_a());
assert_eq!(
TEST_KEY_A.get(), Some(key),
"Roundtrip failed at iteration {i}"
);
}
}
#[test]
fn empty_returns_none() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
assert_eq!(TEST_KEY_A.get(), None);
assert_eq!(TEST_KEY_B.get(), None);
}
#[test]
fn has_key_lifecycle() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
assert!(!TEST_KEY_A.has_key());
TEST_KEY_A.set(test_key(1), &others_for_a());
assert!(TEST_KEY_A.has_key());
TEST_KEY_A.clear(&others_for_a());
assert!(!TEST_KEY_A.has_key());
}
#[test]
fn clear_returns_none() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
TEST_KEY_A.set(test_key(99), &others_for_a());
assert!(TEST_KEY_A.get().is_some());
TEST_KEY_A.clear(&others_for_a());
assert_eq!(TEST_KEY_A.get(), None);
}
#[test]
fn set_overwrites_previous() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let a = test_key(10);
let b = test_key(20);
TEST_KEY_A.set(a, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(a));
TEST_KEY_A.set(b, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(b));
}
#[test]
fn clear_idempotent() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
TEST_KEY_A.clear(&others_for_a());
TEST_KEY_A.clear(&others_for_a());
assert_eq!(TEST_KEY_A.get(), None);
TEST_KEY_A.set(test_key(5), &others_for_a());
TEST_KEY_A.clear(&others_for_a());
TEST_KEY_A.clear(&others_for_a());
assert_eq!(TEST_KEY_A.get(), None);
}
#[test]
fn encryption_key_basic() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let key = test_key(0xEE);
TEST_KEY_B.set(key, &others_for_b());
assert_eq!(TEST_KEY_B.get(), Some(key));
TEST_KEY_B.clear(&others_for_b());
assert_eq!(TEST_KEY_B.get(), None);
}
#[test]
fn cross_key_set_then_set_500() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
let key_a = test_key(0xAA);
let key_b = test_key(0xBB);
for i in 0..500 {
reset();
TEST_KEY_A.set(key_a, &others_for_a());
TEST_KEY_B.set(key_b, &others_for_b());
assert_eq!(
TEST_KEY_A.get(), Some(key_a),
"TEST_KEY_A corrupted at iteration {i}"
);
assert_eq!(
TEST_KEY_B.get(), Some(key_b),
"TEST_KEY_B corrupted at iteration {i}"
);
}
}
#[test]
fn cross_key_reverse_order_500() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
let key_a = test_key(0xCC);
let key_b = test_key(0xDD);
for i in 0..500 {
reset();
TEST_KEY_B.set(key_b, &others_for_b());
TEST_KEY_A.set(key_a, &others_for_a());
assert_eq!(
TEST_KEY_B.get(), Some(key_b),
"TEST_KEY_B corrupted at iteration {i}"
);
assert_eq!(
TEST_KEY_A.get(), Some(key_a),
"TEST_KEY_A corrupted at iteration {i}"
);
}
}
#[test]
fn cross_key_clear_preserves_other_500() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
let key_a = test_key(0x11);
let key_b = test_key(0x22);
for i in 0..500 {
reset();
TEST_KEY_A.set(key_a, &others_for_a());
TEST_KEY_B.set(key_b, &others_for_b());
TEST_KEY_A.clear(&others_for_a());
assert_eq!(
TEST_KEY_B.get(), Some(key_b),
"KEY_B corrupted after KEY_A.clear() at iteration {i}"
);
reset();
TEST_KEY_A.set(key_a, &others_for_a());
TEST_KEY_B.set(key_b, &others_for_b());
TEST_KEY_B.clear(&others_for_b());
assert_eq!(
TEST_KEY_A.get(), Some(key_a),
"KEY_A corrupted after KEY_B.clear() at iteration {i}"
);
}
}
#[test]
fn cross_key_alternating_500() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
for i in 0..500u16 {
reset();
let ka = test_key(i as u8);
let kb = test_key(!(i as u8));
TEST_KEY_A.set(ka, &others_for_a());
TEST_KEY_B.set(kb, &others_for_b());
assert_eq!(TEST_KEY_A.get(), Some(ka), "KEY_A wrong at iter {i}");
assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong at iter {i}");
TEST_KEY_A.clear(&others_for_a());
assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong after KEY_A clear at iter {i}");
}
}
#[test]
fn stress_both_keys_1000() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
for i in 0..1000u32 {
reset();
let ka = test_key((i & 0xFF) as u8);
let kb = test_key(!((i & 0xFF) as u8));
if i % 2 == 0 {
TEST_KEY_A.set(ka, &others_for_a());
TEST_KEY_B.set(kb, &others_for_b());
} else {
TEST_KEY_B.set(kb, &others_for_b());
TEST_KEY_A.set(ka, &others_for_a());
}
assert_eq!(TEST_KEY_A.get(), Some(ka), "KEY_A wrong at iter {i}");
assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong at iter {i}");
}
}
#[test]
fn config_positions_all_unique() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for lane in 0..LANE_COUNT {
for addr in (0x1000..0x2000usize).step_by(8) {
let (a, b, c) = config_positions(addr, lane);
assert_ne!(a, b, "config collision a==b at addr {addr:#x} lane {lane}");
assert_ne!(a, c, "config collision a==c at addr {addr:#x} lane {lane}");
assert_ne!(b, c, "config collision b==c at addr {addr:#x} lane {lane}");
}
}
}
#[test]
fn share_positions_all_unique() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for lane in 0..LANE_COUNT {
for addr in (0x2000..0x2100usize).step_by(8) {
let positions = share_positions(addr, lane);
for i in 0..SHARE_ENTRIES {
for j in (i + 1)..SHARE_ENTRIES {
assert_ne!(
positions[i], positions[j],
"share collision [{i}]==[{j}] at addr {addr:#x} lane {lane}"
);
}
}
}
}
}
#[test]
fn share_positions_no_config_overlap() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for lane in 0..LANE_COUNT {
for addr in (0x3000..0x3100usize).step_by(8) {
let (s, m1, m2) = config_positions(addr, lane);
let config = [s, m1, m2];
let shares = share_positions(addr, lane);
for (i, pos) in shares.iter().enumerate() {
assert!(
!config.contains(pos),
"share[{i}] collides with config at addr {addr:#x} lane {lane}"
);
}
}
}
}
#[test]
fn distinct_lanes_are_disjoint() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for addr in (0x5000..0x5100usize).step_by(8) {
for la in 0..LANE_COUNT {
for lb in 0..LANE_COUNT {
if la == lb { continue; }
let (sa, m1a, m2a) = config_positions(addr, la);
let a_all: Vec<VaultPos> = [sa, m1a, m2a]
.into_iter()
.chain(share_positions(addr, la))
.collect();
let (sb, m1b, m2b) = config_positions(addr, lb);
let b_all: Vec<VaultPos> = [sb, m1b, m2b]
.into_iter()
.chain(share_positions(addr, lb))
.collect();
for p in &a_all {
assert!(
!b_all.contains(p),
"lane {la} and lane {lb} share {p:?} at addr {addr:#x}"
);
}
}
}
}
}
#[test]
fn positions_deterministic() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
let addr = TEST_KEY_A.instance_addr();
let cfg1 = config_positions(addr, 3);
let cfg2 = config_positions(addr, 3);
assert_eq!(cfg1, cfg2);
let sp1 = share_positions(addr, 3);
let sp2 = share_positions(addr, 3);
assert_eq!(sp1, sp2);
}
#[test]
fn all_positions_in_bounds() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for lane in 0..LANE_COUNT {
for addr in (0x4000..0x4200usize).step_by(8) {
let (a, b, c) = config_positions(addr, lane);
for p in [a, b, c] {
assert!(p.array < ARRAY_COUNT);
assert!(p.slot < ARRAY_SIZE);
}
for p in share_positions(addr, lane) {
assert!(p.array < ARRAY_COUNT);
assert!(p.slot < ARRAY_SIZE);
}
}
}
}
#[test]
fn mix_iterations_in_range() {
for addr in 0..10000usize {
let n = mix_iterations(addr);
assert!((4096..=8191).contains(&n), "mix_iterations({addr}) = {n}");
}
}
#[test]
fn addr_mix_zero_iterations_is_identity() {
let h: u64 = 0xDEADBEEFCAFEBABE;
assert_eq!(addr_mix(h, 123, 456, 0), h);
}
#[test]
fn addr_mix_varies_output() {
let a = addr_mix(1, 3, 5, 10);
let b = addr_mix(2, 3, 5, 10);
let c = addr_mix(1, 7, 5, 10);
let d = addr_mix(1, 3, 11, 10);
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
}
#[test]
fn ensure_vaults_all_nonzero() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
for (r, row) in VAULTS.iter().enumerate() {
for (s, slot) in row.iter().enumerate() {
assert_ne!(
slot.load(Ordering::Relaxed), 0,
"VAULTS[{r}][{s}] is zero after ensure_vaults"
);
}
}
}
#[test]
fn ensure_vaults_idempotent() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
let samples: Vec<_> = (0..20)
.map(|i| {
let r = i * 13 % ARRAY_COUNT;
let s = i * 397 % ARRAY_SIZE;
(r, s, VAULTS[r][s].load(Ordering::Relaxed))
})
.collect();
ensure_vaults();
for (r, s, val) in &samples {
assert_eq!(
VAULTS[*r][*s].load(Ordering::Relaxed), *val,
"ensure_vaults changed VAULTS[{r}][{s}]"
);
}
}
#[test]
fn write_decoys_respects_exclusions_500() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
let protected = [
VaultPos { array: 0, slot: 100 },
VaultPos { array: 0, slot: 200 },
VaultPos { array: 50, slot: 2000 },
VaultPos { array: 50, slot: 3000 },
VaultPos { array: 100, slot: 500 },
VaultPos { array: 127, slot: 4095 },
];
let before: Vec<usize> = protected.iter()
.map(|p| VAULTS[p.array][p.slot].load(Ordering::Relaxed))
.collect();
for _ in 0..500 {
write_decoys(&protected);
}
for (i, p) in protected.iter().enumerate() {
assert_eq!(
VAULTS[p.array][p.slot].load(Ordering::Relaxed),
before[i],
"Protected position ({}, {}) overwritten after 500 rounds",
p.array, p.slot
);
}
}
#[test]
fn write_decoys_empty_exclusion_works() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ensure_vaults();
write_decoys(&[]);
}
#[test]
fn zero_key_roundtrip() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let key = [0u8; 32];
TEST_KEY_A.set(key, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(key));
}
#[test]
fn max_key_roundtrip() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let key = [0xFFu8; 32];
TEST_KEY_A.set(key, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(key));
}
#[test]
fn to_keys_roundtrip() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let mut sk_bytes = [0u8; 32];
sk_bytes[31] = 1; TEST_KEY_A.set(sk_bytes, &others_for_a());
let keys = TEST_KEY_A.to_keys();
assert!(keys.is_some(), "to_keys returned None for valid key");
assert_eq!(keys.unwrap().secret_key().secret_bytes(), sk_bytes);
}
#[test]
fn to_keys_none_when_empty() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
assert!(TEST_KEY_A.to_keys().is_none());
}
#[test]
fn store_from_keys_roundtrip() {
let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
let keys = nostr_sdk::Keys::generate();
let expected = keys.secret_key().secret_bytes();
TEST_KEY_A.store_from_keys(&keys, &others_for_a());
assert_eq!(TEST_KEY_A.get(), Some(expected));
}
}