7 Commits
0.5.0 ... 0.5.1

Author SHA1 Message Date
Paul Miller
2bd5e9ac16 Release 0.5.1. 2022-12-31 10:31:10 +01:00
Paul Miller
6890c26091 Fix readme toc 2022-12-31 10:29:25 +01:00
Paul Miller
a15e3a93a9 Docs 2022-12-31 10:00:29 +01:00
Paul Miller
910c508da9 hash-to-curve: elligator in 25519, 448. Stark: adjust type 2022-12-31 07:51:29 +01:00
Paul Miller
12da04a2bb Improve modular math 2022-12-31 07:49:42 +01:00
Paul Miller
cc2c84f040 Improve field tests 2022-12-31 07:49:09 +01:00
Paul Miller
5d42549acc hash-to-curve: add xmd/xof support 2022-12-31 07:48:13 +01:00
18 changed files with 714 additions and 705 deletions

View File

@@ -7,8 +7,8 @@ Minimal, auditable JS implementation of elliptic curve cryptography.
- [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/) - [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
for encoding or hashing an arbitrary string to a point on an elliptic curve for encoding or hashing an arbitrary string to a point on an elliptic curve
- Auditable, [fast](#speed) - Auditable, [fast](#speed)
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
- 🔍 Unique tests ensure correctness. Wycheproof vectors included - 🔍 Unique tests ensure correctness. Wycheproof vectors included
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
There are two parts of the package: There are two parts of the package:
@@ -84,11 +84,12 @@ To define a custom curve, check out API below.
## API ## API
- [Overview](#overview) - [Overview](#overview)
- [abstract/edwards: Twisted Edwards curve](#abstract/edwards-twisted-edwards-curve) - [abstract/edwards: Twisted Edwards curve](#abstractedwards-twisted-edwards-curve)
- [abstract/montgomery: Montgomery curve](#abstract/montgomery-montgomery-curve) - [abstract/montgomery: Montgomery curve](#abstractmontgomery-montgomery-curve)
- [abstract/weierstrass: Short Weierstrass curve](#abstract/weierstrass-short-weierstrass-curve) - [abstract/weierstrass: Short Weierstrass curve](#abstractweierstrass-short-weierstrass-curve)
- [abstract/modular](#abstract/modular) - [abstract/hash-to-curve: Hashing strings to curve points](#abstracthash-to-curve-hashing-strings-to-curve-points)
- [abstract/utils](#abstract/utils) - [abstract/modular](#abstractmodular)
- [abstract/utils](#abstractutils)
### Overview ### Overview
@@ -324,20 +325,70 @@ export type CurveFn = {
}; };
``` ```
### abstract/hash-to-curve: Hashing strings to curve points
The module allows to hash arbitrary strings to elliptic curve points.
- `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..
```ts
function expand_message_xmd(
msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash
): Uint8Array;
function expand_message_xof(
msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash
): Uint8Array;
```
- `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.
* `msg` a byte string containing the message to hash
* `count` the number of elements of F to output
* `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
* Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
```ts
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
type htfOpts = {
// DST: a domain separation tag
// defined in section 2.2.5
DST: string;
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: bigint;
// m: the extension degree of F, m >= 1
// where F is a finite field of characteristic p and order q = p^m
m: number;
// k: the target security level for the suite in bits
// defined in section 5.1
k: number;
// option to use a message that has already been processed by
// expand_message_xmd
expand?: 'xmd' | 'xof';
// Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash;
};
```
### abstract/modular ### abstract/modular
Modular arithmetics utilities. Modular arithmetics utilities.
```typescript ```typescript
import { mod, invert, div, invertBatch, sqrt, Fp } from '@noble/curves/abstract/modular'; import { Fp, mod, invert, div, invertBatch, sqrt } from '@noble/curves/abstract/modular';
const fp = Fp(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n);
fp.pow(481n, 11024858120n);
// Generic non-FP utils are also available
mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10 mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division
invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
sqrt(21n, 73n); // √21 mod 73; square root sqrt(21n, 73n); // √21 mod 73; square root
const fp = Fp(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n);
fp.pow(481n, 11024858120n);
``` ```
### abstract/utils ### abstract/utils

View File

@@ -96,6 +96,10 @@ export const CURVES = {
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery), old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg), secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
}, },
hashToCurve: {
samples: 500,
noble: () => secp256k1.Point.hashToCurve('abcd'),
},
}, },
ed25519: { ed25519: {
data: () => { data: () => {
@@ -124,6 +128,10 @@ export const CURVES = {
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub), old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub), noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
}, },
hashToCurve: {
samples: 500,
noble: () => ed25519.Point.hashToCurve('abcd'),
},
}, },
ed448: { ed448: {
data: () => { data: () => {
@@ -145,6 +153,10 @@ export const CURVES = {
samples: 500, samples: 500,
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub), noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
}, },
hashToCurve: {
samples: 500,
noble: () => ed448.Point.hashToCurve('abcd'),
},
}, },
nist: { nist: {
data: () => { data: () => {
@@ -168,6 +180,12 @@ export const CURVES = {
P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub), P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub),
P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub), P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub),
}, },
hashToCurve: {
samples: 500,
P256: () => P256.Point.hashToCurve('abcd'),
P384: () => P384.Point.hashToCurve('abcd'),
P521: () => P521.Point.hashToCurve('abcd'),
},
}, },
stark: { stark: {
data: () => { data: () => {

View File

@@ -12,13 +12,15 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"micro-bmark": "0.2.0" "micro-bmark": "0.2.1"
}, },
"dependencies": { "dependencies": {
"@noble/bls12-381": "^1.4.0", "@noble/bls12-381": "^1.4.0",
"@noble/ed25519": "^1.7.1", "@noble/ed25519": "^1.7.1",
"@noble/hashes": "^1.1.5", "@noble/hashes": "^1.1.5",
"@noble/secp256k1": "^1.7.0", "@noble/secp256k1": "^1.7.0",
"@starkware-industries/starkware-crypto-utils": "^0.0.2" "@starkware-industries/starkware-crypto-utils": "^0.0.2",
"calculate-correlation": "^1.2.3",
"elliptic": "^6.5.4"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.5.0", "version": "0.5.1",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"lib" "lib"

View File

@@ -17,10 +17,11 @@ export type htfOpts = {
k: number; k: number;
// option to use a message that has already been processed by // option to use a message that has already been processed by
// expand_message_xmd // expand_message_xmd
expand: boolean; expand?: 'xmd' | 'xof';
// Hash functions for: expand_message_xmd is appropriate for use with a // Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others. // wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247 // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
// TODO: verify that hash is shake if expand==='xof' via types
hash: CHash; hash: CHash;
}; };
@@ -29,7 +30,8 @@ export function validateHTFOpts(opts: htfOpts) {
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p'); if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m'); if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k'); if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
if (typeof opts.expand !== 'boolean') throw new Error('Invalid htf/expand'); if (opts.expand !== 'xmd' && opts.expand !== 'xof' && opts.expand !== undefined)
throw new Error('Invalid htf/expand');
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
throw new Error('Invalid htf/hash function'); throw new Error('Invalid htf/hash function');
} }
@@ -101,13 +103,40 @@ export function expand_message_xmd(
return pseudo_random_bytes.slice(0, lenInBytes); return pseudo_random_bytes.slice(0, lenInBytes);
} }
// hashes arbitrary-length byte strings to a list of one or more elements of a finite field F export function expand_message_xof(
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 msg: Uint8Array,
// Inputs: DST: Uint8Array,
// msg - a byte string containing the message to hash. lenInBytes: number,
// count - the number of elements of F to output. k: number,
// Outputs: H: CHash
// [u_0, ..., u_(count - 1)], a list of field elements. ): Uint8Array {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
if (DST.length > 255) {
const dkLen = Math.ceil((2 * k) / 8);
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
}
if (lenInBytes > 65535 || DST.length > 255)
throw new Error('expand_message_xof: invalid lenInBytes');
return (
H.create({ dkLen: lenInBytes })
.update(msg)
.update(i2osp(lenInBytes, 2))
// 2. DST_prime = DST || I2OSP(len(DST), 1)
.update(DST)
.update(i2osp(DST.length, 1))
.digest()
);
}
/**
* 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}`
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] { export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the // if options is provided but incomplete, fill any missing fields with the
// value in hftDefaults (ie hash to G2). // value in hftDefaults (ie hash to G2).
@@ -116,8 +145,10 @@ export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts):
const len_in_bytes = count * options.m * L; const len_in_bytes = count * options.m * L;
const DST = stringToBytes(options.DST); const DST = stringToBytes(options.DST);
let pseudo_random_bytes = msg; let pseudo_random_bytes = msg;
if (options.expand) { if (options.expand === 'xmd') {
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash); pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
} else if (options.expand === 'xof') {
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash);
} }
const u = new Array(count); const u = new Array(count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {

View File

@@ -1,10 +1,11 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// TODO: remove circular imports
import * as utils from './utils.js'; import * as utils from './utils.js';
// Utilities for modular arithmetics and finite fields // Utilities for modular arithmetics and finite fields
// prettier-ignore // prettier-ignore
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3); const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
// prettier-ignore // prettier-ignore
const _4n = BigInt(4), _5n = BigInt(5), _7n = BigInt(7), _8n = BigInt(8); const _4n = BigInt(4), _5n = BigInt(5), _8n = BigInt(8);
// prettier-ignore // prettier-ignore
const _9n = BigInt(9), _16n = BigInt(16); const _9n = BigInt(9), _16n = BigInt(16);
@@ -66,26 +67,68 @@ export function invert(number: bigint, modulo: bigint): bigint {
return mod(x, modulo); return mod(x, modulo);
} }
/** // Tonelli-Shanks algorithm
* Calculates Legendre symbol (a | p), which denotes the value of a^((p-1)/2) (mod p). // https://eprint.iacr.org/2012/685.pdf (page 12)
* * (a | p) ≡ 1 if a is a square (mod p) export function tonelliShanks(P: bigint) {
* * (a | p) ≡ -1 if a is not a square (mod p) // Legendre constant: used to calculate Legendre symbol (a | p),
* * (a | p) ≡ 0 if a ≡ 0 (mod p) // which denotes the value of a^((p-1)/2) (mod p).
*/ // (a | p) ≡ 1 if a is a square (mod p)
export function legendre(num: bigint, fieldPrime: bigint): bigint { // (a | p) ≡ -1 if a is not a square (mod p)
return pow(num, (fieldPrime - _1n) / _2n, fieldPrime); // (a | p) ≡ 0 if a ≡ 0 (mod p)
const legendreC = (P - _1n) / _2n;
let Q: bigint, S: number, Z: bigint;
// Step 1: By factoring out powers of 2 from p - 1,
// find q and s such that p - 1 = q2s with q odd
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++);
// Fast-path
if (S === 1) {
const p1div4 = (P + _1n) / _4n;
return function tonelliFast<T>(Fp: Field<T>, n: T) {
const root = Fp.pow(n, p1div4);
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root');
return root;
};
}
// Slow-path
const Q1div2 = (Q + _1n) / _2n;
return function tonelliSlow<T>(Fp: Field<T>, n: T): T {
// Step 0: Check that n is indeed a square: (n | p) must be ≡ 1
if (Fp.pow(n, legendreC) !== Fp.ONE) throw new Error('Cannot find square root');
let s = S;
let c = pow(Z, Q, P);
let r = Fp.pow(n, Q1div2);
let t = Fp.pow(n, Q);
let t2 = Fp.ZERO;
while (!Fp.equals(Fp.sub(t, Fp.ONE), Fp.ZERO)) {
t2 = Fp.square(t);
let i;
for (i = 1; i < s; i++) {
// stop if t2-1 == 0
if (Fp.equals(Fp.sub(t2, Fp.ONE), Fp.ZERO)) break;
// t2 *= t2
t2 = Fp.square(t2);
}
let b = pow(c, BigInt(1 << (s - i - 1)), P);
r = Fp.mul(r, b);
c = mod(b * b, P);
t = Fp.mul(t, c);
s = i;
}
return r;
};
} }
/** export function FpSqrt(P: bigint) {
* Calculates square root of a number in a finite field. // NOTE: different algorithms can give different roots, it is up to user to decide which one they want.
* √a mod P // For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).
*/
// TODO: rewrite as generic Fp function && remove bls versions
export function sqrt(number: bigint, modulo: bigint): bigint {
// prettier-ignore
const n = number;
const P = modulo;
const p1div4 = (P + _1n) / _4n;
// P ≡ 3 (mod 4) // P ≡ 3 (mod 4)
// √n = n^((P+1)/4) // √n = n^((P+1)/4)
@@ -94,48 +137,54 @@ export function sqrt(number: bigint, modulo: bigint): bigint {
// const ORDER = // const ORDER =
// 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn; // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;
// const NUM = 72057594037927816n; // const NUM = 72057594037927816n;
// TODO: fix sqrtMod in secp256k1 const p1div4 = (P + _1n) / _4n;
const root = pow(n, p1div4, P); return function sqrt3mod4<T>(Fp: Field<T>, n: T) {
if (mod(root * root, modulo) !== number) throw new Error('Cannot find square root'); const root = Fp.pow(n, p1div4);
return root; // Throw if root**2 != n
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root');
return root;
};
} }
// P ≡ 5 (mod 8) // Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)
if (P % _8n === _5n) { if (P % _8n === _5n) {
const n2 = mod(n * _2n, P); const c1 = (P - _5n) / _8n;
const v = pow(n2, (P - _5n) / _8n, P); return function sqrt5mod8<T>(Fp: Field<T>, n: T) {
const nv = mod(n * v, P); const n2 = Fp.mul(n, _2n);
const i = mod(_2n * nv * v, P); const v = Fp.pow(n2, c1);
const r = mod(nv * (i - _1n), P); const nv = Fp.mul(n, v);
return r; const i = Fp.mul(Fp.mul(nv, _2n), v);
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
if (!Fp.equals(Fp.square(root), n)) throw new Error('Cannot find square root');
return root;
};
}
// P ≡ 9 (mod 16)
if (P % _16n === _9n) {
// NOTE: tonelli is too slow for bls-Fp2 calculations even on start
// Means we cannot use sqrt for constants at all!
//
// const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
// const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F
// const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F
// const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic
// sqrt = (x) => {
// let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4
// let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1
// const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1
// let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1
// const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x
// const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x
// tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x
// tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x
// const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x
// return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2
// }
} }
// Other cases: Tonelli-Shanks algorithm // Other cases: Tonelli-Shanks algorithm
if (legendre(n, P) !== _1n) throw new Error('Cannot find square root'); return tonelliShanks(P);
let q: bigint, s: number, z: bigint;
for (q = P - _1n, s = 0; q % _2n === _0n; q /= _2n, s++);
if (s === 1) return pow(n, p1div4, P);
for (z = _2n; z < P && legendre(z, P) !== P - _1n; z++);
let c = pow(z, q, P);
let r = pow(n, (q + _1n) / _2n, P);
let t = pow(n, q, P);
let t2 = _0n;
while (mod(t - _1n, P) !== _0n) {
t2 = mod(t * t, P);
let i;
for (i = 1; i < s; i++) {
if (mod(t2 - _1n, P) === _0n) break;
t2 = mod(t2 * t2, P);
}
let b = pow(c, BigInt(1 << (s - i - 1)), P);
r = mod(r * b, P);
c = mod(b * b, P);
t = mod(t * c, P);
s = i;
}
return r;
} }
// Little-endian check for first LE bit (last BE bit); // Little-endian check for first LE bit (last BE bit);
@@ -176,6 +225,7 @@ export interface Field<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 https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/
// 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;
pow(lhs: T, power: bigint): T; pow(lhs: T, power: bigint): T;
@@ -246,21 +296,31 @@ export function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T {
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs)); return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.invert(rhs));
} }
// This function returns True whenever the value x is a square in the field F.
export function FpIsSquare<T>(f: Field<T>) {
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
return (x: T): boolean => {
const p = f.pow(x, legendreConst);
return f.equals(p, f.ZERO) || f.equals(p, f.ONE);
};
}
// NOTE: very fragile, always bench. Major performance points: // NOTE: very fragile, always bench. Major performance points:
// - NonNormalized ops // - NonNormalized ops
// - Object.freeze // - Object.freeze
// - same shape of object (don't add/remove keys) // - same shape of object (don't add/remove keys)
type FpField = Field<bigint> & Required<Pick<Field<bigint>, 'isOdd'>>;
export function Fp( export function Fp(
ORDER: bigint, ORDER: bigint,
bitLen?: number, bitLen?: number,
isLE = false, isLE = false,
redef: Partial<Field<bigint>> = {} redef: Partial<Field<bigint>> = {}
): Readonly<Field<bigint>> { ): Readonly<FpField> {
if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); if (ORDER <= _0n) throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
const { nBitLength: BITS, nByteLength: BYTES } = utils.nLength(ORDER, bitLen); const { nBitLength: BITS, nByteLength: BYTES } = utils.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 = (num: bigint) => sqrt(num, ORDER); const sqrtP = FpSqrt(ORDER);
const f: Field<bigint> = Object.freeze({ const f: Readonly<FpField> = Object.freeze({
ORDER, ORDER,
BITS, BITS,
BYTES, BYTES,
@@ -292,7 +352,7 @@ export function Fp(
mulN: (lhs, rhs) => lhs * rhs, mulN: (lhs, rhs) => lhs * rhs,
invert: (num) => invert(num, ORDER), invert: (num) => invert(num, ORDER),
sqrt: redef.sqrt || sqrtP, sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
invertBatch: (lst) => FpInvertBatch(f, lst), invertBatch: (lst) => FpInvertBatch(f, lst),
// TODO: do we really need constant cmov? // TODO: do we really need constant cmov?
// We don't have const-time bigints anyway, so probably will be not very useful // We don't have const-time bigints anyway, so probably will be not very useful
@@ -305,87 +365,18 @@ export function Fp(
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes); return isLE ? utils.bytesToNumberLE(bytes) : utils.bytesToNumberBE(bytes);
}, },
} as Field<bigint>); } as FpField);
return Object.freeze(f); return Object.freeze(f);
} }
// TODO: re-use in bls/generic sqrt for field/etc? export function FpSqrtOdd<T>(Fp: Field<T>, elm: T) {
// Something like sqrtUnsafe which always returns value, but sqrt throws exception if non-square if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
// From draft-irtf-cfrg-hash-to-curve-16 const root = Fp.sqrt(elm);
export function FpSqrt<T>(Fp: Field<T>) { return Fp.isOdd(root) ? root : Fp.negate(root);
// NOTE: it requires another sqrt for constant precomputes, but no need for roots of unity, }
// probably we can simply bls code using it
const q = Fp.ORDER; export function FpSqrtEven<T>(Fp: Field<T>, elm: T) {
const squareConst = (q - _1n) / _2n; if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
// is_square(x) := { True, if x^((q - 1) / 2) is 0 or 1 in F; const root = Fp.sqrt(elm);
// { False, otherwise. return Fp.isOdd(root) ? Fp.negate(root) : root;
let isSquare: (x: T) => boolean = (x) => {
const p = Fp.pow(x, squareConst);
return Fp.equals(p, Fp.ZERO) || Fp.equals(p, Fp.ONE);
};
// Constant-time Tonelli-Shanks algorithm
let l = _0n;
for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
const c2 = (q - _1n) / _2n ** c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
// 4. c4, a non-square value in F
// 5. c5 = c4^c2 in F
let c4 = Fp.ONE;
while (isSquare(c4)) c4 = Fp.add(c4, Fp.ONE);
const c5 = Fp.pow(c4, c2);
let sqrt: (x: T) => T = (x) => {
let z = Fp.pow(x, c3); // 1. z = x^c3
let t = Fp.square(z); // 2. t = z * z
t = Fp.mul(t, x); // 3. t = t * x
z = Fp.mul(z, x); // 4. z = z * x
let b = t; // 5. b = t
let c = c5; // 6. c = c5
// 7. for i in (c1, c1 - 1, ..., 2):
for (let i = c1; i > 1; i--) {
// 8. for j in (1, 2, ..., i - 2):
// 9. b = b * b
for (let j = _1n; j < i - _1n; i++) b = Fp.square(b);
const e = Fp.equals(b, Fp.ONE); // 10. e = b == 1
const zt = Fp.mul(z, c); // 11. zt = z * c
z = Fp.cmov(zt, z, e); // 12. z = CMOV(zt, z, e)
c = Fp.square(c); // 13. c = c * c
let tt = Fp.mul(t, c); // 14. tt = t * c
t = Fp.cmov(tt, t, e); // 15. t = CMOV(tt, t, e)
b = t; // 16. b = t
}
return z; // 17. return z
};
if (q % _4n === _3n) {
const c1 = (q + _1n) / _4n; // 1. c1 = (q + 1) / 4 # Integer arithmetic
sqrt = (x) => Fp.pow(x, c1);
} else if (q % _8n === _5n) {
const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
const c2 = (q + _3n) / _8n; // 2. c2 = (q + 3) / 8 # Integer arithmetic
sqrt = (x) => {
let tv1 = Fp.pow(x, c2); // 1. tv1 = x^c2
let tv2 = Fp.mul(tv1, c1); // 2. tv2 = tv1 * c1
let e = Fp.equals(Fp.square(tv1), x); // 3. e = (tv1^2) == x
return Fp.cmov(tv2, tv1, e); // 4. z = CMOV(tv2, tv1, e)
};
} else if (Fp.ORDER % _16n === _9n) {
const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F
const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F
const c4 = (Fp.ORDER + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic
sqrt = (x) => {
let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4
let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1
const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1
let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1
const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x
const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x
tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x
tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x
const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x
return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2
};
}
return { sqrt, isSquare };
} }

View File

@@ -12,7 +12,7 @@ export type CHash = {
(message: Uint8Array | string): Uint8Array; (message: Uint8Array | string): Uint8Array;
blockLen: number; blockLen: number;
outputLen: number; outputLen: number;
create(): any; create(opts?: { dkLen?: number }): any; // For shake
}; };
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h // NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h

View File

@@ -926,12 +926,12 @@ const htfDefaults = {
k: 128, k: 128,
// option to use a message that has already been processed by // option to use a message that has already been processed by
// expand_message_xmd // expand_message_xmd
expand: true, expand: 'xmd',
// Hash functions for: expand_message_xmd is appropriate for use with a // Hash functions for: expand_message_xmd is appropriate for use with a
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others. // wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247 // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
hash: sha256, hash: sha256,
}; } as const;
// Encoding utils // Encoding utils
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)

View File

@@ -3,7 +3,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'; import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js'; import { twistedEdwards, ExtendedPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field } from './abstract/modular.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { import {
ensureBytes, ensureBytes,
equalBytes, equalBytes,
@@ -91,7 +91,80 @@ export const ED25519_TORSION_SUBGROUP = [
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa', 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
]; ];
const Fp = Field(ED25519_P); const Fp = Field(ED25519_P, undefined, true);
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
// SageMath returns different root first and everything falls apart
const ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1
const ELL2_C3 = Fp.sqrt(Fp.negate(Fp.ONE)); // 3. c3 = sqrt(-1)
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic
const ELL2_J = BigInt(486662);
// prettier-ignore
function map_to_curve_elligator2_curve25519(u: bigint) {
let tv1 = Fp.square(u); // 1. tv1 = u^2
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not
let x1n = Fp.negate(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
let tv2 = Fp.square(xd); // 5. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.square(gxd); // 11. tv3 = gxd^2
tv2 = Fp.square(tv3); // 12. tv2 = tv3^2 # gxd^4
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3
tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7
let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)
y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3
tv2 = Fp.square(y11); // 19. tv2 = y11^2
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd
let e1 = Fp.equals(tv2, gx1); // 21. e1 = tv2 == gx1
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt
let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd
let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u
y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2
let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)
tv2 = Fp.square(y21); // 28. tv2 = y21^2
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd
let e2 = Fp.equals(tv2, gx2); // 30. e2 = tv2 == gx2
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt
tv2 = Fp.square(y1); // 32. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd
let e3 = Fp.equals(tv2, gx1); // 34. e3 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.negate(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)
return { xMn: xn, xMd: xd, yMn: y, yMd: 1n }; // 39. return (xn, xd, y, 1)
}
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.negate(BigInt(486664))); // sgn0(c1) MUST equal 0
function map_to_curve_elligator2_edwards25519(u: bigint) {
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u)
let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd
xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1
let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM
let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd
let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd
let e = Fp.equals(tv1, Fp.ZERO); // 8. e = tv1 == 0
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e)
xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e)
yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e)
yd = Fp.cmov(yd, Fp.ONE, e); // 12. yd = CMOV(yd, 1, e)
const inv = Fp.invertBatch([xd, yd]); // batch division
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
}
const ED25519_DEF = { const ED25519_DEF = {
// Param: a // Param: a
@@ -121,14 +194,10 @@ const ED25519_DEF = {
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 128, k: 128,
expand: true, expand: 'xmd',
hash: sha512, hash: sha512,
}, },
mapToCurve: (scalars: bigint[]): { x: bigint; y: bigint } => { mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
throw new Error('Not supported yet');
// const { x, y } = calcElligatorRistrettoMap(scalars[0]).toAffine();
// return { x, y };
},
} as const; } as const;
export const ed25519 = twistedEdwards(ED25519_DEF); export const ed25519 = twistedEdwards(ED25519_DEF);

View File

@@ -2,7 +2,7 @@
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 { twistedEdwards } from './abstract/edwards.js';
import { mod, pow2, Fp } from './abstract/modular.js'; import { mod, pow2, Fp as Field } from './abstract/modular.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
/** /**
@@ -52,6 +52,83 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
return bytes; return bytes;
} }
const Fp = Field(ed448P, 456, true);
// Hash To Curve Elligator2 Map
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
const ELL2_J = BigInt(156326);
function map_to_curve_elligator2_curve448(u: bigint) {
let tv1 = Fp.square(u); // 1. tv1 = u^2
let e1 = Fp.equals(tv1, Fp.ONE); // 2. e1 = tv1 == 1
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1
let x1n = Fp.negate(ELL2_J); // 5. x1n = -J
let tv2 = Fp.square(xd); // 6. tv2 = xd^2
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3
let gx1 = Fp.mul(tv1, Fp.negate(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
let tv3 = Fp.square(gxd); // 12. tv3 = gxd^2
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4)
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4)
let x2n = Fp.mul(x1n, Fp.negate(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1)
tv2 = Fp.square(y1); // 20. tv2 = y1^2
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd
let e2 = Fp.equals(tv2, gx1); // 22. e2 = tv2 == gx1
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y
y = Fp.cmov(y, Fp.negate(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3)
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
}
function map_to_curve_elligator2_edwards448(u: bigint) {
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u)
let xn2 = Fp.square(xn); // 2. xn2 = xn^2
let xd2 = Fp.square(xd); // 3. xd2 = xd^2
let xd4 = Fp.square(xd2); // 4. xd4 = xd2^2
let yn2 = Fp.square(yn); // 5. yn2 = yn^2
let yd2 = Fp.square(yd); // 6. yd2 = yd^2
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn
xEn = Fp.mul(xEn, 4n); // 12. xEn = xEn * 4
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2
let tv3 = Fp.mul(yn2, 4n); // 15. tv3 = 4 * yn2
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd
let e = Fp.equals(tv1, Fp.ZERO); // 33. e = tv1 == 0
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e)
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e)
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e)
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e)
const inv = Fp.invertBatch([xEd, yEd]); // batch division
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
}
const ED448_DEF = { const ED448_DEF = {
// Param: a // Param: a
a: BigInt(1), a: BigInt(1),
@@ -60,7 +137,7 @@ const ED448_DEF = {
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358' '726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
), ),
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n // Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
Fp: Fp(ed448P, 456), Fp,
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n // Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
n: BigInt( n: BigInt(
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779' '181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
@@ -111,6 +188,15 @@ const ED448_DEF = {
// square root exists, and the decoding fails. // square root exists, and the decoding fails.
return { isValid: mod(x2 * v, P) === u, value: x }; return { isValid: mod(x2 * v, P) === u, value: x };
}, },
htfDefaults: {
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
p: Fp.ORDER,
m: 1,
k: 224,
expand: 'xof',
hash: shake256,
},
mapToCurve: (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
} as const; } as const;
export const ed448 = twistedEdwards(ED448_DEF); export const ed448 = twistedEdwards(ED448_DEF);

View File

@@ -37,7 +37,7 @@ export const P256 = createCurve(
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 128, k: 128,
expand: true, expand: 'xmd',
hash: sha256, hash: sha256,
}, },
} as const, } as const,

View File

@@ -41,7 +41,7 @@ export const P384 = createCurve({
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 192, k: 192,
expand: true, expand: 'xmd',
hash: sha384, hash: sha384,
}, },
} as const, } as const,

View File

@@ -54,7 +54,7 @@ export const P521 = createCurve({
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 256, k: 256,
expand: true, expand: 'xmd',
hash: sha512, hash: sha512,
}, },
} as const, sha512); } as const, sha512);

View File

@@ -56,7 +56,9 @@ function sqrtMod(y: bigint): bigint {
const b223 = (pow2(b220, _3n, P) * b3) % P; const b223 = (pow2(b220, _3n, P) * b3) % P;
const t1 = (pow2(b223, _23n, P) * b22) % P; const t1 = (pow2(b223, _23n, P) * b22) % P;
const t2 = (pow2(t1, _6n, P) * b2) % P; const t2 = (pow2(t1, _6n, P) * b2) % P;
return pow2(t2, _2n, P); const root = pow2(t2, _2n, P);
if (!Fp.equals(Fp.square(root), y)) throw new Error('Cannot find square root');
return root;
} }
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod }); const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
@@ -152,7 +154,7 @@ export const secp256k1 = createCurve(
p: Fp.ORDER, p: Fp.ORDER,
m: 1, m: 1,
k: 128, k: 128,
expand: true, expand: 'xmd',
hash: sha256, hash: sha256,
}, },
}, },

View File

@@ -97,7 +97,7 @@ function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) {
return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB); return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB);
} }
function sign0x(msgHash: Hex, privKey: Hex, opts: any) { function sign0x(msgHash: Hex, privKey: Hex, opts?: any) {
if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0'); if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0');
return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts); return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts);
} }
@@ -203,6 +203,8 @@ function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): Projectiv
out.push(p); out.push(p);
p = p.double(); p = p.double();
} }
// NOTE: we cannot use wNAF here, because last 4 bits will require full 248 bits multiplication
// We can add support for this to wNAF, but it will complicate wNAF.
p = p2; p = p2;
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
out.push(p); out.push(p);

View File

@@ -15,7 +15,252 @@ import { starkCurve } from '../lib/esm/stark.js';
import { pallas, vesta } from '../lib/esm/pasta.js'; import { pallas, vesta } from '../lib/esm/pasta.js';
import { bn254 } from '../lib/esm/bn.js'; import { bn254 } from '../lib/esm/bn.js';
import { jubjub } from '../lib/esm/jubjub.js'; import { jubjub } from '../lib/esm/jubjub.js';
import { bls12_381 } from '../lib/esm/bls12-381.js';
// Fields tests
const FIELDS = {
secp192r1: { Fp: [secp192r1.CURVE.Fp] },
secp224r1: { Fp: [secp224r1.CURVE.Fp] },
secp256r1: { Fp: [secp256r1.CURVE.Fp] },
secp521r1: { Fp: [secp521r1.CURVE.Fp] },
secp256k1: { Fp: [secp256k1.CURVE.Fp] },
stark: { Fp: [starkCurve.CURVE.Fp] },
jubjub: { Fp: [jubjub.CURVE.Fp] },
ed25519: { Fp: [ed25519.CURVE.Fp] },
ed448: { Fp: [ed448.CURVE.Fp] },
bn254: { Fp: [bn254.CURVE.Fp] },
pallas: { Fp: [pallas.CURVE.Fp] },
vesta: { Fp: [vesta.CURVE.Fp] },
bls12: {
Fp: [bls12_381.CURVE.Fp],
Fp2: [
bls12_381.CURVE.Fp2,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
minLength: 2,
maxLength: 2,
}),
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
],
// Fp6: [bls12_381.CURVE.Fp6],
Fp12: [
bls12_381.CURVE.Fp12,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
minLength: 12,
maxLength: 12,
}),
(Fp12, num) => Fp12.fromBigTwelve(num),
],
},
};
for (const c in FIELDS) {
const curve = FIELDS[c];
for (const f in curve) {
const Fp = curve[f][0];
const name = `${c}/${f}:`;
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
should(`${name} equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
const b = create(num);
deepStrictEqual(Fp.equals(a, b), true);
deepStrictEqual(Fp.equals(b, a), true);
})
);
});
should(`${name} non-equality`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.equals(a, b), num1 === num2);
deepStrictEqual(Fp.equals(b, a), num1 === num2);
})
);
});
should(`${name} add/subtract/commutativity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
})
);
});
should(`${name} add/subtract/associativity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
})
);
});
should(`${name} add/subtract/x+0=x`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
})
);
});
should(`${name} add/subtract/x-0=x`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
})
);
});
should(`${name} add/subtract/negate equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num1) => {
const a = create(num1);
const b = create(num1);
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b)));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
})
);
});
should(`${name} add/subtract/negate`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a));
deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n)));
})
);
});
should(`${name} multiply/commutativity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
})
);
});
should(`${name} multiply/associativity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
})
);
});
should(`${name} multiply/distributivity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
})
);
});
should(`${name} multiply/add equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.mul(a, 0n), Fp.ZERO);
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(a, 1n), a);
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
deepStrictEqual(Fp.mul(a, 2n), Fp.add(a, a));
deepStrictEqual(Fp.mul(a, 3n), Fp.add(Fp.add(a, a), a));
deepStrictEqual(Fp.mul(a, 4n), Fp.add(Fp.add(Fp.add(a, a), a), a));
})
);
});
should(`${name} multiply/square equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.square(a), Fp.mul(a, a));
})
);
});
should(`${name} multiply/pow equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
deepStrictEqual(Fp.pow(a, 1n), a);
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
})
);
});
const isSquare = mod.FpIsSquare(Fp);
should(`${name} multiply/sqrt`, () => {
if (Fp === bls12_381.CURVE.Fp12) return; // Not implemented
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
let root;
try {
root = Fp.sqrt(a);
} catch (e) {
deepStrictEqual(isSquare(a), false);
return;
}
deepStrictEqual(isSquare(a), true);
deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a');
deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a');
})
);
});
should(`${name} div/division by one equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
if (Fp.equals(a, Fp.ZERO)) return; // No division by zero
deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE);
})
);
});
should(`${name} zero division equality`, () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
})
);
});
should(`${name} div/division distributivity`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
})
);
});
should(`${name} div/division and multiplication equality`, () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b)));
})
);
});
}
}
// Group tests
// prettier-ignore // prettier-ignore
const CURVES = { const CURVES = {
secp192r1, secp224r1, secp256r1, secp384r1, secp521r1, secp192r1, secp224r1, secp256r1, secp384r1, secp521r1,
@@ -29,6 +274,7 @@ const CURVES = {
}; };
const NUM_RUNS = 5; const NUM_RUNS = 5;
const getXY = (p) => ({ x: p.x, y: p.y }); const getXY = (p) => ({ x: p.x, y: p.y });
function equal(a, b, comment) { function equal(a, b, comment) {

View File

@@ -41,145 +41,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
{ {
const Fp = bls.Fp; const Fp = bls.Fp;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
should('bls12-381/Fp/equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
const b = Fp.create(num);
deepStrictEqual(Fp.equals(a, b), true);
deepStrictEqual(Fp.equals(b, a), true);
})
);
});
should('bls12-381/Fp/non-equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
deepStrictEqual(Fp.equals(a, b), num1 === num2);
deepStrictEqual(Fp.equals(b, a), num1 === num2);
})
);
});
should('bls12-381/Fp/add/subtract/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
})
);
});
should('bls12-381/Fp/add/subtract/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
const c = Fp.create(num3);
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
})
);
});
should('bls12-381/Fp/add/subtract/x+0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
})
);
});
should('bls12-381/Fp/add/subtract/x-0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
})
);
});
should('bls12-381/Fp/add/subtract/negate equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num1) => {
const a = Fp.create(num1);
const b = Fp.create(num1);
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b)));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
})
);
});
should('bls12-381/Fp/add/subtract/negate', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a));
deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n)));
})
);
});
should('bls12-381/Fp/multiply/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
})
);
});
should('bls12-381/Fp/multiply/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
const c = Fp.create(num3);
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
})
);
});
should('bls12-381/Fp/multiply/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
const c = Fp.create(num3);
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
})
);
});
should('bls12-381/Fp/multiply/add equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.mul(a, Fp.create(0n)), Fp.ZERO);
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(a, Fp.create(1n)), a);
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
deepStrictEqual(Fp.mul(a, Fp.create(2n)), Fp.add(a, a));
deepStrictEqual(Fp.mul(a, Fp.create(3n)), Fp.add(Fp.add(a, a), a));
deepStrictEqual(Fp.mul(a, Fp.create(4n)), Fp.add(Fp.add(Fp.add(a, a), a), a));
})
);
});
should('bls12-381/Fp/multiply/square equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.square(a), Fp.mul(a, a));
})
);
});
should('bls12-381/Fp/multiply/pow equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
deepStrictEqual(Fp.pow(a, 1n), a);
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
})
);
});
should('bls12-381/Fp/multiply/sqrt', () => { should('bls12-381/Fp/multiply/sqrt', () => {
let sqr1 = Fp.sqrt(Fp.create(300855555557n)); let sqr1 = Fp.sqrt(Fp.create(300855555557n));
deepStrictEqual( deepStrictEqual(
@@ -188,43 +50,6 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
); );
throws(() => Fp.sqrt(Fp.create(72057594037927816n))); throws(() => Fp.sqrt(Fp.create(72057594037927816n)));
}); });
should('bls12-381/Fp/div/division by one equality', () => {
fc.assert(
fc.property(fc.bigInt(1n, Fp.ORDER - 1n), (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE);
})
);
});
should('bls12-381/Fp/div/division by zero equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = Fp.create(num);
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
})
);
});
should('bls12-381/Fp/div/division distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
const c = Fp.create(num3);
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
})
);
});
should('bls12-381/Fp/div/division and multiplication equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = Fp.create(num1);
const b = Fp.create(num2);
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b)));
})
);
});
} }
// Fp2 // Fp2
@@ -234,16 +59,6 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }); const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 });
should('bls12-381 Fp2/equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
const b = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.equals(a, b), true);
deepStrictEqual(Fp2.equals(b, a), true);
})
);
});
should('bls12-381 Fp2/non-equality', () => { should('bls12-381 Fp2/non-equality', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => { fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => {
@@ -254,136 +69,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
}) })
); );
}); });
should('bls12-381 Fp2/add/subtract/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
deepStrictEqual(Fp2.add(a, b), Fp2.add(b, a));
})
);
});
should('bls12-381 Fp2/add/subtract/associativity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, FC_BIGINT_2, (num1, num2, num3) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
const c = Fp2.fromBigTuple([num3[0], num3[1]]);
deepStrictEqual(Fp2.add(a, Fp2.add(b, c)), Fp2.add(Fp2.add(a, b), c));
})
);
});
should('bls12-381 Fp2/add/subtract/x+0=x', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.add(a, Fp2.ZERO), a);
})
);
});
should('bls12-381 Fp2/add/subtract/x-0=x', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.sub(a, Fp2.ZERO), a);
deepStrictEqual(Fp2.sub(a, a), Fp2.ZERO);
})
);
});
should('bls12-381 Fp2/add/subtract/negate equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num1) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num1[0], num1[1]]);
deepStrictEqual(Fp2.sub(Fp2.ZERO, a), Fp2.negate(a));
deepStrictEqual(Fp2.sub(a, b), Fp2.add(a, Fp2.negate(b)));
deepStrictEqual(Fp2.sub(a, b), Fp2.add(a, Fp2.mul(b, -1n)));
})
);
});
should('bls12-381 Fp2/add/subtract/negate', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.negate(a), Fp2.sub(Fp2.ZERO, a));
deepStrictEqual(Fp2.negate(a), Fp2.mul(a, -1n));
})
);
});
should('bls12-381 Fp2/multiply/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
deepStrictEqual(Fp2.mul(a, b), Fp2.mul(b, a));
})
);
});
should('bls12-381 Fp2/multiply/associativity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, FC_BIGINT_2, (num1, num2, num3) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
const c = Fp2.fromBigTuple([num3[0], num3[1]]);
deepStrictEqual(Fp2.mul(a, Fp2.mul(b, c)), Fp2.mul(Fp2.mul(a, b), c));
})
);
});
should('bls12-381 Fp2/multiply/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, FC_BIGINT_2, (num1, num2, num3) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
const c = Fp2.fromBigTuple([num3[0], num3[1]]);
deepStrictEqual(Fp2.mul(a, Fp2.add(b, c)), Fp2.add(Fp2.mul(b, a), Fp2.mul(c, a)));
})
);
});
should('bls12-381 Fp2/multiply/add equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.mul(a, 0n), Fp2.ZERO);
deepStrictEqual(Fp2.mul(a, Fp2.ZERO), Fp2.ZERO);
deepStrictEqual(Fp2.mul(a, 1n), a);
deepStrictEqual(Fp2.mul(a, Fp2.ONE), a);
deepStrictEqual(Fp2.mul(a, 2n), Fp2.add(a, a));
deepStrictEqual(Fp2.mul(a, 3n), Fp2.add(Fp2.add(a, a), a));
deepStrictEqual(Fp2.mul(a, 4n), Fp2.add(Fp2.add(Fp2.add(a, a), a), a));
})
);
});
should('bls12-381 Fp2/multiply/square equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.square(a), Fp2.mul(a, a));
})
);
});
should('bls12-381 Fp2/multiply/pow equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.pow(a, 0n), Fp2.ONE);
deepStrictEqual(Fp2.pow(a, 1n), a);
deepStrictEqual(Fp2.pow(a, 2n), Fp2.mul(a, a));
deepStrictEqual(Fp2.pow(a, 3n), Fp2.mul(Fp2.mul(a, a), a));
})
);
});
should('bls12-381 Fp2/div/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, FC_BIGINT_2, (num1, num2, num3) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
const c = Fp2.fromBigTuple([num3[0], num3[1]]);
deepStrictEqual(Fp2.div(Fp2.add(a, b), c), Fp2.add(Fp2.div(a, c), Fp2.div(b, c)));
})
);
});
should('bls12-381 Fp2/div/x/1=x', () => { should('bls12-381 Fp2/div/x/1=x', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT_2, (num) => { fc.property(FC_BIGINT_2, (num) => {
@@ -394,23 +80,6 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
}) })
); );
}); });
should('bls12-381 Fp2/div/ x/0=0', () => {
fc.assert(
fc.property(FC_BIGINT_2, (num) => {
const a = Fp2.fromBigTuple([num[0], num[1]]);
deepStrictEqual(Fp2.div(Fp2.ZERO, a), Fp2.ZERO);
})
);
});
should('bls12-381 Fp2/div/ multiply equality', () => {
fc.assert(
fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => {
const a = Fp2.fromBigTuple([num1[0], num1[1]]);
const b = Fp2.fromBigTuple([num2[0], num2[1]]);
deepStrictEqual(Fp2.div(a, b), Fp2.mul(a, Fp2.invert(b)));
})
);
});
should('bls12-381 Fp2/frobenius', () => { should('bls12-381 Fp2/frobenius', () => {
// expect(Fp2.FROBENIUS_COEFFICIENTS[0].equals(Fp.ONE)).toBe(true); // expect(Fp2.FROBENIUS_COEFFICIENTS[0].equals(Fp.ONE)).toBe(true);
@@ -472,195 +141,6 @@ const getPubKey = (priv) => bls.getPublicKey(priv);
}); });
} }
// Fp12
{
const Fp = bls.Fp;
const Fp12 = bls.Fp12;
const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n);
const FC_BIGINT_12 = fc.array(FC_BIGINT, {
minLength: 12,
maxLength: 12,
});
should('bls12-381 Fp12/equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
const b = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.equals(a, b), true);
deepStrictEqual(Fp12.equals(b, a), true);
})
);
});
should('bls12-381 Fp12/non-equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, (num1, num2) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
deepStrictEqual(Fp12.equals(a, b), num1[0] === num2[0] && num1[1] === num2[1]);
deepStrictEqual(Fp12.equals(b, a), num1[0] === num2[0] && num1[1] === num2[1]);
})
);
});
should('bls12-381 Fp12/add/subtract/commutativuty', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, (num1, num2) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
deepStrictEqual(Fp12.add(a, b), Fp12.add(b, a));
})
);
});
should('bls12-381 Fp12/add/subtract/associativity', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, FC_BIGINT_12, (num1, num2, num3) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
const c = Fp12.fromBigTwelve(num3);
deepStrictEqual(Fp12.add(a, Fp12.add(b, c)), Fp12.add(Fp12.add(a, b), c));
})
);
});
should('bls12-381 Fp12/add/subtract/x+0=x', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.add(a, Fp12.ZERO), a);
})
);
});
should('bls12-381 Fp12/add/subtract/x-0=x', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.sub(a, Fp12.ZERO), a);
deepStrictEqual(Fp12.sub(a, a), Fp12.ZERO);
})
);
});
should('bls12-381 Fp12/add/subtract/negate equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num1) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num1);
deepStrictEqual(Fp12.sub(Fp12.ZERO, a), Fp12.negate(a));
deepStrictEqual(Fp12.sub(a, b), Fp12.add(a, Fp12.negate(b)));
deepStrictEqual(Fp12.sub(a, b), Fp12.add(a, Fp12.mul(b, -1n)));
})
);
});
should('bls12-381 Fp12/add/subtract/negate', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.negate(a), Fp12.sub(Fp12.ZERO, a));
deepStrictEqual(Fp12.negate(a), Fp12.mul(a, -1n));
})
);
});
should('bls12-381 Fp12/multiply/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, (num1, num2) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
deepStrictEqual(Fp12.mul(a, b), Fp12.mul(b, a));
})
);
});
should('bls12-381 Fp12/multiply/associativity', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, FC_BIGINT_12, (num1, num2, num3) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
const c = Fp12.fromBigTwelve(num3);
deepStrictEqual(Fp12.mul(a, Fp12.mul(b, c)), Fp12.mul(Fp12.mul(a, b), c));
})
);
});
should('bls12-381 Fp12/multiply/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, FC_BIGINT_12, (num1, num2, num3) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
const c = Fp12.fromBigTwelve(num3);
deepStrictEqual(Fp12.mul(a, Fp12.add(b, c)), Fp12.add(Fp12.mul(b, a), Fp12.mul(c, a)));
})
);
});
should('bls12-381 Fp12/multiply/add equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.mul(a, 0n), Fp12.ZERO);
deepStrictEqual(Fp12.mul(a, Fp12.ZERO), Fp12.ZERO);
deepStrictEqual(Fp12.mul(a, 1n), a);
deepStrictEqual(Fp12.mul(a, Fp12.ONE), a);
deepStrictEqual(Fp12.mul(a, 2n), Fp12.add(a, a));
deepStrictEqual(Fp12.mul(a, 3n), Fp12.add(Fp12.add(a, a), a));
deepStrictEqual(Fp12.mul(a, 4n), Fp12.add(Fp12.add(Fp12.add(a, a), a), a));
})
);
});
should('bls12-381 Fp12/multiply/square equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.square(a), Fp12.mul(a, a));
})
);
});
should('bls12-381 Fp12/multiply/pow equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.pow(a, 0n), Fp12.ONE);
deepStrictEqual(Fp12.pow(a, 1n), a);
deepStrictEqual(Fp12.pow(a, 2n), Fp12.mul(a, a));
deepStrictEqual(Fp12.pow(a, 3n), Fp12.mul(Fp12.mul(a, a), a));
})
);
});
should('bls12-381 Fp12/div/x/1=x', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.div(a, 1n), a);
deepStrictEqual(Fp12.div(a, Fp12.ONE), a);
deepStrictEqual(Fp12.div(a, a), Fp12.ONE);
})
);
});
should('bls12-381 Fp12/div/x/0=0', () => {
fc.assert(
fc.property(FC_BIGINT_12, (num) => {
const a = Fp12.fromBigTwelve(num);
deepStrictEqual(Fp12.div(Fp12.ZERO, a), Fp12.ZERO);
})
);
});
should('bls12-381 Fp12/div/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, FC_BIGINT_12, (num1, num2, num3) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
const c = Fp12.fromBigTwelve(num3);
deepStrictEqual(Fp12.div(Fp12.add(a, b), c), Fp12.add(Fp12.div(a, c), Fp12.div(b, c)));
})
);
});
should('bls12-381 Fp12/div/multiply equality', () => {
fc.assert(
fc.property(FC_BIGINT_12, FC_BIGINT_12, (num1, num2) => {
const a = Fp12.fromBigTwelve(num1);
const b = Fp12.fromBigTwelve(num2);
deepStrictEqual(Fp12.div(a, b), Fp12.mul(a, Fp12.invert(b)));
})
);
});
}
// Point // Point
{ {
const Fp = bls.Fp; const Fp = bls.Fp;

View File

@@ -4,16 +4,27 @@ import { bytesToHex } from '@noble/hashes/utils';
// Generic tests for all curves in package // Generic tests for all curves in package
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3';
import { secp256r1 } from '../lib/esm/p256.js'; import { secp256r1 } from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.js'; import { secp384r1 } from '../lib/esm/p384.js';
import { secp521r1 } from '../lib/esm/p521.js'; import { secp521r1 } from '../lib/esm/p521.js';
import { ed25519 } from '../lib/esm/ed25519.js';
import { ed448 } from '../lib/esm/ed448.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { secp256k1 } from '../lib/esm/secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../lib/esm/bls12-381.js';
import { stringToBytes, expand_message_xmd } from '../lib/esm/abstract/hash-to-curve.js'; import {
stringToBytes,
expand_message_xmd,
expand_message_xof,
} from '../lib/esm/abstract/hash-to-curve.js';
// XMD
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' }; import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' }; import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
import { default as xmd_sha512_38 } from './hash-to-curve/expand_message_xmd_SHA512_38.json' assert { type: 'json' }; import { default as xmd_sha512_38 } from './hash-to-curve/expand_message_xmd_SHA512_38.json' assert { type: 'json' };
// XOF
import { default as xof_shake128_36 } from './hash-to-curve/expand_message_xof_SHAKE128_36.json' assert { type: 'json' };
import { default as xof_shake128_256 } from './hash-to-curve/expand_message_xof_SHAKE128_256.json' assert { type: 'json' };
import { default as xof_shake256_36 } from './hash-to-curve/expand_message_xof_SHAKE256_36.json' assert { type: 'json' };
// P256 // P256
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' }; import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' }; import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
@@ -58,6 +69,26 @@ testExpandXMD(sha256, xmd_sha256_38);
testExpandXMD(sha256, xmd_sha256_256); testExpandXMD(sha256, xmd_sha256_256);
testExpandXMD(sha512, xmd_sha512_38); testExpandXMD(sha512, xmd_sha512_38);
function testExpandXOF(hash, vectors) {
for (let i = 0; i < vectors.tests.length; i++) {
const t = vectors.tests[i];
should(`expand_message_xof/${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xof(
stringToBytes(t.msg),
stringToBytes(vectors.DST),
+t.len_in_bytes,
vectors.k,
hash
);
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
});
}
}
testExpandXOF(shake128, xof_shake128_36);
testExpandXOF(shake128, xof_shake128_256);
testExpandXOF(shake256, xof_shake256_36);
function stringToFp(s) { function stringToFp(s) {
// bls-G2 support // bls-G2 support
if (s.includes(',')) { if (s.includes(',')) {
@@ -97,8 +128,8 @@ testCurve(secp521r1, p521_ro, p521_nu);
testCurve(bls12_381.G1, g1_ro, g1_nu); testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu); testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu); testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
//testCurve(ed25519, ed25519_ro, ed25519_nu); testCurve(ed25519, ed25519_ro, ed25519_nu);
//testCurve(ed448, ed448_ro, ed448_nu); testCurve(ed448, ed448_ro, ed448_nu);
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';