npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

threshold-elgamal

v2.2.1

Published

A browser-native TypeScript ElGamal library for Ristretto255-based research prototypes, shipping additive ElGamal, threshold decryption, protocol helpers, board auditing, transport primitives, and log-driven DKG state machines.

Readme

threshold-elgamal

npm version npm downloads


CI Tests coverage Documentation build


License

threshold-elgamal is a browser-native TypeScript library for verifiable score-voting research prototypes. It focuses on one workflow:

  • additive ElGamal on ristretto255
  • honest-majority GJKR DKG
  • one explicit global contiguous score range per ceremony
  • manifest scoreRange.max capped at 100 to keep proofs and tally recovery tractable
  • one public manifest shape: rosterHash, optionList, and scoreRange
  • organizer-signed ballot-close before decryption
  • full local recomputation and full ceremony verification from the public board

This package is library-only. WebSockets, retries, persistence, bulletin-board storage, mobile lifecycle handling, reminders, and organizer UX live in the application.

This is a hardened research prototype. It has not been audited.

Installation

npm install threshold-elgamal

Runtime requirements

  • Use ESM imports such as import { createElectionManifest } from 'threshold-elgamal'.
  • Browsers need native bigint together with Web Crypto.
  • Node must satisfy the package engines.node requirement and expose globalThis.crypto.
  • Authentication signatures require Web Crypto Ed25519.
  • Transport share exchange requires Web Crypto X25519.

See Runtime and compatibility for environment requirements.

Documentation



Browser support

The cryptographic browser path is fixed:

  • Ed25519 for protocol payload signatures

  • X25519 for encrypted share transport

  • Use modern browsers that expose Web Crypto Ed25519, Web Crypto X25519, and native bigint

  • Validate your target environments with pnpm exec tsx ./tools/ci/verify-browser-compat.ts before deployment

Older browsers, stale embedded webviews, and runtimes without Web Crypto X25519 support are not supported.

Supported workflow

The supported boardroom flow is:

  1. Freeze the roster in the application and hash it with hashRosterEntries(...).
  2. Build the manifest with createElectionManifest({ rosterHash, optionList, scoreRange }).
  3. Publish the manifest, registrations, and manifest acceptances.
  4. Run the honest-majority GJKR transcript.
  5. Post ballot payloads for complete scores inside the manifest-declared range.
  6. Post one organizer-signed ballot-close payload that freezes which complete ballots are counted.
  7. Post threshold decryption shares and tally publications for the close-selected ballot set.
  8. Verify the whole ceremony with verifyElectionCeremony(...).

The cryptographic threshold is derived internally from the accepted registration roster:

  • k = ceil(n / 2)
  • odd participant counts are recommended
  • even participant counts are supported and use k = n / 2

There is no supported n-of-n mode and no supported public k-of-n configuration.

Transcript verification requires key-derivation-confirmation payloads from every qualified participant. In the current design those unanimous confirmations are part of verifier soundness: the library does not implement a public post-Feldman complaint/reconstruction phase, so the DKG verifier is participant-confirmed rather than fully public-data-only. Lowering confirmation acceptance to threshold-many is out of scope unless that missing public consistency machinery is added.

See Honest-majority voting flow for the full phase-by-phase transcript.

Getting started

import {
    createElectionManifest,
    deriveSessionId,
    hashElectionManifest,
    hashRosterEntries,
    majorityThreshold,
} from "threshold-elgamal";

const rosterHash = await hashRosterEntries([
    {
        participantIndex: 1,
        authPublicKey: "auth-key-1",
        transportPublicKey: "transport-key-1",
    },
    {
        participantIndex: 2,
        authPublicKey: "auth-key-2",
        transportPublicKey: "transport-key-2",
    },
    {
        participantIndex: 3,
        authPublicKey: "auth-key-3",
        transportPublicKey: "transport-key-3",
    },
]);

const manifest = createElectionManifest({
    rosterHash,
    optionList: ["Option A", "Option B"],
    scoreRange: { min: 0, max: 5 },
});

const manifestHash = await hashElectionManifest(manifest);
const sessionId = await deriveSessionId(
    manifestHash,
    rosterHash,
    "public-nonce",
    "2026-04-11T12:00:00Z",
);

console.log(majorityThreshold(3)); // 2
console.log(sessionId.length); // 64

The example uses 0..5 only as one concrete score range. The supported rule is one manifest-declared contiguous range with non-negative bounds and scoreRange.max <= 100.

If your application consumes a complete public board, start with Verifying a public board and then move directly to the verifier entry point:

import {
    tryVerifyElectionCeremony,
    type VerifyElectionCeremonyInput,
} from "threshold-elgamal";

const bundle: VerifyElectionCeremonyInput = {
    manifest,
    sessionId,
    dkgTranscript,
    ballotPayloads,
    ballotClosePayloads: [ballotClosePayload],
    decryptionSharePayloads,
    tallyPublications,
};

const result = await tryVerifyElectionCeremony(bundle);

if (!result.ok) {
    console.error(result.error.stage, result.error.code, result.error.reason);
} else {
    console.log(result.verified.perOptionTallies);
    console.log(result.verified.boardAudit.overall.fingerprint);
}

Pass the full published ballot-close slot in ballotClosePayloads, even when the normal case is one organizer payload. The verifier audits that slot, collapses only exact retransmissions, and requires exactly one accepted close record.

The root package exposes the builders and lower-level helpers required for the documented ceremony, including:

  • manifest publication
  • registration
  • manifest acceptance
  • phase checkpoints
  • Pedersen commitments
  • encrypted dual-share envelopes
  • Feldman commitments
  • key-derivation confirmations
  • ballot submission
  • ballot close
  • decryption shares
  • tally publication

The reveal path also works from the root package:

  • prepare the accepted aggregate with prepareAggregateForDecryption(...)
  • compute each partial share with createDecryptionShare(...)
  • prove it with createDLEQProof(...)
  • publish it with createDecryptionSharePayload(...)

After collecting a threshold subset, recover the tally with combineDecryptionShares(...) against the prepared aggregate ciphertext.

The grouped public submodules remain available when you prefer narrower imports by subsystem, but the supported full ceremony does not require them.

For concrete posted JSON shapes, use Published payload examples.

Security boundary

The library is designed for an honest-origin, honest-client, static-adversary setting.

What it tries to enforce:

  • additive-only tallying on ristretto255
  • one explicit global contiguous manifest score range
  • grouped per-option ballot verification
  • mandatory local aggregate recomputation before decryption
  • organizer-visible and auditable ballot cutoff through ballot-close
  • end-to-end ceremony verification from signed public payloads

What it does not claim:

  • coercion resistance
  • receipt-freeness
  • cast-as-intended against a compromised client
  • constant-time JavaScript bigint execution
  • production readiness

ballot-close is an auditable administrative cutoff, not a fairness proof about board arrival order. The library proves which ballots count, not whether the organizer waited long enough before closing.

For a production-threat-model verdict that maps these boundaries to the verifier and tests, read the production voting safety review.

Development

pnpm install
pnpm run lint
pnpm run tsc
pnpm run test
pnpm run coverage:node
pnpm run build
pnpm exec playwright install chromium firefox webkit
pnpm exec tsx ./tools/ci/verify-browser-compat.ts
pnpm run verify:docs
pnpm run docs:build:site
pnpm run smoke:pack

License

This project is licensed under MPL-2.0. See LICENSE.