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}