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}