Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d7756dceb | ||
|
|
b716b4603f | ||
|
|
d7a139822d | ||
|
|
fb6c379a26 | ||
|
|
eeac255c88 | ||
|
|
925fc3f810 | ||
|
|
eb8e7ec964 | ||
|
|
e7ac5e85d3 | ||
|
|
d285fcce06 | ||
|
|
ef667bb404 | ||
|
|
62749382e7 | ||
|
|
f90e871725 | ||
|
|
f049398718 | ||
|
|
ca99179bd8 | ||
|
|
1545230ee5 | ||
|
|
2ce3b825f8 | ||
|
|
8315fe3580 | ||
|
|
9b7889e16f | ||
|
|
e8b9509c16 | ||
|
|
d92c9d14ad | ||
|
|
05794c0283 | ||
|
|
ca5583f713 | ||
|
|
8c48abe16a | ||
|
|
08bb00cc8f | ||
|
|
1ef16033fe | ||
|
|
113b6d7c00 | ||
|
|
5c3dc0be50 | ||
|
|
e7d01f4038 | ||
|
|
9a39625eda | ||
|
|
af8462b09e | ||
|
|
bfd9ae040d | ||
|
|
2bd437df4e | ||
|
|
b0af0a8977 | ||
|
|
aee10c8141 | ||
|
|
ff92bafb6f | ||
|
|
54679ff788 | ||
|
|
ee4571c7a1 | ||
|
|
fe7afdd392 | ||
|
|
dba2f0e732 | ||
|
|
52c5df0264 | ||
|
|
ebea4a4bcd | ||
|
|
33a53006f7 | ||
|
|
549e286ef0 | ||
|
|
3f0c0b59f1 | ||
|
|
62205347e1 | ||
|
|
476e75104f | ||
|
|
413725cfb3 | ||
|
|
cf17f7fe01 | ||
|
|
49fb90ae9a | ||
|
|
309d29a084 | ||
|
|
d3aa051770 | ||
|
|
5609ec7644 | ||
|
|
af8c1eebee | ||
|
|
08ea57ce5c | ||
|
|
ee3d3815b4 | ||
|
|
f471405798 | ||
|
|
e3a4bbffe9 | ||
|
|
c2edc97868 | ||
|
|
bf70ba9776 | ||
|
|
c71920722c |
435
README.md
435
README.md
@@ -9,26 +9,23 @@ Audited & minimal JS implementation of elliptic curve cryptography.
|
|||||||
- ➰ Short Weierstrass, Edwards, Montgomery curves
|
- ➰ Short Weierstrass, Edwards, Montgomery curves
|
||||||
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
- ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
||||||
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
|
- 🔖 SUF-CMA and SBS (non-repudiation) for ed25519, ed448 and others
|
||||||
- #️⃣ Hash-to-curve
|
- #️⃣ hash-to-curve for encoding or hashing an arbitrary string to an elliptic curve point
|
||||||
for encoding or hashing an arbitrary string to an elliptic curve point
|
|
||||||
- 🧜♂️ Poseidon ZK-friendly hash
|
- 🧜♂️ Poseidon ZK-friendly hash
|
||||||
|
|
||||||
Check out [Upgrading](#upgrading) if you've previously used single-feature noble
|
|
||||||
packages. See [Resources](#resources) for articles and real-world software that uses curves.
|
|
||||||
|
|
||||||
### This library belongs to _noble_ crypto
|
### This library belongs to _noble_ crypto
|
||||||
|
|
||||||
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
||||||
|
|
||||||
- No dependencies, protection against supply chain attacks
|
- No dependencies, protection against supply chain attacks
|
||||||
- Auditable TypeScript / JS code
|
- Auditable TypeScript / JS code
|
||||||
- Supported in all major browsers and stable node.js versions
|
- Supported on all major platforms
|
||||||
- All releases are signed with PGP keys
|
- Releases are signed with PGP keys and built transparently with NPM provenance
|
||||||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
||||||
[curves](https://github.com/paulmillr/noble-curves)
|
[ciphers](https://github.com/paulmillr/noble-ciphers),
|
||||||
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
[curves](https://github.com/paulmillr/noble-curves),
|
||||||
[ed25519](https://github.com/paulmillr/noble-ed25519)),
|
[hashes](https://github.com/paulmillr/noble-hashes),
|
||||||
[hashes](https://github.com/paulmillr/noble-hashes)
|
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
|
||||||
|
[ed25519](https://github.com/paulmillr/noble-ed25519)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -39,33 +36,52 @@ For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/m
|
|||||||
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
||||||
If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
|
If you don't like NPM, a standalone [noble-curves.js](https://github.com/paulmillr/noble-curves/releases) is also available.
|
||||||
|
|
||||||
The library is tree-shaking-friendly and does not expose root entry point as
|
- [Implementations](#implementations)
|
||||||
`@noble/curves`. Instead, you need to import specific primitives.
|
- [ECDSA signature scheme](#ecdsa-signature-scheme)
|
||||||
This is done to ensure small size of your apps.
|
- [ECDSA public key recovery & extra entropy](#ecdsa-public-key-recovery--extra-entropy)
|
||||||
|
- [ECDH (Elliptic Curve Diffie-Hellman)](#ecdh-elliptic-curve-diffie-hellman)
|
||||||
The package consists of two parts:
|
- [Schnorr signatures over secp256k1, BIP340](#schnorr-signatures-over-secp256k1-bip340)
|
||||||
|
- [ed25519, X25519, ristretto255](#ed25519-x25519-ristretto255)
|
||||||
* [Implementations](#implementations), utilizing one dependency [noble-hashes](https://github.com/paulmillr/noble-hashes),
|
- [ed448, X448, decaf448](#ed448-x448-decaf448)
|
||||||
providing ready-to-use:
|
- [bls12-381](#bls12-381)
|
||||||
- NIST curves secp256r1 / p256, secp384r1 / p384, secp521r1 / p521
|
- [All available imports](#all-available-imports)
|
||||||
- SECG curve secp256k1
|
- [Accessing a curve's variables](#accessing-a-curves-variables)
|
||||||
- ed25519 / curve25519 / x25519 / ristretto255, edwards448 / curve448 / x448
|
- [Abstract API](#abstract-api)
|
||||||
- pairing-friendly curves bls12-381, bn254
|
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
||||||
- [pasta](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) curves
|
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
||||||
2. [Abstract](#abstract-api), zero-dependency elliptic curve algorithms
|
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
||||||
|
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
|
||||||
|
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
||||||
|
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
||||||
|
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
||||||
|
- [Creating private keys from hashes](#creating-private-keys-from-hashes)
|
||||||
|
- [abstract/utils: Useful utilities](#abstractutils-useful-utilities)
|
||||||
|
- [Security](#security)
|
||||||
|
- [Speed](#speed)
|
||||||
|
- [Contributing & testing](#contributing--testing)
|
||||||
|
- [Upgrading](#upgrading)
|
||||||
|
- [Resources](#resources)
|
||||||
|
- [Demos](#demos)
|
||||||
|
- [Projects using curves](#projects-using-curves)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
### Implementations
|
### Implementations
|
||||||
|
|
||||||
#### Generic example for all curves, secp256k1
|
Implementations are utilizing [noble-hashes](https://github.com/paulmillr/noble-hashes).
|
||||||
|
[Abstract API](#abstract-api) doesn't depend on them: you can use a different hashing library.
|
||||||
|
|
||||||
|
#### ECDSA signature scheme
|
||||||
|
|
||||||
|
Generic example that works for all curves, shown for secp256k1:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Each curve has similar methods
|
// import * from '@noble/curves'; // Error: use sub-imports, to ensure small app size
|
||||||
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
|
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
|
||||||
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
|
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
|
||||||
const priv = secp256k1.utils.randomPrivateKey();
|
const priv = secp256k1.utils.randomPrivateKey();
|
||||||
const pub = secp256k1.getPublicKey(priv);
|
const pub = secp256k1.getPublicKey(priv);
|
||||||
const msg = new Uint8Array(32).fill(1);
|
const msg = new Uint8Array(32).fill(1); // message hash (not message) in ecdsa
|
||||||
const sig = secp256k1.sign(msg, priv);
|
const sig = secp256k1.sign(msg, priv); // `{prehash: true}` option is available
|
||||||
const isValid = secp256k1.verify(sig, msg, pub) === true;
|
const isValid = secp256k1.verify(sig, msg, pub) === true;
|
||||||
|
|
||||||
// hex strings are also supported besides Uint8Arrays:
|
// hex strings are also supported besides Uint8Arrays:
|
||||||
@@ -73,29 +89,22 @@ const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c12623
|
|||||||
const pub2 = secp256k1.getPublicKey(privHex);
|
const pub2 = secp256k1.getPublicKey(privHex);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### All imports
|
#### ECDSA public key recovery & extra entropy
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
|
||||||
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
|
|
||||||
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
|
||||||
import { p256 } from '@noble/curves/p256';
|
|
||||||
import { p384 } from '@noble/curves/p384';
|
|
||||||
import { p521 } from '@noble/curves/p521';
|
|
||||||
import { pallas, vesta } from '@noble/curves/pasta';
|
|
||||||
import { bls12_381 } from '@noble/curves/bls12-381';
|
|
||||||
import { bn254 } from '@noble/curves/bn254';
|
|
||||||
import { jubjub } from '@noble/curves/jubjub';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ECDSA public key recovery & ECDH
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
sig.recoverPublicKey(msg).toRawBytes(); // === pub; // public key recovery
|
||||||
|
|
||||||
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
|
// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
|
||||||
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
|
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
|
||||||
sig.recoverPublicKey(msg) === pub; // public key recovery
|
```
|
||||||
|
|
||||||
|
#### ECDH (Elliptic Curve Diffie-Hellman)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 1. The output includes parity byte. Strip it using shared.slice(1)
|
||||||
|
// 2. The output is not hashed. More secure way is sha256(shared) or hkdf(shared)
|
||||||
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
||||||
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH
|
const shared = secp256k1.getSharedSecret(priv, someonesPub);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Schnorr signatures over secp256k1 (BIP340)
|
#### Schnorr signatures over secp256k1 (BIP340)
|
||||||
@@ -129,7 +138,6 @@ It has SUF-CMA (strong unforgeability under chosen message attacks).
|
|||||||
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
|
and additionally provides non-repudiation with SBS [(Strongly Binding Signatures)](https://eprint.iacr.org/2020/1244).
|
||||||
|
|
||||||
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
|
X25519 follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
|
||||||
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Variants from RFC8032: with context, prehashed
|
// Variants from RFC8032: with context, prehashed
|
||||||
@@ -147,17 +155,36 @@ x25519.getPublicKey(x25519.utils.randomPrivateKey());
|
|||||||
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
|
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
|
||||||
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
|
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
|
||||||
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
|
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());
|
||||||
|
```
|
||||||
|
|
||||||
|
ristretto255 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
||||||
|
|
||||||
|
```ts
|
||||||
// hash-to-curve, ristretto255
|
// hash-to-curve, ristretto255
|
||||||
import { hashToCurve, encodeToCurve, RistrettoPoint } from '@noble/curves/ed25519';
|
import { utf8ToBytes } from '@noble/hashes/utils';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import {
|
||||||
|
hashToCurve,
|
||||||
|
encodeToCurve,
|
||||||
|
RistrettoPoint,
|
||||||
|
hashToRistretto255,
|
||||||
|
} from '@noble/curves/ed25519';
|
||||||
|
|
||||||
|
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
|
||||||
|
hashToCurve(msg);
|
||||||
|
|
||||||
const rp = RistrettoPoint.fromHex(
|
const rp = RistrettoPoint.fromHex(
|
||||||
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
|
||||||
);
|
);
|
||||||
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee');
|
RistrettoPoint.BASE.multiply(2n).add(rp).subtract(RistrettoPoint.BASE).toRawBytes();
|
||||||
// also has add(), equals(), multiply(), toRawBytes() methods
|
RistrettoPoint.ZERO.equals(dp) === false;
|
||||||
|
// pre-hashed hash-to-curve
|
||||||
|
RistrettoPoint.hashToCurve(sha512(msg));
|
||||||
|
// full hash-to-curve including domain separation tag
|
||||||
|
hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ed448, X448
|
#### ed448, X448, decaf448
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { ed448 } from '@noble/curves/ed448';
|
import { ed448 } from '@noble/curves/ed448';
|
||||||
@@ -167,17 +194,65 @@ const msg = new TextEncoder().encode('whatsup');
|
|||||||
const sig = ed448.sign(msg, priv);
|
const sig = ed448.sign(msg, priv);
|
||||||
ed448.verify(sig, msg, pub);
|
ed448.verify(sig, msg, pub);
|
||||||
|
|
||||||
import { ed448ph, ed448ctx, x448, hashToCurve, encodeToCurve } from '@noble/curves/ed448';
|
// Variants from RFC8032: prehashed
|
||||||
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
|
import { ed448ph } from '@noble/curves/ed448';
|
||||||
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Same RFC7748 / RFC8032 are followed.
|
ECDH using Curve448 aka X448, follows [RFC7748](https://www.rfc-editor.org/rfc/rfc7748).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { x448 } from '@noble/curves/ed448';
|
||||||
|
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
|
||||||
|
x448.getPublicKey(priv) === x448.scalarMultBase(priv);
|
||||||
|
|
||||||
|
// ed448 => x448 conversion
|
||||||
|
import { edwardsToMontgomeryPub } from '@noble/curves/ed448';
|
||||||
|
edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));
|
||||||
|
```
|
||||||
|
|
||||||
|
decaf448 follows [irtf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { utf8ToBytes } from '@noble/hashes/utils';
|
||||||
|
import { shake256 } from '@noble/hashes/sha3';
|
||||||
|
import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';
|
||||||
|
|
||||||
|
const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
|
||||||
|
hashToCurve(msg);
|
||||||
|
|
||||||
|
const dp = DecafPoint.fromHex(
|
||||||
|
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75'
|
||||||
|
);
|
||||||
|
DecafPoint.BASE.multiply(2n).add(dp).subtract(DecafPoint.BASE).toRawBytes();
|
||||||
|
DecafPoint.ZERO.equals(dp) === false;
|
||||||
|
// pre-hashed hash-to-curve
|
||||||
|
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
|
||||||
|
// full hash-to-curve including domain separation tag
|
||||||
|
hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });
|
||||||
|
```
|
||||||
|
|
||||||
|
Same RFC7748 / RFC8032 / IRTF draft are followed.
|
||||||
|
|
||||||
#### bls12-381
|
#### bls12-381
|
||||||
|
|
||||||
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
|
See [abstract/bls](#abstractbls-barreto-lynn-scott-curves).
|
||||||
|
|
||||||
|
#### All available imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { secp256k1, schnorr } from '@noble/curves/secp256k1';
|
||||||
|
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
|
||||||
|
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
|
||||||
|
import { p256 } from '@noble/curves/p256';
|
||||||
|
import { p384 } from '@noble/curves/p384';
|
||||||
|
import { p521 } from '@noble/curves/p521';
|
||||||
|
import { pallas, vesta } from '@noble/curves/pasta';
|
||||||
|
import { bls12_381 } from '@noble/curves/bls12-381';
|
||||||
|
import { bn254 } from '@noble/curves/bn254'; // also known as alt_bn128
|
||||||
|
import { jubjub } from '@noble/curves/jubjub';
|
||||||
|
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
|
||||||
|
```
|
||||||
|
|
||||||
#### Accessing a curve's variables
|
#### Accessing a curve's variables
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -199,17 +274,6 @@ Precomputes are enabled for weierstrass and edwards BASE points of a curve. You
|
|||||||
could precompute any other point (e.g. for ECDH) using `utils.precompute()`
|
could precompute any other point (e.g. for ECDH) using `utils.precompute()`
|
||||||
method: check out examples.
|
method: check out examples.
|
||||||
|
|
||||||
There are following zero-dependency algorithms:
|
|
||||||
|
|
||||||
- [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
|
|
||||||
- [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
|
|
||||||
- [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
|
|
||||||
- [abstract/bls: Barreto-Lynn-Scott curves](#abstractbls-barreto-lynn-scott-curves)
|
|
||||||
- [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
|
||||||
- [abstract/poseidon: Poseidon hash](#abstractposeidon-poseidon-hash)
|
|
||||||
- [abstract/modular: Modular arithmetics utilities](#abstractmodular-modular-arithmetics-utilities)
|
|
||||||
- [abstract/utils: General utilities](#abstractutils-general-utilities)
|
|
||||||
|
|
||||||
### abstract/weierstrass: Short Weierstrass curve
|
### abstract/weierstrass: Short Weierstrass curve
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -233,7 +297,7 @@ const secq256k1 = weierstrass({
|
|||||||
randomBytes,
|
randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replace weierstrass with weierstrassPoints if you don't need ECDSA, hash, hmac, randomBytes
|
// Replace weierstrass() with weierstrassPoints() if you don't need ECDSA, hash, hmac, randomBytes
|
||||||
```
|
```
|
||||||
|
|
||||||
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
|
Short Weierstrass curve's formula is `y² = x³ + ax + b`. `weierstrass`
|
||||||
@@ -254,6 +318,11 @@ type CHash = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Message hash** is expected instead of message itself:
|
||||||
|
|
||||||
|
- `sign(msgHash, privKey)` is default behavior, assuming you pre-hash msg with sha2, or other hash
|
||||||
|
- `sign(msg, privKey, {prehash: true})` option can be used if you want to pass the message itself
|
||||||
|
|
||||||
**Weierstrass points:**
|
**Weierstrass points:**
|
||||||
|
|
||||||
1. Exported as `ProjectivePoint`
|
1. Exported as `ProjectivePoint`
|
||||||
@@ -349,6 +418,7 @@ More examples:
|
|||||||
const priv = secq256k1.utils.randomPrivateKey();
|
const priv = secq256k1.utils.randomPrivateKey();
|
||||||
secq256k1.getPublicKey(priv); // Convert private key to public.
|
secq256k1.getPublicKey(priv); // Convert private key to public.
|
||||||
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
|
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
|
||||||
|
const sig2 = secq256k1.sign(msg, priv, { prehash: true }); // hash(msg)
|
||||||
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
|
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.
|
||||||
|
|
||||||
const Point = secq256k1.ProjectivePoint;
|
const Point = secq256k1.ProjectivePoint;
|
||||||
@@ -496,6 +566,8 @@ use aggregated, batch-verifiable
|
|||||||
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
|
[threshold signatures](https://medium.com/snigirev.stepan/bls-signatures-better-than-schnorr-5a7fe30ea716),
|
||||||
using Boneh-Lynn-Shacham signature scheme.
|
using Boneh-Lynn-Shacham signature scheme.
|
||||||
|
|
||||||
|
The module doesn't expose `CURVE` property: use `G1.CURVE`, `G2.CURVE` instead.
|
||||||
|
|
||||||
Main methods and properties are:
|
Main methods and properties are:
|
||||||
|
|
||||||
- `getPublicKey(privateKey)`
|
- `getPublicKey(privateKey)`
|
||||||
@@ -539,7 +611,12 @@ const aggSignature3 = bls.aggregateSignatures(signatures3);
|
|||||||
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
|
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
|
||||||
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
|
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
|
||||||
|
|
||||||
// bls.pairing(PointG1, PointG2) // pairings
|
// Pairings, with and without final exponentiation
|
||||||
|
// bls.pairing(PointG1, PointG2);
|
||||||
|
// bls.pairing(PointG1, PointG2, false);
|
||||||
|
// bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(eGS, ePHm));
|
||||||
|
|
||||||
|
// Others
|
||||||
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
|
// bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
|
||||||
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
|
// bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
|
||||||
|
|
||||||
@@ -598,7 +675,7 @@ utils: {
|
|||||||
|
|
||||||
### abstract/hash-to-curve: Hashing strings to curve points
|
### abstract/hash-to-curve: Hashing strings to curve points
|
||||||
|
|
||||||
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16).
|
The module allows to hash arbitrary strings to elliptic curve points. Implements [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
|
||||||
|
|
||||||
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
|
Every curve has exported `hashToCurve` and `encodeToCurve` methods. You should always prefer `hashToCurve` for security:
|
||||||
|
|
||||||
@@ -614,19 +691,17 @@ bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
|
|||||||
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
|
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need low-level methods from spec:
|
Low-level methods from the spec:
|
||||||
|
|
||||||
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
|
||||||
|
|
||||||
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
// produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
||||||
function expand_message_xmd(
|
function expand_message_xmd(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
lenInBytes: number,
|
lenInBytes: number,
|
||||||
H: CHash
|
H: CHash // For CHash see abstract/weierstrass docs section
|
||||||
): Uint8Array;
|
): Uint8Array;
|
||||||
|
// produces a uniformly random byte string using an extendable-output function (XOF) H.
|
||||||
function expand_message_xof(
|
function expand_message_xof(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
@@ -634,13 +709,9 @@ function expand_message_xof(
|
|||||||
k: number,
|
k: number,
|
||||||
H: CHash
|
H: CHash
|
||||||
): Uint8Array;
|
): Uint8Array;
|
||||||
```
|
// Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
||||||
|
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
|
||||||
|
|
||||||
`hash_to_field(msg, count, options)`
|
|
||||||
[(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
|
|
||||||
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
/**
|
/**
|
||||||
* * `DST` is a domain separation tag, defined in section 2.2.5
|
* * `DST` is a domain separation tag, defined in section 2.2.5
|
||||||
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
||||||
@@ -651,23 +722,13 @@ hashes arbitrary-length byte strings to a list of one or more elements of a fini
|
|||||||
*/
|
*/
|
||||||
type UnicodeOrBytes = string | Uint8Array;
|
type UnicodeOrBytes = string | Uint8Array;
|
||||||
type Opts = {
|
type Opts = {
|
||||||
DST: UnicodeOrBytes;
|
DST: UnicodeOrBytes;
|
||||||
p: bigint;
|
p: bigint;
|
||||||
m: number;
|
m: number;
|
||||||
k: number;
|
k: number;
|
||||||
expand?: 'xmd' | 'xof';
|
expand?: 'xmd' | 'xof';
|
||||||
hash: CHash;
|
hash: CHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
|
||||||
* @param msg a byte string containing the message to hash
|
|
||||||
* @param count the number of elements of F to output
|
|
||||||
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
|
||||||
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
|
||||||
*/
|
|
||||||
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/poseidon: Poseidon hash
|
### abstract/poseidon: Poseidon hash
|
||||||
@@ -710,30 +771,40 @@ mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
|
|||||||
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
|
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Field operations are not constant-time: they are using JS bigints, see [security](#security).
|
||||||
|
The fact is mostly irrelevant, but the important method to keep in mind is `pow`,
|
||||||
|
which may leak exponent bits, when used naïvely.
|
||||||
|
|
||||||
|
`mod.Field` is always **field over prime**. Non-prime fields aren't supported for now.
|
||||||
|
We don't test for prime-ness for speed and because algorithms are probabilistic anyway.
|
||||||
|
Initializing a non-prime field could make your app suspectible to
|
||||||
|
DoS (infilite loop) on Tonelli-Shanks square root calculation.
|
||||||
|
|
||||||
|
Unlike `mod.invert`, `mod.invertBatch` won't throw on `0`: make sure to throw an error yourself.
|
||||||
|
|
||||||
#### Creating private keys from hashes
|
#### Creating private keys from hashes
|
||||||
|
|
||||||
Suppose you have `sha256(something)` (e.g. from HMAC) and you want to make a private key from it.
|
You can't simply make a 32-byte private key from a 32-byte hash.
|
||||||
Even though p256 or secp256k1 may have 32-byte private keys,
|
Doing so will make the key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
|
||||||
and sha256 output is also 32-byte, you can't just use it and reduce it modulo `CURVE.n`.
|
|
||||||
|
|
||||||
Doing so will make the result key [biased](https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/).
|
To make the bias negligible, we follow [FIPS 186-5 A.2](https://csrc.nist.gov/publications/detail/fips/186/5/final)
|
||||||
|
and [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-5.2).
|
||||||
|
This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.
|
||||||
|
|
||||||
To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary
|
`hashToPrivateScalar()` that hashes to **private key** was created for this purpose.
|
||||||
byte array and produce valid scalars / private keys with bias being neglible.
|
Use [abstract/hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
||||||
|
if you need to hash to **public key**.
|
||||||
Use [hash-to-curve](#abstracthash-to-curve-hashing-strings-to-curve-points) if you need
|
|
||||||
hashing to **public keys**; the function in the module instead operates on **private keys**.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { p256 } from '@noble/curves/p256';
|
import { p256 } from '@noble/curves/p256';
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { hkdf } from '@noble/hashes/hkdf';
|
import { hkdf } from '@noble/hashes/hkdf';
|
||||||
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
|
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
|
||||||
const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes
|
const derived = hkdf(sha256, someKey, undefined, 'application', 48); // 48 bytes for 32-byte priv
|
||||||
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
|
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/utils: General utilities
|
### abstract/utils: Useful utilities
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import * as utils from '@noble/curves/abstract/utils';
|
import * as utils from '@noble/curves/abstract/utils';
|
||||||
@@ -755,18 +826,36 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
|
|||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
1. The library has been audited in Feb 2023 by an independent security firm [Trail of Bits](https://www.trailofbits.com):
|
1. The library has been independently audited:
|
||||||
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
|
|
||||||
The audit has been funded by [Ryan Shea](https://www.shea.io). Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`, and top-level modules `_shortw_utils` and `secp256k1`. See [changes since audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
|
|
||||||
2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). You can run the fuzzer by yourself to check it.
|
|
||||||
3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve 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. Nonetheless we're targetting algorithmic constant time.
|
|
||||||
|
|
||||||
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. As for devDependencies used by the library:
|
- in Feb 2023 by [Trail of Bits](https://www.trailofbits.com):
|
||||||
|
[PDF](https://github.com/trailofbits/publications/blob/master/reviews/2023-01-ryanshea-noblecurveslibrary-securityreview.pdf).
|
||||||
|
The audit has been funded by [Ryan Shea](https://www.shea.io).
|
||||||
|
Audit scope was abstract modules `curve`, `hash-to-curve`, `modular`, `poseidon`, `utils`, `weierstrass`,
|
||||||
|
and top-level modules `_shortw_utils` and `secp256k1`.
|
||||||
|
See [changes since v0.7.3 audit](https://github.com/paulmillr/noble-curves/compare/0.7.3..main).
|
||||||
|
|
||||||
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us
|
2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
|
||||||
and follow the same practices such as: minimal library size, auditability, signed releases
|
You can run the fuzzer by yourself to check it.
|
||||||
- prettier (linter), fast-check (property-based testing),
|
3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations:
|
||||||
typescript versions are locked and rarely updated. Every update is checked with `npm-diff`.
|
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
|
||||||
|
achieve 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. Nonetheless we're targetting algorithmic constant time.
|
||||||
|
|
||||||
|
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.
|
||||||
|
As for devDependencies used by the library:
|
||||||
|
|
||||||
|
- `@scure` base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing)
|
||||||
|
are developed by us and follow the same practices such as: minimal library size, auditability,
|
||||||
|
signed releases
|
||||||
|
- prettier (linter), fast-check (property-based testing), typescript versions
|
||||||
|
are locked and rarely updated. Every update is checked with `npm-diff`.
|
||||||
The packages are big, which makes it hard to audit their source code thoroughly and fully.
|
The packages are big, which makes it hard to audit their source code thoroughly and fully.
|
||||||
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
|
- They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.
|
||||||
|
|
||||||
@@ -865,35 +954,37 @@ ed448 x 1,247 ops/sec @ 801μs/op
|
|||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
Previously, the library was split into single-feature packages
|
Previously, the library was split into single-feature packages
|
||||||
noble-secp256k1, noble-ed25519 and noble-bls12-381.
|
[noble-secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
||||||
|
[noble-ed25519](https://github.com/paulmillr/noble-ed25519) and
|
||||||
|
[noble-bls12-381](https://github.com/paulmillr/noble-bls12-381).
|
||||||
|
|
||||||
Curves continue their original work. The single-feature packages changed their
|
Curves continue their original work. The single-feature packages changed their
|
||||||
direction towards providing minimal 4kb implementations of cryptography,
|
direction towards providing minimal 4kb implementations of cryptography,
|
||||||
which means they have less features.
|
which means they have less features.
|
||||||
|
|
||||||
Upgrading from @noble/secp256k1 2.0 or @noble/ed25519 2.0: no changes, libraries are compatible.
|
Upgrading from noble-secp256k1 2.0 or noble-ed25519 2.0: no changes, libraries are compatible.
|
||||||
|
|
||||||
Upgrading from [@noble/secp256k1](https://github.com/paulmillr/noble-secp256k1) 1.7:
|
Upgrading from noble-secp256k1 1.7:
|
||||||
|
|
||||||
- `getPublicKey`
|
- `getPublicKey`
|
||||||
- now produce 33-byte compressed signatures by default
|
- now produce 33-byte compressed signatures by default
|
||||||
- to use old behavior, which produced 65-byte uncompressed keys, set
|
- to use old behavior, which produced 65-byte uncompressed keys, set
|
||||||
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
|
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
|
||||||
- `sign`
|
- `sign`
|
||||||
- is now sync; use `signAsync` for async version
|
- is now sync; use `signAsync` for async version
|
||||||
- now returns `Signature` instance with `{ r, s, recovery }` properties
|
- now returns `Signature` instance with `{ r, s, recovery }` properties
|
||||||
- `canonical` option was renamed to `lowS`
|
- `canonical` option was renamed to `lowS`
|
||||||
- `recovered` option has been removed because recovery bit is always returned now
|
- `recovered` option has been removed because recovery bit is always returned now
|
||||||
- `der` option has been removed. There are 2 options:
|
- `der` option has been removed. There are 2 options:
|
||||||
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
|
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
|
||||||
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
|
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
|
||||||
2. If you must use DER encoding, switch to noble-curves (see above).
|
2. If you must use DER encoding, switch to noble-curves (see above).
|
||||||
- `verify`
|
- `verify`
|
||||||
- `strict` option was renamed to `lowS`
|
- `strict` option was renamed to `lowS`
|
||||||
- `getSharedSecret`
|
- `getSharedSecret`
|
||||||
- now produce 33-byte compressed signatures by default
|
- now produce 33-byte compressed signatures by default
|
||||||
- to use old behavior, which produced 65-byte uncompressed keys, set
|
- to use old behavior, which produced 65-byte uncompressed keys, set
|
||||||
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
|
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
|
||||||
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
|
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
|
||||||
- `number` type for private keys have been removed: use `bigint` instead
|
- `number` type for private keys have been removed: use `bigint` instead
|
||||||
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
|
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
|
||||||
@@ -914,53 +1005,65 @@ Upgrading from [@noble/ed25519](https://github.com/paulmillr/noble-ed25519) 1.7:
|
|||||||
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
|
Upgrading from [@noble/bls12-381](https://github.com/paulmillr/noble-bls12-381):
|
||||||
|
|
||||||
- Methods and classes were renamed:
|
- Methods and classes were renamed:
|
||||||
- PointG1 -> G1.Point, PointG2 -> G2.Point
|
- PointG1 -> G1.Point, PointG2 -> G2.Point
|
||||||
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
|
- PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
|
||||||
- Fp2 ORDER was corrected
|
- Fp2 ORDER was corrected
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
Useful documentation and articles about the library or its primitives:
|
|
||||||
|
|
||||||
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
|
- [Learning fast elliptic-curve cryptography](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)
|
||||||
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
|
- EdDSA
|
||||||
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
|
- [A Deep dive into Ed25519 Signatures](https://cendyne.dev/posts/2022-03-06-ed25519-signatures.html)
|
||||||
|
- [Ed25519 Deep Dive Addendum](https://cendyne.dev/posts/2022-09-11-ed25519-deep-dive-addendum.html)
|
||||||
|
- [It’s 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am)
|
||||||
|
- [Taming the many EdDSAs](https://csrc.nist.gov/csrc/media/Presentations/2023/crclub-2023-03-08/images-media/20230308-crypto-club-slides--taming-the-many-EdDSAs.pdf)
|
||||||
|
that describes concepts of Strong UnForgeability under Chosen Message Attacks and Strongly Binding Signatures
|
||||||
|
- [Cofactor Explained: Clearing Elliptic Curves’ dirty little secret](https://loup-vaillant.fr/tutorials/cofactor)
|
||||||
|
- [Surrounded by Elligators](https://loup-vaillant.fr/articles/implementing-elligator)
|
||||||
- Pairings and BLS
|
- Pairings and BLS
|
||||||
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
|
- [BLS signatures for busy people](https://gist.github.com/paulmillr/18b802ad219b1aee34d773d08ec26ca2)
|
||||||
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
|
- [BLS12-381 for the rest of us](https://hackmd.io/@benjaminion/bls12-381)
|
||||||
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
|
- [Key concepts of pairings](https://medium.com/@alonmuroch_65570/bls-signatures-part-2-key-concepts-of-pairings-27a8a9533d0c)
|
||||||
- Pairing over bls12-381:
|
- Pairing over bls12-381:
|
||||||
[part 1](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
|
[fields](https://research.nccgroup.com/2020/07/06/pairing-over-bls12-381-part-1-fields/),
|
||||||
[part 2](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
|
[curves](https://research.nccgroup.com/2020/07/13/pairing-over-bls12-381-part-2-curves/),
|
||||||
[part 3](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
|
[pairings](https://research.nccgroup.com/2020/08/13/pairing-over-bls12-381-part-3-pairing/)
|
||||||
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
|
- [Estimating the bit security of pairing-friendly curves](https://research.nccgroup.com/2022/02/03/estimating-the-bit-security-of-pairing-friendly-curves/)
|
||||||
|
|
||||||
Online demos:
|
### Demos
|
||||||
|
|
||||||
- [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
|
- [Elliptic Curve Calculator](https://paulmillr.com/noble): add / multiply points, sign messages
|
||||||
- [BLS threshold signatures](https://genthresh.com)
|
- [BLS threshold signatures](https://genthresh.com)
|
||||||
|
|
||||||
Projects using noble-curves:
|
### Projects using curves
|
||||||
|
|
||||||
- [scure-bip32](https://github.com/paulmillr/scure-bip32) and separate [bip32](https://github.com/bitcoinjs/bip32) HDkey libraries
|
- HDkey libraries: [scure-bip32](https://github.com/paulmillr/scure-bip32), [bip32](https://github.com/bitcoinjs/bip32)
|
||||||
|
- Social networks: [nostr](https://github.com/nbd-wtf/nostr-tools), [bluesky](https://github.com/bluesky-social/atproto)
|
||||||
- Ethereum libraries:
|
- Ethereum libraries:
|
||||||
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
|
- [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
|
||||||
- [@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
|
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer),
|
||||||
- [micro-eth-signer](https://github.com/paulmillr/micro-eth-signer)
|
[ethers](https://github.com/ethers-io/ethers.js) (old noble),
|
||||||
- [ethers](https://github.com/ethers-io/ethers.js) (old noble-secp256k1 for now)
|
[viem.sh](https://viem.sh),
|
||||||
- [viem.sh](https://viem.sh)
|
[@ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo)
|
||||||
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
|
- [metamask's eth-sig-util](https://github.com/MetaMask/eth-sig-util)
|
||||||
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
|
- [gridplus lattice sdk](https://github.com/GridPlus/lattice-eth2-utils)
|
||||||
- Bitcoin libraries: [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
|
- Bitcoin libraries:
|
||||||
|
- [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer)
|
||||||
|
- [tapscript](https://github.com/cmdruid/tapscript)
|
||||||
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
|
- Solana libraries: [micro-sol-signer](https://github.com/paulmillr/micro-sol-signer), [solana-web3.js](https://github.com/solana-labs/solana-web3.js)
|
||||||
- [polkadot.js](https://github.com/polkadot-js/common), [micro-starknet](https://github.com/paulmillr/micro-starknet)
|
- Other web3 stuff:
|
||||||
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble-ed25519 for now)
|
- [scure-starknet](https://github.com/paulmillr/scure-starknet)
|
||||||
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js), [nostr-tools](https://github.com/nbd-wtf/nostr-tools)
|
- [aztec](https://github.com/AztecProtocol/aztec-packages)
|
||||||
|
- [polkadot.js](https://github.com/polkadot-js/common), [drand-client](https://github.com/drand/drand-client), [moneroj](https://github.com/beritani/moneroj), [tronlib](https://github.com/CoinSpace/tronlib)
|
||||||
|
- [protonmail](https://github.com/ProtonMail/WebClients) (old noble for now)
|
||||||
|
- [did-jwt](https://github.com/decentralized-identity/did-jwt), [hpke-js](https://github.com/dajiaji/hpke-js),
|
||||||
|
[js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise)
|
||||||
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
|
- [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) SSH, PGP, TOR key generation
|
||||||
- [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
|
- [secp256k1 compatibility layer](https://github.com/ethereum/js-ethereum-cryptography/blob/2.0.0/src/secp256k1-compat.ts)
|
||||||
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
|
for users who want to switch from secp256k1-node or tiny-secp256k1. Allows to see which methods map to corresponding noble code.
|
||||||
- [BLS BBS signatures](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
|
- [BLS BBS signatures](https://github.com/Wind4Greg/BBS-Draft-Checks) following [draft-irtf-cfrg-bbs-signatures-latest](https://identity.foundation/bbs-signature/draft-irtf-cfrg-bbs-signatures.html)
|
||||||
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
|
- [KZG trusted setup ceremony](https://github.com/dsrvlabs/czg-keremony)
|
||||||
|
- See [full list of projects on GitHub](https://github.com/paulmillr/noble-curves/network/dependents).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
18
benchmark/decaf448.js
Normal file
18
benchmark/decaf448.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { run, mark, utils } from 'micro-bmark';
|
||||||
|
import { shake256 } from '@noble/hashes/sha3';
|
||||||
|
import * as mod from '../abstract/modular.js';
|
||||||
|
import { ed448, DecafPoint } from '../ed448.js';
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
const RAM = false;
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
console.log(`\x1b[36mdecaf448\x1b[0m`);
|
||||||
|
const priv = mod.hashToPrivateScalar(shake256(ed448.utils.randomPrivateKey(), { dkLen: 112 }), ed448.CURVE.n);
|
||||||
|
const pub = DecafPoint.BASE.multiply(priv);
|
||||||
|
const encoded = pub.toRawBytes();
|
||||||
|
await mark('add', 1000000, () => pub.add(DecafPoint.BASE));
|
||||||
|
await mark('multiply', 1000, () => DecafPoint.BASE.multiply(priv));
|
||||||
|
await mark('encode', 10000, () => DecafPoint.BASE.toRawBytes());
|
||||||
|
await mark('decode', 10000, () => DecafPoint.fromHex(encoded));
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
});
|
||||||
@@ -8,8 +8,8 @@ import { hashToCurve as secp256k1 } from '../secp256k1.js';
|
|||||||
import { hashToCurve as p256 } from '../p256.js';
|
import { hashToCurve as p256 } from '../p256.js';
|
||||||
import { hashToCurve as p384 } from '../p384.js';
|
import { hashToCurve as p384 } from '../p384.js';
|
||||||
import { hashToCurve as p521 } from '../p521.js';
|
import { hashToCurve as p521 } from '../p521.js';
|
||||||
import { hashToCurve as ed25519 } from '../ed25519.js';
|
import { hashToCurve as ed25519, hash_to_ristretto255 } from '../ed25519.js';
|
||||||
import { hashToCurve as ed448 } from '../ed448.js';
|
import { hashToCurve as ed448, hash_to_decaf448 } from '../ed448.js';
|
||||||
import { utf8ToBytes } from '../abstract/utils.js';
|
import { utf8ToBytes } from '../abstract/utils.js';
|
||||||
|
|
||||||
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
||||||
@@ -26,4 +26,7 @@ run(async () => {
|
|||||||
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
|
for (let [title, fn] of Object.entries({ secp256k1, p256, p384, p521, ed25519, ed448 })) {
|
||||||
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
|
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await mark('hash_to_ristretto255', 1000, () => hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }));
|
||||||
|
await mark('hash_to_decaf448', 1000, () => hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }));
|
||||||
});
|
});
|
||||||
|
|||||||
18
benchmark/ristretto255.js
Normal file
18
benchmark/ristretto255.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { run, mark, utils } from 'micro-bmark';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import * as mod from '../abstract/modular.js';
|
||||||
|
import { ed25519, RistrettoPoint } from '../ed25519.js';
|
||||||
|
|
||||||
|
run(async () => {
|
||||||
|
const RAM = false;
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
console.log(`\x1b[36mristretto255\x1b[0m`);
|
||||||
|
const priv = mod.hashToPrivateScalar(sha512(ed25519.utils.randomPrivateKey()), ed25519.CURVE.n);
|
||||||
|
const pub = RistrettoPoint.BASE.multiply(priv);
|
||||||
|
const encoded = pub.toRawBytes();
|
||||||
|
await mark('add', 1000000, () => pub.add(RistrettoPoint.BASE));
|
||||||
|
await mark('multiply', 10000, () => RistrettoPoint.BASE.multiply(priv));
|
||||||
|
await mark('encode', 10000, () => RistrettoPoint.BASE.toRawBytes());
|
||||||
|
await mark('decode', 10000, () => RistrettoPoint.fromHex(encoded));
|
||||||
|
if (RAM) utils.logMem();
|
||||||
|
});
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@noble/curves": "..",
|
"@noble/curves": "..",
|
||||||
"esbuild": "0.17.19"
|
"esbuild": "0.18.11"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx esbuild --bundle input.js --outfile=noble-curves.js --global-name=nobleCurves"
|
"build": "npx esbuild --bundle input.js --outfile=noble-curves.js --global-name=nobleCurves"
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"browser": {
|
"sideEffects": false
|
||||||
"crypto": false,
|
|
||||||
"./crypto": "./esm/crypto.js"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "1.3.1"
|
"@noble/hashes": "1.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@noble/hashes": {
|
"node_modules/@noble/hashes": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"abstract",
|
"abstract",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"*.d.ts.map"
|
"*.d.ts.map"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js",
|
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js; node ristretto255.js; node decaf448.js",
|
||||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||||
"build:release": "rollup -c rollup.config.js",
|
"build:release": "rollup -c rollup.config.js",
|
||||||
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
|
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "1.3.1"
|
"@noble/hashes": "1.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"prettier": "2.8.4",
|
"prettier": "2.8.4",
|
||||||
"typescript": "5.0.2"
|
"typescript": "5.0.2"
|
||||||
},
|
},
|
||||||
|
"sideEffects": false,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* Some projects may prefer to swap this relation, it is not supported for now.
|
* Some projects may prefer to swap this relation, it is not supported for now.
|
||||||
*/
|
*/
|
||||||
import { AffinePoint } from './curve.js';
|
import { AffinePoint } from './curve.js';
|
||||||
import { IField, hashToPrivateScalar } from './modular.js';
|
import { IField, getMinHashLength, mapHashToField } from './modular.js';
|
||||||
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
|
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
|
||||||
import * as htf from './hash-to-curve.js';
|
import * as htf from './hash-to-curve.js';
|
||||||
import {
|
import {
|
||||||
@@ -122,7 +122,6 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
// Fields are specific for curve, so for now we'll need to pass them with opts
|
// Fields are specific for curve, so for now we'll need to pass them with opts
|
||||||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
|
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
|
||||||
const BLS_X_LEN = bitLen(CURVE.params.x);
|
const BLS_X_LEN = bitLen(CURVE.params.x);
|
||||||
const groupLen = 32; // TODO: calculate; hardcoded for now
|
|
||||||
|
|
||||||
// Pre-compute coefficients for sparse multiplication
|
// Pre-compute coefficients for sparse multiplication
|
||||||
// Point addition and point double calculations is reused for coefficients
|
// Point addition and point double calculations is reused for coefficients
|
||||||
@@ -189,7 +188,8 @@ export function bls<Fp2, Fp6, Fp12>(
|
|||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
randomPrivateKey: (): Uint8Array => {
|
randomPrivateKey: (): Uint8Array => {
|
||||||
return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.params.r));
|
const length = getMinHashLength(Fr.ORDER);
|
||||||
|
return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
|
||||||
},
|
},
|
||||||
calcPairingPrecomputes,
|
calcPairingPrecomputes,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function isNum(item: unknown): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
|
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
|
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
|
||||||
export function expand_message_xmd(
|
export function expand_message_xmd(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
@@ -69,7 +69,7 @@ export function expand_message_xmd(
|
|||||||
isBytes(msg);
|
isBytes(msg);
|
||||||
isBytes(DST);
|
isBytes(DST);
|
||||||
isNum(lenInBytes);
|
isNum(lenInBytes);
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
|
||||||
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
||||||
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
|
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
|
||||||
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
||||||
@@ -88,6 +88,11 @@ export function expand_message_xmd(
|
|||||||
return pseudo_random_bytes.slice(0, lenInBytes);
|
return pseudo_random_bytes.slice(0, lenInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Produces a uniformly random byte string using an extendable-output function (XOF) H.
|
||||||
|
// 1. The collision resistance of H MUST be at least k bits.
|
||||||
|
// 2. H MUST be an XOF that has been proved indifferentiable from
|
||||||
|
// a random oracle under a reasonable cryptographic assumption.
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
|
||||||
export function expand_message_xof(
|
export function expand_message_xof(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
DST: Uint8Array,
|
DST: Uint8Array,
|
||||||
@@ -98,7 +103,7 @@ export function expand_message_xof(
|
|||||||
isBytes(msg);
|
isBytes(msg);
|
||||||
isBytes(DST);
|
isBytes(DST);
|
||||||
isNum(lenInBytes);
|
isNum(lenInBytes);
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
|
||||||
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
||||||
if (DST.length > 255) {
|
if (DST.length > 255) {
|
||||||
const dkLen = Math.ceil((2 * k) / 8);
|
const dkLen = Math.ceil((2 * k) / 8);
|
||||||
@@ -119,7 +124,7 @@ export function expand_message_xof(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
* https://www.rfc-editor.org/rfc/rfc9380#section-5.2
|
||||||
* @param msg a byte string containing the message to hash
|
* @param msg a byte string containing the message to hash
|
||||||
* @param count the number of elements of F to output
|
* @param count the number of elements of F to output
|
||||||
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
||||||
@@ -127,7 +132,7 @@ export function expand_message_xof(
|
|||||||
*/
|
*/
|
||||||
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
||||||
validateObject(options, {
|
validateObject(options, {
|
||||||
DST: 'string',
|
DST: 'stringOrUint8Array',
|
||||||
p: 'bigint',
|
p: 'bigint',
|
||||||
m: 'isSafeInteger',
|
m: 'isSafeInteger',
|
||||||
k: 'isSafeInteger',
|
k: 'isSafeInteger',
|
||||||
@@ -201,8 +206,8 @@ export function createHasher<T>(
|
|||||||
) {
|
) {
|
||||||
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
|
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
|
||||||
return {
|
return {
|
||||||
// Encodes byte string to elliptic curve
|
// Encodes byte string to elliptic curve.
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
// hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
|
||||||
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
||||||
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
|
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
|
||||||
const u0 = Point.fromAffine(mapToCurve(u[0]));
|
const u0 = Point.fromAffine(mapToCurve(u[0]));
|
||||||
@@ -212,7 +217,8 @@ export function createHasher<T>(
|
|||||||
return P;
|
return P;
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
// Encodes byte string to elliptic curve.
|
||||||
|
// encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
|
||||||
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
||||||
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
||||||
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
||||||
|
|||||||
@@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
|
|||||||
return mod(x, modulo);
|
return mod(x, modulo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tonelli-Shanks algorithm
|
/**
|
||||||
// Paper 1: https://eprint.iacr.org/2012/685.pdf (page 12)
|
* Tonelli-Shanks square root search algorithm.
|
||||||
// Paper 2: Square Roots from 1; 24, 51, 10 to Dan Shanks
|
* 1. https://eprint.iacr.org/2012/685.pdf (page 12)
|
||||||
|
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
|
||||||
|
* Will start an infinite loop if field order P is not prime.
|
||||||
|
* @param P field order
|
||||||
|
* @returns function that takes field Fp (created from P) and number n
|
||||||
|
*/
|
||||||
export function tonelliShanks(P: bigint) {
|
export function tonelliShanks(P: bigint) {
|
||||||
// Legendre constant: used to calculate Legendre symbol (a | p),
|
// Legendre constant: used to calculate Legendre symbol (a | p),
|
||||||
// which denotes the value of a^((p-1)/2) (mod p).
|
// which denotes the value of a^((p-1)/2) (mod p).
|
||||||
@@ -198,7 +203,7 @@ export function FpSqrt(P: bigint) {
|
|||||||
// Little-endian check for first LE bit (last BE bit);
|
// Little-endian check for first LE bit (last BE bit);
|
||||||
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
|
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
|
||||||
|
|
||||||
// Field is not always over prime, Fp2 for example has ORDER(q)=p^m
|
// Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
|
||||||
export interface IField<T> {
|
export interface IField<T> {
|
||||||
ORDER: bigint;
|
ORDER: bigint;
|
||||||
BYTES: number;
|
BYTES: number;
|
||||||
@@ -228,7 +233,8 @@ export interface IField<T> {
|
|||||||
sqrN(num: T): T;
|
sqrN(num: T): T;
|
||||||
|
|
||||||
// Optional
|
// Optional
|
||||||
// Should be same as sgn0 function in https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
|
// Should be same as sgn0 function in
|
||||||
|
// [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
|
||||||
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
|
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
|
||||||
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
|
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
|
||||||
// legendre?(num: T): T;
|
// legendre?(num: T): T;
|
||||||
@@ -260,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generic field functions
|
// Generic field functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `pow` but for Fp: non-constant-time.
|
||||||
|
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
|
||||||
|
*/
|
||||||
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
||||||
// Should have same speed as pow for bigints
|
// Should have same speed as pow for bigints
|
||||||
// TODO: benchmark!
|
// TODO: benchmark!
|
||||||
@@ -276,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 is non-invertible: non-batched version will throw on 0
|
/**
|
||||||
|
* Efficiently invert an array of Field elements.
|
||||||
|
* `inv(0)` will return `undefined` here: make sure to throw an error.
|
||||||
|
*/
|
||||||
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
|
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
|
||||||
const tmp = new Array(nums.length);
|
const tmp = new Array(nums.length);
|
||||||
// Walk from first to last, multiply them by each other MOD p
|
// Walk from first to last, multiply them by each other MOD p
|
||||||
@@ -319,12 +333,12 @@ export function nLength(n: bigint, nBitLength?: number) {
|
|||||||
|
|
||||||
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
|
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
|
||||||
/**
|
/**
|
||||||
* Initializes a galois field over prime. Non-primes are not supported for now.
|
* Initializes a finite field over prime. **Non-primes are not supported.**
|
||||||
* Do not init in loop: slow. Very fragile: always run a benchmark on change.
|
* Do not init in loop: slow. Very fragile: always run a benchmark on a change.
|
||||||
* Major performance gains:
|
* Major performance optimizations:
|
||||||
* a) non-normalized operations like mulN instead of mul
|
* * a) denormalized operations like mulN instead of mul
|
||||||
* b) `Object.freeze`
|
* * b) same object shape: never add or remove keys
|
||||||
* c) Same object shape: never add or remove keys
|
* * c) Object.freeze
|
||||||
* @param ORDER prime positive bigint
|
* @param ORDER prime positive bigint
|
||||||
* @param bitLen how many bits the field consumes
|
* @param bitLen how many bits the field consumes
|
||||||
* @param isLE (def: false) if encoding / decoding should be in little-endian
|
* @param isLE (def: false) if encoding / decoding should be in little-endian
|
||||||
@@ -336,7 +350,7 @@ export function Field(
|
|||||||
isLE = false,
|
isLE = false,
|
||||||
redef: Partial<IField<bigint>> = {}
|
redef: Partial<IField<bigint>> = {}
|
||||||
): Readonly<FpField> {
|
): Readonly<FpField> {
|
||||||
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
|
if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
|
||||||
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
||||||
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
|
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
|
||||||
const sqrtP = FpSqrt(ORDER);
|
const sqrtP = FpSqrt(ORDER);
|
||||||
@@ -400,15 +414,10 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
|
* "Constant-time" private key generation utility.
|
||||||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
* Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
|
||||||
* and convert them into private scalar, with the modulo bias being negligible.
|
* Which makes it slightly more biased, less secure.
|
||||||
* Needs at least 40 bytes of input for 32-byte private key.
|
* @deprecated use mapKeyToField instead
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
* @param hash hash output from SHA3 or a similar function
|
|
||||||
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
|
|
||||||
* @param isLE interpret hash bytes as LE num
|
|
||||||
* @returns valid private scalar
|
|
||||||
*/
|
*/
|
||||||
export function hashToPrivateScalar(
|
export function hashToPrivateScalar(
|
||||||
hash: string | Uint8Array,
|
hash: string | Uint8Array,
|
||||||
@@ -423,3 +432,53 @@ export function hashToPrivateScalar(
|
|||||||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
||||||
return mod(num, groupOrder - _1n) + _1n;
|
return mod(num, groupOrder - _1n) + _1n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns total number of bytes consumed by the field element.
|
||||||
|
* For example, 32 bytes for usual 256-bit weierstrass curve.
|
||||||
|
* @param fieldOrder number of field elements, usually CURVE.n
|
||||||
|
* @returns byte length of field
|
||||||
|
*/
|
||||||
|
export function getFieldBytesLength(fieldOrder: bigint): number {
|
||||||
|
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
|
||||||
|
const bitLength = fieldOrder.toString(2).length;
|
||||||
|
return Math.ceil(bitLength / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns minimal amount of bytes that can be safely reduced
|
||||||
|
* by field order.
|
||||||
|
* Should be 2^-128 for 128-bit curve such as P256.
|
||||||
|
* @param fieldOrder number of field elements, usually CURVE.n
|
||||||
|
* @returns byte length of target hash
|
||||||
|
*/
|
||||||
|
export function getMinHashLength(fieldOrder: bigint): number {
|
||||||
|
const length = getFieldBytesLength(fieldOrder);
|
||||||
|
return length + Math.ceil(length / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Constant-time" private key generation utility.
|
||||||
|
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
|
||||||
|
* and convert them into private scalar, with the modulo bias being negligible.
|
||||||
|
* Needs at least 48 bytes of input for 32-byte private key.
|
||||||
|
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
||||||
|
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
|
||||||
|
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
|
||||||
|
* @param hash hash output from SHA3 or a similar function
|
||||||
|
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
|
||||||
|
* @param isLE interpret hash bytes as LE num
|
||||||
|
* @returns valid private scalar
|
||||||
|
*/
|
||||||
|
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
|
||||||
|
const len = key.length;
|
||||||
|
const fieldLen = getFieldBytesLength(fieldOrder);
|
||||||
|
const minLen = getMinHashLength(fieldOrder);
|
||||||
|
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
|
||||||
|
if (len < 16 || len < minLen || len > 1024)
|
||||||
|
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
|
||||||
|
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
|
||||||
|
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
|
||||||
|
const reduced = mod(num, fieldOrder - _1n) + _1n;
|
||||||
|
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,34 +15,36 @@ export type PoseidonOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function validateOpts(opts: PoseidonOpts) {
|
export function validateOpts(opts: PoseidonOpts) {
|
||||||
const { Fp } = opts;
|
const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
|
||||||
|
const { roundsFull, roundsPartial, sboxPower, t } = opts;
|
||||||
|
|
||||||
validateField(Fp);
|
validateField(Fp);
|
||||||
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
||||||
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
||||||
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
|
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||||
}
|
}
|
||||||
if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
|
|
||||||
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
|
|
||||||
// Default is 5, but by some reasons stark uses 3
|
|
||||||
let sboxPower = opts.sboxPower;
|
|
||||||
if (sboxPower === undefined) sboxPower = 5;
|
|
||||||
if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
|
|
||||||
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
|
||||||
|
|
||||||
const _sboxPower = BigInt(sboxPower);
|
// MDS is TxT matrix
|
||||||
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
|
if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
|
||||||
// Unwrapped sbox power for common cases (195->142μs)
|
const _mds = mds.map((mdsRow) => {
|
||||||
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
|
if (!Array.isArray(mdsRow) || mdsRow.length !== t)
|
||||||
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
|
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
|
||||||
|
return mdsRow.map((i) => {
|
||||||
|
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
|
||||||
|
return Fp.create(i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (opts.roundsFull % 2 !== 0)
|
if (rev !== undefined && typeof rev !== 'boolean')
|
||||||
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);
|
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
|
||||||
const rounds = opts.roundsFull + opts.roundsPartial;
|
|
||||||
|
|
||||||
if (!Array.isArray(opts.roundConstants) || opts.roundConstants.length !== rounds)
|
if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
|
||||||
|
const rounds = roundsFull + roundsPartial;
|
||||||
|
|
||||||
|
if (!Array.isArray(rc) || rc.length !== rounds)
|
||||||
throw new Error('Poseidon: wrong round constants');
|
throw new Error('Poseidon: wrong round constants');
|
||||||
const roundConstants = opts.roundConstants.map((rc) => {
|
const roundConstants = rc.map((rc) => {
|
||||||
if (!Array.isArray(rc) || rc.length !== opts.t)
|
if (!Array.isArray(rc) || rc.length !== t)
|
||||||
throw new Error(`Poseidon wrong round constants: ${rc}`);
|
throw new Error(`Poseidon wrong round constants: ${rc}`);
|
||||||
return rc.map((i) => {
|
return rc.map((i) => {
|
||||||
if (typeof i !== 'bigint' || !Fp.isValid(i))
|
if (typeof i !== 'bigint' || !Fp.isValid(i))
|
||||||
@@ -50,18 +52,16 @@ export function validateOpts(opts: PoseidonOpts) {
|
|||||||
return Fp.create(i);
|
return Fp.create(i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// MDS is TxT matrix
|
|
||||||
if (!Array.isArray(opts.mds) || opts.mds.length !== opts.t)
|
if (!sboxPower || ![3, 5, 7].includes(sboxPower))
|
||||||
throw new Error('Poseidon: wrong MDS matrix');
|
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
||||||
const mds = opts.mds.map((mdsRow) => {
|
const _sboxPower = BigInt(sboxPower);
|
||||||
if (!Array.isArray(mdsRow) || mdsRow.length !== opts.t)
|
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
|
||||||
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
|
// Unwrapped sbox power for common cases (195->142μs)
|
||||||
return mdsRow.map((i) => {
|
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
|
||||||
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
|
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
|
||||||
return Fp.create(i);
|
|
||||||
});
|
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
|
||||||
});
|
|
||||||
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function splitConstants(rc: bigint[], t: number) {
|
export function splitConstants(rc: bigint[], t: number) {
|
||||||
@@ -80,18 +80,17 @@ export function splitConstants(rc: bigint[], t: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function poseidon(opts: PoseidonOpts) {
|
export function poseidon(opts: PoseidonOpts) {
|
||||||
const { t, Fp, rounds, sboxFn, reversePartialPowIdx } = validateOpts(opts);
|
const _opts = validateOpts(opts);
|
||||||
const halfRoundsFull = Math.floor(opts.roundsFull / 2);
|
const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
|
||||||
const partialIdx = reversePartialPowIdx ? t - 1 : 0;
|
const halfRoundsFull = _opts.roundsFull / 2;
|
||||||
|
const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
|
||||||
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
|
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
|
||||||
values = values.map((i, j) => Fp.add(i, opts.roundConstants[idx][j]));
|
values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
|
||||||
|
|
||||||
if (isFull) values = values.map((i) => sboxFn(i));
|
if (isFull) values = values.map((i) => sboxFn(i));
|
||||||
else values[partialIdx] = sboxFn(values[partialIdx]);
|
else values[partialIdx] = sboxFn(values[partialIdx]);
|
||||||
// Matrix multiplication
|
// Matrix multiplication
|
||||||
values = opts.mds.map((i) =>
|
values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
|
||||||
i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO)
|
|
||||||
);
|
|
||||||
return values;
|
return values;
|
||||||
};
|
};
|
||||||
const poseidonHash = function poseidonHash(values: bigint[]) {
|
const poseidonHash = function poseidonHash(values: bigint[]) {
|
||||||
@@ -105,7 +104,7 @@ export function poseidon(opts: PoseidonOpts) {
|
|||||||
// Apply r_f/2 full rounds.
|
// Apply r_f/2 full rounds.
|
||||||
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
||||||
// Apply r_p partial rounds.
|
// Apply r_p partial rounds.
|
||||||
for (let i = 0; i < opts.roundsPartial; i++) values = poseidonRound(values, false, round++);
|
for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
|
||||||
// Apply r_f/2 full rounds.
|
// Apply r_f/2 full rounds.
|
||||||
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
||||||
|
|
||||||
@@ -114,6 +113,6 @@ export function poseidon(opts: PoseidonOpts) {
|
|||||||
return values;
|
return values;
|
||||||
};
|
};
|
||||||
// For verification in tests
|
// For verification in tests
|
||||||
poseidonHash.roundConstants = opts.roundConstants;
|
poseidonHash.roundConstants = roundConstants;
|
||||||
return poseidonHash;
|
return poseidonHash;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export type CHash = {
|
|||||||
};
|
};
|
||||||
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
||||||
|
|
||||||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
|
||||||
|
i.toString(16).padStart(2, '0')
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
||||||
*/
|
*/
|
||||||
@@ -246,6 +248,7 @@ const validatorFns = {
|
|||||||
function: (val: any) => typeof val === 'function',
|
function: (val: any) => typeof val === 'function',
|
||||||
boolean: (val: any) => typeof val === 'boolean',
|
boolean: (val: any) => typeof val === 'boolean',
|
||||||
string: (val: any) => typeof val === 'string',
|
string: (val: any) => typeof val === 'string',
|
||||||
|
stringOrUint8Array: (val: any) => typeof val === 'string' || val instanceof Uint8Array,
|
||||||
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
||||||
array: (val: any) => Array.isArray(val),
|
array: (val: any) => Array.isArray(val),
|
||||||
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
|
|
||||||
const toBytes =
|
const toBytes =
|
||||||
CURVE.toBytes ||
|
CURVE.toBytes ||
|
||||||
((c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => {
|
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
|
||||||
const a = point.toAffine();
|
const a = point.toAffine();
|
||||||
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
|
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
|
||||||
});
|
});
|
||||||
@@ -333,9 +333,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|||||||
|
|
||||||
// A point on curve is valid if it conforms to equation.
|
// A point on curve is valid if it conforms to equation.
|
||||||
assertValidity(): void {
|
assertValidity(): void {
|
||||||
// Zero is valid point too!
|
|
||||||
if (this.is0()) {
|
if (this.is0()) {
|
||||||
if (CURVE.allowInfinityPoint) return;
|
// (0, 1, 0) aka ZERO is invalid in most contexts.
|
||||||
|
// In BLS, ZERO can be serialized, so we allow it.
|
||||||
|
// (0, 0, 0) is wrong representation of ZERO and is always invalid.
|
||||||
|
if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) return;
|
||||||
throw new Error('bad point: ZERO');
|
throw new Error('bad point: ZERO');
|
||||||
}
|
}
|
||||||
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
||||||
@@ -707,7 +709,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
isWithinCurveOrder,
|
isWithinCurveOrder,
|
||||||
} = weierstrassPoints({
|
} = weierstrassPoints({
|
||||||
...CURVE,
|
...CURVE,
|
||||||
toBytes(c, point, isCompressed: boolean): Uint8Array {
|
toBytes(_c, point, isCompressed: boolean): Uint8Array {
|
||||||
const a = point.toAffine();
|
const a = point.toAffine();
|
||||||
const x = Fp.toBytes(a.x);
|
const x = Fp.toBytes(a.x);
|
||||||
const cat = ut.concatBytes;
|
const cat = ut.concatBytes;
|
||||||
@@ -845,13 +847,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
normPrivateKeyToScalar: normPrivateKeyToScalar,
|
normPrivateKeyToScalar: normPrivateKeyToScalar,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces cryptographically secure private key from random of size (nBitLength+64)
|
* Produces cryptographically secure private key from random of size
|
||||||
* as per FIPS 186 B.4.1 with modulo bias being neglible.
|
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
|
||||||
*/
|
*/
|
||||||
randomPrivateKey: (): Uint8Array => {
|
randomPrivateKey: (): Uint8Array => {
|
||||||
const rand = CURVE.randomBytes(Fp.BYTES + 8);
|
const length = mod.getMinHashLength(CURVE.n);
|
||||||
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
|
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
|
||||||
return ut.numberToBytesBE(num, CURVE.nByteLength);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -964,7 +965,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
if (ent != null) {
|
if (ent != null) {
|
||||||
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
||||||
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
|
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
|
||||||
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
|
seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
|
||||||
}
|
}
|
||||||
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
||||||
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
||||||
@@ -1170,7 +1171,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
|
|||||||
return sqrtRatio;
|
return sqrtRatio;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* From draft-irtf-cfrg-hash-to-curve-16
|
* Simplified Shallue-van de Woestijne-Ulas Method
|
||||||
|
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
|
||||||
*/
|
*/
|
||||||
export function mapToCurveSimpleSWU<T>(
|
export function mapToCurveSimpleSWU<T>(
|
||||||
Fp: mod.IField<T>,
|
Fp: mod.IField<T>,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
|
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
|
||||||
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
|
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
|
||||||
// [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
|
// [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
|
||||||
// [bls-sigs-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
|
// [bls-sigs-04](https:/cfrg-hash-to/tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
|
||||||
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12).
|
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf--curve-12).
|
||||||
//
|
//
|
||||||
// ### Summary
|
// ### Summary
|
||||||
// 1. BLS Relies on Bilinear Pairing (expensive)
|
// 1. BLS Relies on Bilinear Pairing (expensive)
|
||||||
@@ -177,7 +177,7 @@ const Fp2: mod.IField<Fp2> & Fp2Utils = {
|
|||||||
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
|
if (im1 > im2 || (im1 === im2 && re1 > re2)) return x1;
|
||||||
return x2;
|
return x2;
|
||||||
},
|
},
|
||||||
// Same as sgn0_fp2 in draft-irtf-cfrg-hash-to-curve-16
|
// Same as sgn0_m_eq_2 in RFC 9380
|
||||||
isOdd: (x: Fp2) => {
|
isOdd: (x: Fp2) => {
|
||||||
const { re: x0, im: x1 } = Fp2.reim(x);
|
const { re: x0, im: x1 } = Fp2.reim(x);
|
||||||
const sign_0 = x0 % _2n;
|
const sign_0 = x0 % _2n;
|
||||||
@@ -780,8 +780,7 @@ const FP12_FROBENIUS_COEFFICIENTS = [
|
|||||||
|
|
||||||
// HashToCurve
|
// HashToCurve
|
||||||
|
|
||||||
// 3-isogeny map from E' to E
|
// 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-E.3
|
|
||||||
const isogenyMapG2 = isogenyMap(
|
const isogenyMapG2 = isogenyMap(
|
||||||
Fp2,
|
Fp2,
|
||||||
[
|
[
|
||||||
@@ -985,7 +984,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
|
|||||||
//
|
//
|
||||||
// Parameter definitions are in section 5.3 of the spec unless otherwise noted.
|
// Parameter definitions are in section 5.3 of the spec unless otherwise noted.
|
||||||
// Parameter values come from section 8.8.2 of the spec.
|
// Parameter values come from section 8.8.2 of the spec.
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2
|
// https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2
|
||||||
//
|
//
|
||||||
// Base field F is GF(p^m)
|
// Base field F is GF(p^m)
|
||||||
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
|
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
|
||||||
@@ -1040,7 +1039,7 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// To verify curve parameters, see pairing-friendly-curves spec:
|
// To verify curve parameters, see pairing-friendly-curves spec:
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-09
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-11
|
||||||
// Basic math is done over finite fields over p.
|
// Basic math is done over finite fields over p.
|
||||||
// More complicated math is done over polynominal extension fields.
|
// More complicated math is done over polynominal extension fields.
|
||||||
// To simplify calculations in Fp12, we construct extension tower:
|
// To simplify calculations in Fp12, we construct extension tower:
|
||||||
@@ -1108,7 +1107,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|||||||
},
|
},
|
||||||
// Clear cofactor of G1
|
// Clear cofactor of G1
|
||||||
// https://eprint.iacr.org/2019/403
|
// https://eprint.iacr.org/2019/403
|
||||||
clearCofactor: (c, point) => {
|
clearCofactor: (_c, point) => {
|
||||||
// return this.multiplyUnsafe(CURVE.h);
|
// return this.multiplyUnsafe(CURVE.h);
|
||||||
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
|
return point.multiplyUnsafe(bls12_381.params.x).add(point); // x*P + P
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const ed25519Defaults = {
|
|||||||
uvRatio,
|
uvRatio,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ed25519 = twistedEdwards(ed25519Defaults);
|
export const ed25519 = /* @__PURE__ */ twistedEdwards(ed25519Defaults);
|
||||||
|
|
||||||
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||||
if (ctx.length > 255) throw new Error('Context is too big');
|
if (ctx.length > 255) throw new Error('Context is too big');
|
||||||
@@ -135,8 +135,11 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
|
export const ed25519ctx = /* @__PURE__ */ twistedEdwards({
|
||||||
export const ed25519ph = twistedEdwards({
|
...ed25519Defaults,
|
||||||
|
domain: ed25519_domain,
|
||||||
|
});
|
||||||
|
export const ed25519ph = /* @__PURE__ */ twistedEdwards({
|
||||||
...ed25519Defaults,
|
...ed25519Defaults,
|
||||||
domain: ed25519_domain,
|
domain: ed25519_domain,
|
||||||
prehash: sha512,
|
prehash: sha512,
|
||||||
@@ -475,12 +478,12 @@ export const RistrettoPoint = /* @__PURE__ */ (() => {
|
|||||||
return RistPoint;
|
return RistPoint;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
|
// Hashing to ristretto255. https://www.rfc-editor.org/rfc/rfc9380#appendix-B
|
||||||
// Appendix B. Hashing to ristretto255
|
export const hashToRistretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
|
||||||
export const hash_to_ristretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
|
|
||||||
const d = options.DST;
|
const d = options.DST;
|
||||||
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
||||||
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
|
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
|
||||||
const P = RistPoint.hashToCurve(uniform_bytes);
|
const P = RistPoint.hashToCurve(uniform_bytes);
|
||||||
return P;
|
return P;
|
||||||
};
|
};
|
||||||
|
export const hash_to_ristretto255 = hashToRistretto255; // legacy
|
||||||
|
|||||||
284
src/ed448.ts
284
src/ed448.ts
@@ -1,14 +1,25 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { shake256 } from '@noble/hashes/sha3';
|
import { shake256 } from '@noble/hashes/sha3';
|
||||||
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
||||||
import { twistedEdwards } from './abstract/edwards.js';
|
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
|
||||||
import { mod, pow2, Field } from './abstract/modular.js';
|
import { mod, pow2, Field, isNegativeLE } from './abstract/modular.js';
|
||||||
import { montgomery } from './abstract/montgomery.js';
|
import { montgomery } from './abstract/montgomery.js';
|
||||||
import { createHasher } from './abstract/hash-to-curve.js';
|
import { createHasher, htfBasicOpts, expand_message_xof } from './abstract/hash-to-curve.js';
|
||||||
|
import {
|
||||||
|
bytesToHex,
|
||||||
|
bytesToNumberLE,
|
||||||
|
ensureBytes,
|
||||||
|
equalBytes,
|
||||||
|
Hex,
|
||||||
|
numberToBytesLE,
|
||||||
|
} from './abstract/utils.js';
|
||||||
|
import { AffinePoint } from './abstract/curve.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
||||||
* * X448 ECDH
|
* - X448 ECDH
|
||||||
|
* - Decaf cofactor elimination
|
||||||
|
* - Elligator hash-to-group / point indistinguishability
|
||||||
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
|
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -18,15 +29,16 @@ const ed448P = BigInt(
|
|||||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4), _11n = BigInt(11);
|
||||||
|
// prettier-ignore
|
||||||
|
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
|
||||||
|
|
||||||
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
||||||
// Used for efficient square root calculation.
|
// Used for efficient square root calculation.
|
||||||
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
|
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
|
||||||
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
||||||
const P = ed448P;
|
const P = ed448P;
|
||||||
// prettier-ignore
|
|
||||||
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _11n = BigInt(11);
|
|
||||||
// prettier-ignore
|
|
||||||
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
|
|
||||||
const b2 = (x * x * x) % P;
|
const b2 = (x * x * x) % P;
|
||||||
const b3 = (b2 * b2 * x) % P;
|
const b3 = (b2 * b2 * x) % P;
|
||||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||||
@@ -53,8 +65,29 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
|
||||||
|
// Uses algo from RFC8032 5.1.3.
|
||||||
|
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
||||||
|
const P = ed448P;
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc8032#section-5.2.3
|
||||||
|
// To compute the square root of (u/v), the first step is to compute the
|
||||||
|
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
||||||
|
// following trick, to use a single modular powering for both the
|
||||||
|
// inversion of v and the square root:
|
||||||
|
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
|
||||||
|
const u2v = mod(u * u * v, P); // u²v
|
||||||
|
const u3v = mod(u2v * u, P); // u³v
|
||||||
|
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
|
||||||
|
const root = ed448_pow_Pminus3div4(u5v3);
|
||||||
|
const x = mod(u3v * root, P);
|
||||||
|
// Verify that root is exists
|
||||||
|
const x2 = mod(x * x, P); // x²
|
||||||
|
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
|
||||||
|
// square root exists, and the decoding fails.
|
||||||
|
return { isValid: mod(x2 * v, P) === u, value: x };
|
||||||
|
}
|
||||||
|
|
||||||
const Fp = Field(ed448P, 456, true);
|
const Fp = Field(ed448P, 456, true);
|
||||||
const _4n = BigInt(4);
|
|
||||||
|
|
||||||
const ED448_DEF = {
|
const ED448_DEF = {
|
||||||
// Param: a
|
// Param: a
|
||||||
@@ -94,33 +127,12 @@ const ED448_DEF = {
|
|||||||
data
|
data
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
uvRatio,
|
||||||
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
|
|
||||||
// Uses algo from RFC8032 5.1.3.
|
|
||||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
|
||||||
const P = ed448P;
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
|
|
||||||
// To compute the square root of (u/v), the first step is to compute the
|
|
||||||
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
|
||||||
// following trick, to use a single modular powering for both the
|
|
||||||
// inversion of v and the square root:
|
|
||||||
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
|
|
||||||
const u2v = mod(u * u * v, P); // u²v
|
|
||||||
const u3v = mod(u2v * u, P); // u³v
|
|
||||||
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
|
|
||||||
const root = ed448_pow_Pminus3div4(u5v3);
|
|
||||||
const x = mod(u3v * root, P);
|
|
||||||
// Verify that root is exists
|
|
||||||
const x2 = mod(x * x, P); // x²
|
|
||||||
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
|
|
||||||
// square root exists, and the decoding fails.
|
|
||||||
return { isValid: mod(x2 * v, P) === u, value: x };
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ed448 = twistedEdwards(ED448_DEF);
|
export const ed448 = /* @__PURE__ */ twistedEdwards(ED448_DEF);
|
||||||
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
||||||
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
|
export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
|
||||||
|
|
||||||
export const x448 = /* @__PURE__ */ (() =>
|
export const x448 = /* @__PURE__ */ (() =>
|
||||||
montgomery({
|
montgomery({
|
||||||
@@ -245,3 +257,209 @@ const htf = /* @__PURE__ */ (() =>
|
|||||||
))();
|
))();
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
||||||
|
|
||||||
|
function assertDcfPoint(other: unknown) {
|
||||||
|
if (!(other instanceof DcfPoint)) throw new Error('DecafPoint expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1-d
|
||||||
|
const ONE_MINUS_D = BigInt('39082');
|
||||||
|
// 1-2d
|
||||||
|
const ONE_MINUS_TWO_D = BigInt('78163');
|
||||||
|
// √(-d)
|
||||||
|
const SQRT_MINUS_D = BigInt(
|
||||||
|
'98944233647732219769177004876929019128417576295529901074099889598043702116001257856802131563896515373927712232092845883226922417596214'
|
||||||
|
);
|
||||||
|
// 1 / √(-d)
|
||||||
|
const INVSQRT_MINUS_D = BigInt(
|
||||||
|
'315019913931389607337177038330951043522456072897266928557328499619017160722351061360252776265186336876723201881398623946864393857820716'
|
||||||
|
);
|
||||||
|
// Calculates 1/√(number)
|
||||||
|
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
||||||
|
|
||||||
|
const MAX_448B = BigInt(
|
||||||
|
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
||||||
|
);
|
||||||
|
const bytes448ToNumberLE = (bytes: Uint8Array) =>
|
||||||
|
ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
|
||||||
|
|
||||||
|
type ExtendedPoint = ExtPointType;
|
||||||
|
|
||||||
|
// Computes Elligator map for Decaf
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
|
||||||
|
function calcElligatorDecafMap(r0: bigint): ExtendedPoint {
|
||||||
|
const { d } = ed448.CURVE;
|
||||||
|
const P = ed448.CURVE.Fp.ORDER;
|
||||||
|
const mod = ed448.CURVE.Fp.create;
|
||||||
|
|
||||||
|
const r = mod(-(r0 * r0)); // 1
|
||||||
|
const u0 = mod(d * (r - _1n)); // 2
|
||||||
|
const u1 = mod((u0 + _1n) * (u0 - r)); // 3
|
||||||
|
|
||||||
|
const { isValid: was_square, value: v } = uvRatio(ONE_MINUS_TWO_D, mod((r + _1n) * u1)); // 4
|
||||||
|
|
||||||
|
let v_prime = v; // 5
|
||||||
|
if (!was_square) v_prime = mod(r0 * v);
|
||||||
|
|
||||||
|
let sgn = _1n; // 6
|
||||||
|
if (!was_square) sgn = mod(-_1n);
|
||||||
|
|
||||||
|
const s = mod(v_prime * (r + _1n)); // 7
|
||||||
|
let s_abs = s;
|
||||||
|
if (isNegativeLE(s, P)) s_abs = mod(-s);
|
||||||
|
|
||||||
|
const s2 = s * s;
|
||||||
|
const W0 = mod(s_abs * _2n); // 8
|
||||||
|
const W1 = mod(s2 + _1n); // 9
|
||||||
|
const W2 = mod(s2 - _1n); // 10
|
||||||
|
const W3 = mod(v_prime * s * (r - _1n) * ONE_MINUS_TWO_D + sgn); // 11
|
||||||
|
return new ed448.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each ed448/ExtendedPoint has 4 different equivalent points. This can be
|
||||||
|
* a source of bugs for protocols like ring signatures. Decaf was created to solve this.
|
||||||
|
* Decaf point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
|
||||||
|
* but it should work in its own namespace: do not combine those two.
|
||||||
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
||||||
|
*/
|
||||||
|
class DcfPoint {
|
||||||
|
static BASE: DcfPoint;
|
||||||
|
static ZERO: DcfPoint;
|
||||||
|
// Private property to discourage combining ExtendedPoint + DecafPoint
|
||||||
|
// Always use Decaf encoding/decoding instead.
|
||||||
|
constructor(private readonly ep: ExtendedPoint) {}
|
||||||
|
|
||||||
|
static fromAffine(ap: AffinePoint<bigint>) {
|
||||||
|
return new DcfPoint(ed448.ExtendedPoint.fromAffine(ap));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes uniform output of 112-byte hash function like shake256 and converts it to `DecafPoint`.
|
||||||
|
* The hash-to-group operation applies Elligator twice and adds the results.
|
||||||
|
* **Note:** this is one-way map, there is no conversion from point to hash.
|
||||||
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
|
||||||
|
* @param hex 112-byte output of a hash function
|
||||||
|
*/
|
||||||
|
static hashToCurve(hex: Hex): DcfPoint {
|
||||||
|
hex = ensureBytes('decafHash', hex, 112);
|
||||||
|
const r1 = bytes448ToNumberLE(hex.slice(0, 56));
|
||||||
|
const R1 = calcElligatorDecafMap(r1);
|
||||||
|
const r2 = bytes448ToNumberLE(hex.slice(56, 112));
|
||||||
|
const R2 = calcElligatorDecafMap(r2);
|
||||||
|
return new DcfPoint(R1.add(R2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts decaf-encoded string to decaf point.
|
||||||
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-decode-2
|
||||||
|
* @param hex Decaf-encoded 56 bytes. Not every 56-byte string is valid decaf encoding
|
||||||
|
*/
|
||||||
|
static fromHex(hex: Hex): DcfPoint {
|
||||||
|
hex = ensureBytes('decafHex', hex, 56);
|
||||||
|
const { d } = ed448.CURVE;
|
||||||
|
const P = ed448.CURVE.Fp.ORDER;
|
||||||
|
const mod = ed448.CURVE.Fp.create;
|
||||||
|
const emsg = 'DecafPoint.fromHex: the hex is not valid encoding of DecafPoint';
|
||||||
|
const s = bytes448ToNumberLE(hex);
|
||||||
|
|
||||||
|
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
||||||
|
// 2. Check that s is non-negative, or else abort
|
||||||
|
if (!equalBytes(numberToBytesLE(s, 56), hex) || isNegativeLE(s, P)) throw new Error(emsg);
|
||||||
|
|
||||||
|
const s2 = mod(s * s); // 1
|
||||||
|
const u1 = mod(_1n + s2); // 2
|
||||||
|
const u1sq = mod(u1 * u1);
|
||||||
|
const u2 = mod(u1sq - _4n * d * s2); // 3
|
||||||
|
|
||||||
|
const { isValid, value: invsqrt } = invertSqrt(mod(u2 * u1sq)); // 4
|
||||||
|
|
||||||
|
let u3 = mod((s + s) * invsqrt * u1 * SQRT_MINUS_D); // 5
|
||||||
|
if (isNegativeLE(u3, P)) u3 = mod(-u3);
|
||||||
|
|
||||||
|
const x = mod(u3 * invsqrt * u2 * INVSQRT_MINUS_D); // 6
|
||||||
|
const y = mod((_1n - s2) * invsqrt * u1); // 7
|
||||||
|
const t = mod(x * y); // 8
|
||||||
|
|
||||||
|
if (!isValid) throw new Error(emsg);
|
||||||
|
return new DcfPoint(new ed448.ExtendedPoint(x, y, _1n, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes decaf point to Uint8Array.
|
||||||
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-encode-2
|
||||||
|
*/
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
let { ex: x, ey: _y, ez: z, et: t } = this.ep;
|
||||||
|
const P = ed448.CURVE.Fp.ORDER;
|
||||||
|
const mod = ed448.CURVE.Fp.create;
|
||||||
|
|
||||||
|
const u1 = mod(mod(x + t) * mod(x - t)); // 1
|
||||||
|
const x2 = mod(x * x);
|
||||||
|
const { value: invsqrt } = invertSqrt(mod(u1 * ONE_MINUS_D * x2)); // 2
|
||||||
|
|
||||||
|
let ratio = mod(invsqrt * u1 * SQRT_MINUS_D); // 3
|
||||||
|
if (isNegativeLE(ratio, P)) ratio = mod(-ratio);
|
||||||
|
|
||||||
|
const u2 = mod(INVSQRT_MINUS_D * ratio * z - t); // 4
|
||||||
|
|
||||||
|
let s = mod(ONE_MINUS_D * invsqrt * x * u2); // 5
|
||||||
|
if (isNegativeLE(s, P)) s = mod(-s);
|
||||||
|
|
||||||
|
return numberToBytesLE(s, 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
toHex(): string {
|
||||||
|
return bytesToHex(this.toRawBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare one point to another.
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-equals-2
|
||||||
|
equals(other: DcfPoint): boolean {
|
||||||
|
assertDcfPoint(other);
|
||||||
|
const { ex: X1, ey: Y1 } = this.ep;
|
||||||
|
const { ex: X2, ey: Y2 } = other.ep;
|
||||||
|
const mod = ed448.CURVE.Fp.create;
|
||||||
|
// (x1 * y2 == y1 * x2)
|
||||||
|
return mod(X1 * Y2) === mod(Y1 * X2);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(other: DcfPoint): DcfPoint {
|
||||||
|
assertDcfPoint(other);
|
||||||
|
return new DcfPoint(this.ep.add(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(other: DcfPoint): DcfPoint {
|
||||||
|
assertDcfPoint(other);
|
||||||
|
return new DcfPoint(this.ep.subtract(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiply(scalar: bigint): DcfPoint {
|
||||||
|
return new DcfPoint(this.ep.multiply(scalar));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplyUnsafe(scalar: bigint): DcfPoint {
|
||||||
|
return new DcfPoint(this.ep.multiplyUnsafe(scalar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const DecafPoint = /* @__PURE__ */ (() => {
|
||||||
|
// decaf448 base point is ed448 base x 2
|
||||||
|
// https://github.com/dalek-cryptography/curve25519-dalek/blob/59837c6ecff02b77b9d5ff84dbc239d0cf33ef90/vendor/ristretto.sage#L699
|
||||||
|
if (!DcfPoint.BASE) DcfPoint.BASE = new DcfPoint(ed448.ExtendedPoint.BASE).multiply(_2n);
|
||||||
|
if (!DcfPoint.ZERO) DcfPoint.ZERO = new DcfPoint(ed448.ExtendedPoint.ZERO);
|
||||||
|
return DcfPoint;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Hashing to decaf448. https://www.rfc-editor.org/rfc/rfc9380#appendix-C
|
||||||
|
export const hashToDecaf448 = (msg: Uint8Array, options: htfBasicOpts) => {
|
||||||
|
const d = options.DST;
|
||||||
|
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
||||||
|
const uniform_bytes = expand_message_xof(msg, DST, 112, 224, shake256);
|
||||||
|
const P = DcfPoint.hashToCurve(uniform_bytes);
|
||||||
|
return P;
|
||||||
|
};
|
||||||
|
export const hash_to_decaf448 = hashToDecaf448; // legacy
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Field } from './abstract/modular.js';
|
|||||||
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
|
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const jubjub = twistedEdwards({
|
export const jubjub = /* @__PURE__ */ twistedEdwards({
|
||||||
// Params: a, d
|
// Params: a, d
|
||||||
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
||||||
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ const B_192_40 = '40'.padEnd(192, '0');
|
|||||||
const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
|
const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...]
|
||||||
|
|
||||||
const getPubKey = (priv) => bls.getPublicKey(priv);
|
const getPubKey = (priv) => bls.getPublicKey(priv);
|
||||||
|
function replaceZeroPoint(item) {
|
||||||
|
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
const ones = '1000000000000000000000000000000000000000000000000000000000000001';
|
||||||
|
return item === zeros ? ones : item;
|
||||||
|
}
|
||||||
|
|
||||||
function equal(a, b, comment) {
|
function equal(a, b, comment) {
|
||||||
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
||||||
@@ -1234,6 +1239,7 @@ describe('verify()', () => {
|
|||||||
should('verify multi-signature as simple signature', () => {
|
should('verify multi-signature as simple signature', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
|
fc.property(FC_MSG, FC_BIGINT_5, (message, privateKeys) => {
|
||||||
|
message = replaceZeroPoint(message);
|
||||||
const publicKey = privateKeys.map(getPubKey);
|
const publicKey = privateKeys.map(getPubKey);
|
||||||
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
|
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
|
||||||
const aggregatedSignature = bls.aggregateSignatures(signatures);
|
const aggregatedSignature = bls.aggregateSignatures(signatures);
|
||||||
@@ -1245,6 +1251,7 @@ describe('verify()', () => {
|
|||||||
should('not verify wrong multi-signature as simple signature', () => {
|
should('not verify wrong multi-signature as simple signature', () => {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
|
fc.property(FC_MSG, FC_MSG, FC_BIGINT_5, (message, wrongMessage, privateKeys) => {
|
||||||
|
message = replaceZeroPoint(message);
|
||||||
const publicKey = privateKeys.map(getPubKey);
|
const publicKey = privateKeys.map(getPubKey);
|
||||||
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
|
const signatures = privateKeys.map((privateKey) => bls.sign(message, privateKey));
|
||||||
const aggregatedSignature = bls.aggregateSignatures(signatures);
|
const aggregatedSignature = bls.aggregateSignatures(signatures);
|
||||||
|
|||||||
117
test/ed448-addons.test.js
Normal file
117
test/ed448-addons.test.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { bytesToHex as hex, hexToBytes } from '@noble/hashes/utils';
|
||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { describe, should } from 'micro-should';
|
||||||
|
import { bytesToNumberLE } from '../esm/abstract/utils.js';
|
||||||
|
import { ed448, DecafPoint } from '../esm/ed448.js';
|
||||||
|
|
||||||
|
describe('decaf448', () => {
|
||||||
|
should('follow the byte encodings of small multiples', () => {
|
||||||
|
const encodingsOfSmallMultiples = [
|
||||||
|
// This is the identity point
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
// This is the basepoint
|
||||||
|
'6666666666666666666666666666666666666666666666666666666633333333333333333333333333333333333333333333333333333333',
|
||||||
|
// These are small multiples of the basepoint
|
||||||
|
'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75',
|
||||||
|
'a0c09bf2ba7208fda0f4bfe3d0f5b29a543012306d43831b5adc6fe7f8596fa308763db15468323b11cf6e4aeb8c18fe44678f44545a69bc',
|
||||||
|
'b46f1836aa287c0a5a5653f0ec5ef9e903f436e21c1570c29ad9e5f596da97eeaf17150ae30bcb3174d04bc2d712c8c7789d7cb4fda138f4',
|
||||||
|
'1c5bbecf4741dfaae79db72dface00eaaac502c2060934b6eaaeca6a20bd3da9e0be8777f7d02033d1b15884232281a41fc7f80eed04af5e',
|
||||||
|
'86ff0182d40f7f9edb7862515821bd67bfd6165a3c44de95d7df79b8779ccf6460e3c68b70c16aaa280f2d7b3f22d745b97a89906cfc476c',
|
||||||
|
'502bcb6842eb06f0e49032bae87c554c031d6d4d2d7694efbf9c468d48220c50f8ca28843364d70cee92d6fe246e61448f9db9808b3b2408',
|
||||||
|
'0c9810f1e2ebd389caa789374d78007974ef4d17227316f40e578b336827da3f6b482a4794eb6a3975b971b5e1388f52e91ea2f1bcb0f912',
|
||||||
|
'20d41d85a18d5657a29640321563bbd04c2ffbd0a37a7ba43a4f7d263ce26faf4e1f74f9f4b590c69229ae571fe37fa639b5b8eb48bd9a55',
|
||||||
|
'e6b4b8f408c7010d0601e7eda0c309a1a42720d6d06b5759fdc4e1efe22d076d6c44d42f508d67be462914d28b8edce32e7094305164af17',
|
||||||
|
'be88bbb86c59c13d8e9d09ab98105f69c2d1dd134dbcd3b0863658f53159db64c0e139d180f3c89b8296d0ae324419c06fa87fc7daaf34c1',
|
||||||
|
'a456f9369769e8f08902124a0314c7a06537a06e32411f4f93415950a17badfa7442b6217434a3a05ef45be5f10bd7b2ef8ea00c431edec5',
|
||||||
|
'186e452c4466aa4383b4c00210d52e7922dbf9771e8b47e229a9b7b73c8d10fd7ef0b6e41530f91f24a3ed9ab71fa38b98b2fe4746d51d68',
|
||||||
|
'4ae7fdcae9453f195a8ead5cbe1a7b9699673b52c40ab27927464887be53237f7f3a21b938d40d0ec9e15b1d5130b13ffed81373a53e2b43',
|
||||||
|
'841981c3bfeec3f60cfeca75d9d8dc17f46cf0106f2422b59aec580a58f342272e3a5e575a055ddb051390c54c24c6ecb1e0aceb075f6056',
|
||||||
|
];
|
||||||
|
let B = DecafPoint.BASE;
|
||||||
|
let P = DecafPoint.ZERO;
|
||||||
|
for (const encoded of encodingsOfSmallMultiples) {
|
||||||
|
deepStrictEqual(P.toHex(), encoded);
|
||||||
|
deepStrictEqual(DecafPoint.fromHex(encoded).toHex(), encoded);
|
||||||
|
P = P.add(B);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('not convert bad bytes encoding', () => {
|
||||||
|
const badEncodings = [
|
||||||
|
// These are all bad because they're non-canonical field encodings.
|
||||||
|
'8e24f838059ee9fef1e209126defe53dcd74ef9b6304601c6966099effffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'86fcc7212bd4a0b980928666dc28c444a605ef38e09fb569e28d4443ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'866d54bd4c4ff41a55d4eefdbeca73cbd653c7bd3135b383708ec0bdffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'4a380ccdab9c86364a89e77a464d64f9157538cfdfa686adc0d5ece4ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'f22d9d4c945dd44d11e0b1d3d3d358d959b4844d83b08c44e659d79fffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'8cdffc681aa99e9c818c8ef4c3808b58e86acdef1ab68c8477af185bffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'0e1c12ac7b5920effbd044e897c57634e2d05b5c27f8fa3df8a086a1ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
// These are all bad because they're negative field elements.
|
||||||
|
'15141bd2121837ef71a0016bd11be757507221c26542244f23806f3fd3496b7d4c36826276f3bf5deea2c60c4fa4cec69946876da497e795',
|
||||||
|
'455d380238434ab740a56267f4f46b7d2eb2dd8ee905e51d7b0ae8a6cb2bae501e67df34ab21fa45946068c9f233939b1d9521a998b7cb93',
|
||||||
|
'810b1d8e8bf3a9c023294bbfd3d905a97531709bdc0f42390feedd7010f77e98686d400c9c86ed250ceecd9de0a18888ffecda0f4ea1c60d',
|
||||||
|
'd3af9cc41be0e5de83c0c6273bedcb9351970110044a9a41c7b9b2267cdb9d7bf4dc9c2fdb8bed32878184604f1d9944305a8df4274ce301',
|
||||||
|
'9312bcab09009e4330ff89c4bc1e9e000d863efc3c863d3b6c507a40fd2cdefde1bf0892b4b5ed9780b91ed1398fb4a7344c605aa5efda74',
|
||||||
|
'53d11bce9e62a29d63ed82ae93761bdd76e38c21e2822d6ebee5eb1c5b8a03eaf9df749e2490eda9d8ac27d1f71150de93668074d18d1c3a',
|
||||||
|
'697c1aed3cd8858515d4be8ac158b229fe184d79cb2b06e49210a6f3a7cd537bcd9bd390d96c4ab6a4406da5d93640726285370cfa95df80',
|
||||||
|
// These are all bad because they give a nonsquare x².
|
||||||
|
'58ad48715c9a102569b68b88362a4b0645781f5a19eb7e59c6a4686fd0f0750ff42e3d7af1ab38c29d69b670f31258919c9fdbf6093d06c0',
|
||||||
|
'8ca37ee2b15693f06e910cf43c4e32f1d5551dda8b1e48cb6ddd55e440dbc7b296b601919a4e4069f59239ca247ff693f7daa42f086122b1',
|
||||||
|
'982c0ec7f43d9f97c0a74b36db0abd9ca6bfb98123a90782787242c8a523cdc76df14a910d54471127e7662a1059201f902940cd39d57af5',
|
||||||
|
'baa9ab82d07ca282b968a911a6c3728d74bf2fe258901925787f03ee4be7e3cb6684fd1bcfe5071a9a974ad249a4aaa8ca81264216c68574',
|
||||||
|
'2ed9ffe2ded67a372b181ac524996402c42970629db03f5e8636cbaf6074b523d154a7a8c4472c4c353ab88cd6fec7da7780834cc5bd5242',
|
||||||
|
'f063769e4241e76d815800e4933a3a144327a30ec40758ad3723a788388399f7b3f5d45b6351eb8eddefda7d5bff4ee920d338a8b89d8b63',
|
||||||
|
'5a0104f1f55d152ceb68bc138182499891d90ee8f09b40038ccc1e07cb621fd462f781d045732a4f0bda73f0b2acf94355424ff0388d4b9c',
|
||||||
|
];
|
||||||
|
for (const badBytes of badEncodings) {
|
||||||
|
const b = hexToBytes(badBytes);
|
||||||
|
throws(() => DecafPoint.fromHex(b), badBytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('create right points from uniform hash', () => {
|
||||||
|
const hashes = [
|
||||||
|
'cbb8c991fd2f0b7e1913462d6463e4fd2ce4ccdd28274dc2ca1f4165d5ee6cdccea57be3416e166fd06718a31af45a2f8e987e301be59ae6673e963001dbbda80df47014a21a26d6c7eb4ebe0312aa6fffb8d1b26bc62ca40ed51f8057a635a02c2b8c83f48fa6a2d70f58a1185902c0',
|
||||||
|
'b6d8da654b13c3101d6634a231569e6b85961c3f4b460a08ac4a5857069576b64428676584baa45b97701be6d0b0ba18ac28d443403b45699ea0fbd1164f5893d39ad8f29e48e399aec5902508ea95e33bc1e9e4620489d684eb5c26bc1ad1e09aba61fabc2cdfee0b6b6862ffc8e55a',
|
||||||
|
'36a69976c3e5d74e4904776993cbac27d10f25f5626dd45c51d15dcf7b3e6a5446a6649ec912a56895d6baa9dc395ce9e34b868d9fb2c1fc72eb6495702ea4f446c9b7a188a4e0826b1506b0747a6709f37988ff1aeb5e3788d5076ccbb01a4bc6623c92ff147a1e21b29cc3fdd0e0f4',
|
||||||
|
'd5938acbba432ecd5617c555a6a777734494f176259bff9dab844c81aadcf8f7abd1a9001d89c7008c1957272c1786a4293bb0ee7cb37cf3988e2513b14e1b75249a5343643d3c5e5545a0c1a2a4d3c685927c38bc5e5879d68745464e2589e000b31301f1dfb7471a4f1300d6fd0f99',
|
||||||
|
'4dec58199a35f531a5f0a9f71a53376d7b4bdd6bbd2904234a8ea65bbacbce2a542291378157a8f4be7b6a092672a34d85e473b26ccfbd4cdc6739783dc3f4f6ee3537b7aed81df898c7ea0ae89a15b5559596c2a5eeacf8b2b362f3db2940e3798b63203cae77c4683ebaed71533e51',
|
||||||
|
'df2aa1536abb4acab26efa538ce07fd7bca921b13e17bc5ebcba7d1b6b733deda1d04c220f6b5ab35c61b6bcb15808251cab909a01465b8ae3fc770850c66246d5a9eae9e2877e0826e2b8dc1bc08009590bc6778a84e919fbd28e02a0f9c49b48dc689eb5d5d922dc01469968ee81b5',
|
||||||
|
'e9fb440282e07145f1f7f5ecf3c273212cd3d26b836b41b02f108431488e5e84bd15f2418b3d92a3380dd66a374645c2a995976a015632d36a6c2189f202fc766e1c82f50ad9189be190a1f0e8f9b9e69c9c18cc98fdd885608f68bf0fdedd7b894081a63f70016a8abf04953affbefa',
|
||||||
|
];
|
||||||
|
const encodedHashToPoints = [
|
||||||
|
'0c709c9607dbb01c94513358745b7c23953d03b33e39c7234e268d1d6e24f34014ccbc2216b965dd231d5327e591dc3c0e8844ccfd568848',
|
||||||
|
'76ab794e28ff1224c727fa1016bf7f1d329260b7218a39aea2fdb17d8bd9119017b093d641cedf74328c327184dc6f2a64bd90eddccfcdab',
|
||||||
|
'c8d7ac384143500e50890a1c25d643343accce584caf2544f9249b2bf4a6921082be0e7f3669bb5ec24535e6c45621e1f6dec676edd8b664',
|
||||||
|
'62beffc6b8ee11ccd79dbaac8f0252c750eb052b192f41eeecb12f2979713b563caf7d22588eca5e80995241ef963e7ad7cb7962f343a973',
|
||||||
|
'f4ccb31d263731ab88bed634304956d2603174c66da38742053fa37dd902346c3862155d68db63be87439e3d68758ad7268e239d39c4fd3b',
|
||||||
|
'7e79b00e8e0a76a67c0040f62713b8b8c6d6f05e9c6d02592e8a22ea896f5deacc7c7df5ed42beae6fedb9000285b482aa504e279fd49c32',
|
||||||
|
'20b171cb16be977f15e013b9752cf86c54c631c4fc8cbf7c03c4d3ac9b8e8640e7b0e9300b987fe0ab5044669314f6ed1650ae037db853f1',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < hashes.length; i++) {
|
||||||
|
const hash = hexToBytes(hashes[i]);
|
||||||
|
const point = DecafPoint.hashToCurve(hash);
|
||||||
|
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('have proper equality testing', () => {
|
||||||
|
const MAX_448B = BigInt(
|
||||||
|
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
||||||
|
);
|
||||||
|
const bytes448ToNumberLE = (bytes) => ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
|
||||||
|
|
||||||
|
const priv = new Uint8Array([
|
||||||
|
23, 211, 149, 179, 209, 108, 78, 37, 229, 45, 122, 220, 85, 38, 192, 182, 96, 40, 168, 63,
|
||||||
|
175, 194, 73, 202, 14, 175, 78, 15, 117, 175, 40, 32, 218, 221, 151, 58, 158, 91, 250, 141,
|
||||||
|
18, 175, 191, 119, 152, 124, 223, 101, 54, 218, 76, 158, 43, 112, 151, 32,
|
||||||
|
]);
|
||||||
|
const pub = DecafPoint.BASE.multiply(bytes448ToNumberLE(priv));
|
||||||
|
deepStrictEqual(pub.equals(DecafPoint.ZERO), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { should } from 'micro-should';
|
|||||||
import './basic.test.js';
|
import './basic.test.js';
|
||||||
import './nist.test.js';
|
import './nist.test.js';
|
||||||
import './ed448.test.js';
|
import './ed448.test.js';
|
||||||
|
import './ed448-addons.test.js';
|
||||||
import './ed25519.test.js';
|
import './ed25519.test.js';
|
||||||
import './ed25519-addons.test.js';
|
import './ed25519-addons.test.js';
|
||||||
import './secp256k1.test.js';
|
import './secp256k1.test.js';
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ should('poseidonperm_x5_255_3', () => {
|
|||||||
t,
|
t,
|
||||||
roundsFull: 8,
|
roundsFull: 8,
|
||||||
roundsPartial: 57,
|
roundsPartial: 57,
|
||||||
|
sboxPower: 5,
|
||||||
mds,
|
mds,
|
||||||
roundConstants,
|
roundConstants,
|
||||||
});
|
});
|
||||||
@@ -229,6 +230,7 @@ should('poseidonperm_x5_255_5', () => {
|
|||||||
t,
|
t,
|
||||||
roundsFull: 8,
|
roundsFull: 8,
|
||||||
roundsPartial: 60,
|
roundsPartial: 60,
|
||||||
|
sboxPower: 5,
|
||||||
mds,
|
mds,
|
||||||
roundConstants,
|
roundConstants,
|
||||||
});
|
});
|
||||||
@@ -280,6 +282,7 @@ should('poseidonperm_x5_254_3', () => {
|
|||||||
t,
|
t,
|
||||||
roundsFull: 8,
|
roundsFull: 8,
|
||||||
roundsPartial: 57,
|
roundsPartial: 57,
|
||||||
|
sboxPower: 5,
|
||||||
mds,
|
mds,
|
||||||
roundConstants,
|
roundConstants,
|
||||||
});
|
});
|
||||||
@@ -347,6 +350,7 @@ should('poseidonperm_x5_254_5', () => {
|
|||||||
t,
|
t,
|
||||||
roundsFull: 8,
|
roundsFull: 8,
|
||||||
roundsPartial: 60,
|
roundsPartial: 60,
|
||||||
|
sboxPower: 5,
|
||||||
mds,
|
mds,
|
||||||
roundConstants,
|
roundConstants,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -268,6 +268,33 @@ describe('secp256k1', () => {
|
|||||||
deepStrictEqual(sign(ent5), e.extraEntropyMax);
|
deepStrictEqual(sign(ent5), e.extraEntropyMax);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
should('handle one byte {extraData}', () => {
|
||||||
|
const extraEntropy = '01';
|
||||||
|
const privKey = hexToBytes(
|
||||||
|
'0101010101010101010101010101010101010101010101010101010101010101'
|
||||||
|
);
|
||||||
|
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
|
||||||
|
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
|
||||||
|
deepStrictEqual(
|
||||||
|
res,
|
||||||
|
'a250ec23a54bfdecf0e924cbf484077c5044410f915cdba86731cb2e4e925aaa5b1e4e3553d88be2c48a9a0d8d849ce2cc5720d25b2f97473e02f2550abe9545'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('handle 48 bytes {extraData}', () => {
|
||||||
|
const extraEntropy =
|
||||||
|
'000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000001';
|
||||||
|
const privKey = hexToBytes(
|
||||||
|
'0101010101010101010101010101010101010101010101010101010101010101'
|
||||||
|
);
|
||||||
|
const msg = 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b';
|
||||||
|
const res = secp.sign(msg, privKey, { extraEntropy }).toCompactHex();
|
||||||
|
deepStrictEqual(
|
||||||
|
res,
|
||||||
|
'2bdf40f42ac0e42ee12750d03bb12b75306dae58eb3c961c5a80d78efae93e595295b66e8eb28f1eb046bb129a976340312159ec0c20b97342667572e4a8379a'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('verify()', () => {
|
describe('verify()', () => {
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"outDir": "esm",
|
||||||
"outDir": "esm",
|
"target": "es2020",
|
||||||
"target": "es2020",
|
"module": "es6",
|
||||||
"module": "es6",
|
"moduleResolution": "node16",
|
||||||
"moduleResolution": "node16",
|
"baseUrl": ".",
|
||||||
"noUnusedLocals": true,
|
"paths": {
|
||||||
"sourceMap": true,
|
"@noble/hashes/crypto": ["src/crypto"]
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@noble/hashes/crypto": [ "src/crypto" ]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"sourceMap": true,
|
||||||
"exclude": [
|
"strict": true,
|
||||||
"node_modules",
|
"allowSyntheticDefaultImports": false,
|
||||||
"lib",
|
"allowUnreachableCode": false,
|
||||||
],
|
"esModuleInterop": false,
|
||||||
}
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUncheckedIndexedAccess": false,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "lib"]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"outDir": ".",
|
||||||
"declaration": true,
|
"target": "es2020",
|
||||||
"declarationMap": true,
|
"lib": ["es2020"], // Set explicitly to remove DOM
|
||||||
"sourceMap": true,
|
"module": "commonjs",
|
||||||
"outDir": ".",
|
"moduleResolution": "node",
|
||||||
"target": "es2020",
|
"baseUrl": ".",
|
||||||
"lib": ["es2020"], // Set explicitly to remove DOM
|
"sourceMap": true,
|
||||||
"module": "commonjs",
|
"declaration": true,
|
||||||
"moduleResolution": "node",
|
"declarationMap": true,
|
||||||
"noUnusedLocals": true,
|
"strict": true,
|
||||||
"baseUrl": ".",
|
"allowSyntheticDefaultImports": false,
|
||||||
},
|
"allowUnreachableCode": false,
|
||||||
"include": ["src"],
|
"esModuleInterop": false,
|
||||||
"exclude": [
|
"noFallthroughCasesInSwitch": true,
|
||||||
"node_modules",
|
"noImplicitReturns": true,
|
||||||
"*.d.ts"
|
"noUncheckedIndexedAccess": false,
|
||||||
],
|
"noUnusedLocals": true,
|
||||||
}
|
"noUnusedParameters": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "*.d.ts"]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user