code wiki / _hdl_build / nx_social_recovery.nx
nx_social_recovery.nx source
↩ module page · 81 lines · 3468 B
1// nx_social_recovery.nx -- ELDERLY-FRIENDLY recovery: split a user's 256-bit recovery secret across N trusted
2// contacts (family) so ANY K of them can bring the user back in, and K-1 learn NOTHING. This REPLACES the
3// 24-word BIP39 mnemonic the user would otherwise have to safeguard (and could lose) -- the user holds nothing;
4// their family IS the backup. "Best security, least stress": no password to remember, no seed phrase to lose.
5//
6// Built on the sovereign Shamir primitive (nx_vault_shamir, GF(2^31-1)). That field splits ONE scalar < 2^31,
7// so a 256-bit secret is split CHUNK-WISE: 32 bytes -> 11 big-endian chunks (ten 3-byte + one 2-byte, each
8// < 2^24 < 2^31-1), each Shamir-split under the SAME contact x-coords. Reconstruct each chunk from K shares,
9// reassemble the 32 bytes. The recovery secret = the BIP39 entropy behind nx_modern_auth_recover, so a quorum
10// rebuilds exactly what's needed to reset the account -- no mnemonic ever handed to the user. license_tier: ORIGINAL
11import "nx_vault_shamir.nx"
12import "nx_csprng.nx"
13import "nx_syscalls.nx"
14
15const SR_KEY_BYTES: i64 = 32
16const SR_CHUNKS: i64 = 11 // ceil(32 / 3)
17
18func sr_chunk_nbytes(c: i64) -> i64 { if c == SR_CHUNKS - 1 { return 2 } return 3 }
19
20// pack nbytes big-endian from secret[bo..] into a value < 2^24.
21func sr_pack(secret: *u8, bo: i64, nbytes: i64) -> i64 {
22 var v: i64 = 0
23 var i: i64 = 0
24 while i < nbytes { v = (v << 8) | (secret[bo + i] as i64); i = i + 1 }
25 return v
26}
27// write a value back big-endian into out[bo..bo+nbytes).
28func sr_unpack(val: i64, out: *u8, bo: i64, nbytes: i64) -> i64 {
29 var i: i64 = 0
30 while i < nbytes {
31 let shift: i64 = (nbytes - 1 - i) * 8
32 out[bo + i] = ((val >> shift) & 0xff) as u8
33 i = i + 1
34 }
35 return 0
36}
37
38// SPLIT secret_32 into n contact shares, threshold k. shares_out = n rows x SR_CHUNKS i64 (row i = contact
39// x=i+1). Random poly coeffs from the CSPRNG (k-1 per chunk). Returns 0, or -1 on bad params.
40func nx_sr_split(secret: *u8, n: i64, k: i64, shares_out: *i64) -> i64 {
41 if k < 1 { return 0 - 1 }
42 if k > n { return 0 - 1 }
43 if n < 1 { return 0 - 1 }
44 var c: i64 = 0
45 while c < SR_CHUNKS {
46 let nbytes: i64 = sr_chunk_nbytes(c)
47 let bo: i64 = c * 3
48 let coeffs: *i64 = sys_mmap(k * 8) as *i64
49 coeffs[0] = sr_pack(secret, bo, nbytes)
50 var t: i64 = 1
51 while t < k {
52 let rb: *u8 = sys_mmap(8)
53 nx_csprng_fill(rb, 4)
54 let rv: i64 = ((rb[0] as i64) << 24) | ((rb[1] as i64) << 16) | ((rb[2] as i64) << 8) | (rb[3] as i64)
55 coeffs[t] = sh_mod(rv)
56 t = t + 1
57 }
58 var ci: i64 = 0
59 while ci < n {
60 shares_out[ci * SR_CHUNKS + c] = sh_eval(coeffs, k, ci + 1)
61 ci = ci + 1
62 }
63 c = c + 1
64 }
65 return 0
66}
67
68// RECONSTRUCT secret_32 from k contact shares. xs[j] = contact j's x-coord (its index+1); shares_in = k rows
69// x SR_CHUNKS (row j = contact xs[j]'s chunk shares). Returns 0.
70func nx_sr_reconstruct(k: i64, xs: *i64, shares_in: *i64, secret_out: *u8) -> i64 {
71 var c: i64 = 0
72 while c < SR_CHUNKS {
73 let ys: *i64 = sys_mmap(k * 8) as *i64
74 var j: i64 = 0
75 while j < k { ys[j] = shares_in[j * SR_CHUNKS + c]; j = j + 1 }
76 let val: i64 = sh_reconstruct(xs, ys, k)
77 sr_unpack(val, secret_out, c * 3, sr_chunk_nbytes(c))
78 c = c + 1
79 }
80 return 0
81}