noble-ed25519
ed25519, an elliptic curve that could be used for assymetric encryption and EDDSA signature scheme.
Supports ristretto255.
This library belongs to noble crypto
noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies, one small file
- Easily auditable TypeScript/JS code
- Uses es2019 bigint. Supported in Chrome, Firefox, node 10+
- All releases are signed and trusted
- Check out all libraries: secp256k1, ed25519, bls12-381, ripemd160, secretbox-aes-gcm
Usage
npm install noble-ed25519
import * as ed25519 from "noble-ed25519";
const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
const HASH_MESSSAGE = new Uint8Array([99, 100, 101, 102, 103]);
(async () => {
const publicKey = await ed25519.getPublicKey(PRIVATE_KEY);
const signature = await ed25519.sign(HASH_MESSAGE, PRIVATE_KEY);
const isMessageSigned = await ed25519.verify(signature, HASH_MESSAGE, publicKey);
})();
API
getPublicKey(privateKey)
function getPublicKey(privateKey: Uint8Array): Promise<Uint8Array>;
function getPublicKey(privateKey: string): Promise<string>;
function getPublicKey(privateKey: bigint): Promise<Point>;
privateKey: Uint8Array | string | bigint
will be used to generate public key. Public key is generated by executing scalar multiplication of a base Point(x, y) by a fixed integer. The result is anotherPoint(x, y)
which we will by default encode to hex Uint8Array.- Returns:
Promise<Uint8Array>
ifUint8Array
was passedPromise<string>
if hexstring
was passedPromise<Point(x, y)>
instance ifbigint
was passed- Uses promises, because ed25519 uses SHA internally; and we're using built-in browser
window.crypto
, which returnsPromise
.
sign(hash, privateKey)
function sign(hash: Uint8Array, privateKey: Uint8Array | bigint): Promise<Uint8Array>;
function sign(hash: string, privateKey: string | bigint): Promise<string>;
hash: Uint8Array
- message hash which would be signedprivateKey: Uint8Array | bigint
- private key which will sign the hash- Returns EdDSA signature. You can consume it with
SignResult.fromHex()
method:SignResult.fromHex(ed25519.sign(hash, privateKey))
verify(signature, hash, publicKey)
function verify(
signature: Uint8Array | string | SignResult,
hash: Uint8Array | string,
publicKey: string | Point | Uint8Array
): Promise<boolean>
signature: Uint8Array
- object returned by thesign
functionhash: string | Uint8Array
- message hash that needs to be verifiedpublicKey: string | Uint8Array | Point
- e.g. that was generated fromprivateKey
bygetPublicKey
- Returns
Promise<boolean>
:Promise<true>
ifsignature == hash
; otherwisePromise<false>
Helpers & Point
utils.precompute(W = 4, point = BASE_POINT)
This is done by default, no need to run it unless you want to disable precomputation or change window size.
We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT values.
This slows down first getPublicKey() by milliseconds (see Speed section), but allows to speed-up subsequent getPublicKey() calls up to 20x.
The precomputation window is variable. For example, we increase W to 8 for tests, to speed-up tests 2x.
You may want to precompute values for your own point.
// 𝔽p
ed25519.P // 2 ^ 255 - 19
// Subgroup order
ed25519.PRIME_ORDER // 2 ^ 252 - 27742317777372353535851937790883648493
// Elliptic curve point
ed25519.Point {
static fromY(y: bigint);
static fromHex(hash: string);
constructor(x: bigint, y: bigint);
toHex(): string; // Compact representation of a Point
toX25519(): bigint; // To montgomery
encode(): Uint8Array;
add(other: Point): Point;
subtract(other: Point): Point;
multiply(scalar: bigint): Point;
}
ed25519.SignResult {
constructor(r: bigint, s: bigint);
toHex(): string;
}
// Base point
ed25519.BASE_POINT // new ed25519.Point(x, y) where
// x = 15112221349535400772501151409588531511454012693041857206046113283949847762202n;
// y = 46316835694926478169428394003475163141307993866256225615783033603165251855960n;
// Precomputation helper
utils.precompute(W, point);
There are additional ristretto255
helpers in ristretto255.js
file.
Speed
Measured with 2.9Ghz Coffee Lake.
- precomputation: first
getPrivateKey()
orutils.precompute()
: 480ms getPrivateKey()
: 30mssign()
: 86msgetPrivateKey()
withutils.precompute(8)
: 15ms
Security
Noble is production-ready & secure. Our goal is to have it audited by a good security expert.
We're using built-in JS BigInt
, which is "unsuitable for use in cryptography" as per official spec. This means that the lib is potentially vulnerable to timing attacks. 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 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 however 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 rootkits with every
npm install
. Our goal is to minimize this attack vector.
Contributing
- Clone the repository.
npm install
to install build dependencies like TypeScriptnpm run compile
to compile TypeScript codenpm run test
to run jest ontest/index.ts
License
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.