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
347 lines
13 KiB
Markdown
347 lines
13 KiB
Markdown
# 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
|