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}