code wiki / (root) / nx_mls_group.nx

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}