code wiki / (root) / nx_doc_seal.nx

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}