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

@ouronet/dalos-crypto

v4.5.1

Published

DALOS Cryptography — Ouronet's custom 1606-bit Twisted Edwards curve, Schnorr signatures, bitmap key generation, and deterministic RSA-4096 key generation for Arweave from a custom seed phrase. TypeScript port of the Genesis Go reference.

Readme

@ouronet/dalos-crypto

TypeScript port of the DALOS Genesis cryptographic primitive — Ouronet's custom 1606-bit Twisted Edwards curve with six key-generation input paths, Schnorr v2 signatures, AES-256-GCM encryption, a pluggable CryptographicRegistry for multi-generation forward compatibility, and deterministic RSA-4096 key generation for Arweave accounts from the same custom seed phrase.

npm tests license


Install

npm install @ouronet/dalos-crypto
# or
yarn add @ouronet/dalos-crypto

Requires Node ≥ 20 (uses native BigInt + globalThis.crypto.getRandomValues). Runs in the browser without polyfills on any modern evergreen target.

ESM-only. This package ships as a pure ES module ("type": "module" + ESM-only exports conditions). CommonJS consumers using require() will hit ERR_REQUIRE_ESM. Use import syntax with Node ≥ 20, a bundler (Vite, esbuild, Webpack 5+, Rollup), or dynamic import() from CommonJS.


What you get

Genesis curve — DALOS_ELLIPSE

A custom Twisted Edwards curve over P = 2^1605 + 2315 (a 1606-bit prime), with safe-scalar bit width S = 1600. Private key space = 2¹⁶⁰⁰ ≈ 4 × 10⁴⁸¹ — roughly 10⁴⁰⁴ × larger than Bitcoin's.

| Parameter | Value | |---|---| | Name | TEC_S1600_Pr1605p2315_m26 | | Field prime P | 2^1605 + 2315 (1606-bit) | | Subgroup order Q | 2^1603 + 1258387…1380413 (1604-bit prime) | | Cofactor R | 4 | | Coefficients (a, d) | (1, -26) | | Generator G | (2, 479577…907472) | | Safe scalar S | 1600 bits |

Independently audited and verified — see the main repo's AUDIT.md and verification/VERIFICATION_LOG.md.

Six key-generation input paths — one scalar

All six paths produce byte-for-byte identical output with the Go reference's 105-vector test corpus.

| Mode | Input | Typical use | |---|---|---| | random | OS randomness | one-click account spawning | | bitString | 1600-bit 0/1 string | research, direct scalar, paper wallets | | integerBase10 | decimal integer (< Q) | numeric private keys | | integerBase49 | base-49 string (< Q) | DALOS-native compact integer form | | seedWords | array of words (256-glyph DALOS charset) | not BIP-39 — see below | | bitmap | 40×40 Bitmap | hand-painted entropy (1600 pixels = 1600 bits) |

seedWords is deliberately not a BIP-39 mnemonic, and the difference is the point. BIP-39 requires picking from one fixed, English-only, ~2048-word dictionary, in a fixed count (12/15/18/21/24), with a built-in checksum — you can't type a word that isn't on the list. DALOS's seed-word path has no fixed word dictionary: any sequence of glyphs drawn from DALOS's own 256-character set, up to 256 words, each up to 256 glyphs, is a valid phrase — there's no list of permitted whole words. A phrase can be as short as a handful of words or as long as 256 of them — strictly more flexible than a fixed word list, just built on a fixed character set instead of arbitrary Unicode (see the contract below for exactly what that means).

Final contract (settled 2026-09-11), enforced identically everywhere — Go, TypeScript, and the CLI all call the same validator (validateSeedWords / Elliptic.ValidateSeedWords), so what's rejected on one side is rejected on the other, with the same error:

  • 1 to 256 words.
  • 1 to 256 glyphs per word (counted as Unicode code points, matching Go's rune count — not UTF-16 code units or bytes).
  • Every glyph must be one of the 256 characters in CHARACTER_MATRIX_FLAT (ts/src/gen1/character-matrix.ts, mirrors Elliptic/CharacterMatrix.go) — digits, currency signs, the full Latin alphabet plus most Western/Central European diacritics, a curated Greek subset, and a curated Cyrillic subset. This is a closed alphabet, not "any UTF-8 character." In particular: Cyrillic and Greek letters that are visual homoglyphs of a Latin letter already in the set are deliberately excluded (so most everyday Cyrillic words — e.g. привет — contain at least one excluded letter), and there are no accented Greek vowels at all. Calling seedWordsToBitString/fromSeedWords/any registry primitive's generateFromSeedWords with input outside this contract throws InvalidSeedWordsError (exported from /gen1) before any hashing happens — it is not possible to reach key generation with invalid seed-word input.
import { validateSeedWords, InvalidSeedWordsError } from "@ouronet/dalos-crypto/gen1";

const userTypedWords = ["mountain", "whisper", "aurora", "eternal"];

try {
  validateSeedWords(userTypedWords); // same gate generateFromSeedWords runs internally
} catch (e) {
  if (e instanceof InvalidSeedWordsError) {
    // e.message pinpoints which word/character/count failed — surface it directly.
  }
}

Schnorr v2 signatures

Full hardened Schnorr implementation with RFC-6979-style deterministic nonces (Blake3-tagged KDF), length-prefixed Fiat-Shamir challenge, and domain-tag separation. See docs/SCHNORR_V2_SPEC.md.

AES-256-GCM with Blake3 KDF

Matches the Go reference's key-file encryption format exactly — the TS port additionally constrains the IV nibble to avoid a latent Go-side edge case (≈6% failure rate in Go; 0% in TS).

Historical curves (since v1.1.0, production-ready since v3.0.0)

Three extra curves from the author's original Cryptoplasm research phase, named after the Delian family. Same structural family as DALOS (Twisted Edwards, cofactor 4, negative d), smaller primes than DALOS Genesis. As of v3.0.0 all three are full production CryptographicPrimitives — complete key-gen across all 5 input paths (random / bitString / integerBase10 / integerBase49 / seedWords) plus Schnorr v2 sign/verify, with their own frozen byte-identity corpus (testvectors/v1_historical.json). The only thing "historical" about them today is that they predate DALOS Genesis, not that they're unsupported. They are not auto-registered in createDefaultRegistry() — import them explicitly from /registry and use them directly (or call registry.register(Apollo) if you need registry.detect() to recognise their addresses too).

| Curve | Safe-scalar S | Prime P | Keyspace | Standard / smart prefix | |---|---|---|---|---| | LETO | 545 bits | 2^551 + 335 | 2⁵⁴⁵ ≈ 1.15 × 10¹⁶⁴ | Ł. / Λ. | | ARTEMIS | 1023 bits | 2^1029 + 639 | 2¹⁰²³ ≈ 9.0 × 10³⁰⁷ | R. / Ř. | | APOLLO | 1024 bits | 2^1029 + 639 | 2¹⁰²⁴ ≈ 1.8 × 10³⁰⁸ | ₱. / Π. |

APOLLO's 1024-bit derivation is also the other seed length the RSA-4096 package accepts (see below) — a byte-aligned alternative to DALOS Genesis's 1600 bits, from the exact same seed words:

import { Apollo } from "@ouronet/dalos-crypto/registry";
import { generateFromBitStringAsync } from "@ouronet/dalos-crypto/rsa4096";

const seedWords = ["korrigan", "petrichor", "solstice", "yonder"];

// EC derivation: instant, synchronous.
const apolloKey = Apollo.generateFromSeedWords(seedWords);
console.log(apolloKey.privateKey.bitString.length); // 1024
console.log(apolloKey.standardAddress);              // "₱.xxxxx…"

// Feed the same 1024-bit seed into the RSA-4096 prime search (async —
// takes low-single-digit seconds; see "Progress reporting" below).
const rsaKey = await generateFromBitStringAsync(apolloKey.privateKey.bitString);
console.log(rsaKey.address); // 43-char Arweave address

See docs/HISTORICAL_CURVES.md for the full provenance, audit log, and usage.

Deterministic RSA-4096 for Arweave (new)

The same custom seed phrase that mints your DALOS Genesis EC account can also deterministically mint a real, standards-compliant RSA-4096 keypair — the exact key format Arweave requires. Same seed in, same keypair out, byte-for-byte, forever, on any machine — including the Arweave address.

Why this doesn't normally exist: RSA key generation is fundamentally a probabilistic search for two large primes, not one algebraic step like EC key derivation — and every mainstream RSA implementation deliberately resists being made reproducible. We traced this ourselves rather than assume it: Go's standard library silently ignores a caller-supplied random source by default since Go 1.26, and even its escape hatch has a coin-flip anti-determinism safeguard that's been there since 2018, specifically to stop callers from relying on rsa.GenerateKey being seed-reproducible. So this package doesn't wrap a standard RSA generator — it implements the prime search itself from scratch (FIPS 186-5, Miller-Rabin, unbiased rejection-sampled witnesses), sourcing every single random-looking byte from one seeded Blake3-XOF stream. No crypto.getRandomValues, no Math.random, no OS entropy anywhere in the path — verified by grepping the entire dependency chain, not just asserted.

How it actually works, stage by stage (implemented identically, and cross-validated field-by-field, in RSA4096/*.go and src/rsa4096/*.ts):

  1. Seed → endless deterministic byte stream. The seed bitstring is hashed (as its literal ASCII '0'/'1' characters — deliberately not bit-packed, which would introduce an MSB/LSB ordering choice that's an easy place for two independent implementations to silently disagree) into a Blake3 XOF (extendable-output hash) — the same primitive already shipped for the EC path, reused rather than introducing a new DRBG. A dedicated domain tag (DALOS-gen1/RSA4096Stream/v1) keeps it isolated from every other DALOS construction.
  2. Candidate generation. Pull 256 fresh bytes (2048 bits) off the stream; force the top two bits to 1 (guarantees the number is really 2048 bits, and that two such numbers multiply to exactly 4096) and the bottom bit to 1 (every prime above 2 is odd). 2045 of the 2048 bits are untouched seed material.
  3. Cheap rejection. Trial-divide against the first 2000 odd primes (generated by a Sieve of Eratosthenes at call time, not a hardcoded table — chosen from an explicit cost-model computation: one division costs ~32 word-operations, one full primality round costs ~3.1 million on a 2048-bit number, a ~98,000× gap, and 2000 sits inside the flat optimum of that tradeoff).
  4. The real test. Hand-rolled Miller-Rabin, 100 rounds (one-time cost per seed, no reason not to buy the margin), witnesses drawn from the same seeded stream via rejection sampling — not mod, which would be measurably biased here (every candidate's forced top bits mean n always sits in a narrow high range, and n-3 doesn't evenly divide 2^2048). This is a from-scratch implementation, not a call to a library's primality function — math/big.Int.ProbablyPrime appears only in this package's own test suite, as an independent oracle to catch bugs, never on the real generation path.
  5. Safety checks. p ≠ q, |p-q| > 2^1948 (FIPS 186-5's Fermat-factorization-resistance bound), gcd(65537, prime-1) = 1 for each prime individually.
  6. Key assembly. n = p·q, e = 65537 (fixed, universal, never derived from the seed), d = e⁻¹ mod λ(n) using the Carmichael function — confirmed empirically (against a real openssl genrsa 4096 output) to match what OpenSSL/WebCrypto actually produce.
  7. The Arweave-specific step. Package as the canonical 9-field JWK and derive the address as Base64URL(SHA-256(n)) — verified against arweave-core's own address.ts/keyfile.ts logic directly.

Zero real entropy anywhere in that chain — verified by grepping, not asserted. Checked four places: this package's own code, the Blake3 package it calls into, and (on the TS side) @noble/hashes's blake3/ sha2 implementations underneath it. No crypto.getRandomValues, no Math.random, no OS entropy anywhere. Every byte the search ever consumes traces back deterministically to the one input seed.

Validated against real, independent Arweave code, not just internal self-checks: the actual arweave-core package's importKeyfile() and addressOf() accept the generated keys and reproduce the address byte-for-byte, and Node's native WebCrypto completes a real RSA-PSS/ SHA-256 sign→verify round-trip with them. The Go reference and this TS port are cross-validated field-by-field — including the exact internal candidate-search counts, not just the final output — against a frozen test-vector corpus.

As far as our research could establish, no other audited, production- grade library exposes this. The one community project we found attempting seed-derived Arweave keys uses non-standard derivation and has open, reported determinism bugs — which lines up exactly with the entropy-leak failure mode this package was built specifically to avoid.

import { generateFromBitString } from "@ouronet/dalos-crypto/rsa4096";

// Same 1600-bit seed you'd feed to DalosGenesis.generateFromBitString —
// deterministically produces a full RSA-4096 keypair + Arweave address
// instead of (or alongside) an EC account.
const bits1600 = "1".repeat(800) + "0".repeat(800);
const result = generateFromBitString(bits1600);

console.log(result.address);  // 43-char base64url Arweave address
console.log(result.jwk);      // canonical 9-field Arweave JWK (kty, n, e, d, p, q, dp, dq, qi)

// Same seed, run again (even on a different machine) -> identical output.

Generation costs low-single-digit seconds (finding two real 2048-bit primes isn't cheap, and this runs 100 Miller-Rabin rounds per prime — the top of the 64-100 range the design calls for, since this is a one-time-per-seed operation with no per-transaction cost to amortize). Use generateFromBitStringAsync for a UI: it yields to the event loop periodically (same mechanism as schnorrSignAsync/scalarMultiplierAsync above) so a real progress bar can actually repaint while it runs:

import { generateFromBitStringAsync, type ProgressEvent } from "@ouronet/dalos-crypto/rsa4096";

const bits1600 = "1".repeat(800) + "0".repeat(800);

function onProgress(ev: ProgressEvent) {
  // ev.stage is "p" or "q"; ev.overallProgress is a mathematically-honest
  // 0..1 estimate (a memoryless-search completion probability, not a
  // guess) suitable for driving a <progress> element directly.
  console.log(`${ev.stage}: attempt ${ev.attempts}, ~${(ev.overallProgress * 100).toFixed(0)}%`);
}

const result = await generateFromBitStringAsync(bits1600, onProgress);
console.log(result.address); // 43-char base64url Arweave address

The seed isn't hardcoded to DALOS Genesis's 1600 bits — APOLLO's 1024-bit safe-scalar bitstring works too. But the accepted lengths are a closed allow-list, not an open floor: exactly 1024 or exactly 1600, and nothing else, however long. generateFromBitString/generateFromBitStringAsync throw for any other length (settled 2026-09-11 — an earlier, wider "any length ≥ 128 bits" contract was deliberately tightened). RSA-4096's security comes from the 2048-bit prime search space, not seed length, so a longer or custom-length seed buys nothing; gating to exactly these two lengths also ties every RSA seed to one of DALOS_Crypto's two production EC curves' own already-validated input pipelines, rather than accepting arbitrary unaudited bytes.

See .docs/deterministic-rsa4096-from-seed.md for the full design history, empirical research, and everything checked along the way.

One seed, many independent Arweave addresses

A seed phrase's whole point is to derive many usable accounts, not just one — but RSA has no BIP-32-style non-hardened child-key trick (no additive homomorphism the way EC scalars have), so every "child" address here really is an independent full keypair. generateFromBitStringAtIndex derives address #index from the same seed bitstring by running one extra domain-separated Blake3 hash to produce a fresh, independent seed per index, then the same unmodified prime search:

import { generateFromBitStringAtIndex } from "@ouronet/dalos-crypto/rsa4096";

const bits1600 = "1".repeat(800) + "0".repeat(800);

const address0 = generateFromBitStringAtIndex(bits1600, 0); // == generateFromBitString(bits1600), forever
const address1 = generateFromBitStringAtIndex(bits1600, 1); // a real, independent second address
const address777 = generateFromBitStringAtIndex(bits1600, 777); // directly reachable -- no need to generate 1..776 first

console.log(address0.address, address1.address, address777.address);

index === 0 is structurally guaranteed byte-identical, forever, to calling generateFromBitString directly — this can never silently change any address you already generated. Any index is directly reachable without generating the ones before it: a pure function of (seed, index), not a "walk forward from 0" model.

For "the first N addresses" (or any startIndex..startIndex+count-1 range), generateBatchFromBitStringAsync adds sequential orchestration with ONE combined progress readout across the whole batch, plus results delivered incrementally as each one completes:

import {
  generateBatchFromBitStringAsync,
  type BatchProgressEvent,
} from "@ouronet/dalos-crypto/rsa4096";

const bits1600 = "1".repeat(800) + "0".repeat(800);

function onProgress(ev: BatchProgressEvent) {
  // ev.overallProgress combines this address's own progress with how
  // many of the batch's addresses are already done -- one honest 0..1
  // readout for the whole run, not just the address in flight.
  console.log(`address #${ev.index}: ~${(ev.overallProgress * 100).toFixed(0)}% of the whole batch`);
}

function onResult(index: number, result: { address: string }) {
  console.log(`address #${index} ready: ${result.address}`); // render progressively, don't wait for all of them
}

const firstHundred = await generateBatchFromBitStringAsync(bits1600, 0, 100, onProgress, onResult);
console.log(firstHundred.length); // 100 independent Arweave addresses from one seed

A failure partway through a batch throws BatchGenerationError, which carries .completed — the results already finished before the failure — so a caller never has to discard already-completed, multi-second-cost work just because a later index failed.

For an arbitrary LIST of ranges — not just one contiguous startIndex..startIndex+count-1 block — generateFromBitStringAtRangesAsync generalizes the batch API: give it 1-100, 134-167, 234-777 and it deterministically generates the union of all three, deduplicated and sorted ascending. Index 0 is always included, even if none of your ranges cover it:

import {
  generateFromBitStringAtRangesAsync,
  type IndexRange,
} from "@ouronet/dalos-crypto/rsa4096";

const bits1600 = "1".repeat(800) + "0".repeat(800);

const ranges: IndexRange[] = [
  { start: 1, end: 100 },
  { start: 134, end: 167 },
  { start: 234, end: 777 },
];

const addresses = await generateFromBitStringAtRangesAsync(bits1600, ranges);
console.log(addresses.length); // 1 (index 0, always included) + 100 + 34 + 544 = 679
console.log(addresses[0]!.address); // index 0's address, first, deterministically

It's a separate function from generateBatchFromBitStringAsync, not a modification of it — that function's exact contract (precisely [startIndex, startIndex+count-1], no implicit index 0 unless it's already in range) is already published and unchanged for existing callers. Same BatchProgressEvent/onResult/BatchGenerationError vocabulary as the batch API above — no new types to learn.


Quick start

Mint an Ouronet account (every mode)

import { type Bitmap } from "@ouronet/dalos-crypto/gen1";
import { DalosGenesis } from "@ouronet/dalos-crypto/registry";

// 1 — OS randomness (simplest)
const a = DalosGenesis.generateRandom();

// 2 — from a custom seed phrase (any language, 4–256 words)
const b = DalosGenesis.generateFromSeedWords([
  "mountain", "whisper", "aurora", "eternal", "signal", "zen",
]);

// 3 — from a 1600-bit binary string (any sequence qualifies)
const bits1600 = "1".repeat(800) + "0".repeat(800);
const c = DalosGenesis.generateFromBitString(bits1600);

// 4 — from a base-10 integer (must be in curve range; core throws if not)
const d = DalosGenesis.generateFromInteger("123456789012345", 10);

// 5 — from a base-49 integer (DALOS alphabet, 0-9 a-z A-M)
const e = DalosGenesis.generateFromInteger("hello42", 49);

// 6 — from a 40×40 bitmap (row-major, true = black, false = white)
//      Note: the TS port intentionally omits the Go reference's
//      ParsePngFileToBitmap helper (Bitmap/Bitmap.go:178). PNG decoding
//      adds bundle weight and assumes filesystem access not available
//      in browser environments. Construct the boolean[][] directly from
//      whatever input source you have (PNG via @napi-rs/canvas in Node,
//      <canvas> ImageData in browser, hand-painted via UI, etc.).
const bitmap: Bitmap = Array.from({ length: 40 }, () => Array<boolean>(40).fill(false));
const f = DalosGenesis.generateFromBitmap(bitmap);

// Every `FullKey` has:
console.log(f.keyPair.priv);          // base-49 private key
console.log(f.keyPair.publ);          // base-49 prefixed public key
console.log(f.privateKey.bitString);  // 1600-char binary
console.log(f.privateKey.int10);      // base-10 representation
console.log(f.privateKey.int49);      // base-49 representation
console.log(f.standardAddress);       // Ѻ.xxxxx…   (160 chars)
console.log(f.smartAddress);          // Σ.xxxxx…   (160 chars)

// All six paths feed the same Genesis pipeline; here are their addresses.
const accounts = [a, b, c, d, e, f];
console.log(`Generated ${accounts.length} accounts via 6 different input paths.`);
console.log(accounts.map((acc) => acc.standardAddress));

Sign + verify (Schnorr v2)

import { SchnorrSignError, sign, verify } from "@ouronet/dalos-crypto/gen1";
import { DalosGenesis } from "@ouronet/dalos-crypto/registry";

const account = DalosGenesis.generateRandom();
let sig = "";
try {
  sig = sign(account.keyPair, "hello world");
} catch (e) {
  if (e instanceof SchnorrSignError) {
    console.error("sign failed:", e.message);
  }
  throw e;
}
console.log(verify(sig, "hello world", account.keyPair.publ)); // true

Browser-friendly async signing (since v3.1.0)

For browser consumers running Schnorr at full curve scale, the synchronous variants block the UI thread for hundreds of milliseconds to seconds; the async variants yield to the event loop every 8 outer-loop iterations on a fixed data-independent cadence and keep Interaction-to-Next-Paint (INP) under 200 ms.

Three additive functions, all re-exported from @ouronet/dalos-crypto/gen1: scalarMultiplierAsync, schnorrSignAsync, schnorrVerifyAsync. The yield trigger depends only on the scalar-mult outer-loop iteration index — never on the scalar value or any secret-derived branch — so the constant-time property of the synchronous path is preserved. Output is byte-identical to the sync variants for the same inputs (deterministic v2 RFC-6979-style nonces).

import { schnorrSignAsync, schnorrVerifyAsync } from "@ouronet/dalos-crypto/gen1";
import { DalosGenesis } from "@ouronet/dalos-crypto/registry";

const account = DalosGenesis.generateRandom();
const sig = await schnorrSignAsync(account.keyPair, "hello world");
console.log(await schnorrVerifyAsync(sig, "hello world", account.keyPair.publ)); // true

This is the recommended path for any UI thread that issues Q-1-scale operations; the sync variants remain the default for Node/server contexts where blocking is acceptable.

AES encryption (Genesis-compatible key-file format)

import { decrypt, encrypt } from "@ouronet/dalos-crypto/gen1";

const cipher = await encrypt("secret message", "strong-password");
const recovered = await decrypt(cipher, "strong-password");
console.log(recovered === "secret message"); // true

Detect which primitive minted an address

import { createDefaultRegistry, DalosGenesis } from "@ouronet/dalos-crypto/registry";

const registry = createDefaultRegistry();
const account = DalosGenesis.generateRandom();
const detected = registry.detect(account.standardAddress);
if (detected) console.log(detected.id); // "dalos-gen-1"

Subpaths

// Per-subpath narrow imports (recommended for tree-shaking).
import { fromRandom } from "@ouronet/dalos-crypto/gen1";
import { createDefaultRegistry, DalosGenesis } from "@ouronet/dalos-crypto/registry";
import { LETO } from "@ouronet/dalos-crypto/historical";
import { blake3SumCustom } from "@ouronet/dalos-crypto/dalos-blake3";
import { generateFromBitString } from "@ouronet/dalos-crypto/rsa4096";

// All five subpaths exist; pick whichever surface area you need.
console.log(typeof fromRandom, typeof DalosGenesis, typeof createDefaultRegistry, typeof LETO, typeof blake3SumCustom, typeof generateFromBitString);

Every subpath has first-class TypeScript types.


Byte-identity with Go reference

Core value proposition: the same input produces the same output as the Go service at go.ouronetwork.io/api/generate. The port is validated against 105 canonical test vectors:

  • 50 bitstring → keys → addresses
  • 15 seed-word fixtures (ASCII + Unicode)
  • 20 bitmap fixtures (hand-designed + deterministic-random)
  • 20 Schnorr sign + self-verify

Plus [Q]·G = O end-to-end verification per curve. The RSA-4096 package carries the same guarantee against its own frozen corpus (testvectors/v2_rsa4096.json) — every field of every vector, including the exact internal candidate-search counts (proof the two implementations consume the seed identically, not just coincidentally agree on the final key). Run locally:

npm test   # 479 tests, ~60-90s

See the Go-reference corpora: testvectors/v1_genesis.json and testvectors/v2_rsa4096.json.


Security notes

  • No console leakage. The library never logs key material.
  • Constant-time where it matters. The base-49 Horner scalar-mult uses a branch-free linear scan over the precompute matrix (SC-7). See src/gen1/scalar-mult.ts + docs/SCHNORR_V2_SPEC.md.
  • Genesis freeze. Key-generation output is permanently frozen at v1.0.0. Any future additions (new input modes, new curves) MUST preserve byte-identity for existing inputs. The historical curves added in v1.1.0 are additive and do not alter Genesis behaviour.
  • Schnorr v2 deterministic nonces — signatures are reproducible from (message, privateKey); there is no randomness dependency and no nonce-reuse attack surface.
  • AES-256-GCM IV constraint — TS port rejects IVs whose high nibble is zero, eliminating a latent round-trip failure present in the Go reference (~6% of randomly-generated IVs). Ciphertexts produced by the TS port decrypt cleanly on both TS and Go sides.
  • RSA-4096 touches zero real entropy, verified not just asserted. The entire dependency chain — src/rsa4096/*, the Blake3-XOF stream it's built on, and @noble/hashes's underlying blake3/sha2 implementations — was grepped for Math.random, crypto.getRandomValues, and every other entropy source; there are none. Every byte the prime search consumes traces back deterministically to the input seed.

Licence

Proprietary — Copyright © 2026 AncientHoldings GmbH. All rights reserved. See ../LICENSE.


Links