nx_doc_seal.nx source
↩ module page · 129 lines · 5602 B
1// nx_doc_seal.nx -- LEGAL RUNG D5: tamper-evident e-signature SEAL.
2//
3// module: nishi-core.legal.doc_seal
4// capability: LEGAL_ESIGN_SEAL
5//
6// Produces a non-repudiable, tamper-evident seal binding {document hash +
7// signer identity + timestamp + the D0 legal regime/verdict} under a REAL
8// RFC 8032 Ed25519 signature (not the V1 nx_sign_facade stand-in, which is
9// explicitly non-cryptographic). This is the "DocuSign but better" core:
10//
11// 1. NEVER SEAL A VOID INSTRUMENT -- nx_seal_create FIRST runs the D0
12// compliance verdict (nx_legal_compliance) and REFUSES to produce any
13// signature unless the verdict is VALID. The Rule-26 legal invariant is
14// thus carried into the signing layer: you physically cannot e-seal a
15// will that would be legally void. A generic e-sign product signs first
16// and asks never; this one cannot.
17// 2. CANONICAL RECORD -- a deterministic serialization binds the document
18// hash, signer, timestamp, regime and verdict, so the signature commits
19// to the FULL legal validity context, not just "someone clicked sign".
20// 3. TAMPER-EVIDENT -- nx_seal_verify recomputes nothing it isn't given;
21// any single-byte change to the canonical record (document hash, signer,
22// regime, ...) breaks the Ed25519 verification (proven in the gate).
23//
24// Composes: nx_legal_compliance (D0 verdict), the ed25519 RFC-8032 chain
25// (real signature). Distinct from nx_sign_facade because: that is a non-
26// cryptographic V1 stand-in; a legal signature needs real Ed25519.
27// license_tier: ORIGINAL
28// lineage_id: nishi_doc_seal_d5
29
30import "nx_syscalls.nx"
31import "nx_legal_compliance.nx"
32import "nx_x25519.nx"
33import "nx_ed25519_field.nx"
34import "nx_ed25519_point.nx"
35import "nx_ed25519_arith.nx"
36import "nx_ed25519_scalar.nx"
37import "nx_ed25519_signature.nx"
38
39// ---- seal-create status ----
40const SEAL_OK: i64 = 0
41const SEAL_REFUSED_VOID: i64 = 1 // D0 verdict INVALID_VOID -> no signature produced
42const SEAL_REFUSED_NEEDS_MORE: i64 = 2 // D0 verdict NEEDS_MORE -> no signature produced
43// ---- seal-verify status ----
44const SEAL_VERIFIED: i64 = 0
45const SEAL_TAMPERED: i64 = 1
46
47// One e-signature envelope. Caller pre-allocates canon (>=256) + sig (64) and
48// sets the input fields + the doc_hash/signer_id/priv pointers; nx_seal_create
49// fills verdict/regime/reason/canon_len/sig/status.
50struct NxSeal {
51 doc_type: i64,
52 e_wills_allowed: i64,
53 intent: i64,
54 consent: i64,
55 attribution: i64,
56 retainable: i64,
57 witnesses: i64,
58 notarized: i64,
59 doc_hash: *u8,
60 doc_hash_len: i64,
61 signer_id: *u8,
62 signer_id_len: i64,
63 ts: i64,
64 priv: *u8,
65 verdict: i64,
66 regime: i64,
67 reason: i64,
68 canon: *u8,
69 canon_len: i64,
70 sig: *u8,
71 status: i64
72}
73
74func sl_cat(out: *u8, o: i64, s: *u8) -> i64 { var k: i64 = 0; while s[k] != (0 as u8) { out[o] = s[k]; o = o + 1; k = k + 1 } return o }
75
76// Deterministic canonical seal record: magic + doc_type + regime + verdict +
77// ts(8 LE) + len-prefixed doc_hash + len-prefixed signer_id. Returns length.
78func nx_seal_canonical(out: *u8, doc_hash: *u8, doc_hash_len: i64,
79 signer_id: *u8, signer_id_len: i64, ts: i64,
80 doc_type: i64, regime: i64, verdict: i64) -> i64 {
81 var o: i64 = 0
82 o = sl_cat(out, o, "NXSEAL1" as *u8)
83 out[o] = doc_type as u8; o = o + 1
84 out[o] = regime as u8; o = o + 1
85 out[o] = verdict as u8; o = o + 1
86 var k: i64 = 0
87 while k < 8 { out[o] = ((ts >> (k * 8)) & 0xff) as u8; o = o + 1; k = k + 1 }
88 out[o] = doc_hash_len as u8; o = o + 1
89 var i: i64 = 0
90 while i < doc_hash_len { out[o] = doc_hash[i]; o = o + 1; i = i + 1 }
91 out[o] = signer_id_len as u8; o = o + 1
92 i = 0
93 while i < signer_id_len { out[o] = signer_id[i]; o = o + 1; i = i + 1 }
94 return o
95}
96
97// Create the seal: GATE on the D0 legal verdict, then Ed25519-sign the
98// canonical record. Returns SEAL_OK only when the instrument is legally VALID;
99// otherwise REFUSES and produces NO signature (never-seal-a-void-instrument).
100func nx_seal_create(s: *NxSeal) -> i64 {
101 let reason: *i64 = sys_mmap(8) as *i64
102 let regime: *i64 = sys_mmap(8) as *i64
103 let v: i64 = nx_legal_verdict(s.doc_type, s.e_wills_allowed, s.intent, s.consent,
104 s.attribution, s.retainable, s.witnesses, s.notarized,
105 reason, regime)
106 s.verdict = v
107 s.regime = *regime
108 s.reason = *reason
109 if v != LV_VALID {
110 if v == LV_INVALID_VOID { s.status = SEAL_REFUSED_VOID; return SEAL_REFUSED_VOID }
111 s.status = SEAL_REFUSED_NEEDS_MORE
112 return SEAL_REFUSED_NEEDS_MORE
113 }
114 let clen: i64 = nx_seal_canonical(s.canon, s.doc_hash, s.doc_hash_len,
115 s.signer_id, s.signer_id_len, s.ts,
116 s.doc_type, s.regime, v)
117 s.canon_len = clen
118 ed25519_sign_full(s.priv, s.canon, clen, s.sig)
119 s.status = SEAL_OK
120 return SEAL_OK
121}
122
123// Verify the seal's Ed25519 signature over its canonical record. Any tamper of
124// the canonical record (doc hash / signer / regime / verdict / ts) -> TAMPERED.
125func nx_seal_verify(s: *NxSeal, pub: *u8) -> i64 {
126 let r: i64 = ed25519_verify_full(pub, s.canon, s.canon_len, s.sig)
127 if r == NX_ED25519_SIG_OK { return SEAL_VERIFIED }
128 return SEAL_TAMPERED
129}