# noble-ed25519 trades the extra curves for a 5KB surface you can read

> noble-ed25519 is a TypeScript implementation of Ed25519 signatures that is small enough to audit by eye, and it differs from a general purpose curve library in how it verifies: by default it follows ZIP215 rather than strict RFC8032, which accepts a wider set of signatures. It suits an application that needs Ed25519 and nothing else, and the verification default is the decision to read about first.

**paulmillr/noble-ed25519** — Fastest 5KB JS implementation of ed25519 signatures

- Repository: https://github.com/paulmillr/noble-ed25519
- Website: https://paulmillr.com/noble
- Stars: 523 · Forks: 70
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/paulmillr-noble-ed25519

## Four methods, and the async-only default is a deliberate choice

The API surface is four operations, each with an async variant, and the list is worth reading in full because it is the entire public interface. There is `keygen()` and `keygenAsync()`, `getPublicKey(secretKey)` and `getPublicKeyAsync(secretKey)`, `sign(message, secretKey)` and `signAsync(message, secretKey)`, and `verify(signature, message, publicKey)` and `verifyAsync(signature, message, publicKey)`. All of them accept Uint8Arrays. What is unusual is the default, because only async methods are available by default, to keep the library dependency-free. The rationale is a design constraint rather than an oversight. A synchronous hash function means a second dependency, and the whole point of this package is that it has zero or minimal dependencies. If you want the synchronous methods you install the hash library and wire it in yourself, and the wiring is a single assignment:

```ts
import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha2.js';
ed.hashes.sha512 = sha512;
// Sync methods can be used now:
const { secretKey, publicKey } = ed.keygen();
const msg = new TextEncoder().encode('hello noble');
// const publicKey = ed.getPublicKey(secretKey);
const sig = ed.sign(msg, secretKey);
const isValid = ed.verify(sig, msg, publicKey);
```

The comment in the example makes the point about the hash library being a peer you supply rather than a dependency the package pulls in.

## The ZIP215 default is the one behaviour that differs from other libraries

Verification is where this package makes a choice that will surprise you if you do not know about it. By default, verification follows ZIP215 and can be used in consensus-critical apps, and the `zip215: false` option switches verification criteria to strict RFC8032 and FIPS 186-5, providing non-repudiation with SBS, strongly binding signatures. The documentation also notes that most other libraries do not have SUF-CMA and SBS, calling that a less optimal choice for their security. The two properties are listed at the top of the project page as SUF-CMA, strong unforgeability under chosen message attacks, and SBS, non-repudiation or exclusive ownership. The practical difference is a set of edge-case signatures that strict verification rejects and ZIP215 accepts, and the documentation is explicit about the consequence: any message with a public key from `ED25519_TORSION_SUBGROUP` would be valid in signatures under ZIP215. So the default is the permissive one, and the strict one is opt-in. For a consensus system that has agreed on ZIP215, that is the correct default. For a system comparing validity against a conventional implementation, it is a mismatch waiting to happen, and the fix is one option flag.

## A 5KB target, gzipped to 3.7KB, and why the size matters

The size claim is the project's headline, and it is specific in a way that can be checked. The package describes itself as the fastest 5KB JS implementation of ed25519 signatures and states 3.7KB gzipped. The build setup in the package manifest shows how the figure is kept honest, with a benchmark script, a size benchmark that runs the `bismar` tool against the public key, sign, verify and utils entry points, a build step of `tsc`, a check script named `jsbt-check`, a format script, and a test script that runs the test file directly with node. The repository layout matches that, with `index.ts` at the root as the whole implementation, a `test/` directory, an `audit/` directory, a `SECURITY.md`, a `CHANGELOG.md` and a `jsr.json` for the JSR registry alongside the npm package. The size matters because a signature library is code that has to be reviewed by people who are not the author, and a reviewer can read 3.7KB of gzipped source in a way they cannot read 40KB. That is the whole argument, and it is why the package tells you to use it when you need smaller attack surface and better auditability.

## The sister package covers the features this one deliberately leaves out

The scope boundary is stated plainly and it is the difference between the two packages. Use noble-ed25519 if you need smaller attack surface and better auditability, and switch to noble-curves, which the documentation calls a drop-in, if you need features like ristretto255, x25519 or curve25519, ed25519ph, hash-to-curve, or oprf. The module is described as a sister project of noble-curves, and the broader set of libraries is listed as ciphers, curves, hashes and post-quantum, with a separate 5kb secp256k1 and ed25519. Two implications for a reader. First, the split is by audience rather than by version, so a project that starts with Ed25519 only and later needs key agreement has a migration ahead, and the documentation says the switch is drop-in, which is as close to a guarantee as documentation gets. Second, ed25519ph is not in this package, and the sign section says that for prehashed ed25519ph you should switch to noble-curves. The verification section carries the same message from the other direction, and mentions a WASM version of the family at awasm-noble.

## The key API returns more than a public key, and the point is the seed

The getPublicKey section has a detail that catches people. The basic operation generates a 32-byte public key from a 32-byte private key, and the documentation notes that some libraries have 64-byte private keys, where those are just the private and public keys concatenated. Beyond that there are two conversions, and they are not the same thing. `ExtendedPoint.fromHex(publicKey)` converts hex or bytes into a Point, using the decompression algorithm 5.1.3 of RFC 8032. `utils.getExtendedPublicKey` gives you the full SHA512 hash of the seed, and the extended public key example shows both being used side by side, along with `ed.Point.fromBytes` for the plain conversion. The reason the seed hash matters is that it is the pre-image state from which the scalar is derived, and a library that hands you the raw seed hash is not making you derive it yourself. The utilities block goes further, exposing `bytesToHex`, `hexToBytes`, `concatBytes`, `mod`, `invert` and `randomBytes` under `ed.etc`, plus `getExtendedPublicKey`, `getExtendedPublicKeyAsync` and `randomSecretKey` under `ed.utils`, and a `Point` class with `fromAffine`, `fromBytes`, `fromHex`, `assertValidity`, `equals`, `is0` and `negate` methods.

## React Native needs two polyfills and one of them cannot be done from here

The mobile story has a hard limit, and the documentation is candid about which part of it is the platform's fault. React Native does not provide secure getRandomValues by default, and this cannot be securely polyfilled from the library's end, so a React Native specific compile-time dependency is needed. The recipe is an import of a random values package, the library import, the sha512 wiring, and one extra line for the async hash:

```ts
import 'react-native-get-random-values';
import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha2.js';
ed.hashes.sha512 = sha512;
ed.hashes.sha512Async = (m: Uint8Array) => Promise.resolve(sha512(m));
```

Two things are notable in that snippet. The `sha512Async` assignment exists only because the library expects an async hash and the polyfill is synchronous, so the promise is wrapped by hand. And the getRandomValues import is not optional, because a key generated from a predictable random source is not a key. The documentation says the library supports all major platforms and runtimes, and that React Native is the case where additional polyfills are needed.

## Releases are signed and the builds are transparent

The supply chain story is part of the project's design, not a footnote. The noble-cryptography set is described as a high-security, easily auditable set of contained cryptographic libraries and tools, with zero or minimal dependencies, highly readable TypeScript and JS code, PGP-signed releases and transparent NPM builds. The package metadata in the repository backs the transparency claim, since the published files list is short, with `index.js`, `index.d.ts` and `index.ts` and nothing else, and the package is an ES module with `sideEffects` set to false. The dependency list in the manifest is entirely dev dependencies, including the hash library used for tests, a JavaScript benchmarking tool called jsbt, the bismar size tool, TypeScript, Prettier and the Node types. Nothing there is shipped to a consumer, which is the practical meaning of the zero dependency claim for a cryptography package: the code that ends up in your bundle is the code in the repository, and you can read it.

## Where a 5KB Ed25519 package is the wrong choice

Two limits are worth stating. The first is the scope, and it is not a small one. This package does Ed25519 signatures and nothing else, so an application that also needs key agreement, a different curve, a prehashed variant or an OPRF has to adopt a second library or switch to the larger one, and the documentation tells you to switch. The second is the verification default, which is the detail that decides whether you can use it at all. ZIP215 verification accepts a wider set of signatures than strict RFC8032, and the documentation names the torsion subgroup case explicitly. If your system has to interoperate with a validator that implements strict verification, or if you are relying on non-repudiation, then the default is the wrong setting and you must pass `zip215: false`, which means the security property you get depends on an option you have to remember to set. The alternative to weigh against is a general purpose library such as noble-curves, where the same Ed25519 implementation sits alongside the other curves and shares one codebase. The difference in approach is scope versus review: a package this small can be read end to end, while a library covering many curves cannot, and a project that only needs one algorithm has to decide which of those two it values more.

## Conclusion

noble-ed25519 is worth choosing if your application signs with Ed25519 and you want the implementation to be small enough that a reviewer can read it, since the whole point of the package is a smaller attack surface and better auditability, and since it delivers strong unforgeability and strong binding signatures where most other libraries do not. It is a poor fit if you also need x25519, ristretto255, ed25519ph or hash-to-curve, because the documentation says to switch to noble-curves for those, and a poor fit if you are comparing signature validity against a system that does not implement ZIP215, since the default verification accepts more signatures than strict RFC8032 does. Before you adopt it, decide which verification criteria you need, because the `zip215: false` option changes what counts as valid, and note that the last push to the repository was on 2026-08-30 with release 3.2.0 published on 2026-08-27.

## FAQ

### What is noble-ed25519 and what standards does it implement?

noble-ed25519 is a 5KB JavaScript and TypeScript implementation of ed25519 signatures, described as the fastest 5KB JS implementation, and it is 3.7KB gzipped. It implements EdDSA signatures compliant with RFC8032 and FIPS 186-5, and is consensus-friendly and compliant with ZIP215.

### How do I install noble-ed25519?

With npm install @noble/ed25519, or with deno add jsr:@noble/ed25519 for Deno. Only async methods are available by default so the package stays dependency-free; to enable the synchronous methods you also install @noble/hashes and assign sha512 into ed.hashes.sha512.

### What is the difference between ZIP215 and strict verification in noble-ed25519?

By default verification follows ZIP215 and can be used in consensus-critical apps, and passing zip215: false switches the criteria to strict RFC8032 and FIPS 186-5, providing non-repudiation with strongly binding signatures. The readme notes that any message with a public key from the ED25519_TORSION_SUBGROUP would be valid in signatures under ZIP215.

### What do I need to use noble-ed25519 in React Native?

React Native does not provide secure getRandomValues by default and this cannot be securely polyfilled from the library's end, so a React Native specific compile-time dependency is needed. You import react-native-get-random-values, import the library, assign sha512 into ed.hashes.sha512, and set ed.hashes.sha512Async to wrap the synchronous hash in a promise.

## Sources

- [License: MIT](https://github.com/paulmillr/noble-ed25519/blob/main/LICENSE)
- [paulmillr/noble-ed25519 on GitHub](https://github.com/paulmillr/noble-ed25519)
- [Project website](https://paulmillr.com/noble)
- [README](https://github.com/paulmillr/noble-ed25519/blob/main/README.md)
- [Releases](https://github.com/paulmillr/noble-ed25519/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/paulmillr-noble-ed25519
