Coder Social home page Coder Social logo

paulmillr / noble-ed25519 Goto Github PK

View Code? Open in Web Editor NEW
390.0 8.0 49.0 1.62 MB

Fastest 4KB JS implementation of ed25519 signatures

Home Page: https://paulmillr.com/noble

License: MIT License

JavaScript 72.00% TypeScript 28.00%
ed25519 curve25519 eddsa cryptography noble x25519 curve elliptic rfc8032 signature

noble-ed25519's Introduction

noble-ed25519

Fastest 4KB JS implementation of ed25519 signatures.

  • ✍️ EDDSA signatures compliant with RFC8032, FIPS 186-5
  • 🪢 Consensus-friendly, compliant with ZIP215
  • 🔖 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

Use larger drop-in replacement 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. Online demo.

This library belongs to noble cryptography

noble-cryptography — high-security, easily auditable set of contained cryptographic libraries and tools.

Usage

npm install @noble/ed25519

We support all major platforms and runtimes. For node.js <= 18 and React Native, additional polyfills are needed: see below.

import * as ed from '@noble/ed25519';
// 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); // Sync methods below
  const signature = await ed.signAsync(message, privKey);
  const isValid = await ed.verifyAsync(signature, message, pubKey);
})();

Additional polyfills for some environments:

// 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));
// Sync methods can be used now:
// ed.getPublicKey(privKey); ed.sign(msg, privKey); ed.verify(signature, msg, pubKey);

// 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));

API

There are 3 main methods: getPublicKey(privateKey), sign(message, privateKey) and verify(signature, message, publicKey). We accept Hex type everywhere:

type Hex = Uint8Array | string

getPublicKey

function getPublicKey(privateKey: Hex): Uint8Array;
function getPublicKeyAsync(privateKey: Hex): Promise<Uint8Array>;

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

function sign(
  message: Hex, // message which would be signed
  privateKey: Hex // 32-byte private key
): Uint8Array;
function signAsync(message: Hex, privateKey: Hex): Promise<Uint8Array>;

Generates EdDSA signature. Always deterministic.

Assumes unhashed message: it would be hashed by ed25519 internally. For prehashed ed25519ph, switch to noble-curves.

verify

function verify(
  signature: Hex, // returned by the `sign` function
  message: Hex, // message that needs to be verified
  publicKey: Hex // public (not private) key,
  options = { zip215: true } // ZIP215 or RFC8032 verification type
): boolean;
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.

utils

A bunch of useful utilities are also exposed:

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 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
};

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);
  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;
  toAffine(): Point;
  toRawBytes(): Uint8Array;
  toHex(): string; // Compact representation of a Point
}
// 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 library has not been independently audited as of v2, which is a rewrite of v1. v1 has been audited by Cure53 in Feb 2022.

The code is identical to noble-curves, which has been audited.

It is tested against property-based, cross-library and Wycheproof vectors, and has fuzzing by Guido Vranken's cryptofuzz.

Constant-timeness

JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve 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 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 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 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 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 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 20.

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

Compare to alternative implementations:

[email protected] getPublicKey x 1,808 ops/sec @ 552μs/op ± 1.64%
[email protected] sign x 651 ops/sec @ 1ms/op
[email protected] 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 tests

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, 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

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), see LICENSE file.

noble-ed25519's People

Contributors

dsernst avatar gozala avatar jhonnyjason avatar jsmonk avatar lejamon avatar lilnasy avatar mahnunchik avatar matheus23 avatar mrtenz avatar paulmillr avatar pgg avatar quentinadam avatar sangaman avatar soatok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

noble-ed25519's Issues

what is the advantage of distinguishing crypto.web and crypto.node in utils.sha512

sha512: async (message: Uint8Array): Promise<Uint8Array> => {
  if (crypto.web) {
    const buffer = await crypto.web.subtle.digest('SHA-512', message.buffer);
    return new Uint8Array(buffer);
  } else if (crypto.node) {
    return Uint8Array.from(crypto.node.createHash('sha512').update(message).digest());
  } else {
    throw new Error("The environment doesn't have sha512 function");
  }
},

because

  • both of them(crypto.web.subtile.digest and crypto.node.createHash) will generate the same result,
  • and crypto.node.createHash can be used in browser env,
  • and the build out js file size are almost the same

why not delete the crypto.web one, and make it a sync function?

sha512: (message: Uint8Array): Uint8Array => {
  return Uint8Array.from(crypto.node.createHash('sha512').update(message).digest());
},

Can someone help me with the getSharedSecret function?

Can someone tell me what i am doing wrong here in nodejs?

const ed25519 = require('@noble/ed25519');
const prvKey = Uint8Array.from(Buffer.from('e8ddb1cfc09e163915e6c28fcb5fbb563bfef57201857e15288b67abbd91e444','hex'))
console.log(prvKey)

const pubKey = Uint8Array.from(Buffer.from('57758911253f6b31df2a87c10eb08a2c9b8450768cb8dd0d378d93f7c2e220f0','hex'))
console.log(pubKey)

function sharedSecret(prvKey,pubKey) {
 return ed25519.getSharedSecret(prvKey,pubKey)
     .then(result => {
             console.log(result); // Logs the result
             return response;
             }
           );
}
console.log(sharedSecret(prvKey,pubKey)); 

I always get Promise { <pending> } as the output.

Best regards, Martin

Uncaught TypeError: Cannot convert a BigInt value to a number

Using this with typescript / react-scripts, I am getting an error in the production / transpiled code (even though it works in my dev version).

"use strict";
/*! noble-ed25519 - MIT License (c) Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.utils = exports.verify = exports.sign = exports.getPublicKey = exports.SignResult = exports.Signature = exports.Point = exports.ExtendedPoint = exports.CURVE = void 0;
const CURVE = {
    a: -1n,
    d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n,
    P: 2n ** 255n - 19n,
    n: 2n ** 252n + 27742317777372353535851937790883648493n,
    h: 8n,
    Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
    Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
};
index.js:8 Uncaught TypeError: Cannot convert a BigInt value to a number
    at Math.pow (<anonymous>)
    at Object.<anonymous> (index.js:8)
    at Object.<anonymous> (6.c1d27334.chunk.js:2)
    at u ((index):1)
    at Object.<anonymous> (main.75bcf584.chunk.js:1)
    at Object.49 (main.75bcf584.chunk.js:1)
    at u ((index):1)
    at Object.134 (main.75bcf584.chunk.js:1)
    at u ((index):1)
    at Module.781 (main.75bcf584.chunk.js:1)

Same error on chrome and firefox.

I can provide more detail if needed.

v1.2.6: Deno throws because of Node related code

Last working version: 1.0.1

Deno 1.14.2
exit using ctrl+d or close()
> import * as ed from 'https://deno.land/x/ed25519/mod.ts';
Check https://deno.land/x/ed25519/mod.ts
Uncaught TypeError: TS2580 [ERROR]: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
    typeof module !== 'undefined' &&
           ~~~~~~
    at https://deno.land/x/[email protected]/index.ts:762:12

TS2580 [ERROR]: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
    typeof module.require === 'function' &&
           ~~~~~~
    at https://deno.land/x/[email protected]/index.ts:763:12

TS2580 [ERROR]: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
    module.require.bind(module);
    ~~~~~~
    at https://deno.land/x/[email protected]/index.ts:764:5

TS2580 [ERROR]: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
    module.require.bind(module);
                        ~~~~~~
    at https://deno.land/x/[email protected]/index.ts:764:25

Found 4 errors.
    at async <anonymous>:2:12

Case ArrayBuffer as byte argument

function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {

I sometimes treat plain Buffers and ArrayBuffers as Uint8Arrays and pass them directly to functions.

The ensureBytes function helps a lot here and in Nodejs it works for all cases because Buffer is an instance of Uint8Array.
ArrayBuffer in the browser is not - so I suggest to add another line:

-const bytes =  hex instanceof Uint8Array? Uint8Array.from(hex) : hexToBytes(hex);
+const isBytes = hex instanceof Uint8Array || hex isinstanceof ArrayBuffer
+const bytes =  isBytes? Uint8Array.from(hex) : hexToBytes(hex)

If it makes sense I could creae a pull request for it.

Fast fix is to take care to only pass Uint8Arrays to the functions when using the browser.

Cheers for now!

Deno: better solution instead of import map

Currently using this module with Deno requires you to use an import map which aliases crypto to https://deno.land/[email protected]/node/crypto.ts.

This causes two issues:

  • This pulls in a massive amount of code for Deno to recreate Node's crypto, which is then not even used. This is especially relevant when using a Deno codebase with noble-ed25519 to create a bundle intended for the web.
  • It requires all downstream dependents of ed25519 to require an import map. I'm contributing to a project which is a dependency itself, and would require all my users to use noble-ed25519's import map to make it work.

At first glance I'm not entirely sure how to work around this without having a web specific variant of noble-ed25519 that doesn't check for the presence of node's crypto module.

.equals not recognizing equal points

> curve = require("noble-ed25519");
> q = curve.ExtendedPoint.fromRistrettoBytes(new Uint8Array([78,7,201,185,190,254,158,249,198,210,211,189,204,72,156,195,218,195,144,152,106,35,46,206,165,35,154,201,238,165,26,10]))
> s = 2247802787217874114338511867005927249421637399982938640834856366249165554779n
> t = 6941189895179004434540483659899441803715011564437546499587142614761695797379n

> q.multiply(s * t).equals(q.multiply(s).multiply(t))
false
> var equal = require('deep-equal');
> equal(q.multiply(s * t).toRistrettoBytes(), q.multiply(s).multiply(t).toRistrettoBytes())
true

Jest gives an error: "SyntaxError: Unexpected token export"

I'm am using @noble/ed25519 in my NestJS application, and using Jest for unit test coverage.

When running my test, I get the following error:

 FAIL  src/<**********>.spec.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
...

    Details:

    /Users/<*******************************>/node_modules/@noble/ed25519/index.js:373
    export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL
    ^^^^^^

    SyntaxError: Unexpected token 'export'
  • Node version: v20
  • NPM version: 8.16.0

Package.json

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/axios": "^2.0.0",
    "@nestjs/common": "^9.3.12",
    "@nestjs/config": "^2.3.1",
    "@nestjs/core": "^9.3.12",
    "@nestjs/mongoose": "^9.2.2",
    "@nestjs/platform-express": "^9.3.12",
    "@noble/ed25519": "^2.0.0",
    "axios": "^1.3.4",
    "class-validator": "^0.14.0",
    "reflect-metadata": "^0.1.13",
    "regtest-client": "^0.2.0",
    "rxjs": "^7.8.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.3.0",
    "@nestjs/schematics": "^9.1.0",
    "@nestjs/testing": "^9.3.12",
    "@types/express": "^4.17.17",
    "@types/jest": "29.5.0",
    "@types/node": "18.15.11",
    "@types/supertest": "^2.0.12",
    "@typescript-eslint/eslint-plugin": "^5.57.0",
    "@typescript-eslint/parser": "^5.57.0",
    "eslint": "^8.37.0",
    "eslint-config-prettier": "^8.8.0",
    "eslint-plugin-prettier": "^4.2.1",
    "jest": "29.5.0",
    "prettier": "^2.8.7",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "29.0.5",
    "ts-loader": "^9.4.2",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "4.2.0",
    "typescript": "^5.0.3"
  }
}

deno support

is this suppose to work with deno? https://deno.land/x/ed25519/ advertises so, but when I run it i get:


► https://raw.githubusercontent.com/paulmillr/noble-ed25519/0.7.0/index.ts:513:26

513     const buffer = await window.crypto.subtle.digest('SHA-512', message.buffer);
                             ~~~~~~~~~~~~~~~~~~~~

error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i @types/node`.

► https://raw.githubusercontent.com/paulmillr/noble-ed25519/0.7.0/index.ts:519:19

519 } else if (typeof process === 'object' && 'node' in process.versions) {
                      ~~~~~~~

error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i @types/node`.

► https://raw.githubusercontent.com/paulmillr/noble-ed25519/0.7.0/index.ts:519:53

519 } else if (typeof process === 'object' && 'node' in process.versions) {
                                                        ~~~~~~~

error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node`.

► https://raw.githubusercontent.com/paulmillr/noble-ed25519/0.7.0/index.ts:520:15

520   const req = require;```

making API consistent

While all the APIs in the new version are divided into operation and operationAsync the sha512 has the following APIs:
etc.sha512Sync
etc.sha512Async

`noble-ed25519` and `tweetnacl` produce different results

import * as ed from "noble-ed25519";
import tweetnacl from "tweetnacl";

const hashToSign = <Buffer 0f dc d2 0c 4b c0 7a cb 6a 4b 21 7f d1 cd 47 ec 9f b7 f2 ba 4d 77 97 24 82 57 64 40 c3 46 c3 79>;
const privateKey = "d59f31af0aa05e78578fbd9e8a709f3d8fee02e0f27aaebe60b6747d2069b0e4";

const signature = Buffer.from(await ed.sign(hashToSign, privateKey)).toString("hex");
console.log(signature);  // d5fd7e7d55f3b41b5b2a7bc96bcf441a64e74fca6c044b6c5e3ee9206359352d83eef32b73c96e3f123e49c77a7d8d77c83fa975166d35caf904e38b9a76ad08

const signature1 = Buffer.from(await tweetnacl.sign.detached(hashToSign, Buffer.from(privateKey))).toString("hex");
console.log(signature1)  // 742624660870b0d97ffef0d265917d027e1dd81a2d1e22ebd9e1e65c31a7600e744342d194c9096f8f19988828b0d9c22a97a0246485a13fc48d8927a2158201

Why these two libraries produce different results?

The package ( @noble/ed25519 ) is throwing some math errors in production works fine in local

Usage of the module

import * as ed from '@noble/ed25519';

const someFunction = async() => {
    const privateKeyRaw = ed.utils.randomPrivateKey(); // 32-byte Uint8Array or string.
    const publicKeyRaw = await ed.getPublicKey(privateKeyRaw);
}

I'm using this in a react app onClick the function triggers but on production even before the button is pressed I'm getting this error

TypeError: Cannot convert a BigInt value to a number
    at Math.pow (<anonymous>)
    at 88238 (index.js:7:21)
    at __webpack_require__ (create fake namespace object:19:32)
    at 26554 (392.8554e2b8.chunk.js:1:221)
    at Function.__webpack_require__ (create fake namespace object:19:32)

image

I believe the error is arising from the import statement.

Clarify docs about msg hashes

https://datatracker.ietf.org/doc/html/rfc8032#section-7.1

-----TEST 2

ALGORITHM:
Ed25519

SECRET KEY:
4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb

PUBLIC KEY:
3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c

MESSAGE (length 1 byte):
72

SIGNATURE:
92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00

https://paulmillr.com/ecc/

Private key in hex (pasted): 4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb
Public keys (computed OK): 3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
Message to sign: r
Signature ed: ea22ec953464e2c2b33730b2330fba98bdd675f9f2127d40e4cf53ca1792c3f563fabce1b76bbcba83cdb687e433c9737c5417d3f629ebc811c92119eae6960e

Invalid signature!

replacement for getSharedSecret

I have been using the function getSharedSecret from @noble/ed25519 which now seems to be deprecated.

Looking for a replacement in @noble/curves/ed25519 I found it is only supported in the x25519 curve.

This library used to convert the ed25519 curve internally to an x25519 curve, which I cannot reproduce using @noble/curves.

Maybe you can add an example in the Upgrading section?

Suggest clarifying comments and argument names

Your library is most interesting and I like the clear bignum implementation. However, it could be good to more clearly mark which functions work in Ed25519, which do in Curve25519, which parameters and points and which scalars, or whether they are in bignum or buffer format. There are a very few comments and sometimes the arguments are named in a way where this is not at all apparent, at least unless one is already familiar with this field.

Compatibility with libsodium generated keys

I am trying to integrate with the service and replace it. Service was developed in PHP and is using libsodium ed25519 implementation. It has a 64-bit private key, so I am struggling to understand if this library can be used to sign messages and make it compatible with messages signed by libsodium.

Sync version of sign?

Thanks for creating this simple, compact, easy-to-use library. Any chance you could make a Sync version of sign to avoid promises?

Does sign() take a message or its hash?

The README suggests the first parameter to sign() is the message hash:

const msgHash = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
...
  const signature = await ed.sign(msgHash, privateKey);

However the implementation suggests the parameter is the message itself, and not necessarily a hash:

noble-ed25519/index.ts

Lines 818 to 820 in f2881cb

const r = await sha512ModnLE(prefix, msg); // r = hash(prefix + msg)
const R = Point.BASE.multiply(r); // R = rG
const k = await sha512ModnLE(R.toRawBytes(), pubBytes, msg); // k = hash(R + P + msg)

The referenced RFC also seems to mention nothing about hashing for the function input:

The inputs to the signing procedure is the private key, a 32-octet string, and a message M of arbitrary size.

Timing leak in powMod

From the blog post, you correctly noted that "[t]he best we can do is an algorithmic constant-time".

However, the powMod() implementation isn't algorithmic constant-time:

noble-ed25519/index.js

Lines 438 to 440 in c7a621d

if (power & 1n) {
res = mod(res * a, m);
}

The lowest bit of power determines whether a branch gets executed, and the branch where it does contains an extra function call.

The simplest workaround is to use a constant-time conditional select.

For example (not tested):

function powMod(a, power, m = CURVE.P) {
    let res = 1n;
    while (power > 0n) {
        res = conditionalSelect(power & 1n, mod(res * a, m), res);
        power >>= 1n;
        a = mod(a * a, m);
    }
    return res;
}
function conditionalSelect(swap, left, right) {
    mask = -(swap & 1n);
    return right ^ ((left ^ right) & mask);
}

This sort of design pattern only works with functions, not macros (e.g. C compiler directives).


Wanted to clarify: This isn't just leaking the magnitude of power, but the actual bit pattern.

Support key generation from seed as described in RFC 8032 section 5.1.5

Looking through the project, I did not see key generation from seed as describe in RFC 8032 section 5.1.5.

I would suggest ignoring the RFC's term "private key" and follow Go's term "seed". It makes the functions more readable while differentiating private key and seed.

Go's NewKeyFromSeed:
https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/ed25519/ed25519.go;l=116

And Go's SetBytesWithClamping
https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/internal/edwards25519/scalar.go;l=138;drc=7846e25418a087ca15122b88fc179405e26bf768;bpv=1;bpt=1

I would also point out Filippo's comment:

[I]t is lost to history why RFC 8032 adopted the irrelevant RFC 7748 clamping, but it is now required for compatibility.

Edit: The JavaScript TweetNaCl also is doing this like the Go implementation except they've dubbed it "secretKey" whereas the Go library has dubbed it "private key" (with the public 32 byte component appended). The first 32 bytes of secretKey is the secret component.
https://github.com/dchest/tweetnacl-js#naclsignkeypairfromsecretkeysecretkey

ts to mjs/esm export

Hi, i would like to use noble-ed25519 in es6 with import, and it work when i run it in node but it fail when i try to build a static package.

could you add in npm package a built mjs file with export & export default in it ?

Support for service workers

Currently the library does not fully work in service workers, since it relies on window.crypto (which is unavailable in service workers). Instead, self.crypto can be used.

Deno import error

As described here https://deno.land/x/[email protected]

i just do a import * as ed from 'https://deno.land/x/ed25519/mod.ts'; resulting in this:

error: Relative import path "crypto" not prefixed with / or ./ or ../
    at https://deno.land/x/[email protected]/index.ts:11:29

Looks like you try to import npm packages in deno but in index.ts you seem to know that deno has built in webcrypto support since you are doing crypto.subtle.digestSync('SHA-512'... Is this a bug?

EDIT: Also this: import { crypto } from 'https://deno.land/[email protected]/crypto/mod.ts'; is unnecessary, crypto.subtle works in deno out of the box, no import needed. But maybe thats a recent development.

Note: alternative Point#multiply implementation

  1. 200ms precompute, 66ms Point#multiply (below)
  2. 500ms precompute, 30ms Point#multiply (in the repo)
multiply(n: bigint) {
  const zero = new Point(0n, 1n);
  const doubles = this.precomputeDoubles();
  return bitset(n).map((isSet, bit) => isSet ? doubles[bit] : zero).reduce((a, b) => a.add(b));
}

private precomputeDoubles(): Point[] {
  let points = new Array(256);
  if (this.x === BASE_POINT.x && this.y === BASE_POINT.y) {
    if (BASE_POINT_DOUBLES) return BASE_POINT_DOUBLES;
    points = BASE_POINT_DOUBLES = new Array(256);
  }
  for (let bit = 0, point: Point = this; bit < 256; bit++, point = point.add(point)) {
    points[bit] = point;
  }
  return points;
}

Differing outputs between languages

Hey,

I'm signing an identical message with the same key between this package and Rust's ed25519-dalek. The first 32ish bytes of both signatures are identical but the last bytes are not the same. Do you know why this could be?

Regression in 1.5.0

If you sign a piece of data with @noble/ed25519 and make a copy of the signature using TypedArray.set, verification sometimes fails.

Repro:

const ed = require('@noble/ed25519')
const crypto = require('crypto')

async function main () {
  const privateKey = ed.utils.randomPrivateKey()
  const publicKey = await ed.getPublicKey(privateKey)

  while (true) {
    const payload = crypto.randomBytes(100)
    const signature = await ed.sign(payload, privateKey)

    if (!(await ed.verify(signature, payload, publicKey))) {
      throw new Error('Signature verification failed')
    }

    const signatureCopy = Buffer.alloc(signature.byteLength)
    signatureCopy.set(signature, 0) // <-- breaks

    if (!(await ed.verify(signatureCopy, payload, publicKey))) {
      throw new Error('Copied signature verification failed')
    }

    console.info('all ok')
  }
}

main().catch(err => {
  console.error(err)
  process.exit(1)
})

The above code with 1.4.0:

$ node index.js
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
all ok
...forever (probably)

With 1.5.0:

$ node index.js
all ok
all ok  <-- sometimes once or twice, sometimes never
Error: Copied signature verification failed
    at main (/Users/alex/test/ed/index.js:26:13)

Env:

$ node --version
v16.13.0

Elligator support

https://loup-vaillant.fr/articles/implementing-elligator

It's hard to tell if it's supported for ed25519, or only for curve25519.

Here's the implementation:

  static fromRandomHex(hex: Hex) {
    const {a} = CURVE;
    hex = ensureBytes(hex);
    const r = bytesToNumberLE(hex);
    const ufactor = mod(-ELLIGATOR_NON_SQUARE * SQRT_M1);
    const vfactor = sqrtMod(ufactor);

    let t1 = mod(r**2n * ELLIGATOR_NON_SQUARE)    // r1
    let u  = t1 + 1n           // r2
    let t2 = mod(u**2n)
    let t3 = mod((a**2n * t1 - t2) * a) // numerator
    t1 = mod(t2 * u)               // denominator
    const inv = invertSqrt(t3 * t1);
    const is_square = inv.isValid;
    t1 = inv.value;
    u  = mod(r**2n * ufactor)
    let v  = mod(r * vfactor)
    if (is_square) u = 1n;
    if (is_square) v = 1n;
    v  = mod(v * t3 * t1)
    t1 = mod(t1**2n)
    u  = mod(u * -a * t3 * t2 * t1)
    if (is_square != (v > (CURVE.P - 1n) / 2n)) // XOR
        v = mod(-v)
    return new Point(mod(u), mod(v))
  }

  toRandomHex() {
    let u = this.x;
    let v_is_negative = this.y > (CURVE.P - 1n) / 2n
    let t = u + CURVE.a;
    let r = -ELLIGATOR_NON_SQUARE * u * t
    let inv = invertSqrt(r);
    let is_square = inv.isValid;
    let isr = inv.value;
    if (!is_square) return;
    if (v_is_negative) u = t
    r = u * isr
    if (r > (CURVE.P - 1n) / 2n) r = mod(-r);
    return numberToHex(r);
  }

window.crypto is not available over LAN HTTP

Hi there! Thanks for creating this :)

After updating from noble-ed25519 1.0.4 to @noble/ed25519 1.33, I got this error in an http context: Na.web.subtle is undefined. I'm not entirely sure whether this error would have appeared before.

It seems to appear only if I'm running my server on a local network system using HTTP, but not on localhost or in HTTPS in production.

The code seems to emerges from here.

After some googling I learned that the crypto module is not available in non-localhost HTTP environments.

So what does this mean for me as a user of this dependency? I feel like, in my usecase, it should be able to run in HTTP contexts, so I'd like some fallback. Should this library provide a fallback? Should I as a user of this library provide a fallback, or show some custom error? Would love to hear your thoughts on this.

Thanks!

Passing Uint8Array to `sign` leads to `TypeError: hexToBytes: expected string, got object.` when polyfilling `TextEncoder`

First off: thanks so much for this library!

I'm trying to run some tests in a Jest setup (which uses JSDom), and I get this error when calling sign:

TypeError: hexToBytes: expected string, got object.

I'm sure I'm passing a Uint8Array, and the types say it's allowed.

This is my code:

import { sign, getPublicKey, utils } from '@noble/ed25519';

export const signToBase64 = async (
  message: string,
  privateKeyBase64: string,
): Promise<string> => {
  const privateKeyArrayBuffer = decodeB64(privateKeyBase64);
  const privateKeyBytes: Uint8Array = new Uint8Array(privateKeyArrayBuffer);
  // Polyfill for node
  if (typeof TextEncoder === 'undefined') {
    global.TextEncoder = require('util').TextEncoder;
  }
  const utf8Encode = new TextEncoder();
  const messageBytes: Uint8Array = utf8Encode.encode(message);
  console.log(typeof messageBytes, messageBytes.constructor.name);
  console.log(typeof privateKeyBytes, privateKeyBytes.constructor.name);
  const signatureHex = await sign(messageBytes, privateKeyBytes);
  const signatureBase64 = encodeB64(signatureHex);
  return signatureBase64;
};

I think this might be where things go wrong.

There are no issues when running the code in my browser. Maybe hex instanceof Uint8Array is false, because the Uint8Array is different from the one I'm using when polyfilled? If that's the case, it might make sense to do hex.constructor.name == 'Uint8Array' instead.

Why does it produce 128 bytes signature?

I'm running this script to sign a message:

ed = require('@noble/ed25519');

const privateKey = ed.utils.randomPrivateKey(); // 32-byte Uint8Array or string.
const msgHash = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
(async () => {
    const signature = await ed.sign(msgHash, privateKey);
    console.log(Buffer.byteLength(signature, 'utf8') + ' bytes');
})();

// 128 bytes

In ed25519 original documentation (https://ed25519.cr.yp.to) says:
Small signatures. Signatures fit into 64 bytes.

Subtract not working as expected?

Hello,
First of all thank you for this piece of Code!
Much more like it when something exactly does that one thing most possible right ;-)

So to the Issue - either I face some conceptual problem in my mind or there appears to be a problem with subtraction.

securityprimitives.experiment = ->

    G = noble.Point.BASE

    log "- - - "

    G2 = G.multiply(2)
    log "G2: " + G2.toHex()
    G2minusG = G2.subtract(G)
    log "G2minusG: " + G2minusG.toHex()
    log "G: " + G.toHex()
    log "G equals G2minusG: " + G.equals(G2minusG)
    
    log "- - - "

    Z = noble.Point.ZERO
    G = noble.Point.BASE
    ZandG = Z.add(G)
    log "ZandG: " + ZandG.toHex()
    ZandGminusG = ZandG.subtract(G)
    log "ZandGminusG: " + ZandGminusG.toHex()
    log "Z: " + Z.toHex()
    log "Z equals ZandGminusG: " + Z.equals(ZandGminusG)

    return
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: - - -
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: G2: c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: G2minusG: 9599999999999999999999999999999999999999999999999999999999999999
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: G: 5866666666666666666666666666666666666666666666666666666666666666
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: G equals G2minusG: false
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: - - -
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: ZandG: 5866666666666666666666666666666666666666666666666666666666666666
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: ZandGminusG: ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: Z: 0100000000000000000000000000000000000000000000000000000000000000
Aug 13 12:52:59 nova node[238609]: [securityprimitives]: Z equals ZandGminusG: false

Why is that?

How to create hybrid NPM package for Node.js and bundlers?

I've seen the hint to shim for Node.js <= v18 to augment the global object with webcrypto features, and it works fine when executing in a Node environment. However, when my package that is importing @noble/ed25519 is bundled, the bundler stumbles over the node module import.

A workaround is a conditional async import. That makes all operation asynchronous and the bundlers are outputting warnings and polyfill suggestions.

What is your recommendation for creating a hybrid package with ed25519 that works in Node.js as well as with a bundler?

Wrong signature with version > 1.60

Hi there! Thanks for building / maintaining this.

So today I noticed that my newly published library created wrong signatures. After trying some things, the problem was fixed by locking my noble-ed25519 version to v1.6.0. I'm not entirely sure what that means, but all I know is that bumping this version suddenly leads to getting wrong signatures.

Here's the codesandbox where things went wrong.

Edit: But I haven't been able to reproduce the issue in a different codesandbox using only noble-ed25519. Doesn't look like it's this repos fault, then! So maybe it was a bitflip during compiling the JS files? Divine intervention? Maybe I'll never know.

The handling of `S` during verification is badly broken

Per the current standards, the valid range for S in a signature is 0 <= S < L.

RFC 8032 (5.1.7):

   1.  To verify a signature on a message M using public key A, with F
       being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or
       Ed25519ph is being used, C being the context, first split the
       signature into two 32-octet halves.  Decode the first half as a
       point R, and the second half as an integer S, in the range
       0 <= s < L.  Decode the public key A as point A'.  If any of the
       decodings fail (including S being out of range), the signature is
       invalid.

FIPS 186-5 (Draft) 7.7:

1. Decode the first half of the signature as a point R and the second
   half of the signature as an integer s. Verify that the integer s is in
   the range of 0 ≤ s < n. Decode the public key Q into a point Q’. If
   any of the decodings fail, output “reject”.

This implementation throws Point#multiply: invalid scalar, expected positive integer when S = 0, and accepts signatures with S >= L. The latter is particularly concerning because under the RFC 8032 (and or FIPS 186-5) definition of Ed25519, signatures are not supposed to be malleable.

ps: Is this implementation supposed to be using the cofactored verification equation? A quick glance at the code made me suspect "no", but test vectors from speccheck suggest "yes, but it's incorrect".

pps: It's fine to lift code and comments from curve25519-dalek, but it should probably be credited and follow their license (which isn't MIT).

v2 throws error in FF when bundled with parcel

Error: etc.sha512Sync not set
    err index.js:11
    sha512s index.js:234
    getExtendedPublicKey index.js:248
    getPublicKey index.js:250
    generateKeyPair App.js:8
    App App.js:13
    Preact 5
    ["8lqZg"]< index.js:5
    newRequire index.959aab6e.js:71
    <anonymous> index.959aab6e.js:122
    <anonymous> index.959aab6e.js:145
index.js:11:32

I started using this lib in this boilerplate: https://github.com/SafdarJamal/preact-boilerplate
and when I fired up my FF, I got the above error message which then GPT4 suggested me to substitute ed.etc.sha512Sync with a function from crypto-browserify, lol
However, I then downgraded to v1.7.3 and now it works.

Uncaught TypeError: Cannot convert a BigInt value to a number

"use strict";
/*! noble-ed25519 - MIT License (c) Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.utils = exports.verify = exports.sign = exports.getPublicKey = exports.SignResult = exports.Signature = exports.Point = exports.ExtendedPoint = exports.CURVE = void 0;
const CURVE = {
    a: -1n,
    d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n,
    P: 2n ** 255n - 19n,
    n: 2n ** 252n + 27742317777372353535851937790883648493n,
    h: 8n,
    Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
    Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
};

When useded Browser, the browser will throw an error, 'cannot convert a BitInt value to a number'.

How to solve this? Thanks!

Derive key for ECDH

Hi!

We need to implement ECDH and for that we need the key derivation algorithms. Would you find it difficult to implement?

Thank you

ESM support breaks importing the default export (as it doesn't exist any more)

The new ESM support requires use of named exports. This is better, but anyone importing the old CJS version as a default export was broken by the update.

E.g. worked with 1.3.0, is broken with 1.3.1:

import ed from '@noble/ed25519'

with:

import ed from '@noble/ed25519';
       ^^
SyntaxError: The requested module '@noble/ed25519' does not provide an export named 'default'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.