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

@qubic.org/crypto

v0.2.7

Published

Qubic cryptographic primitives: KangarooTwelve hashing, FourQ elliptic curve arithmetic, SchnorrQ signatures, and seed/identity derivation.

Downloads

1,390

Readme

@qubic.org/crypto

Qubic cryptographic primitives: KangarooTwelve hashing, FourQ elliptic curve arithmetic, SchnorrQ signatures, and seed/identity derivation.

This package implements the full Qubic cryptographic stack in pure TypeScript, without native addons. It is a direct port of the Go and C++ reference implementations used by the Qubic network. @qubic.org/tx depends on this package to sign and hash transactions.

Installation

bun add @qubic.org/crypto

Dependencies: @noble/hashes, @qubic.org/types

API

Hashing

function k12(data: Uint8Array, outputLength?: number): Uint8Array

Computes a KangarooTwelve (KT128) hash of arbitrary length. Defaults to 32 bytes. Backed by @noble/hashes's audited kt128 implementation. Qubic uses K12 everywhere SHA-256 would appear in Bitcoin-style systems.

Seed and key derivation

function generateRandomSeed(): Seed

Generates a cryptographically secure random 55-character lowercase seed using globalThis.crypto.getRandomValues. Returns a branded Seed.

function publicKeyFromSeed(seed: Seed): Uint8Array

Derives the 32-byte compressed FourQ public key from a seed. Equivalent to running the full key derivation pipeline without returning intermediate values.

function deriveIdentityFromSeed(seed: Seed): Identity

Derives the 60-character uppercase Qubic identity string from a seed. This is the primary function for turning a seed into an address.

Identity / public key encoding

function publicKeyToIdentity(publicKey: Uint8Array): Identity

Encodes a 32-byte compressed FourQ public key into the 60-character uppercase Qubic identity format. The encoding splits the key into four 64-bit little-endian fragments, converts each digit to base-26, then appends a 4-character checksum computed via k12(publicKey, 3).

function identityToPublicKey(identity: Identity): Uint8Array

Decodes a 60-character identity back to its 32-byte public key. Validates the 4-character checksum and throws InvalidIdentityError if it does not match.

Signing and verification

function sign(message: Uint8Array, seed: Seed): Promise<Uint8Array>

Signs a message digest using SchnorrQ over the FourQ elliptic curve. Internally runs key derivation to obtain the subseed and public key, then executes the SchnorrQ signing protocol. Returns a 64-byte signature (R || s). The message parameter is typically a 32-byte K12 digest computed by @qubic.org/tx — it is not automatically hashed here.

function verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean

Verifies a SchnorrQ signature. Returns true if valid, false otherwise. Does not throw on invalid input — malformed signatures produce false.

Randomness

function getRandomBytes(n: number): Uint8Array

Returns n cryptographically secure random bytes from globalThis.crypto.getRandomValues. Throws RangeError if n <= 0.

Error handling

class InvalidSeedError    extends QubicError  // code: 'INVALID_SEED'
class InvalidIdentityError extends QubicError // code: 'INVALID_IDENTITY'
class SignatureVerificationError extends QubicError // code: 'SIGNATURE_VERIFICATION_FAILED'

deriveIdentityFromSeed, publicKeyFromSeed, and sign throw InvalidSeedError if the seed is not exactly 55 lowercase ASCII letters. identityToPublicKey throws InvalidIdentityError if the identity is malformed or the checksum fails.

Examples

Generate a fresh wallet

import { generateRandomSeed, deriveIdentityFromSeed } from '@qubic.org/crypto'

const seed = generateRandomSeed()
const identity = deriveIdentityFromSeed(seed)

console.log('Seed:    ', seed)      // 55 lowercase letters
console.log('Address: ', identity)  // 60 uppercase letters

Sign arbitrary data

import { k12, sign, verify, publicKeyFromSeed } from '@qubic.org/crypto'
import { toSeed } from '@qubic.org/types'

const seed = toSeed('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
const message = new TextEncoder().encode('hello qubic')
const digest = k12(message, 32)

const signature = await sign(digest, seed)
const publicKey = publicKeyFromSeed(seed)

const valid = verify(digest, signature, publicKey)
console.log('Signature valid:', valid)  // true

Decode an identity to its public key

import { identityToPublicKey, publicKeyToIdentity } from '@qubic.org/crypto'
import { toIdentity } from '@qubic.org/types'

const identity = toIdentity('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
const publicKey = identityToPublicKey(identity)
console.log('Public key bytes:', publicKey)  // Uint8Array(32)

// Round-trip
const roundTripped = publicKeyToIdentity(publicKey)
console.log('Round-trip match:', roundTripped === identity)  // true

Hash data with K12

import { k12 } from '@qubic.org/crypto'

const data = new Uint8Array([1, 2, 3, 4])
const digest32 = k12(data, 32)
const digest64 = k12(data, 64)
console.log('32-byte digest:', digest32)
console.log('64-byte digest:', digest64)

Design notes

Why KangarooTwelve instead of SHA-256 or Keccak-256? K12 is the XOF (extendable-output function) used throughout the Qubic protocol. It has variable output length, which the Qubic identity encoding and SchnorrQ signing both rely on. Using any other hash would produce incompatible addresses and signatures.

Why SchnorrQ over Ed25519 or ECDSA? Qubic chose SchnorrQ on the FourQ curve for its small key/signature sizes and performance characteristics on constrained hardware. FourQ supports constant-time scalar multiplication which is important for a blockchain's security model. This package ports the reference implementation faithfully to ensure compatibility with on-chain signature verification.

Why is sign async? The async marker is intentional forward-compatibility: future runtime targets (e.g. browser WebCrypto, hardware security modules) may provide native SchnorrQ primitives via a Promise-based API. The current implementation resolves synchronously.

Key derivation pipeline:

graph LR
  seed["seed<br/><sub>55 lowercase chars</sub>"]
  seedBytes["seedBytes<br/><sub>char − 'a' per byte</sub>"]
  subseed["subseed<br/><sub>k12(seedBytes, 32)</sub>"]
  privateKey["privateKey<br/><sub>k12(subseed, 32)</sub>"]
  point["point<br/><sub>FourQ.scalarBaseMult</sub>"]
  publicKey["publicKey<br/><sub>32 bytes</sub>"]
  identity["identity<br/><sub>60 chars</sub>"]

  seed --> seedBytes --> subseed --> privateKey --> point --> publicKey --> identity