Implement geo backend, TS client, frontend, and CI tests.

Add a Go HTTP API with Ed25519 auth and invitation onboarding, user-scoped GeoJSON Point management, a Bun-tested @noble/ed25519 TypeScript client, static Vue/Vuetify frontend integration, and a Gitea CI workflow running both Go and Bun test suites.

Made-with: Cursor
This commit is contained in:
2026-03-01 11:41:21 +00:00
parent 5c73295ce5
commit 6e2becb06a
164 changed files with 446560 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Paul Miller (https://paulmillr.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+346
View File
@@ -0,0 +1,346 @@
# noble-ed25519
Fastest 5KB JS implementation of ed25519 signatures.
- ✍️ [EDDSA](https://en.wikipedia.org/wiki/EdDSA) signatures compliant with
[RFC8032](https://tools.ietf.org/html/rfc8032), FIPS 186-5
- 🪢 Consensus-friendly, compliant with [ZIP215](https://zips.z.cash/zip-0215)
- 🔖 SUF-CMA (strong unforgeability under chosen message attacks) and
SBS (non-repudiation / exclusive ownership)
- 🪶 3.66KB (gzipped)
The module is a sister project of [noble-curves](https://github.com/paulmillr/noble-curves),
focusing on smaller attack surface & better auditability.
Curves are drop-in replacement and have more features: ristretto255, x25519 / curve25519, ed25519ph, hash-to-curve, oprf. To upgrade from v1 to v2, see [Upgrading](#upgrading).
### This library belongs to _noble_ cryptography
> **noble-cryptography** — high-security, easily auditable set of contained cryptographic libraries and tools.
- Zero or minimal dependencies
- Highly readable TypeScript / JS code
- PGP-signed releases and transparent NPM builds with provenance
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
[ciphers](https://github.com/paulmillr/noble-ciphers),
[curves](https://github.com/paulmillr/noble-curves),
[hashes](https://github.com/paulmillr/noble-hashes),
[post-quantum](https://github.com/paulmillr/noble-post-quantum),
5KB [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
[ed25519](https://github.com/paulmillr/noble-ed25519)
## Usage
> `npm install @noble/ed25519`
> `deno add jsr:@noble/ed25519`
We support all major platforms and runtimes. For node.js <= 18 and React Native, additional polyfills are needed: see below.
```js
import * as ed from '@noble/ed25519';
(async () => {
const { secretKey, publicKey } = await ed.keygenAsync();
// const publicKey = await ed.getPublicKeyAsync(secretKey);
const message = new TextEncoder().encode('hello noble');
const signature = await ed.signAsync(message, secretKey);
const isValid = await ed.verifyAsync(signature, message, publicKey);
})();
```
### Enabling synchronous methods
Only async methods are available by default, to keep the library dependency-free.
To enable sync methods:
```ts
import { sha512 } from '@noble/hashes/sha2.js';
ed.hashes.sha512 = sha512;
// Sync methods can be used now:
const { secretKey, publicKey } = ed.keygen();
// const publicKey = ed.getPublicKey(secretKey);
const sig = ed.sign(msg, secretKey);
const isValid = ed.verify(sig, msg, publicKey);
```
### React Native: polyfill getRandomValues and sha512
```ts
import 'react-native-get-random-values';
import { sha512 } from '@noble/hashes/sha2.js';
ed.hashes.sha512 = sha512;
ed.hashes.sha512Async = (m: Uint8Array) => Promise.resolve(sha512(m));
```
## API
There are 4 main methods, which accept Uint8Array-s:
- `keygen()` and `keygenAsync()`
- `getPublicKey(secretKey)` and `getPublicKeyAsync(secretKey)`
- `sign(message, secretKey)` and `signAsync(message, secretKey)`
- `verify(signature, message, publicKey)` and `verifyAsync(signature, message, publicKey)`
### keygen
```typescript
import * as ed from '@noble/ed25519';
(async () => {
const keys = ed.keygen(); // needs ed.hashes.sha512
const { secretKey, publicKey } = keys
const keysA = await ed.keygenAsync();
})();
```
### getPublicKey
```typescript
import * as ed from '@noble/ed25519';
(async () => {
const pubKey = ed.getPublicKey(secretKeyA); // needs ed.hashes.sha512
const pubKeyA = await ed.getPublicKeyAsync(secretKeyA);
const pubKeyPoint = ed.Point.fromBytes(pubKeyB);
const pubKeyExtended = ed.utils.getExtendedPublicKey(secretKeyA);
})();
```
Generates 32-byte public key from 32-byte private key.
- Some libraries have 64-byte private keys - those are just priv+pub concatenated
- Use `ExtendedPoint.fromHex(publicKey)` if you want to convert hex / bytes into Point.
It will use decompression algorithm 5.1.3 of RFC 8032.
- Use `utils.getExtendedPublicKey` if you need full SHA512 hash of seed
### sign
```ts
import * as ed from '@noble/ed25519';
(async () => {
const { secretKey, publicKey } = ed.keygen();
const message = new TextEncoder().encode('hello noble');
const signature = ed.sign(message, secretKey);
const signatureA = await ed.signAsync(message, secretKey);
})();
```
Generates deterministic EdDSA signature. `message` would be hashed by ed25519 internally.
For prehashed ed25519ph, switch to noble-curves.
### verify
```ts
import * as ed from '@noble/ed25519';
(async () => {
const { secretKey, publicKey } = ed.keygen();
const message = new TextEncoder().encode('hello noble');
const signature = ed.sign(message, secretKey);
const isValid = ed.verify(signature, message, pubKey);
const isValidFips = ed.verify(signature, message, pubKey, { zip215: false });
const isValidA = await ed.verifyAsync(signature, message, pubKey);
})();
```
Verifies EdDSA signature. Has SUF-CMA (strong unforgeability under chosen message attacks).
By default, follows ZIP215 [^1] and can be used in consensus-critical apps [^2].
`zip215: false` option switches verification criteria to strict
RFC8032 / FIPS 186-5 and provides non-repudiation with SBS (Strongly Binding Signatures) [^3].
### utils
A bunch of useful **utilities** are also exposed:
```typescript
import * as ed from '@noble/ed25519';
const { bytesToHex, hexToBytes, concatBytes, mod, invert, randomBytes } = ed.etc;
const { getExtendedPublicKey, getExtendedPublicKeyAsync, randomSecretKey } = ed.utils;
const { Point } = ed;
console.log(Point.CURVE(), Point.BASE);
/*
class Point {
static BASE: Point;
static ZERO: Point;
readonly X: bigint;
readonly Y: bigint;
readonly Z: bigint;
readonly T: bigint;
constructor(X: bigint, Y: bigint, Z: bigint, T: bigint);
static CURVE(): EdwardsOpts;
static fromAffine(p: AffinePoint): Point;
static fromBytes(hex: Bytes, zip215?: boolean): Point;
static fromHex(hex: string, zip215?: boolean): Point;
get x(): bigint;
get y(): bigint;
assertValidity(): this;
equals(other: Point): boolean;
is0(): boolean;
negate(): Point;
double(): Point;
add(other: Point): Point;
subtract(other: Point): Point;
multiply(n: bigint, safe?: boolean): Point;
multiplyUnsafe(scalar: bigint): Point;
toAffine(): AffinePoint;
toBytes(): Bytes;
toHex(): string;
clearCofactor(): Point;
isSmallOrder(): boolean;
isTorsionFree(): boolean;
}
*/
```
## Security
The module is production-ready.
We cross-test against sister project [noble-curves](https://github.com/paulmillr/noble-curves), which was audited and provides improved security.
- The current version has not been independently audited. It is a rewrite of v1, which has been audited by cure53 in Feb 2022:
[PDF](https://cure53.de/pentest-report_ed25519.pdf).
- It's being fuzzed [in a separate repository](https://github.com/paulmillr/fuzzing)
If you see anything unusual: investigate and report.
### Constant-timeness
We're targetting algorithmic constant time. _JIT-compiler_ and _Garbage Collector_ make "constant time"
extremely hard to achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance
in a scripting language. Which means _any other JS library can't have
constant-timeness_. Even statically typed Rust, a language without GC,
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
Use low-level libraries & languages.
### Supply chain security
- **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures
- **Releases** are transparent and built on GitHub CI.
Check out [attested checksums of single-file builds](https://github.com/paulmillr/noble-ed25519/attestations)
and [provenance logs](https://github.com/paulmillr/noble-ed25519/actions/workflows/release.yml)
- **Rare releasing** is followed to ensure less re-audit need for end-users
- **Dependencies** are minimized and locked-down: any dependency could get hacked and users will be downloading malware with every install.
- We make sure to use as few dependencies as possible
- Automatic dep updates are prevented by locking-down version ranges; diffs are checked with `npm-diff`
- **Dev Dependencies** are disabled for end-users; they are only used to develop / build the source code
For this package, there are 0 dependencies; and a few dev dependencies:
- [noble-hashes](https://github.com/paulmillr/noble-hashes) provides cryptographic hashing functionality
- micro-bmark, micro-should and jsbt are used for benchmarking / testing / build tooling and developed by the same author
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation. It's hard to audit their source code thoroughly and fully because of their size
### Randomness
We're deferring to built-in
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
which is considered cryptographically secure (CSPRNG).
In the past, browsers had bugs that made it weak: it may happen again.
Implementing a userspace CSPRNG to get resilient to the weakness
is even worse: there is no reliable userspace source of quality entropy.
### Quantum computers
Cryptographically relevant quantum computer, if built, will allow to
break elliptic curve cryptography (both ECDSA / EdDSA & ECDH) using Shor's algorithm.
Consider switching to newer / hybrid algorithms, such as SPHINCS+. They are available in
[noble-post-quantum](https://github.com/paulmillr/noble-post-quantum).
NIST prohibits classical cryptography (RSA, DSA, ECDSA, ECDH) [after 2035](https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf). Australian ASD prohibits it [after 2030](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography).
## Speed
npm run bench
Benchmarks measured with Apple M4.
init 11ms
keygen x 11,253 ops/sec @ 88μs/op
sign x 5,891 ops/sec @ 169μs/op
verify x 1,281 ops/sec @ 780μs/op
keygenAsync x 10,205 ops/sec @ 97μs/op
signAsync x 4,985 ops/sec @ 200μs/op
verifyAsync x 1,286 ops/sec @ 777μs/op
Point.fromBytes x 22,811 ops/sec @ 43μs/op
Compare to alternative implementations:
tweetnacl@1.0.3 getPublicKey x 1,808 ops/sec @ 552μs/op ± 1.64%
tweetnacl@1.0.3 sign x 651 ops/sec @ 1ms/op
ristretto255@0.1.2 getPublicKey x 640 ops/sec @ 1ms/op ± 1.59%
sodium-native#sign x 83,654 ops/sec @ 11μs/op
## Upgrading
### v2 to v3
v3 brings the package closer to noble-curves v2.
- Most methods now expect Uint8Array, string hex inputs are prohibited
- Add `keygen`, `keygenAsync` method
- Node v20.19 is now the minimum required version
- Various small changes for types and Point class
- etc: hashes are now set in `hashes` object:
```js
// before
ed.etc.sha512 = sha512;
ed.etc.sha512Async = (m: Uint8Array) => Promise.resolve(sha512(m));
// after
ed.hashes.sha512 = sha512;
ed.hashes.sha512Async = (m: Uint8Array) => Promise.resolve(sha512(m));
```
### v1 to v2
v2 features improved security and smaller attack surface.
The goal of v2 is to provide minimum possible JS library which is safe and fast.
That means the library was reduced 4x, to just over 300 lines. In order to
achieve the goal, **some features were moved** to
[noble-curves](https://github.com/paulmillr/noble-curves), which is
even safer and faster drop-in replacement library with same API.
Switch to curves if you intend to keep using these features:
- x25519 / curve25519 / getSharedSecret
- ristretto255 / RistrettoPoint
- Using `utils.precompute()` for non-base point
- Support for environments which don't support bigint literals
- Common.js support
- Support for node.js 18 and older without [shim](#usage)
Other changes for upgrading from @noble/ed25519 1.7 to 2.0:
- Methods are now sync by default; use `getPublicKeyAsync`, `signAsync`, `verifyAsync` for async versions
- `bigint` is no longer allowed in `getPublicKey`, `sign`, `verify`. Reason: ed25519 is LE, can lead to bugs
- `Point` (2d xy) has been changed to `ExtendedPoint` (xyzt)
- `Signature` was removed: just use raw bytes or hex now
- `utils` were split into `utils` (same api as in noble-curves) and
`etc` (`sha512Sync` and others)
## Contributing & testing
- `npm install && npm run build && npm test` will build the code and run tests.
- `npm run bench` will run benchmarks
- `npm run build:release` will build single file
See [paulmillr.com/noble](https://paulmillr.com/noble/)
for useful resources, articles, documentation and demos
related to the library.
## License
The MIT License (MIT)
Copyright (c) 2019 Paul Miller [(https://paulmillr.com)](https://paulmillr.com)
See LICENSE file.
[^1]: https://zips.z.cash/zip-0215
[^2]: https://hdevalence.ca/blog/2020-10-04-its-25519am
[^3]: https://eprint.iacr.org/2020/1244
+131
View File
@@ -0,0 +1,131 @@
/** Alias to Uint8Array. */
export type Bytes = Uint8Array;
/** Hex-encoded string or Uint8Array. */
/** Edwards elliptic curve options. */
export type EdwardsOpts = Readonly<{
p: bigint;
n: bigint;
h: bigint;
a: bigint;
d: bigint;
Gx: bigint;
Gy: bigint;
}>;
declare const bytesToHex: (b: Bytes) => string;
declare const hexToBytes: (hex: string) => Bytes;
declare const concatBytes: (...arrs: Bytes[]) => Bytes;
/** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */
declare const randomBytes: (len?: number) => Bytes;
/** modular division */
declare const M: (a: bigint, b?: bigint) => bigint;
/** Modular inversion using euclidean GCD (non-CT). No negative exponent for now. */
declare const invert: (num: bigint, md: bigint) => bigint;
declare const hash: (msg: Bytes) => Bytes;
/** Point in 2d xy affine coordinates. */
export type AffinePoint = {
x: bigint;
y: bigint;
};
/** Point in XYZT extended coordinates. */
declare class Point {
static BASE: Point;
static ZERO: Point;
readonly X: bigint;
readonly Y: bigint;
readonly Z: bigint;
readonly T: bigint;
constructor(X: bigint, Y: bigint, Z: bigint, T: bigint);
static CURVE(): EdwardsOpts;
static fromAffine(p: AffinePoint): Point;
/** RFC8032 5.1.3: Uint8Array to Point. */
static fromBytes(hex: Bytes, zip215?: boolean): Point;
static fromHex(hex: string, zip215?: boolean): Point;
get x(): bigint;
get y(): bigint;
/** Checks if the point is valid and on-curve. */
assertValidity(): this;
/** Equality check: compare points P&Q. */
equals(other: Point): boolean;
is0(): boolean;
/** Flip point over y coordinate. */
negate(): Point;
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
double(): Point;
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
add(other: Point): Point;
subtract(other: Point): Point;
/**
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
* Uses {@link wNAF} for base point.
* Uses fake point to mitigate side-channel leakage.
* @param n scalar by which point is multiplied
* @param safe safe mode guards against timing attacks; unsafe mode is faster
*/
multiply(n: bigint, safe?: boolean): Point;
multiplyUnsafe(scalar: bigint): Point;
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
toAffine(): AffinePoint;
toBytes(): Bytes;
toHex(): string;
clearCofactor(): Point;
isSmallOrder(): boolean;
isTorsionFree(): boolean;
}
type ExtK = {
head: Bytes;
prefix: Bytes;
scalar: bigint;
point: Point;
pointBytes: Bytes;
};
declare const getExtendedPublicKeyAsync: (secretKey: Bytes) => Promise<ExtK>;
declare const getExtendedPublicKey: (secretKey: Bytes) => ExtK;
/** Creates 32-byte ed25519 public key from 32-byte secret key. Async. */
declare const getPublicKeyAsync: (secretKey: Bytes) => Promise<Bytes>;
/** Creates 32-byte ed25519 public key from 32-byte secret key. To use, set `hashes.sha512` first. */
declare const getPublicKey: (priv: Bytes) => Bytes;
/**
* Signs message using secret key. Async.
* Follows RFC8032 5.1.6.
*/
declare const signAsync: (message: Bytes, secretKey: Bytes) => Promise<Bytes>;
/**
* Signs message using secret key. To use, set `hashes.sha512` first.
* Follows RFC8032 5.1.6.
*/
declare const sign: (message: Bytes, secretKey: Bytes) => Bytes;
/** Verification options. zip215: true (default) follows ZIP215 spec. false would follow RFC8032. */
export type EdDSAVerifyOpts = {
zip215?: boolean;
};
/** Verifies signature on message and public key. Async. Follows RFC8032 5.1.7. */
declare const verifyAsync: (signature: Bytes, message: Bytes, publicKey: Bytes, opts?: EdDSAVerifyOpts) => Promise<boolean>;
/** Verifies signature on message and public key. To use, set `hashes.sha512` first. Follows RFC8032 5.1.7. */
declare const verify: (signature: Bytes, message: Bytes, publicKey: Bytes, opts?: EdDSAVerifyOpts) => boolean;
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
declare const etc: {
bytesToHex: typeof bytesToHex;
hexToBytes: typeof hexToBytes;
concatBytes: typeof concatBytes;
mod: typeof M;
invert: typeof invert;
randomBytes: typeof randomBytes;
};
declare const hashes: {
sha512Async: (message: Bytes) => Promise<Bytes>;
sha512: undefined | ((message: Bytes) => Bytes);
};
declare const randomSecretKey: (seed?: Bytes) => Bytes;
type KeysSecPub = {
secretKey: Bytes;
publicKey: Bytes;
};
declare const keygen: (seed?: Bytes) => KeysSecPub;
declare const keygenAsync: (seed?: Bytes) => Promise<KeysSecPub>;
/** ed25519-specific key utilities. */
declare const utils: {
getExtendedPublicKeyAsync: typeof getExtendedPublicKeyAsync;
getExtendedPublicKey: typeof getExtendedPublicKey;
randomSecretKey: typeof randomSecretKey;
};
export { etc, getPublicKey, getPublicKeyAsync, hash, hashes, keygen, keygenAsync, Point, sign, signAsync, utils, verify, verifyAsync, };
+630
View File
@@ -0,0 +1,630 @@
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
/**
* 5KB JS implementation of ed25519 EdDSA signatures.
* Compliant with RFC8032, FIPS 186-5 & ZIP215.
* @module
* @example
* ```js
import * as ed from '@noble/ed25519';
(async () => {
const secretKey = ed.utils.randomSecretKey();
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const pubKey = await ed.getPublicKeyAsync(secretKey); // Sync methods are also present
const signature = await ed.signAsync(message, secretKey);
const isValid = await ed.verifyAsync(signature, message, pubKey);
})();
```
*/
/**
* Curve params. ed25519 is twisted edwards curve. Equation is x² + y² = -a + dx²y².
* * P = `2n**255n - 19n` // field over which calculations are done
* * N = `2n**252n + 27742317777372353535851937790883648493n` // group order, amount of curve points
* * h = 8 // cofactor
* * a = `Fp.create(BigInt(-1))` // equation param
* * d = -121665/121666 a.k.a. `Fp.neg(121665 * Fp.inv(121666))` // equation param
* * Gx, Gy are coordinates of Generator / base point
*/
const ed25519_CURVE = {
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
h: 8n,
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n,
};
const { p: P, n: N, Gx, Gy, a: _a, d: _d, h } = ed25519_CURVE;
const L = 32; // field / group byte length
const L2 = 64;
// Helpers and Precomputes sections are reused between libraries
// ## Helpers
// ----------
const captureTrace = (...args) => {
if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(...args);
}
};
const err = (message = '') => {
const e = new Error(message);
captureTrace(e, err);
throw e;
};
const isBig = (n) => typeof n === 'bigint'; // is big integer
const isStr = (s) => typeof s === 'string'; // is string
const isBytes = (a) => a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
/** Asserts something is Uint8Array. */
const abytes = (value, length, title = '') => {
const bytes = isBytes(value);
const len = value?.length;
const needsLen = length !== undefined;
if (!bytes || (needsLen && len !== length)) {
const prefix = title && `"${title}" `;
const ofLen = needsLen ? ` of length ${length}` : '';
const got = bytes ? `length=${len}` : `type=${typeof value}`;
err(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);
}
return value;
};
/** create Uint8Array */
const u8n = (len) => new Uint8Array(len);
const u8fr = (buf) => Uint8Array.from(buf);
const padh = (n, pad) => n.toString(16).padStart(pad, '0');
const bytesToHex = (b) => Array.from(abytes(b))
.map((e) => padh(e, 2))
.join('');
const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; // ASCII characters
const _ch = (ch) => {
if (ch >= C._0 && ch <= C._9)
return ch - C._0; // '2' => 50-48
if (ch >= C.A && ch <= C.F)
return ch - (C.A - 10); // 'B' => 66-(65-10)
if (ch >= C.a && ch <= C.f)
return ch - (C.a - 10); // 'b' => 98-(97-10)
return;
};
const hexToBytes = (hex) => {
const e = 'hex invalid';
if (!isStr(hex))
return err(e);
const hl = hex.length;
const al = hl / 2;
if (hl % 2)
return err(e);
const array = u8n(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
// treat each char as ASCII
const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16
const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char
if (n1 === undefined || n2 === undefined)
return err(e);
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
}
return array;
};
const cr = () => globalThis?.crypto; // WebCrypto is available in all modern environments
const subtle = () => cr()?.subtle ?? err('crypto.subtle must be defined, consider polyfill');
// prettier-ignore
const concatBytes = (...arrs) => {
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0)); // create u8a of summed length
let pad = 0; // walk through each array,
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
return r;
};
/** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */
const randomBytes = (len = L) => {
const c = cr();
return c.getRandomValues(u8n(len));
};
const big = BigInt;
const assertRange = (n, min, max, msg = 'bad number: out of range') => (isBig(n) && min <= n && n < max ? n : err(msg));
/** modular division */
const M = (a, b = P) => {
const r = a % b;
return r >= 0n ? r : b + r;
};
const modN = (a) => M(a, N);
/** Modular inversion using euclidean GCD (non-CT). No negative exponent for now. */
// prettier-ignore
const invert = (num, md) => {
if (num === 0n || md <= 0n)
err('no inverse n=' + num + ' mod=' + md);
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
while (a !== 0n) {
const q = b / a, r = b % a;
const m = x - u * q, n = y - v * q;
b = a, a = r, x = u, y = v, u = m, v = n;
}
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point
};
const callHash = (name) => {
// @ts-ignore
const fn = hashes[name];
if (typeof fn !== 'function')
err('hashes.' + name + ' not set');
return fn;
};
const hash = (msg) => callHash('sha512')(msg);
const apoint = (p) => (p instanceof Point ? p : err('Point expected'));
// ## End of Helpers
// -----------------
const B256 = 2n ** 256n;
/** Point in XYZT extended coordinates. */
class Point {
static BASE;
static ZERO;
X;
Y;
Z;
T;
constructor(X, Y, Z, T) {
const max = B256;
this.X = assertRange(X, 0n, max);
this.Y = assertRange(Y, 0n, max);
this.Z = assertRange(Z, 1n, max);
this.T = assertRange(T, 0n, max);
Object.freeze(this);
}
static CURVE() {
return ed25519_CURVE;
}
static fromAffine(p) {
return new Point(p.x, p.y, 1n, M(p.x * p.y));
}
/** RFC8032 5.1.3: Uint8Array to Point. */
static fromBytes(hex, zip215 = false) {
const d = _d;
// Copy array to not mess it up.
const normed = u8fr(abytes(hex, L));
// adjust first LE byte = last BE byte
const lastByte = hex[31];
normed[31] = lastByte & ~0x80;
const y = bytesToNumLE(normed);
// zip215=true: 0 <= y < 2^256
// zip215=false, RFC8032: 0 <= y < 2^255-19
const max = zip215 ? B256 : P;
assertRange(y, 0n, max);
const y2 = M(y * y); // y²
const u = M(y2 - 1n); // u=y²-1
const v = M(d * y2 + 1n); // v=dy²+1
let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root
if (!isValid)
err('bad point: y not sqrt'); // not square root: bad point
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
if (!zip215 && x === 0n && isLastByteOdd)
err('bad point: x==0, isLastByteOdd'); // x=0, x_0=1
if (isLastByteOdd !== isXOdd)
x = M(-x);
return new Point(x, y, 1n, M(x * y)); // Z=1, T=xy
}
static fromHex(hex, zip215) {
return Point.fromBytes(hexToBytes(hex), zip215);
}
get x() {
return this.toAffine().x;
}
get y() {
return this.toAffine().y;
}
/** Checks if the point is valid and on-curve. */
assertValidity() {
const a = _a;
const d = _d;
const p = this;
if (p.is0())
return err('bad point: ZERO'); // TODO: optimize, with vars below?
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
const { X, Y, Z, T } = p;
const X2 = M(X * X); // X²
const Y2 = M(Y * Y); // Y²
const Z2 = M(Z * Z); // Z²
const Z4 = M(Z2 * Z2); // Z⁴
const aX2 = M(X2 * a); // aX²
const left = M(Z2 * M(aX2 + Y2)); // (aX² + Y²)Z²
const right = M(Z4 + M(d * M(X2 * Y2))); // Z⁴ + dX²Y²
if (left !== right)
return err('bad point: equation left != right (1)');
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
const XY = M(X * Y);
const ZT = M(Z * T);
if (XY !== ZT)
return err('bad point: equation left != right (2)');
return this;
}
/** Equality check: compare points P&Q. */
equals(other) {
const { X: X1, Y: Y1, Z: Z1 } = this;
const { X: X2, Y: Y2, Z: Z2 } = apoint(other); // checks class equality
const X1Z2 = M(X1 * Z2);
const X2Z1 = M(X2 * Z1);
const Y1Z2 = M(Y1 * Z2);
const Y2Z1 = M(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
}
is0() {
return this.equals(I);
}
/** Flip point over y coordinate. */
negate() {
return new Point(M(-this.X), this.Y, this.Z, M(-this.T));
}
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
double() {
const { X: X1, Y: Y1, Z: Z1 } = this;
const a = _a;
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
const A = M(X1 * X1);
const B = M(Y1 * Y1);
const C = M(2n * M(Z1 * Z1));
const D = M(a * A);
const x1y1 = X1 + Y1;
const E = M(M(x1y1 * x1y1) - A - B);
const G = D + B;
const F = G - C;
const H = D - B;
const X3 = M(E * F);
const Y3 = M(G * H);
const T3 = M(E * H);
const Z3 = M(F * G);
return new Point(X3, Y3, Z3, T3);
}
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
add(other) {
const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
const { X: X2, Y: Y2, Z: Z2, T: T2 } = apoint(other); // doesn't check if other on-curve
const a = _a;
const d = _d;
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
const A = M(X1 * X2);
const B = M(Y1 * Y2);
const C = M(T1 * d * T2);
const D = M(Z1 * Z2);
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
const F = M(D - C);
const G = M(D + C);
const H = M(B - a * A);
const X3 = M(E * F);
const Y3 = M(G * H);
const T3 = M(E * H);
const Z3 = M(F * G);
return new Point(X3, Y3, Z3, T3);
}
subtract(other) {
return this.add(apoint(other).negate());
}
/**
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
* Uses {@link wNAF} for base point.
* Uses fake point to mitigate side-channel leakage.
* @param n scalar by which point is multiplied
* @param safe safe mode guards against timing attacks; unsafe mode is faster
*/
multiply(n, safe = true) {
if (!safe && (n === 0n || this.is0()))
return I;
assertRange(n, 1n, N);
if (n === 1n)
return this;
if (this.equals(G))
return wNAF(n).p;
// init result point & fake point
let p = I;
let f = G;
for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
// if bit is present, add to point
// if not present, add to fake, for timing safety
if (n & 1n)
p = p.add(d);
else if (safe)
f = f.add(d);
}
return p;
}
multiplyUnsafe(scalar) {
return this.multiply(scalar, false);
}
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
toAffine() {
const { X, Y, Z } = this;
// fast-paths for ZERO point OR Z=1
if (this.equals(I))
return { x: 0n, y: 1n };
const iz = invert(Z, P);
// (Z * Z^-1) must be 1, otherwise bad math
if (M(Z * iz) !== 1n)
err('invalid inverse');
// x = X*Z^-1; y = Y*Z^-1
const x = M(X * iz);
const y = M(Y * iz);
return { x, y };
}
toBytes() {
const { x, y } = this.assertValidity().toAffine();
const b = numTo32bLE(y);
// store sign in first LE byte
b[31] |= x & 1n ? 0x80 : 0;
return b;
}
toHex() {
return bytesToHex(this.toBytes());
}
clearCofactor() {
return this.multiply(big(h), false);
}
isSmallOrder() {
return this.clearCofactor().is0();
}
isTorsionFree() {
// Multiply by big number N. We can't `mul(N)` because of checks. Instead, we `mul(N/2)*2+1`
let p = this.multiply(N / 2n, false).double();
if (N % 2n)
p = p.add(this);
return p.is0();
}
}
/** Generator / base point */
const G = new Point(Gx, Gy, 1n, M(Gx * Gy));
/** Identity / zero point */
const I = new Point(0n, 1n, 1n, 0n);
// Static aliases
Point.BASE = G;
Point.ZERO = I;
const numTo32bLE = (num) => hexToBytes(padh(assertRange(num, 0n, B256), L2)).reverse();
const bytesToNumLE = (b) => big('0x' + bytesToHex(u8fr(abytes(b)).reverse()));
const pow2 = (x, power) => {
// pow2(x, 4) == x^(2^4)
let r = x;
while (power-- > 0n) {
r *= r;
r %= P;
}
return r;
};
// prettier-ignore
const pow_2_252_3 = (x) => {
const x2 = (x * x) % P; // x^2, bits 1
const b2 = (x2 * x) % P; // x^3, bits 11
const b4 = (pow2(b2, 2n) * b2) % P; // x^(2^4-1), bits 1111
const b5 = (pow2(b4, 1n) * x) % P; // x^(2^5-1), bits 11111
const b10 = (pow2(b5, 5n) * b5) % P; // x^(2^10)
const b20 = (pow2(b10, 10n) * b10) % P; // x^(2^20)
const b40 = (pow2(b20, 20n) * b20) % P; // x^(2^40)
const b80 = (pow2(b40, 40n) * b40) % P; // x^(2^80)
const b160 = (pow2(b80, 80n) * b80) % P; // x^(2^160)
const b240 = (pow2(b160, 80n) * b80) % P; // x^(2^240)
const b250 = (pow2(b240, 10n) * b10) % P; // x^(2^250)
const pow_p_5_8 = (pow2(b250, 2n) * x) % P; // < To pow to (p+3)/8, multiply it by x.
return { pow_p_5_8, b2 };
};
const RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n; // √-1
// for sqrt comp
// prettier-ignore
const uvRatio = (u, v) => {
const v3 = M(v * v * v); // v³
const v7 = M(v3 * v3 * v); // v⁷
const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8
let x = M(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8
const vx2 = M(v * x * x); // vx²
const root1 = x; // First root candidate
const root2 = M(x * RM1); // Second root candidate; RM1 is √-1
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
const useRoot2 = vx2 === M(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4)
const noRoot = vx2 === M(-u * RM1); // There is no valid root, vx² = -u√-1
if (useRoot1)
x = root1;
if (useRoot2 || noRoot)
x = root2; // We return root2 anyway, for const-time
if ((M(x) & 1n) === 1n)
x = M(-x); // edIsNegative
return { isValid: useRoot1 || useRoot2, value: x };
};
// N == L, just weird naming
const modL_LE = (hash) => modN(bytesToNumLE(hash)); // modulo L; but little-endian
/** hashes.sha512 should conform to the interface. */
// TODO: rename
const sha512a = (...m) => hashes.sha512Async(concatBytes(...m)); // Async SHA512
const sha512s = (...m) => callHash('sha512')(concatBytes(...m));
// RFC8032 5.1.5
const hash2extK = (hashed) => {
// slice creates a copy, unlike subarray
const head = hashed.slice(0, L);
head[0] &= 248; // Clamp bits: 0b1111_1000
head[31] &= 127; // 0b0111_1111
head[31] |= 64; // 0b0100_0000
const prefix = hashed.slice(L, L2); // secret key "prefix"
const scalar = modL_LE(head); // modular division over curve order
const point = G.multiply(scalar); // public key point
const pointBytes = point.toBytes(); // point serialized to Uint8Array
return { head, prefix, scalar, point, pointBytes };
};
// RFC8032 5.1.5; getPublicKey async, sync. Hash priv key and extract point.
const getExtendedPublicKeyAsync = (secretKey) => sha512a(abytes(secretKey, L)).then(hash2extK);
const getExtendedPublicKey = (secretKey) => hash2extK(sha512s(abytes(secretKey, L)));
/** Creates 32-byte ed25519 public key from 32-byte secret key. Async. */
const getPublicKeyAsync = (secretKey) => getExtendedPublicKeyAsync(secretKey).then((p) => p.pointBytes);
/** Creates 32-byte ed25519 public key from 32-byte secret key. To use, set `hashes.sha512` first. */
const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
const hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
const hashFinishS = (res) => res.finish(sha512s(res.hashable));
// Code, shared between sync & async sign
const _sign = (e, rBytes, msg) => {
const { pointBytes: P, scalar: s } = e;
const r = modL_LE(rBytes); // r was created outside, reduce it modulo L
const R = G.multiply(r).toBytes(); // R = [r]B
const hashable = concatBytes(R, P, msg); // dom2(F, C) || R || A || PH(M)
const finish = (hashed) => {
// k = SHA512(dom2(F, C) || R || A || PH(M))
const S = modN(r + modL_LE(hashed) * s); // S = (r + k * s) mod L; 0 <= s < l
return abytes(concatBytes(R, numTo32bLE(S)), L2); // 64-byte sig: 32b R.x + 32b LE(S)
};
return { hashable, finish };
};
/**
* Signs message using secret key. Async.
* Follows RFC8032 5.1.6.
*/
const signAsync = async (message, secretKey) => {
const m = abytes(message);
const e = await getExtendedPublicKeyAsync(secretKey);
const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinishA(_sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
/**
* Signs message using secret key. To use, set `hashes.sha512` first.
* Follows RFC8032 5.1.6.
*/
const sign = (message, secretKey) => {
const m = abytes(message);
const e = getExtendedPublicKey(secretKey);
const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinishS(_sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
const defaultVerifyOpts = { zip215: true };
const _verify = (sig, msg, pub, opts = defaultVerifyOpts) => {
sig = abytes(sig, L2); // Signature hex str/Bytes, must be 64 bytes
msg = abytes(msg); // Message hex str/Bytes
pub = abytes(pub, L);
const { zip215 } = opts; // switch between zip215 and rfc8032 verif
let A;
let R;
let s;
let SB;
let hashable = Uint8Array.of();
try {
A = Point.fromBytes(pub, zip215); // public key A decoded
R = Point.fromBytes(sig.slice(0, L), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P
s = bytesToNumLE(sig.slice(L, L2)); // Decode second half as an integer S
SB = G.multiply(s, false); // in the range 0 <= s < L
hashable = concatBytes(R.toBytes(), A.toBytes(), msg); // dom2(F, C) || R || A || PH(M)
}
catch (error) { }
const finish = (hashed) => {
// k = SHA512(dom2(F, C) || R || A || PH(M))
if (SB == null)
return false; // false if try-catch catched an error
if (!zip215 && A.isSmallOrder())
return false; // false for SBS: Strongly Binding Signature
const k = modL_LE(hashed); // decode in little-endian, modulo L
const RkA = R.add(A.multiply(k, false)); // [8]R + [8][k]A'
return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A'
};
return { hashable, finish };
};
/** Verifies signature on message and public key. Async. Follows RFC8032 5.1.7. */
const verifyAsync = async (signature, message, publicKey, opts = defaultVerifyOpts) => hashFinishA(_verify(signature, message, publicKey, opts));
/** Verifies signature on message and public key. To use, set `hashes.sha512` first. Follows RFC8032 5.1.7. */
const verify = (signature, message, publicKey, opts = defaultVerifyOpts) => hashFinishS(_verify(signature, message, publicKey, opts));
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
const etc = {
bytesToHex: bytesToHex,
hexToBytes: hexToBytes,
concatBytes: concatBytes,
mod: M,
invert: invert,
randomBytes: randomBytes,
};
const hashes = {
sha512Async: async (message) => {
const s = subtle();
const m = concatBytes(message);
return u8n(await s.digest('SHA-512', m.buffer));
},
sha512: undefined,
};
// FIPS 186 B.4.1 compliant key generation produces private keys
// with modulo bias being neglible. takes >N+16 bytes, returns (hash mod n-1)+1
const randomSecretKey = (seed = randomBytes(L)) => seed;
const keygen = (seed) => {
const secretKey = randomSecretKey(seed);
const publicKey = getPublicKey(secretKey);
return { secretKey, publicKey };
};
const keygenAsync = async (seed) => {
const secretKey = randomSecretKey(seed);
const publicKey = await getPublicKeyAsync(secretKey);
return { secretKey, publicKey };
};
/** ed25519-specific key utilities. */
const utils = {
getExtendedPublicKeyAsync: getExtendedPublicKeyAsync,
getExtendedPublicKey: getExtendedPublicKey,
randomSecretKey: randomSecretKey,
};
// ## Precomputes
// --------------
const W = 8; // W is window size
const scalarBits = 256;
const pwindows = Math.ceil(scalarBits / W) + 1; // 33 for W=8, NOT 32 - see wNAF loop
const pwindowSize = 2 ** (W - 1); // 128 for W=8
const precompute = () => {
const points = [];
let p = G;
let b = p;
for (let w = 0; w < pwindows; w++) {
b = p;
points.push(b);
for (let i = 1; i < pwindowSize; i++) {
b = b.add(p);
points.push(b);
} // i=1, bc we skip 0
p = b.double();
}
return points;
};
let Gpows = undefined; // precomputes for base point G
// const-time negate
const ctneg = (cnd, p) => {
const n = p.negate();
return cnd ? n : p;
};
/**
* Precomputes give 12x faster getPublicKey(), 10x sign(), 2x verify() by
* caching multiples of G (base point). Cache is stored in 32MB of RAM.
* Any time `G.multiply` is done, precomputes are used.
* Not used for getSharedSecret, which instead multiplies random pubkey `P.multiply`.
*
* w-ary non-adjacent form (wNAF) precomputation method is 10% slower than windowed method,
* but takes 2x less RAM. RAM reduction is possible by utilizing `.subtract`.
*
* !! Precomputes can be disabled by commenting-out call of the wNAF() inside Point#multiply().
*/
const wNAF = (n) => {
const comp = Gpows || (Gpows = precompute());
let p = I;
let f = G; // f must be G, or could become I in the end
const pow_2_w = 2 ** W; // 256 for W=8
const maxNum = pow_2_w; // 256 for W=8
const mask = big(pow_2_w - 1); // 255 for W=8 == mask 0b11111111
const shiftBy = big(W); // 8 for W=8
for (let w = 0; w < pwindows; w++) {
let wbits = Number(n & mask); // extract W bits.
n >>= shiftBy; // shift number by W bits.
// We use negative indexes to reduce size of precomputed table by 2x.
// Instead of needing precomputes 0..256, we only calculate them for 0..128.
// If an index > 128 is found, we do (256-index) - where 256 is next window.
// Naive: index +127 => 127, +224 => 224
// Optimized: index +127 => 127, +224 => 256-32
if (wbits > pwindowSize) {
wbits -= maxNum;
n += 1n;
}
const off = w * pwindowSize;
const offF = off; // offsets, evaluate both
const offP = off + Math.abs(wbits) - 1;
const isEven = w % 2 !== 0; // conditions, evaluate both
const isNeg = wbits < 0;
if (wbits === 0) {
// off == I: can't add it. Adding random offF instead.
f = f.add(ctneg(isEven, comp[offF])); // bits are 0: add garbage to fake point
}
else {
p = p.add(ctneg(isNeg, comp[offP])); // bits are 1: add to result point
}
}
if (n !== 0n)
err('invalid wnaf');
return { p, f }; // return both real and fake points for JIT
};
// !! Remove the export to easily use in REPL / browser console
export { etc, getPublicKey, getPublicKeyAsync, hash, hashes, keygen, keygenAsync, Point, sign, signAsync, utils, verify, verifyAsync, };
+685
View File
@@ -0,0 +1,685 @@
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
/**
* 5KB JS implementation of ed25519 EdDSA signatures.
* Compliant with RFC8032, FIPS 186-5 & ZIP215.
* @module
* @example
* ```js
import * as ed from '@noble/ed25519';
(async () => {
const secretKey = ed.utils.randomSecretKey();
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const pubKey = await ed.getPublicKeyAsync(secretKey); // Sync methods are also present
const signature = await ed.signAsync(message, secretKey);
const isValid = await ed.verifyAsync(signature, message, pubKey);
})();
```
*/
/**
* Curve params. ed25519 is twisted edwards curve. Equation is x² + y² = -a + dx²y².
* * P = `2n**255n - 19n` // field over which calculations are done
* * N = `2n**252n + 27742317777372353535851937790883648493n` // group order, amount of curve points
* * h = 8 // cofactor
* * a = `Fp.create(BigInt(-1))` // equation param
* * d = -121665/121666 a.k.a. `Fp.neg(121665 * Fp.inv(121666))` // equation param
* * Gx, Gy are coordinates of Generator / base point
*/
const ed25519_CURVE: EdwardsOpts = {
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
h: 8n,
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n,
};
const { p: P, n: N, Gx, Gy, a: _a, d: _d, h } = ed25519_CURVE;
const L = 32; // field / group byte length
const L2 = 64;
/** Alias to Uint8Array. */
export type Bytes = Uint8Array;
/** Hex-encoded string or Uint8Array. */
/** Edwards elliptic curve options. */
export type EdwardsOpts = Readonly<{
p: bigint;
n: bigint;
h: bigint;
a: bigint;
d: bigint;
Gx: bigint;
Gy: bigint;
}>;
// Helpers and Precomputes sections are reused between libraries
// ## Helpers
// ----------
const captureTrace = (...args: Parameters<typeof Error.captureStackTrace>): void => {
if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(...args);
}
};
const err = (message = ''): never => {
const e = new Error(message);
captureTrace(e, err);
throw e;
};
const isBig = (n: unknown): n is bigint => typeof n === 'bigint'; // is big integer
const isStr = (s: unknown): s is string => typeof s === 'string'; // is string
const isBytes = (a: unknown): a is Bytes =>
a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
/** Asserts something is Uint8Array. */
const abytes = (value: Bytes, length?: number, title: string = ''): Bytes => {
const bytes = isBytes(value);
const len = value?.length;
const needsLen = length !== undefined;
if (!bytes || (needsLen && len !== length)) {
const prefix = title && `"${title}" `;
const ofLen = needsLen ? ` of length ${length}` : '';
const got = bytes ? `length=${len}` : `type=${typeof value}`;
err(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);
}
return value;
};
/** create Uint8Array */
const u8n = (len: number) => new Uint8Array(len);
const u8fr = (buf: ArrayLike<number>) => Uint8Array.from(buf);
const padh = (n: number | bigint, pad: number) => n.toString(16).padStart(pad, '0');
const bytesToHex = (b: Bytes): string =>
Array.from(abytes(b))
.map((e) => padh(e, 2))
.join('');
const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const; // ASCII characters
const _ch = (ch: number): number | undefined => {
if (ch >= C._0 && ch <= C._9) return ch - C._0; // '2' => 50-48
if (ch >= C.A && ch <= C.F) return ch - (C.A - 10); // 'B' => 66-(65-10)
if (ch >= C.a && ch <= C.f) return ch - (C.a - 10); // 'b' => 98-(97-10)
return;
};
const hexToBytes = (hex: string): Bytes => {
const e = 'hex invalid';
if (!isStr(hex)) return err(e);
const hl = hex.length;
const al = hl / 2;
if (hl % 2) return err(e);
const array = u8n(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
// treat each char as ASCII
const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16
const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char
if (n1 === undefined || n2 === undefined) return err(e);
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
}
return array;
};
declare const globalThis: Record<string, any> | undefined; // Typescript symbol present in browsers
const cr = () => globalThis?.crypto; // WebCrypto is available in all modern environments
const subtle = () => cr()?.subtle ?? err('crypto.subtle must be defined, consider polyfill');
// prettier-ignore
const concatBytes = (...arrs: Bytes[]): Bytes => {
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0)); // create u8a of summed length
let pad = 0; // walk through each array,
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
return r;
};
/** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */
const randomBytes = (len: number = L): Bytes => {
const c = cr();
return c.getRandomValues(u8n(len));
};
const big = BigInt;
const assertRange = (
n: bigint,
min: bigint,
max: bigint,
msg = 'bad number: out of range'
): bigint => (isBig(n) && min <= n && n < max ? n : err(msg));
/** modular division */
const M = (a: bigint, b: bigint = P): bigint => {
const r = a % b;
return r >= 0n ? r : b + r;
};
const modN = (a: bigint) => M(a, N);
/** Modular inversion using euclidean GCD (non-CT). No negative exponent for now. */
// prettier-ignore
const invert = (num: bigint, md: bigint): bigint => {
if (num === 0n || md <= 0n) err('no inverse n=' + num + ' mod=' + md);
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
while (a !== 0n) {
const q = b / a, r = b % a;
const m = x - u * q, n = y - v * q;
b = a, a = r, x = u, y = v, u = m, v = n;
}
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point
};
const callHash = (name: string) => {
// @ts-ignore
const fn = hashes[name];
if (typeof fn !== 'function') err('hashes.' + name + ' not set');
return fn;
};
const hash = (msg: Bytes): Bytes => callHash('sha512')(msg);
const apoint = (p: unknown) => (p instanceof Point ? p : err('Point expected'));
/** Point in 2d xy affine coordinates. */
export type AffinePoint = {
x: bigint;
y: bigint;
};
// ## End of Helpers
// -----------------
const B256 = 2n ** 256n;
/** Point in XYZT extended coordinates. */
class Point {
static BASE: Point;
static ZERO: Point;
readonly X: bigint;
readonly Y: bigint;
readonly Z: bigint;
readonly T: bigint;
constructor(X: bigint, Y: bigint, Z: bigint, T: bigint) {
const max = B256;
this.X = assertRange(X, 0n, max);
this.Y = assertRange(Y, 0n, max);
this.Z = assertRange(Z, 1n, max);
this.T = assertRange(T, 0n, max);
Object.freeze(this);
}
static CURVE(): EdwardsOpts {
return ed25519_CURVE;
}
static fromAffine(p: AffinePoint): Point {
return new Point(p.x, p.y, 1n, M(p.x * p.y));
}
/** RFC8032 5.1.3: Uint8Array to Point. */
static fromBytes(hex: Bytes, zip215 = false): Point {
const d = _d;
// Copy array to not mess it up.
const normed = u8fr(abytes(hex, L));
// adjust first LE byte = last BE byte
const lastByte = hex[31];
normed[31] = lastByte & ~0x80;
const y = bytesToNumLE(normed);
// zip215=true: 0 <= y < 2^256
// zip215=false, RFC8032: 0 <= y < 2^255-19
const max = zip215 ? B256 : P;
assertRange(y, 0n, max);
const y2 = M(y * y); // y²
const u = M(y2 - 1n); // u=y²-1
const v = M(d * y2 + 1n); // v=dy²+1
let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root
if (!isValid) err('bad point: y not sqrt'); // not square root: bad point
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
if (!zip215 && x === 0n && isLastByteOdd) err('bad point: x==0, isLastByteOdd'); // x=0, x_0=1
if (isLastByteOdd !== isXOdd) x = M(-x);
return new Point(x, y, 1n, M(x * y)); // Z=1, T=xy
}
static fromHex(hex: string, zip215?: boolean): Point {
return Point.fromBytes(hexToBytes(hex), zip215);
}
get x(): bigint {
return this.toAffine().x;
}
get y(): bigint {
return this.toAffine().y;
}
/** Checks if the point is valid and on-curve. */
assertValidity(): this {
const a = _a;
const d = _d;
const p = this;
if (p.is0()) return err('bad point: ZERO'); // TODO: optimize, with vars below?
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
const { X, Y, Z, T } = p;
const X2 = M(X * X); // X²
const Y2 = M(Y * Y); // Y²
const Z2 = M(Z * Z); // Z²
const Z4 = M(Z2 * Z2); // Z⁴
const aX2 = M(X2 * a); // aX²
const left = M(Z2 * M(aX2 + Y2)); // (aX² + Y²)Z²
const right = M(Z4 + M(d * M(X2 * Y2))); // Z⁴ + dX²Y²
if (left !== right) return err('bad point: equation left != right (1)');
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
const XY = M(X * Y);
const ZT = M(Z * T);
if (XY !== ZT) return err('bad point: equation left != right (2)');
return this;
}
/** Equality check: compare points P&Q. */
equals(other: Point): boolean {
const { X: X1, Y: Y1, Z: Z1 } = this;
const { X: X2, Y: Y2, Z: Z2 } = apoint(other); // checks class equality
const X1Z2 = M(X1 * Z2);
const X2Z1 = M(X2 * Z1);
const Y1Z2 = M(Y1 * Z2);
const Y2Z1 = M(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
}
is0(): boolean {
return this.equals(I);
}
/** Flip point over y coordinate. */
negate(): Point {
return new Point(M(-this.X), this.Y, this.Z, M(-this.T));
}
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
double(): Point {
const { X: X1, Y: Y1, Z: Z1 } = this;
const a = _a;
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
const A = M(X1 * X1);
const B = M(Y1 * Y1);
const C = M(2n * M(Z1 * Z1));
const D = M(a * A);
const x1y1 = X1 + Y1;
const E = M(M(x1y1 * x1y1) - A - B);
const G = D + B;
const F = G - C;
const H = D - B;
const X3 = M(E * F);
const Y3 = M(G * H);
const T3 = M(E * H);
const Z3 = M(F * G);
return new Point(X3, Y3, Z3, T3);
}
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
add(other: Point): Point {
const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
const { X: X2, Y: Y2, Z: Z2, T: T2 } = apoint(other); // doesn't check if other on-curve
const a = _a;
const d = _d;
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
const A = M(X1 * X2);
const B = M(Y1 * Y2);
const C = M(T1 * d * T2);
const D = M(Z1 * Z2);
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
const F = M(D - C);
const G = M(D + C);
const H = M(B - a * A);
const X3 = M(E * F);
const Y3 = M(G * H);
const T3 = M(E * H);
const Z3 = M(F * G);
return new Point(X3, Y3, Z3, T3);
}
subtract(other: Point): Point {
return this.add(apoint(other).negate());
}
/**
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
* Uses {@link wNAF} for base point.
* Uses fake point to mitigate side-channel leakage.
* @param n scalar by which point is multiplied
* @param safe safe mode guards against timing attacks; unsafe mode is faster
*/
multiply(n: bigint, safe = true): Point {
if (!safe && (n === 0n || this.is0())) return I;
assertRange(n, 1n, N);
if (n === 1n) return this;
if (this.equals(G)) return wNAF(n).p;
// init result point & fake point
let p = I;
let f = G;
for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) {
// if bit is present, add to point
// if not present, add to fake, for timing safety
if (n & 1n) p = p.add(d);
else if (safe) f = f.add(d);
}
return p;
}
multiplyUnsafe(scalar: bigint): Point {
return this.multiply(scalar, false);
}
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
toAffine(): AffinePoint {
const { X, Y, Z } = this;
// fast-paths for ZERO point OR Z=1
if (this.equals(I)) return { x: 0n, y: 1n };
const iz = invert(Z, P);
// (Z * Z^-1) must be 1, otherwise bad math
if (M(Z * iz) !== 1n) err('invalid inverse');
// x = X*Z^-1; y = Y*Z^-1
const x = M(X * iz);
const y = M(Y * iz);
return { x, y };
}
toBytes(): Bytes {
const { x, y } = this.assertValidity().toAffine();
const b = numTo32bLE(y);
// store sign in first LE byte
b[31] |= x & 1n ? 0x80 : 0;
return b;
}
toHex(): string {
return bytesToHex(this.toBytes());
}
clearCofactor(): Point {
return this.multiply(big(h), false);
}
isSmallOrder(): boolean {
return this.clearCofactor().is0();
}
isTorsionFree(): boolean {
// Multiply by big number N. We can't `mul(N)` because of checks. Instead, we `mul(N/2)*2+1`
let p = this.multiply(N / 2n, false).double();
if (N % 2n) p = p.add(this);
return p.is0();
}
}
/** Generator / base point */
const G: Point = new Point(Gx, Gy, 1n, M(Gx * Gy));
/** Identity / zero point */
const I: Point = new Point(0n, 1n, 1n, 0n);
// Static aliases
Point.BASE = G;
Point.ZERO = I;
const numTo32bLE = (num: bigint) => hexToBytes(padh(assertRange(num, 0n, B256), L2)).reverse();
const bytesToNumLE = (b: Bytes): bigint => big('0x' + bytesToHex(u8fr(abytes(b)).reverse()));
const pow2 = (x: bigint, power: bigint): bigint => {
// pow2(x, 4) == x^(2^4)
let r = x;
while (power-- > 0n) {
r *= r;
r %= P;
}
return r;
};
// prettier-ignore
const pow_2_252_3 = (x: bigint) => { // x^(2^252-3) unrolled util for square root
const x2 = (x * x) % P; // x^2, bits 1
const b2 = (x2 * x) % P; // x^3, bits 11
const b4 = (pow2(b2, 2n) * b2) % P; // x^(2^4-1), bits 1111
const b5 = (pow2(b4, 1n) * x) % P; // x^(2^5-1), bits 11111
const b10 = (pow2(b5, 5n) * b5) % P; // x^(2^10)
const b20 = (pow2(b10, 10n) * b10) % P; // x^(2^20)
const b40 = (pow2(b20, 20n) * b20) % P; // x^(2^40)
const b80 = (pow2(b40, 40n) * b40) % P; // x^(2^80)
const b160 = (pow2(b80, 80n) * b80) % P; // x^(2^160)
const b240 = (pow2(b160, 80n) * b80) % P; // x^(2^240)
const b250 = (pow2(b240, 10n) * b10) % P; // x^(2^250)
const pow_p_5_8 = (pow2(b250, 2n) * x) % P; // < To pow to (p+3)/8, multiply it by x.
return { pow_p_5_8, b2 };
}
const RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n; // √-1
// for sqrt comp
// prettier-ignore
const uvRatio = (u: bigint, v: bigint): { isValid: boolean, value: bigint } => {
const v3 = M(v * v * v); // v³
const v7 = M(v3 * v3 * v); // v⁷
const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8
let x = M(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8
const vx2 = M(v * x * x); // vx²
const root1 = x; // First root candidate
const root2 = M(x * RM1); // Second root candidate; RM1 is √-1
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
const useRoot2 = vx2 === M(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4)
const noRoot = vx2 === M(-u * RM1); // There is no valid root, vx² = -u√-1
if (useRoot1) x = root1;
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
if ((M(x) & 1n) === 1n) x = M(-x); // edIsNegative
return { isValid: useRoot1 || useRoot2, value: x };
}
// N == L, just weird naming
const modL_LE = (hash: Bytes): bigint => modN(bytesToNumLE(hash)); // modulo L; but little-endian
/** hashes.sha512 should conform to the interface. */
// TODO: rename
const sha512a = (...m: Bytes[]) => hashes.sha512Async(concatBytes(...m)); // Async SHA512
const sha512s = (...m: Bytes[]) => callHash('sha512')(concatBytes(...m));
type ExtK = { head: Bytes; prefix: Bytes; scalar: bigint; point: Point; pointBytes: Bytes };
// RFC8032 5.1.5
const hash2extK = (hashed: Bytes): ExtK => {
// slice creates a copy, unlike subarray
const head = hashed.slice(0, L);
head[0] &= 248; // Clamp bits: 0b1111_1000
head[31] &= 127; // 0b0111_1111
head[31] |= 64; // 0b0100_0000
const prefix = hashed.slice(L, L2); // secret key "prefix"
const scalar = modL_LE(head); // modular division over curve order
const point = G.multiply(scalar); // public key point
const pointBytes = point.toBytes(); // point serialized to Uint8Array
return { head, prefix, scalar, point, pointBytes };
};
// RFC8032 5.1.5; getPublicKey async, sync. Hash priv key and extract point.
const getExtendedPublicKeyAsync = (secretKey: Bytes): Promise<ExtK> =>
sha512a(abytes(secretKey, L)).then(hash2extK);
const getExtendedPublicKey = (secretKey: Bytes): ExtK => hash2extK(sha512s(abytes(secretKey, L)));
/** Creates 32-byte ed25519 public key from 32-byte secret key. Async. */
const getPublicKeyAsync = (secretKey: Bytes): Promise<Bytes> =>
getExtendedPublicKeyAsync(secretKey).then((p) => p.pointBytes);
/** Creates 32-byte ed25519 public key from 32-byte secret key. To use, set `hashes.sha512` first. */
const getPublicKey = (priv: Bytes): Bytes => getExtendedPublicKey(priv).pointBytes;
type Finishable<T> = {
// Reduces logic duplication between
hashable: Bytes;
finish: (hashed: Bytes) => T; // sync & async versions of sign(), verify()
};
const hashFinishA = <T>(res: Finishable<T>): Promise<T> => sha512a(res.hashable).then(res.finish);
const hashFinishS = <T>(res: Finishable<T>): T => res.finish(sha512s(res.hashable));
// Code, shared between sync & async sign
const _sign = (e: ExtK, rBytes: Bytes, msg: Bytes): Finishable<Bytes> => {
const { pointBytes: P, scalar: s } = e;
const r = modL_LE(rBytes); // r was created outside, reduce it modulo L
const R = G.multiply(r).toBytes(); // R = [r]B
const hashable = concatBytes(R, P, msg); // dom2(F, C) || R || A || PH(M)
const finish = (hashed: Bytes): Bytes => {
// k = SHA512(dom2(F, C) || R || A || PH(M))
const S = modN(r + modL_LE(hashed) * s); // S = (r + k * s) mod L; 0 <= s < l
return abytes(concatBytes(R, numTo32bLE(S)), L2); // 64-byte sig: 32b R.x + 32b LE(S)
};
return { hashable, finish };
};
/**
* Signs message using secret key. Async.
* Follows RFC8032 5.1.6.
*/
const signAsync = async (message: Bytes, secretKey: Bytes): Promise<Bytes> => {
const m = abytes(message);
const e = await getExtendedPublicKeyAsync(secretKey);
const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinishA(_sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
/**
* Signs message using secret key. To use, set `hashes.sha512` first.
* Follows RFC8032 5.1.6.
*/
const sign = (message: Bytes, secretKey: Bytes): Bytes => {
const m = abytes(message);
const e = getExtendedPublicKey(secretKey);
const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinishS(_sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
/** Verification options. zip215: true (default) follows ZIP215 spec. false would follow RFC8032. */
export type EdDSAVerifyOpts = { zip215?: boolean };
const defaultVerifyOpts: EdDSAVerifyOpts = { zip215: true };
const _verify = (
sig: Bytes,
msg: Bytes,
pub: Bytes,
opts: EdDSAVerifyOpts = defaultVerifyOpts
): Finishable<boolean> => {
sig = abytes(sig, L2); // Signature hex str/Bytes, must be 64 bytes
msg = abytes(msg); // Message hex str/Bytes
pub = abytes(pub, L);
const { zip215 } = opts; // switch between zip215 and rfc8032 verif
let A: Point;
let R: Point;
let s: bigint;
let SB: Point;
let hashable: Bytes = Uint8Array.of();
try {
A = Point.fromBytes(pub, zip215); // public key A decoded
R = Point.fromBytes(sig.slice(0, L), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P
s = bytesToNumLE(sig.slice(L, L2)); // Decode second half as an integer S
SB = G.multiply(s, false); // in the range 0 <= s < L
hashable = concatBytes(R.toBytes(), A.toBytes(), msg); // dom2(F, C) || R || A || PH(M)
} catch (error) {}
const finish = (hashed: Bytes): boolean => {
// k = SHA512(dom2(F, C) || R || A || PH(M))
if (SB == null) return false; // false if try-catch catched an error
if (!zip215 && A.isSmallOrder()) return false; // false for SBS: Strongly Binding Signature
const k = modL_LE(hashed); // decode in little-endian, modulo L
const RkA = R.add(A.multiply(k, false)); // [8]R + [8][k]A'
return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A'
};
return { hashable, finish };
};
/** Verifies signature on message and public key. Async. Follows RFC8032 5.1.7. */
const verifyAsync = async (
signature: Bytes,
message: Bytes,
publicKey: Bytes,
opts: EdDSAVerifyOpts = defaultVerifyOpts
): Promise<boolean> => hashFinishA(_verify(signature, message, publicKey, opts));
/** Verifies signature on message and public key. To use, set `hashes.sha512` first. Follows RFC8032 5.1.7. */
const verify = (
signature: Bytes,
message: Bytes,
publicKey: Bytes,
opts: EdDSAVerifyOpts = defaultVerifyOpts
): boolean => hashFinishS(_verify(signature, message, publicKey, opts));
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
const etc = {
bytesToHex: bytesToHex as typeof bytesToHex,
hexToBytes: hexToBytes as typeof hexToBytes,
concatBytes: concatBytes as typeof concatBytes,
mod: M as typeof M,
invert: invert as typeof invert,
randomBytes: randomBytes as typeof randomBytes,
};
const hashes = {
sha512Async: async (message: Bytes): Promise<Bytes> => {
const s = subtle();
const m = concatBytes(message);
return u8n(await s.digest('SHA-512', m.buffer));
},
sha512: undefined as undefined | ((message: Bytes) => Bytes),
};
// FIPS 186 B.4.1 compliant key generation produces private keys
// with modulo bias being neglible. takes >N+16 bytes, returns (hash mod n-1)+1
const randomSecretKey = (seed: Bytes = randomBytes(L)): Bytes => seed;
type KeysSecPub = { secretKey: Bytes; publicKey: Bytes };
const keygen = (seed?: Bytes): KeysSecPub => {
const secretKey = randomSecretKey(seed);
const publicKey = getPublicKey(secretKey);
return { secretKey, publicKey };
};
const keygenAsync = async (seed?: Bytes): Promise<KeysSecPub> => {
const secretKey = randomSecretKey(seed);
const publicKey = await getPublicKeyAsync(secretKey);
return { secretKey, publicKey };
};
/** ed25519-specific key utilities. */
const utils = {
getExtendedPublicKeyAsync: getExtendedPublicKeyAsync as typeof getExtendedPublicKeyAsync,
getExtendedPublicKey: getExtendedPublicKey as typeof getExtendedPublicKey,
randomSecretKey: randomSecretKey as typeof randomSecretKey,
};
// ## Precomputes
// --------------
const W = 8; // W is window size
const scalarBits = 256;
const pwindows = Math.ceil(scalarBits / W) + 1; // 33 for W=8, NOT 32 - see wNAF loop
const pwindowSize = 2 ** (W - 1); // 128 for W=8
const precompute = () => {
const points: Point[] = [];
let p = G;
let b = p;
for (let w = 0; w < pwindows; w++) {
b = p;
points.push(b);
for (let i = 1; i < pwindowSize; i++) {
b = b.add(p);
points.push(b);
} // i=1, bc we skip 0
p = b.double();
}
return points;
};
let Gpows: Point[] | undefined = undefined; // precomputes for base point G
// const-time negate
const ctneg = (cnd: boolean, p: Point) => {
const n = p.negate();
return cnd ? n : p;
};
/**
* Precomputes give 12x faster getPublicKey(), 10x sign(), 2x verify() by
* caching multiples of G (base point). Cache is stored in 32MB of RAM.
* Any time `G.multiply` is done, precomputes are used.
* Not used for getSharedSecret, which instead multiplies random pubkey `P.multiply`.
*
* w-ary non-adjacent form (wNAF) precomputation method is 10% slower than windowed method,
* but takes 2x less RAM. RAM reduction is possible by utilizing `.subtract`.
*
* !! Precomputes can be disabled by commenting-out call of the wNAF() inside Point#multiply().
*/
const wNAF = (n: bigint): { p: Point; f: Point } => {
const comp = Gpows || (Gpows = precompute());
let p = I;
let f = G; // f must be G, or could become I in the end
const pow_2_w = 2 ** W; // 256 for W=8
const maxNum = pow_2_w; // 256 for W=8
const mask = big(pow_2_w - 1); // 255 for W=8 == mask 0b11111111
const shiftBy = big(W); // 8 for W=8
for (let w = 0; w < pwindows; w++) {
let wbits = Number(n & mask); // extract W bits.
n >>= shiftBy; // shift number by W bits.
// We use negative indexes to reduce size of precomputed table by 2x.
// Instead of needing precomputes 0..256, we only calculate them for 0..128.
// If an index > 128 is found, we do (256-index) - where 256 is next window.
// Naive: index +127 => 127, +224 => 224
// Optimized: index +127 => 127, +224 => 256-32
if (wbits > pwindowSize) {
wbits -= maxNum;
n += 1n;
}
const off = w * pwindowSize;
const offF = off; // offsets, evaluate both
const offP = off + Math.abs(wbits) - 1;
const isEven = w % 2 !== 0; // conditions, evaluate both
const isNeg = wbits < 0;
if (wbits === 0) {
// off == I: can't add it. Adding random offF instead.
f = f.add(ctneg(isEven, comp[offF])); // bits are 0: add garbage to fake point
} else {
p = p.add(ctneg(isNeg, comp[offP])); // bits are 1: add to result point
}
}
if (n !== 0n) err('invalid wnaf');
return { p, f }; // return both real and fake points for JIT
};
// !! Remove the export to easily use in REPL / browser console
export {
etc,
getPublicKey,
getPublicKeyAsync,
hash,
hashes,
keygen,
keygenAsync,
Point,
sign,
signAsync,
utils,
verify,
verifyAsync,
};
+55
View File
@@ -0,0 +1,55 @@
{
"name": "@noble/ed25519",
"version": "3.0.0",
"description": "Fastest 5KB JS implementation of ed25519 EDDSA signatures compliant with RFC8032, FIPS 186-5 & ZIP215",
"files": [
"index.js",
"index.d.ts",
"index.ts"
],
"devDependencies": {
"@noble/hashes": "2.0.0",
"@paulmillr/jsbt": "0.4.4",
"@types/node": "24.2.1",
"fast-check": "4.2.0",
"prettier": "3.6.2",
"typescript": "5.9.2"
},
"scripts": {
"build": "tsc",
"build:release": "npx --no @paulmillr/jsbt esbuild test/build",
"bench": "node test/benchmark.ts",
"format": "prettier --write 'index.ts' 'test/**/*.{js,ts}'",
"test": "node --experimental-strip-types --disable-warning=ExperimentalWarning test/index.ts",
"test:bun": "bun test/index.ts",
"test:deno": "deno --allow-env --allow-read test/index.ts",
"test:node20": "cd test; npx tsc; node compiled/test/index.js"
},
"keywords": [
"ed25519",
"rfc8032",
"fips186",
"signature",
"eddsa",
"noble",
"cryptography",
"elliptic curve",
"rfc7748",
"zip215",
"x25519",
"curve25519"
],
"homepage": "https://paulmillr.com/noble/",
"funding": "https://paulmillr.com/funding/",
"repository": {
"type": "git",
"url": "git+https://github.com/paulmillr/noble-ed25519.git"
},
"type": "module",
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
"sideEffects": false,
"author": "Paul Miller (https://paulmillr.com)",
"license": "MIT"
}