@umbra-privacy/ceremony 0.2.5 → 0.2.6

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 +93 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/components/App.tsx
7
- import { useEffect as useEffect4, useState as useState4 } from "react";
7
+ import { useEffect as useEffect5, useState as useState5 } from "react";
8
8
  import { Box as Box7, Text as Text7, useApp, useInput as useInput2 } from "ink";
9
9
 
10
10
  // src/cleanup.ts
@@ -315,22 +315,61 @@ function Header({ ceremony, subtitle }) {
315
315
  }
316
316
 
317
317
  // src/components/QueueView.tsx
318
- import { useEffect, useRef, useState } from "react";
318
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
319
319
  import { Box as Box2, Text as Text2 } from "ink";
320
320
  import Spinner from "ink-spinner";
321
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
321
+
322
+ // src/hooks/useCyclingMessage.ts
323
+ import { useEffect, useState } from "react";
324
+ function useCyclingMessage(messages, intervalMs = 2500, active = true) {
325
+ const [idx, setIdx] = useState(0);
326
+ useEffect(() => {
327
+ if (!active || messages.length <= 1) return;
328
+ const t = setInterval(() => setIdx((i) => (i + 1) % messages.length), intervalMs);
329
+ return () => clearInterval(t);
330
+ }, [active, intervalMs, messages.length]);
331
+ return messages[idx] ?? messages[0] ?? "";
332
+ }
333
+ var COMPUTING_MESSAGES = [
334
+ "summoning fresh entropy",
335
+ "shuffling response bytes",
336
+ "tickling bn128 pairings",
337
+ "applying tau locally",
338
+ "folding contribution proof",
339
+ "blinding the trapdoor",
340
+ "sealing the bellman response"
341
+ ];
342
+ var VERIFYING_MESSAGES = [
343
+ "worker pulling your response from S3",
344
+ "importing bellman contribution into a new zkey",
345
+ "checking the contribution's pairing proof",
346
+ "writing the next zkey",
347
+ "uploading the new zkey",
348
+ "anchoring contribution hash in the audit chain",
349
+ "almost there \u2014 finalising your receipt"
350
+ ];
351
+ var EXPORTING_MESSAGES = [
352
+ "preparing your challenge file",
353
+ "extracting bellman params from the current zkey",
354
+ "wrapping the zkey for handoff",
355
+ "stamping a sha256 over the challenge bundle",
356
+ "presigning your download URL"
357
+ ];
358
+
359
+ // src/components/QueueView.tsx
360
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
322
361
  var POLL_FAST_MS = 5e3;
323
362
  var POLL_SLOW_MS = 15e3;
324
363
  function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }) {
325
- const [status, setStatus] = useState(null);
326
- const [pollErr, setPollErr] = useState(null);
327
- const [tick, setTick] = useState(0);
364
+ const [status, setStatus] = useState2(null);
365
+ const [pollErr, setPollErr] = useState2(null);
366
+ const [tick, setTick] = useState2(0);
328
367
  const timeoutRef = useRef(null);
329
- useEffect(() => {
368
+ useEffect2(() => {
330
369
  const id = setInterval(() => setTick((t) => (t + 1) % 4), 500);
331
370
  return () => clearInterval(id);
332
371
  }, []);
333
- useEffect(() => {
372
+ useEffect2(() => {
334
373
  let cancelled = false;
335
374
  async function poll() {
336
375
  try {
@@ -395,7 +434,7 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
395
434
  ] })
396
435
  ] })
397
436
  ] }),
398
- status.status === "exporting" || status.status === "your_turn" || status.status === "ready_to_download" ? /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(Text2, { color: "green", children: status.status === "exporting" ? "Preparing your challenge file \u2014 hang tight..." : "Challenge ready \u2014 loading contribution flow..." }) }) : status.active_since ? /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
437
+ status.status === "exporting" || status.status === "your_turn" || status.status === "ready_to_download" ? /* @__PURE__ */ jsx2(ExportingMessage, { status: status.status }) : status.active_since ? /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
399
438
  "Another contributor is active",
400
439
  expiresAt ? ` \xB7 slot expires at ${expiresAt}` : ""
401
440
  ] }) }) : status.queue_position > 1 ? (
@@ -419,9 +458,21 @@ function QueueView({ ceremonyId: ceremonyId2, trackId, token, onReady, onError }
419
458
  ] })
420
459
  ] });
421
460
  }
461
+ function ExportingMessage({ status }) {
462
+ const exportingMsg = useCyclingMessage(EXPORTING_MESSAGES, 2500, status === "exporting");
463
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: status === "exporting" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
464
+ /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
465
+ /* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
466
+ " ",
467
+ exportingMsg,
468
+ "\u2026"
469
+ ] }),
470
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Your slot is active. The worker is preparing the challenge file just for you." })
471
+ ] }) : /* @__PURE__ */ jsx2(Text2, { color: "green", children: "Challenge ready \u2014 loading contribution flow\u2026" }) });
472
+ }
422
473
 
423
474
  // src/components/EntropyCollector.tsx
424
- import { useRef as useRef2, useState as useState2 } from "react";
475
+ import { useRef as useRef2, useState as useState3 } from "react";
425
476
  import { Box as Box3, Text as Text3, useInput } from "ink";
426
477
 
427
478
  // src/entropy.ts
@@ -442,8 +493,8 @@ function buildEntropyFromKeystrokes(chars, timingsNs) {
442
493
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
443
494
  var TARGET = 20;
444
495
  function EntropyCollector({ onComplete, onError }) {
445
- const [count, setCount] = useState2(0);
446
- const [done, setDone] = useState2(false);
496
+ const [count, setCount] = useState3(0);
497
+ const [done, setDone] = useState3(false);
447
498
  const charsRef = useRef2([]);
448
499
  const timingsRef = useRef2([]);
449
500
  const lastRef = useRef2(process.hrtime.bigint());
@@ -511,7 +562,7 @@ function EntropyCollector({ onComplete, onError }) {
511
562
  }
512
563
 
513
564
  // src/components/ContributeFlow.tsx
514
- import { useEffect as useEffect3, useState as useState3 } from "react";
565
+ import { useEffect as useEffect4, useState as useState4 } from "react";
515
566
  import { Box as Box4, Text as Text4 } from "ink";
516
567
  import Spinner2 from "ink-spinner";
517
568
  import { tmpdir as tmpdir2 } from "os";
@@ -550,8 +601,8 @@ var STEP_INDEX = {
550
601
  };
551
602
  function ContributeFlow(props) {
552
603
  const { ceremonyId: ceremonyId2, trackId, token, slotStatus, entropy, displayName: displayName2 } = props;
553
- const [step, setStep] = useState3({ name: "downloading", bytesReceived: 0, total: null });
554
- useEffect3(() => {
604
+ const [step, setStep] = useState4({ name: "downloading", bytesReceived: 0, total: null });
605
+ useEffect4(() => {
555
606
  let cancelled = false;
556
607
  const challengePath = join4(tmpdir2(), `ceremony-challenge-${Date.now()}.mpcparams`);
557
608
  let responsePath = null;
@@ -597,8 +648,8 @@ function ContributeFlow(props) {
597
648
  await api.signalUploaded(ceremonyId2, trackId, slotStatus.contribution_id, token);
598
649
  if (cancelled) return;
599
650
  setStep({ name: "verifying", attempt: 1 });
600
- const TOTAL_POLLS = 80;
601
- const FAST_POLLS = 20;
651
+ const TOTAL_POLLS = 100;
652
+ const FAST_POLLS = 10;
602
653
  let receipt = null;
603
654
  let lastErr = null;
604
655
  for (let i = 0; i < TOTAL_POLLS; i++) {
@@ -632,6 +683,8 @@ function ContributeFlow(props) {
632
683
  };
633
684
  }, []);
634
685
  const currentIdx = STEP_INDEX[step.name] ?? 0;
686
+ const computingMsg = useCyclingMessage(COMPUTING_MESSAGES, 2500, step.name === "computing");
687
+ const verifyingMsg = useCyclingMessage(VERIFYING_MESSAGES, 3e3, step.name === "verifying");
635
688
  return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", gap: 1, children: STEP_LABELS.map((label, i) => {
636
689
  const isDone = i < currentIdx;
637
690
  const isActive = i === currentIdx;
@@ -655,17 +708,25 @@ function ContributeFlow(props) {
655
708
  ] });
656
709
  }
657
710
  if (isActive && step.name === "verifying") {
658
- const elapsed = step.attempt <= 20 ? step.attempt * 3 : 20 * 3 + (step.attempt - 20) * 10;
711
+ const elapsed = step.attempt <= 10 ? step.attempt * 3 : 10 * 3 + (step.attempt - 10) * 10;
659
712
  const mins = Math.floor(elapsed / 60);
660
713
  const secs = elapsed % 60;
661
714
  const time = mins > 0 ? `${mins}m ${secs.toString().padStart(2, "0")}s` : `${secs}s`;
662
715
  detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
663
716
  " ",
664
- "worker verifying \u2014 large circuits can take a few minutes (",
717
+ verifyingMsg,
718
+ "\u2026 (",
665
719
  time,
666
720
  " elapsed)"
667
721
  ] });
668
722
  }
723
+ if (isActive && step.name === "computing") {
724
+ detail = /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
725
+ " ",
726
+ computingMsg,
727
+ "\u2026"
728
+ ] });
729
+ }
669
730
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
670
731
  /* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
671
732
  indicator,
@@ -678,8 +739,7 @@ function ContributeFlow(props) {
678
739
  children: [
679
740
  label.charAt(0).toUpperCase() + label.slice(1),
680
741
  " ",
681
- isActive && label === "computing" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(entropy stays local)" }),
682
- isActive && label === "uploading" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(~544 bytes)" })
742
+ isActive && label === "computing" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(entropy stays local)" })
683
743
  ]
684
744
  }
685
745
  )
@@ -755,19 +815,19 @@ var NAME_MAX_LEN = 100;
755
815
  var NAME_VALID_RE = /^[\p{L}\p{N} _.\-]*$/u;
756
816
  function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName }) {
757
817
  const { exit } = useApp();
758
- const [activeCeremonyId, setActiveCeremonyId] = useState4(initialCeremonyId);
759
- const [displayName2, setDisplayName] = useState4(initialDisplayName ?? "anonymous");
760
- const [nameSet, setNameSet] = useState4(initialDisplayName !== void 0);
761
- const [screen, setScreen] = useState4(
818
+ const [activeCeremonyId, setActiveCeremonyId] = useState5(initialCeremonyId);
819
+ const [displayName2, setDisplayName] = useState5(initialDisplayName ?? "anonymous");
820
+ const [nameSet, setNameSet] = useState5(initialDisplayName !== void 0);
821
+ const [screen, setScreen] = useState5(
762
822
  initialDisplayName === void 0 ? { name: "name-input", value: "" } : initialCeremonyId ? { name: "loading" } : { name: "ceremony-picker", ceremonies: [], loading: true }
763
823
  );
764
- const [ceremony, setCeremony] = useState4(null);
765
- const [session, setSession] = useState4(null);
766
- const [contributed, setContributed] = useState4({});
767
- const [selectedIdx, setSelectedIdx] = useState4(0);
768
- const [tab, setTab] = useState4(0);
769
- const [showInfo, setShowInfo] = useState4(false);
770
- useEffect4(() => {
824
+ const [ceremony, setCeremony] = useState5(null);
825
+ const [session, setSession] = useState5(null);
826
+ const [contributed, setContributed] = useState5({});
827
+ const [selectedIdx, setSelectedIdx] = useState5(0);
828
+ const [tab, setTab] = useState5(0);
829
+ const [showInfo, setShowInfo] = useState5(false);
830
+ useEffect5(() => {
771
831
  if (!nameSet) return;
772
832
  if (!initialCeremonyId) {
773
833
  loadCeremonies();
@@ -890,7 +950,7 @@ function App({ ceremonyId: initialCeremonyId, displayName: initialDisplayName })
890
950
  setScreen({ name: "ceremony-picker", ceremonies: [], loading: true });
891
951
  loadCeremonies();
892
952
  }
893
- useEffect4(() => {
953
+ useEffect5(() => {
894
954
  if (screen.name === "queue" && session) {
895
955
  const { trackId } = screen;
896
956
  setQueueCleanup(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbra-privacy/ceremony",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Terminal UI for the Umbra Phase 2 trusted setup ceremony",
5
5
  "type": "module",
6
6
  "bin": {