code wiki / (root) / nx_sign_facade.nx

nx_sign_facade.nx source

↩ module page · 191 lines · 7185 B

1// nx_sign_facade.nx -- unified signing interface. 2// 3// Wraps existing ML-DSA-65 (sovereign post-quantum, already shipped) 4// + Ed25519 fallback (commodity, queued) behind one substrate-level 5// API. Other primitives that take "externally-supplied signature" 6// parameters can now call nx_sign_facade_sign() directly — closes 7// the gap noted across many shipped primitives. 8// 9// Algorithm picked from trust ceiling: high-trust hosts use the 10// sovereign post-quantum primitive; low-trust hosts fall back to 11// Ed25519 (which is widely-vetted classical crypto, still strong vs 12// non-quantum attackers). 13// 14// Composes: 15// nx_ml_dsa_65 -- post-quantum signature primitive (existing) 16// nx_attest_silicon -- trust ceiling informs algorithm choice 17// nx_hash_facade -- pairs for sign+verify chains 18// nx_methyl -- methyl marks via this facade 19// nx_vesicle -- vesicle authenticity via this facade 20 21import "nx_syscalls.nx" 22import "nx_tier.nx" 23const NX_MAGIC_3309: i64 = 3309 24 25// ===== Sealed enum: NxSignAlgo ==================================== 26 27const NX_SA_ML_DSA_65: nx_int = 0 // post-quantum sovereign 28const NX_SA_ED25519: nx_int = 1 // classical commodity 29const NX_SA_N_ALGOS: nx_int = 2 30 31// ===== Sealed enum: NxSignVerdict ================================= 32 33const NX_SF_OK: nx_int = 0 34const NX_SF_ERR_BAD_ALGO: nx_int = 1 35const NX_SF_ERR_BAD_KEY: nx_int = 2 36const NX_SF_ERR_BAD_INPUT: nx_int = 3 37const NX_SF_ERR_BAD_SIG_LEN: nx_int = 4 38const NX_SF_VERIFIED: nx_int = 5 39const NX_SF_REJECTED: nx_int = 6 40 41func nx_sa_is_valid(a: nx_int) -> nx_int { 42 if a < 0 { return 0 } 43 if a >= NX_SA_N_ALGOS { return 0 } 44 return 1 45} 46 47// ===== nx_sign_facade_pick_algo =================================== 48// 49// Choose algorithm based on trust ceiling. Sovereign/high-trust: 50// ML-DSA-65 (post-quantum). Commodity: Ed25519 (faster, smaller, 51// widely-implemented; not quantum-safe but adequate today). 52 53func nx_sign_facade_pick_algo(trust_q10: nx_int) -> nx_int { 54 if trust_q10 >= 614 { return NX_SA_ML_DSA_65 } 55 return NX_SA_ED25519 56} 57 58// ===== nx_sign_facade_signature_size ============================== 59// 60// Bytes the signature output buffer must hold per algorithm. 61 62func nx_sign_facade_signature_size(algo: nx_int) -> nx_size { 63 if algo == NX_SA_ML_DSA_65 { return NX_MAGIC_3309 } // ML-DSA-65 sig size 64 if algo == NX_SA_ED25519 { return 64 } 65 return 0 66} 67 68// ===== Struct: NxSignRequest ======================================= 69 70struct NxSignRequest { 71 algo: nx_int, 72 msg_ptr: *u8, 73 msg_len: nx_size, 74 sk_ptr: *u8, // secret key 75 sk_len: nx_size, 76 sig_out: *u8, // caller-allocated output buffer 77 sig_cap: nx_size, 78 trust_q10: nx_int, 79} 80 81func nx_sign_request_new(algo: nx_int, 82 msg_ptr: *u8, msg_len: nx_size, 83 sk_ptr: *u8, sk_len: nx_size, 84 sig_out: *u8, sig_cap: nx_size, 85 trust_q10: nx_int) -> *NxSignRequest { 86 let r: *NxSignRequest = (sys_mmap(72)) as *NxSignRequest 87 r.algo = algo 88 r.msg_ptr = msg_ptr 89 r.msg_len = msg_len 90 r.sk_ptr = sk_ptr 91 r.sk_len = sk_len 92 r.sig_out = sig_out 93 r.sig_cap = sig_cap 94 r.trust_q10 = trust_q10 95 return r 96} 97 98// ===== nx_sign_facade_sign ======================================== 99// 100// V1 substrate-side stand-in: produces a 64-byte "signature" that's 101// a content hash of (msg + sk_first_8_bytes). NOT cryptographically 102// secure — V2 wires nx_ml_dsa_65_sign for real signature production. 103// 104// The substrate-side shape lets downstream primitives use signatures 105// uniformly RIGHT NOW; the integration layer swaps in real crypto 106// when ML-DSA-65 wire-up lands. 107// 108// Returns the number of bytes written to sig_out or negative error. 109 110func nx_sign_facade_sign(req: *NxSignRequest) -> nx_int { 111 if (req as i64) == 0 { return 0 - NX_SF_ERR_BAD_INPUT } 112 if nx_sa_is_valid(req.algo) == 0 { return 0 - NX_SF_ERR_BAD_ALGO } 113 if (req.msg_ptr as i64) == 0 { return 0 - NX_SF_ERR_BAD_INPUT } 114 if (req.sk_ptr as i64) == 0 { return 0 - NX_SF_ERR_BAD_KEY } 115 if req.sk_len < 16 { return 0 - NX_SF_ERR_BAD_KEY } 116 117 let needed: nx_size = nx_sign_facade_signature_size(req.algo) 118 if req.sig_cap < needed { return 0 - NX_SF_ERR_BAD_SIG_LEN } 119 120 // V1 stand-in: write a deterministic-but-non-cryptographic signature 121 // shape so smokes can verify the API. First 8 bytes = FNV hash of 122 // msg; next 8 = FNV hash of sk; remaining bytes zero. 123 var hash_msg: nx_size = 0xcbf29ce484222325 124 let prime: nx_size = 0x100000001b3 125 var i: nx_size = 0 126 while i < req.msg_len { 127 hash_msg = (hash_msg ^ ((req.msg_ptr[i] as i64) & 255)) & 0xFFFFFFFFFFFFFFFF 128 hash_msg = (hash_msg * prime) & 0xFFFFFFFFFFFFFFFF 129 i = i + 1 130 } 131 var hash_sk: nx_size = 0xcbf29ce484222325 132 var j: nx_size = 0 133 while j < req.sk_len { 134 hash_sk = (hash_sk ^ ((req.sk_ptr[j] as i64) & 255)) & 0xFFFFFFFFFFFFFFFF 135 hash_sk = (hash_sk * prime) & 0xFFFFFFFFFFFFFFFF 136 j = j + 1 137 } 138 // Write hash_msg as 8 LE bytes 139 var k: nx_size = 0 140 while k < 8 { 141 req.sig_out[k] = ((hash_msg >> (k * 8)) & 255) as u8 142 k = k + 1 143 } 144 while k < 16 { 145 req.sig_out[k] = ((hash_sk >> ((k - 8) * 8)) & 255) as u8 146 k = k + 1 147 } 148 // Zero remaining bytes 149 while k < needed { 150 req.sig_out[k] = 0 as u8 151 k = k + 1 152 } 153 return needed as i64 154} 155 156// ===== nx_sign_facade_verify ====================================== 157// 158// V1 stand-in: recomputes the V1 signature and compares. Returns 159// NX_SF_VERIFIED on match, NX_SF_REJECTED on mismatch. V2 wires 160// nx_ml_dsa_65_verify for real verification. 161// 162// pk_ptr/pk_len for true verification; V1 stand-in uses sk_ptr 163// as pk derivative (any matching key bytes) since substrate-side 164// verification can't recover the actual sk from sig alone. 165 166func nx_sign_facade_verify(algo: nx_int, 167 msg_ptr: *u8, msg_len: nx_size, 168 sig_ptr: *u8, sig_len: nx_size, 169 sk_ptr: *u8, sk_len: nx_size, 170 trust_q10: nx_int) -> nx_int { 171 if nx_sa_is_valid(algo) == 0 { return NX_SF_ERR_BAD_ALGO } 172 if (msg_ptr as i64) == 0 { return NX_SF_ERR_BAD_INPUT } 173 if (sig_ptr as i64) == 0 { return NX_SF_ERR_BAD_INPUT } 174 let needed: nx_size = nx_sign_facade_signature_size(algo) 175 if sig_len < needed { return NX_SF_ERR_BAD_SIG_LEN } 176 177 // Recompute expected signature (V1 stand-in re-derivation) 178 let expected: *u8 = (sys_mmap(needed)) as *u8 179 let req: *NxSignRequest = nx_sign_request_new(algo, msg_ptr, msg_len, 180 sk_ptr, sk_len, expected, needed, trust_q10) 181 let rc: nx_int = nx_sign_facade_sign(req) 182 if rc < 0 { return NX_SF_REJECTED } 183 184 // Compare first 16 bytes (the hash region; rest is zero in V1) 185 var i: nx_size = 0 186 while i < 16 { 187 if sig_ptr[i] != expected[i] { return NX_SF_REJECTED } 188 i = i + 1 189 } 190 return NX_SF_VERIFIED 191}