code wiki / (root) / nx_install_attest_signed.nx

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}