QuipNetwork/hashsigs-rsPublic

Hash-based post quantum signatures in Rust

AI summary: Core Rust workspace for post-quantum hash-based signature primitives.

Stars
11.2K
+-1 today
Forks
43
Watchers
14
Open issues
1
Open PRs
6
Contributors
~4
Commits
54
Branches
30

RustAGPL-3.0Created Mar 19, 2025Last push 10d ago+-1 stars this week+-10 this month

Star history

since Nov 9, 2025
05K10KNov 2025Feb 2026May 2026Aug 2026
11.2K stars as of Aug 7, 2026, tracked back to Nov 9, 2025. Historical curve reconstructed from public GitHub event archives, calibrated to the current total.

Contribution activity

commits per day, last 52 weeks
AugSepOctNovDecJanFebMarAprMayJunJulAugMonWedFri2025-08-10: 0 commits2025-08-11: 0 commits2025-08-12: 6 commits2025-08-13: 1 commit2025-08-14: 0 commits2025-08-15: 0 commits2025-08-16: 0 commits2025-08-17: 0 commits2025-08-18: 0 commits2025-08-19: 0 commits2025-08-20: 0 commits2025-08-21: 0 commits2025-08-22: 0 commits2025-08-23: 0 commits2025-08-24: 0 commits2025-08-25: 0 commits2025-08-26: 5 commits2025-08-27: 0 commits2025-08-28: 0 commits2025-08-29: 0 commits2025-08-30: 0 commits2025-08-31: 0 commits2025-09-01: 0 commits2025-09-02: 0 commits2025-09-03: 0 commits2025-09-04: 0 commits2025-09-05: 0 commits2025-09-06: 0 commits2025-09-07: 0 commits2025-09-08: 0 commits2025-09-09: 0 commits2025-09-10: 0 commits2025-09-11: 0 commits2025-09-12: 0 commits2025-09-13: 0 commits2025-09-14: 0 commits2025-09-15: 0 commits2025-09-16: 0 commits2025-09-17: 0 commits2025-09-18: 0 commits2025-09-19: 0 commits2025-09-20: 0 commits2025-09-21: 0 commits2025-09-22: 0 commits2025-09-23: 0 commits2025-09-24: 0 commits2025-09-25: 0 commits2025-09-26: 0 commits2025-09-27: 0 commits2025-09-28: 0 commits2025-09-29: 0 commits2025-09-30: 0 commits2025-10-01: 0 commits2025-10-02: 0 commits2025-10-03: 0 commits2025-10-04: 0 commits2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-08: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 0 commits2025-12-08: 0 commits2025-12-09: 0 commits2025-12-10: 0 commits2025-12-11: 0 commits2025-12-12: 0 commits2025-12-13: 0 commits2025-12-14: 0 commits2025-12-15: 0 commits2025-12-16: 0 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 0 commits2025-12-21: 0 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 0 commits2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 0 commits2026-01-04: 0 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 0 commits2026-01-09: 0 commits2026-01-10: 0 commits2026-01-11: 0 commits2026-01-12: 0 commits2026-01-13: 0 commits2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 0 commits2026-01-20: 0 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 0 commits2026-01-24: 0 commits2026-01-25: 0 commits2026-01-26: 0 commits2026-01-27: 0 commits2026-01-28: 0 commits2026-01-29: 0 commits2026-01-30: 0 commits2026-01-31: 0 commits2026-02-01: 0 commits2026-02-02: 0 commits2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 0 commits2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 0 commits2026-02-14: 0 commits2026-02-15: 0 commits2026-02-16: 0 commits2026-02-17: 0 commits2026-02-18: 0 commits2026-02-19: 0 commits2026-02-20: 0 commits2026-02-21: 0 commits2026-02-22: 0 commits2026-02-23: 0 commits2026-02-24: 0 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 0 commits2026-03-02: 0 commits2026-03-03: 0 commits2026-03-04: 0 commits2026-03-05: 0 commits2026-03-06: 0 commits2026-03-07: 0 commits2026-03-08: 0 commits2026-03-09: 0 commits2026-03-10: 0 commits2026-03-11: 1 commit2026-03-12: 0 commits2026-03-13: 0 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 0 commits2026-03-22: 0 commits2026-03-23: 0 commits2026-03-24: 0 commits2026-03-25: 1 commit2026-03-26: 0 commits2026-03-27: 0 commits2026-03-28: 0 commits2026-03-29: 0 commits2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 0 commits2026-04-04: 0 commits2026-04-05: 0 commits2026-04-06: 0 commits2026-04-07: 0 commits2026-04-08: 0 commits2026-04-09: 0 commits2026-04-10: 0 commits2026-04-11: 0 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 0 commits2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 0 commits2026-04-22: 0 commits2026-04-23: 0 commits2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 0 commits2026-05-10: 0 commits2026-05-11: 0 commits2026-05-12: 0 commits2026-05-13: 0 commits2026-05-14: 0 commits2026-05-15: 0 commits2026-05-16: 0 commits2026-05-17: 0 commits2026-05-18: 0 commits2026-05-19: 0 commits2026-05-20: 0 commits2026-05-21: 0 commits2026-05-22: 0 commits2026-05-23: 0 commits2026-05-24: 0 commits2026-05-25: 0 commits2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 0 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 0 commits2026-06-01: 0 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 0 commits2026-06-05: 0 commits2026-06-06: 0 commits2026-06-07: 0 commits2026-06-08: 0 commits2026-06-09: 0 commits2026-06-10: 0 commits2026-06-11: 0 commits2026-06-12: 0 commits2026-06-13: 0 commits2026-06-14: 0 commits2026-06-15: 0 commits2026-06-16: 0 commits2026-06-17: 0 commits2026-06-18: 0 commits2026-06-19: 0 commits2026-06-20: 0 commits2026-06-21: 0 commits2026-06-22: 0 commits2026-06-23: 0 commits2026-06-24: 0 commits2026-06-25: 0 commits2026-06-26: 0 commits2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 0 commits2026-07-07: 0 commits2026-07-08: 0 commits2026-07-09: 0 commits2026-07-10: 0 commits2026-07-11: 0 commits2026-07-12: 0 commits2026-07-13: 0 commits2026-07-14: 0 commits2026-07-15: 5 commits2026-07-16: 0 commits2026-07-17: 0 commits2026-07-18: 0 commits2026-07-19: 0 commits2026-07-20: 0 commits2026-07-21: 0 commits2026-07-22: 0 commits2026-07-23: 0 commits2026-07-24: 0 commits2026-07-25: 0 commits2026-07-26: 12 commits2026-07-27: 0 commits2026-07-28: 0 commits2026-07-29: 0 commits2026-07-30: 0 commits2026-07-31: 0 commits2026-08-01: 0 commits2026-08-02: 0 commits2026-08-03: 0 commits2026-08-04: 0 commits2026-08-05: 0 commits2026-08-06: 0 commits2026-08-07: 0 commits2026-08-08: 0 commits
31 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Widely adopted

    11,221 stars

  • Continuous integration

    Automated checks passing

What hashsigs-rs does

This repository provides a Rust workspace implementing hash-based post-quantum signature schemes. It contains the core primitives for WOTS+ and SHRinCS, acting as a foundational library for post-quantum cryptography. It also includes an account policy wrapper and provides WASM bindings for verifiers, signers, and accounts. Additionally, it features direct integration for Solana programs.

Cryptographers, blockchain developers (especially on Solana), and security engineers building systems resilient to quantum computing threats.

  • WOTS+ Implementation: Includes primitives for the Winternitz One-Time Signature scheme.
  • SHRinCS Support: Provides signer and verifier primitives for SHRinCS.
  • WASM Bindings: Exposes core cryptography functions to WebAssembly environments.
  • Solana Integration: Includes dedicated modules for use in Solana smart contracts.
  • Account Policy Wrapper: Manages cryptographic accounts and policy enforcement.

Where teams use it

Post-Quantum Security

Protecting systems against future quantum computer attacks using hash-based signatures.

Solana Smart Contracts

Integrating quantum-resistant cryptography directly into Solana programs.

Browser-Based Cryptography

Using WASM bindings to sign and verify in web applications.

High-Security Key Management

Managing accounts with strict, post-quantum cryptographic policies.

Getting started: cargo build

README

main branch

hashsigs-rs

Core Rust hash-signature workspace with:

  • hashsigs-rs: one crate containing:
    • wotsplus primitives
    • shrincs signer / verifier primitives
    • account policy wrapper
    • wasm verifier / signer / account bindings
  • solana/: Solana program integration

Building

To build the library:

cargo build

For release build:

cargo build --release

To build the Solana program:

cd solana
cargo build-sbf

WASM Packaging

The crate exposes SHRINCS verifier, signer, and account bindings under src/wasm/ behind the wasm-bindings feature. The supported build path is bin/build-wasm.sh, which runs cargo build for wasm32-unknown-unknown and then the wasm-bindgen CLI (not wasm-pack) for the nodejs and web targets.

Prerequisites:

rustup target add wasm32-unknown-unknown
# Must equal the crate's wasm-bindgen dependency (Cargo.toml =0.2.100).
cargo install wasm-bindgen-cli --version 0.2.100

Build from the crate root (default output directory is ts/src):

./bin/build-wasm.sh
# or
./bin/build-wasm.sh ts/src

That writes:

ts/src/nodejs/   # wasm-bindgen nodejs target (CommonJS)
ts/src/web/      # wasm-bindgen web target (ESM)

Optional custom output directory:

./bin/build-wasm.sh /tmp/hashsigs-wasm

The TypeScript package that wraps those bindings lives in ts/ and is named @quip.network/hashsigs-wasm. After the wasm build:

cd ts
npm ci
npm run build   # also rebuilds wasm, inlines browser wasm as base64, runs tsc
npm test        # packaging conformance against dist/

Published consumers load one async entry point. The package "browser" field swaps the Node loader for the browser loader at bundle time:

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const keypair = wasm.shrincsKeygen("0x" + "ab".repeat(32), 16);

CI builds and tests this package on merge requests and the default branch (ts-conformance job). Version tags matching vX.Y.Z (optional pre-release suffix) run the same build and publish to npm.

Current WASM scope:

  • supported:
    • SHRINCS verifier bindings
    • SHRINCS key generation, raw signing, and signing-key import/export
    • account-layer wrapper bindings
    • hex / plain-object JS entry points via loadShrincsWasm()
    • Node and browser packaging under @quip.network/hashsigs-wasm
  • not implemented:
    • WOTS-specific wasm bindings
    • a separate wasm-pack / pkg/<target> layout

WASM Testing

Two layers cover the wasm surface:

  1. Rust host tests (cargo test --features wasm-bindings): DTO parsing, hex helpers, and feature-gated conversion logic on the host. They do not run the exported bindings inside a wasm runtime.
  2. TS packaging conformance (cd ts && npm test, after npm run build): loads the built dist/ package through both Node and browser loaders and exercises keygen, sign, verify, import/export, and typed errors.

For Rust-only wasm target unit tests (optional), install a matching wasm-bindgen-test-runner and run:

cargo test --features wasm-bindings --target wasm32-unknown-unknown

When changing WasmShrincsKeypair, WasmShrincsAccount, or verifier exports in src/wasm/, treat the TS conformance suite as the packaging gate and the Rust suite as the crypto / DTO gate.

WASM API

loadShrincsWasm() resolves to the generated wasm module. That module exposes verifier functions, signer/keypair APIs, and account-wrapper APIs. Byte fields are 0x-prefixed hex strings; structured values are plain camelCase objects. u64 fields cross the boundary as JavaScript bigint.

Verifier exports

  • shrincsVerifyStatefulRaw(...)
  • shrincsVerifyStatefulAction(...)
  • shrincsVerifyStatelessRaw(...)
  • shrincsVerifyStatelessAction(...)

Malformed hex or wrong-length fields throw an Error with a typed error.code (ERR_HEX_INVALID, ERR_BAD_LENGTH, or ERR_INVALID_INPUT for serde shape errors). Cryptographic verification failure returns false and does not throw.

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const ok = wasm.shrincsVerifyStatelessAction(
  publicKey.publicKeyCommitment,
  publicKey,
  {
    domainSeparator: "0x...",
    nonce: "0x...",
    keyVersion: "0x...",
    actionType: "0x...",
    payloadHash: "0x...",
  },
  signature,
);

Signer exports

  • shrincsKeygen(seedHex, maxStatefulSignatures)
  • shrincsImportSigningKey(exportedKey)
  • WasmShrincsKeypair.publicKey()
  • WasmShrincsKeypair.signStatefulRaw(...) — returns { signature, nextStatefulLeafIndex }
  • WasmShrincsKeypair.signStatefulRawAt(messageHex, leaf)
  • WasmShrincsKeypair.signStatelessRaw(...)
  • WasmShrincsKeypair.exportSigningKeyUnsafe()
  • WasmShrincsKeypair.exportSigningKey() — legacy alias of the Unsafe form
  • WasmShrincsKeypair.destroy()
  • getters: nextStatefulLeafIndex, maxStatefulSignatures, remainingStatefulSignatures
  • version()
import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
// Seed must be at least 32 bytes of hex.
const keypair = wasm.shrincsKeygen("0x" + "11".repeat(32), 16);

const publicKey = keypair.publicKey();
const { signature: statefulSignature, nextStatefulLeafIndex } =
  keypair.signStatefulRaw("0xdeadbeef");
const statelessSignature = keypair.signStatelessRaw("0xdeadbeef");
const signingKeySnapshot = keypair.exportSigningKeyUnsafe();
// signingKeySnapshot.formatVersion === 1 (required on import)

Keypair lifecycle: destroy() and free()

destroy() is the supported explicit lifecycle method:

  • clears in-memory signing and public-key state on the handle (best-effort wipe)
  • permanently invalidates the handle
  • later method or getter calls throw an Error with error.code === "ERR_HANDLE_DESTROYED"

free() is the wasm-bindgen-generated finalizer that drops the underlying wasm object when the JS wrapper is garbage-collected. Prefer destroy() when you still hold a reference and want secrets cleared early. After destroy(), do not call other keypair methods; you may still call free() if your embedding requires an explicit drop, but the handle is already empty.

WASM Secret Handling

The WASM signer surface is for environments where the surrounding JS context is trusted.

  • WasmShrincsKeypair holds live signing-key material in wasm memory while the handle exists
  • exportSigningKeyUnsafe() materializes the full private signing state into JS
  • XSS, same-origin script, compromised dependencies, or hostile extensions that run in the page can exfiltrate that material

Operational guidance:

  • do not use the browser signer in pages that execute untrusted third-party JS
  • avoid exportSigningKeyUnsafe() except for explicit backup or migration
  • call destroy() as soon as the keypair is no longer needed
  • treat browser memory as a soft boundary, not a hardware-backed secret store

Safe defaults:

  • keep the keypair in wasm and use signStatefulRaw / signStatelessRaw for routine operation
  • export secret state only at explicit persistence boundaries
  • treat exportSigningKey() the same as exportSigningKeyUnsafe()

shrincsKeygen(seedHex, maxStatefulSignatures) rejects seeds shorter than 32 bytes (ERR_SEED_TOO_SHORT) and maxStatefulSignatures outside 1..=4096 (ERR_INVALID_INPUT). Stateful signing consumes one leaf per signature; signStatefulRaw throws ERR_STATEFUL_LEAVES_EXHAUSTED when the budget is spent.

Account exports

  • new WasmShrincsAccount(owner, chainId, contractAddress, publicKeyCommitment)
  • snapshot()
  • verifyStatefulAction(...)
  • verifyStatelessAction(...)
  • rotateToFreshKey(...)
  • rotateFullKey(...)
  • setStatefulPolicyMonotonicIndex(...)
  • setStatefulPolicyRecoveryRotation(...)
  • setStatefulPolicyLeafBitmap(...)
  • enterRecoveryMode(...)

Canonical message helpers:

  • shrincsStatefulActionMessageHash(...)
  • shrincsStatelessActionMessageHash(...)
  • shrincsStatefulRotationMessageHash(...)
  • shrincsFullRotationMessageHash(...)
import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const owner = "0x" + "11".repeat(32);
const chainId = "0x" + "22".repeat(32);
const contractAddress = "0x" + "33".repeat(20);

const keypair = wasm.shrincsKeygen("0x" + "12".repeat(32), 8);
const publicKey = keypair.publicKey();

const account = new wasm.WasmShrincsAccount(
  owner,
  chainId,
  contractAddress,
  publicKey.publicKeyCommitment,
);

account.setStatefulPolicyRecoveryRotation(owner);
account.enterRecoveryMode(owner);

console.log(account.snapshot());

Stateful action verification:

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const owner = "0x" + "11".repeat(32);
const chainId = "0x" + "22".repeat(32);
const contractAddress = "0x" + "33".repeat(20);
const actionType = "0x" + "44".repeat(32);
const payloadHash = "0x" + "55".repeat(32);

const keypair = wasm.shrincsKeygen("0x" + "00".repeat(32), 8);
const publicKey = keypair.publicKey();
const account = new wasm.WasmShrincsAccount(
  owner,
  chainId,
  contractAddress,
  publicKey.publicKeyCommitment,
);

const snapshot = account.snapshot();
const message = wasm.shrincsStatefulActionMessageHash(
  publicKey.publicKeyCommitment,
  {
    domainSeparator: snapshot.domainSeparator,
    nonce: snapshot.nonce,
    keyVersion: snapshot.keyVersion,
    actionType,
    payloadHash,
  },
);
const { signature } = keypair.signStatefulRaw(message);
const ok = account.verifyStatefulAction(
  publicKey,
  actionType,
  payloadHash,
  signature,
);

console.log({ ok, snapshot: account.snapshot() });

Stateless action verification:

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const owner = "0x" + "11".repeat(32);
const chainId = "0x" + "22".repeat(32);
const contractAddress = "0x" + "33".repeat(20);
const actionType = "0x" + "66".repeat(32);
const payloadHash = "0x" + "77".repeat(32);

const keypair = wasm.shrincsKeygen("0x" + "ab".repeat(32), 8);
const publicKey = keypair.publicKey();
const account = new wasm.WasmShrincsAccount(
  owner,
  chainId,
  contractAddress,
  publicKey.publicKeyCommitment,
);

const snapshot = account.snapshot();
const message = wasm.shrincsStatelessActionMessageHash(
  publicKey.publicKeyCommitment,
  {
    domainSeparator: snapshot.domainSeparator,
    nonce: snapshot.nonce,
    keyVersion: snapshot.keyVersion,
    actionType,
    payloadHash,
  },
);
const signature = keypair.signStatelessRaw(message);
const ok = account.verifyStatelessAction(
  publicKey,
  actionType,
  payloadHash,
  signature,
);

console.log({ ok, snapshot: account.snapshot() });

Stateful-only rotation:

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";
import { concat, getBytes, keccak256, toUtf8Bytes } from "ethers";

function statefulOnlyTargetCommitment(
  nextStatefulPublicKey: string,
  currentPublicKey: { pkSeed: string; hypertreeRoot: string },
) {
  return keccak256(
    concat([
      toUtf8Bytes("shrincs-public-key"),
      getBytes(nextStatefulPublicKey),
      getBytes(currentPublicKey.pkSeed),
      getBytes(currentPublicKey.hypertreeRoot),
    ]),
  );
}

const wasm = await loadShrincsWasm();
const owner = "0x" + "11".repeat(32);
const chainId = "0x" + "22".repeat(32);
const contractAddress = "0x" + "33".repeat(20);

const currentKeypair = wasm.shrincsKeygen("0x" + "11".repeat(32), 8);
const nextKeypair = wasm.shrincsKeygen("0x" + "22".repeat(32), 16);

const currentPublicKey = currentKeypair.publicKey();
const nextPublicKey = nextKeypair.publicKey();
const account = new wasm.WasmShrincsAccount(
  owner,
  chainId,
  contractAddress,
  currentPublicKey.publicKeyCommitment,
);

account.setStatefulPolicyRecoveryRotation(owner);
account.enterRecoveryMode(owner);

const snapshot = account.snapshot();
const nextStatefulTarget = {
  statefulPublicKey: nextPublicKey.statefulPublicKey,
  publicKeyCommitment: statefulOnlyTargetCommitment(
    nextPublicKey.statefulPublicKey,
    currentPublicKey,
  ),
};

const recoveryMessage = wasm.shrincsStatefulRotationMessageHash(
  currentPublicKey.publicKeyCommitment,
  currentPublicKey,
  {
    domainSeparator: snapshot.domainSeparator,
    nonce: snapshot.nonce,
    keyVersion: snapshot.keyVersion,
  },
  nextStatefulTarget,
);
const recoverySignature = currentKeypair.signStatelessRaw(recoveryMessage);
const rotated = account.rotateToFreshKey(
  currentPublicKey,
  recoverySignature,
  nextStatefulTarget,
);

console.log({ rotated, snapshot: account.snapshot() });

Full rotation:

import { loadShrincsWasm } from "@quip.network/hashsigs-wasm";

const wasm = await loadShrincsWasm();
const owner = "0x" + "11".repeat(32);
const chainId = "0x" + "22".repeat(32);
const contractAddress = "0x" + "33".repeat(20);

const currentKeypair = wasm.shrincsKeygen("0x" + "aa".repeat(32), 8);
const nextKeypair = wasm.shrincsKeygen("0x" + "bb".repeat(32), 16);

const currentPublicKey = currentKeypair.publicKey();
const nextPublicKey = nextKeypair.publicKey();
const account = new wasm.WasmShrincsAccount(
  owner,
  chainId,
  contractAddress,
  currentPublicKey.publicKeyCommitment,
);

account.setStatefulPolicyRecoveryRotation(owner);
account.enterRecoveryMode(owner);

const snapshot = account.snapshot();
const nextFullTarget = {
  statefulPublicKey: nextPublicKey.statefulPublicKey,
  publicKeyCommitment: nextPublicKey.publicKeyCommitment,
  pkSeed: nextPublicKey.pkSeed,
  hypertreeRoot: nextPublicKey.hypertreeRoot,
};

const recoveryMessage = wasm.shrincsFullRotationMessageHash(
  currentPublicKey.publicKeyCommitment,
  currentPublicKey,
  {
    domainSeparator: snapshot.domainSeparator,
    nonce: snapshot.nonce,
    keyVersion: snapshot.keyVersion,
  },
  nextFullTarget,
);
const recoverySignature = currentKeypair.signStatelessRaw(recoveryMessage);
const rotated = account.rotateFullKey(
  currentPublicKey,
  recoverySignature,
  nextFullTarget,
);

console.log({ rotated, snapshot: account.snapshot() });

Canonical end-to-end pattern:

  • read domainSeparator, nonce, and keyVersion from account.snapshot()
  • hash with the matching message helper
  • sign with signStatefulRaw or signStatelessRaw
  • submit through the matching account verify or rotate method

Object shapes

Names match the generated Tsify types re-exported from @quip.network/hashsigs-wasm (see ts/src/index.ts). Fields are camelCase.

Public key (ShrincsPublicKey):

type ShrincsPublicKey = {
  statefulPublicKey: string;
  publicKeyCommitment: string;
  pkSeed: string;
  hypertreeRoot: string;
};

Action context (ActionContext):

type ActionContext = {
  domainSeparator: string;
  nonce: string;
  keyVersion: string;
  actionType: string;
  payloadHash: string;
};

Account snapshot (ShrincsAccountSnapshot):

type ShrincsAccountSnapshot = {
  currentShrincsPublicKey: string;
  owner: string;
  chainId: string;
  contractAddress: string;
  domainSeparator: string;
  nonce: string;
  keyVersion: string;
  statelessSignaturesUsed: bigint; // u64 over the wasm boundary
  statefulPolicy: string;
  nextStatefulLeafIndex: number;
  recoveryMode: boolean;
};

Rotation context (RotationContext):

type RotationContext = {
  domainSeparator: string;
  nonce: string;
  keyVersion: string;
};

Stateful rotation target (StatefulRotationTarget):

type StatefulRotationTarget = {
  statefulPublicKey: string;
  publicKeyCommitment: string;
};

Full rotation target (RotationTarget):

type RotationTarget = {
  statefulPublicKey: string;
  publicKeyCommitment: string;
  pkSeed: string;
  hypertreeRoot: string;
};

Stateful signature (StatefulSignature):

type StatefulSignature = {
  randomizer: string;
  counter: number;
  chains: string[];
  authPath: string[];
};

Stateless signature (StatelessSignature):

type ForsEntry = {
  secretLeaf: string;
  authPath: string[];
};

type ForsSignature = {
  randomizer: string;
  counter: number;
  entries: ForsEntry[];
};

type WotsCSignature = {
  randomizer: string;
  counter: number;
  chains: string[];
};

type HypertreeLayerSignature = {
  treeIndex: bigint; // u64 over the wasm boundary
  leafIndex: number;
  wotsCPkHash: string;
  wotsCSignature: WotsCSignature;
  authPath: string[];
};

type StatelessSignature = {
  fors: ForsSignature;
  hypertree: HypertreeLayerSignature[];
};

Signing key snapshot from exportSigningKeyUnsafe() / exportSigningKey() (ShrincsExportedSigningKey):

type ShrincsExportedSigningKey = {
  formatVersion: 1; // required; import rejects other versions
  statefulSkSeed: string;
  statefulPrfSeed: string;
  statefulPkSeed: string;
  statefulRoot: string;
  maxStatefulSignatures: number;
  nextStatefulLeafIndex: number;
  statelessSkSeed: string;
  statelessPrfSeed: string;
  pkSeed: string;
  hypertreeRoot: string;
};

Testing

Run all tests:

cargo test

Run specific test vectors:

cargo test test_wotsplus_keccak256_vectors

Generate SHRINCS vectors for the Solidity verifier:

cargo test --test generate_shrincs_vectors -- --ignored --nocapture

The generator writes the SHRINCS vector JSON inside this Rust repository:

tests/test_vectors/shrincs_sphincs_256s_keccak.json

SHRINCS public keys use one stateless pkSeed and one hypertreeRoot, matching the SPHINCS+/FIPS-style PK = (PK.seed, PK.root) abstraction for the stateless path, while the full hybrid bundle stays bound together by public_key_commitment.

To use those vectors with the Solidity verifier tests, copy the generated file into the Solidity repository's expected fixture path:

# copy the generated JSON to the Solidity repository's expected fixture path
cp tests/test_vectors/shrincs_sphincs_256s_keccak.json \
  /path/to/hashsigs-solidity/test/test_vectors/shrincs_sphincs_256s_keccak.json

To cross-check Solidity-exported account vectors against the Rust verifier, generate the account-vector JSON in hashsigs-solidity first, then copy it into this Rust repository manually. The repos are separate, so this handoff is intentionally not automated.

# in hashsigs-solidity
bash dev/export-account-vectors.sh

# copy the generated JSON into hashsigs-rs manually
cp /path/to/hashsigs-solidity/test/test_vectors/shrincs_account_wrapper_vectors.json \
  tests/test_vectors/shrincs_account_wrapper_vectors.json

Then run the Rust-side cross-check. The tests are #[ignore]d because the fixture is copied in manually, so pass --ignored explicitly:

cargo test --test solidity_account_vectors -- --ignored

Generate the kth stateful gas vector for Solidity gas benchmarks. The generator requires Foundry's cast on PATH and writes tests/test_vectors/shrincs_stateful_k_gas_vector.json (gitignored):

cargo test --test generate_stateful_gas_vector -- --ignored --nocapture

Run Solana program tests:

cd solana
cargo test-sbf

For test output and backtrace:

RUST_BACKTRACE=1 cargo test-sbf -- --nocapture 2>&1

Show compute units only:

RUST_BACKTRACE=1 cargo test-sbf -- --nocapture 2>&1 | grep "compute units:"

Development Requirements

  • Rust 1.79 or later; the current local test pass was with Rust 1.95.0
  • Solana/Agave SBF cargo subcommands, including cargo build-sbf and cargo test-sbf, for Solana program development: https://solana.com/docs/intro/installation

NOTE: if on Mac, do not use brew to install rust and instead use https://www.rust-lang.org/tools/install

Project Structure

.
├── bin/
│   └── build-wasm.sh  # cargo + wasm-bindgen helper (nodejs + web → ts/src)
├── src/
│   ├── wotsplus/  # WOTS+ primitives
│   ├── shrincs/   # SHRINCS signer / verifier primitives
│   ├── account/   # Rust account-policy wrapper
│   └── wasm/      # Verifier / signer / account wasm-bindgen surface
├── ts/            # @quip.network/hashsigs-wasm (loadShrincsWasm entry)
├── solana/        # Solana program implementation
└── tests/         # Test vectors and unit tests

Account Layer Notes

The account module is an off-chain Rust policy wrapper that tracks nonce, key-version, stateful-leaf use, and recovery-mode transitions around the core SHRINCS primitives. It is intentionally close to the Solidity example account wrapper, but it is not a literal runtime-equivalent copy.

The Rust account wrapper enforces a hardened security policy model:

  • switches between stateful leaf-tracking policies (monotonic index and leaf bitmap) are frozen after the first successful stateful use in a key epoch; switching to RecoveryRotation stays available so key rotation is always reachable
  • RecoveryRotation disables the stateful path for the whole recovery-policy epoch
  • rotateToFreshKey(...) preserves stateless usage accounting because the stateless key is unchanged
  • rotateFullKey(...) resets stateless usage accounting only when the supplied rotation target actually changes the stateless key material

Current intentional differences:

  • Owner / caller model: Rust stores owner as a generic 32-byte value and takes an explicit caller argument for owner-gated methods. Solidity stores owner as an address and relies on msg.sender. The Rust model is therefore an integration-supplied authority check, not a chain-enforced caller model.

  • Event semantics: Solidity emits wrapper events such as policy changes, recovery-mode entry, key rotation, and successful signature verification. Rust currently mutates wrapper state and returns bool / Result values, but does not emit first-class event records. Treat this as an observability difference rather than a cryptographic or policy-enforcement difference.

  • Constructor / account identity model: Rust account initialization takes owner, chainId, contractAddress, and the initial SHRINCS public-key commitment as explicit inputs. Solidity gets owner, chain id, and contract identity from the live execution environment. The Rust constructor should therefore be understood as an off-chain simulation/adaptation surface, not a one-to-one deployment API mirror.

The account module now recomputes its domain separator from stored chainId and contractAddress, and rotateToFreshKey(...) is narrowed to a dedicated stateful-only recovery-rotation target. It also enforces hardened policy/accounting behavior that goes beyond the current Solidity example wrapper:

  • the stateful leaf-tracking model must be chosen before the first successful stateful signature in a key epoch; only RecoveryRotation may still be selected afterwards, so a used key can always rotate out
  • selecting RecoveryRotation blocks the stateful path immediately; enterRecoveryMode(...) then permits stateless action verification and stateless recovery rotations
  • stateful-only rotation consumes one stateless recovery use and carries that counter forward
  • full-key rotation consumes one stateless recovery use under the old key and resets the counter only when the newly installed stateless key actually differs

License

AGPL-3.0, see COPYING

View on GitHub

Recent activity

commits and pull requests

Code frequency

additions and deletions
+30.5K-30.5KWeek of 2025-08-10: +1,921 linesWeek of 2025-08-10: -1,274 linesWeek of 2025-08-17: +0 linesWeek of 2025-08-17: -0 linesWeek of 2025-08-24: +348 linesWeek of 2025-08-24: -287 linesWeek of 2025-08-31: +0 linesWeek of 2025-08-31: -0 linesWeek of 2025-09-07: +0 linesWeek of 2025-09-07: -0 linesWeek of 2025-09-14: +0 linesWeek of 2025-09-14: -0 linesWeek of 2025-09-21: +0 linesWeek of 2025-09-21: -0 linesWeek of 2025-09-28: +0 linesWeek of 2025-09-28: -0 linesWeek of 2025-10-05: +0 linesWeek of 2025-10-05: -0 linesWeek of 2025-10-12: +0 linesWeek of 2025-10-12: -0 linesWeek of 2025-10-19: +0 linesWeek of 2025-10-19: -0 linesWeek of 2025-10-26: +0 linesWeek of 2025-10-26: -0 linesWeek of 2025-11-02: +0 linesWeek of 2025-11-02: -0 linesWeek of 2025-11-09: +0 linesWeek of 2025-11-09: -0 linesWeek of 2025-11-16: +0 linesWeek of 2025-11-16: -0 linesWeek of 2025-11-23: +0 linesWeek of 2025-11-23: -0 linesWeek of 2025-11-30: +0 linesWeek of 2025-11-30: -0 linesWeek of 2025-12-07: +0 linesWeek of 2025-12-07: -0 linesWeek of 2025-12-14: +0 linesWeek of 2025-12-14: -0 linesWeek of 2025-12-21: +0 linesWeek of 2025-12-21: -0 linesWeek of 2025-12-28: +0 linesWeek of 2025-12-28: -0 linesWeek of 2026-01-04: +0 linesWeek of 2026-01-04: -0 linesWeek of 2026-01-11: +0 linesWeek of 2026-01-11: -0 linesWeek of 2026-01-18: +0 linesWeek of 2026-01-18: -0 linesWeek of 2026-01-25: +0 linesWeek of 2026-01-25: -0 linesWeek of 2026-02-01: +0 linesWeek of 2026-02-01: -0 linesWeek of 2026-02-08: +0 linesWeek of 2026-02-08: -0 linesWeek of 2026-02-15: +0 linesWeek of 2026-02-15: -0 linesWeek of 2026-02-22: +0 linesWeek of 2026-02-22: -0 linesWeek of 2026-03-01: +0 linesWeek of 2026-03-01: -0 linesWeek of 2026-03-08: +550 linesWeek of 2026-03-08: -0 linesWeek of 2026-03-15: +0 linesWeek of 2026-03-15: -0 linesWeek of 2026-03-22: +2 linesWeek of 2026-03-22: -2 linesWeek of 2026-03-29: +0 linesWeek of 2026-03-29: -0 linesWeek of 2026-04-05: +0 linesWeek of 2026-04-05: -0 linesWeek of 2026-04-12: +0 linesWeek of 2026-04-12: -0 linesWeek of 2026-04-19: +0 linesWeek of 2026-04-19: -0 linesWeek of 2026-04-26: +0 linesWeek of 2026-04-26: -0 linesWeek of 2026-05-03: +0 linesWeek of 2026-05-03: -0 linesWeek of 2026-05-10: +0 linesWeek of 2026-05-10: -0 linesWeek of 2026-05-17: +0 linesWeek of 2026-05-17: -0 linesWeek of 2026-05-24: +0 linesWeek of 2026-05-24: -0 linesWeek of 2026-05-31: +0 linesWeek of 2026-05-31: -0 linesWeek of 2026-06-07: +0 linesWeek of 2026-06-07: -0 linesWeek of 2026-06-14: +0 linesWeek of 2026-06-14: -0 linesWeek of 2026-06-21: +0 linesWeek of 2026-06-21: -0 linesWeek of 2026-06-28: +0 linesWeek of 2026-06-28: -0 linesWeek of 2026-07-05: +0 linesWeek of 2026-07-05: -0 linesWeek of 2026-07-12: +30,502 linesWeek of 2026-07-12: -1,073 linesWeek of 2026-07-19: +0 linesWeek of 2026-07-19: -0 linesWeek of 2026-07-26: +3,410 linesWeek of 2026-07-26: -2,770 linesWeek of 2026-08-02: +0 linesWeek of 2026-08-02: -0 linesAug 10, 2025Aug 2, 2026
+36.7K lines added, -5.4K removed over the last year.

Commits per week

last 52 weeks
120Week of 2025-08-10: 7 commitsWeek of 2025-08-17: 0 commitsWeek of 2025-08-24: 5 commitsWeek of 2025-08-31: 0 commitsWeek of 2025-09-07: 0 commitsWeek of 2025-09-14: 0 commitsWeek of 2025-09-21: 0 commitsWeek of 2025-09-28: 0 commitsWeek of 2025-10-05: 0 commitsWeek of 2025-10-12: 0 commitsWeek of 2025-10-19: 0 commitsWeek of 2025-10-26: 0 commitsWeek of 2025-11-02: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 0 commitsWeek of 2025-12-14: 0 commitsWeek of 2025-12-21: 0 commitsWeek of 2025-12-28: 0 commitsWeek of 2026-01-04: 0 commitsWeek of 2026-01-11: 0 commitsWeek of 2026-01-18: 0 commitsWeek of 2026-01-25: 0 commitsWeek of 2026-02-01: 0 commitsWeek of 2026-02-08: 0 commitsWeek of 2026-02-15: 0 commitsWeek of 2026-02-22: 0 commitsWeek of 2026-03-01: 0 commitsWeek of 2026-03-08: 1 commitsWeek of 2026-03-15: 0 commitsWeek of 2026-03-22: 1 commitsWeek of 2026-03-29: 0 commitsWeek of 2026-04-05: 0 commitsWeek of 2026-04-12: 0 commitsWeek of 2026-04-19: 0 commitsWeek of 2026-04-26: 0 commitsWeek of 2026-05-03: 0 commitsWeek of 2026-05-10: 0 commitsWeek of 2026-05-17: 0 commitsWeek of 2026-05-24: 0 commitsWeek of 2026-05-31: 0 commitsWeek of 2026-06-07: 0 commitsWeek of 2026-06-14: 0 commitsWeek of 2026-06-21: 0 commitsWeek of 2026-06-28: 0 commitsWeek of 2026-07-05: 0 commitsWeek of 2026-07-12: 5 commitsWeek of 2026-07-19: 0 commitsWeek of 2026-07-26: 12 commitsWeek of 2026-08-02: 0 commitsAug 10, 2025Aug 2, 2026
31 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 0 commitsSun 1:00 — 0 commitsSun 2:00 — 0 commitsSun 3:00 — 0 commitsSun 4:00 — 0 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 0 commitsSun 9:00 — 0 commitsSun 10:00 — 0 commitsSun 11:00 — 0 commitsSun 12:00 — 0 commitsSun 13:00 — 0 commitsSun 14:00 — 0 commitsSun 15:00 — 0 commitsSun 16:00 — 1 commitsSun 17:00 — 4 commitsSun 18:00 — 1 commitsSun 19:00 — 1 commitsSun 20:00 — 0 commitsSun 21:00 — 0 commitsSun 22:00 — 2 commitsSun 23:00 — 3 commitsMon 0:00 — 0 commitsMon 1:00 — 0 commitsMon 2:00 — 0 commitsMon 3:00 — 0 commitsMon 4:00 — 0 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 0 commitsMon 8:00 — 0 commitsMon 9:00 — 0 commitsMon 10:00 — 0 commitsMon 11:00 — 0 commitsMon 12:00 — 0 commitsMon 13:00 — 0 commitsMon 14:00 — 0 commitsMon 15:00 — 0 commitsMon 16:00 — 0 commitsMon 17:00 — 0 commitsMon 18:00 — 0 commitsMon 19:00 — 0 commitsMon 20:00 — 0 commitsMon 21:00 — 0 commitsMon 22:00 — 0 commitsMon 23:00 — 0 commitsTue 0:00 — 0 commitsTue 1:00 — 0 commitsTue 2:00 — 0 commitsTue 3:00 — 0 commitsTue 4:00 — 0 commitsTue 5:00 — 0 commitsTue 6:00 — 0 commitsTue 7:00 — 0 commitsTue 8:00 — 0 commitsTue 9:00 — 0 commitsTue 10:00 — 4 commitsTue 11:00 — 1 commitsTue 12:00 — 1 commitsTue 13:00 — 5 commitsTue 14:00 — 1 commitsTue 15:00 — 0 commitsTue 16:00 — 0 commitsTue 17:00 — 0 commitsTue 18:00 — 0 commitsTue 19:00 — 0 commitsTue 20:00 — 0 commitsTue 21:00 — 0 commitsTue 22:00 — 1 commitsTue 23:00 — 3 commitsWed 0:00 — 1 commitsWed 1:00 — 0 commitsWed 2:00 — 0 commitsWed 3:00 — 0 commitsWed 4:00 — 0 commitsWed 5:00 — 0 commitsWed 6:00 — 0 commitsWed 7:00 — 0 commitsWed 8:00 — 0 commitsWed 9:00 — 0 commitsWed 10:00 — 0 commitsWed 11:00 — 2 commitsWed 12:00 — 4 commitsWed 13:00 — 1 commitsWed 14:00 — 0 commitsWed 15:00 — 1 commitsWed 16:00 — 0 commitsWed 17:00 — 1 commitsWed 18:00 — 0 commitsWed 19:00 — 2 commitsWed 20:00 — 1 commitsWed 21:00 — 1 commitsWed 22:00 — 1 commitsWed 23:00 — 0 commitsThu 0:00 — 0 commitsThu 1:00 — 0 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 0 commitsThu 5:00 — 0 commitsThu 6:00 — 0 commitsThu 7:00 — 0 commitsThu 8:00 — 0 commitsThu 9:00 — 0 commitsThu 10:00 — 0 commitsThu 11:00 — 0 commitsThu 12:00 — 0 commitsThu 13:00 — 0 commitsThu 14:00 — 0 commitsThu 15:00 — 0 commitsThu 16:00 — 0 commitsThu 17:00 — 0 commitsThu 18:00 — 0 commitsThu 19:00 — 0 commitsThu 20:00 — 0 commitsThu 21:00 — 0 commitsThu 22:00 — 0 commitsThu 23:00 — 0 commitsFri 0:00 — 0 commitsFri 1:00 — 0 commitsFri 2:00 — 0 commitsFri 3:00 — 0 commitsFri 4:00 — 0 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 0 commitsFri 8:00 — 0 commitsFri 9:00 — 0 commitsFri 10:00 — 0 commitsFri 11:00 — 0 commitsFri 12:00 — 0 commitsFri 13:00 — 0 commitsFri 14:00 — 0 commitsFri 15:00 — 0 commitsFri 16:00 — 0 commitsFri 17:00 — 0 commitsFri 18:00 — 1 commitsFri 19:00 — 0 commitsFri 20:00 — 0 commitsFri 21:00 — 0 commitsFri 22:00 — 0 commitsFri 23:00 — 0 commitsSat 0:00 — 0 commitsSat 1:00 — 0 commitsSat 2:00 — 0 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 0 commitsSat 8:00 — 0 commitsSat 9:00 — 0 commitsSat 10:00 — 0 commitsSat 11:00 — 0 commitsSat 12:00 — 0 commitsSat 13:00 — 0 commitsSat 14:00 — 0 commitsSat 15:00 — 0 commitsSat 16:00 — 0 commitsSat 17:00 — 0 commitsSat 18:00 — 0 commitsSat 19:00 — 0 commitsSat 20:00 — 0 commitsSat 21:00 — 0 commitsSat 22:00 — 0 commitsSat 23:00 — 0 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.
DateListRankStars gained
Feb 12, 2026daily#25+144
Feb 11, 2026daily#12+264
  • ultraworkers/claw-code

    An agent-managed museum exhibit, built in Rust with Gajae-Code / LazyCodex — developed and maintained with no human intervention.

    195K stars · Rust

  • ultraworkers/claw-code

    An agent-managed museum exhibit, built in Rust with Gajae-Code / LazyCodex — developed and maintained with no human intervention.

    194.9K stars · Rust

  • ultraworkers/claw-code

    An agent-managed museum exhibit, built in Rust with Gajae-Code / LazyCodex — developed and maintained with no human intervention.

    194.9K stars · Rust

  • farion1231/cc-switch

    A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io

    125.4K stars · Rust

  • denoland/deno

    A modern runtime for JavaScript and TypeScript.

    108.2K stars · Rust

  • openai/codex

    Lightweight coding agent that runs in your terminal

    104.6K stars · Rust