code wiki / (root) / nx_sign_envelope_lib.nx

nx_sign_envelope_lib.nx source

↩ module page · 97 lines · 3767 B

1// nx_sign_envelope_lib.nx -- Nishi Sign CORE LIB: tamper-evident e-signature audit chain. 2// h[0]=sha256("nishi-sign-genesis"); h[n]=sha256(h[n-1] || canonical(event_n)) -> altering ANY 3// past event changes the audit head = TAMPER DETECTED BY CONSTRUCTION. Events canonicalized + 4// content-addressed (nx_canon_cid), persisted on the append-only seg_store (nx_registry). 5// LIB (no main) so the CLI organ AND the independent gate import the SAME shipped code. 6// SCALE-LAW (DECLARED): SE_EVT_CAP=4096 B/canonical event, fail-closed never truncates. 7// license_tier: ORIGINAL 8 9import "nx_sha256.nx" 10import "nx_canon_cid.nx" 11import "nx_registry.nx" 12import "nx_itoa_lib.nx" // THE shared MSB-first emitter -- se_catn composes it (see below) 13 14const SE_EVT_CAP: i64 = 4096 15 16func se_catcopy(dst: *u8, off: i64, s: *u8) -> i64 { 17 var i: i64 = 0 18 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } 19 return off + i 20} 21 22// ★COMPOSES nxi_buf (2026-08-15, found by nx_itoaclone). The hand-rolled body carried the same three 23// defects every clone of this shape does: a negative emitted ZERO BYTES (`while m > 0` with `m == 0` the 24// only special case), INT64_MIN would emit a lone '-', and it did sys_mmap(24) PER CALL and never freed 25// it -- needed only because it built digits least-significant-first and had to reverse through scratch. 26// That matters HERE in particular: this lib canonicalizes the events whose hash chain IS the tamper 27// evidence, so a value that renders as an empty field does not merely read wrong, it changes the 28// canonical bytes that get hashed. Contract is identical (digits only, no NUL, returns the offset 29// AFTER them) -- exactly why nxi_buf exists alongside ccz_cat_num. 30func se_catn(dst: *u8, off: i64, v: i64) -> i64 { 31 return nxi_buf(dst, off, v) 32} 33 34// canonical event record for (env,seq,signer,action,ts) into out; returns len 35func se_event_encode(env: *u8, seq: i64, signer: *u8, action: *u8, ts: i64, out: *u8) -> i64 { 36 let seqs: *u8 = sys_mmap(24) 37 var so: i64 = se_catn(seqs, 0, seq) 38 seqs[so] = 0 as u8 39 let tss: *u8 = sys_mmap(24) 40 var to: i64 = se_catn(tss, 0, ts) 41 tss[to] = 0 as u8 42 let keys: *i64 = sys_mmap(8 * 5) as *i64 43 let vals: *i64 = sys_mmap(8 * 5) as *i64 44 keys[0] = ("env" as *u8) as i64 45 vals[0] = env as i64 46 keys[1] = ("seq" as *u8) as i64 47 vals[1] = seqs as i64 48 keys[2] = ("signer" as *u8) as i64 49 vals[2] = signer as i64 50 keys[3] = ("action" as *u8) as i64 51 vals[3] = action as i64 52 keys[4] = ("ts" as *u8) as i64 53 vals[4] = tss as i64 54 return canon_encode(keys, vals, 5, out) 55} 56 57// chain step: out32 = sha256( prev32 || evt[0..evtlen) ) 58func se_chain(prev: *u8, evt: *u8, evtlen: i64, out32: *u8) -> i64 { 59 let buf: *u8 = sys_mmap(32 + SE_EVT_CAP + 16) 60 var i: i64 = 0 61 while i < 32 { buf[i] = prev[i]; i = i + 1 } 62 var j: i64 = 0 63 while j < evtlen { buf[32 + j] = evt[j]; j = j + 1 } 64 sha256_digest(buf, 32 + evtlen, out32) 65 return 0 66} 67 68// hex of 32-byte digest into out (64 chars + NUL) 69func se_hex(dg: *u8, out: *u8) -> i64 { 70 var i: i64 = 0 71 while i < 32 { 72 let b: i64 = dg[i] & 0xff 73 let hi: i64 = (b >> 4) & 15 74 let lo: i64 = b & 15 75 var c1: i64 = 48 + hi 76 if hi > 9 { c1 = 87 + hi } 77 var c2: i64 = 48 + lo 78 if lo > 9 { c2 = 87 + lo } 79 out[i * 2] = c1 as u8 80 out[i * 2 + 1] = c2 as u8 81 i = i + 1 82 } 83 out[64] = 0 as u8 84 return 64 85} 86 87// 1 if two 32-byte digests equal, else 0 88func se_eq32(a: *u8, b: *u8) -> i64 { 89 var i: i64 = 0 90 while i < 32 { if a[i] != b[i] { return 0 } i = i + 1 } 91 return 1 92} 93 94func se_genesis(out32: *u8) -> i64 { 95 sha256_digest("nishi-sign-genesis" as *u8, 18, out32) 96 return 0 97}