nx_mls_group.nx source
↩ module page · 799 lines · 35211 B
1// nx_mls_group.nx -- C7 of the comms lane: MLS-CLASS EPOCHED GROUP RATCHET (RFC 9420 key schedule).
2// Contract symbol mls_epoch_advance == the /compare/comms C7 watch. REFEREE: nx_mls_group_gate.
3// DONE-RULE (comms.plan): RFC 9420-class epoched group keys on chat AND calls; member removal forces
4// an epoch advance and the gate PROVES a removed member cannot decrypt the next frame (negative
5// control), with forward-secrecy and post-compromise teeth; replaces the static room key.
6//
7// HONEST SCOPE, stated so nothing is over-claimed: this implements the RFC 9420 KEY SCHEDULE and its
8// security properties -- one-way epoch chaining (forward secrecy), commit-secret injection
9// (post-compromise security), and membership-bound epoch secrets (removal excludes). Commit secrets
10// are distributed to the remaining members by FLAT per-member X25519 (O(n) wraps). TreeKEM's ratchet
11// tree is the O(log n) DISTRIBUTION efficiency, and it is a follow-on, NOT a security gap -- every
12// property proven by the gate is the same property TreeKEM delivers, just distributed pairwise here.
13// PURE COMPOSITION of the sovereign primitives, no new crypto: x25519 (RFC 7748 ECDH) + sha256
14// (FIPS 180-4, as HMAC/HKDF-Expand) + chacha20 (RFC 8439). No WebRTC, no OpenSSL, one auditable stack.
15//
16// THE PROPERTIES, and how each is a tooth:
17// * epoch_secret is per-member, never transmitted; only COMMITMENTS sha256("commit-check"||secret)
18// are printed, so the gate can prove convergence without exposing a secret.
19// * advance: prk = HMAC(old_epoch_secret, commit_secret); new = HKDF-Expand(prk,"epoch",group_ctx).
20// One-way (HKDF) => forward secrecy. commit_secret fresh + ECDH-wrapped => post-compromise.
21// * group_ctx = sha256(group_id || epoch || roster) => the epoch secret is BOUND to the membership,
22// so a divergent roster diverges the secret (defence in depth behind the wrap exclusion).
23// * a removed member is absent from the new roster, so no wrap is addressed to it: apply REFUSES,
24// and even holding the old epoch_secret it cannot reach the new one (no commit_secret).
25// * frames are real ChaCha20 sealed under framekey=HKDF(epoch_secret,"app",seq) + an HMAC tag, so
26// open with the WRONG epoch_secret fails the MAC -- the removed member cannot open the next frame.
27// license_tier: ORIGINAL No hw writes (Rule 26).
28import "nx_syscalls.nx"
29import "nx_itoa_lib.nx"
30import "nx_x25519.nx"
31import "nx_sha256.nx"
32import "nx_chacha20.nx"
33
34const MG_EXIT_OK: i64 = 0
35const MG_EXIT_USAGE: i64 = 2
36const MG_EXIT_REFUSED: i64 = 3
37const MG_EXIT_CORRUPT: i64 = 5
38// structural crypto sizes (algorithm constants, named -- not tunable policy):
39const MG_KEY: i64 = 32 // X25519 scalar / pubkey / epoch_secret / chacha key
40const MG_MAC: i64 = 32 // HMAC-SHA256 tag
41const MG_NONCE: i64 = 12 // chacha20 nonce
42const MG_BLOCK: i64 = 64 // sha256/HMAC block
43const MG_HEXCAP: i64 = 4096
44const MG_FILECAP: i64 = 262144
45const MG_NL: i64 = 10
46const MG_PIPE: i64 = 124
47const MG_COMMA: i64 = 44
48const MG_IPAD: i64 = 54 // 0x36
49const MG_OPAD: i64 = 92 // 0x5c
50const MG_ROSTER_MAX: i64 = 256
51
52func mg_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
53func mg_wn(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 }
54func mg_num(v: i64) -> i64 { nxi_out(v); return 0 }
55func mg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
56func mg_eq(a: *u8, b: *u8) -> i64 {
57 var i: i64 = 0
58 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
59 if b[i] != (0 as u8) { return 0 }
60 return 1
61}
62func mg_cpy(d: *u8, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[i] = s[i]; i = i + 1 } return 0 }
63func mg_memeq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { if a[i] != b[i] { return 0 } i = i + 1 } return 1 }
64// ---- hex ---------------------------------------------------------------------------------------
65func mg_hexe(src: *u8, n: i64, dst: *u8) -> i64 {
66 var i: i64 = 0
67 while i < n {
68 let b: i64 = src[i] as i64
69 let hi: i64 = b / 16
70 let lo: i64 = b % 16
71 if hi < 10 { dst[i*2] = (48 + hi) as u8 } else { dst[i*2] = (87 + hi) as u8 }
72 if lo < 10 { dst[i*2+1] = (48 + lo) as u8 } else { dst[i*2+1] = (87 + lo) as u8 }
73 i = i + 1
74 }
75 dst[n*2] = 0 as u8
76 return n * 2
77}
78func mg_hexv(c: i64) -> i64 {
79 if c >= 48 { if c <= 57 { return c - 48 } }
80 if c >= 97 { if c <= 102 { return c - 87 } }
81 if c >= 65 { if c <= 70 { return c - 55 } }
82 return 0 - 1
83}
84func mg_hexd(src: *u8, n: i64, dst: *u8) -> i64 {
85 if n % 2 != 0 { return 0 - 1 }
86 var i: i64 = 0
87 while i < n {
88 let h: i64 = mg_hexv(src[i] as i64)
89 let l: i64 = mg_hexv(src[i+1] as i64)
90 if h < 0 { return 0 - 1 }
91 if l < 0 { return 0 - 1 }
92 dst[i/2] = (h * 16 + l) as u8
93 i = i + 2
94 }
95 return n / 2
96}
97// ---- files -------------------------------------------------------------------------------------
98func mg_read(path: *u8, buf: *u8, cap: i64) -> i64 {
99 let fd: i64 = sys_openat_rd(path)
100 if fd < 0 { return 0 - 1 }
101 var tot: i64 = 0
102 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r }
103 sys_close(fd)
104 return tot
105}
106func mg_write(path: *u8, buf: *u8, n: i64) -> i64 {
107 let fd: i64 = sys_openat_wr(path, 420)
108 if fd < 0 { return 0 - 1 }
109 var w: i64 = 0
110 while w < n { let r: i64 = sys_write(fd, (buf as i64 + w) as *u8, n - w); if r <= 0 { break } w = w + r }
111 sys_fsync(fd)
112 sys_close(fd)
113 return 0
114}
115func mg_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o }
116func mg_catn(d: *u8, o: i64, v: i64) -> i64 {
117 if v == 0 { d[o] = 48 as u8; return o + 1 }
118 var tmp: *u8 = sys_mmap(24)
119 var k: i64 = 0
120 var m: i64 = v
121 while m > 0 { tmp[k] = (48 + m % 10) as u8; m = m / 10; k = k + 1 }
122 var j: i64 = k - 1
123 while j >= 0 { d[o] = tmp[j]; o = o + 1; j = j - 1 }
124 return o
125}
126// ---- HMAC-SHA256 (composes the canonical sha256_digest) -----------------------------------------
127func mg_hmac(key: *u8, klen: i64, msg: *u8, mlen: i64, out: *u8) -> i64 {
128 let k0: *u8 = sys_mmap(MG_BLOCK)
129 var i: i64 = 0
130 if klen > MG_BLOCK {
131 sha256_digest(key, klen, k0)
132 i = MG_KEY
133 while i < MG_BLOCK { k0[i] = 0 as u8; i = i + 1 }
134 } else {
135 i = 0
136 while i < klen { k0[i] = key[i]; i = i + 1 }
137 while i < MG_BLOCK { k0[i] = 0 as u8; i = i + 1 }
138 }
139 let inner: *u8 = sys_mmap(MG_BLOCK + mlen + 16)
140 i = 0
141 while i < MG_BLOCK { inner[i] = ((k0[i] as i64) ^ MG_IPAD) as u8; i = i + 1 }
142 var j: i64 = 0
143 while j < mlen { inner[MG_BLOCK + j] = msg[j]; j = j + 1 }
144 let ih: *u8 = sys_mmap(MG_KEY)
145 sha256_digest(inner, MG_BLOCK + mlen, ih)
146 let outer: *u8 = sys_mmap(MG_BLOCK + MG_KEY + 16)
147 i = 0
148 while i < MG_BLOCK { outer[i] = ((k0[i] as i64) ^ MG_OPAD) as u8; i = i + 1 }
149 j = 0
150 while j < MG_KEY { outer[MG_BLOCK + j] = ih[j]; j = j + 1 }
151 sha256_digest(outer, MG_BLOCK + MG_KEY, out)
152 return 0
153}
154// HKDF-Expand one 32-byte block with an MLS-shaped labeled info: info = "MLS 1.0 "||label||ctx||0x01
155func mg_derive(secret: *u8, label: *u8, ctx: *u8, clen: i64, out: *u8) -> i64 {
156 let info: *u8 = sys_mmap(512 + clen)
157 var o: i64 = mg_cat(info, 0, "MLS 1.0 " as *u8)
158 o = mg_cat(info, o, label)
159 var i: i64 = 0
160 while i < clen { info[o] = ctx[i]; o = o + 1; i = i + 1 }
161 info[o] = 1 as u8 // T(1) counter
162 o = o + 1
163 mg_hmac(secret, MG_KEY, info, o, out)
164 return 0
165}
166// group context binds the epoch secret to (group, epoch, roster)
167func mg_ctx(group_id: *u8, epoch: i64, roster: *u8, out: *u8) -> i64 {
168 let b: *u8 = sys_mmap(MG_HEXCAP + 64)
169 var o: i64 = mg_cat(b, 0, "mlsctx|" as *u8)
170 o = mg_cat(b, o, group_id)
171 b[o] = MG_PIPE as u8
172 o = o + 1
173 o = mg_catn(b, o, epoch)
174 b[o] = MG_PIPE as u8
175 o = o + 1
176 o = mg_cat(b, o, roster)
177 sha256_digest(b, o, out)
178 return 0
179}
180// advance the epoch: prk = HMAC(old, commit_secret); new = HKDF-Expand(prk, "epoch", ctx). ONE-WAY.
181// THE CONTRACT SYMBOL.
182func mls_epoch_advance(old_secret: *u8, commit_secret: *u8, group_id: *u8, epoch: i64, roster: *u8, out: *u8) -> i64 {
183 let prk: *u8 = sys_mmap(MG_KEY)
184 mg_hmac(old_secret, MG_KEY, commit_secret, MG_KEY, prk)
185 let ctx: *u8 = sys_mmap(MG_KEY)
186 mg_ctx(group_id, epoch, roster, ctx)
187 mg_derive(prk, "epoch" as *u8, ctx, MG_KEY, out)
188 return 0
189}
190// clamp per X25519 and derive pubkey
191func mg_clamp(p: *u8) -> i64 {
192 p[0] = ((p[0] as i64) & 248) as u8
193 p[31] = (((p[31] as i64) & 127) | 64) as u8
194 return 0
195}
196func mg_pub(priv: *u8, pub: *u8) -> i64 {
197 let bp: *u8 = sys_mmap(MG_KEY)
198 bp[0] = 9 as u8
199 var i: i64 = 1
200 while i < MG_KEY { bp[i] = 0 as u8; i = i + 1 }
201 x25519(priv, bp, pub)
202 return 0
203}
204// wrap a 32-byte payload TO a member pubkey using ephemeral ECDH. out = ct(32)||mac(32).
205func mg_wrap(eph_priv: *u8, member_pub: *u8, payload: *u8, out: *u8) -> i64 {
206 let dh: *u8 = sys_mmap(MG_KEY)
207 x25519(eph_priv, member_pub, dh)
208 let wk: *u8 = sys_mmap(MG_KEY)
209 mg_derive(dh, "welcome key" as *u8, dh, 0, wk) // key bound to the dh itself
210 let nz: *u8 = sys_mmap(MG_NONCE)
211 var i: i64 = 0
212 while i < MG_NONCE { nz[i] = 0 as u8; i = i + 1 } // safe: wk is unique per (eph,member)
213 chacha20_encrypt(wk, 0, nz, payload, MG_KEY, out)
214 mg_hmac(wk, MG_KEY, out, MG_KEY, (out as i64 + MG_KEY) as *u8)
215 return 0
216}
217// unwrap; returns 0 on success (payload filled), -1 on MAC failure.
218func mg_unwrap(member_priv: *u8, eph_pub: *u8, blob: *u8, payload: *u8) -> i64 {
219 let dh: *u8 = sys_mmap(MG_KEY)
220 x25519(member_priv, eph_pub, dh)
221 let wk: *u8 = sys_mmap(MG_KEY)
222 mg_derive(dh, "welcome key" as *u8, dh, 0, wk)
223 let mac: *u8 = sys_mmap(MG_MAC)
224 mg_hmac(wk, MG_KEY, blob, MG_KEY, mac)
225 if mg_memeq(mac, (blob as i64 + MG_KEY) as *u8, MG_MAC) == 0 { return 0 - 1 }
226 let nz: *u8 = sys_mmap(MG_NONCE)
227 var i: i64 = 0
228 while i < MG_NONCE { nz[i] = 0 as u8; i = i + 1 }
229 chacha20_encrypt(wk, 0, nz, blob, MG_KEY, payload)
230 return 0
231}
232func mg_commit_hash(secret: *u8, out: *u8) -> i64 {
233 let b: *u8 = sys_mmap(64)
234 var o: i64 = mg_cat(b, 0, "commit-check" as *u8)
235 var i: i64 = 0
236 while i < MG_KEY { b[o] = secret[i]; o = o + 1; i = i + 1 }
237 sha256_digest(b, o, out)
238 return 0
239}
240// find member index in a roster CSV of pubkey-hex; -1 if absent (== removed).
241func mg_roster_idx(roster: *u8, mypubhex: *u8) -> i64 {
242 let want: i64 = mg_len(mypubhex)
243 var idx: i64 = 0
244 var i: i64 = 0
245 let rl: i64 = mg_len(roster)
246 while i < rl {
247 var s: i64 = i
248 while i < rl { if roster[i] == (MG_COMMA as u8) { break } i = i + 1 }
249 let flen: i64 = i - s
250 if flen == want {
251 var m: i64 = 1
252 var k: i64 = 0
253 while k < want { if roster[s+k] != mypubhex[k] { m = 0; break } k = k + 1 }
254 if m == 1 { return idx }
255 }
256 idx = idx + 1
257 i = i + 1
258 }
259 return 0 - 1
260}
261// pull field <idx> (0-based, pipe-separated) of a NUL/NL-terminated line copy into out; len or -1.
262func mg_field(line: *u8, n: i64, idx: i64, out: *u8) -> i64 {
263 var f: i64 = 0
264 var i: i64 = 0
265 var st: i64 = 0
266 while i < n { if f == idx { st = i; break } if line[i] == (MG_PIPE as u8) { f = f + 1 } i = i + 1 }
267 if f != idx { return 0 - 1 }
268 var e: i64 = st
269 while e < n { if line[e] == (MG_PIPE as u8) { break } if line[e] == (MG_NL as u8) { break } e = e + 1 }
270 var o: i64 = 0
271 while st < e { out[o] = line[st]; o = o + 1; st = st + 1 }
272 out[o] = 0 as u8
273 return o
274}
275// find the line beginning `to|<idx>|` in a commit/welcome buffer; copy its ct||mac blob (64B) out; 0 ok / -1 absent
276func mg_find_wrap(buf: *u8, n: i64, idx: i64, blob: *u8) -> i64 {
277 let pre: *u8 = sys_mmap(32)
278 var po: i64 = mg_cat(pre, 0, "to|" as *u8)
279 po = mg_catn(pre, po, idx)
280 pre[po] = MG_PIPE as u8
281 po = po + 1
282 pre[po] = 0 as u8
283 let pl: i64 = mg_len(pre)
284 var i: i64 = 0
285 while i < n {
286 var atl: i64 = 1
287 if i == 0 { atl = 1 } else { if buf[i-1] == (MG_NL as u8) { atl = 1 } else { atl = 0 } }
288 if atl == 1 {
289 var m: i64 = 1
290 var k: i64 = 0
291 while k < pl { if i + k >= n { m = 0; break } if buf[i+k] != pre[k] { m = 0; break } k = k + 1 }
292 if m == 1 {
293 // field 2 is the hex blob (128 hex chars)
294 var s: i64 = i + pl
295 var e: i64 = s
296 while e < n { if buf[e] == (MG_PIPE as u8) { break } if buf[e] == (MG_NL as u8) { break } e = e + 1 }
297 if e - s != (MG_KEY + MG_MAC) * 2 { return 0 - 1 }
298 if mg_hexd((buf as i64 + s) as *u8, e - s, blob) != (MG_KEY + MG_MAC) { return 0 - 1 }
299 return 0
300 }
301 }
302 i = i + 1
303 }
304 return 0 - 1
305}
306
307// ==== VERBS =====================================================================================
308func mg_keygen(priv_out: *u8, seed_hex: *u8) -> i64 {
309 let seed: *u8 = sys_mmap(MG_HEXCAP)
310 let sl: i64 = mg_hexd(seed_hex, mg_len(seed_hex), seed)
311 if sl < 0 { mg_w("MLS-REFUSED bad-hex seed\n" as *u8) return MG_EXIT_REFUSED }
312 let priv: *u8 = sys_mmap(MG_KEY)
313 sha256_digest(seed, sl, priv)
314 mg_clamp(priv)
315 let ph: *u8 = sys_mmap(MG_HEXCAP)
316 mg_hexe(priv, MG_KEY, ph)
317 if mg_write(priv_out, ph, MG_KEY * 2) != 0 { mg_w("MLS-RED priv-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
318 let pub: *u8 = sys_mmap(MG_KEY)
319 mg_pub(priv, pub)
320 let pubh: *u8 = sys_mmap(MG_HEXCAP)
321 mg_hexe(pub, MG_KEY, pubh)
322 mg_w("MLS-KEYGEN priv=" as *u8)
323 mg_w(priv_out)
324 mg_w(" PUB " as *u8)
325 mg_w(pubh)
326 mg_w("\n" as *u8)
327 return MG_EXIT_OK
328}
329func mg_pubverb(priv_file: *u8) -> i64 {
330 let ph: *u8 = sys_mmap(MG_HEXCAP)
331 if mg_read(priv_file, ph, MG_HEXCAP) < MG_KEY * 2 { mg_w("MLS-REFUSED priv-file-short\n" as *u8) return MG_EXIT_REFUSED }
332 let priv: *u8 = sys_mmap(MG_KEY)
333 if mg_hexd(ph, MG_KEY * 2, priv) != MG_KEY { mg_w("MLS-REFUSED bad-priv-hex\n" as *u8) return MG_EXIT_REFUSED }
334 let pub: *u8 = sys_mmap(MG_KEY)
335 mg_pub(priv, pub)
336 let pubh: *u8 = sys_mmap(MG_HEXCAP)
337 mg_hexe(pub, MG_KEY, pubh)
338 mg_w("PUB " as *u8)
339 mg_w(pubh)
340 mg_w("\n" as *u8)
341 return MG_EXIT_OK
342}
343// create <group_id> <epoch_seed_hex> <eph_priv_hex> <welcome_out> <roster_csv>
344// founder wraps epoch_secret_0 to every roster member; writes welcome; prints its own state is a join.
345func mg_create(group_id: *u8, seed_hex: *u8, eph_hex: *u8, welcome_out: *u8, roster: *u8) -> i64 {
346 let seed: *u8 = sys_mmap(MG_HEXCAP)
347 let sl: i64 = mg_hexd(seed_hex, mg_len(seed_hex), seed)
348 if sl < 0 { mg_w("MLS-REFUSED bad-hex seed\n" as *u8) return MG_EXIT_REFUSED }
349 let es0: *u8 = sys_mmap(MG_KEY)
350 let sb: *u8 = sys_mmap(MG_HEXCAP)
351 var o: i64 = mg_cat(sb, 0, "mls-init|" as *u8)
352 var i: i64 = 0
353 while i < sl { sb[o] = seed[i]; o = o + 1; i = i + 1 }
354 sha256_digest(sb, o, es0)
355 let eph: *u8 = sys_mmap(MG_KEY)
356 if mg_hexd(eph_hex, mg_len(eph_hex), eph) != MG_KEY { mg_w("MLS-REFUSED bad-eph-hex (need 64)\n" as *u8) return MG_EXIT_REFUSED }
357 mg_clamp(eph)
358 let ephpub: *u8 = sys_mmap(MG_KEY)
359 mg_pub(eph, ephpub)
360 let ephpubh: *u8 = sys_mmap(MG_HEXCAP)
361 mg_hexe(ephpub, MG_KEY, ephpubh)
362 let wb: *u8 = sys_mmap(MG_FILECAP)
363 var wo: i64 = mg_cat(wb, 0, "welcome|0|" as *u8)
364 wo = mg_cat(wb, wo, ephpubh)
365 wb[wo] = MG_PIPE as u8
366 wo = wo + 1
367 wo = mg_cat(wb, wo, group_id)
368 wb[wo] = MG_PIPE as u8
369 wo = wo + 1
370 wo = mg_cat(wb, wo, roster)
371 wb[wo] = MG_NL as u8
372 wo = wo + 1
373 // wrap es0 to each roster member
374 let rl: i64 = mg_len(roster)
375 var idx: i64 = 0
376 i = 0
377 while i < rl {
378 var s: i64 = i
379 while i < rl { if roster[i] == (MG_COMMA as u8) { break } i = i + 1 }
380 let mpub: *u8 = sys_mmap(MG_KEY)
381 if mg_hexd((roster as i64 + s) as *u8, i - s, mpub) != MG_KEY { mg_w("MLS-REFUSED bad-roster-pub\n" as *u8) return MG_EXIT_REFUSED }
382 let blob: *u8 = sys_mmap(MG_KEY + MG_MAC)
383 mg_wrap(eph, mpub, es0, blob)
384 let bh: *u8 = sys_mmap(MG_HEXCAP)
385 mg_hexe(blob, MG_KEY + MG_MAC, bh)
386 wo = mg_cat(wb, wo, "to|" as *u8)
387 wo = mg_catn(wb, wo, idx)
388 wb[wo] = MG_PIPE as u8
389 wo = wo + 1
390 wo = mg_cat(wb, wo, bh)
391 wb[wo] = MG_NL as u8
392 wo = wo + 1
393 idx = idx + 1
394 i = i + 1
395 }
396 if mg_write(welcome_out, wb, wo) != 0 { mg_w("MLS-RED welcome-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
397 mg_w("MLS-CREATE-OK group=" as *u8)
398 mg_w(group_id)
399 mg_w(" epoch=0 members=" as *u8)
400 mg_num(idx)
401 mg_w(" welcome=" as *u8)
402 mg_w(welcome_out)
403 mg_w("\n" as *u8)
404 return MG_EXIT_OK
405}
406// join <welcome_file> <member_priv_file> <state_out>
407func mg_join(welcome_file: *u8, priv_file: *u8, state_out: *u8) -> i64 {
408 let wb: *u8 = sys_mmap(MG_FILECAP)
409 let wn: i64 = mg_read(welcome_file, wb, MG_FILECAP)
410 if wn <= 0 { mg_w("MLS-REFUSED welcome-absent\n" as *u8) return MG_EXIT_REFUSED }
411 let ph: *u8 = sys_mmap(MG_HEXCAP)
412 if mg_read(priv_file, ph, MG_HEXCAP) < MG_KEY * 2 { mg_w("MLS-REFUSED priv-file-short\n" as *u8) return MG_EXIT_REFUSED }
413 let priv: *u8 = sys_mmap(MG_KEY)
414 if mg_hexd(ph, MG_KEY * 2, priv) != MG_KEY { mg_w("MLS-REFUSED bad-priv-hex\n" as *u8) return MG_EXIT_REFUSED }
415 let mypub: *u8 = sys_mmap(MG_KEY)
416 mg_pub(priv, mypub)
417 let mypubh: *u8 = sys_mmap(MG_HEXCAP)
418 mg_hexe(mypub, MG_KEY, mypubh)
419 // header line: welcome|0|<ephpubh>|<group>|<roster>
420 let ephh: *u8 = sys_mmap(MG_HEXCAP)
421 let roster: *u8 = sys_mmap(MG_FILECAP)
422 let gid: *u8 = sys_mmap(MG_HEXCAP)
423 mg_field(wb, wn, 2, ephh)
424 mg_field(wb, wn, 3, gid)
425 mg_field(wb, wn, 4, roster)
426 let eph_pub: *u8 = sys_mmap(MG_KEY)
427 if mg_hexd(ephh, mg_len(ephh), eph_pub) != MG_KEY { mg_w("MLS-CORRUPT bad-eph in welcome\n" as *u8) return MG_EXIT_CORRUPT }
428 let idx: i64 = mg_roster_idx(roster, mypubh)
429 if idx < 0 { mg_w("MLS-REFUSED not-in-roster -- this welcome is not addressed to you\n" as *u8) return MG_EXIT_REFUSED }
430 let blob: *u8 = sys_mmap(MG_KEY + MG_MAC)
431 if mg_find_wrap(wb, wn, idx, blob) != 0 { mg_w("MLS-REFUSED no-wrap-for-index\n" as *u8) return MG_EXIT_REFUSED }
432 let es0: *u8 = sys_mmap(MG_KEY)
433 if mg_unwrap(priv, eph_pub, blob, es0) != 0 { mg_w("MLS-REFUSED mac-fail (tampered welcome or wrong key)\n" as *u8) return MG_EXIT_REFUSED }
434 // state file: <epoch>\n<group>\n<roster>\n<secret_hex>\n
435 let stb: *u8 = sys_mmap(MG_FILECAP)
436 var so: i64 = mg_cat(stb, 0, "0\n" as *u8)
437 so = mg_cat(stb, so, gid)
438 stb[so] = MG_NL as u8
439 so = so + 1
440 so = mg_cat(stb, so, roster)
441 stb[so] = MG_NL as u8
442 so = so + 1
443 let esh: *u8 = sys_mmap(MG_HEXCAP)
444 mg_hexe(es0, MG_KEY, esh)
445 so = mg_cat(stb, so, esh)
446 stb[so] = MG_NL as u8
447 so = so + 1
448 if mg_write(state_out, stb, so) != 0 { mg_w("MLS-RED state-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
449 let ch: *u8 = sys_mmap(MG_KEY)
450 mg_commit_hash(es0, ch)
451 let chh: *u8 = sys_mmap(MG_HEXCAP)
452 mg_hexe(ch, MG_KEY, chh)
453 mg_w("MLS-JOIN-OK member_idx=" as *u8)
454 mg_num(idx)
455 mg_w(" epoch=0 commit=" as *u8)
456 mg_w(chh)
457 mg_w("\n" as *u8)
458 return MG_EXIT_OK
459}
460// read a state file into (epoch, gid, roster, secret)
461func mg_state_line(buf: *u8, n: i64, which: i64, out: *u8) -> i64 {
462 var ln: i64 = 0
463 var i: i64 = 0
464 var s: i64 = 0
465 while i < n {
466 if buf[i] == (MG_NL as u8) {
467 if ln == which { var o: i64 = 0; while s < i { out[o] = buf[s]; o = o + 1; s = s + 1 } out[o] = 0 as u8; return o }
468 ln = ln + 1
469 s = i + 1
470 }
471 i = i + 1
472 }
473 return 0 - 1
474}
475// commit <state_file> <eph_priv_hex> <new_roster_csv> <commit_out> <new_state_out>
476func mg_commit(state_file: *u8, eph_hex: *u8, new_roster: *u8, commit_out: *u8, new_state_out: *u8) -> i64 {
477 let stb: *u8 = sys_mmap(MG_FILECAP)
478 let sn: i64 = mg_read(state_file, stb, MG_FILECAP)
479 if sn <= 0 { mg_w("MLS-REFUSED state-absent\n" as *u8) return MG_EXIT_REFUSED }
480 let epa: *u8 = sys_mmap(64)
481 let gid: *u8 = sys_mmap(MG_HEXCAP)
482 let esh: *u8 = sys_mmap(MG_HEXCAP)
483 mg_state_line(stb, sn, 0, epa)
484 mg_state_line(stb, sn, 1, gid)
485 mg_state_line(stb, sn, 3, esh)
486 var ep: i64 = 0
487 var z: i64 = 0
488 while epa[z] != (0 as u8) { ep = ep * 10 + (epa[z] as i64 - 48); z = z + 1 }
489 let old: *u8 = sys_mmap(MG_KEY)
490 if mg_hexd(esh, mg_len(esh), old) != MG_KEY { mg_w("MLS-CORRUPT bad-secret in state\n" as *u8) return MG_EXIT_CORRUPT }
491 let eph: *u8 = sys_mmap(MG_KEY)
492 if mg_hexd(eph_hex, mg_len(eph_hex), eph) != MG_KEY { mg_w("MLS-REFUSED bad-eph-hex (need 64)\n" as *u8) return MG_EXIT_REFUSED }
493 mg_clamp(eph)
494 // fresh commit_secret (deterministic from eph+old for the gate; prod eph is CSPRNG)
495 let cs: *u8 = sys_mmap(MG_KEY)
496 let csb: *u8 = sys_mmap(MG_BLOCK + MG_KEY)
497 var co: i64 = mg_cat(csb, 0, "commit-secret" as *u8)
498 var j: i64 = 0
499 while j < MG_KEY { csb[co] = eph[j]; co = co + 1; j = j + 1 }
500 j = 0
501 while j < MG_KEY { csb[co] = old[j]; co = co + 1; j = j + 1 }
502 sha256_digest(csb, co, cs)
503 let newep: i64 = ep + 1
504 let ns: *u8 = sys_mmap(MG_KEY)
505 mls_epoch_advance(old, cs, gid, newep, new_roster, ns)
506 // wrap cs to each NEW-roster member
507 let ephpub: *u8 = sys_mmap(MG_KEY)
508 mg_pub(eph, ephpub)
509 let ephpubh: *u8 = sys_mmap(MG_HEXCAP)
510 mg_hexe(ephpub, MG_KEY, ephpubh)
511 let cb: *u8 = sys_mmap(MG_FILECAP)
512 var cbo: i64 = mg_cat(cb, 0, "commit|" as *u8)
513 cbo = mg_catn(cb, cbo, newep)
514 cb[cbo] = MG_PIPE as u8
515 cbo = cbo + 1
516 cbo = mg_cat(cb, cbo, ephpubh)
517 cb[cbo] = MG_PIPE as u8
518 cbo = cbo + 1
519 cbo = mg_cat(cb, cbo, gid)
520 cb[cbo] = MG_PIPE as u8
521 cbo = cbo + 1
522 cbo = mg_cat(cb, cbo, new_roster)
523 cb[cbo] = MG_NL as u8
524 cbo = cbo + 1
525 let rl: i64 = mg_len(new_roster)
526 var idx: i64 = 0
527 var i: i64 = 0
528 while i < rl {
529 var s: i64 = i
530 while i < rl { if new_roster[i] == (MG_COMMA as u8) { break } i = i + 1 }
531 let mpub: *u8 = sys_mmap(MG_KEY)
532 if mg_hexd((new_roster as i64 + s) as *u8, i - s, mpub) != MG_KEY { mg_w("MLS-REFUSED bad-roster-pub\n" as *u8) return MG_EXIT_REFUSED }
533 let blob: *u8 = sys_mmap(MG_KEY + MG_MAC)
534 mg_wrap(eph, mpub, cs, blob)
535 let bh: *u8 = sys_mmap(MG_HEXCAP)
536 mg_hexe(blob, MG_KEY + MG_MAC, bh)
537 cbo = mg_cat(cb, cbo, "to|" as *u8)
538 cbo = mg_catn(cb, cbo, idx)
539 cb[cbo] = MG_PIPE as u8
540 cbo = cbo + 1
541 cbo = mg_cat(cb, cbo, bh)
542 cb[cbo] = MG_NL as u8
543 cbo = cbo + 1
544 idx = idx + 1
545 i = i + 1
546 }
547 if mg_write(commit_out, cb, cbo) != 0 { mg_w("MLS-RED commit-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
548 // committer's own new state
549 let outst: *u8 = sys_mmap(MG_FILECAP)
550 var oo: i64 = mg_catn(outst, 0, newep)
551 outst[oo] = MG_NL as u8
552 oo = oo + 1
553 oo = mg_cat(outst, oo, gid)
554 outst[oo] = MG_NL as u8
555 oo = oo + 1
556 oo = mg_cat(outst, oo, new_roster)
557 outst[oo] = MG_NL as u8
558 oo = oo + 1
559 let nsh: *u8 = sys_mmap(MG_HEXCAP)
560 mg_hexe(ns, MG_KEY, nsh)
561 oo = mg_cat(outst, oo, nsh)
562 outst[oo] = MG_NL as u8
563 oo = oo + 1
564 if mg_write(new_state_out, outst, oo) != 0 { mg_w("MLS-RED newstate-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
565 let ch: *u8 = sys_mmap(MG_KEY)
566 mg_commit_hash(ns, ch)
567 let chh: *u8 = sys_mmap(MG_HEXCAP)
568 mg_hexe(ch, MG_KEY, chh)
569 mg_w("MLS-COMMIT-OK epoch=" as *u8)
570 mg_num(newep)
571 mg_w(" new_members=" as *u8)
572 mg_num(idx)
573 mg_w(" commit=" as *u8)
574 mg_w(chh)
575 mg_w("\n" as *u8)
576 return MG_EXIT_OK
577}
578// apply <commit_file> <member_priv_file> <old_state_file> <new_state_out>
579func mg_apply(commit_file: *u8, priv_file: *u8, old_state_file: *u8, new_state_out: *u8) -> i64 {
580 let cb: *u8 = sys_mmap(MG_FILECAP)
581 let cn: i64 = mg_read(commit_file, cb, MG_FILECAP)
582 if cn <= 0 { mg_w("MLS-REFUSED commit-absent\n" as *u8) return MG_EXIT_REFUSED }
583 let ph: *u8 = sys_mmap(MG_HEXCAP)
584 if mg_read(priv_file, ph, MG_HEXCAP) < MG_KEY * 2 { mg_w("MLS-REFUSED priv-file-short\n" as *u8) return MG_EXIT_REFUSED }
585 let priv: *u8 = sys_mmap(MG_KEY)
586 if mg_hexd(ph, MG_KEY * 2, priv) != MG_KEY { mg_w("MLS-REFUSED bad-priv-hex\n" as *u8) return MG_EXIT_REFUSED }
587 let mypub: *u8 = sys_mmap(MG_KEY)
588 mg_pub(priv, mypub)
589 let mypubh: *u8 = sys_mmap(MG_HEXCAP)
590 mg_hexe(mypub, MG_KEY, mypubh)
591 // commit header: commit|<ep>|<ephpubh>|<gid>|<roster>
592 let epa: *u8 = sys_mmap(64)
593 let ephh: *u8 = sys_mmap(MG_HEXCAP)
594 let gid: *u8 = sys_mmap(MG_HEXCAP)
595 let roster: *u8 = sys_mmap(MG_FILECAP)
596 mg_field(cb, cn, 1, epa)
597 mg_field(cb, cn, 2, ephh)
598 mg_field(cb, cn, 3, gid)
599 mg_field(cb, cn, 4, roster)
600 var newep: i64 = 0
601 var z: i64 = 0
602 while epa[z] != (0 as u8) { newep = newep * 10 + (epa[z] as i64 - 48); z = z + 1 }
603 let eph_pub: *u8 = sys_mmap(MG_KEY)
604 if mg_hexd(ephh, mg_len(ephh), eph_pub) != MG_KEY { mg_w("MLS-CORRUPT bad-eph in commit\n" as *u8) return MG_EXIT_CORRUPT }
605 let idx: i64 = mg_roster_idx(roster, mypubh)
606 if idx < 0 {
607 mg_w("MLS-REMOVED cannot-advance -- you are absent from the new roster, no commit secret is addressed to you; holding the old epoch secret gives you NOTHING for epoch " as *u8)
608 mg_num(newep)
609 mg_w(" (this is the removal negative control succeeding)\n" as *u8)
610 return MG_EXIT_REFUSED
611 }
612 let blob: *u8 = sys_mmap(MG_KEY + MG_MAC)
613 if mg_find_wrap(cb, cn, idx, blob) != 0 { mg_w("MLS-REFUSED no-wrap-for-index\n" as *u8) return MG_EXIT_REFUSED }
614 let cs: *u8 = sys_mmap(MG_KEY)
615 if mg_unwrap(priv, eph_pub, blob, cs) != 0 { mg_w("MLS-REFUSED mac-fail (tampered commit or wrong key)\n" as *u8) return MG_EXIT_REFUSED }
616 // old secret
617 let stb: *u8 = sys_mmap(MG_FILECAP)
618 let sn: i64 = mg_read(old_state_file, stb, MG_FILECAP)
619 if sn <= 0 { mg_w("MLS-REFUSED old-state-absent\n" as *u8) return MG_EXIT_REFUSED }
620 let esh: *u8 = sys_mmap(MG_HEXCAP)
621 mg_state_line(stb, sn, 3, esh)
622 let old: *u8 = sys_mmap(MG_KEY)
623 if mg_hexd(esh, mg_len(esh), old) != MG_KEY { mg_w("MLS-CORRUPT bad-secret in old state\n" as *u8) return MG_EXIT_CORRUPT }
624 let ns: *u8 = sys_mmap(MG_KEY)
625 mls_epoch_advance(old, cs, gid, newep, roster, ns)
626 let outst: *u8 = sys_mmap(MG_FILECAP)
627 var oo: i64 = mg_catn(outst, 0, newep)
628 outst[oo] = MG_NL as u8
629 oo = oo + 1
630 oo = mg_cat(outst, oo, gid)
631 outst[oo] = MG_NL as u8
632 oo = oo + 1
633 oo = mg_cat(outst, oo, roster)
634 outst[oo] = MG_NL as u8
635 oo = oo + 1
636 let nsh: *u8 = sys_mmap(MG_HEXCAP)
637 mg_hexe(ns, MG_KEY, nsh)
638 oo = mg_cat(outst, oo, nsh)
639 outst[oo] = MG_NL as u8
640 oo = oo + 1
641 if mg_write(new_state_out, outst, oo) != 0 { mg_w("MLS-RED newstate-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
642 let ch: *u8 = sys_mmap(MG_KEY)
643 mg_commit_hash(ns, ch)
644 let chh: *u8 = sys_mmap(MG_HEXCAP)
645 mg_hexe(ch, MG_KEY, chh)
646 mg_w("MLS-APPLY-OK member_idx=" as *u8)
647 mg_num(idx)
648 mg_w(" epoch=" as *u8)
649 mg_num(newep)
650 mg_w(" commit=" as *u8)
651 mg_w(chh)
652 mg_w("\n" as *u8)
653 return MG_EXIT_OK
654}
655func mg_epochverb(state_file: *u8) -> i64 {
656 let stb: *u8 = sys_mmap(MG_FILECAP)
657 let sn: i64 = mg_read(state_file, stb, MG_FILECAP)
658 if sn <= 0 { mg_w("MLS-REFUSED state-absent\n" as *u8) return MG_EXIT_REFUSED }
659 let epa: *u8 = sys_mmap(64)
660 let esh: *u8 = sys_mmap(MG_HEXCAP)
661 mg_state_line(stb, sn, 0, epa)
662 mg_state_line(stb, sn, 3, esh)
663 let es: *u8 = sys_mmap(MG_KEY)
664 if mg_hexd(esh, mg_len(esh), es) != MG_KEY { mg_w("MLS-CORRUPT bad-secret\n" as *u8) return MG_EXIT_CORRUPT }
665 let ch: *u8 = sys_mmap(MG_KEY)
666 mg_commit_hash(es, ch)
667 let chh: *u8 = sys_mmap(MG_HEXCAP)
668 mg_hexe(ch, MG_KEY, chh)
669 mg_w("EPOCH " as *u8)
670 mg_w(epa)
671 mg_w(" COMMIT " as *u8)
672 mg_w(chh)
673 mg_w("\n" as *u8)
674 return MG_EXIT_OK
675}
676// derive frame key + nonce from epoch_secret and seq
677func mg_framekey(es: *u8, seq: i64, fk: *u8, nonce: *u8) -> i64 {
678 let sb: *u8 = sys_mmap(16)
679 let sl: i64 = mg_catn(sb, 0, seq)
680 mg_derive(es, "app" as *u8, sb, sl, fk)
681 let nb: *u8 = sys_mmap(MG_KEY)
682 mg_derive(es, "nonce" as *u8, sb, sl, nb)
683 var i: i64 = 0
684 while i < MG_NONCE { nonce[i] = nb[i]; i = i + 1 }
685 return 0
686}
687// seal <state_file> <seq> <plaintext> <frame_out>
688func mg_seal(state_file: *u8, seq: i64, plaintext: *u8, frame_out: *u8) -> i64 {
689 let stb: *u8 = sys_mmap(MG_FILECAP)
690 let sn: i64 = mg_read(state_file, stb, MG_FILECAP)
691 if sn <= 0 { mg_w("MLS-REFUSED state-absent\n" as *u8) return MG_EXIT_REFUSED }
692 let epa: *u8 = sys_mmap(64)
693 let esh: *u8 = sys_mmap(MG_HEXCAP)
694 mg_state_line(stb, sn, 0, epa)
695 mg_state_line(stb, sn, 3, esh)
696 let es: *u8 = sys_mmap(MG_KEY)
697 if mg_hexd(esh, mg_len(esh), es) != MG_KEY { mg_w("MLS-CORRUPT bad-secret\n" as *u8) return MG_EXIT_CORRUPT }
698 let fk: *u8 = sys_mmap(MG_KEY)
699 let nz: *u8 = sys_mmap(MG_NONCE)
700 mg_framekey(es, seq, fk, nz)
701 let pl: i64 = mg_len(plaintext)
702 let ct: *u8 = sys_mmap(pl + 16)
703 chacha20_encrypt(fk, 0, nz, plaintext, pl, ct)
704 let mac: *u8 = sys_mmap(MG_MAC)
705 mg_hmac(fk, MG_KEY, ct, pl, mac)
706 let fb: *u8 = sys_mmap(MG_FILECAP)
707 var fo: i64 = mg_cat(fb, 0, "frame|" as *u8)
708 fo = mg_cat(fb, fo, epa)
709 fb[fo] = MG_PIPE as u8
710 fo = fo + 1
711 fo = mg_catn(fb, fo, seq)
712 fb[fo] = MG_PIPE as u8
713 fo = fo + 1
714 let cth: *u8 = sys_mmap(pl * 2 + 4)
715 mg_hexe(ct, pl, cth)
716 fo = mg_cat(fb, fo, cth)
717 fb[fo] = MG_PIPE as u8
718 fo = fo + 1
719 let mh: *u8 = sys_mmap(MG_HEXCAP)
720 mg_hexe(mac, MG_MAC, mh)
721 fo = mg_cat(fb, fo, mh)
722 fb[fo] = MG_NL as u8
723 fo = fo + 1
724 if mg_write(frame_out, fb, fo) != 0 { mg_w("MLS-RED frame-write-failed\n" as *u8) return MG_EXIT_CORRUPT }
725 mg_w("MLS-SEAL-OK epoch=" as *u8)
726 mg_w(epa)
727 mg_w(" seq=" as *u8)
728 mg_num(seq)
729 mg_w(" bytes=" as *u8)
730 mg_num(pl)
731 mg_w("\n" as *u8)
732 return MG_EXIT_OK
733}
734// open <state_file> <frame_file>
735func mg_open(state_file: *u8, frame_file: *u8) -> i64 {
736 let fb: *u8 = sys_mmap(MG_FILECAP)
737 let fn: i64 = mg_read(frame_file, fb, MG_FILECAP)
738 if fn <= 0 { mg_w("MLS-REFUSED frame-absent\n" as *u8) return MG_EXIT_REFUSED }
739 let seqa: *u8 = sys_mmap(64)
740 let cth: *u8 = sys_mmap(MG_FILECAP)
741 let mh: *u8 = sys_mmap(MG_HEXCAP)
742 mg_field(fb, fn, 2, seqa)
743 mg_field(fb, fn, 3, cth)
744 mg_field(fb, fn, 4, mh)
745 var seq: i64 = 0
746 var z: i64 = 0
747 while seqa[z] != (0 as u8) { seq = seq * 10 + (seqa[z] as i64 - 48); z = z + 1 }
748 let stb: *u8 = sys_mmap(MG_FILECAP)
749 let sn: i64 = mg_read(state_file, stb, MG_FILECAP)
750 if sn <= 0 { mg_w("MLS-REFUSED state-absent\n" as *u8) return MG_EXIT_REFUSED }
751 let esh: *u8 = sys_mmap(MG_HEXCAP)
752 mg_state_line(stb, sn, 3, esh)
753 let es: *u8 = sys_mmap(MG_KEY)
754 if mg_hexd(esh, mg_len(esh), es) != MG_KEY { mg_w("MLS-CORRUPT bad-secret\n" as *u8) return MG_EXIT_CORRUPT }
755 let fk: *u8 = sys_mmap(MG_KEY)
756 let nz: *u8 = sys_mmap(MG_NONCE)
757 mg_framekey(es, seq, fk, nz)
758 let ctlen_hex: i64 = mg_len(cth)
759 let ct: *u8 = sys_mmap(ctlen_hex / 2 + 4)
760 let ctlen: i64 = mg_hexd(cth, ctlen_hex, ct)
761 if ctlen < 0 { mg_w("MLS-CORRUPT bad-ct-hex\n" as *u8) return MG_EXIT_CORRUPT }
762 let mac: *u8 = sys_mmap(MG_MAC)
763 mg_hmac(fk, MG_KEY, ct, ctlen, mac)
764 let gotmac: *u8 = sys_mmap(MG_MAC)
765 if mg_hexd(mh, mg_len(mh), gotmac) != MG_MAC { mg_w("MLS-CORRUPT bad-mac-hex\n" as *u8) return MG_EXIT_CORRUPT }
766 if mg_memeq(mac, gotmac, MG_MAC) == 0 {
767 mg_w("MLS-OPEN-DENIED mac-fail -- this epoch secret cannot open this frame (wrong epoch, removed member, or tamper). Nothing decrypted.\n" as *u8)
768 return MG_EXIT_REFUSED
769 }
770 let pt: *u8 = sys_mmap(ctlen + 4)
771 chacha20_encrypt(fk, 0, nz, ct, ctlen, pt)
772 mg_w("MLS-OPEN-OK seq=" as *u8)
773 mg_num(seq)
774 mg_w(" plaintext=" as *u8)
775 mg_wn(pt, ctlen)
776 mg_w("\n" as *u8)
777 return MG_EXIT_OK
778}
779
780func mg_usage() -> i64 {
781 mg_w("usage: nx_mls_group keygen <priv_out> <seed_hex> | pub <priv_file> | create <group_id> <epoch_seed_hex> <eph_priv_hex> <welcome_out> <roster_csv> | join <welcome_file> <priv_file> <state_out> | commit <state_file> <eph_priv_hex> <new_roster_csv> <commit_out> <new_state_out> | apply <commit_file> <priv_file> <old_state_file> <new_state_out> | epoch <state_file> | seal <state_file> <seq> <plaintext> <frame_out> | open <state_file> <frame_file>\n" as *u8)
782 return MG_EXIT_USAGE
783}
784func main(argc: i64, argv: *i64) -> i64 {
785 if argc < 2 { let u: i64 = mg_usage() sys_exit(u) return u }
786 let v: *u8 = argv[1] as *u8
787 if mg_eq(v, "keygen" as *u8) == 1 { if argc < 4 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_keygen(argv[2] as *u8, argv[3] as *u8) sys_exit(r) return r }
788 if mg_eq(v, "pub" as *u8) == 1 { if argc < 3 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_pubverb(argv[2] as *u8) sys_exit(r) return r }
789 if mg_eq(v, "create" as *u8) == 1 { if argc < 7 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_create(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) sys_exit(r) return r }
790 if mg_eq(v, "join" as *u8) == 1 { if argc < 5 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_join(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8) sys_exit(r) return r }
791 if mg_eq(v, "commit" as *u8) == 1 { if argc < 7 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_commit(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) sys_exit(r) return r }
792 if mg_eq(v, "apply" as *u8) == 1 { if argc < 6 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_apply(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) sys_exit(r) return r }
793 if mg_eq(v, "epoch" as *u8) == 1 { if argc < 3 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_epochverb(argv[2] as *u8) sys_exit(r) return r }
794 if mg_eq(v, "seal" as *u8) == 1 { if argc < 6 { let u: i64 = mg_usage() sys_exit(u) return u } let s: *u8 = argv[3] as *u8; var sq: i64 = 0; var z: i64 = 0; while s[z] != (0 as u8) { sq = sq * 10 + (s[z] as i64 - 48); z = z + 1 } let r: i64 = mg_seal(argv[2] as *u8, sq, argv[4] as *u8, argv[5] as *u8) sys_exit(r) return r }
795 if mg_eq(v, "open" as *u8) == 1 { if argc < 4 { let u: i64 = mg_usage() sys_exit(u) return u } let r: i64 = mg_open(argv[2] as *u8, argv[3] as *u8) sys_exit(r) return r }
796 let u2: i64 = mg_usage()
797 sys_exit(u2)
798 return u2
799}