@noble/ed25519
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -25,3 +25,3 @@ declare const CURVE: { | ||
static fromAffine(p: AffinePoint): Point; | ||
static fromHex(hex: Hex, strict?: boolean): Point; | ||
static fromHex(hex: Hex, zip215?: boolean): Point; | ||
get x(): bigint; | ||
@@ -55,4 +55,8 @@ get y(): bigint; | ||
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 verifyAsync: (s: Hex, m: Hex, p: Hex, opts?: { | ||
zip215: boolean; | ||
}) => Promise<boolean>; | ||
declare const verify: (s: Hex, m: Hex, p: Hex, opts?: { | ||
zip215: boolean; | ||
}) => boolean; | ||
declare const etc: { | ||
@@ -64,3 +68,3 @@ bytesToHex: (b: Bytes) => string; | ||
invert: (num: bigint, md?: bigint) => bigint; | ||
randomBytes: (len: number) => Bytes; | ||
randomBytes: (len?: number) => Bytes; | ||
sha512Async: (...messages: Bytes[]) => Promise<Bytes>; | ||
@@ -67,0 +71,0 @@ sha512Sync: Sha512FnSync; |
73
index.js
@@ -7,3 +7,3 @@ /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
const CURVE = { | ||
a: -1n, | ||
a: -1n, // where a=-1, d = -(121665/121666) == -(121665 * inv(121666)) mod P | ||
d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n, | ||
@@ -14,10 +14,11 @@ p: P, n: N, h: 8, Gx, Gy // field prime, curve (group) order, cofactor | ||
const str = (s) => typeof s === 'string'; // is string | ||
const isu8 = (a) => (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
const au8 = (a, l) => // is Uint8Array (of specific length) | ||
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
err('Uint8Array expected') : a; | ||
!isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
err('Uint8Array of valid length 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 toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(au8(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 { | ||
@@ -31,16 +32,13 @@ constructor(ex, ey, ez, et) { | ||
static fromAffine(p) { return new Point(p.x, p.y, 1n, mod(p.x * p.y)); } | ||
static fromHex(hex, strict = true) { | ||
static fromHex(hex, zip215 = false) { | ||
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 lastByte = hex[31]; | ||
normed[31] = lastByte & ~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] | ||
} | ||
if (zip215 && !(0n <= y && y < 2n ** 256n)) | ||
err('bad y coord 1'); // zip215=true [1..2^256-1] | ||
if (!zip215 && !(0n <= y && y < P)) | ||
err('bad y coord 2'); // zip215=false [1..P-1] | ||
const y2 = mod(y * y); // y² | ||
@@ -53,4 +51,6 @@ const u = mod(y2 - 1n); // u=y²-1 | ||
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate | ||
const isHeadOdd = (hex[31] & 0x80) !== 0; | ||
if (isHeadOdd !== isXOdd) | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit | ||
if (!zip215 && x === 0n && isLastByteOdd) | ||
err('bad y coord 3'); // x=0 and x_0 = 1 | ||
if (isLastByteOdd !== isXOdd) | ||
x = mod(-x); | ||
@@ -137,4 +137,4 @@ return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy | ||
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 | ||
if (this.equals(I)) | ||
return { x: 0n, y: 1n }; // fast-path for zero point | ||
const iz = invert(z); // z^-1: invert z | ||
@@ -284,11 +284,21 @@ if (mod(z * iz) !== 1n) | ||
}; | ||
const _verify = (sig, msg, pub) => { | ||
const dvo = { zip215: true }; | ||
const _verify = (sig, msg, pub, opts = dvo) => { | ||
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 { zip215 } = opts; // switch between zip215 and rfc8032 verif | ||
let A, R, s, SB, hashable = new Uint8Array(); | ||
try { | ||
A = Point.fromHex(pub, zip215); // public key A decoded | ||
R = Point.fromHex(sig.slice(0, 32), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P | ||
s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S | ||
SB = G.mul(s, false); // in the range 0 <= s < L | ||
hashable = concatB(R.toRawBytes(), A.toRawBytes(), msg); // dom2(F, C) || R || A || PH(M) | ||
} | ||
catch (error) { } | ||
const finish = (hashed) => { | ||
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 | ||
@@ -301,4 +311,4 @@ const RkA = R.add(A.mul(k, false)); // [8]R + [8][k]A' | ||
// 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 verifyAsync = async (s, m, p, opts = dvo) => hashFinish(true, _verify(s, m, p, opts)); | ||
const verify = (s, m, p, opts = dvo) => hashFinish(false, _verify(s, m, p, opts)); | ||
const cr = () => // We support: 1) browsers 2) node.js 19+ | ||
@@ -309,7 +319,7 @@ typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined; | ||
mod, invert, | ||
randomBytes: (len) => { | ||
randomBytes: (len = 32) => { | ||
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) | ||
if (!crypto || !crypto.getRandomValues) | ||
err('crypto.getRandomValues must be defined'); | ||
@@ -320,3 +330,3 @@ return crypto.getRandomValues(u8n(len)); | ||
const crypto = cr(); | ||
if (!crypto) | ||
if (!crypto || !crypto.subtle) | ||
err('crypto.subtle or etc.sha512Async must be defined'); | ||
@@ -335,3 +345,3 @@ const m = concatB(...messages); | ||
randomPrivateKey: () => etc.randomBytes(32), | ||
precompute(w = 8, p = G) { p.multiply(3n); return p; }, // no-op | ||
precompute(w = 8, p = G) { p.multiply(3n); w; return p; }, // no-op | ||
}; | ||
@@ -354,2 +364,3 @@ const W = 8; // Precomputes-related code. W = window size | ||
}; | ||
let Gpows = undefined; // precomputes for base point G | ||
const wNAF = (n) => { | ||
@@ -356,0 +367,0 @@ // Compared to other point mult methods, |
70
index.ts
@@ -6,3 +6,3 @@ /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
const Gy = 0x6666666666666666666666666666666666666666666666666666666666666658n; // base point y | ||
const CURVE = { // Curve's formula is −x² + y² = -a + dx²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 | ||
@@ -15,10 +15,13 @@ d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n, | ||
const str = (s: unknown): s is string => typeof s === 'string'; // is string | ||
const isu8 = (a: unknown): a is Uint8Array => ( | ||
a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
); | ||
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; | ||
!isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? | ||
err('Uint8Array of valid length 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 toU8 = (a: Hex, len?: number) => au8(str(a) ? h2b(a) : u8n(au8(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 | ||
@@ -30,13 +33,11 @@ class Point { // Point in xyzt extended coordinates | ||
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. | ||
static fromHex(hex: Hex, zip215 = false) { // 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 lastByte = hex[31]; | ||
normed[31] = lastByte & ~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] | ||
} | ||
if (zip215 && !(0n <= y && y < 2n ** 256n)) err('bad y coord 1'); // zip215=true [1..2^256-1] | ||
if (!zip215 && !(0n <= y && y < P)) err('bad y coord 2'); // zip215=false [1..P-1] | ||
const y2 = mod(y * y); // y² | ||
@@ -48,4 +49,5 @@ const u = mod(y2 - 1n); // u=y²-1 | ||
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate | ||
const isHeadOdd = (hex[31] & 0x80) !== 0; | ||
if (isHeadOdd !== isXOdd) x = mod(-x); | ||
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit | ||
if (!zip215 && x === 0n && isLastByteOdd) err('bad y coord 3'); // x=0 and x_0 = 1 | ||
if (isLastByteOdd !== isXOdd) x = mod(-x); | ||
return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy | ||
@@ -107,3 +109,3 @@ } | ||
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 | ||
if (this.equals(I)) return { x: 0n, y: 1n }; // fast-path for zero point | ||
const iz = invert(z); // z^-1: invert z | ||
@@ -120,3 +122,3 @@ if (mod(z * iz) !== 1n) err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math | ||
toHex(): string { return b2h(this.toRawBytes()); } // encode to hex string | ||
} | ||
@@ -250,14 +252,21 @@ const { BASE: G, ZERO: I } = Point; // Generator, identity points | ||
}; | ||
const _verify = (sig: Hex, msg: Hex, pub: Hex): Finishable<boolean> => { // sig verification | ||
const dvo = { zip215: true }; | ||
const _verify = (sig: Hex, msg: Hex, pub: Hex, opts = dvo): Finishable<boolean> => { | ||
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 { zip215 } = opts; // switch between zip215 and rfc8032 verif | ||
let A: Point, R: Point, s: bigint, SB: Point, hashable = new Uint8Array(); | ||
try { | ||
A = Point.fromHex(pub, zip215); // public key A decoded | ||
R = Point.fromHex(sig.slice(0, 32), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P | ||
s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S | ||
SB = G.mul(s, false); // in the range 0 <= s < L | ||
hashable = concatB(R.toRawBytes(), A.toRawBytes(), 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.mul(k, false)); // [8]R + [8][k]A' | ||
return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A' | ||
return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A' | ||
} | ||
@@ -267,4 +276,6 @@ 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)); | ||
const verifyAsync = async (s: Hex, m: Hex, p: Hex, opts = dvo) => | ||
hashFinish(true, _verify(s, m, p, opts)); | ||
const verify = (s: Hex, m: Hex, p: Hex, opts = dvo) => | ||
hashFinish(false, _verify(s, m, p, opts)); | ||
declare const globalThis: Record<string, any> | undefined; // Typescript symbol present in browsers | ||
@@ -276,7 +287,7 @@ const cr = () => // We support: 1) browsers 2) node.js 19+ | ||
mod, invert, | ||
randomBytes: (len: number): Bytes => { // CSPRNG (random number generator) | ||
randomBytes: (len = 32): 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'); | ||
if (!crypto || !crypto.getRandomValues) err('crypto.getRandomValues must be defined'); | ||
return crypto.getRandomValues(u8n(len)); | ||
@@ -286,3 +297,3 @@ }, | ||
const crypto = cr(); | ||
if (!crypto) err('crypto.subtle or etc.sha512Async must be defined'); | ||
if (!crypto || !crypto.subtle) err('crypto.subtle or etc.sha512Async must be defined'); | ||
const m = concatB(...messages); | ||
@@ -299,3 +310,3 @@ return u8n(await crypto.subtle.digest('SHA-512', m.buffer)); | ||
randomPrivateKey: (): Bytes => etc.randomBytes(32), | ||
precompute(w=8, p: Point = G) { p.multiply(3n); return p; }, // no-op | ||
precompute(w=8, p: Point = G) { p.multiply(3n); w; return p; }, // no-op | ||
} | ||
@@ -315,2 +326,3 @@ const W = 8; // Precomputes-related code. W = window size | ||
} | ||
let Gpows: Point[] | undefined = undefined; // precomputes for base point G | ||
const wNAF = (n: bigint): { p: Point; f: Point } => { // w-ary non-adjacent form (wNAF) method. | ||
@@ -317,0 +329,0 @@ // Compared to other point mult methods, |
{ | ||
"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", | ||
"version": "2.1.0", | ||
"description": "Fastest 4KB JS implementation of ed25519 EDDSA signatures compliant with RFC8032, FIPS 186-5 & ZIP215", | ||
"files": [ | ||
@@ -14,9 +14,12 @@ "index.js", | ||
"types": "index.d.ts", | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "tsc", | ||
"build:release": "rollup -c rollup.config.js", | ||
"test": "node test/ed25519.test.mjs", | ||
"build:min": "cd test/build; npm install; npm run terser", | ||
"build:mingz": "npm run --silent build:min | gzip -c8", | ||
"build:release": "npm run --silent build:min > test/build/noble-ed25519.min.js; npm run --silent build:mingz > test/build/noble-ed25519.min.js.gz", | ||
"test": "node test/index.test.js", | ||
"test:webcrypto": "node test/ed25519.webcrypto.test.js", | ||
"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\"" | ||
"loc": "echo \"`npm run --silent build:min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent build:mingz | wc -c`B gzipped\"" | ||
}, | ||
@@ -27,11 +30,12 @@ "author": "Paul Miller (https://paulmillr.com)", | ||
"type": "git", | ||
"url": "https://github.com/paulmillr/noble-ed25519.git" | ||
"url": "git+https://github.com/paulmillr/noble-ed25519.git" | ||
}, | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@noble/hashes": "1.3.0", | ||
"@noble/hashes": "1.4.0", | ||
"@paulmillr/jsbt": "0.1.0", | ||
"fast-check": "3.0.0", | ||
"micro-bmark": "0.3.0", | ||
"micro-should": "0.4.0", | ||
"typescript": "5.0.2" | ||
"typescript": "5.3.2" | ||
}, | ||
@@ -41,2 +45,3 @@ "keywords": [ | ||
"rfc8032", | ||
"fips186", | ||
"signature", | ||
@@ -47,7 +52,4 @@ "eddsa", | ||
"elliptic curve", | ||
"ecc", | ||
"curve", | ||
"rfc7748", | ||
"zip215", | ||
"ristretto255", | ||
"x25519", | ||
@@ -62,8 +64,3 @@ "curve25519" | ||
}, | ||
"funding": [ | ||
{ | ||
"type": "individual", | ||
"url": "https://paulmillr.com/funding/" | ||
} | ||
] | ||
"funding": "https://paulmillr.com/funding/" | ||
} |
255
README.md
# 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). | ||
Fastest 4KB JS implementation of ed25519 signatures. | ||
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. | ||
- ✍️ [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) | ||
- 📦 Pure ESM, can be imported without transpilers | ||
- 🪶 4KB gzipped, 350 lines of code | ||
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. | ||
Use larger drop-in replacement [noble-curves](https://github.com/paulmillr/noble-curves) instead, | ||
if you need additional features such as common.js support, ristretto255, X25519, curve25519, ed25519ph, ed25519ctx. | ||
To upgrade from v1 to v2, see [Upgrading](#upgrading). [Online demo](https://paulmillr.com/noble/). | ||
### This library belongs to _noble_ crypto | ||
### This library belongs to _noble_ cryptography | ||
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools. | ||
> **noble-cryptography** — 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 | ||
- 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: | ||
[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) | ||
[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), | ||
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) / | ||
[ed25519](https://github.com/paulmillr/noble-ed25519) | ||
## Usage | ||
Browser, deno, node.js and unpkg are supported: | ||
> npm install @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'; // ESM-only. Use bundler for common.js | ||
import * as ed from '@noble/ed25519'; | ||
// import * as ed from "https://deno.land/x/ed25519/mod.ts"; // Deno | ||
@@ -47,3 +46,3 @@ // import * as ed from "https://unpkg.com/@noble/ed25519"; // Unpkg | ||
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]); | ||
const pubKey = await ed.getPublicKeyAsync(privKey); | ||
const pubKey = await ed.getPublicKeyAsync(privKey); // Sync methods below | ||
const signature = await ed.signAsync(message, privKey); | ||
@@ -54,19 +53,22 @@ const isValid = await ed.verifyAsync(signature, message, pubKey); | ||
Advanced examples: | ||
Additional polyfills for some environments: | ||
```ts | ||
// 1. Use the shim to enable synchronous methods. | ||
// Only async methods are available by default to keep library dependency-free. | ||
// 1. Enable synchronous methods. | ||
// Only async methods are available by default, to keep the 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); | ||
// Sync methods can be used now: | ||
// ed.getPublicKey(privKey); ed.sign(msg, privKey); ed.verify(signature, msg, pubKey); | ||
// 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. | ||
// 2. node.js 18 and older, requires polyfilling globalThis.crypto | ||
import { webcrypto } from 'node:crypto'; | ||
// @ts-ignore | ||
if (!globalThis.crypto) globalThis.crypto = webcrypto; | ||
// 3. React Native needs crypto.getRandomValues polyfill and sha512 | ||
import 'react-native-get-random-values'; | ||
import { sha512 } from '@noble/hashes/sha512'; | ||
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); | ||
ed.etc.sha512Async = (...m) => Promise.resolve(ed.etc.sha512Sync(...m)); | ||
``` | ||
@@ -77,17 +79,26 @@ | ||
There are 3 main methods: `getPublicKey(privateKey)`, `sign(message, privateKey)` | ||
and `verify(signature, message, publicKey)`. | ||
and `verify(signature, message, publicKey)`. We accept Hex type everywhere: | ||
```ts | ||
type Hex = Uint8Array | string | ||
``` | ||
### getPublicKey | ||
```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. | ||
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 | ||
### sign | ||
```ts | ||
function sign( | ||
@@ -98,15 +109,17 @@ message: Hex, // message which would be signed | ||
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 [It’s 255:19AM](https://hdevalence.ca/blog/2020-10-04-its-25519am). | ||
// - _Not compatible with RFC8032_ because RFC enforces canonical encoding of | ||
// R/publicKey. | ||
Generates EdDSA signature. Always deterministic. | ||
Assumes unhashed `message`: it would be hashed by ed25519 internally. | ||
For prehashed ed25519ph, switch to noble-curves. | ||
### verify | ||
```ts | ||
function verify( | ||
signature: Hex, // returned by the `sign` function | ||
message: Hex, // message that needs to be verified | ||
publicKey: Hex // public (not private) key | ||
publicKey: Hex // public (not private) key, | ||
options = { zip215: true } // ZIP215 or RFC8032 verification type | ||
): boolean; | ||
@@ -116,35 +129,50 @@ function verifyAsync(signature: Hex, message: Hex, publicKey: Hex): Promise<boolean>; | ||
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]. | ||
[1]: https://zips.z.cash/zip-0215 | ||
[2]: https://hdevalence.ca/blog/2020-10-04-its-25519am | ||
[3]: https://eprint.iacr.org/2020/1244 | ||
### utils | ||
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; | ||
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; | ||
const utils: { | ||
getExtendedPublicKeyAsync: (priv: Hex) => Promise<ExtK>; | ||
getExtendedPublicKey: (priv: Hex) => ExtK; | ||
precompute(p: Point, w?: number): Point; | ||
randomPrivateKey: () => Bytes; // Uses CSPRNG https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues | ||
}; | ||
export class ExtendedPoint { // Elliptic curve point in Extended (x, y, z, t) coordinates. | ||
constructor(x: bigint, y: bigint, z: bigint, t: bigint); | ||
class ExtendedPoint { // Elliptic curve point in Extended (x, y, z, t) coordinates. | ||
constructor(ex: bigint, ey: bigint, ez: bigint, et: bigint); | ||
static readonly BASE: Point; | ||
static readonly ZERO: Point; | ||
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; | ||
get x(): bigint; | ||
get y(): bigint; | ||
// Note: It does not check whether the `other` point is valid point on curve. | ||
add(other: ExtendedPoint): ExtendedPoint; | ||
equals(other: ExtendedPoint): boolean; | ||
isTorsionFree(): boolean; // Multiplies the point by curve order | ||
multiply(scalar: bigint): ExtendedPoint; | ||
subtract(other: ExtendedPoint): ExtendedPoint; | ||
multiply(scalar: bigint): ExtendedPoint; | ||
toAffine(): Point; | ||
toRawBytes(): Uint8Array; | ||
toHex(): string; // Compact representation of a Point | ||
} | ||
@@ -161,37 +189,58 @@ // Curve params | ||
The module is production-ready. | ||
It is cross-tested against [noble-curves](https://github.com/paulmillr/noble-curves), | ||
and has similar security. | ||
The library has not been independently audited as of v2, which is a rewrite of v1. | ||
v1 has been audited by [Cure53](https://cure53.de/pentest-report_ed25519.pdf) in Feb 2022. | ||
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. | ||
The code is identical to [noble-curves](https://github.com/paulmillr/noble-curves), which *has* been audited. | ||
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, | ||
It is tested against property-based, cross-library and Wycheproof vectors, | ||
and has fuzzing by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). | ||
### Constant-timeness | ||
_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. | ||
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. Nonetheless we're targetting algorithmic constant time. | ||
### Supply chain security | ||
1. **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures. | ||
2. **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs | ||
3. **Rare releasing** is followed. | ||
The less often it is done, the less code dependents would need to audit | ||
4. **Dependencies** are minimal: | ||
- All deps are prevented from automatic updates and have locked-down version ranges. Every update is checked with `npm-diff` | ||
- Updates themselves are rare, to ensure rogue updates are not catched accidentally | ||
5. devDependencies are only used if you want to contribute to the repo. They are disabled for end-users: | ||
- [noble-hashes](https://github.com/paulmillr/noble-hashes) is used, by the same author, to provide hashing functionality tests | ||
- micro-bmark and micro-should are developed by the same author and follow identical security practices | ||
- fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. | ||
The packages are big, which makes it hard to audit their source code thoroughly and fully | ||
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. | ||
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 install. Our goal is to minimize this attack vector. | ||
If you see anything unusual: investigate and report. | ||
### 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. | ||
## Speed | ||
Benchmarks done with Apple M2 on macOS 13 with Node.js 19. | ||
Benchmarks done with Apple M2 on macOS 13 with Node.js 20. | ||
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 | ||
getPublicKey(utils.randomPrivateKey()) x 9,173 ops/sec @ 109μs/op | ||
sign x 4,567 ops/sec @ 218μs/op | ||
verify x 994 ops/sec @ 1ms/op | ||
Point.fromHex decompression x 16,164 ops/sec @ 61μs/op | ||
@@ -210,3 +259,3 @@ Compare to alternative implementations: | ||
3. `npm run build` to compile TypeScript code | ||
4. `npm run test` to run jest on `test/index.ts` | ||
4. `npm run test` to run tests | ||
@@ -213,0 +262,0 @@ ## Upgrading |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
59595
803
285
0
6