86 Commits
0.6.0 ... 0.8.0

Author SHA1 Message Date
Paul Miller
49a659b248 Release 0.8.0. 2023-03-03 05:12:36 +04:00
Paul Miller
9d0a2e25dc readme: esm-only 2023-03-03 05:11:21 +04:00
Paul Miller
7c461af2b2 test: remove common.js support 2023-03-03 05:09:50 +04:00
Paul Miller
4a8f447c8d package.json, tsconfig: remove common.js support. Pure ESM now 2023-03-03 05:09:36 +04:00
Paul Miller
4b2d31ce7f stark: more methods 2023-02-28 23:18:06 +04:00
Paul Miller
16115f27a6 readme update 2023-02-28 14:04:15 +04:00
Paul Miller
0e0d0f530d benchmark: add tonneli-shanks sqrt 2023-02-28 02:59:28 +04:00
Paul Miller
fa5105aef2 ecdsa: remove scalar blinding. CSPRNG dep not good: cryptofuzz, other envs will fail 2023-02-28 01:48:06 +04:00
Paul Miller
11f1626ecc modular: Add comment. Add benchmark 2023-02-27 22:41:24 +04:00
Paul Miller
53ff287bf7 Schnorr: remove getExtendedPublicKey 2023-02-27 20:29:47 +04:00
Paul Miller
214c9aa553 secp256k1: Fix schnorrGetExtPubKey y coordinate 2023-02-27 20:20:13 +04:00
Paul Miller
ec2c3e1248 Add test for ristretto equality testing 2023-02-27 19:33:41 +04:00
Paul Miller
e64a9d654c Fix ristretto255 equals 2023-02-27 19:07:45 +04:00
Paul Miller
088edd0fbb h2c: move params validation. add experimental hash_to_ristretto255 2023-02-27 15:07:24 +01:00
Paul Miller
3e90930e9d Fix types 2023-02-26 19:10:50 +01:00
Paul Miller
b8b2e91f74 Release 0.7.3. 2023-02-26 19:05:53 +01:00
Paul Miller
9ee694ae23 docs updates 2023-02-26 19:05:40 +01:00
Paul Miller
6bc4b35cf4 hash-to-curve: speed-up os2ip, change code a bit 2023-02-26 18:55:30 +01:00
Paul Miller
0163b63532 Release 0.7.2. 2023-02-25 10:13:45 +01:00
Paul Miller
7e825520f1 README 2023-02-25 10:05:48 +01:00
Paul Miller
d739297b2c Move p192, p224 from main pkg to tests for now. Reason: not popular 2023-02-25 10:00:24 +01:00
Paul Miller
285aa6375d stark: refactor 2023-02-20 16:50:29 +01:00
Paul Miller
8c77331ef2 add hash-to-curve benchmark 2023-02-20 16:33:05 +01:00
Paul Miller
669641e0a3 README wording 2023-02-16 17:54:17 +01:00
Paul Miller
68dd57ed31 Cryptofuzz 2023-02-16 17:49:48 +01:00
Paul Miller
a9fdd6df9f readme: typo 2023-02-16 12:33:32 +01:00
Paul Miller
d485d8b0e6 Fix prettier 2023-02-16 12:32:32 +01:00
Paul Miller
0fdd763dc7 montgomery: add randomPrivateKey. Add ecdh benchmark. 2023-02-16 12:32:18 +01:00
Paul Miller
586e2ad5fb Release 0.7.1. 2023-02-16 00:20:37 +01:00
Paul Miller
ed81707bdc readme 2023-02-16 00:12:23 +01:00
Paul Miller
6d56b2d78e readme 2023-02-16 00:08:18 +01:00
Paul Miller
8397241a8f bls, stark: adjust methods 2023-02-16 00:03:20 +01:00
Paul Miller
001d0cc24a weierstrass: rename method, adjust comments 2023-02-16 00:03:10 +01:00
Paul Miller
ce9d165657 readme hash-to-scalar 2023-02-15 23:46:43 +01:00
Paul Miller
2902b0299a readme 2023-02-15 23:38:26 +01:00
Paul Miller
e1cb8549e8 weierstrass, montgomery, secp: add comments 2023-02-15 23:26:56 +01:00
Paul Miller
26ebb5dcce x25519, x448: change param from a24 to a. Change Gu to bigint 2023-02-15 23:07:52 +01:00
Paul Miller
8b2863aeac Fix benchmark 2023-02-15 22:50:32 +01:00
Paul Miller
b1f50d9364 hash-to-curve: bls examples 2023-02-15 00:08:38 +01:00
Paul Miller
b81d74d3cb readme 2023-02-15 00:06:39 +01:00
Paul Miller
d5fe537159 hash-to-curve readme 2023-02-15 00:03:18 +01:00
Paul Miller
cde1d5c488 Fix tests 2023-02-14 23:51:11 +01:00
Paul Miller
3486bbf6b8 Release 0.7.0. 2023-02-14 23:45:53 +01:00
Paul Miller
0d7a8296c5 gitignore update 2023-02-14 23:45:39 +01:00
Paul Miller
0f1e7a5a43 Move output from lib to root. React Native does not support pkg.json#exports 2023-02-14 23:43:28 +01:00
Paul Miller
3da48cf899 bump bmark 2023-02-14 23:24:11 +01:00
Paul Miller
4ec46dd65d Remove scure-base from top-level dep 2023-02-14 18:00:11 +01:00
Paul Miller
7073f63c6b drbg: move from weierstrass to utils 2023-02-14 17:54:57 +01:00
Paul Miller
80966cbd03 hash-to-curve: more type checks. Rename method to createHasher 2023-02-14 17:39:56 +01:00
Paul Miller
98ea15dca4 edwards: improve hex errors 2023-02-14 17:35:19 +01:00
Paul Miller
e1910e85ea mod, utils, weierstrass, secp: improve hex errors. secp: improve verify() logic and schnorr 2023-02-14 17:34:31 +01:00
Paul Miller
4d311d7294 Emit source maps 2023-02-14 17:23:51 +01:00
Paul Miller
c36d90cae6 bump lockfile, add comment to shortw 2023-02-13 23:55:58 +01:00
Paul Miller
af5aa8424f readme: supply chain attacks 2023-02-13 23:32:49 +01:00
Paul Miller
67b99652fc BLS: add docs 2023-02-12 22:25:36 +01:00
Paul Miller
c8d292976b README 2023-02-12 22:25:22 +01:00
Paul Miller
daffaa2339 README: more docs 2023-02-12 21:37:27 +01:00
Paul Miller
a462fc5779 readme updates 2023-02-12 11:30:55 +01:00
Paul Miller
fe3491c5aa Release 0.6.4. 2023-02-09 23:19:15 +01:00
Paul Miller
c0877ba69a Fix weierstrass type 2023-02-09 23:18:32 +01:00
Paul Miller
8e449cc78c ed25519 tests: unify with noble-ed25519 2023-02-09 21:26:24 +01:00
Paul Miller
1b6071cabd weierstrass: rename normPrivKey util. tests: prepare for unification w old noble pkg 2023-02-09 20:26:20 +01:00
Paul Miller
debb9d9709 Release 0.6.3. 2023-02-09 16:19:08 +01:00
Paul Miller
d2c6459756 Update wnaf comments 2023-02-09 15:45:21 +01:00
Paul Miller
47533b6336 Add more tests for weierstrass, etc 2023-02-09 13:29:19 +01:00
Paul Miller
00b73b68d3 hash-to-curve small refactor 2023-02-06 20:50:52 +01:00
Paul Miller
cef4b52d12 Update hashes to 1.2, scure devdeps, add lockfile 2023-02-06 20:50:41 +01:00
Paul Miller
47ce547dcf README update 2023-02-06 20:50:23 +01:00
Paul Miller
e2a7594eae Release 0.6.2. 2023-01-30 08:18:07 +01:00
Paul Miller
823149ecd9 Clarify comment 2023-01-30 08:17:08 +01:00
Paul Miller
e57aec63d8 Fix edwards assertValidity 2023-01-30 08:04:36 +01:00
Paul Miller
837aca98c9 Fix bugs 2023-01-30 06:10:56 +01:00
Paul Miller
dbb16b0e5e edwards: add assertValidity 2023-01-30 06:10:08 +01:00
Paul Miller
e14af67254 utils: fix hexToNumber, improve validateObject 2023-01-30 06:07:53 +01:00
Paul Miller
4780850748 montgomery: fix fieldLen 2023-01-30 05:56:07 +01:00
Paul Miller
3374a70f47 README update 2023-01-30 05:55:36 +01:00
Paul Miller
131f88b504 Release 0.6.1. 2023-01-29 05:14:10 +01:00
Paul Miller
4333e9a686 README 2023-01-29 05:12:58 +01:00
Paul Miller
a60d15ff05 Upgrading guide from other noble libraries 2023-01-29 05:10:58 +01:00
Paul Miller
ceffbc69da More Schnorr utils 2023-01-29 04:46:38 +01:00
Paul Miller
c75129e629 Use declarative curve field validation 2023-01-28 03:19:46 +01:00
Paul Miller
f39fb80c52 weierstrass: rename normalizePrivateKey to allowedPrivateKeyLengths 2023-01-27 23:45:55 +01:00
Paul Miller
fcd422d246 README updates 2023-01-27 03:48:53 +01:00
Paul Miller
ed9bf89038 stark: isCompressed=false. Update benchmarks 2023-01-27 03:43:18 +01:00
Paul Miller
7262b4219f Bump micro-should 2023-01-26 08:26:07 +01:00
Paul Miller
02b0b25147 New schnorr exports. Simplify RFC6979 k gen, privkey checks 2023-01-26 08:16:00 +01:00
59 changed files with 2647 additions and 2156 deletions

14
.gitignore vendored
View File

@@ -1,7 +1,13 @@
build/ build/
node_modules/ node_modules/
coverage/ coverage/
/lib/**/*.js /*.js
/lib/**/*.ts /*.ts
/lib/**/*.d.ts.map /*.js.map
/curve-definitions/lib /*.d.ts.map
/esm/*.js
/esm/*.ts
/esm/*.js.map
/esm/*.d.ts.map
/esm/abstract
/abstract/

874
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| >=0.5.0 | :white_check_mark: | | >=1.0.0 | :white_check_mark: |
| <0.5.0 | :x: | | <1.0.0 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

7
benchmark/_shared.js Normal file
View File

@@ -0,0 +1,7 @@
export function generateData(curve) {
const priv = curve.utils.randomPrivateKey();
const pub = curve.getPublicKey(priv);
const msg = curve.utils.randomPrivateKey();
const sig = curve.sign(msg, priv);
return { priv, pub, msg, sig };
}

52
benchmark/bls.js Normal file
View File

@@ -0,0 +1,52 @@
import { readFileSync } from 'fs';
import { mark, run } from 'micro-bmark';
import { bls12_381 as bls } from '../bls12-381.js';
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
run(async () => {
console.log(`\x1b[36mbls12-381\x1b[0m`);
let p1, p2, sig;
await mark('init', 1, () => {
p1 =
bls.G1.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
p2 =
bls.G2.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
bls.pairing(p1, p2);
});
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
sig = bls.sign('09', priv);
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
const sigs = G2_VECTORS.map((v) => v[2]);
const pub = bls.getPublicKey(priv);
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
const pub32 = pub512.slice(0, 32);
const pub128 = pub512.slice(0, 128);
const pub2048 = pub512.concat(pub512, pub512, pub512);
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
const sig32 = sig512.slice(0, 32);
const sig128 = sig512.slice(0, 128);
const sig2048 = sig512.concat(sig512, sig512, sig512);
await mark('getPublicKey 1-bit', 1000, () => bls.getPublicKey('2'.padStart(64, '0')));
await mark('getPublicKey', 1000, () => bls.getPublicKey(priv));
await mark('sign', 50, () => bls.sign('09', priv));
await mark('verify', 50, () => bls.verify(sig, '09', pub));
await mark('pairing', 100, () => bls.pairing(p1, p2));
await mark('aggregatePublicKeys/8', 100, () => bls.aggregatePublicKeys(pubs.slice(0, 8)));
await mark('aggregatePublicKeys/32', 50, () => bls.aggregatePublicKeys(pub32));
await mark('aggregatePublicKeys/128', 20, () => bls.aggregatePublicKeys(pub128));
await mark('aggregatePublicKeys/512', 10, () => bls.aggregatePublicKeys(pub512));
await mark('aggregatePublicKeys/2048', 5, () => bls.aggregatePublicKeys(pub2048));
await mark('aggregateSignatures/8', 100, () => bls.aggregateSignatures(sigs.slice(0, 8)));
await mark('aggregateSignatures/32', 50, () => bls.aggregateSignatures(sig32));
await mark('aggregateSignatures/128', 20, () => bls.aggregateSignatures(sig128));
await mark('aggregateSignatures/512', 10, () => bls.aggregateSignatures(sig512));
await mark('aggregateSignatures/2048', 5, () => bls.aggregateSignatures(sig2048));
});

23
benchmark/curves.js Normal file
View File

@@ -0,0 +1,23 @@
import { run, mark, utils } from 'micro-bmark';
import { generateData } from './_shared.js';
import { P256 } from '../p256.js';
import { P384 } from '../p384.js';
import { P521 } from '../p521.js';
import { ed25519 } from '../ed25519.js';
import { ed448 } from '../ed448.js';
run(async () => {
const RAM = false
for (let kv of Object.entries({ P256, P384, P521, ed25519, ed448 })) {
const [name, curve] = kv;
console.log();
console.log(`\x1b[36m${name}\x1b[0m`);
if (RAM) utils.logMem();
await mark('init', 1, () => curve.utils.precompute(8));
const d = generateData(curve);
await mark('getPublicKey', 5000, () => curve.getPublicKey(d.priv));
await mark('sign', 5000, () => curve.sign(d.msg, d.priv));
await mark('verify', 500, () => curve.verify(d.sig, d.msg, d.pub));
if (RAM) utils.logMem();
}
});

19
benchmark/ecdh.js Normal file
View File

@@ -0,0 +1,19 @@
import { run, mark, compare, utils } from 'micro-bmark';
import { generateData } from './_shared.js';
import { secp256k1 } from '../secp256k1.js';
import { P256 } from '../p256.js';
import { P384 } from '../p384.js';
import { P521 } from '../p521.js';
import { x25519 } from '../ed25519.js';
import { x448 } from '../ed448.js';
run(async () => {
const curves = { x25519, secp256k1, P256, P384, P521, x448 };
const fns = {};
for (let [k, c] of Object.entries(curves)) {
const pubB = c.getPublicKey(c.utils.randomPrivateKey());
const privA = c.utils.randomPrivateKey();
fns[k] = () => c.getSharedSecret(privA, pubB);
}
await compare('ecdh', 1000, fns);
});

View File

@@ -0,0 +1,29 @@
import { run, mark, utils } from 'micro-bmark';
import { hash_to_field } from '../abstract/hash-to-curve.js';
import { hashToPrivateScalar } from '../abstract/modular.js';
import { randomBytes } from '@noble/hashes/utils';
import { sha256 } from '@noble/hashes/sha256';
// import { generateData } from './_shared.js';
import { hashToCurve as secp256k1 } from '../secp256k1.js';
import { hashToCurve as P256 } from '../p256.js';
import { hashToCurve as P384 } from '../p384.js';
import { hashToCurve as P521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
run(async () => {
const rand = randomBytes(40);
await mark('hashToPrivateScalar', 1000000, () => hashToPrivateScalar(rand, N));
// - p, the characteristic of F
// - m, the extension degree of F, m >= 1
// - L = ceil((ceil(log2(p)) + k) / 8), where k is the security of suite (e.g. 128)
await mark('hash_to_field', 1000000, () =>
hash_to_field(rand, 1, { DST: 'secp256k1', hash: sha256, p: N, m: 1, k: 128 })
);
const msg = utf8ToBytes('message');
for (let [title, fn] of Object.entries({ secp256k1, P256, P384, P521, ed25519, ed448 })) {
await mark(`hashToCurve ${title}`, 1000, () => fn(msg));
}
});

View File

@@ -1,424 +0,0 @@
import * as bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark
import { readFileSync } from 'fs';
// Curves
import { secp256k1 } from '../lib/secp256k1.js';
import { P256 } from '../lib/p256.js';
import { P384 } from '../lib/p384.js';
import { P521 } from '../lib/p521.js';
import { ed25519 } from '../lib/ed25519.js';
import { ed448 } from '../lib/ed448.js';
import { bls12_381 as bls } from '../lib/bls12-381.js';
// Others
import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import * as old_secp from '@noble/secp256k1';
import * as old_bls from '@noble/bls12-381';
import { concatBytes, hexToBytes } from '@noble/hashes/utils';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as stark from '../lib/stark.js';
old_secp.utils.sha256Sync = (...msgs) =>
sha256
.create()
.update(concatBytes(...msgs))
.digest();
old_secp.utils.hmacSha256Sync = (key, ...msgs) =>
hmac
.create(sha256, key)
.update(concatBytes(...msgs))
.digest();
import * as noble_ed25519 from '@noble/ed25519';
noble_ed25519.utils.sha512Sync = (...m) => sha512(concatBytes(...m));
// BLS
const G2_VECTORS = readFileSync('../test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
let p1, p2, oldp1, oldp2;
// /BLS
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521]) item.utils.precompute(8);
for (let item of [old_secp, noble_ed25519]) item.utils.precompute(8);
const ONLY_NOBLE = process.argv[2] === 'noble';
function generateData(namespace) {
const priv = namespace.utils.randomPrivateKey();
const pub = namespace.getPublicKey(priv);
const msg = namespace.utils.randomPrivateKey();
const sig = namespace.sign(msg, priv);
return { priv, pub, msg, sig };
}
export const CURVES = {
secp256k1: {
data: () => {
return generateData(secp256k1);
},
getPublicKey1: {
samples: 10000,
secp256k1_old: () => old_secp.getPublicKey(3n),
secp256k1: () => secp256k1.getPublicKey(3n),
},
getPublicKey255: {
samples: 10000,
secp256k1_old: () => old_secp.getPublicKey(2n ** 255n - 1n),
secp256k1: () => secp256k1.getPublicKey(2n ** 255n - 1n),
},
sign: {
samples: 5000,
secp256k1_old: ({ msg, priv }) => old_secp.signSync(msg, priv),
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv).toCompactRawBytes(),
},
verify: {
samples: 1000,
secp256k1_old: ({ sig, msg, pub }) => {
return old_secp.verify(new old_secp.Signature(sig.r, sig.s), msg, pub);
},
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig, msg, pub),
},
getSharedSecret: {
samples: 1000,
secp256k1_old: ({ pub, priv }) => old_secp.getSharedSecret(priv, pub),
secp256k1: ({ pub, priv }) => secp256k1.getSharedSecret(priv, pub),
},
recoverPublicKey: {
samples: 1000,
secp256k1_old: ({ sig, msg }) =>
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
},
// hashToCurve: {
// samples: 500,
// noble: () => secp256k1.Point.hashToCurve('abcd'),
// },
},
ed25519: {
data: () => {
function to32Bytes(numOrStr) {
const hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
return hexToBytes(hex.padStart(64, '0'));
}
const priv = to32Bytes(0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60n);
const pub = noble_ed25519.sync.getPublicKey(priv);
const msg = to32Bytes('deadbeefdeadbeefdeadbeefdeadbeefdeadbeef');
const sig = noble_ed25519.sync.sign(msg, priv);
return { pub, priv, msg, sig };
},
getPublicKey: {
samples: 10000,
old: () => noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
noble: () => ed25519.getPublicKey(ed25519.utils.randomPrivateKey()),
},
sign: {
samples: 5000,
old: ({ msg, priv }) => noble_ed25519.sync.sign(msg, priv),
noble: ({ msg, priv }) => ed25519.sign(msg, priv),
},
verify: {
samples: 1000,
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
},
// hashToCurve: {
// samples: 500,
// noble: () => ed25519.Point.hashToCurve('abcd'),
// },
},
ed448: {
data: () => {
const priv = ed448.utils.randomPrivateKey();
const pub = ed448.getPublicKey(priv);
const msg = ed448.utils.randomPrivateKey();
const sig = ed448.sign(msg, priv);
return { priv, pub, msg, sig };
},
getPublicKey: {
samples: 5000,
noble: () => ed448.getPublicKey(ed448.utils.randomPrivateKey()),
},
sign: {
samples: 2500,
noble: ({ msg, priv }) => ed448.sign(msg, priv),
},
verify: {
samples: 500,
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
},
// hashToCurve: {
// samples: 500,
// noble: () => ed448.Point.hashToCurve('abcd'),
// },
},
nist: {
data: () => {
return { p256: generateData(P256), p384: generateData(P384), p521: generateData(P521) };
},
getPublicKey: {
samples: 2500,
P256: () => P256.getPublicKey(P256.utils.randomPrivateKey()),
P384: () => P384.getPublicKey(P384.utils.randomPrivateKey()),
P521: () => P521.getPublicKey(P521.utils.randomPrivateKey()),
},
sign: {
samples: 1000,
P256: ({ p256: { msg, priv } }) => P256.sign(msg, priv),
P384: ({ p384: { msg, priv } }) => P384.sign(msg, priv),
P521: ({ p521: { msg, priv } }) => P521.sign(msg, priv),
},
verify: {
samples: 250,
P256: ({ p256: { sig, msg, pub } }) => P256.verify(sig, msg, pub),
P384: ({ p384: { sig, msg, pub } }) => P384.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: {
data: () => {
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const pub = stark.getPublicKey(priv);
const sig = stark.sign(msg, priv);
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair };
},
pedersen: {
samples: 500,
old: () => {
return starkwareCrypto.default.pedersen([
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
]);
},
noble: () => {
return stark.pedersen(
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
);
},
},
poseidon: {
samples: 2000,
noble: () => {
return stark.poseidonHash(
0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cbn,
0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31an
);
},
},
verify: {
samples: 500,
old: ({ publicKeyStark, msgHash, keyPair }) => {
return starkwareCrypto.default.verify(
publicKeyStark,
msgHash,
starkwareCrypto.default.sign(keyPair, msgHash)
);
},
noble: ({ priv, msg, pub }) => {
return stark.verify(stark.sign(msg, priv), msg, pub);
},
},
},
'bls12-381': {
data: async () => {
const priv = '28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4c';
const pubs = G2_VECTORS.map((v) => bls.getPublicKey(v[0]));
const sigs = G2_VECTORS.map((v) => v[2]);
const pub = bls.getPublicKey(priv);
const pub512 = pubs.slice(0, 512); // .map(bls.PointG1.fromHex)
const pub32 = pub512.slice(0, 32);
const pub128 = pub512.slice(0, 128);
const pub2048 = pub512.concat(pub512, pub512, pub512);
const sig512 = sigs.slice(0, 512); // .map(bls.PointG2.fromSignature);
const sig32 = sig512.slice(0, 32);
const sig128 = sig512.slice(0, 128);
const sig2048 = sig512.concat(sig512, sig512, sig512);
return {
priv,
pubs,
sigs,
pub,
pub512,
pub32,
pub128,
pub2048,
sig32,
sig128,
sig512,
sig2048,
};
},
init: {
samples: 1,
old: () => {
oldp1 =
old_bls.PointG1.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
oldp2 =
old_bls.PointG2.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
old_bls.pairing(oldp1, oldp2);
},
noble: () => {
p1 =
bls.G1.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4cn
);
p2 =
bls.G2.ProjectivePoint.BASE.multiply(
0x28b90deaf189015d3a325908c5e0e4bf00f84f7e639b056ff82d7e70b6eede4dn
);
bls.pairing(p1, p2);
},
},
'getPublicKey (1-bit)': {
samples: 1000,
old: () => old_bls.getPublicKey('2'.padStart(64, '0')),
noble: () => bls.getPublicKey('2'.padStart(64, '0')),
},
getPublicKey: {
samples: 1000,
old: ({ priv }) => old_bls.getPublicKey(priv),
noble: ({ priv }) => bls.getPublicKey(priv),
},
sign: {
samples: 50,
old: ({ priv }) => old_bls.sign('09', priv),
noble: ({ priv }) => bls.sign('09', priv),
},
verify: {
samples: 50,
old: ({ pub }) =>
old_bls.verify(
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
'09',
pub
),
noble: ({ pub }) =>
bls.verify(
'8647aa9680cd0cdf065b94e818ff2bb948cc97838bcee987b9bc1b76d0a0a6e0d85db4e9d75aaedfc79d4ea2733a21ae0579014de7636dd2943d45b87c82b1c66a289006b0b9767921bb8edd3f6c5c5dec0d54cd65f61513113c50cc977849e5',
'09',
pub
),
},
pairing: {
samples: 100,
old: () => old_bls.pairing(oldp1, oldp2),
noble: () => bls.pairing(p1, p2),
},
// 'hashToCurve/G1': {
// samples: 500,
// old: () => old_bls.PointG1.hashToCurve('abcd'),
// noble: () => bls.hashToCurve.G1.hashToCurve('abcd'),
// },
// 'hashToCurve/G2': {
// samples: 200,
// old: () => old_bls.PointG2.hashToCurve('abcd'),
// noble: () => bls.hashToCurve.G2.hashToCurve('abcd'),
// },
// SLOW PART
// Requires points which we cannot init before (data fn same for all)
// await mark('sign/nc', 30, () => bls.sign(msgp, priv));
// await mark('verify/nc', 30, () => bls.verify(sigp, msgp, pubp));
'aggregatePublicKeys/8': {
samples: 100,
old: ({ pubs }) => old_bls.aggregatePublicKeys(pubs.slice(0, 8)),
noble: ({ pubs }) => bls.aggregatePublicKeys(pubs.slice(0, 8)),
},
'aggregatePublicKeys/32': {
samples: 50,
old: ({ pub32 }) => old_bls.aggregatePublicKeys(pub32.map(old_bls.PointG1.fromHex)),
noble: ({ pub32 }) => bls.aggregatePublicKeys(pub32.map(bls.G1.ProjectivePoint.fromHex)),
},
'aggregatePublicKeys/128': {
samples: 20,
old: ({ pub128 }) => old_bls.aggregatePublicKeys(pub128.map(old_bls.PointG1.fromHex)),
noble: ({ pub128 }) => bls.aggregatePublicKeys(pub128.map(bls.G1.ProjectivePoint.fromHex)),
},
'aggregatePublicKeys/512': {
samples: 10,
old: ({ pub512 }) => old_bls.aggregatePublicKeys(pub512.map(old_bls.PointG1.fromHex)),
noble: ({ pub512 }) => bls.aggregatePublicKeys(pub512.map(bls.G1.ProjectivePoint.fromHex)),
},
'aggregatePublicKeys/2048': {
samples: 5,
old: ({ pub2048 }) => old_bls.aggregatePublicKeys(pub2048.map(old_bls.PointG1.fromHex)),
noble: ({ pub2048 }) => bls.aggregatePublicKeys(pub2048.map(bls.G1.ProjectivePoint.fromHex)),
},
'aggregateSignatures/8': {
samples: 50,
old: ({ sigs }) => old_bls.aggregateSignatures(sigs.slice(0, 8)),
noble: ({ sigs }) => bls.aggregateSignatures(sigs.slice(0, 8)),
},
'aggregateSignatures/32': {
samples: 10,
old: ({ sig32 }) => old_bls.aggregateSignatures(sig32.map(old_bls.PointG2.fromSignature)),
noble: ({ sig32 }) => bls.aggregateSignatures(sig32.map(bls.Signature.decode)),
},
'aggregateSignatures/128': {
samples: 5,
old: ({ sig128 }) => old_bls.aggregateSignatures(sig128.map(old_bls.PointG2.fromSignature)),
noble: ({ sig128 }) => bls.aggregateSignatures(sig128.map(bls.Signature.decode)),
},
'aggregateSignatures/512': {
samples: 3,
old: ({ sig512 }) => old_bls.aggregateSignatures(sig512.map(old_bls.PointG2.fromSignature)),
noble: ({ sig512 }) => bls.aggregateSignatures(sig512.map(bls.Signature.decode)),
},
'aggregateSignatures/2048': {
samples: 2,
old: ({ sig2048 }) => old_bls.aggregateSignatures(sig2048.map(old_bls.PointG2.fromSignature)),
noble: ({ sig2048 }) => bls.aggregateSignatures(sig2048.map(bls.Signature.decode)),
},
},
};
const main = () =>
run(async () => {
for (const [name, curve] of Object.entries(CURVES)) {
console.log(`==== ${name} ====`);
const data = await curve.data();
for (const [fnName, libs] of Object.entries(curve)) {
if (fnName === 'data') continue;
const samples = libs.samples;
console.log(` - ${fnName} (samples: ${samples})`);
for (const [lib, fn] of Object.entries(libs)) {
if (lib === 'samples') continue;
if (ONLY_NOBLE && lib !== 'noble') continue;
await mark(` ${lib}`, samples, () => fn(data));
}
}
}
// Log current RAM
bench.logMem();
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
main();
}

13
benchmark/modular.js Normal file
View File

@@ -0,0 +1,13 @@
import { run, mark } from 'micro-bmark';
import { secp256k1 } from '../secp256k1.js';
import { Fp } from '../abstract/modular.js';
run(async () => {
console.log(`\x1b[36mmodular, secp256k1 field\x1b[0m`);
const { Fp: secpFp } = secp256k1.CURVE;
await mark('invert a', 300000, () => secpFp.inv(2n ** 232n - 5910n));
await mark('invert b', 300000, () => secpFp.inv(2n ** 231n - 5910n));
await mark('sqrt p = 3 mod 4', 15000, () => secpFp.sqrt(2n ** 231n - 5910n));
const FpStark = Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001'));
await mark('sqrt tonneli-shanks', 500, () => FpStark.sqrt(2n ** 231n - 5909n))
});

View File

@@ -1,26 +1,22 @@
{ {
"name": "benchmark", "name": "benchmark",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.0",
"description": "benchmarks", "description": "benchmarks",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"bench": "node index.js" "bench": "node index.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"micro-bmark": "0.2.1" "micro-bmark": "0.3.0"
}, },
"dependencies": { "dependencies": {
"@noble/bls12-381": "^1.4.0", "@noble/hashes": "^1.1.5",
"@noble/ed25519": "^1.7.1", "@starkware-industries/starkware-crypto-utils": "^0.0.2",
"@noble/hashes": "^1.1.5", "elliptic": "^6.5.4"
"@noble/secp256k1": "^1.7.0", }
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
"calculate-correlation": "^1.2.3",
"elliptic": "^6.5.4"
}
} }

22
benchmark/secp256k1.js Normal file
View File

@@ -0,0 +1,22 @@
import { run, mark, utils } from 'micro-bmark';
import { secp256k1, schnorr } from '../secp256k1.js';
import { generateData } from './_shared.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36msecp256k1\x1b[0m`);
await mark('init', 1, () => secp256k1.utils.precompute(8));
const d = generateData(secp256k1);
await mark('getPublicKey', 10000, () => secp256k1.getPublicKey(d.priv));
await mark('sign', 10000, () => secp256k1.sign(d.msg, d.priv));
await mark('verify', 1000, () => secp256k1.verify(d.sig, d.msg, d.pub));
const pub2 = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
await mark('getSharedSecret', 1000, () => secp256k1.getSharedSecret(d.priv, pub2));
await mark('recoverPublicKey', 1000, () => d.sig.recoverPublicKey(d.msg));
const s = schnorr.sign(d.msg, d.priv);
const spub = schnorr.getPublicKey(d.priv);
await mark('schnorr.sign', 1000, () => schnorr.sign(d.msg, d.priv));
await mark('schnorr.verify', 1000, () => schnorr.verify(s, d.msg, spub));
if (RAM) utils.logMem();
});

56
benchmark/stark.js Normal file
View File

@@ -0,0 +1,56 @@
import { run, mark, compare, utils } from 'micro-bmark';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as stark from '../stark.js';
run(async () => {
const RAM = false;
if (RAM) utils.logMem();
console.log(`\x1b[36mstark\x1b[0m`);
await mark('init', 1, () => stark.utils.precompute(8));
const d = (() => {
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const pub = stark.getPublicKey(priv);
const sig = stark.sign(msg, priv);
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair };
})();
await compare('pedersen', 500, {
old: () => {
return starkwareCrypto.default.pedersen([
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
]);
},
noble: () => {
return stark.pedersen(
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
);
},
});
await mark('poseidon', 10000, () => stark.poseidonHash(
0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cbn,
0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31an
));
await compare('verify', 500, {
old: () => {
return starkwareCrypto.default.verify(
d.publicKeyStark,
d.msgHash,
starkwareCrypto.default.sign(d.keyPair, d.msgHash)
);
},
noble: () => {
return stark.verify(stark.sign(d.msg, d.priv), d.msg, d.pub);
},
});
if (RAM) utils.logMem();
});

178
package-lock.json generated Normal file
View File

@@ -0,0 +1,178 @@
{
"name": "@noble/curves",
"version": "0.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "0.8.0",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.2.0"
},
"devDependencies": {
"@scure/bip32": "~1.1.5",
"@scure/bip39": "~1.1.1",
"@types/node": "18.11.3",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.3",
"typescript": "4.7.3"
}
},
"node_modules/@noble/hashes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz",
"integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@scure/bip32": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz",
"integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@noble/secp256k1": "~1.7.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@scure/bip39": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz",
"integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"@noble/hashes": "~1.2.0",
"@scure/base": "~1.1.0"
}
},
"node_modules/@types/node": {
"version": "18.11.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz",
"integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==",
"dev": true
},
"node_modules/fast-check": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.0.0.tgz",
"integrity": "sha512-uujtrFJEQQqnIMO52ARwzPcuV4omiL1OJBUBLE9WnNFeu0A97sREXDOmCIHY+Z6KLVcemUf09rWr0q0Xy/Y/Ew==",
"dev": true,
"dependencies": {
"pure-rand": "^5.0.1"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
},
"node_modules/micro-bmark": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.3.1.tgz",
"integrity": "sha512-bNaKObD4yPAAPrpEqp5jO6LJ2sEFgLoFSmRjEY809mJ62+2AehI/K3+RlVpN3Oo92RHpgC2RQhj6b1Tb4dmo+w==",
"dev": true
},
"node_modules/micro-should": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/micro-should/-/micro-should-0.4.0.tgz",
"integrity": "sha512-Vclj8yrngSYc9Y3dL2C+AdUlTkyx/syWc4R7LYfk4h7+icfF0DoUBGjjUIaEDzZA19RzoI+Hg8rW9IRoNGP0tQ==",
"dev": true
},
"node_modules/prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pure-rand": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz",
"integrity": "sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
]
},
"node_modules/typescript": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
}
}
}

View File

@@ -1,13 +1,19 @@
{ {
"name": "@noble/curves", "name": "@noble/curves",
"version": "0.6.0", "version": "0.8.0",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography", "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [ "files": [
"lib" "abstract",
"src",
"*.js",
"*.js.map",
"*.d.ts",
"*.d.ts.map"
], ],
"scripts": { "scripts": {
"bench": "cd benchmark; node index.js", "bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node stark.js; node bls.js",
"build": "tsc && tsc -p tsconfig.esm.json", "build": "tsc",
"build:clean": "rm *.{js,d.ts,js.map} esm/*.{js,js.map} 2> /dev/null",
"build:release": "rollup -c rollup.config.js", "build:release": "rollup -c rollup.config.js",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'", "lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'", "format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
@@ -21,147 +27,112 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.1.5" "@noble/hashes": "1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "13.3.0", "@scure/bip32": "~1.1.5",
"@scure/base": "~1.1.1", "@scure/bip39": "~1.1.1",
"@scure/bip32": "~1.1.1",
"@scure/bip39": "~1.1.0",
"@types/node": "18.11.3", "@types/node": "18.11.3",
"fast-check": "3.0.0", "fast-check": "3.0.0",
"micro-bmark": "0.2.0", "micro-bmark": "0.3.1",
"micro-should": "0.3.0", "micro-should": "0.4.0",
"prettier": "2.8.3", "prettier": "2.8.3",
"rollup": "2.75.5",
"typescript": "4.7.3" "typescript": "4.7.3"
}, },
"main": "index.js", "main": "index.js",
"type": "module",
"exports": { "exports": {
".": { ".": {
"types": "./lib/index.d.ts", "types": "./index.d.ts",
"import": "./lib/esm/index.js", "default": "./index.js"
"default": "./lib/index.js"
}, },
"./abstract/edwards": { "./abstract/edwards": {
"types": "./lib/abstract/edwards.d.ts", "types": "./abstract/edwards.d.ts",
"import": "./lib/esm/abstract/edwards.js", "default": "./abstract/edwards.js"
"default": "./lib/abstract/edwards.js"
}, },
"./abstract/modular": { "./abstract/modular": {
"types": "./lib/abstract/modular.d.ts", "types": "./abstract/modular.d.ts",
"import": "./lib/esm/abstract/modular.js", "default": "./abstract/modular.js"
"default": "./lib/abstract/modular.js"
}, },
"./abstract/montgomery": { "./abstract/montgomery": {
"types": "./lib/abstract/montgomery.d.ts", "types": "./abstract/montgomery.d.ts",
"import": "./lib/esm/abstract/montgomery.js", "default": "./abstract/montgomery.js"
"default": "./lib/abstract/montgomery.js"
}, },
"./abstract/weierstrass": { "./abstract/weierstrass": {
"types": "./lib/abstract/weierstrass.d.ts", "types": "./abstract/weierstrass.d.ts",
"import": "./lib/esm/abstract/weierstrass.js", "default": "./abstract/weierstrass.js"
"default": "./lib/abstract/weierstrass.js"
}, },
"./abstract/bls": { "./abstract/bls": {
"types": "./lib/abstract/bls.d.ts", "types": "./abstract/bls.d.ts",
"import": "./lib/esm/abstract/bls.js", "default": "./abstract/bls.js"
"default": "./lib/abstract/bls.js"
}, },
"./abstract/hash-to-curve": { "./abstract/hash-to-curve": {
"types": "./lib/abstract/hash-to-curve.d.ts", "types": "./abstract/hash-to-curve.d.ts",
"import": "./lib/esm/abstract/hash-to-curve.js", "default": "./abstract/hash-to-curve.js"
"default": "./lib/abstract/hash-to-curve.js"
}, },
"./abstract/curve": { "./abstract/curve": {
"types": "./lib/abstract/curve.d.ts", "types": "./abstract/curve.d.ts",
"import": "./lib/esm/abstract/curve.js", "default": "./abstract/curve.js"
"default": "./lib/abstract/curve.js"
}, },
"./abstract/utils": { "./abstract/utils": {
"types": "./lib/abstract/utils.d.ts", "types": "./abstract/utils.d.ts",
"import": "./lib/esm/abstract/utils.js", "default": "./abstract/utils.js"
"default": "./lib/abstract/utils.js"
}, },
"./abstract/poseidon": { "./abstract/poseidon": {
"types": "./lib/abstract/poseidon.d.ts", "types": "./abstract/poseidon.d.ts",
"import": "./lib/esm/abstract/poseidon.js", "default": "./abstract/poseidon.js"
"default": "./lib/abstract/poseidon.js"
}, },
"./_shortw_utils": { "./_shortw_utils": {
"types": "./lib/_shortw_utils.d.ts", "types": "./_shortw_utils.d.ts",
"import": "./lib/esm/_shortw_utils.js", "default": "./_shortw_utils.js"
"default": "./lib/_shortw_utils.js"
}, },
"./bls12-381": { "./bls12-381": {
"types": "./lib/bls12-381.d.ts", "types": "./bls12-381.d.ts",
"import": "./lib/esm/bls12-381.js", "default": "./bls12-381.js"
"default": "./lib/bls12-381.js"
}, },
"./bn": { "./bn": {
"types": "./lib/bn.d.ts", "types": "./bn.d.ts",
"import": "./lib/esm/bn.js", "default": "./bn.js"
"default": "./lib/bn.js"
}, },
"./ed25519": { "./ed25519": {
"types": "./lib/ed25519.d.ts", "types": "./ed25519.d.ts",
"import": "./lib/esm/ed25519.js", "default": "./ed25519.js"
"default": "./lib/ed25519.js"
}, },
"./ed448": { "./ed448": {
"types": "./lib/ed448.d.ts", "types": "./ed448.d.ts",
"import": "./lib/esm/ed448.js", "default": "./ed448.js"
"default": "./lib/ed448.js"
}, },
"./index": { "./index": {
"types": "./lib/index.d.ts", "types": "./index.d.ts",
"import": "./lib/esm/index.js", "default": "./index.js"
"default": "./lib/index.js"
}, },
"./jubjub": { "./jubjub": {
"types": "./lib/jubjub.d.ts", "types": "./jubjub.d.ts",
"import": "./lib/esm/jubjub.js", "default": "./jubjub.js"
"default": "./lib/jubjub.js"
},
"./p192": {
"types": "./lib/p192.d.ts",
"import": "./lib/esm/p192.js",
"default": "./lib/p192.js"
},
"./p224": {
"types": "./lib/p224.d.ts",
"import": "./lib/esm/p224.js",
"default": "./lib/p224.js"
}, },
"./p256": { "./p256": {
"types": "./lib/p256.d.ts", "types": "./p256.d.ts",
"import": "./lib/esm/p256.js", "default": "./p256.js"
"default": "./lib/p256.js"
}, },
"./p384": { "./p384": {
"types": "./lib/p384.d.ts", "types": "./p384.d.ts",
"import": "./lib/esm/p384.js", "default": "./p384.js"
"default": "./lib/p384.js"
}, },
"./p521": { "./p521": {
"types": "./lib/p521.d.ts", "types": "./p521.d.ts",
"import": "./lib/esm/p521.js", "default": "./p521.js"
"default": "./lib/p521.js"
}, },
"./pasta": { "./pasta": {
"types": "./lib/pasta.d.ts", "types": "./pasta.d.ts",
"import": "./lib/esm/pasta.js", "default": "./pasta.js"
"default": "./lib/pasta.js"
}, },
"./secp256k1": { "./secp256k1": {
"types": "./lib/secp256k1.d.ts", "types": "./secp256k1.d.ts",
"import": "./lib/esm/secp256k1.js", "default": "./secp256k1.js"
"default": "./lib/secp256k1.js"
}, },
"./stark": { "./stark": {
"types": "./lib/stark.d.ts", "types": "./stark.d.ts",
"import": "./lib/esm/stark.js", "default": "./stark.js"
"default": "./lib/stark.js"
} }
}, },
"keywords": [ "keywords": [

View File

@@ -4,6 +4,7 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
import { weierstrass, CurveType } from './abstract/weierstrass.js'; import { weierstrass, CurveType } from './abstract/weierstrass.js';
import { CHash } from './abstract/utils.js'; import { CHash } from './abstract/utils.js';
// connects noble-curves to noble-hashes
export function getHash(hash: CHash) { export function getHash(hash: CHash) {
return { return {
hash, hash,

View File

@@ -13,7 +13,7 @@
*/ */
import { AffinePoint } from './curve.js'; import { AffinePoint } from './curve.js';
import { Field, hashToPrivateScalar } from './modular.js'; import { Field, hashToPrivateScalar } from './modular.js';
import { Hex, PrivKey, CHash, bitLen, bitGet, hexToBytes, bytesToHex } 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 {
CurvePointsType, CurvePointsType,
@@ -67,16 +67,11 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
Fp2: Field<Fp2>; Fp2: Field<Fp2>;
Fp6: Field<Fp6>; Fp6: Field<Fp6>;
Fp12: Field<Fp12>; Fp12: Field<Fp12>;
G1: CurvePointsRes<Fp>; G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2>; G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>; Signature: SignatureCoder<Fp2>;
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12; millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][]; calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
// prettier-ignore
hashToCurve: {
G1: ReturnType<(typeof htf.hashToCurve<Fp>)>,
G2: ReturnType<(typeof htf.hashToCurve<Fp2>)>,
},
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12; pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
getPublicKey: (privateKey: PrivKey) => Uint8Array; getPublicKey: (privateKey: PrivKey) => Uint8Array;
sign: { sign: {
@@ -102,16 +97,14 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
publicKeys: (Hex | ProjPointType<Fp>)[] publicKeys: (Hex | ProjPointType<Fp>)[]
) => boolean; ) => boolean;
utils: { utils: {
stringToBytes: typeof htf.stringToBytes; randomPrivateKey: () => Uint8Array;
hashToField: typeof htf.hash_to_field;
expandMessageXMD: typeof htf.expand_message_xmd;
}; };
}; };
export function bls<Fp2, Fp6, Fp12>( export function bls<Fp2, Fp6, Fp12>(
CURVE: CurveType<Fp, Fp2, Fp6, Fp12> CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
): CurveFn<Fp, Fp2, Fp6, Fp12> { ): CurveFn<Fp, Fp2, Fp6, Fp12> {
// Fields looks pretty specific for curve, so for now we need to pass them with options // Fields looks pretty specific for curve, so for now we need to pass them with opts
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE; const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE;
const BLS_X_LEN = bitLen(CURVE.x); const BLS_X_LEN = bitLen(CURVE.x);
const groupLen = 32; // TODO: calculate; hardcoded for now const groupLen = 32; // TODO: calculate; hardcoded for now
@@ -180,31 +173,20 @@ export function bls<Fp2, Fp6, Fp12>(
} }
const utils = { const utils = {
hexToBytes: hexToBytes, randomPrivateKey: (): Uint8Array => {
bytesToHex: bytesToHex, return Fr.toBytes(hashToPrivateScalar(CURVE.randomBytes(groupLen + 8), CURVE.r));
stringToBytes: htf.stringToBytes, },
// TODO: do we need to export it here?
hashToField: (
msg: Uint8Array,
count: number,
options: Partial<typeof CURVE.htfDefaults> = {}
) => htf.hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
expandMessageXMD: (msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H = CURVE.hash) =>
htf.expand_message_xmd(msg, DST, lenInBytes, H),
hashToPrivateKey: (hash: Hex): Uint8Array => Fr.toBytes(hashToPrivateScalar(hash, CURVE.r)),
randomBytes: (bytesLength: number = groupLen): Uint8Array => CURVE.randomBytes(bytesLength),
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(utils.randomBytes(groupLen + 8)),
}; };
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
const G1 = weierstrassPoints({ const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
n: Fr.ORDER, const G1 = Object.assign(
...CURVE.G1, G1_,
}); htf.createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
const G1HashToCurve = htf.hashToCurve(G1.ProjectivePoint, CURVE.G1.mapToCurve, { ...CURVE.htfDefaults,
...CURVE.htfDefaults, ...CURVE.G1.htfDefaults,
...CURVE.G1.htfDefaults, })
}); );
// Sparse multiplication against precomputed coefficients // Sparse multiplication against precomputed coefficients
// TODO: replace with weakmap? // TODO: replace with weakmap?
@@ -223,15 +205,14 @@ export function bls<Fp2, Fp6, Fp12>(
// } // }
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i) // Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
const G2 = weierstrassPoints({ const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
n: Fr.ORDER, const G2 = Object.assign(
...CURVE.G2, G2_,
}); htf.createHasher(G2_.ProjectivePoint as htf.H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
const C = G2.ProjectivePoint as htf.H2CPointConstructor<Fp2>; // TODO: fix ...CURVE.htfDefaults,
const G2HashToCurve = htf.hashToCurve(C, CURVE.G2.mapToCurve, { ...CURVE.G2.htfDefaults,
...CURVE.htfDefaults, })
...CURVE.G2.htfDefaults, );
});
const { Signature } = CURVE.G2; const { Signature } = CURVE.G2;
@@ -260,7 +241,7 @@ export function bls<Fp2, Fp6, Fp12>(
function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 { function normP2Hash(point: G2Hex, htfOpts?: htf.htfBasicOpts): G2 {
return point instanceof G2.ProjectivePoint return point instanceof G2.ProjectivePoint
? point ? point
: (G2HashToCurve.hashToCurve(point, htfOpts) as G2); : (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
} }
// Multiplies generator by private key. // Multiplies generator by private key.
@@ -276,7 +257,7 @@ export function bls<Fp2, Fp6, Fp12>(
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 { function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array | G2 {
const msgPoint = normP2Hash(message, htfOpts); const msgPoint = normP2Hash(message, htfOpts);
msgPoint.assertValidity(); msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normalizePrivateKey(privateKey)); const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G2.ProjectivePoint) return sigPoint; if (message instanceof G2.ProjectivePoint) return sigPoint;
return Signature.encode(sigPoint); return Signature.encode(sigPoint);
} }
@@ -383,7 +364,6 @@ export function bls<Fp2, Fp6, Fp12>(
Signature, Signature,
millerLoop, millerLoop,
calcPairingPrecomputes, calcPairingPrecomputes,
hashToCurve: { G1: G1HashToCurve, G2: G2HashToCurve },
pairing, pairing,
getPublicKey, getPublicKey,
sign, sign,

View File

@@ -1,6 +1,7 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Abelian group utilities // Abelian group utilities
import { Field, validateField, nLength } from './modular.js'; import { Field, validateField, nLength } from './modular.js';
import { validateObject } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
@@ -24,8 +25,17 @@ export type GroupConstructor<T> = {
}; };
export type Mapper<T> = (i: T[]) => T[]; export type Mapper<T> = (i: T[]) => T[];
// Elliptic curve multiplication of Point by scalar. Complicated and fragile. Uses wNAF method. // Elliptic curve multiplication of Point by scalar. Fragile.
// Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory. // Scalars should always be less than curve order: this should be checked inside of a curve itself.
// Creates precomputation tables for fast multiplication:
// - private scalar is split by fixed size windows of W bits
// - every window point is collected from window's table & added to accumulator
// - since windows are different, same point inside tables won't be accessed more than once per calc
// - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
// - +1 window is neccessary for wNAF
// - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
// TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
// windows to be in different memory locations
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) { export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
const constTimeNegate = (condition: boolean, item: T): T => { const constTimeNegate = (condition: boolean, item: T): T => {
const neg = item.negate(); const neg = item.negate();
@@ -53,8 +63,12 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
/** /**
* Creates a wNAF precomputation window. Used for caching. * Creates a wNAF precomputation window. Used for caching.
* Default window size is set by `utils.precompute()` and is equal to 8. * Default window size is set by `utils.precompute()` and is equal to 8.
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256. * Number of precomputed points depends on the curve size:
* @returns 65K precomputed points, depending on W * 2^(𝑊1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
* - 𝑊 is the window size
* - 𝑛 is the bitlength of the curve order.
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
* @returns precomputed point tables flattened to a single array
*/ */
precomputeWindow(elm: T, W: number): Group<T>[] { precomputeWindow(elm: T, W: number): Group<T>[] {
const { windows, windowSize } = opts(W); const { windows, windowSize } = opts(W);
@@ -75,14 +89,14 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
}, },
/** /**
* Implements w-ary non-adjacent form for calculating ec multiplication. * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
* @param W window size * @param W window size
* @param affinePoint optional 2d point to save cached precompute windows on it. * @param precomputes precomputed tables
* @param n bits * @param n scalar (we don't check here, but should be less than curve order)
* @returns real and fake (for const-time) points * @returns real and fake (for const-time) points
*/ */
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } { wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
// TODO: maybe check that scalar is less than group order? wNAF will fail otherwise // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
// But need to carefully remove other checks before wNAF. ORDER == bits here // But need to carefully remove other checks before wNAF. ORDER == bits here
const { windows, windowSize } = opts(W); const { windows, windowSize } = opts(W);
@@ -153,7 +167,7 @@ export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok. // Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
// Though generator can be different (Fp2 / Fp6 for BLS). // Though generator can be different (Fp2 / Fp6 for BLS).
export type AbstractCurve<T> = { export type BasicCurve<T> = {
Fp: Field<T>; // Field over which we'll do calculations (Fp) Fp: Field<T>; // Field over which we'll do calculations (Fp)
n: bigint; // Curve order, total count of valid points in the field n: bigint; // Curve order, total count of valid points in the field
nBitLength?: number; // bit length of curve order nBitLength?: number; // bit length of curve order
@@ -162,24 +176,24 @@ export type AbstractCurve<T> = {
hEff?: bigint; // Number to multiply to clear cofactor hEff?: bigint; // Number to multiply to clear cofactor
Gx: T; // base point X coordinate Gx: T; // base point X coordinate
Gy: T; // base point Y coordinate Gy: T; // base point Y coordinate
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
}; };
export function validateAbsOpts<FP, T>(curve: AbstractCurve<FP> & T) { export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
validateField(curve.Fp); validateField(curve.Fp);
for (const i of ['n', 'h'] as const) { validateObject(
const val = curve[i]; curve,
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); {
} n: 'bigint',
if (!curve.Fp.isValid(curve.Gx)) throw new Error('Invalid generator X coordinate Fp element'); h: 'bigint',
if (!curve.Fp.isValid(curve.Gy)) throw new Error('Invalid generator Y coordinate Fp element'); Gx: 'field',
Gy: 'field',
for (const i of ['nBitLength', 'nByteLength'] as const) { },
const val = curve[i]; {
if (val === undefined) continue; // Optional nBitLength: 'isSafeInteger',
if (!Number.isSafeInteger(val)) throw new Error(`Invalid param ${i}=${val} (${typeof val})`); nByteLength: 'isSafeInteger',
} }
);
// Set defaults // Set defaults
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const); return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
} }

View File

@@ -1,23 +1,9 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y² // Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
import { mod } from './modular.js'; import { mod } from './modular.js';
import { import * as ut from './utils.js';
bytesToHex, import { ensureBytes, FHash, Hex } from './utils.js';
bytesToNumberLE, import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
concatBytes,
ensureBytes,
FHash,
Hex,
numberToBytesLE,
} from './utils.js';
import {
Group,
GroupConstructor,
wNAF,
AbstractCurve,
validateAbsOpts,
AffinePoint,
} from './curve.js';
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
const _0n = BigInt(0); const _0n = BigInt(0);
@@ -26,7 +12,7 @@ const _2n = BigInt(2);
const _8n = BigInt(8); const _8n = BigInt(8);
// Edwards curves must declare params a & d. // Edwards curves must declare params a & d.
export type CurveType = AbstractCurve<bigint> & { export type CurveType = BasicCurve<bigint> & {
a: bigint; // curve param a a: bigint; // curve param a
d: bigint; // curve param d d: bigint; // curve param d
hash: FHash; // Hashing hash: FHash; // Hashing
@@ -39,19 +25,22 @@ export type CurveType = AbstractCurve<bigint> & {
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = validateAbsOpts(curve); const opts = validateBasic(curve);
if (typeof opts.hash !== 'function') throw new Error('Invalid hash function'); ut.validateObject(
for (const i of ['a', 'd'] as const) { curve,
const val = opts[i]; {
if (typeof val !== 'bigint') throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`); hash: 'function',
} a: 'bigint',
for (const fn of ['randomBytes'] as const) { d: 'bigint',
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); randomBytes: 'function',
} },
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio', 'mapToCurve'] as const) { {
if (opts[fn] === undefined) continue; // Optional adjustScalarBytes: 'function',
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`); domain: 'function',
} uvRatio: 'function',
mapToCurve: 'function',
}
);
// Set defaults // Set defaults
return Object.freeze({ ...opts } as const); return Object.freeze({ ...opts } as const);
} }
@@ -75,7 +64,7 @@ export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType; new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
fromAffine(p: AffinePoint<bigint>): ExtPointType; fromAffine(p: AffinePoint<bigint>): ExtPointType;
fromHex(hex: Hex): ExtPointType; fromHex(hex: Hex): ExtPointType;
fromPrivateKey(privateKey: Hex): ExtPointType; // TODO: remove fromPrivateKey(privateKey: Hex): ExtPointType;
} }
export type CurveFn = { export type CurveFn = {
@@ -182,8 +171,27 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
this._WINDOW_SIZE = windowSize; this._WINDOW_SIZE = windowSize;
pointPrecomputes.delete(this); pointPrecomputes.delete(this);
} }
// Not required for fromHex(), which always creates valid points.
assertValidity(): void {} // Could be useful for fromAffine().
assertValidity(): void {
const { a, d } = CURVE;
if (this.is0()) throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
const { ex: X, ey: Y, ez: Z, et: T } = this;
const X2 = modP(X * X); // X²
const Y2 = modP(Y * Y); // Y²
const Z2 = modP(Z * Z); // Z²
const Z4 = modP(Z2 * Z2); // Z⁴
const aX2 = modP(X2 * a); // aX²
const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²
const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²
if (left !== right) throw new Error('bad point: equation left != right (1)');
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
const XY = modP(X * Y);
const ZT = modP(Z * T);
if (XY !== ZT) throw new Error('bad point: equation left != right (2)');
}
// Compare one point to another. // Compare one point to another.
equals(other: Point): boolean { equals(other: Point): boolean {
@@ -336,11 +344,11 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
static fromHex(hex: Hex, strict = true): Point { static fromHex(hex: Hex, strict = true): Point {
const { d, a } = CURVE; const { d, a } = CURVE;
const len = Fp.BYTES; const len = Fp.BYTES;
hex = ensureBytes(hex, len); // copy hex to a new array hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
const normed = hex.slice(); // copy again, we'll manipulate it const normed = hex.slice(); // copy again, we'll manipulate it
const lastByte = hex[len - 1]; // select last byte const lastByte = hex[len - 1]; // select last byte
normed[len - 1] = lastByte & ~0x80; // clear last bit normed[len - 1] = lastByte & ~0x80; // clear last bit
const y = bytesToNumberLE(normed); const y = ut.bytesToNumberLE(normed);
if (y === _0n) { if (y === _0n) {
// y=0 is allowed // y=0 is allowed
} else { } else {
@@ -366,12 +374,12 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
toRawBytes(): Uint8Array { toRawBytes(): Uint8Array {
const { x, y } = this.toAffine(); const { x, y } = this.toAffine();
const bytes = numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y) const bytes = ut.numberToBytesLE(y, Fp.BYTES); // each y has 2 x values (x, -y)
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y
return bytes; // and use the last byte to encode sign of x return bytes; // and use the last byte to encode sign of x
} }
toHex(): string { toHex(): string {
return bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string. return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
} }
} }
const { BASE: G, ZERO: I } = Point; const { BASE: G, ZERO: I } = Point;
@@ -382,20 +390,16 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
} }
// Little-endian SHA512 with modulo n // Little-endian SHA512 with modulo n
function modN_LE(hash: Uint8Array): bigint { function modN_LE(hash: Uint8Array): bigint {
return modN(bytesToNumberLE(hash)); return modN(ut.bytesToNumberLE(hash));
}
function isHex(item: Hex, err: string) {
if (typeof item !== 'string' && !(item instanceof Uint8Array))
throw new Error(`${err} must be hex string or Uint8Array`);
} }
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */ /** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
function getExtendedPublicKey(key: Hex) { function getExtendedPublicKey(key: Hex) {
isHex(key, 'private key');
const len = nByteLength; const len = nByteLength;
key = ensureBytes('private key', key, len);
// Hash private key with curve's hash function to produce uniformingly random input // Hash private key with curve's hash function to produce uniformingly random input
// Check byte lengths: ensure(64, h(ensure(32, key))) // Check byte lengths: ensure(64, h(ensure(32, key)))
const hashed = ensureBytes(cHash(ensureBytes(key, len)), 2 * len); const hashed = ensureBytes('hashed private key', cHash(key), 2 * len);
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6) const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
const scalar = modN_LE(head); // The actual private scalar const scalar = modN_LE(head); // The actual private scalar
@@ -411,14 +415,13 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
// int('LE', SHA512(dom2(F, C) || msgs)) mod N // int('LE', SHA512(dom2(F, C) || msgs)) mod N
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) { function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
const msg = concatBytes(...msgs); const msg = ut.concatBytes(...msgs);
return modN_LE(cHash(domain(msg, ensureBytes(context), !!preHash))); return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!preHash)));
} }
/** Signs message with privateKey. RFC8032 5.1.6 */ /** Signs message with privateKey. RFC8032 5.1.6 */
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array { function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
isHex(msg, 'message'); msg = ensureBytes('message', msg);
msg = ensureBytes(msg);
if (preHash) msg = preHash(msg); // for ed25519ph etc. if (preHash) msg = preHash(msg); // for ed25519ph etc.
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
@@ -426,20 +429,18 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M) const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
const s = modN(r + k * scalar); // S = (r + k * s) mod L const s = modN(r + k * scalar); // S = (r + k * s) mod L
assertGE0(s); // 0 <= s < l assertGE0(s); // 0 <= s < l
const res = concatBytes(R, numberToBytesLE(s, Fp.BYTES)); const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
return ensureBytes(res, nByteLength * 2); // 64-byte signature return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
} }
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean { function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
isHex(sig, 'sig');
isHex(msg, 'message');
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7. const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
sig = ensureBytes(sig, 2 * len); // An extended group equation is checked. sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
msg = ensureBytes(msg); // ZIP215 compliant, which means not fully RFC8032 compliant. msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
if (preHash) msg = preHash(msg); // for ed25519ph, etc if (preHash) msg = preHash(msg); // for ed25519ph, etc
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
const s = bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l const s = ut.bytesToNumberLE(sig.slice(len, 2 * len)); // 0 <= s < l
const SB = G.multiplyUnsafe(s); const SB = G.multiplyUnsafe(s);
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg); const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
const RkA = R.add(A.multiplyUnsafe(k)); const RkA = R.add(A.multiplyUnsafe(k));

View File

@@ -1,66 +1,36 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import type { Group, GroupConstructor, AffinePoint } from './curve.js'; import type { Group, GroupConstructor, AffinePoint } from './curve.js';
import { mod, Field } from './modular.js'; import { mod, Field } from './modular.js';
import { CHash, Hex, concatBytes, ensureBytes } from './utils.js'; import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
/**
* * `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
* * `m` is extension degree (1 for prime fields)
* * `k` is the target security target in bits (e.g. 128), from section 5.1
* * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
* * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
*/
type UnicodeOrBytes = string | Uint8Array;
export type Opts = { export type Opts = {
// DST: a domain separation tag DST: UnicodeOrBytes;
// defined in section 2.2.5
DST: string;
encodeDST: string;
// p: the characteristic of F
// where F is a finite field of characteristic p and order q = p^m
p: bigint; 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; m: number;
// k: the target security level for the suite in bits
// defined in section 5.1
k: number; k: number;
// option to use a message that has already been processed by
// expand_message_xmd
expand?: 'xmd' | 'xof'; 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; hash: CHash;
}; };
export function validateOpts(opts: Opts) { function validateDST(dst: UnicodeOrBytes): Uint8Array {
if (typeof opts.DST !== 'string') throw new Error('Invalid htf/DST'); if (dst instanceof Uint8Array) return dst;
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p'); if (typeof dst === 'string') return utf8ToBytes(dst);
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m'); throw new Error('DST must be Uint8Array or string');
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
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))
throw new Error('Invalid htf/hash function');
} }
// Global symbols in both browsers and Node.js since v11 // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
// See https://github.com/microsoft/TypeScript/issues/31535 const os2ip = bytesToNumberBE;
declare const TextEncoder: any;
declare const TextDecoder: any;
export function stringToBytes(str: string): Uint8Array { // Integer to Octet Stream (numberToBytesBE)
if (typeof str !== 'string') {
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
// Octet Stream to Integer (bytesToNumberBE)
function os2ip(bytes: Uint8Array): bigint {
let result = 0n;
for (let i = 0; i < bytes.length; i++) {
result <<= 8n;
result += BigInt(bytes[i]);
}
return result;
}
// Integer to Octet Stream
function i2osp(value: number, length: number): Uint8Array { function i2osp(value: number, length: number): Uint8Array {
if (value < 0 || value >= 1 << (8 * length)) { if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`); throw new Error(`bad I2OSP call: value=${value} length=${length}`);
@@ -81,6 +51,13 @@ function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
return arr; return arr;
} }
function isBytes(item: unknown): void {
if (!(item instanceof Uint8Array)) throw new Error('Uint8Array expected');
}
function isNum(item: unknown): void {
if (!Number.isSafeInteger(item)) throw new Error('number expected');
}
// 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://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
export function expand_message_xmd( export function expand_message_xmd(
@@ -89,15 +66,17 @@ export function expand_message_xmd(
lenInBytes: number, lenInBytes: number,
H: CHash H: CHash
): Uint8Array { ): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
if (DST.length > 255) DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST)); if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
const b_in_bytes = H.outputLen; const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const r_in_bytes = H.blockLen;
const ell = Math.ceil(lenInBytes / b_in_bytes); const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length'); if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1)); const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
const Z_pad = i2osp(0, r_in_bytes); const Z_pad = i2osp(0, r_in_bytes);
const l_i_b_str = i2osp(lenInBytes, 2); const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
const b = new Array<Uint8Array>(ell); const b = new Array<Uint8Array>(ell);
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime)); const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime)); b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
@@ -116,11 +95,14 @@ export function expand_message_xof(
k: number, k: number,
H: CHash H: CHash
): Uint8Array { ): Uint8Array {
isBytes(msg);
isBytes(DST);
isNum(lenInBytes);
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3 // 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)); // 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);
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest(); DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
} }
if (lenInBytes > 65535 || DST.length > 255) if (lenInBytes > 65535 || DST.length > 255)
throw new Error('expand_message_xof: invalid lenInBytes'); throw new Error('expand_message_xof: invalid lenInBytes');
@@ -140,29 +122,41 @@ export function expand_message_xof(
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 * 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 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}` * @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. * @returns [u_0, ..., u_(count - 1)], a list of field elements.
*/ */
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] { export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
// if options is provided but incomplete, fill any missing fields with the validateObject(options, {
// value in hftDefaults (ie hash to G2). DST: 'string',
const log2p = options.p.toString(2).length; p: 'bigint',
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above m: 'isSafeInteger',
const len_in_bytes = count * options.m * L; k: 'isSafeInteger',
const DST = stringToBytes(options.DST); hash: 'hash',
let pseudo_random_bytes = msg; });
if (options.expand === 'xmd') { const { p, k, m, hash, expand, DST: _DST } = options;
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash); isBytes(msg);
} else if (options.expand === 'xof') { isNum(count);
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash); const DST = validateDST(_DST);
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
let prb; // pseudo_random_bytes
if (expand === 'xmd') {
prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
} else if (expand === 'xof') {
prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
} else if (expand === undefined) {
prb = msg;
} else {
throw new Error('expand must be "xmd", "xof" or undefined');
} }
const u = new Array(count); const u = new Array(count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const e = new Array(options.m); const e = new Array(m);
for (let j = 0; j < options.m; j++) { for (let j = 0; j < m; j++) {
const elm_offset = L * (j + i * options.m); const elm_offset = L * (j + i * m);
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L); const tv = prb.subarray(elm_offset, elm_offset + L);
e[j] = mod(os2ip(tv), options.p); e[j] = mod(os2ip(tv), p);
} }
u[i] = e; u[i] = e;
} }
@@ -195,38 +189,30 @@ export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>; export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
// Separated from initialization opts, so users won't accidentally change per-curve parameters (changing DST is ok!) // Separated from initialization opts, so users won't accidentally change per-curve parameters
export type htfBasicOpts = { // (changing DST is ok!)
DST: string; export type htfBasicOpts = { DST: UnicodeOrBytes };
};
export function hashToCurve<T>( export function createHasher<T>(
Point: H2CPointConstructor<T>, Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>, mapToCurve: MapToCurve<T>,
def: Opts def: Opts & { encodeDST?: UnicodeOrBytes }
) { ) {
validateOpts(def); if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been 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-11#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
hashToCurve(msg: Hex, options?: htfBasicOpts) { hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
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 P = Point.fromAffine(mapToCurve(u[0])) const u0 = Point.fromAffine(mapToCurve(u[0]));
.add(Point.fromAffine(mapToCurve(u[1]))) const u1 = Point.fromAffine(mapToCurve(u[1]));
.clearCofactor(); const P = u0.add(u1).clearCofactor();
P.assertValidity(); P.assertValidity();
return P; return P;
}, },
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3 // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
encodeToCurve(msg: Hex, options?: htfBasicOpts) { encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
if (!mapToCurve) throw new Error('CURVE.mapToCurve() has not been defined');
msg = ensureBytes(msg);
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();
P.assertValidity(); P.assertValidity();

View File

@@ -7,6 +7,7 @@ import {
bytesToNumberBE, bytesToNumberBE,
bytesToNumberLE, bytesToNumberLE,
ensureBytes, ensureBytes,
validateObject,
} from './utils.js'; } from './utils.js';
// 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);
@@ -40,7 +41,6 @@ export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
} }
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4) // Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
// TODO: Fp version?
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint { export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
let res = x; let res = x;
while (power-- > _0n) { while (power-- > _0n) {
@@ -56,6 +56,7 @@ export function invert(number: bigint, modulo: bigint): bigint {
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
} }
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/ // Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
// Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower.
let a = mod(number, modulo); let a = mod(number, modulo);
let b = modulo; let b = modulo;
// prettier-ignore // prettier-ignore
@@ -249,18 +250,17 @@ const FIELD_FIELDS = [
'addN', 'subN', 'mulN', 'sqrN' 'addN', 'subN', 'mulN', 'sqrN'
] as const; ] as const;
export function validateField<T>(field: Field<T>) { export function validateField<T>(field: Field<T>) {
for (const i of ['ORDER', 'MASK'] as const) { const initial = {
if (typeof field[i] !== 'bigint') ORDER: 'bigint',
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); MASK: 'bigint',
} BYTES: 'isSafeInteger',
for (const i of ['BYTES', 'BITS'] as const) { BITS: 'isSafeInteger',
if (typeof field[i] !== 'number') } as Record<string, string>;
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); const opts = FIELD_FIELDS.reduce((map, val: string) => {
} map[val] = 'function';
for (const i of FIELD_FIELDS) { return map;
if (typeof field[i] !== 'function') }, initial);
throw new Error(`Invalid field param ${i}=${field[i]} (${typeof field[i]})`); return validateObject(field, opts);
}
} }
// Generic field functions // Generic field functions
@@ -408,7 +408,7 @@ export function hashToPrivateScalar(
groupOrder: bigint, groupOrder: bigint,
isLE = false isLE = false
): bigint { ): bigint {
hash = ensureBytes(hash); hash = ensureBytes('privateHash', hash);
const hashLen = hash.length; const hashLen = hash.length;
const minLen = nLength(groupOrder).nByteLength + 8; const minLen = nLength(groupOrder).nByteLength + 8;
if (minLen < 24 || hashLen < minLen || hashLen > 1024) if (minLen < 24 || hashLen < minLen || hashLen > 1024)

View File

@@ -1,50 +1,47 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { mod, pow } from './modular.js'; import { mod, pow } from './modular.js';
import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js'; import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
const _0n = BigInt(0); const _0n = BigInt(0);
const _1n = BigInt(1); const _1n = BigInt(1);
type Hex = string | Uint8Array; type Hex = string | Uint8Array;
export type CurveType = { export type CurveType = {
// Field over which we'll do calculations. Verify with: P: bigint; // finite field prime
P: bigint;
nByteLength: number; nByteLength: number;
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
a24: bigint; // Related to d, but cannot be derived from it a: bigint;
montgomeryBits: number; montgomeryBits: number;
powPminus2?: (x: bigint) => bigint; powPminus2?: (x: bigint) => bigint;
xyToU?: (x: bigint, y: bigint) => bigint; xyToU?: (x: bigint, y: bigint) => bigint;
Gu: string; Gu: bigint;
randomBytes?: (bytesLength?: number) => Uint8Array;
}; };
export type CurveFn = { export type CurveFn = {
scalarMult: (scalar: Hex, u: Hex) => Uint8Array; scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
scalarMultBase: (scalar: Hex) => Uint8Array; scalarMultBase: (scalar: Hex) => Uint8Array;
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array; getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
getPublicKey: (privateKey: Hex) => Uint8Array; getPublicKey: (privateKey: Hex) => Uint8Array;
Gu: string; utils: { randomPrivateKey: () => Uint8Array };
GuBytes: Uint8Array;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
for (const i of ['a24'] as const) { validateObject(
if (typeof curve[i] !== 'bigint') curve,
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); {
} a: 'bigint',
for (const i of ['montgomeryBits', 'nByteLength'] as const) { },
if (curve[i] === undefined) continue; // Optional {
if (!Number.isSafeInteger(curve[i])) montgomeryBits: 'isSafeInteger',
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`); nByteLength: 'isSafeInteger',
} adjustScalarBytes: 'function',
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) { domain: 'function',
if (curve[fn] === undefined) continue; // Optional powPminus2: 'function',
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`); Gu: 'bigint',
} }
for (const i of ['Gu'] as const) { );
if (curve[i] === undefined) continue; // Optional
if (typeof curve[i] !== 'string')
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
}
// Set defaults // Set defaults
return Object.freeze({ ...curve } as const); return Object.freeze({ ...curve } as const);
} }
@@ -54,34 +51,14 @@ function validateOpts(curve: CurveType) {
export function montgomery(curveDef: CurveType): CurveFn { export function montgomery(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef); const CURVE = validateOpts(curveDef);
const { P } = CURVE; const { P } = CURVE;
const modP = (a: bigint) => mod(a, P); const modP = (n: bigint) => mod(n, P);
const montgomeryBits = CURVE.montgomeryBits; const montgomeryBits = CURVE.montgomeryBits;
const montgomeryBytes = Math.ceil(montgomeryBits / 8); const montgomeryBytes = Math.ceil(montgomeryBits / 8);
const fieldLen = CURVE.nByteLength; const fieldLen = CURVE.nByteLength;
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P)); const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
/** // cswap from RFC7748. But it is not from RFC7748!
* Checks for num to be in range:
* For strict == true: `0 < num < max`.
* For strict == false: `0 <= num < max`.
* Converts non-float safe numbers to bigints.
*/
function normalizeScalar(num: bigint, max: bigint, strict = true): bigint {
if (!max) throw new TypeError('Specify max value');
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
if (typeof num === 'bigint' && num < max) {
if (strict) {
if (_0n < num) return num;
} else {
if (_0n <= num) return num;
}
}
throw new TypeError('Expected valid scalar: 0 < scalar < max');
}
// cswap from RFC7748
// NOTE: cswap is not from RFC7748!
/* /*
cswap(swap, x_2, x_3): cswap(swap, x_2, x_3):
dummy = mask(swap) AND (x_2 XOR x_3) dummy = mask(swap) AND (x_2 XOR x_3)
@@ -98,7 +75,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
return [x_2, x_3]; return [x_2, x_3];
} }
// Accepts 0 as well
function assertFieldElement(n: bigint): bigint {
if (typeof n === 'bigint' && _0n <= n && n < P) return n;
throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
}
// x25519 from 4 // x25519 from 4
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
/** /**
* *
* @param pointU u coordinate (x) on Montgomery Curve 25519 * @param pointU u coordinate (x) on Montgomery Curve 25519
@@ -106,13 +91,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
* @returns new Point on Montgomery curve * @returns new Point on Montgomery curve
*/ */
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint { function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
const { P } = CURVE; const u = assertFieldElement(pointU);
const u = normalizeScalar(pointU, P);
// Section 5: Implementations MUST accept non-canonical values and process them as // Section 5: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. // if they had been reduced modulo the field prime.
const k = normalizeScalar(scalar, P); const k = assertFieldElement(scalar);
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
const a24 = CURVE.a24;
const x_1 = u; const x_1 = u;
let x_2 = _1n; let x_2 = _1n;
let z_2 = _0n; let z_2 = _0n;
@@ -166,28 +148,21 @@ export function montgomery(curveDef: CurveType): CurveFn {
} }
function decodeUCoordinate(uEnc: Hex): bigint { function decodeUCoordinate(uEnc: Hex): bigint {
const u = ensureBytes(uEnc, montgomeryBytes);
// Section 5: When receiving such an array, implementations of X25519 // Section 5: When receiving such an array, implementations of X25519
// MUST mask the most significant bit in the final byte. // MUST mask the most significant bit in the final byte.
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519 // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
u[fieldLen - 1] &= 127; // 0b0111_1111 const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
// u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
return bytesToNumberLE(u); return bytesToNumberLE(u);
} }
function decodeScalar(n: Hex): bigint { function decodeScalar(n: Hex): bigint {
const bytes = ensureBytes(n); const bytes = ensureBytes('scalar', n);
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen) if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`); throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
return bytesToNumberLE(adjustScalarBytes(bytes)); return bytesToNumberLE(adjustScalarBytes(bytes));
} }
/**
* Computes shared secret between private key "scalar" and public key's "u" (x) coordinate.
* We can get 'y' coordinate from 'u',
* but Point.fromHex also wants 'x' coordinate oddity flag,
* and we cannot get 'x' without knowing 'v'.
* Need to add generic conversion between twisted edwards and complimentary curve for JubJub.
*/
function scalarMult(scalar: Hex, u: Hex): Uint8Array { function scalarMult(scalar: Hex, u: Hex): Uint8Array {
const pointU = decodeUCoordinate(u); const pointU = decodeUCoordinate(u);
const _scalar = decodeScalar(scalar); const _scalar = decodeScalar(scalar);
@@ -197,14 +172,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
if (pu === _0n) throw new Error('Invalid private or public key received'); if (pu === _0n) throw new Error('Invalid private or public key received');
return encodeUCoordinate(pu); return encodeUCoordinate(pu);
} }
/** // Computes public key from private. By doing scalar multiplication of base point.
* Computes public key from private. const GuBytes = encodeUCoordinate(CURVE.Gu);
* Executes scalar multiplication of curve's base point by scalar.
* @param scalar private key
* @returns new public key
*/
function scalarMultBase(scalar: Hex): Uint8Array { function scalarMultBase(scalar: Hex): Uint8Array {
return scalarMult(scalar, CURVE.Gu); return scalarMult(scalar, GuBytes);
} }
return { return {
@@ -212,6 +183,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase, scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey), getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey), getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
Gu: CURVE.Gu, utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
GuBytes: GuBytes,
}; };
} }

View File

@@ -1,6 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info // Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
import { Field, validateField, FpPow } from './modular.js'; import { Field, FpPow, validateField } from './modular.js';
// We don't provide any constants, since different implementations use different constants. // We don't provide any constants, since different implementations use different constants.
// For reference constants see './test/poseidon.test.js'. // For reference constants see './test/poseidon.test.js'.
export type PoseidonOpts = { export type PoseidonOpts = {

View File

@@ -18,7 +18,7 @@ export type FHash = (message: Uint8Array | string) => Uint8Array;
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
export function bytesToHex(bytes: Uint8Array): string { export function bytesToHex(bytes: Uint8Array): string {
if (!u8a(bytes)) throw new Error('Expected Uint8Array'); if (!u8a(bytes)) throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x // pre-caching improves the speed 6x
let hex = ''; let hex = '';
for (let i = 0; i < bytes.length; i++) { for (let i = 0; i < bytes.length; i++) {
@@ -33,21 +33,21 @@ export function numberToHexUnpadded(num: number | bigint): string {
} }
export function hexToNumber(hex: string): bigint { export function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') throw new Error('hexToNumber: expected string, got ' + typeof hex); if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
// Big Endian // Big Endian
return BigInt(`0x${hex}`); return BigInt(hex === '' ? '0' : `0x${hex}`);
} }
// Caching slows it down 2-3x // Caching slows it down 2-3x
export function hexToBytes(hex: string): Uint8Array { export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('hexToBytes: expected string, got ' + typeof hex); if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length); if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
const array = new Uint8Array(hex.length / 2); const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
const j = i * 2; const j = i * 2;
const hexByte = hex.slice(j, j + 2); const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16); const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence'); if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
array[i] = byte; array[i] = byte;
} }
return array; return array;
@@ -58,7 +58,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(bytes)); return hexToNumber(bytesToHex(bytes));
} }
export function bytesToNumberLE(bytes: Uint8Array): bigint { export function bytesToNumberLE(bytes: Uint8Array): bigint {
if (!u8a(bytes)) throw new Error('Expected Uint8Array'); if (!u8a(bytes)) throw new Error('Uint8Array expected');
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
} }
@@ -66,33 +66,39 @@ export const numberToBytesBE = (n: bigint, len: number) =>
hexToBytes(n.toString(16).padStart(len * 2, '0')); hexToBytes(n.toString(16).padStart(len * 2, '0'));
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse(); export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
// Returns variable number bytes (minimal bigint encoding?) // Returns variable number bytes (minimal bigint encoding?)
export const numberToVarBytesBE = (n: bigint) => { export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
let hex = n.toString(16);
if (hex.length & 1) hex = '0' + hex;
return hexToBytes(hex);
};
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array { export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer let res: Uint8Array;
// is instance of Uint8Array, and its slice() creates **mutable** copy if (typeof hex === 'string') {
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex); try {
if (typeof expectedLength === 'number' && bytes.length !== expectedLength) res = hexToBytes(hex);
throw new Error(`Expected ${expectedLength} bytes`); } catch (e) {
return bytes; throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
}
} else if (u8a(hex)) {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
res = Uint8Array.from(hex);
} else {
throw new Error(`${title} must be hex string or Uint8Array`);
}
const len = res.length;
if (typeof expectedLength === 'number' && len !== expectedLength)
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
return res;
} }
// Copies several Uint8Arrays into one. // Copies several Uint8Arrays into one.
export function concatBytes(...arrays: Uint8Array[]): Uint8Array { export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
if (!arrays.every((b) => u8a(b))) throw new Error('Uint8Array list expected'); const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
if (arrays.length === 1) return arrays[0]; let pad = 0; // walk through each item, ensure they have proper type
const length = arrays.reduce((a, arr) => a + arr.length, 0); arrs.forEach((a) => {
const result = new Uint8Array(length); if (!u8a(a)) throw new Error('Uint8Array expected');
for (let i = 0, pad = 0; i < arrays.length; i++) { r.set(a, pad);
const arr = arrays[i]; pad += a.length;
result.set(arr, pad); });
pad += arr.length; return r;
}
return result;
} }
export function equalBytes(b1: Uint8Array, b2: Uint8Array) { export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
@@ -102,6 +108,16 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
return true; return true;
} }
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
// Bit operations // Bit operations
// Amount of bits inside bigint (Same as n.toString(2).length) // Amount of bits inside bigint (Same as n.toString(2).length)
@@ -119,3 +135,112 @@ export const bitSet = (n: bigint, pos: number, value: boolean) =>
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`)) // Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
// Not using ** operator with bigints for old engines. // Not using ** operator with bigints for old engines.
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n; export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
// DRBG
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
type Pred<T> = (v: Uint8Array) => T | undefined;
/**
* Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
* @returns function that will call DRBG until 2nd arg returns something meaningful
* @example
* const drbg = createHmacDRBG<Key>(32, 32, hmac);
* drbg(seed, bytesToKey); // bytesToKey must return Key or undefined
*/
export function createHmacDrbg<T>(
hashLen: number,
qByteLen: number,
hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array
): (seed: Uint8Array, predicate: Pred<T>) => T {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => {
v.fill(1);
k.fill(0);
i = 0;
};
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
const reseed = (seed = u8n()) => {
// HMAC-DRBG reseed() function. Steps D-G
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0) return;
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
// HMAC-DRBG generate() function
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
let len = 0;
const out: Uint8Array[] = [];
while (len < qByteLen) {
v = h();
const sl = v.slice();
out.push(sl);
len += v.length;
}
return concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
reseed(seed); // Steps D-G
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen()))) reseed();
reset();
return res;
};
return genUntil;
}
// Validating curves and fields
const validatorFns = {
bigint: (val: any) => typeof val === 'bigint',
function: (val: any) => typeof val === 'function',
boolean: (val: any) => typeof val === 'boolean',
string: (val: any) => typeof val === 'string',
isSafeInteger: (val: any) => Number.isSafeInteger(val),
array: (val: any) => Array.isArray(val),
field: (val: any, object: any) => (object as any).Fp.isValid(val),
hash: (val: any) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
} as const;
type Validator = keyof typeof validatorFns;
type ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
export function validateObject<T extends Record<string, any>>(
object: T,
validators: ValMap<T>,
optValidators: ValMap<T> = {}
) {
const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {
const checkVal = validatorFns[type];
if (typeof checkVal !== 'function')
throw new Error(`Invalid validator "${type}", expected function`);
const val = object[fieldName as keyof typeof object];
if (isOptional && val === undefined) return;
if (!checkVal(val, object)) {
throw new Error(
`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`
);
}
};
for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);
for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);
return object;
}
// validate type tests
// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
// // Should fail type-check
// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });

View File

@@ -2,15 +2,8 @@
// Short Weierstrass curve. The formula is: y² = x³ + ax + b // Short Weierstrass curve. The formula is: y² = x³ + ax + b
import * as mod from './modular.js'; import * as mod from './modular.js';
import * as ut from './utils.js'; import * as ut from './utils.js';
import { Hex, PrivKey, ensureBytes, CHash } from './utils.js'; import { CHash, Hex, PrivKey, ensureBytes } from './utils.js';
import { import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
Group,
GroupConstructor,
wNAF,
AbstractCurve,
validateAbsOpts,
AffinePoint,
} from './curve.js';
export type { AffinePoint }; export type { AffinePoint };
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array; type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
@@ -18,18 +11,15 @@ type EndomorphismOpts = {
beta: bigint; beta: bigint;
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint }; splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
}; };
export type BasicCurve<T> = AbstractCurve<T> & { export type BasicWCurve<T> = BasicCurve<T> & {
// Params: a, b // Params: a, b
a: T; a: T;
b: T; b: T;
// Optional params // Optional params
// Executed before privkey validation. Useful for P521 with var-length priv key allowedPrivateKeyLengths?: readonly number[]; // for P521
normalizePrivateKey?: (key: PrivKey) => PrivKey; wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
// Whether to execute modular division on a private key, useful for bls curves with cofactor > 1 endo?: EndomorphismOpts; // Endomorphism options for Koblitz curves
wrapPrivateKey?: boolean;
// Endomorphism options for Koblitz curves
endo?: EndomorphismOpts;
// When a cofactor != 1, there can be an effective methods to: // When a cofactor != 1, there can be an effective methods to:
// 1. Determine whether a point is torsion-free // 1. Determine whether a point is torsion-free
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean; isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
@@ -69,9 +59,6 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
readonly py: T; readonly py: T;
readonly pz: T; readonly pz: T;
multiply(scalar: bigint): ProjPointType<T>; multiply(scalar: bigint): ProjPointType<T>;
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
_setWindowSize(windowSize: number): void;
toAffine(iz?: T): AffinePoint<T>; toAffine(iz?: T): AffinePoint<T>;
isTorsionFree(): boolean; isTorsionFree(): boolean;
clearCofactor(): ProjPointType<T>; clearCofactor(): ProjPointType<T>;
@@ -79,6 +66,10 @@ export interface ProjPointType<T> extends Group<ProjPointType<T>> {
hasEvenY(): boolean; hasEvenY(): boolean;
toRawBytes(isCompressed?: boolean): Uint8Array; toRawBytes(isCompressed?: boolean): Uint8Array;
toHex(isCompressed?: boolean): string; toHex(isCompressed?: boolean): string;
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
_setWindowSize(windowSize: number): void;
} }
// Static methods for 3d XYZ points // Static methods for 3d XYZ points
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> { export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
@@ -89,26 +80,33 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[]; normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
} }
export type CurvePointsType<T> = BasicCurve<T> & { export type CurvePointsType<T> = BasicWCurve<T> & {
// Bytes // Bytes
fromBytes: (bytes: Uint8Array) => AffinePoint<T>; fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array; toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, compressed: boolean) => Uint8Array;
}; };
function validatePointOpts<T>(curve: CurvePointsType<T>) { function validatePointOpts<T>(curve: CurvePointsType<T>) {
const opts = validateAbsOpts(curve); const opts = validateBasic(curve);
const Fp = opts.Fp; ut.validateObject(
for (const i of ['a', 'b'] as const) { opts,
if (!Fp.isValid(curve[i])) {
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`); a: 'field',
} b: 'field',
for (const i of ['isTorsionFree', 'clearCofactor'] as const) { fromBytes: 'function',
if (curve[i] === undefined) continue; // Optional toBytes: 'function',
if (typeof curve[i] !== 'function') throw new Error(`Invalid ${i} function`); },
} {
const endo = opts.endo; allowedPrivateKeyLengths: 'array',
wrapPrivateKey: 'boolean',
isTorsionFree: 'function',
clearCofactor: 'function',
allowInfinityPoint: 'boolean',
}
);
const { endo, Fp, a } = opts;
if (endo) { if (endo) {
if (!Fp.eql(opts.a, Fp.ZERO)) { if (!Fp.eql(a, Fp.ZERO)) {
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
} }
if ( if (
@@ -119,15 +117,12 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function'); throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
} }
} }
if (typeof opts.fromBytes !== 'function') throw new Error('Invalid fromBytes function');
if (typeof opts.toBytes !== 'function') throw new Error('Invalid fromBytes function');
// Set defaults
return Object.freeze({ ...opts } as const); return Object.freeze({ ...opts } as const);
} }
export type CurvePointsRes<T> = { export type CurvePointsRes<T> = {
ProjectivePoint: ProjConstructor<T>; ProjectivePoint: ProjConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T; weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean; isWithinCurveOrder: (num: bigint) => boolean;
}; };
@@ -207,32 +202,27 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
function assertGE(num: bigint) { function assertGE(num: bigint) {
if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n'); if (!isWithinCurveOrder(num)) throw new Error('Expected valid bigint: 0 < bigint < curve.n');
} }
/** // Validates if priv key is valid and converts it to bigint.
* Validates if a private key is valid and converts it to bigint form. // Supports options allowedPrivateKeyLengths and wrapPrivateKey.
* Supports two options, that are passed when CURVE is initialized: function normPrivateKeyToScalar(key: PrivKey): bigint {
* - `normalizePrivateKey()` executed before all checks const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
* - `wrapPrivateKey` when true, executed after most checks, but before `0 < key < n` if (lengths && typeof key !== 'bigint') {
*/ if (key instanceof Uint8Array) key = ut.bytesToHex(key);
function normalizePrivateKey(key: PrivKey): bigint { // Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
const { normalizePrivateKey: custom, nByteLength: groupLen, wrapPrivateKey, n } = CURVE; if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
if (typeof custom === 'function') key = custom(key); key = key.padStart(nByteLength * 2, '0');
let num: bigint;
if (typeof key === 'bigint') {
// Curve order check is done below
num = key;
} else if (typeof key === 'string') {
if (key.length !== 2 * groupLen) throw new Error(`must be ${groupLen} bytes`);
// Validates individual octets
num = ut.bytesToNumberBE(ensureBytes(key));
} else if (key instanceof Uint8Array) {
if (key.length !== groupLen) throw new Error(`must be ${groupLen} bytes`);
num = ut.bytesToNumberBE(key);
} else {
throw new Error('private key must be bytes, hex or bigint, not ' + typeof key);
} }
// Useful for curves with cofactor != 1 let num: bigint;
if (wrapPrivateKey) num = mod.mod(num, n); try {
assertGE(num); num =
typeof key === 'bigint'
? key
: ut.bytesToNumberBE(ensureBytes('private key', key, nByteLength));
} catch (error) {
throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
}
if (wrapPrivateKey) num = mod.mod(num, n); // disabled by default, enabled for BLS
assertGE(num); // num in range [1..N-1]
return num; return num;
} }
@@ -255,6 +245,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
if (pz == null || !Fp.isValid(pz)) throw new Error('z required'); if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
} }
// Does not validate if the point is on-curve.
// Use fromHex instead, or call assertValidity() later.
static fromAffine(p: AffinePoint<T>): Point { static fromAffine(p: AffinePoint<T>): Point {
const { x, y } = p || {}; const { x, y } = p || {};
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point'); if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
@@ -288,14 +280,14 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* @param hex short/long ECDSA hex * @param hex short/long ECDSA hex
*/ */
static fromHex(hex: Hex): Point { static fromHex(hex: Hex): Point {
const P = Point.fromAffine(CURVE.fromBytes(ensureBytes(hex))); const P = Point.fromAffine(CURVE.fromBytes(ensureBytes('pointHex', hex)));
P.assertValidity(); P.assertValidity();
return P; return P;
} }
// Multiplies generator point by privateKey. // Multiplies generator point by privateKey.
static fromPrivateKey(privateKey: PrivKey) { static fromPrivateKey(privateKey: PrivKey) {
return Point.BASE.multiply(normalizePrivateKey(privateKey)); return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
} }
// We calculate precomputes for elliptic curve point multiplication // We calculate precomputes for elliptic curve point multiplication
@@ -496,8 +488,9 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
* Constant time multiplication. * Constant time multiplication.
* Uses wNAF method. Windowed method may be 10% faster, * Uses wNAF method. Windowed method may be 10% faster,
* but takes 2x longer to generate and consumes 2x memory. * but takes 2x longer to generate and consumes 2x memory.
* Uses precomputes when available.
* Uses endomorphism for Koblitz curves.
* @param scalar by which the point would be multiplied * @param scalar by which the point would be multiplied
* @param affinePoint optional point ot save cached precompute windows on it
* @returns New point * @returns New point
*/ */
multiply(scalar: bigint): Point { multiply(scalar: bigint): Point {
@@ -525,6 +518,8 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
/** /**
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. * Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly.
* Not using Strauss-Shamir trick: precomputation tables are faster.
* The trick could be useful if both P and Q are not G (not in our case).
* @returns non-zero affine point * @returns non-zero affine point
*/ */
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
@@ -580,7 +575,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
return { return {
ProjectivePoint: Point as ProjConstructor<T>, ProjectivePoint: Point as ProjConstructor<T>,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
}; };
@@ -612,25 +607,30 @@ type SignatureLike = { r: bigint; s: bigint };
export type PubKey = Hex | ProjPointType<bigint>; export type PubKey = Hex | ProjPointType<bigint>;
export type CurveType = BasicCurve<bigint> & { export type CurveType = BasicWCurve<bigint> & {
// Default options hash: CHash; // CHash not FHash because we need outputLen for DRBG
lowS?: boolean;
// Hashes
hash: CHash; // Because we need outputLen for DRBG
hmac: HmacFnSync; hmac: HmacFnSync;
randomBytes: (bytesLength?: number) => Uint8Array; randomBytes: (bytesLength?: number) => Uint8Array;
// truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => Uint8Array; lowS?: boolean;
bits2int?: (bytes: Uint8Array) => bigint; bits2int?: (bytes: Uint8Array) => bigint;
bits2int_modN?: (bytes: Uint8Array) => bigint; bits2int_modN?: (bytes: Uint8Array) => bigint;
}; };
function validateOpts(curve: CurveType) { function validateOpts(curve: CurveType) {
const opts = validateAbsOpts(curve); const opts = validateBasic(curve);
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen)) ut.validateObject(
throw new Error('Invalid hash function'); opts,
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function'); {
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function'); hash: 'hash',
// Set defaults hmac: 'function',
randomBytes: 'function',
},
{
bits2int: 'function',
bits2int_modN: 'function',
lowS: 'boolean',
}
);
return Object.freeze({ lowS: true, ...opts } as const); return Object.freeze({ lowS: true, ...opts } as const);
} }
@@ -643,66 +643,13 @@ export type CurveFn = {
ProjectivePoint: ProjConstructor<bigint>; ProjectivePoint: ProjConstructor<bigint>;
Signature: SignatureConstructor; Signature: SignatureConstructor;
utils: { utils: {
_normalizePrivateKey: (key: PrivKey) => bigint; normPrivateKeyToScalar: (key: PrivKey) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean; isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array; randomPrivateKey: () => Uint8Array;
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
}; };
}; };
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
const u8fr = (arr: any) => Uint8Array.from(arr); // another shortcut
// Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
type Pred<T> = (v: Uint8Array) => T | undefined;
function hmacDrbg<T>(
hashLen: number,
qByteLen: number,
hmacFn: HmacFnSync
): (seed: Uint8Array, predicate: Pred<T>) => T {
if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
let i = 0; // Iterations counter, will throw when over 1000
const reset = () => {
v.fill(1);
k.fill(0);
i = 0;
};
const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
const reseed = (seed = u8n()) => {
// HMAC-DRBG reseed() function. Steps D-G
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
v = h(); // v = hmac(k || v)
if (seed.length === 0) return;
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
v = h(); // v = hmac(k || v)
};
const gen = () => {
// HMAC-DRBG generate() function
if (i++ >= 1000) throw new Error('drbg: tried 1000 values');
let len = 0;
const out: Uint8Array[] = [];
while (len < qByteLen) {
v = h();
const sl = v.slice();
out.push(sl);
len += v.length;
}
return ut.concatBytes(...out);
};
const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {
reset();
reseed(seed); // Steps D-G
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
while (!(res = pred(gen()))) reseed();
reset();
return res;
};
return genUntil;
}
export function weierstrass(curveDef: CurveType): CurveFn { export function weierstrass(curveDef: CurveType): CurveFn {
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>; const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
const CURVE_ORDER = CURVE.n; const CURVE_ORDER = CURVE.n;
@@ -722,7 +669,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const { const {
ProjectivePoint: Point, ProjectivePoint: Point,
normalizePrivateKey, normPrivateKeyToScalar,
weierstrassEquation, weierstrassEquation,
isWithinCurveOrder, isWithinCurveOrder,
} = weierstrassPoints({ } = weierstrassPoints({
@@ -732,7 +679,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const x = Fp.toBytes(a.x); const x = Fp.toBytes(a.x);
const cat = ut.concatBytes; const cat = ut.concatBytes;
if (isCompressed) { if (isCompressed) {
// TODO: hasEvenY
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
} else { } else {
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
@@ -759,7 +705,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
return { x, y }; return { x, y };
} else { } else {
throw new Error( throw new Error(
`Point.fromHex: received invalid point. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes, not ${len}` `Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`
); );
} }
}, },
@@ -788,24 +734,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// pair (bytes of r, bytes of s) // pair (bytes of r, bytes of s)
static fromCompact(hex: Hex) { static fromCompact(hex: Hex) {
const gl = CURVE.nByteLength; const l = CURVE.nByteLength;
hex = ensureBytes(hex, gl * 2); hex = ensureBytes('compactSignature', hex, l * 2);
return new Signature(slcNum(hex, 0, gl), slcNum(hex, gl, 2 * gl)); return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l));
} }
// DER encoded ECDSA signature // DER encoded ECDSA signature
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script // https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script
static fromDER(hex: Hex) { static fromDER(hex: Hex) {
if (typeof hex !== 'string' && !(hex instanceof Uint8Array)) const { r, s } = DER.toSig(ensureBytes('DER', hex));
throw new Error(`Signature.fromDER: Expected string or Uint8Array`);
const { r, s } = DER.toSig(ensureBytes(hex));
return new Signature(r, s); return new Signature(r, s);
} }
assertValidity(): void { assertValidity(): void {
// can use assertGE here // can use assertGE here
if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < n'); if (!isWithinCurveOrder(this.r)) throw new Error('r must be 0 < r < CURVE.n');
if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < n'); if (!isWithinCurveOrder(this.s)) throw new Error('s must be 0 < s < CURVE.n');
} }
addRecoveryBit(recovery: number) { addRecoveryBit(recovery: number) {
@@ -813,18 +757,17 @@ export function weierstrass(curveDef: CurveType): CurveFn {
} }
recoverPublicKey(msgHash: Hex): typeof Point.BASE { recoverPublicKey(msgHash: Hex): typeof Point.BASE {
const { n: N } = CURVE; // ECDSA public key recovery secg.org/sec1-v2.pdf 4.1.6
const { r, s, recovery: rec } = this; const { r, s, recovery: rec } = this;
const h = bits2int_modN(ensureBytes(msgHash)); // Truncate hash const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid'); if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
const radj = rec === 2 || rec === 3 ? r + N : r; const radj = rec === 2 || rec === 3 ? r + CURVE.n : r;
if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid'); if (radj >= Fp.ORDER) throw new Error('recovery id 2 or 3 invalid');
const prefix = (rec & 1) === 0 ? '02' : '03'; const prefix = (rec & 1) === 0 ? '02' : '03';
const R = Point.fromHex(prefix + numToNByteStr(radj)); const R = Point.fromHex(prefix + numToNByteStr(radj));
const ir = invN(radj); // r^-1 const ir = invN(radj); // r^-1
const u1 = modN(-h * ir); // -hr^-1 const u1 = modN(-h * ir); // -hr^-1
const u2 = modN(s * ir); // sr^-1 const u2 = modN(s * ir); // sr^-1
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked if (!Q) throw new Error('point at infinify'); // unsafe is fine: no priv data leaked
Q.assertValidity(); Q.assertValidity();
return Q; return Q;
@@ -859,37 +802,35 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const utils = { const utils = {
isValidPrivateKey(privateKey: PrivKey) { isValidPrivateKey(privateKey: PrivKey) {
try { try {
normalizePrivateKey(privateKey); normPrivateKeyToScalar(privateKey);
return true; return true;
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
_normalizePrivateKey: normalizePrivateKey, normPrivateKeyToScalar: normPrivateKeyToScalar,
/**
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
*/
hashToPrivateKey: (hash: Hex): Uint8Array =>
ut.numberToBytesBE(mod.hashToPrivateScalar(hash, CURVE_ORDER), CURVE.nByteLength),
/** /**
* Produces cryptographically secure private key from random of size (nBitLength+64) * Produces cryptographically secure private key from random of size (nBitLength+64)
* as per FIPS 186 B.4.1 with modulo bias being neglible. * as per FIPS 186 B.4.1 with modulo bias being neglible.
*/ */
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(Fp.BYTES + 8)), randomPrivateKey: (): Uint8Array => {
const rand = CURVE.randomBytes(Fp.BYTES + 8);
const num = mod.hashToPrivateScalar(rand, CURVE_ORDER);
return ut.numberToBytesBE(num, CURVE.nByteLength);
},
/** /**
* 1. Returns cached point which you can use to pass to `getSharedSecret` or `#multiply` by it. * Creates precompute table for an arbitrary EC point. Makes point "cached".
* 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call. * Allows to massively speed-up `point.multiply(scalar)`.
* If you want your first getPublicKey to take 0.16ms instead of 20ms, make sure to call
* utils.precompute() somewhere without arguments first.
* @param windowSize 2, 4, 8, 16
* @returns cached point * @returns cached point
* @example
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey));
* fast.multiply(privKey); // much faster ECDH now
*/ */
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE { precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
point._setWindowSize(windowSize); point._setWindowSize(windowSize);
point.multiply(BigInt(3)); point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
return point; return point;
}, },
}; };
@@ -920,7 +861,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
/** /**
* ECDH (Elliptic Curve Diffie Hellman). * ECDH (Elliptic Curve Diffie Hellman).
* Computes shared public key from private key and public key. * Computes shared public key from private key and public key.
* Checks: 1) private key validity 2) shared key is on-curve * Checks: 1) private key validity 2) shared key is on-curve.
* Does NOT hash the result.
* @param privateA private key * @param privateA private key
* @param publicB different public key * @param publicB different public key
* @param isCompressed whether to return compact (default), or full key * @param isCompressed whether to return compact (default), or full key
@@ -930,7 +872,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
if (isProbPub(privateA)) throw new Error('first arg must be private key'); if (isProbPub(privateA)) throw new Error('first arg must be private key');
if (!isProbPub(publicB)) throw new Error('second arg must be public key'); if (!isProbPub(publicB)) throw new Error('second arg must be public key');
const b = Point.fromHex(publicB); // check for being on-curve const b = Point.fromHex(publicB); // check for being on-curve
return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed);
} }
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
@@ -942,8 +884,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
function (bytes: Uint8Array): bigint { function (bytes: Uint8Array): bigint {
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
// for some cases, since bytes.length * 8 is not actual bitLength. // for some cases, since bytes.length * 8 is not actual bitLength.
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here const num = ut.bytesToNumberBE(bytes); // check for == u8 done here
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits
return delta > 0 ? num >> BigInt(delta) : num; return delta > 0 ? num >> BigInt(delta) : num;
}; };
const bits2int_modN = const bits2int_modN =
@@ -953,10 +895,13 @@ export function weierstrass(curveDef: CurveType): CurveFn {
}; };
// NOTE: pads output with zero as per spec // NOTE: pads output with zero as per spec
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); const ORDER_MASK = ut.bitMask(CURVE.nBitLength);
/**
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`.
*/
function int2octets(num: bigint): Uint8Array { function int2octets(num: bigint): Uint8Array {
if (typeof num !== 'bigint') throw new Error('Expected bigint'); if (typeof num !== 'bigint') throw new Error('bigint expected');
if (!(_0n <= num && num < ORDER_MASK)) if (!(_0n <= num && num < ORDER_MASK))
throw new Error(`Expected number < 2^${CURVE.nBitLength}`); throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
// works with order, can have different size than numToField! // works with order, can have different size than numToField!
return ut.numberToBytesBE(num, CURVE.nByteLength); return ut.numberToBytesBE(num, CURVE.nByteLength);
} }
@@ -967,32 +912,25 @@ export function weierstrass(curveDef: CurveType): CurveFn {
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521. // NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521.
// Also it can be bigger for P224 + SHA256 // Also it can be bigger for P224 + SHA256
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) { function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
if (msgHash == null) throw new Error(`sign: expected valid message hash, not "${msgHash}"`);
if (['recovered', 'canonical'].some((k) => k in opts)) if (['recovered', 'canonical'].some((k) => k in opts))
// Ban legacy options
throw new Error('sign() legacy options not supported'); throw new Error('sign() legacy options not supported');
const { hash, randomBytes } = CURVE;
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
if (prehash) msgHash = CURVE.hash(ensureBytes(msgHash)); if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because msgHash = ensureBytes('msgHash', msgHash);
// Step A is ignored, since we already provide hash instead of msg if (prehash) msgHash = ensureBytes('prehashed msgHash', hash(msgHash));
// NOTE: instead of bits2int, we calling here truncateHash, since we need // We can't later call bits2octets, since nested bits2int is broken for curves
// custom truncation for stark. For other curves it is essentially same as calling bits2int + mod // with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call.
// However, we cannot later call bits2octets (which is truncateHash + int2octets), since nested bits2int is broken // const bits2octets = (bits) => int2octets(bits2int_modN(bits))
// for curves where nBitLength % 8 !== 0, so we unwrap it here as int2octets call. const h1int = bits2int_modN(msgHash);
// const bits2octets = (bits)=>int2octets(bytesToNumberBE(truncateHash(bits))) const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
const h1int = bits2int_modN(ensureBytes(msgHash)); const seedArgs = [int2octets(d), int2octets(h1int)];
const h1octets = int2octets(h1int); // extraEntropy. RFC6979 3.6: additional k' (optional).
const d = normalizePrivateKey(privateKey);
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const seedArgs = [int2octets(d), h1octets];
if (ent != null) { if (ent != null) {
// RFC6979 3.6: additional k' (optional) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
if (ent === true) ent = CURVE.randomBytes(Fp.BYTES); const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
const e = ensureBytes(ent); seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
if (e.length !== Fp.BYTES) throw new Error(`sign: Expected ${Fp.BYTES} bytes of extra data`);
seedArgs.push(e);
} }
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!
@@ -1005,7 +943,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
const q = Point.BASE.multiply(k).toAffine(); // q = Gk const q = Point.BASE.multiply(k).toAffine(); // q = Gk
const r = modN(q.x); // r = q.x mod n const r = modN(q.x); // r = q.x mod n
if (r === _0n) return; if (r === _0n) return;
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q1] according to
// https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
// a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
const s = modN(ik * modN(m + r * d)); // Not using blinding here
if (s === _0n) return; if (s === _0n) return;
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n) let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
let normS = s; let normS = s;
@@ -1032,8 +973,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
*/ */
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature { function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): Signature {
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
const genUntil = hmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac); const drbg = ut.createHmacDrbg<Signature>(CURVE.hash.outputLen, CURVE.nByteLength, CURVE.hmac);
return genUntil(seed, k2sig); // Steps B, C, D, E, F, G return drbg(seed, k2sig); // Steps B, C, D, E, F, G
} }
// Enable precomputes. Slows down first publicKey computation by 20ms. // Enable precomputes. Slows down first publicKey computation by 20ms.
@@ -1054,35 +995,43 @@ export function weierstrass(curveDef: CurveType): CurveFn {
* ``` * ```
*/ */
function verify( function verify(
signature: Hex | { r: bigint; s: bigint }, signature: Hex | SignatureLike,
msgHash: Hex, msgHash: Hex,
publicKey: Hex, publicKey: Hex,
opts = defaultVerOpts opts = defaultVerOpts
): boolean { ): boolean {
let P: ProjPointType<bigint>; const sg = signature;
msgHash = ensureBytes('msgHash', msgHash);
publicKey = ensureBytes('publicKey', publicKey);
if ('strict' in opts) throw new Error('options.strict was renamed to lowS');
const { lowS, prehash } = opts;
let _sig: Signature | undefined = undefined; let _sig: Signature | undefined = undefined;
if (publicKey instanceof Point) throw new Error('publicKey must be hex'); let P: ProjPointType<bigint>;
try { try {
if (signature && typeof signature === 'object' && !(signature instanceof Uint8Array)) { if (typeof sg === 'string' || sg instanceof Uint8Array) {
const { r, s } = signature;
_sig = new Signature(r, s); // assertValidity() is executed on creation
} else {
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length). // Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
// Since DER can also be 2*nByteLength bytes, we check for it first. // Since DER can also be 2*nByteLength bytes, we check for it first.
try { try {
_sig = Signature.fromDER(signature as Hex); _sig = Signature.fromDER(sg);
} catch (derError) { } catch (derError) {
if (!(derError instanceof DER.Err)) throw derError; if (!(derError instanceof DER.Err)) throw derError;
_sig = Signature.fromCompact(signature as Hex); _sig = Signature.fromCompact(sg);
} }
} else if (typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint') {
const { r, s } = sg;
_sig = new Signature(r, s);
} else {
throw new Error('PARSE');
} }
msgHash = ensureBytes(msgHash);
P = Point.fromHex(publicKey); P = Point.fromHex(publicKey);
} catch (error) { } catch (error) {
if ((error as Error).message === 'PARSE')
throw new Error(`signature must be Signature instance, Uint8Array or hex string`);
return false; return false;
} }
if (opts.lowS && _sig.hasHighS()) return false; if (lowS && _sig.hasHighS()) return false;
if (opts.prehash) msgHash = CURVE.hash(msgHash); if (prehash) msgHash = CURVE.hash(msgHash);
const { r, s } = _sig; const { r, s } = _sig;
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
const is = invN(s); // s^-1 const is = invN(s); // s^-1
@@ -1099,7 +1048,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
getSharedSecret, getSharedSecret,
sign, sign,
verify, verify,
// Point,
ProjectivePoint: Point, ProjectivePoint: Point,
Signature, Signature,
utils, utils,

View File

@@ -1,9 +1,43 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// The pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to: // bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to:
// - Construct zk-SNARKs at the 128-bit security // - Construct zk-SNARKs at the 128-bit security
// - Use threshold signatures, which allows a user to sign lots of messages with one signature and verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme. // - Use threshold signatures, which allows a user to sign lots of messages with one signature and
// Differences from @noble/bls12-381 1.4: // verify them swiftly in a batch, using Boneh-Lynn-Shacham signature scheme.
//
// 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
// [pairing-curves-10](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-10),
// [bls-sigs-04](https://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).
//
// ### Summary
// 1. BLS Relies on Bilinear Pairing (expensive)
// 2. Private Keys: 32 bytes
// 3. Public Keys: 48 bytes: 381 bit affine x coordinate, encoded into 48 big-endian bytes.
// 4. Signatures: 96 bytes: two 381 bit integers (affine x coordinate), encoded into two 48 big-endian byte arrays.
// - The signature is a point on the G2 subgroup, which is defined over a finite field
// with elements twice as big as the G1 curve (G2 is over Fp2 rather than Fp. Fp2 is analogous to the complex numbers).
// 5. The 12 stands for the Embedding degree.
//
// ### Formulas
// - `P = pk x G` - public keys
// - `S = pk x H(m)` - signing
// - `e(P, H(m)) == e(G, S)` - verification using pairings
// - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
// Filecoin uses little endian byte arrays for private keys -
// so ensure to reverse byte order if you'll use it with FIL.
//
// ### Resources
// - [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)
// - Pairing over bls12-381:
// [part 1](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/),
// [part 3](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/)
//
// ### Differences from @noble/bls12-381 1.4
// - PointG1 -> G1.Point // - PointG1 -> G1.Point
// - PointG2 -> G2.Point // - PointG2 -> G2.Point
// - PointG2.fromSignature -> Signature.decode // - PointG2.fromSignature -> Signature.decode
@@ -910,7 +944,7 @@ function G2psi2(c: ProjConstructor<Fp2>, P: ProjPointType<Fp2>) {
// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
// m = 2 (or 1 for G1 see section 8.8.1) // m = 2 (or 1 for G1 see section 8.8.1)
// k = 128 // k = 128
const htfDefaults = { const htfDefaults = Object.freeze({
// DST: a domain separation tag // DST: a domain separation tag
// defined in section 2.2.5 // defined in section 2.2.5
// Use utils.getDSTLabel(), utils.setDSTLabel(value) // Use utils.getDSTLabel(), utils.setDSTLabel(value)
@@ -932,7 +966,7 @@ const htfDefaults = {
// 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; } as const);
// Encoding utils // Encoding utils
// Point on G1 curve: (x, y) // Point on G1 curve: (x, y)
@@ -1186,7 +1220,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
Signature: { Signature: {
// TODO: Optimize, it's very slow because of sqrt. // TODO: Optimize, it's very slow because of sqrt.
decode(hex: Hex): ProjPointType<Fp2> { decode(hex: Hex): ProjPointType<Fp2> {
hex = ensureBytes(hex); hex = ensureBytes('signatureHex', hex);
const P = Fp.ORDER; const P = Fp.ORDER;
const half = hex.length / 2; const half = hex.length / 2;
if (half !== 48 && half !== 96) if (half !== 48 && half !== 96)
@@ -1213,7 +1247,6 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1; const isZero = y1 === 0n && (y0 * 2n) / P !== aflag1;
if (isGreater || isZero) y = Fp2.neg(y); if (isGreater || isZero) y = Fp2.neg(y);
const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y }); const point = bls12_381.G2.ProjectivePoint.fromAffine({ x, y });
// console.log('Signature.decode', point);
point.assertValidity(); point.assertValidity();
return point; return point;
}, },

View File

@@ -5,14 +5,15 @@ import { twistedEdwards, ExtPointType } from './abstract/edwards.js';
import { montgomery } from './abstract/montgomery.js'; import { montgomery } from './abstract/montgomery.js';
import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js'; import { mod, pow2, isNegativeLE, Fp as Field, FpSqrtEven } from './abstract/modular.js';
import { import {
ensureBytes,
equalBytes, equalBytes,
bytesToHex, bytesToHex,
bytesToNumberLE, bytesToNumberLE,
numberToBytesLE, numberToBytesLE,
Hex, Hex,
ensureBytes,
} from './abstract/utils.js'; } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
import { AffinePoint } from './abstract/curve.js';
/** /**
* ed25519 Twisted Edwards curve with following addons: * ed25519 Twisted Edwards curve with following addons:
@@ -138,10 +139,10 @@ export const ed25519ph = twistedEdwards({
export const x25519 = montgomery({ export const x25519 = montgomery({
P: ED25519_P, P: ED25519_P,
a24: BigInt('121665'), a: BigInt(486662),
montgomeryBits: 255, // n is 253 bits montgomeryBits: 255, // n is 253 bits
nByteLength: 32, nByteLength: 32,
Gu: '0900000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(9),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ED25519_P; const P = ED25519_P;
// x^(p-2) aka x^(2^255-21) // x^(p-2) aka x^(2^255-21)
@@ -149,6 +150,7 @@ export const x25519 = montgomery({
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P); return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes,
}); });
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator) // Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
@@ -223,7 +225,7 @@ function map_to_curve_elligator2_edwards25519(u: bigint) {
const inv = Fp.invertBatch([xd, yd]); // batch division 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) return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
} }
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
ed25519.ExtendedPoint, ed25519.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
{ {
@@ -308,6 +310,11 @@ export class RistrettoPoint {
// Private property to discourage combining ExtendedPoint + RistrettoPoint // Private property to discourage combining ExtendedPoint + RistrettoPoint
// Always use Ristretto encoding/decoding instead. // Always use Ristretto encoding/decoding instead.
constructor(private readonly ep: ExtendedPoint) {} constructor(private readonly ep: ExtendedPoint) {}
static fromAffine(ap: AffinePoint<bigint>) {
return new RistrettoPoint(ed25519.ExtendedPoint.fromAffine(ap));
}
/** /**
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`. * Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`.
* The hash-to-group operation applies Elligator twice and adds the results. * The hash-to-group operation applies Elligator twice and adds the results.
@@ -316,7 +323,7 @@ export class RistrettoPoint {
* @param hex 64-bit output of a hash function * @param hex 64-bit output of a hash function
*/ */
static hashToCurve(hex: Hex): RistrettoPoint { static hashToCurve(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 64); hex = ensureBytes('ristrettoHash', hex, 64);
const r1 = bytes255ToNumberLE(hex.slice(0, 32)); const r1 = bytes255ToNumberLE(hex.slice(0, 32));
const R1 = calcElligatorRistrettoMap(r1); const R1 = calcElligatorRistrettoMap(r1);
const r2 = bytes255ToNumberLE(hex.slice(32, 64)); const r2 = bytes255ToNumberLE(hex.slice(32, 64));
@@ -330,7 +337,7 @@ export class RistrettoPoint {
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding * @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
*/ */
static fromHex(hex: Hex): RistrettoPoint { static fromHex(hex: Hex): RistrettoPoint {
hex = ensureBytes(hex, 32); hex = ensureBytes('ristrettoHex', hex, 32);
const { a, d } = ed25519.CURVE; const { a, d } = ed25519.CURVE;
const P = ed25519.CURVE.Fp.ORDER; const P = ed25519.CURVE.Fp.ORDER;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
@@ -400,7 +407,7 @@ export class RistrettoPoint {
equals(other: RistrettoPoint): boolean { equals(other: RistrettoPoint): boolean {
assertRstPoint(other); assertRstPoint(other);
const { ex: X1, ey: Y1 } = this.ep; const { ex: X1, ey: Y1 } = this.ep;
const { ex: X2, ey: Y2 } = this.ep; const { ex: X2, ey: Y2 } = other.ep;
const mod = ed25519.CURVE.Fp.create; const mod = ed25519.CURVE.Fp.create;
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2) // (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
const one = mod(X1 * Y2) === mod(Y1 * X2); const one = mod(X1 * Y2) === mod(Y1 * X2);
@@ -426,3 +433,13 @@ export class RistrettoPoint {
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar)); return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
} }
} }
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/14/
// Appendix B. Hashing to ristretto255
export const hash_to_ristretto255 = (msg: Uint8Array, options: htf.htfBasicOpts) => {
const d = options.DST;
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
const uniform_bytes = htf.expand_message_xmd(msg, DST, 64, sha512);
const P = RistrettoPoint.hashToCurve(uniform_bytes);
return P;
};

View File

@@ -122,11 +122,11 @@ export const ed448 = twistedEdwards(ED448_DEF);
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 }); export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
export const x448 = montgomery({ export const x448 = montgomery({
a24: BigInt(39081), a: BigInt(156326),
montgomeryBits: 448, montgomeryBits: 448,
nByteLength: 57, nByteLength: 57,
P: ed448P, P: ed448P,
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', Gu: BigInt(5),
powPminus2: (x: bigint): bigint => { powPminus2: (x: bigint): bigint => {
const P = ed448P; const P = ed448P;
const Pminus3div4 = ed448_pow_Pminus3div4(x); const Pminus3div4 = ed448_pow_Pminus3div4(x);
@@ -134,6 +134,7 @@ export const x448 = montgomery({
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2 return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
}, },
adjustScalarBytes, adjustScalarBytes,
randomBytes,
// The 4-isogeny maps between the Montgomery curve and this Edwards // The 4-isogeny maps between the Montgomery curve and this Edwards
// curve are: // curve are:
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3) // (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)
@@ -225,7 +226,7 @@ function map_to_curve_elligator2_edwards448(u: bigint) {
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd) return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
} }
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
ed448.ExtendedPoint, ed448.ExtendedPoint,
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]), (scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
{ {

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha256 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const P192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
} as const,
sha256
);
export const secp192r1 = P192;

View File

@@ -1,25 +0,0 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js';
import { sha224 } from '@noble/hashes/sha256';
import { Fp } from './abstract/modular.js';
// NIST secp224r1 aka P224
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-224
export const P224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations; 2n**224n - 2n**96n + 1n
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
} as const,
sha224
);
export const secp224r1 = P224;

View File

@@ -37,7 +37,7 @@ export const P256 = createCurve(
); );
export const secp256r1 = P256; export const secp256r1 = P256;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256r1.ProjectivePoint, secp256r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

View File

@@ -41,7 +41,7 @@ export const P384 = createCurve({
); );
export const secp384r1 = P384; export const secp384r1 = P384;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp384r1.ProjectivePoint, secp384r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

View File

@@ -1,7 +1,6 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from './_shortw_utils.js'; import { createCurve } from './_shortw_utils.js';
import { sha512 } from '@noble/hashes/sha512'; import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, PrivKey } from './abstract/utils.js';
import { Fp as Field } from './abstract/modular.js'; import { Fp as Field } from './abstract/modular.js';
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js'; import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
@@ -38,20 +37,11 @@ export const P521 = createCurve({
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'), Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
h: BigInt(1), h: BigInt(1),
lowS: false, lowS: false,
// P521 keys could be 130, 131, 132 bytes. We normalize to 132 bytes. allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
// Does not replace validation; invalid keys would still be rejected.
normalizePrivateKey(key: PrivKey) {
if (typeof key === 'bigint') return key;
if (key instanceof Uint8Array) key = bytesToHex(key);
if (typeof key !== 'string' || !([130, 131, 132].includes(key.length))) {
throw new Error('Invalid key');
}
return key.padStart(66 * 2, '0'); // ensure it's always 132 bytes
},
} as const, sha512); } as const, sha512);
export const secp521r1 = P521; export const secp521r1 = P521;
const { hashToCurve, encodeToCurve } = htf.hashToCurve( const { hashToCurve, encodeToCurve } = htf.createHasher(
secp521r1.ProjectivePoint, secp521r1.ProjectivePoint,
(scalars: bigint[]) => mapSWU(scalars[0]), (scalars: bigint[]) => mapSWU(scalars[0]),
{ {

View File

@@ -1,26 +1,12 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { createCurve } from './_shortw_utils.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import {
ensureBytes,
concatBytes,
Hex,
bytesToNumberBE as bytesToNum,
PrivKey,
numberToBytesBE,
} from './abstract/utils.js';
import { randomBytes } from '@noble/hashes/utils'; import { randomBytes } from '@noble/hashes/utils';
import { Fp as Field, mod, pow2 } from './abstract/modular.js';
import { ProjPointType as PointType, mapToCurveSimpleSWU } from './abstract/weierstrass.js';
import type { Hex, PrivKey } from './abstract/utils.js';
import { bytesToNumberBE, concatBytes, ensureBytes, numberToBytesBE } from './abstract/utils.js';
import * as htf from './abstract/hash-to-curve.js'; import * as htf from './abstract/hash-to-curve.js';
import { createCurve } from './_shortw_utils.js';
/**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* Should always be used for Projective's double-and-add multiplication.
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'); const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'); const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
@@ -61,23 +47,22 @@ type Fp = bigint;
export const secp256k1 = createCurve( export const secp256k1 = createCurve(
{ {
// Params: a, b a: BigInt(0), // equation params: a, b
// Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975 b: BigInt(7), // Seem to be rigid: bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
a: BigInt(0), Fp, // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
b: BigInt(7), n: secp256k1N, // Curve order, total count of valid points in the field
// Field over which we'll do calculations;
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
Fp,
// Curve order, total count of valid points in the field
n: secp256k1N,
// Base point (x, y) aka generator point // Base point (x, y) aka generator point
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'), Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'), Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
h: BigInt(1), h: BigInt(1), // Cofactor
// Alllow only low-S signatures by default in sign() and verify() lowS: true, // Allow only low-S signatures by default in sign() and verify()
lowS: true, /**
* secp256k1 belongs to Koblitz curves: it has efficiently computable endomorphism.
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
* Explanation: https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
*/
endo: { endo: {
// Params taken from https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'), beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
splitScalar: (k: bigint) => { splitScalar: (k: bigint) => {
const n = secp256k1N; const n = secp256k1N;
@@ -105,19 +90,11 @@ export const secp256k1 = createCurve(
sha256 sha256
); );
// Schnorr signatures are superior to ECDSA from above. // Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
// Below is Schnorr-specific code as per BIP0340.
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
const _0n = BigInt(0); const _0n = BigInt(0);
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P; const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N; const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
const TAGS = {
challenge: 'BIP0340/challenge',
aux: 'BIP0340/aux',
nonce: 'BIP0340/nonce',
} as const;
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {}; const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array { function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
@@ -130,60 +107,70 @@ function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
return sha256(concatBytes(tagP, ...messages)); return sha256(concatBytes(tagP, ...messages));
} }
const toRawX = (point: PointType<bigint>) => point.toRawBytes(true).slice(1); // ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
const pointToBytes = (point: PointType<bigint>) => point.toRawBytes(true).slice(1);
const numTo32b = (n: bigint) => numberToBytesBE(n, 32); const numTo32b = (n: bigint) => numberToBytesBE(n, 32);
const modP = (x: bigint) => mod(x, secp256k1P);
const modN = (x: bigint) => mod(x, secp256k1N); const modN = (x: bigint) => mod(x, secp256k1N);
const _Point = secp256k1.ProjectivePoint; const Point = secp256k1.ProjectivePoint;
const Gmul = (priv: PrivKey) => _Point.fromPrivateKey(priv);
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) => const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
_Point.BASE.multiplyAndAddUnsafe(Q, a, b); Point.BASE.multiplyAndAddUnsafe(Q, a, b);
function schnorrGetScalar(priv: bigint) {
// Let d' = int(sk) // Calculate point, scalar and bytes
// Fail if d' = 0 or d' ≥ n function schnorrGetExtPubKey(priv: PrivKey) {
// Let P = d'⋅G let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey
// Let d = d' if has_even_y(P), otherwise let d = n - d' . let p = Point.fromPrivateKey(d_); // P = d'⋅G; 0 < d' < n check is done inside
const point = Gmul(priv); const scalar = p.hasEvenY() ? d_ : modN(-d_);
const scalar = point.hasEvenY() ? priv : modN(-priv); return { scalar: scalar, bytes: pointToBytes(p) };
return { point, scalar, x: toRawX(point) };
} }
/**
* lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
* @returns valid point checked for being on-curve
*/
function lift_x(x: bigint): PointType<bigint> { function lift_x(x: bigint): PointType<bigint> {
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p. if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ p.
const c = mod(x * x * x + BigInt(7), secp256k1P); // Let c = x³ + 7 mod p. const xx = modP(x * x);
const c = modP(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.
let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p. let y = sqrtMod(c); // Let y = c^(p+1)/4 mod p.
if (y % 2n !== 0n) y = mod(-y, secp256k1P); // Return the unique point P such that x(P) = x and if (y % 2n !== 0n) y = modP(-y); // Return the unique point P such that x(P) = x and
const p = new _Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. const p = new Point(x, y, _1n); // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
p.assertValidity(); p.assertValidity();
return p; return p;
} }
function challenge(...args: Uint8Array[]): bigint {
return modN(bytesToNum(taggedHash(TAGS.challenge, ...args)));
}
function schnorrGetPublicKey(privateKey: PrivKey): Uint8Array {
return toRawX(Gmul(privateKey)); // Let d' = int(sk). Fail if d' = 0 or d' ≥ n. Return bytes(d'⋅G)
}
/** /**
* Synchronously creates Schnorr signature. Improved security: verifies itself before * Create tagged hash, convert it to bigint, reduce modulo-n.
* producing an output.
* @param msg message (not message hash)
* @param privateKey private key
* @param auxRand random bytes that would be added to k. Bad RNG won't break it.
*/ */
function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(32)): Uint8Array { function challenge(...args: Uint8Array[]): bigint {
if (message == null) throw new Error(`sign: Expected valid message, not "${message}"`); return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
const m = ensureBytes(message); }
// checks for isWithinCurveOrder
const { x: px, scalar: d } = schnorrGetScalar(bytesToNum(ensureBytes(privateKey, 32))); /**
const a = ensureBytes(auxRand, 32); // Auxiliary random data a: a 32-byte array * Schnorr public key is just `x` coordinate of Point as per BIP340.
// TODO: replace with proper xor? */
const t = numTo32b(d ^ bytesToNum(taggedHash(TAGS.aux, a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a) function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
const rand = taggedHash(TAGS.nonce, t, px, m); // Let rand = hash/nonce(t || bytes(P) || m) return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
const k_ = modN(bytesToNum(rand)); // Let k' = int(rand) mod n }
/**
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
*/
function schnorrSign(
message: Hex,
privateKey: PrivKey,
auxRand: Hex = randomBytes(32)
): Uint8Array {
const m = ensureBytes('message', message);
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey); // checks for isWithinCurveOrder
const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array
const t = numTo32b(d ^ bytesToNumberBE(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
const k_ = modN(bytesToNumberBE(rand)); // Let k' = int(rand) mod n
if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0. if (k_ === _0n) throw new Error('sign failed: k is zero'); // Fail if k' = 0.
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k_); // Let R = k'⋅G. const { bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); // Let R = k'⋅G.
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n). const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
sig.set(numTo32b(R.px), 0); sig.set(rx, 0);
sig.set(numTo32b(modN(k + e * d)), 32); sig.set(numTo32b(modN(k + e * d)), 32);
// If Verify(bytes(P), m, sig) (see below) returns failure, abort // If Verify(bytes(P), m, sig) (see below) returns failure, abort
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced'); if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
@@ -191,18 +178,20 @@ function schnorrSign(message: Hex, privateKey: Hex, auxRand: Hex = randomBytes(3
} }
/** /**
* Verifies Schnorr signature synchronously. * Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/ */
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean { function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
const sig = ensureBytes('signature', signature, 64);
const m = ensureBytes('message', message);
const pub = ensureBytes('publicKey', publicKey, 32);
try { try {
const P = lift_x(bytesToNum(ensureBytes(publicKey, 32))); // P = lift_x(int(pk)); fail if that fails const P = lift_x(bytesToNumberBE(pub)); // P = lift_x(int(pk)); fail if that fails
const sig = ensureBytes(signature, 64); const r = bytesToNumberBE(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
const r = bytesToNum(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
if (!fe(r)) return false; if (!fe(r)) return false;
const s = bytesToNum(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n. const s = bytesToNumberBE(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
if (!ge(s)) return false; if (!ge(s)) return false;
const m = ensureBytes(message); const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
const e = challenge(numTo32b(r), toRawX(P), m); // int(challenge(bytes(r)||bytes(P)||m)) mod n
const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P const R = GmulAdd(P, s, modN(-e)); // R = s⋅G - e⋅P
if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P if (!R || !R.hasEvenY() || R.toAffine().x !== r) return false; // -eP == (n-e)P
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r. return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
@@ -212,11 +201,18 @@ function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
} }
export const schnorr = { export const schnorr = {
// Schnorr's pubkey is just `x` of Point (BIP340)
getPublicKey: schnorrGetPublicKey, getPublicKey: schnorrGetPublicKey,
sign: schnorrSign, sign: schnorrSign,
verify: schnorrVerify, verify: schnorrVerify,
utils: { lift_x, int: bytesToNum, taggedHash }, utils: {
randomPrivateKey: secp256k1.utils.randomPrivateKey,
lift_x,
pointToBytes,
numberToBytesBE,
bytesToNumberBE,
taggedHash,
mod,
},
}; };
const isoMap = htf.isogenyMap( const isoMap = htf.isogenyMap(
@@ -256,7 +252,7 @@ const mapSWU = mapToCurveSimpleSWU(Fp, {
B: BigInt('1771'), B: BigInt('1771'),
Z: Fp.create(BigInt('-11')), Z: Fp.create(BigInt('-11')),
}); });
const { hashToCurve, encodeToCurve } = htf.hashToCurve( export const { hashToCurve, encodeToCurve } = htf.createHasher(
secp256k1.ProjectivePoint, secp256k1.ProjectivePoint,
(scalars: bigint[]) => { (scalars: bigint[]) => {
const { x, y } = mapSWU(Fp.create(scalars[0])); const { x, y } = mapSWU(Fp.create(scalars[0]));
@@ -272,4 +268,3 @@ const { hashToCurve, encodeToCurve } = htf.hashToCurve(
hash: sha256, hash: sha256,
} }
); );
export { hashToCurve, encodeToCurve };

View File

@@ -1,164 +1,117 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { keccak_256 } from '@noble/hashes/sha3'; import { keccak_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import { weierstrass, ProjPointType } from './abstract/weierstrass.js';
import * as cutils from './abstract/utils.js';
import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { getHash } from './_shortw_utils.js';
import * as poseidon from './abstract/poseidon.js';
import { utf8ToBytes } from '@noble/hashes/utils'; import { utf8ToBytes } from '@noble/hashes/utils';
import { Fp, mod, Field, validateField } from './abstract/modular.js';
import { poseidon } from './abstract/poseidon.js';
import { weierstrass, ProjPointType, SignatureType } from './abstract/weierstrass.js';
import * as u from './abstract/utils.js';
import type { Hex } from './abstract/utils.js';
import { getHash } from './_shortw_utils.js';
type ProjectivePoint = ProjPointType<bigint>;
// Stark-friendly elliptic curve // Stark-friendly elliptic curve
// https://docs.starkware.co/starkex/stark-curve.html // https://docs.starkware.co/starkex/stark-curve.html
const CURVE_N = BigInt( type ProjectivePoint = ProjPointType<bigint>;
const CURVE_ORDER = BigInt(
'3618502788666131213697322783095070105526743751716087489154079457884512865583' '3618502788666131213697322783095070105526743751716087489154079457884512865583'
); );
const nBitLength = 252; const nBitLength = 252;
// Copy-pasted from weierstrass.ts
function bits2int(bytes: Uint8Array): bigint { function bits2int(bytes: Uint8Array): bigint {
while (bytes[0] === 0) bytes = bytes.subarray(1); // strip leading 0s
// Copy-pasted from weierstrass.ts
const delta = bytes.length * 8 - nBitLength; const delta = bytes.length * 8 - nBitLength;
const num = cutils.bytesToNumberBE(bytes); const num = u.bytesToNumberBE(bytes);
return delta > 0 ? num >> BigInt(delta) : num; return delta > 0 ? num >> BigInt(delta) : num;
} }
function bits2int_modN(bytes: Uint8Array): bigint { function hex0xToBytes(hex: string): Uint8Array {
return mod(bits2int(bytes), CURVE_N); if (typeof hex === 'string') {
hex = strip0x(hex); // allow 0x prefix
if (hex.length & 1) hex = '0' + hex; // allow unpadded hex
}
return u.hexToBytes(hex);
} }
export const starkCurve = weierstrass({ const curve = weierstrass({
// Params: a, b a: BigInt(1), // Params: a, b
a: BigInt(1),
b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'), b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'),
// Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n // Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n
// There is no efficient sqrt for field (P%4==1) // There is no efficient sqrt for field (P%4==1)
Fp: Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001')), Fp: Fp(BigInt('0x800000000000011000000000000000000000000000000000000000000000001')),
// Curve order, total count of valid points in the field. n: CURVE_ORDER, // Curve order, total count of valid points in the field.
n: CURVE_N, nBitLength, // len(bin(N).replace('0b',''))
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
// Base point (x, y) aka generator point // Base point (x, y) aka generator point
Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'), Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'),
Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'), Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'),
h: BigInt(1), h: BigInt(1), // cofactor
// Default options lowS: false, // Allow high-s signatures
lowS: false,
...getHash(sha256), ...getHash(sha256),
// Custom truncation routines for stark curve // Custom truncation routines for stark curve
bits2int: (bytes: Uint8Array): bigint => { bits2int,
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int(bytes);
},
bits2int_modN: (bytes: Uint8Array): bigint => { bits2int_modN: (bytes: Uint8Array): bigint => {
let hashS = cutils.bytesToNumberBE(bytes).toString(16); // 2102820b232636d200cb21f1d330f20d096cae09d1bf3edb1cc333ddee11318 =>
if (hashS.length === 63) { // 2102820b232636d200cb21f1d330f20d096cae09d1bf3edb1cc333ddee113180
hashS += '0'; const hex = u.bytesToNumberBE(bytes).toString(16); // toHex unpadded
bytes = hexToBytes0x(hashS); if (hex.length === 63) bytes = hex0xToBytes(hex + '0'); // append trailing 0
} return mod(bits2int(bytes), CURVE_ORDER);
// Truncate zero bytes on left (compat with elliptic)
while (bytes[0] === 0) bytes = bytes.subarray(1);
return bits2int_modN(bytes);
}, },
}); });
export const _starkCurve = curve;
// Custom Starknet type conversion functions that can handle 0x and unpadded hex function ensureBytes(hex: Hex): Uint8Array {
function hexToBytes0x(hex: string): Uint8Array { return u.ensureBytes('', typeof hex === 'string' ? hex0xToBytes(hex) : hex);
if (typeof hex !== 'string') {
throw new Error('hexToBytes: expected string, got ' + typeof hex);
}
hex = strip0x(hex);
if (hex.length & 1) hex = '0' + hex; // padding
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
function hexToNumber0x(hex: string): bigint {
if (typeof hex !== 'string') {
throw new Error('hexToNumber: expected string, got ' + typeof hex);
}
// Big Endian
// TODO: strip vs no strip?
return BigInt(`0x${strip0x(hex)}`);
}
function bytesToNumber0x(bytes: Uint8Array): bigint {
return hexToNumber0x(cutils.bytesToHex(bytes));
}
function ensureBytes0x(hex: Hex): Uint8Array {
// Uint8Array.from() instead of hash.slice() because node.js Buffer
// is instance of Uint8Array, and its slice() creates **mutable** copy
return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes0x(hex);
} }
function normalizePrivateKey(privKey: Hex) { function normPrivKey(privKey: Hex): string {
return cutils.bytesToHex(ensureBytes0x(privKey)).padStart(64, '0'); return u.bytesToHex(ensureBytes(privKey)).padStart(64, '0');
} }
function getPublicKey0x(privKey: Hex, isCompressed?: boolean) { export function getPublicKey(privKey: Hex, isCompressed = false): Uint8Array {
return starkCurve.getPublicKey(normalizePrivateKey(privKey), isCompressed); return curve.getPublicKey(normPrivKey(privKey), isCompressed);
} }
function getSharedSecret0x(privKeyA: Hex, pubKeyB: Hex) { export function getSharedSecret(privKeyA: Hex, pubKeyB: Hex): Uint8Array {
return starkCurve.getSharedSecret(normalizePrivateKey(privKeyA), pubKeyB); return curve.getSharedSecret(normPrivKey(privKeyA), pubKeyB);
}
export function sign(msgHash: Hex, privKey: Hex, opts?: any): SignatureType {
return curve.sign(ensureBytes(msgHash), normPrivKey(privKey), opts);
}
export function verify(signature: SignatureType | Hex, msgHash: Hex, pubKey: Hex) {
const sig = signature instanceof Signature ? signature : ensureBytes(signature);
return curve.verify(sig, ensureBytes(msgHash), ensureBytes(pubKey));
} }
function sign0x(msgHash: Hex, privKey: Hex, opts?: any) { const { CURVE, ProjectivePoint, Signature, utils } = curve;
if (typeof privKey === 'string') privKey = strip0x(privKey).padStart(64, '0'); export { CURVE, ProjectivePoint, Signature, utils };
return starkCurve.sign(ensureBytes0x(msgHash), normalizePrivateKey(privKey), opts);
function extractX(bytes: Uint8Array): string {
const hex = u.bytesToHex(bytes.subarray(1));
const stripped = hex.replace(/^0+/gm, ''); // strip leading 0s
return `0x${stripped}`;
} }
function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) { function strip0x(hex: string) {
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature); return hex.replace(/^0x/i, '');
return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey)); }
} function numberTo0x16(num: bigint) {
// can't use utils.numberToHexUnpadded: adds leading 0 for even byte length
const { CURVE, ProjectivePoint, Signature } = starkCurve; return `0x${num.toString(16)}`;
export const utils = starkCurve.utils;
export {
CURVE,
Signature,
ProjectivePoint,
getPublicKey0x as getPublicKey,
getSharedSecret0x as getSharedSecret,
sign0x as sign,
verify0x as verify,
};
const stripLeadingZeros = (s: string) => s.replace(/^0+/gm, '');
export const bytesToHexEth = (uint8a: Uint8Array): string =>
`0x${stripLeadingZeros(cutils.bytesToHex(uint8a))}`;
export const strip0x = (hex: string) => hex.replace(/^0x/i, '');
export const numberToHexEth = (num: bigint | number) => `0x${num.toString(16)}`;
// We accept hex strings besides Uint8Array for simplicity
type Hex = Uint8Array | string;
// 1. seed generation
function hashKeyWithIndex(key: Uint8Array, index: number) {
let indexHex = cutils.numberToHexUnpadded(index);
if (indexHex.length & 1) indexHex = '0' + indexHex;
return sha256Num(cutils.concatBytes(key, hexToBytes0x(indexHex)));
} }
// seed generation
export function grindKey(seed: Hex) { export function grindKey(seed: Hex) {
const _seed = ensureBytes0x(seed); const _seed = ensureBytes(seed);
const sha256mask = 2n ** 256n; const sha256mask = 2n ** 256n;
const limit = sha256mask - mod(sha256mask, CURVE_ORDER);
const limit = sha256mask - mod(sha256mask, CURVE_N);
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
const key = hashKeyWithIndex(_seed, i); const key = sha256Num(u.concatBytes(_seed, u.numberToVarBytesBE(BigInt(i))));
// key should be in [0, limit) if (key < limit) return mod(key, CURVE_ORDER).toString(16); // key should be in [0, limit)
if (key < limit) return mod(key, CURVE_N).toString(16); if (i === 100000) throw new Error('grindKey is broken: tried 100k vals'); // prevent dos
} }
} }
export function getStarkKey(privateKey: Hex) { export function getStarkKey(privateKey: Hex): string {
return bytesToHexEth(getPublicKey0x(privateKey, true).slice(1)); return extractX(getPublicKey(privateKey, true));
} }
export function ethSigToPrivate(signature: string) { export function ethSigToPrivate(signature: string): string {
signature = strip0x(signature.replace(/^0x/, '')); signature = strip0x(signature);
if (signature.length !== 130) throw new Error('Wrong ethereum signature'); if (signature.length !== 130) throw new Error('Wrong ethereum signature');
return grindKey(signature.substring(0, 64)); return grindKey(signature.substring(0, 64));
} }
@@ -170,15 +123,15 @@ export function getAccountPath(
application: string, application: string,
ethereumAddress: string, ethereumAddress: string,
index: number index: number
) { ): string {
const layerNum = int31(sha256Num(layer)); const layerNum = int31(sha256Num(layer));
const applicationNum = int31(sha256Num(application)); const applicationNum = int31(sha256Num(application));
const eth = hexToNumber0x(ethereumAddress); const eth = u.hexToNumber(strip0x(ethereumAddress));
return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`; return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
} }
// https://docs.starkware.co/starkex/pedersen-hash-function.html // https://docs.starkware.co/starkex/pedersen-hash-function.html
const PEDERSEN_POINTS_AFFINE = [ const PEDERSEN_POINTS = [
new ProjectivePoint( new ProjectivePoint(
2089986280348253421170679821480865132823066470938446095505822317253594081284n, 2089986280348253421170679821480865132823066470938446095505822317253594081284n,
1713931329540660377023406109199410414810705867260802078187082345529207694986n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n,
@@ -205,8 +158,6 @@ const PEDERSEN_POINTS_AFFINE = [
1n 1n
), ),
]; ];
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE;
function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] { function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] {
const out: ProjectivePoint[] = []; const out: ProjectivePoint[] = [];
@@ -230,14 +181,16 @@ const PEDERSEN_POINTS2 = pedersenPrecompute(PEDERSEN_POINTS[3], PEDERSEN_POINTS[
type PedersenArg = Hex | bigint | number; type PedersenArg = Hex | bigint | number;
function pedersenArg(arg: PedersenArg): bigint { function pedersenArg(arg: PedersenArg): bigint {
let value: bigint; let value: bigint;
if (typeof arg === 'bigint') value = arg; if (typeof arg === 'bigint') {
else if (typeof arg === 'number') { value = arg;
} else if (typeof arg === 'number') {
if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`); if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`);
value = BigInt(arg); value = BigInt(arg);
} else value = bytesToNumber0x(ensureBytes0x(arg)); } else {
// [0..Fp) value = u.bytesToNumberBE(ensureBytes(arg));
if (!(0n <= value && value < starkCurve.CURVE.Fp.ORDER)) }
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`); if (!(0n <= value && value < curve.CURVE.Fp.ORDER))
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`); // [0..Fp)
return value; return value;
} }
@@ -245,7 +198,7 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
let x = pedersenArg(value); let x = pedersenArg(value);
for (let j = 0; j < 252; j++) { for (let j = 0; j < 252; j++) {
const pt = constants[j]; const pt = constants[j];
if (pt.px === point.px) throw new Error('Same point'); if (pt.equals(point)) throw new Error('Same point');
if ((x & 1n) !== 0n) point = point.add(pt); if ((x & 1n) !== 0n) point = point.add(pt);
x >>= 1n; x >>= 1n;
} }
@@ -253,17 +206,17 @@ function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: P
} }
// shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3 // shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3
export function pedersen(x: PedersenArg, y: PedersenArg) { export function pedersen(x: PedersenArg, y: PedersenArg): string {
let point: ProjectivePoint = PEDERSEN_POINTS[0]; let point: ProjectivePoint = PEDERSEN_POINTS[0];
point = pedersenSingle(point, x, PEDERSEN_POINTS1); point = pedersenSingle(point, x, PEDERSEN_POINTS1);
point = pedersenSingle(point, y, PEDERSEN_POINTS2); point = pedersenSingle(point, y, PEDERSEN_POINTS2);
return bytesToHexEth(point.toRawBytes(true).slice(1)); return extractX(point.toRawBytes(true));
} }
export function hashChain(data: PedersenArg[], fn = pedersen) { export function hashChain(data: PedersenArg[], fn = pedersen) {
if (!Array.isArray(data) || data.length < 1) if (!Array.isArray(data) || data.length < 1)
throw new Error('data should be array of at least 1 element'); throw new Error('data should be array of at least 1 element');
if (data.length === 1) return numberToHexEth(pedersenArg(data[0])); if (data.length === 1) return numberTo0x16(pedersenArg(data[0]));
return Array.from(data) return Array.from(data)
.reverse() .reverse()
.reduce((acc, i) => fn(i, acc)); .reduce((acc, i) => fn(i, acc));
@@ -272,9 +225,9 @@ export function hashChain(data: PedersenArg[], fn = pedersen) {
export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) => export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) =>
[0, ...data, data.length].reduce((x, y) => fn(x, y)); [0, ...data, data.length].reduce((x, y) => fn(x, y));
const MASK_250 = cutils.bitMask(250); const MASK_250 = u.bitMask(250);
export const keccak = (data: Uint8Array): bigint => bytesToNumber0x(keccak_256(data)) & MASK_250; export const keccak = (data: Uint8Array): bigint => u.bytesToNumberBE(keccak_256(data)) & MASK_250;
const sha256Num = (data: Uint8Array | string): bigint => cutils.bytesToNumberBE(sha256(data)); const sha256Num = (data: Uint8Array | string): bigint => u.bytesToNumberBE(sha256(data));
// Poseidon hash // Poseidon hash
export const Fp253 = Fp( export const Fp253 = Fp(
@@ -318,7 +271,13 @@ export type PoseidonOpts = {
roundsPartial: number; roundsPartial: number;
}; };
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) { export type PoseidonFn = ReturnType<typeof poseidon> & {
m: number;
rate: number;
capacity: number;
};
export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]): PoseidonFn {
validateField(opts.Fp); validateField(opts.Fp);
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity)) if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
throw new Error(`Wrong poseidon opts: ${opts}`); throw new Error(`Wrong poseidon opts: ${opts}`);
@@ -330,7 +289,7 @@ export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j)); for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
roundConstants.push(row); roundConstants.push(row);
} }
return poseidon.poseidon({ const res: Partial<PoseidonFn> = poseidon({
...opts, ...opts,
t: m, t: m,
sboxPower: 3, sboxPower: 3,
@@ -338,6 +297,10 @@ export function poseidonBasic(opts: PoseidonOpts, mds: bigint[][]) {
mds, mds,
roundConstants, roundConstants,
}); });
res.m = m;
res.rate = opts.rate;
res.capacity = opts.capacity;
return res as PoseidonFn;
} }
export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) { export function poseidonCreate(opts: PoseidonOpts, mdsAttempt = 0) {
@@ -351,6 +314,28 @@ export const poseidonSmall = poseidonBasic(
MDS_SMALL MDS_SMALL
); );
export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall) { export function poseidonHash(x: bigint, y: bigint, fn = poseidonSmall): bigint {
return fn([x, y, 2n])[0]; return fn([x, y, 2n])[0];
} }
export function poseidonHashFunc(x: Uint8Array, y: Uint8Array, fn = poseidonSmall): Uint8Array {
return u.numberToVarBytesBE(poseidonHash(u.bytesToNumberBE(x), u.bytesToNumberBE(y), fn));
}
export function poseidonHashSingle(x: bigint, fn = poseidonSmall): bigint {
return fn([x, 0n, 1n])[0];
}
export function poseidonHashMany(values: bigint[], fn = poseidonSmall): bigint {
const { m, rate } = fn;
if (!Array.isArray(values)) throw new Error('bigint array expected in values');
const padded = Array.from(values); // copy
padded.push(1n);
while (padded.length % rate !== 0) padded.push(0n);
let state: bigint[] = new Array(m).fill(0n);
for (let i = 0; i < padded.length; i += rate) {
for (let j = 0; j < rate; j++) state[j] += padded[i + j];
state = fn(state);
}
return state[0];
}

View File

@@ -0,0 +1,44 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { createCurve } from '../_shortw_utils.js';
import { sha224, sha256 } from '@noble/hashes/sha256';
import { Fp } from '../abstract/modular.js';
// NIST secp192r1 aka P192
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
export const P192 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
Fp: Fp(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
// Curve order, total count of valid points in the field.
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
// Base point (x, y) aka generator point
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
h: BigInt(1),
lowS: false,
},
sha256
);
export const secp192r1 = P192;
export const P224 = createCurve(
{
// Params: a, b
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
// Field over which we'll do calculations;
Fp: Fp(BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001')),
// Curve order, total count of valid points in the field
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
// Base point (x, y) aka generator point
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
h: BigInt(1),
lowS: false,
},
sha224
);
export const secp224r1 = P224;

View File

@@ -1,22 +1,21 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../abstract/modular.js';
import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js'; import { bytesToHex as toHex } from '../abstract/utils.js';
// Generic tests for all curves in package // Generic tests for all curves in package
import { secp192r1 } from '../lib/esm/p192.js'; import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp224r1 } from '../lib/esm/p224.js'; import { secp256r1 } from '../p256.js';
import { secp256r1 } from '../lib/esm/p256.js'; import { secp384r1 } from '../p384.js';
import { secp384r1 } from '../lib/esm/p384.js'; import { secp521r1 } from '../p521.js';
import { secp521r1 } from '../lib/esm/p521.js'; import { secp256k1 } from '../secp256k1.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../ed25519.js';
import { ed25519, ed25519ctx, ed25519ph } from '../lib/esm/ed25519.js'; import { ed448, ed448ph } from '../ed448.js';
import { ed448, ed448ph } from '../lib/esm/ed448.js'; import { _starkCurve as starkCurve } from '../stark.js';
import { starkCurve } from '../lib/esm/stark.js'; import { pallas, vesta } from '../pasta.js';
import { pallas, vesta } from '../lib/esm/pasta.js'; import { bn254 } from '../bn.js';
import { bn254 } from '../lib/esm/bn.js'; import { jubjub } from '../jubjub.js';
import { jubjub } from '../lib/esm/jubjub.js'; import { bls12_381 } from '../bls12-381.js';
import { bls12_381 } from '../lib/esm/bls12-381.js';
// Fields tests // Fields tests
const FIELDS = { const FIELDS = {
@@ -239,6 +238,11 @@ for (const c in FIELDS) {
deepStrictEqual(isSquare(a), true); deepStrictEqual(isSquare(a), true);
deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a'); deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a'); deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
// Returns odd/even element
deepStrictEqual(Fp.isOdd(mod.FpSqrtOdd(Fp, a)), true);
deepStrictEqual(Fp.isOdd(mod.FpSqrtEven(Fp, a)), false);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtOdd(Fp, a)), a), true);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtEven(Fp, a)), a), true);
}) })
); );
}); });
@@ -261,6 +265,9 @@ for (const c in FIELDS) {
if (Fp.eql(a, Fp.ZERO)) return; // No division by zero if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
deepStrictEqual(Fp.div(a, Fp.ONE), a); deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE); deepStrictEqual(Fp.div(a, a), Fp.ONE);
// FpDiv tests
deepStrictEqual(mod.FpDiv(Fp, a, Fp.ONE), a);
deepStrictEqual(mod.FpDiv(Fp, a, a), Fp.ONE);
}) })
); );
}); });
@@ -269,6 +276,7 @@ for (const c in FIELDS) {
fc.property(FC_BIGINT, (num) => { fc.property(FC_BIGINT, (num) => {
const a = create(num); const a = create(num);
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO); deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
deepStrictEqual(mod.FpDiv(Fp, Fp.ZERO, a), Fp.ZERO);
}) })
); );
}); });
@@ -279,6 +287,10 @@ for (const c in FIELDS) {
const b = create(num2); const b = create(num2);
const c = create(num3); const c = create(num3);
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c))); deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
deepStrictEqual(
mod.FpDiv(Fp, Fp.add(a, b), c),
Fp.add(mod.FpDiv(Fp, a, c), mod.FpDiv(Fp, b, c))
);
}) })
); );
}); });
@@ -436,9 +448,16 @@ for (const name in CURVES) {
throws(() => G[1][op](0n), '0n'); throws(() => G[1][op](0n), '0n');
G[1][op](G[2]); G[1][op](G[2]);
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER'); throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
throws(() => G[1][op](-123n), '-123n');
throws(() => G[1][op](123), '123');
throws(() => G[1][op](123.456), '123.456'); throws(() => G[1][op](123.456), '123.456');
throws(() => G[1][op](true), 'true'); throws(() => G[1][op](true), 'true');
throws(() => G[1][op](false), 'false');
throws(() => G[1][op](null), 'null');
throws(() => G[1][op](undefined), 'undefined');
throws(() => G[1][op]('1'), "'1'"); throws(() => G[1][op]('1'), "'1'");
throws(() => G[1][op]({ x: 1n, y: 1n }), '{ x: 1n, y: 1n }');
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n }), '{ x: 1n, y: 1n, z: 1n }');
throws( throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), () => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ x: 1n, y: 1n, z: 1n, t: 1n }' '{ x: 1n, y: 1n, z: 1n, t: 1n }'
@@ -514,8 +533,22 @@ for (const name in CURVES) {
should('fromHex(toHex()) roundtrip', () => { should('fromHex(toHex()) roundtrip', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, (x) => { fc.property(FC_BIGINT, (x) => {
const hex = p.BASE.multiply(x).toHex(); const point = p.BASE.multiply(x);
const hex = point.toHex();
const bytes = point.toRawBytes();
deepStrictEqual(p.fromHex(hex).toHex(), hex); deepStrictEqual(p.fromHex(hex).toHex(), hex);
deepStrictEqual(p.fromHex(bytes).toHex(), hex);
})
);
});
should('fromHex(toHex(compressed=true)) roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point = p.BASE.multiply(x);
const hex = point.toHex(true);
const bytes = point.toRawBytes(true);
deepStrictEqual(p.fromHex(hex).toHex(true), hex);
deepStrictEqual(p.fromHex(bytes).toHex(true), hex);
}) })
); );
}); });
@@ -527,10 +560,13 @@ for (const name in CURVES) {
should('.getPublicKey() type check', () => { should('.getPublicKey() type check', () => {
throws(() => C.getPublicKey(0), '0'); throws(() => C.getPublicKey(0), '0');
throws(() => C.getPublicKey(0n), '0n'); throws(() => C.getPublicKey(0n), '0n');
throws(() => C.getPublicKey(false), 'false'); throws(() => C.getPublicKey(-123n), '-123n');
throws(() => C.getPublicKey(123), '123'); throws(() => C.getPublicKey(123), '123');
throws(() => C.getPublicKey(123.456), '123.456'); throws(() => C.getPublicKey(123.456), '123.456');
throws(() => C.getPublicKey(true), 'true'); throws(() => C.getPublicKey(true), 'true');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(null), 'null');
throws(() => C.getPublicKey(undefined), 'undefined');
throws(() => C.getPublicKey(''), "''"); throws(() => C.getPublicKey(''), "''");
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable? // NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
// throws(() => C.getPublicKey('1'), "'1'"); // throws(() => C.getPublicKey('1'), "'1'");
@@ -551,39 +587,96 @@ for (const name in CURVES) {
deepStrictEqual( deepStrictEqual(
C.verify(sig, msg, pub), C.verify(sig, msg, pub),
true, true,
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}' `priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}`
); );
}), }),
{ numRuns: NUM_RUNS } { numRuns: NUM_RUNS }
) )
); );
should('.verify() should verify empty signatures', () => {
const msg = new Uint8Array([]);
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(
C.verify(sig, msg, pub),
true,
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
);
});
should('.sign() edge cases', () => { should('.sign() edge cases', () => {
throws(() => C.sign()); throws(() => C.sign());
throws(() => C.sign('')); throws(() => C.sign(''));
throws(() => C.sign('', ''));
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
}); });
describe('verify()', () => { describe('verify()', () => {
const msg = '01'.repeat(32);
should('true for proper signatures', () => { should('true for proper signatures', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv); const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, msg, pub), true); deepStrictEqual(C.verify(sig, msg, pub), true);
}); });
should('false for wrong messages', () => { should('false for wrong messages', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv); const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false); deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
}); });
should('false for wrong keys', () => { should('false for wrong keys', () => {
const msg = '01'.repeat(32);
const priv = C.utils.randomPrivateKey(); const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv); const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false); deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);
}); });
}); });
if (C.Signature) {
should('Signature serialization roundtrip', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const sigRS = (sig) => ({ s: sig.s, r: sig.r });
// Compact
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactRawBytes())), sigRS(sig));
// DER
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERRawBytes())), sigRS(sig));
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.addRecoveryBit/Signature.recoveryPublicKey', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(sig.recoverPublicKey(msg).toRawBytes(), pub);
const sig2 = C.Signature.fromCompact(sig.toCompactHex());
throws(() => sig2.recoverPublicKey(msg));
const sig3 = sig2.addRecoveryBit(sig.recovery);
deepStrictEqual(sig3.recoverPublicKey(msg).toRawBytes(), pub);
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.normalizeS', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
const sig2 = sig.normalizeS();
deepStrictEqual(sig2.hasHighS(), false);
}),
{ numRuns: NUM_RUNS }
)
);
}
// NOTE: fails for ed, because of empty message. Since we convert it to scalar, // NOTE: fails for ed, because of empty message. Since we convert it to scalar,
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case? // need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
// should('should not verify signature with wrong message', () => { // should('should not verify signature with wrong message', () => {
@@ -641,6 +734,16 @@ should('secp224k1 sqrt bug', () => {
deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n)); deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
}); });
should('bigInt private keys', () => {
// Doesn't support bigints anymore
throws(() => ed25519.sign('', 123n));
throws(() => ed25519.getPublicKey(123n));
throws(() => x25519.getPublicKey(123n));
// Weierstrass still supports
secp256k1.getPublicKey(123n);
secp256k1.sign('', 123n);
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,18 +1,14 @@
import { bls12_381 } from '../lib/esm/bls12-381.js';
import { describe, should } from 'micro-should';
import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert'; import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert';
import { sha512 } from '@noble/hashes/sha512';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { describe, should } from 'micro-should';
import { wNAF } from '../abstract/curve.js';
import { bytesToHex, utf8ToBytes } from '../abstract/utils.js';
import { hash_to_field } from '../abstract/hash-to-curve.js';
import { bls12_381 as bls } from '../bls12-381.js';
import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' }; import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' }; import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
import { wNAF } from '../lib/esm/abstract/curve.js';
const bls = bls12_381;
const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8') const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim() .trim()
.split('\n') .split('\n')
@@ -28,7 +24,10 @@ const SCALAR_VECTORS = readFileSync('./test/bls12-381/bls12-381-scalar-test-vect
const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time const NUM_RUNS = Number(process.env.RUNS_COUNT || 10); // reduce to 1 to shorten test time
fc.configureGlobal({ numRuns: NUM_RUNS }); fc.configureGlobal({ numRuns: NUM_RUNS });
// @ts-ignore const { Fp2 } = bls;
const G1Point = bls.G1.ProjectivePoint;
const G2Point = bls.G2.ProjectivePoint;
const G1Aff = (x, y) => G1Point.fromAffine({ x, y });
const CURVE_ORDER = bls.CURVE.r; const CURVE_ORDER = bls.CURVE.r;
const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 }); const FC_MSG = fc.hexaString({ minLength: 64, maxLength: 64 });
@@ -851,20 +850,20 @@ describe('bls12-381/basic', () => {
for (let vector of G2_VECTORS) { for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector; const [priv, msg, expected] = vector;
const sig = bls.sign(msg, priv); const sig = bls.sign(msg, priv);
deepStrictEqual(bls.utils.bytesToHex(sig), expected); deepStrictEqual(bytesToHex(sig), expected);
} }
}); });
should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => { should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
const options = { const options = {
p: bls.CURVE.r, p: bls.CURVE.r,
m: 1, m: 1,
expand: false, expand: undefined,
}; };
for (let vector of SCALAR_VECTORS) { for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector; const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex); const expected = BigInt('0x' + expectedHex);
const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); const okm = utf8ToBytes(okmAscii);
const scalars = bls.utils.hashToField(okm, 1, options); const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected); deepStrictEqual(scalars[0][0], expected);
} }
}); });
@@ -973,25 +972,25 @@ describe('hash-to-curve', () => {
// Point G1 // Point G1
const VECTORS_G1 = [ const VECTORS_G1 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' + '0576730ab036cbac1d95b38dca905586f28d0a59048db4e8778782d89bff856ddef89277ead5a21e2975c4a6e3d8c79e' +
'1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae', '1273e568bebf1864393c517f999b87c1eaa1b8432f95aea8160cd981b5b05d8cd4a7cf00103b6ef87f728e4b547dd7ae',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' + '061daf0cc00d8912dac1d4cf5a7c32fca97f8b3bf3f805121888e5eb89f77f9a9f406569027ac6d0e61b1229f42c43d6' +
'0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d', '0de1601e5ba02cb637c1d35266f5700acee9850796dc88e860d022d7b9e7e3dce5950952e97861e5bb16d215c87f030d',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' + '0fb3455436843e76079c7cf3dfef75e5a104dfe257a29a850c145568d500ad31ccfe79be9ae0ea31a722548070cf98cd' +
'177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57', '177989f7e2c751658df1b26943ee829d3ebcf131d8f805571712f3a7527ee5334ecff8a97fc2a50cea86f5e6212e9a57',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@@ -1002,7 +1001,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G1.length; i++) { for (let i = 0; i < VECTORS_G1.length; i++) {
const t = VECTORS_G1[i]; const t = VECTORS_G1[i];
should(`hashToCurve/G1 Killic (${i})`, () => { should(`hashToCurve/G1 Killic (${i})`, () => {
const p = bls.hashToCurve.G1.hashToCurve(t.msg, { const p = bls.G1.hashToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@@ -1011,25 +1010,25 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G1 = [ const VECTORS_ENCODE_G1 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' + '1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c' +
'0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5', '0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' + '179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6' +
'0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4', '0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' + '15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af' +
'0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788', '0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@@ -1040,7 +1039,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G1.length; i++) {
const t = VECTORS_ENCODE_G1[i]; const t = VECTORS_ENCODE_G1[i];
should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => { should(`hashToCurve/G1 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G1.encodeToCurve(t.msg, { const p = bls.G1.encodeToCurve(t.msg, {
DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G1_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@@ -1049,7 +1048,7 @@ describe('hash-to-curve', () => {
// Point G2 // Point G2
const VECTORS_G2 = [ const VECTORS_G2 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' + '0fbdae26f9f9586a46d4b0b70390d09064ef2afe5c99348438a3c7d9756471e015cb534204c1b6824617a85024c772dc' +
'0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' + '0a650bd36ae7455cb3fe5d8bb1310594551456f5c6593aec9ee0c03d2f6cb693bd2c5e99d4e23cbaec767609314f51d3' +
@@ -1057,7 +1056,7 @@ describe('hash-to-curve', () => {
'0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83', '0d8d49e7737d8f9fc5cef7c4b8817633103faf2613016cb86a1f3fc29968fe2413e232d9208d2d74a89bf7a48ac36f83',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' + '03578447618463deb106b60e609c6f7cc446dc6035f84a72801ba17c94cd800583b493b948eff0033f09086fdd7f6175' +
'1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' + '1953ce6d4267939c7360756d9cca8eb34aac4633ef35369a7dc249445069888e7d1b3f9d2e75fbd468fbcbba7110ea02' +
@@ -1065,7 +1064,7 @@ describe('hash-to-curve', () => {
'0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e', '0882ab045b8fe4d7d557ebb59a63a35ac9f3d312581b509af0f8eaa2960cbc5e1e36bb969b6e22980b5cbdd0787fcf4e',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' + '195fad48982e186ce3c5c82133aefc9b26d55979b6f530992a8849d4263ec5d57f7a181553c8799bcc83da44847bdc8d' +
'17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' + '17b461fc3b96a30c2408958cbfa5f5927b6063a8ad199d5ebf2d7cdeffa9c20c85487204804fab53f950b2f87db365aa' +
@@ -1073,7 +1072,7 @@ describe('hash-to-curve', () => {
'174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e', '174a3473a3af2d0302b9065e895ca4adba4ece6ce0b41148ba597001abb152f852dd9a96fb45c9de0a43d944746f833e',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@@ -1086,7 +1085,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_G2.length; i++) { for (let i = 0; i < VECTORS_G2.length; i++) {
const t = VECTORS_G2[i]; const t = VECTORS_G2[i];
should(`hashToCurve/G2 Killic (${i})`, () => { should(`hashToCurve/G2 Killic (${i})`, () => {
const p = bls.hashToCurve.G2.hashToCurve(t.msg, { const p = bls.G2.hashToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@@ -1095,7 +1094,7 @@ describe('hash-to-curve', () => {
const VECTORS_ENCODE_G2 = [ const VECTORS_ENCODE_G2 = [
{ {
msg: bls.utils.stringToBytes(''), msg: utf8ToBytes(''),
expected: expected:
'0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' + '0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8' +
'027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' + '027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d' +
@@ -1103,7 +1102,7 @@ describe('hash-to-curve', () => {
'053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7', '053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7',
}, },
{ {
msg: bls.utils.stringToBytes('abc'), msg: utf8ToBytes('abc'),
expected: expected:
'18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' + '18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778' +
'09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' + '09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b' +
@@ -1111,7 +1110,7 @@ describe('hash-to-curve', () => {
'02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811', '02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811',
}, },
{ {
msg: bls.utils.stringToBytes('abcdef0123456789'), msg: utf8ToBytes('abcdef0123456789'),
expected: expected:
'19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' + '19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163' +
'149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' + '149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7' +
@@ -1119,7 +1118,7 @@ describe('hash-to-curve', () => {
'04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551', '04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551',
}, },
{ {
msg: bls.utils.stringToBytes( msg: utf8ToBytes(
'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
), ),
expected: expected:
@@ -1132,7 +1131,7 @@ describe('hash-to-curve', () => {
for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) { for (let i = 0; i < VECTORS_ENCODE_G2.length; i++) {
const t = VECTORS_ENCODE_G2[i]; const t = VECTORS_ENCODE_G2[i];
should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => { should(`hashToCurve/G2 (Killic, encodeToCurve) (${i})`, () => {
const p = bls.hashToCurve.G2.encodeToCurve(t.msg, { const p = bls.G2.encodeToCurve(t.msg, {
DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN', DST: 'BLS12381G2_XMD:SHA-256_SSWU_NU_TESTGEN',
}); });
deepStrictEqual(p.toHex(false), t.expected); deepStrictEqual(p.toHex(false), t.expected);
@@ -1265,7 +1264,7 @@ describe('bls12-381 deterministic', () => {
should('Killic based/Pairing', () => { should('Killic based/Pairing', () => {
const t = bls.pairing(G1Point.BASE, G2Point.BASE); const t = bls.pairing(G1Point.BASE, G2Point.BASE);
deepStrictEqual( deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(t)), bytesToHex(Fp12.toBytes(t)),
killicHex([ killicHex([
'0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631', '0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631',
'04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef', '04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef',
@@ -1287,7 +1286,7 @@ describe('bls12-381 deterministic', () => {
let p2 = G2Point.BASE; let p2 = G2Point.BASE;
for (let v of pairingVectors) { for (let v of pairingVectors) {
deepStrictEqual( deepStrictEqual(
bls.utils.bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))), bytesToHex(Fp12.toBytes(bls.pairing(p1, p2))),
// Reverse order // Reverse order
v.match(/.{96}/g).reverse().join('') v.match(/.{96}/g).reverse().join('')
); );

303
test/ed25519-addons.test.js Normal file
View File

@@ -0,0 +1,303 @@
import { sha512 } from '@noble/hashes/sha512';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { deepStrictEqual, strictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import { bytesToNumberLE, numberToBytesLE } from '../abstract/utils.js';
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
import { ed25519ctx, ed25519ph, RistrettoPoint, x25519 } from '../ed25519.js';
// const ed = ed25519;
const hex = bytesToHex;
// const Point = ed.ExtendedPoint;
const VECTORS_RFC8032_CTX = [
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
'b520b36292911876cada7323198dd87a' +
'8b36950b95130022907a7fb7c4e9b2d5' +
'f6cca685a587b4b21f4b888e4e7edb0d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '626172',
signature:
'fc60d5872fc46b3aa69f8b5b4351d580' +
'8f92bcc044606db097abab6dbcb1aee3' +
'216c48e8b3b66431b5b186d1d28f8ee1' +
'5a5ca2df6668346291c2043d4eb3e90d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: '508e9e6882b979fea900f62adceaca35',
context: '666f6f',
signature:
'8b70c1cc8310e1de20ac53ce28ae6e72' +
'07f33c3295e03bb5c0732a1d20dc6490' +
'8922a8b052cf99b7c4fe107a5abb5b2c' +
'4085ae75890d02df26269d8945f84b0b',
},
{
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'21655b5f1aa965996b3f97b3c849eafb' +
'a922a0a62992f73b3d1b73106a84ad85' +
'e9b86a7b6005ea868337ff2d20a7f5fb' +
'd4cd10b0be49a68da2b2e0dc0ad8960f',
},
];
describe('RFC8032ctx', () => {
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
const v = VECTORS_RFC8032_CTX[i];
should(`${i}`, () => {
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
});
}
});
const VECTORS_RFC8032_PH = [
{
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
message: '616263',
signature:
'98a70222f0b8121aa9d30f813d683f80' +
'9e462b469c7ff87639499bb94e6dae41' +
'31f85042463c2a355a2003d062adf5aa' +
'a10b8c61e636062aaad11c2a26083406',
},
];
describe('RFC8032ph', () => {
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
const v = VECTORS_RFC8032_PH[i];
should(`${i}`, () => {
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
});
}
});
// x25519
should('X25519 base point', () => {
const { y } = ed25519ph.ExtendedPoint.BASE;
const { Fp } = ed25519ph.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
});
describe('RFC7748', () => {
const rfc7748Mul = [
{
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
},
{
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
},
];
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`scalarMult (${i})`, () => {
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
});
}
const rfc7748Iter = [
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`scalarMult iteration (${i})`, () => {
let k = x25519.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('getSharedKey', () => {
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
});
});
describe('Wycheproof', () => {
const group = x25519vectors.testGroups[0];
should(`X25519`, () => {
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const comment = `(${i}, ${v.result}) ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
try {
const shared = hex(x25519.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, comment);
} catch (e) {
// We are more strict
if (e.message.includes('Expected valid scalar')) return;
if (e.message.includes('Invalid private or public key received')) return;
throw e;
}
} else if (v.result === 'invalid') {
let failed = false;
try {
x25519.scalarMult(v.private, v.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, comment);
} else throw new Error('unknown test result');
}
});
});
function utf8ToBytes(str) {
if (typeof str !== 'string') {
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
}
return new TextEncoder().encode(str);
}
describe('ristretto255', () => {
should('follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
// These are small multiples of the basepoint
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
];
let B = RistrettoPoint.BASE;
let P = RistrettoPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(RistrettoPoint.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.
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
// These are all bad because they're negative field elements.
'0100000000000000000000000000000000000000000000000000000000000000',
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
// These are all bad because they give a nonsquare x².
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
// These are all bad because they give a negative xy value.
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
// This is s = -1, which causes y = 0.
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => RistrettoPoint.fromHex(b), badBytes);
}
});
should('create right points from uniform hash', () => {
const labels = [
'Ristretto is traditionally a short shot of espresso coffee',
'made with the normal amount of ground coffee but extracted with',
'about half the amount of water in the same amount of time',
'by using a finer grind.',
'This produces a concentrated shot of coffee per volume.',
'Just pulling a normal shot short will produce a weaker shot',
'and is not a Ristretto as some believe.',
];
const encodedHashToPoints = [
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
];
for (let i = 0; i < labels.length; i++) {
const hash = sha512(utf8ToBytes(labels[i]));
const point = RistrettoPoint.hashToCurve(hash);
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
}
});
should('have proper equality testing', () => {
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
const bytes255ToNumberLE = (bytes) =>
ed25519ctx.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
const priv = new Uint8Array([
198, 101, 65, 165, 93, 120, 37, 238, 16, 133, 10, 35, 253, 243, 161, 246, 229, 135, 12, 137,
202, 114, 222, 139, 146, 123, 4, 125, 152, 173, 1, 7,
]);
const pub = RistrettoPoint.BASE.multiply(bytes255ToNumberLE(priv));
deepStrictEqual(pub.equals(RistrettoPoint.ZERO), false);
});
});
// ESM is broken.
import url from 'url';
import { assert } from 'console';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

1
test/ed25519.helpers.js Normal file
View File

@@ -0,0 +1 @@
export { ed25519, ED25519_TORSION_SUBGROUP } from '../ed25519.js';

View File

@@ -1,21 +1,11 @@
import { deepEqual, deepStrictEqual, strictEqual, throws } from 'assert'; import { deepStrictEqual, strictEqual, throws } from 'assert';
import { describe, should } from 'micro-should';
import * as fc from 'fast-check';
import {
ed25519,
ed25519ctx,
ed25519ph,
x25519,
RistrettoPoint,
ED25519_TORSION_SUBGROUP,
} from '../lib/esm/ed25519.js';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import * as fc from 'fast-check';
import { sha512 } from '@noble/hashes/sha512'; import { describe, should } from 'micro-should';
import { ed25519, ED25519_TORSION_SUBGROUP } from './ed25519.helpers.js';
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' }; import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
describe('ed25519', () => { describe('ed25519', () => {
const ed = ed25519; const ed = ed25519;
@@ -292,104 +282,6 @@ describe('ed25519', () => {
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); // // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false); // // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
// // }); // // });
should('ristretto255/should follow the byte encodings of small multiples', () => {
const encodingsOfSmallMultiples = [
// This is the identity point
'0000000000000000000000000000000000000000000000000000000000000000',
// This is the basepoint
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
// These are small multiples of the basepoint
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
];
let B = RistrettoPoint.BASE;
let P = RistrettoPoint.ZERO;
for (const encoded of encodingsOfSmallMultiples) {
deepStrictEqual(P.toHex(), encoded);
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
P = P.add(B);
}
});
should('ristretto255/should not convert bad bytes encoding', () => {
const badEncodings = [
// These are all bad because they're non-canonical field encodings.
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
// These are all bad because they're negative field elements.
'0100000000000000000000000000000000000000000000000000000000000000',
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
// These are all bad because they give a nonsquare x².
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
// These are all bad because they give a negative xy value.
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
// This is s = -1, which causes y = 0.
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
];
for (const badBytes of badEncodings) {
const b = hexToBytes(badBytes);
throws(() => RistrettoPoint.fromHex(b), badBytes);
}
});
should('ristretto255/should create right points from uniform hash', () => {
const labels = [
'Ristretto is traditionally a short shot of espresso coffee',
'made with the normal amount of ground coffee but extracted with',
'about half the amount of water in the same amount of time',
'by using a finer grind.',
'This produces a concentrated shot of coffee per volume.',
'Just pulling a normal shot short will produce a weaker shot',
'and is not a Ristretto as some believe.',
];
const encodedHashToPoints = [
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
];
for (let i = 0; i < labels.length; i++) {
const hash = sha512(utf8ToBytes(labels[i]));
const point = RistrettoPoint.hashToCurve(hash);
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
}
});
should('input immutability: sign/verify are immutable', () => { should('input immutability: sign/verify are immutable', () => {
const privateKey = ed.utils.randomPrivateKey(); const privateKey = ed.utils.randomPrivateKey();
@@ -432,51 +324,6 @@ describe('ed25519', () => {
throws(() => ed.verify(sig, 'deadbeef', Point.BASE)); throws(() => ed.verify(sig, 'deadbeef', Point.BASE));
}); });
const rfc7748Mul = [
{
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
},
{
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
},
];
for (let i = 0; i < rfc7748Mul.length; i++) {
const v = rfc7748Mul[i];
should(`RFC7748: scalarMult (${i})`, () => {
deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU);
});
}
const rfc7748Iter = [
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
];
for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i];
should(`RFC7748: scalarMult iteration (${i})`, () => {
let k = x25519.Gu;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar);
});
}
should('RFC7748 getSharedKey', () => {
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
});
// should('X25519/getSharedSecret() should be commutative', () => { // should('X25519/getSharedSecret() should be commutative', () => {
// for (let i = 0; i < 512; i++) { // for (let i = 0; i < 512; i++) {
// const asec = ed.utils.randomPrivateKey(); // const asec = ed.utils.randomPrivateKey();
@@ -499,35 +346,6 @@ describe('ed25519', () => {
// ); // );
// }); // });
{
const group = x25519vectors.testGroups[0];
should(`Wycheproof/X25519`, () => {
for (let i = 0; i < group.tests.length; i++) {
const v = group.tests[i];
const comment = `(${i}, ${v.result}) ${v.comment}`;
if (v.result === 'valid' || v.result === 'acceptable') {
try {
const shared = hex(x25519.scalarMult(v.private, v.public));
deepStrictEqual(shared, v.shared, comment);
} catch (e) {
// We are more strict
if (e.message.includes('Expected valid scalar')) return;
if (e.message.includes('Invalid private or public key received')) return;
throw e;
}
} else if (v.result === 'invalid') {
let failed = false;
try {
x25519.scalarMult(v.private, v.public);
} catch (error) {
failed = true;
}
deepStrictEqual(failed, true, comment);
} else throw new Error('unknown test result');
}
});
}
should(`Wycheproof/ED25519`, () => { should(`Wycheproof/ED25519`, () => {
for (let g = 0; g < ed25519vectors.testGroups.length; g++) { for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
const group = ed25519vectors.testGroups[g]; const group = ed25519vectors.testGroups[g];
@@ -559,91 +377,6 @@ describe('ed25519', () => {
deepStrictEqual(ed.verify(signature, message, publicKey), true); deepStrictEqual(ed.verify(signature, message, publicKey), true);
}); });
const VECTORS_RFC8032_CTX = [
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
'b520b36292911876cada7323198dd87a' +
'8b36950b95130022907a7fb7c4e9b2d5' +
'f6cca685a587b4b21f4b888e4e7edb0d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '626172',
signature:
'fc60d5872fc46b3aa69f8b5b4351d580' +
'8f92bcc044606db097abab6dbcb1aee3' +
'216c48e8b3b66431b5b186d1d28f8ee1' +
'5a5ca2df6668346291c2043d4eb3e90d',
},
{
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
message: '508e9e6882b979fea900f62adceaca35',
context: '666f6f',
signature:
'8b70c1cc8310e1de20ac53ce28ae6e72' +
'07f33c3295e03bb5c0732a1d20dc6490' +
'8922a8b052cf99b7c4fe107a5abb5b2c' +
'4085ae75890d02df26269d8945f84b0b',
},
{
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
message: 'f726936d19c800494e3fdaff20b276a8',
context: '666f6f',
signature:
'21655b5f1aa965996b3f97b3c849eafb' +
'a922a0a62992f73b3d1b73106a84ad85' +
'e9b86a7b6005ea868337ff2d20a7f5fb' +
'd4cd10b0be49a68da2b2e0dc0ad8960f',
},
];
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
const v = VECTORS_RFC8032_CTX[i];
should(`RFC8032ctx/${i}`, () => {
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
});
}
const VECTORS_RFC8032_PH = [
{
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
message: '616263',
signature:
'98a70222f0b8121aa9d30f813d683f80' +
'9e462b469c7ff87639499bb94e6dae41' +
'31f85042463c2a355a2003d062adf5aa' +
'a10b8c61e636062aaad11c2a26083406',
},
];
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
const v = VECTORS_RFC8032_PH[i];
should(`RFC8032ph/${i}`, () => {
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
});
}
should('X25519 base point', () => {
const { y } = ed25519.ExtendedPoint.BASE;
const { Fp } = ed25519.CURVE;
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
});
should('isTorsionFree()', () => { should('isTorsionFree()', () => {
const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point; const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point;
for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) { for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) {
@@ -656,6 +389,15 @@ describe('ed25519', () => {
}); });
}); });
should('ed25519 bug', () => {
const t = 81718630521762619991978402609047527194981150691135404693881672112315521837062n;
const point = ed25519.ExtendedPoint.fromAffine({ x: t, y: t });
throws(() => point.assertValidity());
// Otherwise (without assertValidity):
// const point2 = point.double();
// point2.toAffine(); // crash!
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,9 +1,9 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js'; import { ed448, ed448ph, x448 } from '../ed448.js';
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { numberToBytesLE } from '../abstract/utils.js';
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' }; import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' }; import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
@@ -509,7 +509,7 @@ describe('ed448', () => {
for (let i = 0; i < rfc7748Iter.length; i++) { for (let i = 0; i < rfc7748Iter.length; i++) {
const { scalar, iters } = rfc7748Iter[i]; const { scalar, iters } = rfc7748Iter[i];
should(`RFC7748: scalarMult iteration (${i})`, () => { should(`RFC7748: scalarMult iteration (${i})`, () => {
let k = x448.Gu; let k = x448.GuBytes;
for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k]; for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k];
deepStrictEqual(hex(k), scalar); deepStrictEqual(hex(k), scalar);
}); });
@@ -664,7 +664,7 @@ describe('ed448', () => {
// const invX = Fp.invert(x * x); // x² // const invX = Fp.invert(x * x); // x²
const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²)
// const u = Fp.create(y * y * invX); // const u = Fp.create(y * y * invX);
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); deepStrictEqual(numberToBytesLE(u, 56), x448.GuBytes);
}); });
}); });

View File

@@ -5,18 +5,15 @@ import { bytesToHex } from '@noble/hashes/utils';
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 { shake128, shake256 } from '@noble/hashes/sha3';
import * as secp256r1 from '../lib/esm/p256.js'; import * as secp256r1 from '../p256.js';
import * as secp384r1 from '../lib/esm/p384.js'; import * as secp384r1 from '../p384.js';
import * as secp521r1 from '../lib/esm/p521.js'; import * as secp521r1 from '../p521.js';
import * as ed25519 from '../lib/esm/ed25519.js'; import * as ed25519 from '../ed25519.js';
import * as ed448 from '../lib/esm/ed448.js'; import * as ed448 from '../ed448.js';
import * as secp256k1 from '../lib/esm/secp256k1.js'; import * as secp256k1 from '../secp256k1.js';
import { bls12_381 } from '../lib/esm/bls12-381.js'; import { bls12_381 } from '../bls12-381.js';
import { import { expand_message_xmd, expand_message_xof } from '../abstract/hash-to-curve.js';
stringToBytes, import { utf8ToBytes } from '../abstract/utils.js';
expand_message_xmd,
expand_message_xof,
} from '../lib/esm/abstract/hash-to-curve.js';
// XMD // 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' };
@@ -56,9 +53,9 @@ function testExpandXMD(hash, vectors) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => { should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => {
const p = expand_message_xmd( const p = expand_message_xmd(
stringToBytes(t.msg), utf8ToBytes(t.msg),
stringToBytes(vectors.DST), utf8ToBytes(vectors.DST),
t.len_in_bytes, Number.parseInt(t.len_in_bytes),
hash hash
); );
deepStrictEqual(bytesToHex(p), t.uniform_bytes); deepStrictEqual(bytesToHex(p), t.uniform_bytes);
@@ -79,9 +76,9 @@ function testExpandXOF(hash, vectors) {
const t = vectors.tests[i]; const t = vectors.tests[i];
should(`${i}`, () => { should(`${i}`, () => {
const p = expand_message_xof( const p = expand_message_xof(
stringToBytes(t.msg), utf8ToBytes(t.msg),
stringToBytes(vectors.DST), utf8ToBytes(vectors.DST),
+t.len_in_bytes, Number.parseInt(t.len_in_bytes),
vectors.k, vectors.k,
hash hash
); );
@@ -112,7 +109,7 @@ function testCurve(curve, ro, nu) {
const t = ro.vectors[i]; const t = ro.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve const p = curve
.hashToCurve(stringToBytes(t.msg), { .hashToCurve(utf8ToBytes(t.msg), {
DST: ro.dst, DST: ro.dst,
}) })
.toAffine(); .toAffine();
@@ -126,7 +123,7 @@ function testCurve(curve, ro, nu) {
const t = nu.vectors[i]; const t = nu.vectors[i];
should(`(${i})`, () => { should(`(${i})`, () => {
const p = curve const p = curve
.encodeToCurve(stringToBytes(t.msg), { .encodeToCurve(utf8ToBytes(t.msg), {
DST: nu.dst, DST: nu.dst,
}) })
.toAffine(); .toAffine();
@@ -140,8 +137,8 @@ function testCurve(curve, ro, nu) {
testCurve(secp256r1, p256_ro, p256_nu); testCurve(secp256r1, p256_ro, p256_nu);
testCurve(secp384r1, p384_ro, p384_nu); testCurve(secp384r1, p384_ro, p384_nu);
testCurve(secp521r1, p521_ro, p521_nu); testCurve(secp521r1, p521_ro, p521_nu);
testCurve(bls12_381.hashToCurve.G1, g1_ro, g1_nu); testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.hashToCurve.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);

View File

@@ -6,6 +6,7 @@ import './nist.test.js';
import './ed448.test.js'; import './ed448.test.js';
import './ed25519.test.js'; import './ed25519.test.js';
import './secp256k1.test.js'; import './secp256k1.test.js';
import './secp256k1-schnorr.test.js';
import './stark/index.test.js'; import './stark/index.test.js';
import './jubjub.test.js'; import './jubjub.test.js';
import './bls12-381.test.js'; import './bls12-381.test.js';

View File

@@ -1,4 +1,4 @@
import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; import { jubjub, findGroupHash } from '../jubjub.js';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
const Point = jubjub.ExtendedPoint; const Point = jubjub.ExtendedPoint;

View File

@@ -1,12 +1,11 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../lib/esm/p192.js'; import { secp192r1, secp224r1, P192, P224 } from './_more-curves.helpers.js';
import { secp224r1, P224 } from '../lib/esm/p224.js'; import { secp256r1, P256 } from '../p256.js';
import { secp256r1, P256 } from '../lib/esm/p256.js'; import { secp384r1, P384 } from '../p384.js';
import { secp384r1, P384 } from '../lib/esm/p384.js'; import { secp521r1, P521 } from '../p521.js';
import { secp521r1, P521 } from '../lib/esm/p521.js'; import { secp256k1 } from '../secp256k1.js';
import { secp256k1 } from '../lib/esm/secp256k1.js'; import { hexToBytes, bytesToHex } from '../abstract/utils.js';
import { hexToBytes, bytesToHex } from '../lib/esm/abstract/utils.js';
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' }; import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' }; import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' }; import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
@@ -86,7 +85,8 @@ describe('wycheproof ECDH', () => {
try { try {
const pub = CURVE.ProjectivePoint.fromHex(test.public); const pub = CURVE.ProjectivePoint.fromHex(test.public);
} catch (e) { } catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue; // Our strict validation filter doesn't let weird-length DER vectors
if (e.message.startsWith('Point of length')) continue;
throw e; throw e;
} }
const shared = CURVE.getSharedSecret(test.private, test.public); const shared = CURVE.getSharedSecret(test.private, test.public);
@@ -140,7 +140,8 @@ describe('wycheproof ECDH', () => {
try { try {
const pub = curve.ProjectivePoint.fromHex(test.public); const pub = curve.ProjectivePoint.fromHex(test.public);
} catch (e) { } catch (e) {
if (e.message.includes('Point.fromHex: received invalid point.')) continue; // Our strict validation filter doesn't let weird-length DER vectors
if (e.message.includes('Point of length')) continue;
throw e; throw e;
} }
const shared = curve.getSharedSecret(test.private, test.public); const shared = curve.getSharedSecret(test.private, test.public);
@@ -194,7 +195,6 @@ const WYCHEPROOF_ECDSA = {
secp256k1: { secp256k1: {
curve: secp256k1, curve: secp256k1,
hashes: { hashes: {
// TODO: debug why fails, can be bug
sha256: { sha256: {
hash: sha256, hash: sha256,
tests: [secp256k1_sha256_test], tests: [secp256k1_sha256_test],

View File

@@ -1,8 +1,8 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { should, describe } from 'micro-should'; import { should, describe } from 'micro-should';
import * as poseidon from '../lib/esm/abstract/poseidon.js'; import * as poseidon from '../abstract/poseidon.js';
import * as stark from '../lib/esm/stark.js'; import * as stark from '../stark.js';
import * as mod from '../lib/esm/abstract/modular.js'; import * as mod from '../abstract/modular.js';
import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' }; import { default as pvectors } from './vectors/poseidon.json' assert { type: 'json' };
const { st1, st2, st3, st4 } = pvectors; const { st1, st2, st3, st4 } = pvectors;

View File

@@ -0,0 +1,34 @@
import { deepStrictEqual, throws } from 'assert';
import { readFileSync } from 'fs';
import { should, describe } from 'micro-should';
import { bytesToHex as hex } from '@noble/hashes/utils';
import { schnorr } from '../secp256k1.js';
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
describe('schnorr.sign()', () => {
// index,secret key,public key,aux_rand,message,signature,verification result,comment
const vectors = schCsv
.split('\n')
.map((line) => line.split(','))
.slice(1, -1);
for (let vec of vectors) {
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
should(`${comment || 'vector ' + index}`, () => {
if (sec) {
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
const sig = schnorr.sign(msg, sec, rnd);
deepStrictEqual(hex(sig), expSig.toLowerCase());
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
} else {
const passed = schnorr.verify(expSig, msg, pub);
deepStrictEqual(passed, passes === 'TRUE');
}
});
}
});
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

14
test/secp256k1.helpers.js Normal file
View File

@@ -0,0 +1,14 @@
// @ts-ignore
export { secp256k1 as secp } from '../secp256k1.js';
import { secp256k1 as _secp } from '../secp256k1.js';
export { bytesToNumberBE, numberToBytesBE } from '../abstract/utils.js';
export { mod } from '../abstract/modular.js';
export const sigFromDER = (der) => {
return _secp.Signature.fromDER(der);
};
export const sigToDER = (sig) => sig.toDERHex();
export const selectHash = (secp) => secp.CURVE.hash;
export const normVerifySig = (s) => _secp.Signature.fromDER(s);
// export const bytesToNumberBE = secp256k1.utils.bytesToNumberBE;
// export const numberToBytesBE = secp256k1.utils.numberToBytesBE;
// export const mod = mod_;

View File

@@ -1,22 +1,21 @@
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils';
import { deepStrictEqual, throws } from 'assert';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
import { Fp } from '../lib/esm/abstract/modular.js';
import { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { should, describe } from 'micro-should';
// prettier-ignore
import {
secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE
} from './secp256k1.helpers.js';
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' }; import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' }; import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
import { default as privates } from './vectors/privates.json' assert { type: 'json' }; import { default as privates } from './vectors/privates.json' assert { type: 'json' };
import { default as points } from './vectors/points.json' assert { type: 'json' }; import { default as points } from './vectors/points.json' assert { type: 'json' };
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' }; import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
import { should, describe } from 'micro-should';
import { deepStrictEqual, throws } from 'assert';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
const hex = bytesToHex;
const secp = secp256k1;
const Point = secp.ProjectivePoint; const Point = secp.ProjectivePoint;
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8'); const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
// prettier-ignore // prettier-ignore
@@ -193,7 +192,7 @@ describe('secp256k1', () => {
fc.assert( fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig); deepStrictEqual(sigFromDER(sigToDER(sig)), sig);
}) })
); );
}); });
@@ -241,9 +240,9 @@ describe('secp256k1', () => {
); );
for (const [msg, exp] of CASES) { for (const [msg, exp] of CASES) {
const res = secp.sign(msg, privKey, { extraEntropy: undefined }); const res = secp.sign(msg, privKey, { extraEntropy: undefined });
deepStrictEqual(res.toDERHex(), exp); deepStrictEqual(sigToDER(res), exp);
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex(); const rs = sigFromDER(sigToDER(res)).toCompactHex();
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp); deepStrictEqual(sigToDER(secp.Signature.fromCompact(rs)), exp);
} }
}); });
should('handle {extraData} option', () => { should('handle {extraData} option', () => {
@@ -342,7 +341,7 @@ describe('secp256k1', () => {
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
const pub = new Point(x, y, 1n).toRawBytes(); const pub = new Point(x, y, 1n).toRawBytes();
const sig = new secp.Signature(r, s); const sig = new secp.Signature(r, s);
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true);
}); });
should('not verify invalid deterministic signatures with RFC 6979', () => { should('not verify invalid deterministic signatures with RFC 6979', () => {
for (const vector of ecdsa.invalid.verify) { for (const vector of ecdsa.invalid.verify) {
@@ -351,29 +350,6 @@ describe('secp256k1', () => {
} }
}); });
}); });
describe('schnorr.sign()', () => {
// index,secret key,public key,aux_rand,message,signature,verification result,comment
const vectors = schCsv
.split('\n')
.map((line) => line.split(','))
.slice(1, -1);
for (let vec of vectors) {
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
should(`${comment || 'vector ' + index}`, () => {
if (sec) {
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
const sig = schnorr.sign(msg, sec, rnd);
deepStrictEqual(hex(sig), expSig.toLowerCase());
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
} else {
const passed = schnorr.verify(expSig, msg, pub);
deepStrictEqual(passed, passes === 'TRUE');
}
});
}
});
describe('recoverPublicKey()', () => { describe('recoverPublicKey()', () => {
should('recover public key from recovery bit', () => { should('recover public key from recovery bit', () => {
const message = '00000000000000000000000000000000000000000000000000000000deadbeef'; const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
@@ -404,7 +380,7 @@ describe('secp256k1', () => {
should('handle RFC 6979 vectors', () => { should('handle RFC 6979 vectors', () => {
for (const vector of ecdsa.valid) { for (const vector of ecdsa.valid) {
let usig = secp.sign(vector.m, vector.d); let usig = secp.sign(vector.m, vector.d);
let sig = usig.toDERHex(); let sig = sigToDER(usig);
const vpub = secp.getPublicKey(vector.d); const vpub = secp.getPublicKey(vector.d);
const recovered = usig.recoverPublicKey(vector.m); const recovered = usig.recoverPublicKey(vector.m);
deepStrictEqual(recovered.toHex(), hex(vpub)); deepStrictEqual(recovered.toHex(), hex(vpub));
@@ -459,52 +435,46 @@ describe('secp256k1', () => {
}); });
describe('tweak utilities (legacy)', () => { describe('tweak utilities (legacy)', () => {
const Fn = Fp(secp.CURVE.n); const normal = secp.utils.normPrivateKeyToScalar;
const normal = secp.utils._normalizePrivateKey;
const tweakUtils = { const tweakUtils = {
privateAdd: (privateKey, tweak) => { privateAdd: (privateKey, tweak) => {
const p = normal(privateKey); return numberToBytesBE(mod(normal(privateKey) + normal(tweak), secp.CURVE.n), 32);
const t = normal(tweak);
return numberToBytesBE(Fn.create(p + t), 32);
}, },
privateNegate: (privateKey) => { privateNegate: (privateKey) => {
return numberToBytesBE(Fn.neg(normal(privateKey)), 32); return numberToBytesBE(mod(-normal(privateKey), secp.CURVE.n), 32);
}, },
pointAddScalar: (p, tweak, isCompressed) => { pointAddScalar: (p, tweak, isCompressed) => {
const P = Point.fromHex(p); const tweaked = Point.fromHex(p).add(Point.fromPrivateKey(tweak));
const t = normal(tweak); if (tweaked.equals(Point.ZERO)) throw new Error('Tweaked point at infinity');
const Q = Point.BASE.multiplyAndAddUnsafe(P, t, 1n); return tweaked.toRawBytes(isCompressed);
if (!Q) throw new Error('Tweaked point at infinity');
return Q.toRawBytes(isCompressed);
}, },
pointMultiply: (p, tweak, isCompressed) => { pointMultiply: (p, tweak, isCompressed) => {
const P = Point.fromHex(p); if (typeof tweak === 'string') tweak = hexToBytes(tweak);
const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak); const t = bytesToNumberBE(tweak);
const t = BigInt(`0x${h}`); return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
return P.multiply(t).toRawBytes(isCompressed);
}, },
}; };
should('privateAdd()', () => { should('privateAdd()', () => {
for (const vector of privates.valid.add) { for (const vector of privates.valid.add) {
const { a, b, expected } = vector; const { a, b, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected); deepStrictEqual(hex(tweakUtils.privateAdd(a, b)), expected);
} }
}); });
should('privateNegate()', () => { should('privateNegate()', () => {
for (const vector of privates.valid.negate) { for (const vector of privates.valid.negate) {
const { a, expected } = vector; const { a, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected); deepStrictEqual(hex(tweakUtils.privateNegate(a)), expected);
} }
}); });
should('pointAddScalar()', () => { should('pointAddScalar()', () => {
for (const vector of points.valid.pointAddScalar) { for (const vector of points.valid.pointAddScalar) {
const { description, P, d, expected } = vector; const { description, P, d, expected } = vector;
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected); deepStrictEqual(hex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
} }
}); });
should('pointAddScalar() invalid', () => { should('pointAddScalar() invalid', () => {
@@ -516,7 +486,7 @@ describe('secp256k1', () => {
should('pointMultiply()', () => { should('pointMultiply()', () => {
for (const vector of points.valid.pointMultiply) { for (const vector of points.valid.pointMultiply) {
const { P, d, expected } = vector; const { P, d, expected } = vector;
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected); deepStrictEqual(hex(tweakUtils.pointMultiply(P, d, true)), expected);
} }
}); });
should('pointMultiply() invalid', () => { should('pointMultiply() invalid', () => {
@@ -532,10 +502,12 @@ describe('secp256k1', () => {
// const pubKey = Point.fromHex().toRawBytes(); // const pubKey = Point.fromHex().toRawBytes();
const pubKey = group.key.uncompressed; const pubKey = group.key.uncompressed;
for (let test of group.tests) { for (let test of group.tests) {
const m = secp.CURVE.hash(hexToBytes(test.msg)); const h = selectHash(secp);
const m = h(hexToBytes(test.msg));
if (test.result === 'valid' || test.result === 'acceptable') { if (test.result === 'valid' || test.result === 'acceptable') {
const verified = secp.verify(test.sig, m, pubKey); const verified = secp.verify(normVerifySig(test.sig), m, pubKey);
if (secp.Signature.fromDER(test.sig).hasHighS()) { if (sigFromDER(test.sig).hasHighS()) {
deepStrictEqual(verified, false); deepStrictEqual(verified, false);
} else { } else {
deepStrictEqual(verified, true); deepStrictEqual(verified, true);

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../stark.js';
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' }; import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
import * as bip32 from '@scure/bip32'; import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39'; import * as bip39 from '@scure/bip39';

View File

@@ -1,4 +1,4 @@
import * as microStark from '../../../lib/esm/stark.js'; import * as microStark from '../../../stark.js';
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils'; import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
import * as bench from 'micro-bmark'; import * as bench from 'micro-bmark';
const { run, mark } = bench; // or bench.mark const { run, mark } = bench; // or bench.mark

View File

@@ -1,4 +1,11 @@
import { describe, should } from 'micro-should';
import './basic.test.js'; import './basic.test.js';
import './stark.test.js'; import './stark.test.js';
import './property.test.js'; import './property.test.js';
import './poseidon.test.js'; import './poseidon.test.js';
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}

View File

@@ -1,6 +1,7 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../stark.js';
import { bytesToHex as hex } from '@noble/hashes/utils';
import * as fs from 'fs'; import * as fs from 'fs';
function parseTest(path) { function parseTest(path) {
@@ -107,6 +108,96 @@ should('Poseidon examples', () => {
]); ]);
}); });
should('Poseidon 2', () => {
// Cross-test with cairo-lang 0.11
deepStrictEqual(
starknet.poseidonHash(1n, 1n),
315729444126170353286530004158376771769107830460625027134495740547491428733n
);
deepStrictEqual(
starknet.poseidonHash(123n, 123n),
3149184350054566761517315875549307360045573205732410509163060794402900549639n
);
deepStrictEqual(
starknet.poseidonHash(1231231231231231231231231312312n, 1231231231231231231231231312312n),
2544250291965936388474000136445328679708604225006461780180655815882994563864n
);
// poseidonHashSingle
deepStrictEqual(
starknet.poseidonHashSingle(1n),
3085182978037364507644541379307921604860861694664657935759708330416374536741n
);
deepStrictEqual(
starknet.poseidonHashSingle(123n),
2751345659320901472675327541550911744303539407817894466726181731796247467344n
);
deepStrictEqual(
starknet.poseidonHashSingle(1231231231231231231231231312312n),
3083085683696942145160394401206391098729120397175152900096470498748103599322n
);
// poseidonHashMany
throws(() => starknet.poseidonHash(new Uint8Array([1, 2, 3])));
deepStrictEqual(
starknet.poseidonHashMany([1n]),
154809849725474173771833689306955346864791482278938452209165301614543497938n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n]),
1557996165160500454210437319447297236715335099509187222888255133199463084263n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n]),
976552833909388839716191681593200982850734838655927116322079791360264131378n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n, 7n]),
1426681430756292883765769449684978541173830451959857824597431064948702170774n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n, 6n]),
3578895185591466904832617962452140411216018208734547126302182794057260630783n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n, 5n]),
2047942584693618630610564708884241243670450597197937863619828684896211911953n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n, 4n]),
717812721730784692894550948559585317289413466140233907962980309405694367376n
);
deepStrictEqual(
starknet.poseidonHashMany([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 1n, 2n, 3n]),
2926122208425648133778911655767364584769133265503722614793281770361723147648n
);
deepStrictEqual(
starknet.poseidonHashMany([
154809849725474173771833689306955346864791482278938452209165301614543497938n,
1557996165160500454210437319447297236715335099509187222888255133199463084263n,
976552833909388839716191681593200982850734838655927116322079791360264131378n,
1426681430756292883765769449684978541173830451959857824597431064948702170774n,
3578895185591466904832617962452140411216018208734547126302182794057260630783n,
]),
1019392520709073131437410341528874594624843119359955302374885123884546721410n
);
// poseidon_hash_func
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array([1, 2]), new Uint8Array([3, 4]))),
'01f87cbb9c58139605384d0f0df49b446600af020aa9dac92301d45c96d78c0a'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(32).fill(255), new Uint8Array(32).fill(255))),
'05fd546b5ee3bcbbcbb733ed90bfc33033169d6765ac37bba71794a11cbb51a6'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(64).fill(255), new Uint8Array(64).fill(255))),
'07dba6b4d94b3e32697afe0825d6dac2dccafd439f7806a9575693c93735596b'
);
deepStrictEqual(
hex(starknet.poseidonHashFunc(new Uint8Array(256).fill(255), new Uint8Array(256).fill(255))),
'02f048581901865201dad701a5653d946b961748ec770fc11139aa7c06a9432a'
);
});
// ESM is broken. // ESM is broken.
import url from 'url'; import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {

View File

@@ -1,6 +1,6 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../stark.js';
import * as fc from 'fast-check'; import * as fc from 'fast-check';
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n); const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);

View File

@@ -1,15 +1,15 @@
import { deepStrictEqual, throws } from 'assert'; import { deepStrictEqual, throws } from 'assert';
import { describe, should } from 'micro-should'; import { describe, should } from 'micro-should';
import { hex, utf8 } from '@scure/base'; import { utf8ToBytes } from '@noble/hashes/utils';
import * as bip32 from '@scure/bip32'; import * as bip32 from '@scure/bip32';
import * as bip39 from '@scure/bip39'; import * as bip39 from '@scure/bip39';
import * as starknet from '../../lib/esm/stark.js'; import * as starknet from '../../stark.js';
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' }; import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' }; import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
describe('starknet', () => { describe('starknet', () => {
should('custom keccak', () => { should('custom keccak', () => {
const value = starknet.keccak(utf8.decode('hello')); const value = starknet.keccak(utf8ToBytes('hello'));
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n); deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
deepStrictEqual(value < 2n ** 250n, true); deepStrictEqual(value < 2n ** 250n, true);
}); });

View File

@@ -1,19 +0,0 @@
{
"compilerOptions": {
"strict": true,
"outDir": "lib/esm",
"target": "es2020",
"module": "es6",
"moduleResolution": "node16",
"noUnusedLocals": true,
"baseUrl": ".",
"paths": {
"@noble/hashes/crypto": [ "src/crypto" ]
},
},
"include": ["src"],
"exclude": [
"node_modules",
"lib",
],
}

View File

@@ -2,11 +2,13 @@
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"declaration": true, "declaration": true,
"outDir": "lib", "declarationMap": true,
"sourceMap": true,
"outDir": ".",
"target": "es2020", "target": "es2020",
"lib": ["es2020"], // Set explicitly to remove DOM "lib": ["es2020"], // Set explicitly to remove DOM
"module": "commonjs", "module": "es6",
"moduleResolution": "node", "moduleResolution": "node16",
"noUnusedLocals": true, "noUnusedLocals": true,
"baseUrl": ".", "baseUrl": ".",
}, },