@qubic-labs/schnorrq
v1.0.2
Published
Standalone SchnorrQ (FourQ) + K12 library for Qubic.
Downloads
320
Readme
ts-schnorrq
Standalone SchnorrQ (FourQ) + K12 library for Qubic.
Status: implemented (pure TypeScript). unsafeSign/sign is not side-channel hardened.
When to use this
- You need a pure-TS SchnorrQ implementation for Qubic-compatible keygen, signing, and verification.
- You’re targeting browsers, Bun, or Node and want a single, portable crypto backend.
- You need deterministic compatibility with the existing Qubic SchnorrQ behavior.
When not to use this
- You need side-channel hardened signing in hostile or multi-tenant environments.
- You need high-throughput signing; use a native/WASM backend instead.
Where to use this
- Good fit: local tools, CLIs, offline signing, server-side verification, browser verification.
- Use with caution: browser or shared servers for signing; prefer external signer backends.
Why this exists
Qubic tooling historically relied on a WASM shim for SchnorrQ. This library replaces that dependency with a portable, readable TypeScript implementation while keeping behavior compatible.
Install
bun add @qubic-labs/schnorrqUsage
import { generatePublicKey, sign, verify } from "@qubic-labs/schnorrq";
// Qubic convention: `subSeed32` is a 32-byte value derived from the 55-char seed elsewhere.
// `messageDigest32` must be a 32-byte digest (SchnorrQ signs digests, not arbitrary-length messages).
const subSeed32 = new Uint8Array(32);
const publicKey32 = generatePublicKey(subSeed32);
const messageDigest32 = new Uint8Array(32);
const signature64 = sign(subSeed32, publicKey32, messageDigest32);
const ok = verify(publicKey32, messageDigest32, signature64);External signer (recommended for production)
import { createSchnorrq } from "@qubic-labs/schnorrq";
const schnorrq = createSchnorrq({
signer: {
sign(subSeed32, publicKey32, messageDigest32) {
// Call into native/WASM/HW/external signer here.
return mySigner.sign(subSeed32, publicKey32, messageDigest32);
},
},
});
const signature64 = schnorrq.sign(subSeed32, publicKey32, messageDigest32);API
k12(input: Uint8Array, dkLen: number): Uint8ArraygeneratePublicKey(subSeed32: Uint8Array): Uint8Array(32 bytes)verify(publicKey32: Uint8Array, messageDigest32: Uint8Array, signature64: Uint8Array): 0 | 1unsafeSign(subSeed32: Uint8Array, publicKey32: Uint8Array, messageDigest32: Uint8Array): Uint8Array(64 bytes)sign(...): alias ofunsafeSign(...)createSchnorrq({ signer }): helper for wiring an external signer backend (recommended for production)
Security notes
unsafeSign/signis a pure-JS/TS implementation and is not designed to be constant-time or side-channel resistant.- “Side-channel hardened” means the implementation avoids leaking secrets through timing, cache/memory access patterns, or branch behavior.
- Prefer
createSchnorrq({ signer })with a constant-time backend (native/WASM/HW/external signer) for high-value secrets.
Performance
- The FourQ math here uses
bigintand is correctness-first. - If you need high throughput, use a native/WASM backend and wire it in via
createSchnorrq.
Development
bun install
bun test
bun run buildRelease (semantic-release)
Automated releases run on pushes to main via .github/workflows/release.yml.
Required secrets:
NPM_TOKEN(npm publish)
