1use std::sync::atomic::{AtomicUsize, Ordering};
38use rand::RngCore;
39use zeroize::Zeroize;
40
41const ARRAY_SIZE: usize = 4096;
44const ARRAY_COUNT: usize = 128;
48const NUM_SHARES: usize = 4;
50const USIZES_PER_SHARE: usize = 32 / std::mem::size_of::<usize>();
52const SHARE_ENTRIES: usize = NUM_SHARES * USIZES_PER_SHARE;
54const LANE_COUNT: usize = 16;
61const LANE_WIDTH: usize = ARRAY_SIZE / LANE_COUNT;
63
64static VAULTS: [[AtomicUsize; ARRAY_SIZE]; ARRAY_COUNT] = {
67 const ZERO: AtomicUsize = AtomicUsize::new(0);
68 const ROW: [AtomicUsize; ARRAY_SIZE] = [ZERO; ARRAY_SIZE];
69 [ROW; ARRAY_COUNT]
70};
71
72fn ensure_vaults() {
75 if VAULTS[0][0].load(Ordering::Relaxed) == 0 {
76
77 let mut rng = rand::rngs::OsRng;
78 for row in VAULTS.iter() {
79 for slot in row.iter() {
80 let mut val = rng.next_u64() as usize;
81 if val == 0 { val = 1; }
82 let _ = slot.compare_exchange(0, val, Ordering::SeqCst, Ordering::Relaxed);
83 }
84 }
85 }
86}
87
88#[derive(Clone, Copy, PartialEq)]
90#[cfg_attr(test, derive(Debug))]
91struct VaultPos {
92 array: usize,
93 slot: usize,
94}
95
96#[inline]
100fn mix_iterations(instance_addr: usize) -> usize {
101 let base = VAULTS.as_ptr() as usize;
102 let h = (instance_addr ^ base).wrapping_mul(instance_addr | 1);
103 4096 + ((h >> 7) & 4095)
104}
105
106#[inline]
110fn addr_mix(mut h: u64, m1: u64, m2: u64, iterations: usize) -> u64 {
111 for _ in 0..iterations {
112 h ^= h >> 33;
113 h = h.wrapping_mul(m1);
114 h ^= h >> 29;
115 h = h.wrapping_mul(m2);
116 h ^= h >> 31;
117 }
118 h
119}
120
121#[inline]
126fn lane_slot(raw: usize, lane: usize) -> usize {
127 (lane << LANE_WIDTH.trailing_zeros()) | (raw & (LANE_WIDTH - 1))
128}
129
130fn config_positions(instance_addr: usize, lane: usize) -> (VaultPos, VaultPos, VaultPos) {
138 let base = VAULTS.as_ptr() as usize;
139 let m1 = (instance_addr as u64) | 1;
142 let m2 = (base as u64) | 1;
143 let iters = mix_iterations(instance_addr);
144 let mut h = addr_mix((instance_addr as u64) ^ (base as u64).rotate_left(19), m1, m2, iters);
145
146 let mut positions = [VaultPos { array: 0, slot: 0 }; 3];
147 for i in 0..3 {
148 loop {
149 let candidate = VaultPos {
150 array: ((h >> 32) as usize) & (ARRAY_COUNT - 1),
151 slot: lane_slot(h as usize, lane),
152 };
153 if !positions[..i].contains(&candidate) {
154 positions[i] = candidate;
155 h = addr_mix(h, m1, m2, iters);
156 break;
157 }
158 h = addr_mix(h, m1, m2, iters);
159 }
160 }
161
162 (positions[0], positions[1], positions[2])
163}
164
165fn share_positions(instance_addr: usize, lane: usize) -> [VaultPos; SHARE_ENTRIES] {
171 let (seed_pos, mul1_pos, mul2_pos) = config_positions(instance_addr, lane);
172 let seed = VAULTS[seed_pos.array][seed_pos.slot].load(Ordering::Relaxed) as u64;
173 let mul1 = VAULTS[mul1_pos.array][mul1_pos.slot].load(Ordering::Relaxed) as u64;
174 let mul2 = VAULTS[mul2_pos.array][mul2_pos.slot].load(Ordering::Relaxed) as u64;
175
176 let mut h = seed ^ (instance_addr as u64).rotate_left(23);
177 let mut positions = [VaultPos { array: 0, slot: 0 }; SHARE_ENTRIES];
178 let config = [seed_pos, mul1_pos, mul2_pos];
179
180 for i in 0..SHARE_ENTRIES {
181 loop {
182 h ^= h >> 17;
183 h = h.wrapping_mul(mul1 | 1);
184 h ^= h >> 13;
185 h = h.wrapping_mul(mul2 | 1);
186 h ^= h >> 16;
187 let candidate = VaultPos {
188 array: ((h >> 32) as usize) & (ARRAY_COUNT - 1),
189 slot: lane_slot(h as usize, lane),
190 };
191 if !config.contains(&candidate) && !positions[..i].contains(&candidate) {
192 positions[i] = candidate;
193 break;
194 }
195 }
197 }
198
199 positions
200}
201
202fn write_decoys(protected: &[VaultPos]) {
207
208 let mut rng = rand::rngs::OsRng;
209 for (array_idx, row) in VAULTS.iter().enumerate() {
210 for _ in 0..SHARE_ENTRIES {
211 let mut slot = (rng.next_u64() as usize) & (ARRAY_SIZE - 1);
212 while protected.iter().any(|p| p.array == array_idx && p.slot == slot) {
214 slot = (rng.next_u64() as usize) & (ARRAY_SIZE - 1);
215 }
216 let mut val = rng.next_u64() as usize;
217 if val == 0 { val = 1; }
218 row[slot].store(val, Ordering::Release);
219 }
220 }
221}
222
223pub struct GuardedKey {
225 active: AtomicUsize,
228}
229
230impl GuardedKey {
231 pub const fn empty() -> Self {
232 Self { active: AtomicUsize::new(0) }
233 }
234
235 #[inline]
236 fn instance_addr(&self) -> usize {
237 &self.active as *const _ as usize
238 }
239
240 #[inline]
244 fn lane_of(marker: usize) -> usize {
245 marker & (LANE_COUNT - 1)
246 }
247
248 #[inline]
250 fn lane(&self) -> usize {
251 Self::lane_of(self.active.load(Ordering::Acquire))
252 }
253
254 fn pick_marker(&self, others: &[&GuardedKey], rng: &mut rand::rngs::OsRng) -> usize {
261 let mut taken = [false; LANE_COUNT];
262 for &key in others {
263 if std::ptr::eq(key, self) || !key.has_key() { continue; }
264 taken[key.lane()] = true;
265 }
266 loop {
267 let mut marker = rng.next_u64() as usize;
268 if marker == 0 { marker = 1; }
269 if !taken[Self::lane_of(marker)] {
270 return marker;
271 }
272 }
273 }
274
275 fn collect_other_protected(&self, others: &[&GuardedKey]) -> ([VaultPos; (3 + SHARE_ENTRIES) * LANE_COUNT], usize) {
286 let mut buf = [VaultPos { array: 0, slot: 0 }; (3 + SHARE_ENTRIES) * LANE_COUNT];
287 let mut n = 0;
288 for &key in others {
289 if std::ptr::eq(key, self) || !key.has_key() { continue; }
290 if n + 3 + SHARE_ENTRIES > buf.len() { break; }
291 let addr = key.instance_addr();
292 let lane = key.lane();
293 let (s, m1, m2) = config_positions(addr, lane);
294 buf[n] = s; n += 1;
295 buf[n] = m1; n += 1;
296 buf[n] = m2; n += 1;
297 for &pos in share_positions(addr, lane).iter() {
298 buf[n] = pos;
299 n += 1;
300 }
301 }
302 (buf, n)
303 }
304
305 #[inline]
312 pub fn store_from_keys(&self, keys: &nostr_sdk::Keys, others: &[&GuardedKey]) {
313 let mut sk_bytes = keys.secret_key().secret_bytes();
314 self.set(sk_bytes, others);
315 sk_bytes.zeroize();
316 }
317
318 pub fn set(&self, mut key: [u8; 32], others: &[&GuardedKey]) {
327
328 let mut rng = rand::rngs::OsRng;
329 ensure_vaults();
330
331 let marker = self.pick_marker(others, &mut rng);
337 let lane = Self::lane_of(marker);
338
339 let old_marker = self.active.load(Ordering::Acquire);
342 if old_marker != 0 {
343 let old_lane = Self::lane_of(old_marker);
344 if old_lane != lane {
345 for pos in share_positions(self.instance_addr(), old_lane).iter() {
346 let mut val = rng.next_u64() as usize;
347 if val == 0 { val = 1; }
348 VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
349 }
350 }
351 }
352
353 let (protected, pcount) = self.collect_other_protected(others);
355
356 write_decoys(&protected[..pcount]);
360
361 let (_, mul1_pos, mul2_pos) = config_positions(self.instance_addr(), lane);
364 let v = VAULTS[mul1_pos.array][mul1_pos.slot].load(Ordering::Relaxed);
365 VAULTS[mul1_pos.array][mul1_pos.slot].store(v | 1, Ordering::Relaxed);
366 let v = VAULTS[mul2_pos.array][mul2_pos.slot].load(Ordering::Relaxed);
367 VAULTS[mul2_pos.array][mul2_pos.slot].store(v | 1, Ordering::Relaxed);
368
369 let mut shares = [[0u8; 32]; NUM_SHARES];
371 for share in shares.iter_mut().take(NUM_SHARES - 1) {
372 rng.fill_bytes(share);
373 }
374 shares[NUM_SHARES - 1] = key;
375 for i in 0..NUM_SHARES - 1 {
376 for j in 0..32 {
377 shares[NUM_SHARES - 1][j] ^= shares[i][j];
378 }
379 }
380 key.zeroize();
381
382 let positions = share_positions(self.instance_addr(), lane);
384 for (share_idx, share) in shares.iter().enumerate() {
385 for u_idx in 0..USIZES_PER_SHARE {
386 let byte_off = u_idx * std::mem::size_of::<usize>();
387 let val = usize::from_ne_bytes(
388 share[byte_off..byte_off + std::mem::size_of::<usize>()]
389 .try_into().unwrap()
390 );
391 let pos = positions[share_idx * USIZES_PER_SHARE + u_idx];
392 VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
393 }
394 }
395 for share in shares.iter_mut() { share.zeroize(); }
396
397 self.active.store(marker, Ordering::Release);
400 }
401
402 pub fn get(&self) -> Option<[u8; 32]> {
404 let marker = self.active.load(Ordering::Acquire);
405 if marker == 0 {
406 return None;
407 }
408
409 let positions = share_positions(self.instance_addr(), Self::lane_of(marker));
411 let mut key = [0u8; 32];
412
413 for share_idx in 0..NUM_SHARES {
414 let mut share = [0u8; 32];
415 for u_idx in 0..USIZES_PER_SHARE {
416 let pos = positions[share_idx * USIZES_PER_SHARE + u_idx];
417 let val = VAULTS[pos.array][pos.slot].load(Ordering::Acquire);
418 let byte_off = u_idx * std::mem::size_of::<usize>();
419 share[byte_off..byte_off + std::mem::size_of::<usize>()]
420 .copy_from_slice(&val.to_ne_bytes());
421 }
422 for (a, b) in key.iter_mut().zip(share.iter()) {
423 *a ^= *b;
424 }
425 }
426
427 Some(key)
428 }
429
430 pub fn clear(&self, others: &[&GuardedKey]) {
435 let old_marker = self.active.swap(0, Ordering::SeqCst);
437 if old_marker != 0 {
438
439 let mut rng = rand::rngs::OsRng;
440 let positions = share_positions(self.instance_addr(), Self::lane_of(old_marker));
442 for pos in &positions {
443 let mut val = rng.next_u64() as usize;
444 if val == 0 { val = 1; }
445 VAULTS[pos.array][pos.slot].store(val, Ordering::Release);
446 }
447 let (protected, pcount) = self.collect_other_protected(others);
448 write_decoys(&protected[..pcount]);
449 }
450 }
451
452 pub fn has_key(&self) -> bool {
453 self.active.load(Ordering::Acquire) != 0
454 }
455
456 pub fn to_keys(&self) -> Option<nostr_sdk::Keys> {
457 let mut bytes = self.get()?;
458 let result = nostr_sdk::SecretKey::from_slice(&bytes);
459 bytes.zeroize();
460 Some(nostr_sdk::Keys::new(result.ok()?))
461 }
462}
463
464#[cfg(test)]
469mod tests {
470 use super::*;
471
472 static TEST_KEY_A: GuardedKey = GuardedKey::empty();
474 static TEST_KEY_B: GuardedKey = GuardedKey::empty();
475
476 fn reset() {
487 TEST_KEY_A.active.store(0, Ordering::SeqCst);
488 TEST_KEY_B.active.store(0, Ordering::SeqCst);
489 ensure_vaults();
490 }
491
492 fn others_for_a() -> [&'static GuardedKey; 1] {
494 [&TEST_KEY_B]
495 }
496
497 fn others_for_b() -> [&'static GuardedKey; 1] {
499 [&TEST_KEY_A]
500 }
501
502 fn test_key(seed: u8) -> [u8; 32] {
504 let mut k = [0u8; 32];
505 for (i, b) in k.iter_mut().enumerate() {
506 *b = seed.wrapping_add(i as u8).wrapping_mul(37).wrapping_add(7);
507 }
508 k
509 }
510
511 #[test]
516 fn set_get_roundtrip() {
517 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
518 reset();
519 let key = test_key(42);
520 TEST_KEY_A.set(key, &others_for_a());
521 assert_eq!(TEST_KEY_A.get(), Some(key));
522 }
523
524 #[test]
525 fn set_get_1000_iterations() {
526 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
527 for i in 0..1000u16 {
528 reset();
529 let key = test_key((i ^ (i >> 3)) as u8);
530 TEST_KEY_A.set(key, &others_for_a());
531 assert_eq!(
532 TEST_KEY_A.get(), Some(key),
533 "Roundtrip failed at iteration {i}"
534 );
535 }
536 }
537
538 #[test]
539 fn empty_returns_none() {
540 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
541 reset();
542 assert_eq!(TEST_KEY_A.get(), None);
543 assert_eq!(TEST_KEY_B.get(), None);
544 }
545
546 #[test]
547 fn has_key_lifecycle() {
548 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
549 reset();
550 assert!(!TEST_KEY_A.has_key());
551 TEST_KEY_A.set(test_key(1), &others_for_a());
552 assert!(TEST_KEY_A.has_key());
553 TEST_KEY_A.clear(&others_for_a());
554 assert!(!TEST_KEY_A.has_key());
555 }
556
557 #[test]
558 fn clear_returns_none() {
559 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
560 reset();
561 TEST_KEY_A.set(test_key(99), &others_for_a());
562 assert!(TEST_KEY_A.get().is_some());
563 TEST_KEY_A.clear(&others_for_a());
564 assert_eq!(TEST_KEY_A.get(), None);
565 }
566
567 #[test]
568 fn set_overwrites_previous() {
569 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
570 reset();
571 let a = test_key(10);
572 let b = test_key(20);
573 TEST_KEY_A.set(a, &others_for_a());
574 assert_eq!(TEST_KEY_A.get(), Some(a));
575 TEST_KEY_A.set(b, &others_for_a());
576 assert_eq!(TEST_KEY_A.get(), Some(b));
577 }
578
579 #[test]
580 fn clear_idempotent() {
581 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
582 reset();
583 TEST_KEY_A.clear(&others_for_a());
584 TEST_KEY_A.clear(&others_for_a());
585 assert_eq!(TEST_KEY_A.get(), None);
586 TEST_KEY_A.set(test_key(5), &others_for_a());
587 TEST_KEY_A.clear(&others_for_a());
588 TEST_KEY_A.clear(&others_for_a());
589 assert_eq!(TEST_KEY_A.get(), None);
590 }
591
592 #[test]
593 fn encryption_key_basic() {
594 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
595 reset();
596 let key = test_key(0xEE);
597 TEST_KEY_B.set(key, &others_for_b());
598 assert_eq!(TEST_KEY_B.get(), Some(key));
599 TEST_KEY_B.clear(&others_for_b());
600 assert_eq!(TEST_KEY_B.get(), None);
601 }
602
603 #[test]
610 fn cross_key_set_then_set_500() {
611 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
612 let key_a = test_key(0xAA);
613 let key_b = test_key(0xBB);
614 for i in 0..500 {
615 reset();
616 TEST_KEY_A.set(key_a, &others_for_a());
617 TEST_KEY_B.set(key_b, &others_for_b());
618 assert_eq!(
619 TEST_KEY_A.get(), Some(key_a),
620 "TEST_KEY_A corrupted at iteration {i}"
621 );
622 assert_eq!(
623 TEST_KEY_B.get(), Some(key_b),
624 "TEST_KEY_B corrupted at iteration {i}"
625 );
626 }
627 }
628
629 #[test]
630 fn cross_key_reverse_order_500() {
631 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
632 let key_a = test_key(0xCC);
633 let key_b = test_key(0xDD);
634 for i in 0..500 {
635 reset();
636 TEST_KEY_B.set(key_b, &others_for_b());
637 TEST_KEY_A.set(key_a, &others_for_a());
638 assert_eq!(
639 TEST_KEY_B.get(), Some(key_b),
640 "TEST_KEY_B corrupted at iteration {i}"
641 );
642 assert_eq!(
643 TEST_KEY_A.get(), Some(key_a),
644 "TEST_KEY_A corrupted at iteration {i}"
645 );
646 }
647 }
648
649 #[test]
650 fn cross_key_clear_preserves_other_500() {
651 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
652 let key_a = test_key(0x11);
653 let key_b = test_key(0x22);
654 for i in 0..500 {
655 reset();
657 TEST_KEY_A.set(key_a, &others_for_a());
658 TEST_KEY_B.set(key_b, &others_for_b());
659 TEST_KEY_A.clear(&others_for_a());
660 assert_eq!(
661 TEST_KEY_B.get(), Some(key_b),
662 "KEY_B corrupted after KEY_A.clear() at iteration {i}"
663 );
664 reset();
666 TEST_KEY_A.set(key_a, &others_for_a());
667 TEST_KEY_B.set(key_b, &others_for_b());
668 TEST_KEY_B.clear(&others_for_b());
669 assert_eq!(
670 TEST_KEY_A.get(), Some(key_a),
671 "KEY_A corrupted after KEY_B.clear() at iteration {i}"
672 );
673 }
674 }
675
676 #[test]
677 fn cross_key_alternating_500() {
678 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
679 for i in 0..500u16 {
680 reset();
681 let ka = test_key(i as u8);
682 let kb = test_key(!(i as u8));
683 TEST_KEY_A.set(ka, &others_for_a());
684 TEST_KEY_B.set(kb, &others_for_b());
685 assert_eq!(TEST_KEY_A.get(), Some(ka), "KEY_A wrong at iter {i}");
686 assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong at iter {i}");
687 TEST_KEY_A.clear(&others_for_a());
688 assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong after KEY_A clear at iter {i}");
689 }
690 }
691
692 #[test]
696 fn stress_both_keys_1000() {
697 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
698 for i in 0..1000u32 {
699 reset();
700 let ka = test_key((i & 0xFF) as u8);
701 let kb = test_key(!((i & 0xFF) as u8));
702 if i % 2 == 0 {
703 TEST_KEY_A.set(ka, &others_for_a());
704 TEST_KEY_B.set(kb, &others_for_b());
705 } else {
706 TEST_KEY_B.set(kb, &others_for_b());
707 TEST_KEY_A.set(ka, &others_for_a());
708 }
709 assert_eq!(TEST_KEY_A.get(), Some(ka), "KEY_A wrong at iter {i}");
710 assert_eq!(TEST_KEY_B.get(), Some(kb), "KEY_B wrong at iter {i}");
711 }
712 }
713
714 #[test]
719 fn config_positions_all_unique() {
720 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
721 ensure_vaults();
722 for lane in 0..LANE_COUNT {
723 for addr in (0x1000..0x2000usize).step_by(8) {
724 let (a, b, c) = config_positions(addr, lane);
725 assert_ne!(a, b, "config collision a==b at addr {addr:#x} lane {lane}");
726 assert_ne!(a, c, "config collision a==c at addr {addr:#x} lane {lane}");
727 assert_ne!(b, c, "config collision b==c at addr {addr:#x} lane {lane}");
728 }
729 }
730 }
731
732 #[test]
733 fn share_positions_all_unique() {
734 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
735 ensure_vaults();
736 for lane in 0..LANE_COUNT {
737 for addr in (0x2000..0x2100usize).step_by(8) {
738 let positions = share_positions(addr, lane);
739 for i in 0..SHARE_ENTRIES {
740 for j in (i + 1)..SHARE_ENTRIES {
741 assert_ne!(
742 positions[i], positions[j],
743 "share collision [{i}]==[{j}] at addr {addr:#x} lane {lane}"
744 );
745 }
746 }
747 }
748 }
749 }
750
751 #[test]
752 fn share_positions_no_config_overlap() {
753 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
754 ensure_vaults();
755 for lane in 0..LANE_COUNT {
756 for addr in (0x3000..0x3100usize).step_by(8) {
757 let (s, m1, m2) = config_positions(addr, lane);
758 let config = [s, m1, m2];
759 let shares = share_positions(addr, lane);
760 for (i, pos) in shares.iter().enumerate() {
761 assert!(
762 !config.contains(pos),
763 "share[{i}] collides with config at addr {addr:#x} lane {lane}"
764 );
765 }
766 }
767 }
768 }
769
770 #[test]
774 fn distinct_lanes_are_disjoint() {
775 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
776 ensure_vaults();
777 for addr in (0x5000..0x5100usize).step_by(8) {
778 for la in 0..LANE_COUNT {
779 for lb in 0..LANE_COUNT {
780 if la == lb { continue; }
781 let (sa, m1a, m2a) = config_positions(addr, la);
782 let a_all: Vec<VaultPos> = [sa, m1a, m2a]
783 .into_iter()
784 .chain(share_positions(addr, la))
785 .collect();
786 let (sb, m1b, m2b) = config_positions(addr, lb);
787 let b_all: Vec<VaultPos> = [sb, m1b, m2b]
788 .into_iter()
789 .chain(share_positions(addr, lb))
790 .collect();
791 for p in &a_all {
792 assert!(
793 !b_all.contains(p),
794 "lane {la} and lane {lb} share {p:?} at addr {addr:#x}"
795 );
796 }
797 }
798 }
799 }
800 }
801
802 #[test]
803 fn positions_deterministic() {
804 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
805 ensure_vaults();
806 let addr = TEST_KEY_A.instance_addr();
807 let cfg1 = config_positions(addr, 3);
808 let cfg2 = config_positions(addr, 3);
809 assert_eq!(cfg1, cfg2);
810 let sp1 = share_positions(addr, 3);
811 let sp2 = share_positions(addr, 3);
812 assert_eq!(sp1, sp2);
813 }
814
815 #[test]
816 fn all_positions_in_bounds() {
817 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
818 ensure_vaults();
819 for lane in 0..LANE_COUNT {
820 for addr in (0x4000..0x4200usize).step_by(8) {
821 let (a, b, c) = config_positions(addr, lane);
822 for p in [a, b, c] {
823 assert!(p.array < ARRAY_COUNT);
824 assert!(p.slot < ARRAY_SIZE);
825 }
826 for p in share_positions(addr, lane) {
827 assert!(p.array < ARRAY_COUNT);
828 assert!(p.slot < ARRAY_SIZE);
829 }
830 }
831 }
832 }
833
834 #[test]
839 fn mix_iterations_in_range() {
840 for addr in 0..10000usize {
841 let n = mix_iterations(addr);
842 assert!((4096..=8191).contains(&n), "mix_iterations({addr}) = {n}");
843 }
844 }
845
846 #[test]
847 fn addr_mix_zero_iterations_is_identity() {
848 let h: u64 = 0xDEADBEEFCAFEBABE;
849 assert_eq!(addr_mix(h, 123, 456, 0), h);
850 }
851
852 #[test]
853 fn addr_mix_varies_output() {
854 let a = addr_mix(1, 3, 5, 10);
855 let b = addr_mix(2, 3, 5, 10);
856 let c = addr_mix(1, 7, 5, 10);
857 let d = addr_mix(1, 3, 11, 10);
858 assert_ne!(a, b);
859 assert_ne!(a, c);
860 assert_ne!(a, d);
861 }
862
863 #[test]
864 fn ensure_vaults_all_nonzero() {
865 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
866 ensure_vaults();
867 for (r, row) in VAULTS.iter().enumerate() {
868 for (s, slot) in row.iter().enumerate() {
869 assert_ne!(
870 slot.load(Ordering::Relaxed), 0,
871 "VAULTS[{r}][{s}] is zero after ensure_vaults"
872 );
873 }
874 }
875 }
876
877 #[test]
878 fn ensure_vaults_idempotent() {
879 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
880 ensure_vaults();
881 let samples: Vec<_> = (0..20)
882 .map(|i| {
883 let r = i * 13 % ARRAY_COUNT;
884 let s = i * 397 % ARRAY_SIZE;
885 (r, s, VAULTS[r][s].load(Ordering::Relaxed))
886 })
887 .collect();
888 ensure_vaults();
889 for (r, s, val) in &samples {
890 assert_eq!(
891 VAULTS[*r][*s].load(Ordering::Relaxed), *val,
892 "ensure_vaults changed VAULTS[{r}][{s}]"
893 );
894 }
895 }
896
897 #[test]
901 fn write_decoys_respects_exclusions_500() {
902 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
903 ensure_vaults();
904 let protected = [
905 VaultPos { array: 0, slot: 100 },
906 VaultPos { array: 0, slot: 200 },
907 VaultPos { array: 50, slot: 2000 },
908 VaultPos { array: 50, slot: 3000 },
909 VaultPos { array: 100, slot: 500 },
910 VaultPos { array: 127, slot: 4095 },
911 ];
912 let before: Vec<usize> = protected.iter()
913 .map(|p| VAULTS[p.array][p.slot].load(Ordering::Relaxed))
914 .collect();
915 for _ in 0..500 {
916 write_decoys(&protected);
917 }
918 for (i, p) in protected.iter().enumerate() {
919 assert_eq!(
920 VAULTS[p.array][p.slot].load(Ordering::Relaxed),
921 before[i],
922 "Protected position ({}, {}) overwritten after 500 rounds",
923 p.array, p.slot
924 );
925 }
926 }
927
928 #[test]
929 fn write_decoys_empty_exclusion_works() {
930 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
931 ensure_vaults();
932 write_decoys(&[]);
933 }
934
935 #[test]
940 fn zero_key_roundtrip() {
941 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
942 reset();
943 let key = [0u8; 32];
944 TEST_KEY_A.set(key, &others_for_a());
945 assert_eq!(TEST_KEY_A.get(), Some(key));
946 }
947
948 #[test]
949 fn max_key_roundtrip() {
950 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
951 reset();
952 let key = [0xFFu8; 32];
953 TEST_KEY_A.set(key, &others_for_a());
954 assert_eq!(TEST_KEY_A.get(), Some(key));
955 }
956
957 #[test]
958 fn to_keys_roundtrip() {
959 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
960 reset();
961 let mut sk_bytes = [0u8; 32];
962 sk_bytes[31] = 1; TEST_KEY_A.set(sk_bytes, &others_for_a());
964 let keys = TEST_KEY_A.to_keys();
965 assert!(keys.is_some(), "to_keys returned None for valid key");
966 assert_eq!(keys.unwrap().secret_key().secret_bytes(), sk_bytes);
967 }
968
969 #[test]
970 fn to_keys_none_when_empty() {
971 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
972 reset();
973 assert!(TEST_KEY_A.to_keys().is_none());
974 }
975
976 #[test]
977 fn store_from_keys_roundtrip() {
978 let _l = crate::db::DB_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
979 reset();
980 let keys = nostr_sdk::Keys::generate();
981 let expected = keys.secret_key().secret_bytes();
982 TEST_KEY_A.store_from_keys(&keys, &others_for_a());
983 assert_eq!(TEST_KEY_A.get(), Some(expected));
984 }
985
986 }