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}