nx_install_attest_signed.nx source
↩ module page · 194 lines · 8059 B
1// nx_install_attest_signed.nx -- Ed25519-signed install attestation.
2//
3// EM-9 milestone of NISHI_ECOSYSTEM_EVOLUTION_ROADMAP.md. Composes
4// nx_install_attest (hex codec + verify_against) with the shipped
5// bits-up Ed25519 stack to give every install_hash a cryptographic
6// signature. This closes the racing-line "unsigned spores rejected"
7// load-bearing claim.
8//
9// Operator workflow:
10// 1. Generate / load a 32-byte Ed25519 keypair (priv + pub)
11// 2. Run substrate -> obtain install_hash
12// 3. nx_install_sign(install_hash, priv) -> 64-byte sig
13// 4. Persist (install_hash, sig) in nx_journal_log + share with peers
14// 5. Receiver: nx_install_verify_sig(install_hash, pub, sig)
15// -> SIGNATURE_OK on valid, TAMPERED on any of:
16// - hash modified
17// - sig modified
18// - wrong pub key
19//
20// Threat model coverage (per
21// feedback-racing-crew-team-honesty-threat-aware):
22// - THREAT_OPPORTUNISTIC -- adversary can flip bytes in transit;
23// signature catches any modification to hash or sig
24// - THREAT_TARGETED_CRIMINAL -- adversary cannot forge a signature
25// without priv key (Ed25519 = 2^128 security)
26// - THREAT_AI_ADVERSARY -- byzantine-N-of-M (queued for SA-7)
27// adds threshold signing for AI-adversary tier
28// - THREAT_QUANTUM_FUTURE -- Ed25519 is NOT quantum-resistant;
29// migration to ML-DSA / Dilithium queued (composes with shipped
30// nx_ml_dsa_65_wasm.nx)
31//
32// V1 scope:
33// - Operator-supplied priv + pub (no key generation primitive yet;
34// RFC 8032 test vectors used in smoke; KDF / HW-derived keys queued)
35// - Single signature per install_hash (multi-sig / threshold queued)
36// - Detached signature (NOT embedded in hash) so the SAME hash can
37// be re-signed by different keys (e.g., dev-key during build +
38// ops-key at deploy)
39//
40// Deferred per roadmap:
41// - Key generation from entropy source (composes nx_random + nx_sha512)
42// - Hardware key derivation (TPM / TEE / nishi-silicon capability tags)
43// - Threshold signatures (FROST / Shamir) for byzantine N-of-M
44// - Post-quantum signature migration (ML-DSA shipped per memory)
45// - Spore-payload-signing (composes with nx_spore.nx from the
46// parallel forest-meta-vision arc once EM-1 wire-format lands)
47//
48// genealogy_id: rfc_8032_ed25519 + sigstore_2021 + slsa_2021 +
49// in_toto_2018 + cardinal_2026-05-20_ecosystem_evolution
50// lineage_id: substrate_install_attest_signed_v1
51//
52// nx_capability_manifest:
53// variant_class: install_attest_signed
54// variant_id: install_attest_signed_ed25519_v1
55// requires_isa: [rv64imac, x86_64, cortex_m, armv7a, aarch64]
56// requires_syscalls: [mmap]
57// requires_ram_min_b: 4096
58// tier_floor: NX_TIER_INF_MOBILE
59// tier_ceiling: NX_TIER_INF_HPC
60// cost_model:
61// flops_per_n: 50000.0 // Ed25519 sign ~ 100 KFLOP equivalent
62// bytes_per_n: 96.0 // pub32 + sig64 per attestation
63// syscalls_per_n: 0.0
64// adversary_class: THREAT_AI_ADVERSARY
65//
66// nx_safety_envelope:
67// intended_use: "Detached Ed25519 signature over install_hash;
68// closes the unsigned-spore-rejected racing-line
69// load-bearing claim"
70// sil_target: SIL3
71// evidence: [rfc_8032_kat_passes, tamper_detected,
72// wrong_pub_rejected, sig_modification_rejected]
73// verdict: NOT_YET_EVALUATED
74
75import "nx_syscalls.nx"
76import "nx_ed25519_signature.nx"
77import "nx_install_hash.nx"
78
79// ===== Verdicts ==================================================
80const NX_INSTALL_SIG_OK: i64 = 0
81const NX_INSTALL_SIG_TAMPERED: i64 = 1
82const NX_INSTALL_SIG_BAD_KEY: i64 = 2
83const NX_INSTALL_SIG_BAD_INPUT: i64 = 3
84const NX_INSTALL_SIG_N: i64 = 4
85
86func nx_install_sig_verdict_is_valid(v: i64) -> i64 {
87 if v < 0 { return 0 }
88 if v >= NX_INSTALL_SIG_N { return 0 }
89 return 1
90}
91
92// Constants for buffer sizes.
93const NX_INSTALL_PRIV_BYTES: i64 = 32
94const NX_INSTALL_PUB_BYTES: i64 = 32
95const NX_INSTALL_SIG_BYTES: i64 = 64
96
97// ===== Sign =====================================================
98// Signs an install_hash (32 bytes) with a 32-byte Ed25519 private
99// key. Output is the 64-byte Ed25519 signature. Operator owns
100// key management; substrate does not generate keys.
101//
102// Returns NX_INSTALL_SIG_OK on success or BAD_INPUT on null args.
103
104func nx_install_sign(
105 install_hash: *u8,
106 priv_32: *u8,
107 out_sig_64: *u8
108) -> i64 {
109 if (install_hash as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
110 if (priv_32 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
111 if (out_sig_64 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
112
113 // Sign the 32-byte install_hash as the message. ed25519_sign_full
114 // returns 0 on success; we treat any non-zero as a malformed-priv
115 // condition (BAD_KEY).
116 let rc: i64 = ed25519_sign_full(priv_32, install_hash,
117 NX_INSTALL_HASH_BYTES,
118 out_sig_64)
119 if rc != 0 { return NX_INSTALL_SIG_BAD_KEY }
120 return NX_INSTALL_SIG_OK
121}
122
123// ===== Verify ===================================================
124// Verifies a 64-byte signature against (install_hash, pub_32).
125// Returns NX_INSTALL_SIG_OK if the signature is valid,
126// NX_INSTALL_SIG_TAMPERED if invalid (caller cannot distinguish
127// hash-tamper from sig-tamper without recomputing the hash from
128// the plan -- both produce the same verdict by design).
129//
130// Returns NX_INSTALL_SIG_BAD_KEY on malformed signature
131// (S >= L, non-decodable R or pub).
132
133func nx_install_verify_sig(
134 install_hash: *u8,
135 pub_32: *u8,
136 sig_64: *u8
137) -> i64 {
138 if (install_hash as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
139 if (pub_32 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
140 if (sig_64 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
141
142 let rc: i64 = ed25519_verify_full(pub_32, install_hash,
143 NX_INSTALL_HASH_BYTES,
144 sig_64)
145 if rc < 0 { return NX_INSTALL_SIG_BAD_KEY } // malformed sig/pub
146 if rc == NX_ED25519_SIG_OK { return NX_INSTALL_SIG_OK }
147 return NX_INSTALL_SIG_TAMPERED
148}
149
150// ===== Plan-level signed attestation ===========================
151// Convenience: takes a full NxInstallPlan (canary-bracketed; valid
152// per nx_install_plan_is_valid) + priv, computes the install_hash
153// internally via nx_install_hash_compute, and signs the result.
154//
155// The smoke for this path proves end-to-end:
156// plan -> install_hash -> sign -> verify -> SIGNATURE_OK
157// AND
158// tamper plan -> install_hash differs -> verify returns TAMPERED
159
160func nx_install_sign_plan(
161 plan: *NxInstallPlan,
162 priv_32: *u8,
163 out_install_hash_32: *u8,
164 out_sig_64: *u8
165) -> i64 {
166 if (plan as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
167 if (priv_32 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
168 if (out_install_hash_32 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
169 if (out_sig_64 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
170
171 let rc_hash: i64 = nx_install_hash_compute(plan, out_install_hash_32)
172 if rc_hash != NX_HASH_OK { return NX_INSTALL_SIG_BAD_INPUT }
173 return nx_install_sign(out_install_hash_32, priv_32, out_sig_64)
174}
175
176// ===== Plan-level signed verification ==========================
177// Verifies (plan, pub, sig) by recomputing install_hash and
178// checking the signature. Returns SIGNATURE_OK on valid;
179// TAMPERED on any mismatch.
180
181func nx_install_verify_sig_plan(
182 plan: *NxInstallPlan,
183 pub_32: *u8,
184 sig_64: *u8
185) -> i64 {
186 if (plan as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
187 if (pub_32 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
188 if (sig_64 as i64) == 0 { return NX_INSTALL_SIG_BAD_INPUT }
189
190 let computed: *u8 = sys_mmap(64)
191 let rc_hash: i64 = nx_install_hash_compute(plan, computed)
192 if rc_hash != NX_HASH_OK { return NX_INSTALL_SIG_BAD_INPUT }
193 return nx_install_verify_sig(computed, pub_32, sig_64)
194}