@umbra-privacy/ceremony 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +187 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -337,7 +337,32 @@ var COMPUTING_MESSAGES = [
337
337
  "applying tau locally",
338
338
  "folding contribution proof",
339
339
  "blinding the trapdoor",
340
- "sealing the bellman response"
340
+ "sealing the bellman response",
341
+ "asking the curve where it lives",
342
+ "weaving G1 points into the new delta",
343
+ "negotiating with the pairing engine",
344
+ "rotating the keypair commitment",
345
+ "checking that tau is non-zero (just in case)",
346
+ "evaluating polynomials over your secret",
347
+ "scrubbing the entropy buffer mid-flight",
348
+ "ratifying the contribution sequence",
349
+ "computing s \xB7 \u03C4 in G1",
350
+ "computing t \xB7 \u03C4 in G2",
351
+ "running the signature-of-knowledge",
352
+ "binding the transcript to your contribution",
353
+ "double-checking nothing is at infinity",
354
+ "compressing the response footprint",
355
+ "verifying your math one more time",
356
+ "stamping the public key bundle",
357
+ "writing the response file to disk",
358
+ "preparing to hand the response back to the worker",
359
+ "almost done \u2014 finalising your tau",
360
+ "this is the cryptography working hard for you",
361
+ "your secret never leaves this machine",
362
+ "if your power-21 circuit feels slow, blame BN254",
363
+ "snarkjs is single-threaded, give it a moment",
364
+ "trusted setup ceremonies are a team sport",
365
+ "the worker will verify everything you did"
341
366
  ];
342
367
  var VERIFYING_MESSAGES = [
343
368
  "worker pulling your response from S3",
@@ -346,20 +371,75 @@ var VERIFYING_MESSAGES = [
346
371
  "writing the next zkey",
347
372
  "uploading the new zkey",
348
373
  "anchoring contribution hash in the audit chain",
349
- "almost there \u2014 finalising your receipt"
374
+ "almost there \u2014 finalising your receipt",
375
+ "parsing your response's wire format",
376
+ "validating every G1 point is on the curve",
377
+ "validating every G2 point is in the right subgroup",
378
+ "rebuilding the transcript hash chain",
379
+ "running the knowledge proof pairing",
380
+ "verifying the new delta_g1 matches its evidence",
381
+ "verifying the new delta_g2 matches its evidence",
382
+ "checking the L array against your contribution",
383
+ "checking the H array against your contribution",
384
+ "deriving Fiat-Shamir scalars for the batched check",
385
+ "running the random-linear-combination same-ratio",
386
+ "confirming the vKey fields were not altered",
387
+ "sequencing the new zkey for the next contributor",
388
+ "this is the part where every byte gets re-checked",
389
+ "soundness of the whole ceremony rides on this step",
390
+ "exporting the new bellman params for the next round",
391
+ "writing your contribution into the ceremony transcript",
392
+ "publishing the new audit chain entry",
393
+ "any tampering would have failed by now",
394
+ "if you see this, your contribution is provably honest",
395
+ "computing your contribution receipt",
396
+ "the math agrees \u2014 wrapping things up",
397
+ "finalising the verified state in Postgres",
398
+ "you did the cryptographic work \u2014 we just had to check"
350
399
  ];
351
400
  var EXPORTING_MESSAGES = [
352
401
  "preparing your challenge file",
353
402
  "extracting bellman params from the current zkey",
354
403
  "wrapping the zkey for handoff",
355
404
  "stamping a sha256 over the challenge bundle",
356
- "presigning your download URL"
405
+ "presigning your download URL",
406
+ "fetching the latest verified state for this circuit",
407
+ "downloading the previous contribution from S3",
408
+ "checking the previous contribution's hash",
409
+ "carving out a personal challenge for your tau",
410
+ "the worker is queuing your slot",
411
+ "uploading the challenge so you can download it",
412
+ "rotating the worker's S3 connection",
413
+ "binding your challenge to the audit chain",
414
+ "stamping the challenge metadata in Postgres",
415
+ "AWS SDK is recycling connection pools",
416
+ "your slot timer starts the moment this finishes",
417
+ "writing the challenge to a fresh S3 key",
418
+ "double-checking the integrity hash",
419
+ "if this takes >1 min, S3 is having a bad time",
420
+ "Fargate is parsing the current bellman params",
421
+ "verifying the previous contribution's chain link",
422
+ "this step has no contributor-side analogue",
423
+ "the worker does the boring work so you don't have to",
424
+ "any second now \u2014 challenge is almost ready",
425
+ "encoding the challenge in Bellman wire format",
426
+ "making sure the file size matches the spec",
427
+ "presign URL is signed for 1 hour",
428
+ "your download will be tamper-evident",
429
+ "the challenge SHA-256 will be on the public transcript",
430
+ "patience \u2014 this is the longest server-side step",
431
+ "almost there \u2014 your turn is moments away"
357
432
  ];
358
433
 
359
434
  // src/components/QueueView.tsx
360
435
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
361
436
  var POLL_FAST_MS = 5e3;
362
437
  var POLL_SLOW_MS = 15e3;
438
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
439
+ "timed_out",
440
+ "failed",
441
+ "verified"
442
+ ]);
363
443
  function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }) {
364
444
  const [status, setStatus] = useState2(null);
365
445
  const [pollErr, setPollErr] = useState2(null);
@@ -381,6 +461,9 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
381
461
  onReady(s);
382
462
  return;
383
463
  }
464
+ if (TERMINAL_STATUSES.has(s.status)) {
465
+ return;
466
+ }
384
467
  const interval = s.queue_position <= 2 ? POLL_FAST_MS : POLL_SLOW_MS;
385
468
  timeoutRef.current = setTimeout(poll, interval);
386
469
  } catch (err) {
@@ -406,6 +489,9 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
406
489
  ] })
407
490
  ] });
408
491
  }
492
+ if (TERMINAL_STATUSES.has(status.status)) {
493
+ return /* @__PURE__ */ jsx2(TerminalSlotMessage, { status: status.status });
494
+ }
409
495
  const waitMins = Math.ceil((status.estimated_wait_secs ?? 0) / 60);
410
496
  const expiresAt = status.slot_expires_at ? new Date(status.slot_expires_at).toLocaleTimeString([], {
411
497
  hour: "2-digit",
@@ -458,6 +544,31 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
458
544
  ] })
459
545
  ] });
460
546
  }
547
+ function TerminalSlotMessage({ status }) {
548
+ const lines = {
549
+ timed_out: {
550
+ headline: "Your slot expired before the challenge file was ready.",
551
+ detail: "This is usually a transient S3 hiccup on the worker side, not a problem with your machine. Press B or Backspace to return to the track list and rejoin the queue \u2014 it almost always succeeds the second time."
552
+ },
553
+ failed: {
554
+ headline: "The worker reported a failure on this contribution.",
555
+ detail: "Press B or Backspace to return to the track list. If this happens repeatedly on the same track, the admin needs to investigate."
556
+ },
557
+ verified: {
558
+ headline: "This contribution is already recorded as verified.",
559
+ detail: "Press B or Backspace to return to the track list and pick another track."
560
+ }
561
+ };
562
+ const key = status;
563
+ const msg = lines[key] ?? {
564
+ headline: `Contribution ended with status: ${status}.`,
565
+ detail: "Press B or Backspace to return to the track list."
566
+ };
567
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", gap: 1, children: [
568
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: msg.headline }),
569
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: msg.detail })
570
+ ] });
571
+ }
461
572
  function ExportingMessage({ status }) {
462
573
  const exportingMsg = useCyclingMessage(EXPORTING_MESSAGES, 2500, status === "exporting");
463
574
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: status === "exporting" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
@@ -813,6 +924,16 @@ function InfoModal() {
813
924
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
814
925
  var NAME_MAX_LEN = 100;
815
926
  var NAME_VALID_RE = /^[\p{L}\p{N} _.\-]*$/u;
927
+ function elideMiddle(s, maxLen) {
928
+ if (s.length <= maxLen) return s;
929
+ const keepHead = Math.ceil((maxLen - 1) / 2);
930
+ const keepTail = Math.floor((maxLen - 1) / 2);
931
+ return s.slice(0, keepHead) + "\u2026" + s.slice(s.length - keepTail);
932
+ }
933
+ function copyToClipboardOSC52(value) {
934
+ const payload = Buffer.from(value, "utf8").toString("base64");
935
+ process.stdout.write(`\x1B]52;c;${payload}\x07`);
936
+ }
816
937
  function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName }) {
817
938
  const { exit } = useApp();
818
939
  const [activeCeremonyId, setActiveCeremonyId] = useState5(initialCeremonyId);
@@ -826,6 +947,8 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
826
947
  const [contributed, setContributed] = useState5({});
827
948
  const [selectedIdx, setSelectedIdx] = useState5(0);
828
949
  const [tab, setTab] = useState5(0);
950
+ const [contribCursor, setContribCursor] = useState5(0);
951
+ const [copyToast, setCopyToast] = useState5(null);
829
952
  const [showInfo, setShowInfo] = useState5(false);
830
953
  useEffect5(() => {
831
954
  if (!nameSet) return;
@@ -1046,6 +1169,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1046
1169
  const { tracks } = screen;
1047
1170
  if (key.tab) {
1048
1171
  setTab((t) => (t + 1) % 2);
1172
+ setContribCursor(0);
1049
1173
  return;
1050
1174
  }
1051
1175
  if (tab === 0) {
@@ -1062,6 +1186,30 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1062
1186
  if (t && t.status === "open" && !contributed[t.id]) joinTrack(t);
1063
1187
  return;
1064
1188
  }
1189
+ } else if (tab === 1) {
1190
+ const myContribs = Object.values(contributed).filter(
1191
+ (c) => c.ceremonyId === activeCeremonyId
1192
+ );
1193
+ if (key.upArrow) {
1194
+ setContribCursor((i) => Math.max(0, i - 1));
1195
+ return;
1196
+ }
1197
+ if (key.downArrow) {
1198
+ setContribCursor((i) => Math.min(myContribs.length - 1, i + 1));
1199
+ return;
1200
+ }
1201
+ if (q === "c") {
1202
+ const target = myContribs[contribCursor];
1203
+ if (target && target.contributionHash) {
1204
+ copyToClipboardOSC52(target.contributionHash);
1205
+ setCopyToast(`\u2713 Hash copied (round #${target.sequenceNumber})`);
1206
+ setTimeout(() => setCopyToast(null), 2e3);
1207
+ } else if (target) {
1208
+ setCopyToast("Nothing to copy \u2014 hash is still pending");
1209
+ setTimeout(() => setCopyToast(null), 2e3);
1210
+ }
1211
+ return;
1212
+ }
1065
1213
  }
1066
1214
  if (q === "r") {
1067
1215
  goHome();
@@ -1190,29 +1338,34 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1190
1338
  /* @__PURE__ */ jsx7(TabBar, {}),
1191
1339
  tab === 0 ? (
1192
1340
  // ── Dashboard tab ────────────────────────────────────────────────
1341
+ // CIRCUIT column is sized to fit the longest mainnet name
1342
+ // (`claim-deposit-into-confidential-amount-n4` = 41 chars). Names
1343
+ // longer than that are middle-elided so the disambiguating suffix
1344
+ // (-n1/-n2/-n4) stays visible — previously a hard slice(0,24)
1345
+ // showed every claim variant as `claim-deposit-into-confi..`.
1193
1346
  /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1194
1347
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1195
- " CIRCUIT".padEnd(30),
1196
- "TOTAL".padEnd(10),
1348
+ " CIRCUIT".padEnd(46),
1349
+ "TOTAL".padEnd(8),
1197
1350
  "QUEUE".padEnd(8),
1198
- "STATUS".padEnd(16),
1351
+ "STATUS".padEnd(14),
1199
1352
  "MY CONTRIBUTIONS"
1200
1353
  ] }),
1201
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(70) }),
1354
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
1202
1355
  tracks.map((t, i) => {
1203
1356
  const isSelected = i === selectedIdx;
1204
1357
  const canContribute = t.status === "open";
1205
- const nameClipped = t.circuit_name.length > 26 ? t.circuit_name.slice(0, 24) + ".." : t.circuit_name;
1358
+ const nameDisplay = elideMiddle(t.circuit_name, 42);
1206
1359
  const statusColor = t.status === "open" ? "green" : t.status === "finalized" ? "cyan" : "yellow";
1207
1360
  const myContrib = contributed[t.id];
1208
1361
  return /* @__PURE__ */ jsxs7(Box7, { children: [
1209
1362
  /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : canContribute ? void 0 : "gray", children: [
1210
1363
  isSelected ? "\u25B6 " : " ",
1211
- nameClipped.padEnd(28),
1212
- String(t.contribution_count).padEnd(10),
1364
+ nameDisplay.padEnd(44),
1365
+ String(t.contribution_count).padEnd(8),
1213
1366
  String(t.queue_depth).padEnd(8)
1214
1367
  ] }),
1215
- /* @__PURE__ */ jsx7(Text7, { color: statusColor, children: trackStatusLabel(t.status).padEnd(16) }),
1368
+ /* @__PURE__ */ jsx7(Text7, { color: statusColor, children: trackStatusLabel(t.status).padEnd(14) }),
1216
1369
  myContrib ? isSelected ? /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
1217
1370
  "\u2713 contributed (round #",
1218
1371
  myContrib.sequenceNumber,
@@ -1237,32 +1390,35 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
1237
1390
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Switch to Dashboard tab and press Enter on a circuit to contribute." })
1238
1391
  ] }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1239
1392
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1240
- " CIRCUIT".padEnd(28),
1241
- "ROUND".padEnd(8),
1242
- "HASH".padEnd(20),
1243
- "TIME"
1393
+ " CIRCUIT".padEnd(44),
1394
+ "ROUND".padEnd(7),
1395
+ "VERIFIED AT".padEnd(22)
1244
1396
  ] }),
1245
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(68) }),
1246
- myContributions.map((c, i) => /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1247
- /* @__PURE__ */ jsxs7(Box7, { children: [
1248
- /* @__PURE__ */ jsx7(Text7, { color: "green", children: " \u2713 " }),
1249
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: c.circuitName.padEnd(24) }),
1250
- /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "#" + c.sequenceNumber + " " }),
1251
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: c.contributionHash ? c.contributionHash.slice(0, 16) + "..." : "(pending)" })
1252
- ] }),
1253
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1254
- " ",
1255
- c.verifiedAt ? new Date(c.verifiedAt).toLocaleString() : ""
1256
- ] })
1257
- ] }, i)),
1258
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(68) }),
1259
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1397
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
1398
+ myContributions.map((c, i) => {
1399
+ const isSel = i === contribCursor;
1400
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1401
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1402
+ /* @__PURE__ */ jsx7(Text7, { color: isSel ? "cyan" : "green", children: isSel ? "\u25B6 " : " " }),
1403
+ /* @__PURE__ */ jsx7(Text7, { bold: isSel, children: elideMiddle(c.circuitName, 40).padEnd(42) }),
1404
+ /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: ("#" + c.sequenceNumber).padEnd(7) }),
1405
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: c.verifiedAt ? new Date(c.verifiedAt).toLocaleString() : "\u2014" })
1406
+ ] }),
1407
+ /* @__PURE__ */ jsx7(Box7, { paddingLeft: 4, children: /* @__PURE__ */ jsxs7(Text7, { color: isSel ? "cyan" : "gray", dimColor: !isSel, children: [
1408
+ "hash:",
1409
+ " ",
1410
+ c.contributionHash ? c.contributionHash : "(pending \u2014 verify still in flight)"
1411
+ ] }) })
1412
+ ] }, i);
1413
+ }),
1414
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + "\u2500".repeat(90) }),
1415
+ copyToast ? /* @__PURE__ */ jsx7(Text7, { color: "green", children: copyToast }) : /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1260
1416
  "Total: ",
1261
1417
  myContributions.length,
1262
1418
  " contribution",
1263
1419
  myContributions.length !== 1 ? "s" : "",
1264
1420
  " \xB7 ",
1265
- "Tab to switch \xB7 Q to quit"
1421
+ "\u2191/\u2193 select \xB7 C copy hash \xB7 Tab switch \xB7 Q quit"
1266
1422
  ] })
1267
1423
  ] }) })
1268
1424
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbra-privacy/ceremony",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Terminal UI for the Umbra Phase 2 trusted setup ceremony",
5
5
  "type": "module",
6
6
  "bin": {