@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.
Maintainers
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
CryptographicRegistryfor multi-generation forward compatibility, and deterministic RSA-4096 key generation for Arweave accounts from the same custom seed phrase.
Install
npm install @ouronet/dalos-crypto
# or
yarn add @ouronet/dalos-cryptoRequires 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-onlyexportsconditions). CommonJS consumers usingrequire()will hitERR_REQUIRE_ESM. Useimportsyntax with Node ≥ 20, a bundler (Vite, esbuild, Webpack 5+, Rollup), or dynamicimport()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, mirrorsElliptic/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. CallingseedWordsToBitString/fromSeedWords/any registry primitive'sgenerateFromSeedWordswith input outside this contract throwsInvalidSeedWordsError(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 addressSee 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):
- 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. - 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 to1(every prime above 2 is odd). 2045 of the 2048 bits are untouched seed material. - 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).
- 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 meannalways sits in a narrow high range, andn-3doesn't evenly divide2^2048). This is a from-scratch implementation, not a call to a library's primality function —math/big.Int.ProbablyPrimeappears only in this package's own test suite, as an independent oracle to catch bugs, never on the real generation path. - Safety checks.
p ≠ q,|p-q| > 2^1948(FIPS 186-5's Fermat-factorization-resistance bound),gcd(65537, prime-1) = 1for each prime individually. - 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 realopenssl genrsa 4096output) to match what OpenSSL/WebCrypto actually produce. - The Arweave-specific step. Package as the canonical 9-field JWK
and derive the address as
Base64URL(SHA-256(n))— verified againstarweave-core's ownaddress.ts/keyfile.tslogic 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 addressThe 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 seedA 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, deterministicallyIt'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)); // trueBrowser-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)); // trueThis 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"); // trueDetect 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-90sSee 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 underlyingblake3/sha2implementations — was grepped forMath.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
- Main repo: github.com/StoaChain/DALOS_Crypto
- Architecture deep-dive:
docs/DALOS_CRYPTO_GEN1.md - TS port phase tracker:
docs/TS_PORT_PLAN.md - Historical curves:
docs/HISTORICAL_CURVES.md - Schnorr spec:
docs/SCHNORR_V2_SPEC.md - Gitbook: demiourgos-holdings-tm.gitbook.io
