code wiki / (root) / nx_substrate_manifest.nx

nx_substrate_manifest.nx source

↩ module page · 274 lines · 10762 B

1// nx_substrate_manifest.nx -- declared expected-blob set + completion 2// tracking for the spore-up NETWORK phase. 3// 4// EM-3 partial milestone of NISHI_ECOSYSTEM_EVOLUTION_ROADMAP.md. 5// The spore germinates with a "Merkle root" -- a set of expected 6// blob hashes that together comprise the substrate it will assemble. 7// This primitive captures that expected set + tracks which blobs 8// have been received + verified, so the germination loop can 9// terminate when all expected blobs are present. 10// 11// Transport-agnostic: this primitive doesn't care HOW bytes arrive 12// (HTTPS / sneakernet / file / serial / Bluetooth). The caller 13// hands received bytes + the hash they CLAIM the bytes match; 14// this primitive verifies + stores + tracks. 15// 16// Composes: 17// nx_blob_store -- where verified blobs land 18// nx_sha256 -- hash verification (inside nx_blob_store_put) 19// 20// V1 scope: 21// - Fixed-array expected-hash set (up to NX_SM_MAX_EXPECTED) 22// - Per-hash received_flag: 0 (not yet) / 1 (received + verified) 23// - Ingest verifies: hash matches expected AND expected is in 24// manifest set (refuses unknown hashes -- closes the "peer can 25// push arbitrary content" attack vector) 26// - Idempotent re-ingest: ALREADY_RECEIVED is OK, not error 27// - Canary-bracketed 28// 29// Deferred per roadmap: 30// - Merkle-tree (not just flat set) for O(log n) inclusion proofs 31// - Per-blob freshness timestamps (when did we receive it?) 32// - Per-blob source attribution (which peer served it?) 33// - Larger expected set (V1 caps at 64 expected blobs) 34// 35// genealogy_id: git_pack_index_2005 + ipfs_pinning_2015 + 36// bittorrent_torrent_file_2001 + 37// cardinal_2026-05-20_ecosystem_evolution 38// lineage_id: substrate_manifest_v1 39// 40// nx_capability_manifest: 41// variant_class: substrate_manifest 42// variant_id: substrate_manifest_v1_flat_set 43// requires_isa: [rv64imac, x86_64, cortex_m, armv7a, aarch64] 44// requires_syscalls: [mmap] 45// requires_ram_min_b: 8192 46// tier_floor: NX_TIER_INF_MCU 47// tier_ceiling: NX_TIER_INF_HPC 48// cost_model: 49// flops_per_n: 50.0 // SHA-256 per ingest 50// bytes_per_n: 1.0 // received bytes pass-through 51// syscalls_per_n: 0.0 52// adversary_class: THREAT_AI_ADVERSARY 53// 54// nx_safety_envelope: 55// intended_use: "Tracks the expected-blob set for substrate 56// assembly; refuses unknown hashes; integrates 57// L0 blob_store" 58// sil_target: SIL2 59// evidence: [canary_bracketed, hash_verified_ingest, 60// unknown_hash_refused, idempotent_reingest] 61// verdict: NOT_YET_EVALUATED 62 63import "nx_syscalls.nx" 64import "nx_blob_store.nx" 65 66// ===== Constants ================================================= 67const NX_SM_MAX_EXPECTED: i64 = 64 68 69// Canary distinct from all prior primitive canaries. 70const NX_SM_CANARY_PRE: i64 = 0x4E58534D414E5000 // "NXSMANP\0" 71const NX_SM_CANARY_POST: i64 = 0x4E58534D414E454E // "NXSMANEN" 72 73// ===== Verdicts ================================================== 74const NX_SM_INGESTED: i64 = 0 // first successful receive 75const NX_SM_ALREADY_RECEIVED: i64 = 1 // idempotent re-ingest 76const NX_SM_NOT_IN_MANIFEST: i64 = 2 // hash not in expected set 77const NX_SM_HASH_MISMATCH: i64 = 3 // bytes don't hash to claimed 78const NX_SM_BAD_INPUT: i64 = 4 79const NX_SM_BLOB_STORE_FULL: i64 = 5 80const NX_SM_TAMPER: i64 = 6 81const NX_SM_FULL: i64 = 7 // expected set too large 82const NX_SM_N_VERDICTS: i64 = 8 83 84func nx_sm_verdict_is_valid(v: i64) -> i64 { 85 if v < 0 { return 0 } 86 if v >= NX_SM_N_VERDICTS { return 0 } 87 return 1 88} 89 90// ===== NxSubstrateManifest ======================================= 91struct NxSubstrateManifest { 92 canary_pre: i64, 93 n_expected: i64, 94 max_expected: i64, 95 n_received: i64, 96 n_rejected_unknown: i64, 97 n_rejected_tamper: i64, 98 expected_hashes: *i64, // *i64 array of NxBlobHash pointers 99 received_flags: *i64, // *i64 array of 0/1 per expected slot 100 blob_store: *NxBlobStore, // L0 backing 101 canary_post: i64, 102} 103 104// ===== Construction ============================================== 105func nx_substrate_manifest_new(blob_store: *NxBlobStore) -> *NxSubstrateManifest { 106 if (blob_store as i64) == 0 { return (0 as i64) as *NxSubstrateManifest } 107 let m: *NxSubstrateManifest = (sys_mmap(96)) as *NxSubstrateManifest 108 m.canary_pre = NX_SM_CANARY_PRE 109 m.canary_post = NX_SM_CANARY_POST 110 m.n_expected = 0 111 m.max_expected = NX_SM_MAX_EXPECTED 112 m.n_received = 0 113 m.n_rejected_unknown = 0 114 m.n_rejected_tamper = 0 115 m.expected_hashes = (sys_mmap(NX_SM_MAX_EXPECTED * 8)) as *i64 116 m.received_flags = (sys_mmap(NX_SM_MAX_EXPECTED * 8)) as *i64 117 var i: i64 = 0 118 while i < NX_SM_MAX_EXPECTED { 119 m.expected_hashes[i] = 0 120 m.received_flags[i] = 0 121 i = i + 1 122 } 123 m.blob_store = blob_store 124 return m 125} 126 127// ===== Validity gate ============================================ 128func nx_substrate_manifest_is_valid(m: *NxSubstrateManifest) -> i64 { 129 if (m as i64) == 0 { return 0 } 130 if m.canary_pre != NX_SM_CANARY_PRE { return 0 } 131 if m.canary_post != NX_SM_CANARY_POST { return 0 } 132 if m.n_expected < 0 { return 0 } 133 if m.n_expected > m.max_expected { return 0 } 134 if m.n_received < 0 { return 0 } 135 if m.n_received > m.n_expected { return 0 } 136 if (m.blob_store as i64) == 0 { return 0 } 137 return 1 138} 139 140// ===== Add an expected hash (manifest authoring) ================ 141// Operator declares "this hash is expected to arrive." Adds to the 142// manifest. Refuses on duplicate / full / null. 143func nx_substrate_manifest_add_expected( 144 m: *NxSubstrateManifest, 145 hash: *NxBlobHash 146) -> i64 { 147 if nx_substrate_manifest_is_valid(m) != 1 { return NX_SM_TAMPER } 148 if (hash as i64) == 0 { return NX_SM_BAD_INPUT } 149 if m.n_expected >= m.max_expected { return NX_SM_FULL } 150 151 // Refuse duplicate. 152 var i: i64 = 0 153 while i < m.n_expected { 154 let h_addr: i64 = m.expected_hashes[i] 155 if h_addr != 0 { 156 let h: *NxBlobHash = h_addr as *NxBlobHash 157 if nx_blob_hash_eq(h, hash) == 1 { 158 return NX_SM_ALREADY_RECEIVED // re-add is idempotent 159 } 160 } 161 i = i + 1 162 } 163 // Append. Store a COPY of the hash so manifest survives caller 164 // freeing the original. 165 let copy: *NxBlobHash = nx_blob_hash_new() 166 copy.w0 = hash.w0 167 copy.w1 = hash.w1 168 copy.w2 = hash.w2 169 copy.w3 = hash.w3 170 m.expected_hashes[m.n_expected] = copy as i64 171 m.received_flags[m.n_expected] = 0 172 m.n_expected = m.n_expected + 1 173 return NX_SM_INGESTED 174} 175 176// ===== Look up expected slot index by hash ===================== 177// Returns slot index 0..n_expected-1 on hit, -1 on miss. 178func _sm_find_expected_idx(m: *NxSubstrateManifest, hash: *NxBlobHash) -> i64 { 179 var i: i64 = 0 180 while i < m.n_expected { 181 let h_addr: i64 = m.expected_hashes[i] 182 if h_addr != 0 { 183 let h: *NxBlobHash = h_addr as *NxBlobHash 184 if nx_blob_hash_eq(h, hash) == 1 { return i } 185 } 186 i = i + 1 187 } 188 return -1 189} 190 191// ===== Ingest ================================================= 192// Caller hands bytes + the hash they claim the bytes match. 193// 194// Decision tree: 195// - Validate inputs + canary 196// - Verify the bytes ACTUALLY hash to claimed_hash (rejects 197// corrupted transit). We do this by put_blob into the 198// blob_store which computes SHA-256, then compare returned 199// hash to claimed. 200// - Verify claimed_hash is in the expected set (rejects 201// adversarial peer pushing arbitrary content). 202// - On success: set received_flags[i]=1; increment n_received 203// (only on first receipt -- idempotent re-ingest). 204func nx_substrate_manifest_ingest( 205 m: *NxSubstrateManifest, 206 claimed_hash: *NxBlobHash, 207 bytes: *u8, len: i64 208) -> i64 { 209 if nx_substrate_manifest_is_valid(m) != 1 { return NX_SM_TAMPER } 210 if (claimed_hash as i64) == 0 { return NX_SM_BAD_INPUT } 211 if len < 0 { return NX_SM_BAD_INPUT } 212 if len > 0 { 213 if (bytes as i64) == 0 { return NX_SM_BAD_INPUT } 214 } 215 216 // Step 1: check if claimed_hash is in the expected set. 217 let slot: i64 = _sm_find_expected_idx(m, claimed_hash) 218 if slot < 0 { 219 m.n_rejected_unknown = m.n_rejected_unknown + 1 220 return NX_SM_NOT_IN_MANIFEST 221 } 222 223 // Step 2: hash the bytes via L0 put_blob (which computes SHA-256 224 // and dedups). Compare the computed hash to claimed. 225 let computed_hash: *NxBlobHash = nx_blob_hash_new() 226 let rc_put: i64 = nx_blob_store_put(m.blob_store, bytes, len, computed_hash) 227 if rc_put == NX_BLOB_FULL { return NX_SM_BLOB_STORE_FULL } 228 if rc_put != NX_BLOB_OK { return NX_SM_BAD_INPUT } 229 230 if nx_blob_hash_eq(computed_hash, claimed_hash) != 1 { 231 // Bytes don't hash to claimed -- transit corruption or 232 // adversarial peer. put_blob already stored the corrupt 233 // bytes under their TRUE hash; that's fine (dedup blob store 234 // is content-addressed; the bad bytes are content-named by 235 // their actual hash, not the claimed one). We just refuse 236 // to mark the manifest slot as received. 237 m.n_rejected_tamper = m.n_rejected_tamper + 1 238 return NX_SM_HASH_MISMATCH 239 } 240 241 // Step 3: success. Idempotent: re-ingest is ALREADY_RECEIVED, 242 // not error. 243 if m.received_flags[slot] == 1 { 244 return NX_SM_ALREADY_RECEIVED 245 } 246 m.received_flags[slot] = 1 247 m.n_received = m.n_received + 1 248 return NX_SM_INGESTED 249} 250 251// ===== Completion query ========================================= 252func nx_substrate_manifest_is_complete(m: *NxSubstrateManifest) -> i64 { 253 if nx_substrate_manifest_is_valid(m) != 1 { return 0 } 254 if m.n_expected == 0 { return 0 } // empty manifest is not "complete" 255 if m.n_received == m.n_expected { return 1 } 256 return 0 257} 258 259// ===== Missing count ============================================ 260func nx_substrate_manifest_n_missing(m: *NxSubstrateManifest) -> i64 { 261 if nx_substrate_manifest_is_valid(m) != 1 { return -1 } 262 return m.n_expected - m.n_received 263} 264 265// ===== Audit accessors ========================================== 266func nx_substrate_manifest_n_rejected_unknown(m: *NxSubstrateManifest) -> i64 { 267 if nx_substrate_manifest_is_valid(m) != 1 { return -1 } 268 return m.n_rejected_unknown 269} 270 271func nx_substrate_manifest_n_rejected_tamper(m: *NxSubstrateManifest) -> i64 { 272 if nx_substrate_manifest_is_valid(m) != 1 { return -1 } 273 return m.n_rejected_tamper 274}