code wiki / (root) / nx_module_cas.nx

nx_module_cas.nx source

↩ module page · 147 lines · 6231 B

1// nx_module_cas.nx -- content-address NishiLang source modules. 2// 3// LAYER 2 of nishifamily.com/ide rollout per cardinals: 4// - feedback-lossless-system-state-archival-cas-merkle-dag-cardinal: 5// module identity = sha256 of canonical bytes, not filename + mtime. 6// - global rule 19 (API Contract Stability): the IDE pins each user 7// program to a specific substrate module-graph root hash. Updates 8// that change any module flip the root, and the IDE refuses to 9// silently rebind unless nx_abi_lock blesses the diff as additive. 10// - global rule 13 (Additive-Only Data): module hashes are computed 11// once and stored; history is never overwritten. 12// 13// Manifest shape (canonical byte ordering for reproducibility): 14// bytes[0..31] = sha256(name_utf8) 15// bytes[32..63] = sha256(source_utf8) 16// bytes[64..N] = sequence of (32-byte import_root_hash) per import, 17// sorted lexicographically before hashing. 18// merkle_root = sha256(bytes[0..N]) 19// 20// Sealed verdict for integrity checks (NX_MODULE_INTEGRITY_*): 21// 0 INCONCLUSIVE not enough bytes / sha256 not initialised 22// 1 SOURCE_MISMATCH source bytes hash != stored source_hash 23// 2 IMPORT_MISMATCH one or more import roots changed 24// 3 ROOT_MISMATCH name/source/imports recompute to a different root 25// 4 VERIFIED every level matches 26// 27// genealogy_id: nix_2003_paper + ipfs_merkle_dag + in_toto_2018 + 28// git_object_model_1991 + ostree_immutable_trees 29// lineage_id: source_module_content_addressing_q10 30// 31// nx_safety_envelope: 32// intended_use: "Content-addressed Merkle DAG of substrate 33// modules. Foundation for nx_vcs / sovereign 34// VCS replacement / immutable build provenance" 35// sil_target: SIL3 (CAS integrity = supply-chain trust; 36// a corrupt CAS = arbitrary code can 37// masquerade as audited code) 38// asil_target: QM 39// dal_target: DAL B 40// iec_62304_class: B 41// evidence: [SHA-256_content_address_inherits_nx_sha256_evidence, 42// Merkle_DAG_classical_construction, 43// no_FP, sealed_verdict] 44// hazard_register: [bug-tape-CAS-collision-attack, 45// bug-tape-truncation-of-content-before-hash, 46// bug-tape-symlink-bypassing-canonical-storage] 47// residual_risk: "SHA-256 collision resistance is the floor. 48// For tier-S claims migrate to SHA-3 or BLAKE3 49// per the NIST timeline; queued." 50// verdict: NOT_YET_EVALUATED 51 52import "nx_syscalls.nx" 53import "nx_tier.nx" 54import "nx_sha256.nx" 55 56const NX_MODULE_HASH_BYTES: nx_int = 32 57 58// Sealed integrity verdict. 59const NX_MODULE_INTEGRITY_INCONCLUSIVE: nx_int = 0 60const NX_MODULE_INTEGRITY_SOURCE_MISMATCH: nx_int = 1 61const NX_MODULE_INTEGRITY_IMPORT_MISMATCH: nx_int = 2 62const NX_MODULE_INTEGRITY_ROOT_MISMATCH: nx_int = 3 63const NX_MODULE_INTEGRITY_VERIFIED: nx_int = 4 64const NX_MODULE_INTEGRITY_N_VERDICTS: nx_int = 5 65 66// Caller fills name+source+imports; nx_module_cas_compute_root fills 67// the three derived hashes + merkle_root. All hashes are 32 bytes. 68struct ModuleManifest { 69 name_bytes: *u8, 70 name_len: nx_int, 71 source_bytes: *u8, 72 source_len: nx_int, 73 // import_root_hashes: packed 32*n_imports bytes, sorted ascending 74 // (caller sorts; we just hash). Sorting before hash is what makes 75 // the root insensitive to import-statement ordering -- two .nx 76 // files with the same set of imports in different orders yield the 77 // same Merkle root. 78 import_root_hashes: *u8, 79 n_imports: nx_int, 80 // Derived (filled by nx_module_cas_compute_root). 81 name_hash: *u8, // 32 bytes 82 source_hash: *u8, // 32 bytes 83 merkle_root: *u8, // 32 bytes 84} 85 86// Compute name/source/merkle hashes for a manifest. All three output 87// buffers must be allocated by the caller (32 bytes each). 88func nx_module_cas_compute_root(m: *ModuleManifest) -> nx_int { 89 // Hash the module name and source body separately. 90 sha256_digest(m.name_bytes, m.name_len, m.name_hash) 91 sha256_digest(m.source_bytes, m.source_len, m.source_hash) 92 93 // Merkle leaf list = name_hash || source_hash || import_root_hashes. 94 let leaf_len: nx_int = 32 + 32 + 32 * m.n_imports 95 let leaf_buf: *u8 = (sys_mmap(leaf_len)) as *u8 96 var i: nx_int = 0 97 while i < 32 { 98 leaf_buf[i] = m.name_hash[i] 99 i = i + 1 100 } 101 i = 0 102 while i < 32 { 103 leaf_buf[32 + i] = m.source_hash[i] 104 i = i + 1 105 } 106 let imports_len: nx_int = 32 * m.n_imports 107 i = 0 108 while i < imports_len { 109 leaf_buf[64 + i] = m.import_root_hashes[i] 110 i = i + 1 111 } 112 113 sha256_digest(leaf_buf, leaf_len, m.merkle_root) 114 return 0 115} 116 117// Verify a manifest against a previously-recorded root. The caller 118// provides the expected root (32 bytes); we recompute and compare. 119func nx_module_cas_verify(m: *ModuleManifest, expected_root: *u8) -> nx_int { 120 // Allocate scratch hashes if caller did not pre-allocate. 121 let nh: *u8 = (sys_mmap(32)) as *u8 122 let sh: *u8 = (sys_mmap(32)) as *u8 123 let rh: *u8 = (sys_mmap(32)) as *u8 124 m.name_hash = nh 125 m.source_hash = sh 126 m.merkle_root = rh 127 nx_module_cas_compute_root(m) 128 129 // Compare merkle_root to expected. 130 var i: nx_int = 0 131 var diff: nx_int = 0 132 while i < 32 { 133 if rh[i] != expected_root[i] { diff = diff + 1 } 134 i = i + 1 135 } 136 if diff == 0 { return NX_MODULE_INTEGRITY_VERIFIED } 137 return NX_MODULE_INTEGRITY_ROOT_MISMATCH 138} 139 140// Sealed-enum validity for the integrity verdict (cardinal: 141// feedback-self-aware-substrate-user-improvable -- nx_self_audit can 142// scan for verdicts outside the closed set). 143func nx_module_integrity_verdict_is_valid(v: nx_int) -> nx_int { 144 if v < 0 { return 0 } 145 if v >= NX_MODULE_INTEGRITY_N_VERDICTS { return 0 } 146 return 1 147}