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

@noy-db/on-shamir

v0.6.0

Published

k-of-n Shamir Secret Sharing of the vault KEK for multi-party unlock. Any K of N shares recombines the key; fewer than K leaks zero bits. Composable — each share can be protected by any other on-* method. Zero runtime dependencies.

Readme

@noy-db/on-shamir

k-of-n Shamir Secret Sharing of the vault KEK for multi-party unlock. Any K of N enrolled shares recombines the KEK; fewer than K leaks zero bits.

The defining feature is composability — each share can itself be protected by any other @noy-db/on-* method. Share 1 behind a WebAuthn passkey, share 2 behind an OIDC login, share 3 printed on paper in a corporate safe. Fractional trust across different authentication modes.

Part of the @noy-db/on-* authentication family.

Install

pnpm add @noy-db/on-shamir

Use cases

  • "Any 2 of 3 admins must authorise unlocking the audit vault."
  • "CFO + COO consent required to unlock the CEO vault during vacation."
  • "3-of-5 board escrow — the vault survives any 2 resignations."
  • "Executive key is split across a passkey + OIDC + paper backup — any 2 of 3 unlock."

Threat model

Protects against:

  • Up to K-1 colluding share holders (mathematically — fewer than K shares reveals zero bits of the KEK)
  • Loss of up to N-K shares (remaining K shares still reconstruct)

Does NOT protect against:

  • K colluding share holders (by design — that's the threshold contract)
  • Device compromise of the combining machine during reconstruction (the KEK is briefly in memory; if that machine is malicious, no library-level hedge helps)
  • Side-channel attacks on the Lagrange interpolation (not constant-time by design — the threat model assumes a trusted combine-device)

Math

Shamir Secret Sharing over GF(2^8), byte-wise. For each byte of the KEK:

  1. Construct a random polynomial of degree k-1 whose constant term is the KEK byte.
  2. Each share is the polynomial evaluated at a distinct x-coordinate (1..255; x=0 reserved because it would reveal the byte).
  3. Lagrange interpolation at x=0 given any K points recovers the byte.

Implemented in ~120 LoC of pure TypeScript (gf256.ts + shamir.ts). Zero cryptographic dependencies — just Web Crypto's getRandomValues for randomness and subtle.importKey to rehydrate the reconstructed KEK.

Reduction polynomial: x^8 + x^4 + x^3 + x + 1 (0x11b, same as AES).

Usage

Enroll — after primary unlock

import { splitKEK, encodeShareBase32 } from '@noy-db/on-shamir'

const shares = await splitKEK(currentKEK, { k: 2, n: 3 })

// Each share is now a RawShare structure. Serialise for distribution:
const shareStrings = shares.map(encodeShareBase32)
// Example: 'SHAMIR_S1_K2N3__AKHT-P4L7-...'

// Distribute via any on-* method:
//   shareStrings[0] → store under a WebAuthn-protected keyring entry
//   shareStrings[1] → store under an OIDC-protected keyring entry
//   shareStrings[2] → print on paper, put in the corporate safe

Unlock — collect K shares and combine

import { combineKEK, decodeShareBase32 } from '@noy-db/on-shamir'

// Unlock each share via its own on-* method (not shown — uses whichever
// on-webauthn / on-oidc / on-recovery / on-magic-link the holder chose).
const shareA = decodeShareBase32(shareStringFromCFO)
const shareB = decodeShareBase32(shareStringFromCOO)

// Combine — returns a non-extractable KEK ready to use
const kek = await combineKEK([shareA, shareB])

Low-level — for custom integrations

import { splitSecret, combineSecret } from '@noy-db/on-shamir'

const shares = splitSecret(new Uint8Array(secretBytes), 2, 3)
const recovered = combineSecret([shares[0], shares[1]])
// recovered is a Uint8Array — you handle its lifecycle

JSON form — store shares inside other on-* keyring entries:

import { encodeShareJSON, decodeShareJSON } from '@noy-db/on-shamir'

const json = encodeShareJSON(shares[0])
// { v: 1, x: 1, k: 2, n: 3, y: '<base64>' }
await keyring.put('_recovery_share_1', json)

API

// High-level — wraps a CryptoKey
async function splitKEK(kek: CryptoKey, options: { k: number; n: number }): Promise<RawShare[]>
async function combineKEK(shares: readonly RawShare[]): Promise<CryptoKey>

// Low-level — operates on raw bytes
function splitSecret(
  secret: Uint8Array,
  k: number,
  n: number,
  randomBytes?: (count: number) => Uint8Array,  // Injectable for tests
): RawShare[]
function combineSecret(shares: readonly RawShare[]): Uint8Array

// Serialisation
function encodeShareBytes(share: RawShare): Uint8Array
function decodeShareBytes(bytes: Uint8Array): RawShare

function encodeShareBase32(share: RawShare): string
function decodeShareBase32(input: string): RawShare

function encodeShareJSON(share: RawShare): ShareJSON
function decodeShareJSON(json: ShareJSON): RawShare

interface RawShare {
  x: number          // 1..255
  y: Uint8Array      // One byte per secret byte
  k: number          // Threshold
  n: number          // Total
}

// GF(2^8) arithmetic — exported for composition / auditing
function gfAdd(a: number, b: number): number
function gfMul(a: number, b: number): number
function gfInv(a: number): number
function gfDiv(a: number, b: number): number
function gfPolyEval(coeffs: readonly number[], x: number): number
function lagrangeInterpolateAtZero(points: readonly [number, number][]): number

Share format

Binary (6-byte header + y-bytes):

offset  size  field
0       1     version (= 1)
1       1     x-coordinate (1..255)
2       1     k (threshold)
3       1     n (total)
4       2     byteLength (big-endian uint16)
6+      L     y-bytes (L = byteLength)

For a 32-byte KEK: 38 bytes total per share.

Base32 form includes a human-readable prefix (SHAMIR_S{x}_K{k}N{n}__) followed by the payload in groups of 4:

SHAMIR_S2_K2N3__AKHT-P4L7-KDFG-H3JX-M8E...

The prefix is stripped by the decoder — metadata is recovered from the binary header. Consumer tools can show the prefix to the user for at-a-glance share identification without trusting it.

Composability recipe — Shamir + any on-* method

import { splitKEK, encodeShareJSON } from '@noy-db/on-shamir'
import { createMagicLinkToken } from '@noy-db/on-magic-link'
// Plus whichever other on-* packages you use

const shares = await splitKEK(currentKEK, { k: 2, n: 3 })

// Share 1 — passkey
await webAuthn.enrollWithPayload(ceoPasskey, encodeShareJSON(shares[0]))

// Share 2 — magic link to an auditor's email
const link = createMagicLinkToken('escrow-vault', { ttlMs: 30 * 24 * 60 * 60 * 1000 })
await emailAuditor(link, encodeShareJSON(shares[1]))

// Share 3 — paper backup
console.log('Corporate-safe backup:', encodeShareBase32(shares[2]))

Performance

GF(2^8) operations are table-lookup O(1) — all micro-operations run in nanoseconds. Splitting a 32-byte KEK into 3 shares takes sub-millisecond. Combining likewise. Web Crypto's importKey on the reconstructed KEK is the dominant cost (~1ms).

License

MIT