Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 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.
+235
View File
@@ -0,0 +1,235 @@
# noble-ed25519
[Fastest](#speed) 4KB JS implementation of [ed25519](https://en.wikipedia.org/wiki/EdDSA)
elliptic curve. Auditable, high-security, 0-dependency EdDSA signatures compliant with
[RFC8032](https://tools.ietf.org/html/rfc8032) and [ZIP215](https://zips.z.cash/zip-0215).
The library is a tiny single-feature version of
[noble-curves](https://github.com/paulmillr/noble-curves), with some features
removed. Check out curves as a drop-in replacement with
[ristretto255](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448),
X25519 / curve25519, ed25519ph and ed25519ctx.
Take a look at: [Upgrading](#upgrading) section for v1 to v2 transition instructions,
[the online demo](https://paulmillr.com/noble/) and
[ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) if you need
SSH/PGP/HDKey implementation using the library.
### This library belongs to _noble_ crypto
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies, protection against supply chain attacks
- Auditable TypeScript / JS code
- Supported in all major browsers and stable node.js versions
- All releases are signed with PGP keys
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
[curves](https://github.com/paulmillr/noble-curves)
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1),
[ed25519](https://github.com/paulmillr/noble-ed25519)),
[hashes](https://github.com/paulmillr/noble-hashes)
## Usage
Browser, deno, node.js and unpkg are supported:
> npm install @noble/ed25519
```js
import * as ed from '@noble/ed25519'; // ESM-only. Use bundler for common.js
// import * as ed from "https://deno.land/x/ed25519/mod.ts"; // Deno
// import * as ed from "https://unpkg.com/@noble/ed25519"; // Unpkg
(async () => {
// keys, messages & other inputs can be Uint8Arrays or hex strings
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef'
const privKey = ed.utils.randomPrivateKey(); // Secure random private key
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
const pubKey = await ed.getPublicKeyAsync(privKey);
const signature = await ed.signAsync(message, privKey);
const isValid = await ed.verifyAsync(signature, message, pubKey);
})();
```
Advanced examples:
```ts
// 1. Use the shim to enable synchronous methods.
// Only async methods are available by default to keep library dependency-free.
import { sha512 } from '@noble/hashes/sha512';
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
ed.getPublicKey(privateKey); // sync methods can be used now
ed.sign(message, privateKey);
ed.verify(signature, message, publicKey);
// 2. Use the shim only for node.js <= 18 BEFORE importing noble-secp256k1.
// The library depends on global variable crypto to work. It is available in
// all browsers and many environments, but node.js <= 18 don't have it.
import { webcrypto } from 'node:crypto';
// @ts-ignore
if (!globalThis.crypto) globalThis.crypto = webcrypto;
```
## API
There are 3 main methods: `getPublicKey(privateKey)`, `sign(message, privateKey)`
and `verify(signature, message, publicKey)`.
```typescript
type Hex = Uint8Array | string;
// Generates 32-byte public key from 32-byte private key.
// - Some libraries have 64-byte private keys. Don't worry, those are just
// priv+pub concatenated. Slice it: `priv64b.slice(0, 32)`
// - Use `Point.fromPrivateKey(privateKey)` if you want `Point` instance instead
// - Use `Point.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
function getPublicKey(privateKey: Hex): Uint8Array;
function getPublicKeyAsync(privateKey: Hex): Promise<Uint8Array>;
// Generates EdDSA signature.
function sign(
message: Hex, // message which would be signed
privateKey: Hex // 32-byte private key
): Uint8Array;
function signAsync(message: Hex, privateKey: Hex): Promise<Uint8Array>;
// Verifies EdDSA signature. Compatible with [ZIP215](https://zips.z.cash/zip-0215):
// - `0 <= sig.R/publicKey < 2**256` (can be `>= curve.P` aka non-canonical encoding)
// - `0 <= sig.s < l`
// - There is no security risk in ZIP behavior, and there is no effect on
// honestly generated sigs, but it is verify important for consensus-critical
// apps. See [Its 255:19AM](https://hdevalence.ca/blog/2020-10-04-its-25519am).
// - _Not compatible with RFC8032_ because RFC enforces canonical encoding of
// R/publicKey.
function verify(
signature: Hex, // returned by the `sign` function
message: Hex, // message that needs to be verified
publicKey: Hex // public (not private) key
): boolean;
function verifyAsync(signature: Hex, message: Hex, publicKey: Hex): Promise<boolean>;
```
A bunch of useful **utilities** are also exposed:
```typescript
export const etc: {
bytesToHex: (b: Bytes) => string;
hexToBytes: (hex: string) => Bytes;
concatBytes: (...arrs: Bytes[]) => Uint8Array;
mod: (a: bigint, b?: bigint) => bigint;
invert: (num: bigint, md?: bigint) => bigint;
randomBytes: (len: number) => Bytes;
sha512Async: (...messages: Bytes[]) => Promise<Bytes>;
sha512Sync: Sha512FnSync;
};
export const utils: {
getExtendedPublicKeyAsync: (priv: Hex) => Promise<ExtK>;
getExtendedPublicKey: (priv: Hex) => ExtK;
precompute(p: Point, w?: number): Point;
randomPrivateKey: () => Bytes;
};
export class ExtendedPoint { // Elliptic curve point in Extended (x, y, z, t) coordinates.
constructor(x: bigint, y: bigint, z: bigint, t: bigint);
static fromAffine(point: AffinePoint): ExtendedPoint;
static fromHex(hash: string);
toRawBytes(): Uint8Array;
toHex(): string; // Compact representation of a Point
isTorsionFree(): boolean; // Multiplies the point by curve order
toAffine(): Point;
equals(other: ExtendedPoint): boolean;
// Note: It does not check whether the `other` point is valid point on curve.
add(other: ExtendedPoint): ExtendedPoint;
subtract(other: ExtendedPoint): ExtendedPoint;
multiply(scalar: bigint): ExtendedPoint;
}
// Curve params
ed25519.CURVE.p // 2 ** 255 - 19
ed25519.CURVE.n // 2 ** 252 + 27742317777372353535851937790883648493
ed25519.ExtendedPoint.BASE // new ed25519.Point(Gx, Gy) where
// Gx=15112221349535400772501151409588531511454012693041857206046113283949847762202n
// Gy=46316835694926478169428394003475163141307993866256225615783033603165251855960n;
```
## Security
The module is production-ready.
It is cross-tested against [noble-curves](https://github.com/paulmillr/noble-curves),
and has similar security.
1. The current version is rewrite of v1, which has been audited by cure53:
[PDF](https://cure53.de/pentest-report_ed25519.pdf).
2. It's being fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz):
run the fuzzer by yourself to check.
Our EC multiplication is hardened to be algorithmically constant time.
We're using built-in JS `BigInt`, which is potentially vulnerable to
[timing attacks](https://en.wikipedia.org/wiki/Timing_attack) as
[per MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#cryptography).
But, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard
to achieve in a scripting language. Which means _any other JS library doesn't
use constant-time bigints_. Including bn.js or anything else.
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.
We consider infrastructure attacks like rogue NPM modules very important;
that's why it's crucial to minimize the amount of 3rd-party dependencies & native
bindings. If your app uses 500 dependencies, any dep could get hacked and you'll
be downloading malware with every `npm install`. Our goal is to minimize this attack vector.
## Speed
Benchmarks done with Apple M2 on macOS 13 with Node.js 19.
getPublicKey 1 bit x 8,260 ops/sec @ 121μs/op
getPublicKey(utils.randomPrivateKey()) x 8,096 ops/sec @ 123μs/op
sign x 4,084 ops/sec @ 244μs/op
verify x 872 ops/sec @ 1ms/op
Point.fromHex decompression x 14,523 ops/sec @ 68μ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
## Contributing
1. Clone the repository
2. `npm install` to install build dependencies like TypeScript
3. `npm run build` to compile TypeScript code
4. `npm run test` to run jest on `test/index.ts`
## Upgrading
noble-ed25519 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)
## License
MIT (c) 2019 Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.
+73
View File
@@ -0,0 +1,73 @@
declare const CURVE: {
a: bigint;
d: bigint;
p: bigint;
n: bigint;
h: number;
Gx: bigint;
Gy: bigint;
};
type Bytes = Uint8Array;
type Hex = Bytes | string;
interface AffinePoint {
x: bigint;
y: bigint;
}
declare class Point {
readonly ex: bigint;
readonly ey: bigint;
readonly ez: bigint;
readonly et: bigint;
constructor(ex: bigint, ey: bigint, ez: bigint, et: bigint);
static readonly BASE: Point;
static readonly ZERO: Point;
static fromAffine(p: AffinePoint): Point;
static fromHex(hex: Hex, strict?: boolean): Point;
get x(): bigint;
get y(): bigint;
equals(other: Point): boolean;
is0(): boolean;
negate(): Point;
double(): Point;
add(other: Point): Point;
mul(n: bigint, safe?: boolean): Point;
multiply(scalar: bigint): Point;
clearCofactor(): Point;
isSmallOrder(): boolean;
isTorsionFree(): boolean;
toAffine(): AffinePoint;
toRawBytes(): Bytes;
toHex(): string;
}
type Sha512FnSync = undefined | ((...messages: Bytes[]) => Bytes);
type ExtK = {
head: Bytes;
prefix: Bytes;
scalar: bigint;
point: Point;
pointBytes: Bytes;
};
declare const getPublicKeyAsync: (priv: Hex) => Promise<Bytes>;
declare const getPublicKey: (priv: Hex) => Bytes;
declare const signAsync: (msg: Hex, privKey: Hex) => Promise<Bytes>;
declare const sign: (msg: Hex, privKey: Hex) => Bytes;
declare const verifyAsync: (s: Hex, m: Hex, p: Hex) => Promise<boolean>;
declare const verify: (s: Hex, m: Hex, p: Hex) => boolean;
declare const etc: {
bytesToHex: (b: Bytes) => string;
hexToBytes: (hex: string) => Bytes;
concatBytes: (...arrs: Bytes[]) => Uint8Array;
mod: (a: bigint, b?: bigint) => bigint;
invert: (num: bigint, md?: bigint) => bigint;
randomBytes: (len: number) => Bytes;
sha512Async: (...messages: Bytes[]) => Promise<Bytes>;
sha512Sync: Sha512FnSync;
};
declare const utils: {
getExtendedPublicKeyAsync: (priv: Hex) => Promise<ExtK>;
getExtendedPublicKey: (priv: Hex) => ExtK;
randomPrivateKey: () => Bytes;
precompute(w?: number, p?: Point): Point;
};
export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL
signAsync, verifyAsync, CURVE, etc, utils, Point as ExtendedPoint };
+374
View File
@@ -0,0 +1,374 @@
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
const P = 2n ** 255n - 19n; // ed25519 is twisted edwards curve
const N = 2n ** 252n + 27742317777372353535851937790883648493n; // curve's (group) order
const Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an; // base point x
const Gy = 0x6666666666666666666666666666666666666666666666666666666666666658n; // base point y
const CURVE = {
a: -1n,
d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n,
p: P, n: N, h: 8, Gx, Gy // field prime, curve (group) order, cofactor
};
const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace
const str = (s) => typeof s === 'string'; // is string
const au8 = (a, l) => // is Uint8Array (of specific length)
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ?
err('Uint8Array expected') : a;
const u8n = (data) => new Uint8Array(data); // creates Uint8Array
const toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a
const mod = (a, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division
const isPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is xyzt point
let Gpows = undefined; // precomputes for base point G
class Point {
constructor(ex, ey, ez, et) {
this.ex = ex;
this.ey = ey;
this.ez = ez;
this.et = et;
}
static fromAffine(p) { return new Point(p.x, p.y, 1n, mod(p.x * p.y)); }
static fromHex(hex, strict = true) {
const { d } = CURVE;
hex = toU8(hex, 32);
const normed = hex.slice(); // copy the array to not mess it up
normed[31] = hex[31] & ~0x80; // adjust first LE byte = last BE byte
const y = b2n_LE(normed); // decode as little-endian, convert to num
if (y === 0n) { // y=0 is valid, proceed
}
else {
if (strict && !(0n < y && y < P))
err('bad y coord 1'); // strict=true [1..P-1]
if (!strict && !(0n < y && y < 2n ** 256n))
err('bad y coord 2'); // strict=false [1..2^256-1]
}
const y2 = mod(y * y); // y²
const u = mod(y2 - 1n); // u=y²-1
const v = mod(d * y2 + 1n); // v=dy²+1
let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root
if (!isValid)
err('bad y coordinate 3'); // not square root: bad point
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate
const isHeadOdd = (hex[31] & 0x80) !== 0;
if (isHeadOdd !== isXOdd)
x = mod(-x);
return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy
}
get x() { return this.toAffine().x; } // .x, .y will call expensive toAffine.
get y() { return this.toAffine().y; } // Should be used with care.
equals(other) {
const { ex: X1, ey: Y1, ez: Z1 } = this;
const { ex: X2, ey: Y2, ez: Z2 } = isPoint(other); // isPoint() checks class equality
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1);
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
}
is0() { return this.equals(I); }
negate() {
return new Point(mod(-this.ex), this.ey, this.ez, mod(-this.et));
}
double() {
const { ex: X1, ey: Y1, ez: Z1 } = this; // Cost: 4M + 4S + 1*a + 6add + 1*2
const { a } = CURVE; // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
const A = mod(X1 * X1);
const B = mod(Y1 * Y1);
const C = mod(2n * mod(Z1 * Z1));
const D = mod(a * A);
const x1y1 = X1 + Y1;
const E = mod(mod(x1y1 * x1y1) - A - B);
const G = D + B;
const F = G - C;
const H = D - B;
const X3 = mod(E * F);
const Y3 = mod(G * H);
const T3 = mod(E * H);
const Z3 = mod(F * G);
return new Point(X3, Y3, Z3, T3);
}
add(other) {
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; // Cost: 8M + 1*k + 8add + 1*2.
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = isPoint(other); // doesn't check if other on-curve
const { a, d } = CURVE; // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
const A = mod(X1 * X2);
const B = mod(Y1 * Y2);
const C = mod(T1 * d * T2);
const D = mod(Z1 * Z2);
const E = mod((X1 + Y1) * (X2 + Y2) - A - B);
const F = mod(D - C);
const G = mod(D + C);
const H = mod(B - a * A);
const X3 = mod(E * F);
const Y3 = mod(G * H);
const T3 = mod(E * H);
const Z3 = mod(F * G);
return new Point(X3, Y3, Z3, T3);
}
mul(n, safe = true) {
if (n === 0n)
return safe === true ? err('cannot multiply by 0') : I;
if (!(typeof n === 'bigint' && 0n < n && n < N))
err('invalid scalar, must be < L');
if (!safe && this.is0() || n === 1n)
return this; // safe=true bans 0. safe=false allows 0.
if (this.equals(G))
return wNAF(n).p; // use wNAF precomputes for base points
let p = I, f = G; // init result point & fake point
for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
if (n & 1n)
p = p.add(d); // if bit is present, add to point
else if (safe)
f = f.add(d); // if not, add to fake for timing safety
}
return p;
}
multiply(scalar) { return this.mul(scalar); } // Aliases for compatibilty
clearCofactor() { return this.mul(BigInt(CURVE.h), false); } // multiply by cofactor
isSmallOrder() { return this.clearCofactor().is0(); } // check if P is small order
isTorsionFree() {
let p = this.mul(N / 2n, false).double(); // ensures the point is not "bad".
if (N % 2n)
p = p.add(this); // P^(N+1) // P*N == (P*(N/2))*2+P
return p.is0();
}
toAffine() {
const { ex: x, ey: y, ez: z } = this; // (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy)
if (this.is0())
return { x: 0n, y: 0n }; // fast-path for zero point
const iz = invert(z); // z^-1: invert z
if (mod(z * iz) !== 1n)
err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math
return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1
}
toRawBytes() {
const { x, y } = this.toAffine(); // convert to affine 2d point
const b = n2b_32LE(y); // encode number to 32 bytes
b[31] |= x & 1n ? 0x80 : 0; // store sign in first LE byte
return b;
}
toHex() { return b2h(this.toRawBytes()); } // encode to hex string
}
Point.BASE = new Point(Gx, Gy, 1n, mod(Gx * Gy)); // Generator / Base point
Point.ZERO = new Point(0n, 1n, 1n, 0n); // Identity / Zero point
const { BASE: G, ZERO: I } = Point; // Generator, identity points
const padh = (num, pad) => num.toString(16).padStart(pad, '0');
const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex
const h2b = (hex) => {
const l = hex.length; // error if not string,
if (!str(hex) || l % 2)
err('hex invalid 1'); // or has odd length like 3, 5.
const arr = u8n(l / 2); // create result array
for (let i = 0; i < arr.length; i++) {
const j = i * 2;
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr
const b = Number.parseInt(h, 16); // byte, created from string part
if (Number.isNaN(b) || b < 0)
err('hex invalid 2'); // byte must be valid 0 <= byte < 256
arr[i] = b;
}
return arr;
};
const n2b_32LE = (num) => h2b(padh(num, 32 * 2)).reverse(); // number to bytes LE
const b2n_LE = (b) => BigInt('0x' + b2h(u8n(au8(b)).reverse())); // bytes LE to num
const concatB = (...arrs) => {
const r = u8n(arrs.reduce((sum, a) => sum + au8(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;
};
const invert = (num, md = P) => {
if (num === 0n || md <= 0n)
err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
while (a !== 0n) { // uses euclidean gcd algorithm
const q = b / a, r = b % a; // not constant-time
const m = x - u * q, n = y - v * q;
b = a, a = r, x = u, y = v, u = m, v = n;
}
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point
};
const pow2 = (x, power) => {
let r = x;
while (power-- > 0n) {
r *= r;
r %= P;
}
return r;
};
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 = 19681161376707505956807079304988542015446066515923890162744021073123829784752n; // √-1
const uvRatio = (u, v) => {
const v3 = mod(v * v * v); // v³
const v7 = mod(v3 * v3 * v); // v⁷
const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8
let x = mod(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8
const vx2 = mod(v * x * x); // vx²
const root1 = x; // First root candidate
const root2 = mod(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 === mod(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4)
const noRoot = vx2 === mod(-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 ((mod(x) & 1n) === 1n)
x = mod(-x); // edIsNegative
return { isValid: useRoot1 || useRoot2, value: x };
};
const modL_LE = (hash) => mod(b2n_LE(hash), N); // modulo L; but little-endian
let _shaS;
const sha512a = (...m) => etc.sha512Async(...m); // Async SHA512
const sha512s = (...m) => // Sync SHA512, not set by default
typeof _shaS === 'function' ? _shaS(...m) : err('etc.sha512Sync not set');
const hash2extK = (hashed) => {
const head = hashed.slice(0, 32); // slice creates a copy, unlike subarray
head[0] &= 248; // Clamp bits: 0b1111_1000,
head[31] &= 127; // 0b0111_1111,
head[31] |= 64; // 0b0100_0000
const prefix = hashed.slice(32, 64); // private key "prefix"
const scalar = modL_LE(head); // modular division over curve order
const point = G.mul(scalar); // public key point
const pointBytes = point.toRawBytes(); // 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 = (priv) => sha512a(toU8(priv, 32)).then(hash2extK);
const getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, 32)));
const getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then(p => p.pointBytes);
const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
function hashFinish(asynchronous, res) {
if (asynchronous)
return sha512a(res.hashable).then(res.finish);
return res.finish(sha512s(res.hashable));
}
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.mul(r).toRawBytes(); // R = [r]B
const hashable = concatB(R, P, msg); // dom2(F, C) || R || A || PH(M)
const finish = (hashed) => {
const S = mod(r + modL_LE(hashed) * s, N); // S = (r + k * s) mod L; 0 <= s < l
return au8(concatB(R, n2b_32LE(S)), 64); // 64-byte sig: 32b R.x + 32b LE(S)
};
return { hashable, finish };
};
const signAsync = async (msg, privKey) => {
const m = toU8(msg); // RFC8032 5.1.6: sign msg with key async
const e = await getExtendedPublicKeyAsync(privKey); // pub,prfx
const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinish(true, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
const sign = (msg, privKey) => {
const m = toU8(msg); // RFC8032 5.1.6: sign msg with key sync
const e = getExtendedPublicKey(privKey); // pub,prfx
const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinish(false, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
const _verify = (sig, msg, pub) => {
msg = toU8(msg); // Message hex str/Bytes
sig = toU8(sig, 64); // Signature hex str/Bytes, must be 64 bytes
const A = Point.fromHex(pub, false); // public key A decoded
const R = Point.fromHex(sig.slice(0, 32), false); // 0 <= R < 2^256: ZIP215 R can be >= P
const s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S
const SB = G.mul(s, false); // in the range 0 <= s < L
const hashable = concatB(R.toRawBytes(), A.toRawBytes(), msg); // dom2(F, C) || R || A || PH(M)
const finish = (hashed) => {
const k = modL_LE(hashed); // decode in little-endian, modulo L
const RkA = R.add(A.mul(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 };
};
// RFC8032 5.1.7: verification async, sync
const verifyAsync = async (s, m, p) => hashFinish(true, _verify(s, m, p));
const verify = (s, m, p) => hashFinish(false, _verify(s, m, p));
const cr = () => // We support: 1) browsers 2) node.js 19+
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
const etc = {
bytesToHex: b2h, hexToBytes: h2b, concatBytes: concatB,
mod, invert,
randomBytes: (len) => {
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error:
// import { webcrypto } from 'node:crypto';
// if (!globalThis.crypto) globalThis.crypto = webcrypto;
if (!crypto)
err('crypto.getRandomValues must be defined');
return crypto.getRandomValues(u8n(len));
},
sha512Async: async (...messages) => {
const crypto = cr();
if (!crypto)
err('crypto.subtle or etc.sha512Async must be defined');
const m = concatB(...messages);
return u8n(await crypto.subtle.digest('SHA-512', m.buffer));
},
sha512Sync: undefined, // Actual logic below
};
Object.defineProperties(etc, { sha512Sync: {
configurable: false, get() { return _shaS; }, set(f) { if (!_shaS)
_shaS = f; },
} });
const utils = {
getExtendedPublicKeyAsync, getExtendedPublicKey,
randomPrivateKey: () => etc.randomBytes(32),
precompute(w = 8, p = G) { p.multiply(3n); return p; }, // no-op
};
const W = 8; // Precomputes-related code. W = window size
const precompute = () => {
const points = []; // 10x sign(), 2x verify(). To achieve this,
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
let p = G, b = p; // a lot of points related to base point G.
for (let w = 0; w < windows; w++) { // Points are stored in array and used
b = p; // any time Gx multiplication is done.
points.push(b); // They consume 16-32 MiB of RAM.
for (let i = 1; i < 2 ** (W - 1); i++) {
b = b.add(p);
points.push(b);
}
p = b.double(); // Precomputes don't speed-up getSharedKey,
} // which multiplies user point by scalar,
return points; // when precomputes are using base point
};
const wNAF = (n) => {
// Compared to other point mult methods,
const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction
const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate
let p = I, f = G; // f must be G, or could become I in the end
const windows = 1 + 256 / W; // W=8 17 windows
const wsize = 2 ** (W - 1); // W=8 128 window size
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
const maxNum = 2 ** W; // W=8 256
const shiftBy = BigInt(W); // W=8 8
for (let w = 0; w < windows; w++) {
const off = w * wsize;
let wbits = Number(n & mask); // extract W bits.
n >>= shiftBy; // shift number by W bits.
if (wbits > wsize) {
wbits -= maxNum;
n += 1n;
} // split if bits > max: +224 => 256-32
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
if (wbits === 0) {
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
}
else { // ^ can't add off2, off2 = I
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
}
}
return { p, f }; // return both real and fake points for JIT
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL
signAsync, verifyAsync, CURVE, etc, utils, Point as ExtendedPoint }; // envs like browser console
+330
View File
@@ -0,0 +1,330 @@
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
const P = 2n ** 255n - 19n; // ed25519 is twisted edwards curve
const N = 2n ** 252n + 27742317777372353535851937790883648493n; // curve's (group) order
const Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an; // base point x
const Gy = 0x6666666666666666666666666666666666666666666666666666666666666658n; // base point y
const CURVE = { // Curve's formula is x² + y² = -a + dx²y²
a: -1n, // where a=-1, d = -(121665/121666) == -(121665 * inv(121666)) mod P
d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n,
p: P, n: N, h: 8, Gx, Gy // field prime, curve (group) order, cofactor
};
type Bytes = Uint8Array; type Hex = Bytes | string; // types
const err = (m = ''): never => { throw new Error(m); }; // error helper, messes-up stack trace
const str = (s: unknown): s is string => typeof s === 'string'; // is string
const au8 = (a: unknown, l?: number): Bytes => // is Uint8Array (of specific length)
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ?
err('Uint8Array expected') : a;
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const toU8 = (a: Hex, len?: number) => au8(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a
const mod = (a: bigint, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division
const isPoint = (p: any) => (p instanceof Point ? p : err('Point expected')); // is xyzt point
let Gpows: Point[] | undefined = undefined; // precomputes for base point G
interface AffinePoint { x: bigint, y: bigint } // Point in 2d xy affine coordinates
class Point { // Point in xyzt extended coordinates
constructor(readonly ex: bigint, readonly ey: bigint, readonly ez: bigint, readonly et: bigint) {}
static readonly BASE = new Point(Gx, Gy, 1n, mod(Gx * Gy)); // Generator / Base point
static readonly ZERO = new Point(0n, 1n, 1n, 0n); // Identity / Zero point
static fromAffine(p: AffinePoint) { return new Point(p.x, p.y, 1n, mod(p.x * p.y)); }
static fromHex(hex: Hex, strict = true) { // RFC8032 5.1.3: hex / Uint8Array to Point.
const { d } = CURVE;
hex = toU8(hex, 32);
const normed = hex.slice(); // copy the array to not mess it up
normed[31] = hex[31] & ~0x80; // adjust first LE byte = last BE byte
const y = b2n_LE(normed); // decode as little-endian, convert to num
if (y === 0n) { // y=0 is valid, proceed
} else {
if (strict && !(0n < y && y < P)) err('bad y coord 1'); // strict=true [1..P-1]
if (!strict && !(0n < y && y < 2n ** 256n)) err('bad y coord 2'); // strict=false [1..2^256-1]
}
const y2 = mod(y * y); // y²
const u = mod(y2 - 1n); // u=y²-1
const v = mod(d * y2 + 1n); // v=dy²+1
let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root
if (!isValid) err('bad y coordinate 3'); // not square root: bad point
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate
const isHeadOdd = (hex[31] & 0x80) !== 0;
if (isHeadOdd !== isXOdd) x = mod(-x);
return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy
}
get x() { return this.toAffine().x; } // .x, .y will call expensive toAffine.
get y() { return this.toAffine().y; } // Should be used with care.
equals(other: Point): boolean { // equality check: compare points
const { ex: X1, ey: Y1, ez: Z1 } = this;
const { ex: X2, ey: Y2, ez: Z2 } = isPoint(other); // isPoint() checks class equality
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1);
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1);
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
}
is0(): boolean { return this.equals(I); }
negate(): Point { // negate: flip over the affine x coordinate
return new Point(mod(-this.ex), this.ey, this.ez, mod(-this.et));
}
double(): Point { // Point doubling. Complete formula.
const { ex: X1, ey: Y1, ez: Z1 } = this; // Cost: 4M + 4S + 1*a + 6add + 1*2
const { a } = CURVE; // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
const A = mod(X1 * X1); const B = mod(Y1 * Y1); const C = mod(2n * mod(Z1 * Z1));
const D = mod(a * A); const x1y1 = X1 + Y1; const E = mod(mod(x1y1 * x1y1) - A - B);
const G = D + B; const F = G - C; const H = D - B;
const X3 = mod(E * F); const Y3 = mod(G * H); const T3 = mod(E * H); const Z3 = mod(F * G);
return new Point(X3, Y3, Z3, T3);
}
add(other: Point) { // Point addition. Complete formula.
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; // Cost: 8M + 1*k + 8add + 1*2.
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = isPoint(other); // doesn't check if other on-curve
const { a, d } = CURVE; // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
const A = mod(X1 * X2); const B = mod(Y1 * Y2); const C = mod(T1 * d * T2);
const D = mod(Z1 * Z2); const E = mod((X1 + Y1) * (X2 + Y2) - A - B);
const F = mod(D - C); const G = mod(D + C); const H = mod(B - a * A);
const X3 = mod(E * F); const Y3 = mod(G * H); const T3 = mod(E * H); const Z3 = mod(F * G);
return new Point(X3, Y3, Z3, T3);
}
mul(n: bigint, safe = true): Point { // Multiply point by scalar n
if (n === 0n) return safe === true ? err('cannot multiply by 0') : I;
if (!(typeof n === 'bigint' && 0n < n && n < N)) err('invalid scalar, must be < L');
if (!safe && this.is0() || n === 1n) return this; // safe=true bans 0. safe=false allows 0.
if (this.equals(G)) return wNAF(n).p; // use wNAF precomputes for base points
let p = I, f = G; // init result point & fake point
for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
if (n & 1n) p = p.add(d); // if bit is present, add to point
else if (safe) f = f.add(d); // if not, add to fake for timing safety
}
return p;
}
multiply(scalar: bigint) { return this.mul(scalar); } // Aliases for compatibilty
clearCofactor(): Point { return this.mul(BigInt(CURVE.h), false); } // multiply by cofactor
isSmallOrder(): boolean { return this.clearCofactor().is0(); } // check if P is small order
isTorsionFree(): boolean { // multiply by big number CURVE.n
let p = this.mul(N / 2n, false).double(); // ensures the point is not "bad".
if (N % 2n) p = p.add(this); // P^(N+1) // P*N == (P*(N/2))*2+P
return p.is0();
}
toAffine(): AffinePoint { // converts point to 2d xy affine point
const { ex: x, ey: y, ez: z } = this; // (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy)
if (this.is0()) return { x: 0n, y: 0n }; // fast-path for zero point
const iz = invert(z); // z^-1: invert z
if (mod(z * iz) !== 1n) err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math
return { x: mod(x * iz), y: mod(y * iz) } // x = x*z^-1; y = y*z^-1
}
toRawBytes(): Bytes { // Encode to Uint8Array
const { x, y } = this.toAffine(); // convert to affine 2d point
const b = n2b_32LE(y); // encode number to 32 bytes
b[31] |= x & 1n ? 0x80 : 0; // store sign in first LE byte
return b;
}
toHex(): string { return b2h(this.toRawBytes()); } // encode to hex string
}
const { BASE: G, ZERO: I } = Point; // Generator, identity points
const padh = (num: number | bigint, pad: number) => num.toString(16).padStart(pad, '0')
const b2h = (b: Bytes): string => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex
const h2b = (hex: string): Bytes => { // hex to bytes
const l = hex.length; // error if not string,
if (!str(hex) || l % 2) err('hex invalid 1'); // or has odd length like 3, 5.
const arr = u8n(l / 2); // create result array
for (let i = 0; i < arr.length; i++) {
const j = i * 2;
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr
const b = Number.parseInt(h, 16); // byte, created from string part
if (Number.isNaN(b) || b < 0) err('hex invalid 2'); // byte must be valid 0 <= byte < 256
arr[i] = b;
}
return arr;
};
const n2b_32LE = (num: bigint) => h2b(padh(num, 32 * 2)).reverse(); // number to bytes LE
const b2n_LE = (b: Bytes): bigint => BigInt('0x' + b2h(u8n(au8(b)).reverse())); // bytes LE to num
const concatB = (...arrs: Bytes[]) => { // concatenate Uint8Array-s
const r = u8n(arrs.reduce((sum, a) => sum + au8(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;
};
const invert = (num: bigint, md = P): bigint => { // modular inversion
if (num === 0n || md <= 0n) err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
while (a !== 0n) { // uses euclidean gcd algorithm
const q = b / a, r = b % a; // not constant-time
const m = x - u * q, n = y - v * q;
b = a, a = r, x = u, y = v, u = m, v = n;
}
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point
};
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;
}
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 = 19681161376707505956807079304988542015446066515923890162744021073123829784752n; // √-1
const uvRatio = (u: bigint, v: bigint): { isValid: boolean, value: bigint } => { // for sqrt comp
const v3 = mod(v * v * v); // v³
const v7 = mod(v3 * v3 * v); // v⁷
const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8
let x = mod(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8
const vx2 = mod(v * x * x); // vx²
const root1 = x; // First root candidate
const root2 = mod(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 === mod(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4)
const noRoot = vx2 === mod(-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 ((mod(x) & 1n) === 1n) x = mod(-x); // edIsNegative
return { isValid: useRoot1 || useRoot2, value: x };
}
const modL_LE = (hash: Bytes): bigint => mod(b2n_LE(hash), N); // modulo L; but little-endian
type Sha512FnSync = undefined | ((...messages: Bytes[]) => Bytes);
let _shaS: Sha512FnSync;
const sha512a = (...m: Bytes[]) => etc.sha512Async(...m); // Async SHA512
const sha512s = (...m: Bytes[]) => // Sync SHA512, not set by default
typeof _shaS === 'function' ? _shaS(...m) : err('etc.sha512Sync not set');
type ExtK = { head: Bytes, prefix: Bytes, scalar: bigint, point: Point, pointBytes: Bytes };
const hash2extK = (hashed: Bytes): ExtK => { // RFC8032 5.1.5
const head = hashed.slice(0, 32); // slice creates a copy, unlike subarray
head[0] &= 248; // Clamp bits: 0b1111_1000,
head[31] &= 127; // 0b0111_1111,
head[31] |= 64; // 0b0100_0000
const prefix = hashed.slice(32, 64); // private key "prefix"
const scalar = modL_LE(head); // modular division over curve order
const point = G.mul(scalar); // public key point
const pointBytes = point.toRawBytes(); // 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 = (priv: Hex) => sha512a(toU8(priv, 32)).then(hash2extK);
const getExtendedPublicKey = (priv: Hex) => hash2extK(sha512s(toU8(priv, 32)))
const getPublicKeyAsync = (priv: Hex): Promise<Bytes> =>
getExtendedPublicKeyAsync(priv).then(p => p.pointBytes)
const getPublicKey = (priv: Hex): Bytes => getExtendedPublicKey(priv).pointBytes;
type Finishable<T> = { // Reduces logic duplication between
hashable: Bytes, finish: (hashed: Bytes) => T // sync & async versions of sign(), verify()
} // hashable=start(); finish(hash(hashable));
function hashFinish<T>(asynchronous: true, res: Finishable<T>): Promise<T>;
function hashFinish<T>(asynchronous: false, res: Finishable<T>): T;
function hashFinish<T>(asynchronous: boolean, res: Finishable<T>) {
if (asynchronous) return sha512a(res.hashable).then(res.finish);
return res.finish(sha512s(res.hashable));
}
const _sign = (e: ExtK, rBytes: Bytes, msg: Bytes): Finishable<Bytes> => { // sign() shared code
const { pointBytes: P, scalar: s } = e;
const r = modL_LE(rBytes); // r was created outside, reduce it modulo L
const R = G.mul(r).toRawBytes(); // R = [r]B
const hashable = concatB(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 = mod(r + modL_LE(hashed) * s, N); // S = (r + k * s) mod L; 0 <= s < l
return au8(concatB(R, n2b_32LE(S)), 64); // 64-byte sig: 32b R.x + 32b LE(S)
}
return { hashable, finish };
};
const signAsync = async (msg: Hex, privKey: Hex): Promise<Bytes> => {
const m = toU8(msg); // RFC8032 5.1.6: sign msg with key async
const e = await getExtendedPublicKeyAsync(privKey); // pub,prfx
const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinish(true, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
const sign = (msg: Hex, privKey: Hex): Bytes => {
const m = toU8(msg); // RFC8032 5.1.6: sign msg with key sync
const e = getExtendedPublicKey(privKey); // pub,prfx
const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M))
return hashFinish(false, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature
};
const _verify = (sig: Hex, msg: Hex, pub: Hex): Finishable<boolean> => { // sig verification
msg = toU8(msg); // Message hex str/Bytes
sig = toU8(sig, 64); // Signature hex str/Bytes, must be 64 bytes
const A = Point.fromHex(pub, false); // public key A decoded
const R = Point.fromHex(sig.slice(0, 32), false); // 0 <= R < 2^256: ZIP215 R can be >= P
const s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S
const SB = G.mul(s, false); // in the range 0 <= s < L
const hashable = concatB(R.toRawBytes(), A.toRawBytes(), msg); // dom2(F, C) || R || A || PH(M)
const finish = (hashed: Bytes): boolean => { // k = SHA512(dom2(F, C) || R || A || PH(M))
const k = modL_LE(hashed); // decode in little-endian, modulo L
const RkA = R.add(A.mul(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 };
};
// RFC8032 5.1.7: verification async, sync
const verifyAsync = async (s: Hex, m: Hex, p: Hex) => hashFinish(true, _verify(s, m, p));
const verify = (s: Hex, m: Hex, p: Hex) => hashFinish(false, _verify(s, m, p));
declare const globalThis: Record<string, any> | undefined; // Typescript symbol present in browsers
const cr = () => // We support: 1) browsers 2) node.js 19+
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
const etc = {
bytesToHex: b2h, hexToBytes: h2b, concatBytes: concatB,
mod, invert,
randomBytes: (len: number): Bytes => { // CSPRNG (random number generator)
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error:
// import { webcrypto } from 'node:crypto';
// if (!globalThis.crypto) globalThis.crypto = webcrypto;
if (!crypto) err('crypto.getRandomValues must be defined');
return crypto.getRandomValues(u8n(len));
},
sha512Async: async (...messages: Bytes[]): Promise<Bytes> => {
const crypto = cr();
if (!crypto) err('crypto.subtle or etc.sha512Async must be defined');
const m = concatB(...messages);
return u8n(await crypto.subtle.digest('SHA-512', m.buffer));
},
sha512Sync: undefined as Sha512FnSync, // Actual logic below
};
Object.defineProperties(etc, { sha512Sync: { // Allow setting it once. Next sets will be ignored
configurable: false, get() { return _shaS; }, set(f) { if (!_shaS) _shaS = f; },
} });
const utils = {
getExtendedPublicKeyAsync, getExtendedPublicKey,
randomPrivateKey: (): Bytes => etc.randomBytes(32),
precompute(w=8, p: Point = G) { p.multiply(3n); return p; }, // no-op
}
const W = 8; // Precomputes-related code. W = window size
const precompute = () => { // They give 12x faster getPublicKey(),
const points: Point[] = []; // 10x sign(), 2x verify(). To achieve this,
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
let p = G, b = p; // a lot of points related to base point G.
for (let w = 0; w < windows; w++) { // Points are stored in array and used
b = p; // any time Gx multiplication is done.
points.push(b); // They consume 16-32 MiB of RAM.
for (let i = 1; i < 2 ** (W - 1); i++) { b = b.add(p); points.push(b); }
p = b.double(); // Precomputes don't speed-up getSharedKey,
} // which multiplies user point by scalar,
return points; // when precomputes are using base point
}
const wNAF = (n: bigint): { p: Point; f: Point } => { // w-ary non-adjacent form (wNAF) method.
// Compared to other point mult methods,
const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction
const neg = (cnd: boolean, p: Point) => { let n = p.negate(); return cnd ? n : p; } // negate
let p = I, f = G; // f must be G, or could become I in the end
const windows = 1 + 256 / W; // W=8 17 windows
const wsize = 2 ** (W - 1); // W=8 128 window size
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
const maxNum = 2 ** W; // W=8 256
const shiftBy = BigInt(W); // W=8 8
for (let w = 0; w < windows; w++) {
const off = w * wsize;
let wbits = Number(n & mask); // extract W bits.
n >>= shiftBy; // shift number by W bits.
if (wbits > wsize) { wbits -= maxNum; n += 1n; } // split if bits > max: +224 => 256-32
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
if (wbits === 0) {
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
} else { // ^ can't add off2, off2 = I
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
}
}
return { p, f } // return both real and fake points for JIT
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL
signAsync, verifyAsync, CURVE, etc, utils, Point as ExtendedPoint } // envs like browser console
+64
View File
@@ -0,0 +1,64 @@
{
"name": "@noble/ed25519",
"version": "2.0.0",
"description": "Fastest 4KB JS implementation of ed25519 elliptic curve. Auditable, high-security, 0-dependency EDDSA signatures compliant with RFC8032 & ZIP215",
"files": [
"index.js",
"index.d.ts",
"index.ts"
],
"type": "module",
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
"scripts": {
"build": "tsc",
"build:release": "rollup -c rollup.config.js",
"test": "node test/ed25519.test.mjs",
"bench": "node test/benchmark/benchmark.js",
"min": "cd test/build; npm install; npm run terser",
"loc": "echo \"`npm run --silent min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent min | gzip -c8 | wc -c`B gzipped\""
},
"author": "Paul Miller (https://paulmillr.com)",
"homepage": "https://paulmillr.com/noble/",
"repository": {
"type": "git",
"url": "https://github.com/paulmillr/noble-ed25519.git"
},
"license": "MIT",
"devDependencies": {
"@noble/hashes": "1.3.0",
"fast-check": "3.0.0",
"micro-bmark": "0.3.0",
"micro-should": "0.4.0",
"typescript": "5.0.2"
},
"keywords": [
"ed25519",
"rfc8032",
"signature",
"eddsa",
"noble",
"cryptography",
"elliptic curve",
"ecc",
"curve",
"rfc7748",
"zip215",
"ristretto255",
"x25519",
"curve25519"
],
"exports": {
".": {
"types": "./index.d.ts",
"default": "./index.js"
}
},
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
}