Compare commits
No commits in common. "main" and "0.1.0" have entirely different histories.
1
.github/funding.yml
vendored
1
.github/funding.yml
vendored
@ -1 +1,2 @@
|
|||||||
github: paulmillr
|
github: paulmillr
|
||||||
|
# custom: https://paulmillr.com/funding/
|
||||||
33
.github/workflows/nodejs.yml
vendored
33
.github/workflows/nodejs.yml
vendored
@ -1,23 +1,18 @@
|
|||||||
name: Run node.js tests
|
name: Node CI
|
||||||
on:
|
|
||||||
- push
|
on: [push, pull_request]
|
||||||
- pull_request
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: v${{ matrix.node }} @ ubuntu-latest
|
name: v18 @ ubuntu-latest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node:
|
|
||||||
- 18
|
|
||||||
- 20
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
|
- uses: actions/checkout@v3
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: 18
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build --if-present
|
- run: npm run build --if-present
|
||||||
- run: npm test
|
- run: cd curve-definitions; npm install; npm run build --if-present
|
||||||
- run: npm run lint --if-present
|
- run: npm test
|
||||||
|
- run: npm run lint --if-present
|
||||||
|
|||||||
23
.github/workflows/publish-npm.yml
vendored
23
.github/workflows/publish-npm.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: Publish package to npm
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
|
|
||||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
cache: npm
|
|
||||||
- run: npm install -g npm
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
|
||||||
- run: npm publish --provenance --access public
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
|
||||||
28
.github/workflows/upload-release.yml
vendored
28
.github/workflows/upload-release.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: Upload standalone file to GitHub Releases
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
|
|
||||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
cache: npm
|
|
||||||
- run: npm install -g npm
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
|
||||||
- run: |
|
|
||||||
cd build
|
|
||||||
npm ci
|
|
||||||
npm run build:release
|
|
||||||
cd ..
|
|
||||||
- run: gh release upload ${{ github.event.release.tag_name }} build/`npx jsbt outfile`
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
16
.gitignore
vendored
16
.gitignore
vendored
@ -1,9 +1,7 @@
|
|||||||
node_modules
|
build/
|
||||||
/*.js
|
node_modules/
|
||||||
/esm/*.js
|
coverage/
|
||||||
*.d.ts
|
/lib/**/*.js
|
||||||
*.d.ts.map
|
/lib/**/*.ts
|
||||||
*.js.map
|
/lib/**/*.d.ts.map
|
||||||
/build
|
/curve-definitions/lib
|
||||||
/abstract
|
|
||||||
/esm/abstract
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"singleQuote": true,
|
"singleQuote": true
|
||||||
"trailingComma": "es5"
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"*.{js,d.ts,js.map,d.ts.map}": true,
|
|
||||||
"esm/*.{js,d.ts,js.map,d.ts.map}": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
SECURITY.md
20
SECURITY.md
@ -1,20 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
See [README's Security section](./README.md#security) for detailed description of internal security practices.
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
| ------- | ------------------ |
|
|
||||||
| >=1.0.0 | :white_check_mark: |
|
|
||||||
| <1.0.0 | :x: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Use maintainer's email specified at https://github.com/paulmillr.
|
|
||||||
|
|
||||||
It's preferred that you use
|
|
||||||
PGP key from [pgp proof](https://paulmillr.com/pgp_proof.txt) (current is [697079DA6878B89B](https://paulmillr.com/pgp_proof.txt)).
|
|
||||||
Ensure the pgp proof page has maintainer's site/github specified.
|
|
||||||
|
|
||||||
You will get an update as soon as the email is read; a "Security vulnerability" phrase in email's title would help.
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,7 +0,0 @@
|
|||||||
# Audit
|
|
||||||
|
|
||||||
All audits of the library are described in [README's Security section](../README.md#security)
|
|
||||||
|
|
||||||
`2023-01-trailofbits-audit-curves.pdf` file in the directory was saved from
|
|
||||||
[github.com/trailofbits/publications](https://github.com/trailofbits/publications).
|
|
||||||
Check out their repo and verify checksums to ensure the PDF in this directory has not been altered.
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
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 };
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
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));
|
|
||||||
|
|
||||||
const scalars1 = Array(4096).fill(0).map(i => 2n ** 235n - BigInt(i));
|
|
||||||
const scalars2 = Array(4096).fill(0).map(i => 2n ** 241n + BigInt(i));
|
|
||||||
const points = scalars1.map(s => bls.G1.ProjectivePoint.BASE.multiply(s));
|
|
||||||
await mark('MSM 4096 scalars x points', 1, () => {
|
|
||||||
// naive approach, not using multi-scalar-multiplication
|
|
||||||
let sum = bls.G1.ProjectivePoint.ZERO;
|
|
||||||
for (let i = 0; i < 4096; i++) {
|
|
||||||
const scalar = scalars2[i];
|
|
||||||
const G1 = points[i];
|
|
||||||
const mutliplied = G1.multiplyUnsafe(scalar);
|
|
||||||
sum = sum.add(mutliplied);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
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({ ed25519, ed448, p256, p384, p521 })) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { run, mark, utils } from 'micro-bmark';
|
|
||||||
import { shake256 } from '@noble/hashes/sha3';
|
|
||||||
import * as mod from '../abstract/modular.js';
|
|
||||||
import { ed448, DecafPoint } from '../ed448.js';
|
|
||||||
|
|
||||||
run(async () => {
|
|
||||||
const RAM = false;
|
|
||||||
if (RAM) utils.logMem();
|
|
||||||
console.log(`\x1b[36mdecaf448\x1b[0m`);
|
|
||||||
const priv = mod.hashToPrivateScalar(shake256(ed448.utils.randomPrivateKey(), { dkLen: 112 }), ed448.CURVE.n);
|
|
||||||
const pub = DecafPoint.BASE.multiply(priv);
|
|
||||||
const encoded = pub.toRawBytes();
|
|
||||||
await mark('add', 1000000, () => pub.add(DecafPoint.BASE));
|
|
||||||
await mark('multiply', 1000, () => DecafPoint.BASE.multiply(priv));
|
|
||||||
await mark('encode', 10000, () => DecafPoint.BASE.toRawBytes());
|
|
||||||
await mark('decode', 10000, () => DecafPoint.fromHex(encoded));
|
|
||||||
if (RAM) utils.logMem();
|
|
||||||
});
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { run, compare } from 'micro-bmark';
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
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, hash_to_ristretto255 } from '../ed25519.js';
|
|
||||||
import { hashToCurve as ed448, hash_to_decaf448 } 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, expand: 'xmd', 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
await mark('hash_to_ristretto255', 1000, () => hash_to_ristretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' }));
|
|
||||||
await mark('hash_to_decaf448', 1000, () => hash_to_decaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' }));
|
|
||||||
});
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { run, mark } from 'micro-bmark';
|
|
||||||
import { secp256k1 } from '../secp256k1.js';
|
|
||||||
import { Field as 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))
|
|
||||||
});
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "benchmark",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "benchmarks",
|
|
||||||
"main": "index.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"bench": "node index.js"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "MIT",
|
|
||||||
"devDependencies": {
|
|
||||||
"micro-bmark": "0.3.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "^1.1.5",
|
|
||||||
"elliptic": "^6.5.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { run, mark, utils } from 'micro-bmark';
|
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
import * as mod from '../abstract/modular.js';
|
|
||||||
import { ed25519, RistrettoPoint } from '../ed25519.js';
|
|
||||||
|
|
||||||
run(async () => {
|
|
||||||
const RAM = false;
|
|
||||||
if (RAM) utils.logMem();
|
|
||||||
console.log(`\x1b[36mristretto255\x1b[0m`);
|
|
||||||
const priv = mod.hashToPrivateScalar(sha512(ed25519.utils.randomPrivateKey()), ed25519.CURVE.n);
|
|
||||||
const pub = RistrettoPoint.BASE.multiply(priv);
|
|
||||||
const encoded = pub.toRawBytes();
|
|
||||||
await mark('add', 1000000, () => pub.add(RistrettoPoint.BASE));
|
|
||||||
await mark('multiply', 10000, () => RistrettoPoint.BASE.multiply(priv));
|
|
||||||
await mark('encode', 10000, () => RistrettoPoint.BASE.toRawBytes());
|
|
||||||
await mark('decode', 10000, () => RistrettoPoint.fromHex(encoded));
|
|
||||||
if (RAM) utils.logMem();
|
|
||||||
});
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
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();
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { hexToBytes } from '../abstract/utils.js';
|
|
||||||
import { run, mark } from 'micro-bmark';
|
|
||||||
|
|
||||||
run(async () => {
|
|
||||||
const hex32 = '0123456789abcdef'.repeat(4);
|
|
||||||
const hex256 = hex32.repeat(8);
|
|
||||||
await mark('hexToBytes 32b', 5000000, () => hexToBytes(hex32));
|
|
||||||
await mark('hexToBytes 256b', 500000, () => hexToBytes(hex256));
|
|
||||||
});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# build
|
|
||||||
|
|
||||||
The directory is used to build a single file which contains everything.
|
|
||||||
|
|
||||||
The single file uses iife wrapper and can be used in browsers as-is.
|
|
||||||
|
|
||||||
Don't use it unless you can't use NPM/ESM, which support tree shaking.
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
|
|
||||||
|
|
||||||
export { secp256k1, schnorr as secp256k1_schnorr } from '@noble/curves/secp256k1';
|
|
||||||
export {
|
|
||||||
ed25519,
|
|
||||||
x25519,
|
|
||||||
edwardsToMontgomeryPub as ed25519_edwardsToMontgomeryPub,
|
|
||||||
edwardsToMontgomeryPriv as ed25519_edwardsToMontgomeryPriv,
|
|
||||||
} from '@noble/curves/ed25519';
|
|
||||||
export {
|
|
||||||
ed448,
|
|
||||||
x448,
|
|
||||||
edwardsToMontgomeryPub as ed448_edwardsToMontgomeryPub,
|
|
||||||
} from '@noble/curves/ed448';
|
|
||||||
export { p256 } from '@noble/curves/p256';
|
|
||||||
export { p384 } from '@noble/curves/p384';
|
|
||||||
export { p521 } from '@noble/curves/p521';
|
|
||||||
export { bls12_381 } from '@noble/curves/bls12-381';
|
|
||||||
|
|
||||||
export const utils = { bytesToHex, concatBytes, hexToBytes, utf8ToBytes };
|
|
||||||
445
build/package-lock.json
generated
445
build/package-lock.json
generated
@ -1,445 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "build",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "build",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"@noble/curves": "file:..",
|
|
||||||
"esbuild": "0.20.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"..": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "1.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@paulmillr/jsbt": "0.1.0",
|
|
||||||
"fast-check": "3.0.0",
|
|
||||||
"micro-bmark": "0.3.1",
|
|
||||||
"micro-should": "0.4.0",
|
|
||||||
"prettier": "3.1.1",
|
|
||||||
"typescript": "5.3.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==",
|
|
||||||
"cpu": [
|
|
||||||
"ppc64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"aix"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/android-arm": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/android-arm64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/android-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-arm": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==",
|
|
||||||
"cpu": [
|
|
||||||
"loong64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==",
|
|
||||||
"cpu": [
|
|
||||||
"mips64el"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==",
|
|
||||||
"cpu": [
|
|
||||||
"ppc64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==",
|
|
||||||
"cpu": [
|
|
||||||
"riscv64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==",
|
|
||||||
"cpu": [
|
|
||||||
"s390x"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/linux-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"netbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"sunos"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/win32-x64": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@noble/curves": {
|
|
||||||
"resolved": "..",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/esbuild": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"bin": {
|
|
||||||
"esbuild": "bin/esbuild"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@esbuild/aix-ppc64": "0.20.1",
|
|
||||||
"@esbuild/android-arm": "0.20.1",
|
|
||||||
"@esbuild/android-arm64": "0.20.1",
|
|
||||||
"@esbuild/android-x64": "0.20.1",
|
|
||||||
"@esbuild/darwin-arm64": "0.20.1",
|
|
||||||
"@esbuild/darwin-x64": "0.20.1",
|
|
||||||
"@esbuild/freebsd-arm64": "0.20.1",
|
|
||||||
"@esbuild/freebsd-x64": "0.20.1",
|
|
||||||
"@esbuild/linux-arm": "0.20.1",
|
|
||||||
"@esbuild/linux-arm64": "0.20.1",
|
|
||||||
"@esbuild/linux-ia32": "0.20.1",
|
|
||||||
"@esbuild/linux-loong64": "0.20.1",
|
|
||||||
"@esbuild/linux-mips64el": "0.20.1",
|
|
||||||
"@esbuild/linux-ppc64": "0.20.1",
|
|
||||||
"@esbuild/linux-riscv64": "0.20.1",
|
|
||||||
"@esbuild/linux-s390x": "0.20.1",
|
|
||||||
"@esbuild/linux-x64": "0.20.1",
|
|
||||||
"@esbuild/netbsd-x64": "0.20.1",
|
|
||||||
"@esbuild/openbsd-x64": "0.20.1",
|
|
||||||
"@esbuild/sunos-x64": "0.20.1",
|
|
||||||
"@esbuild/win32-arm64": "0.20.1",
|
|
||||||
"@esbuild/win32-ia32": "0.20.1",
|
|
||||||
"@esbuild/win32-x64": "0.20.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "build",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "input.js",
|
|
||||||
"type": "module",
|
|
||||||
"devDependencies": {
|
|
||||||
"@noble/curves": "file:..",
|
|
||||||
"esbuild": "0.20.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build:release": "npx esbuild --bundle input.js --outfile=`npx jsbt outfile` --global-name=`npx jsbt global`"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
curve-definitions/LICENSE
Normal file
21
curve-definitions/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the “Software”), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
28
curve-definitions/README.md
Normal file
28
curve-definitions/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# micro-curve-definitions
|
||||||
|
|
||||||
|
Elliptic curves implementations. `@noble/curves` is zero-dependency library for internal arithmetics.
|
||||||
|
|
||||||
|
`micro-curve-definitions` is the actual implementations. Current functionality:
|
||||||
|
|
||||||
|
- NIST curves: P192, P224, P256, P384, P521 (ECDSA)
|
||||||
|
- secp256k1 (ECDSA, without Schnorr)
|
||||||
|
- stark curve
|
||||||
|
- bn254
|
||||||
|
|
||||||
|
Pairings are not implemented.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install micro-curve-definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as nist from 'micro-curve-definitions';
|
||||||
|
|
||||||
|
// P192, P224, P256, P384, P521, bn254
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.
|
||||||
62
curve-definitions/package.json
Normal file
62
curve-definitions/package.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "micro-curve-definitions",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Curve definitions for @noble/curves",
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"index.d.ts",
|
||||||
|
"index.d.ts.map",
|
||||||
|
"index.ts"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/curves": "~0.1.0",
|
||||||
|
"@noble/hashes": "1.1.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@scure/base": "~1.1.0",
|
||||||
|
"@scure/bip32": "^1.1.1",
|
||||||
|
"@scure/bip39": "^1.1.0",
|
||||||
|
"@types/node": "^18.11.3",
|
||||||
|
"fast-check": "3.0.0",
|
||||||
|
"micro-should": "0.2.0",
|
||||||
|
"prettier": "2.6.2",
|
||||||
|
"typescript": "4.7.3"
|
||||||
|
},
|
||||||
|
"author": "Paul Miller (https://paulmillr.com)",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/paulmillr/noble-curves",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/paulmillr/noble-curves.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "prettier --check index.ts",
|
||||||
|
"test": "node test/index.test.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"secp192r1",
|
||||||
|
"secp224r1",
|
||||||
|
"secp256r1",
|
||||||
|
"secp384r1",
|
||||||
|
"secp521r1",
|
||||||
|
"NIST P192",
|
||||||
|
"NIST P224",
|
||||||
|
"NIST P256",
|
||||||
|
"NIST P384",
|
||||||
|
"NIST P521",
|
||||||
|
"NIST curves",
|
||||||
|
"EC",
|
||||||
|
"elliptic curves"
|
||||||
|
],
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
curve-definitions/src/bn.ts
Normal file
25
curve-definitions/src/bn.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { weierstrass, CHash } from '@noble/curves/shortw';
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
|
||||||
|
function getHash(hash: CHash) {
|
||||||
|
return {
|
||||||
|
hash,
|
||||||
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||||
|
randomBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was known as alt_bn128 when it had 128-bit security. Now that it's much lower, the naming
|
||||||
|
// has been changed to its prime bit count.
|
||||||
|
export const bn254 = weierstrass({
|
||||||
|
a: 0n,
|
||||||
|
b: 3n,
|
||||||
|
P: 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47n,
|
||||||
|
n: 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n,
|
||||||
|
Gx: 1n,
|
||||||
|
Gy: 2n,
|
||||||
|
...getHash(sha256),
|
||||||
|
});
|
||||||
190
curve-definitions/src/nist.ts
Normal file
190
curve-definitions/src/nist.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { sha384, sha512 } from '@noble/hashes/sha512';
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { weierstrass, CHash } from '@noble/curves/shortw';
|
||||||
|
import { mod, pow2 } from '@noble/curves/modular';
|
||||||
|
|
||||||
|
function getHash(hash: CHash) {
|
||||||
|
return {
|
||||||
|
hash,
|
||||||
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||||
|
randomBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.secg.org/sec2-v2.pdf
|
||||||
|
// https://neuromancer.sk/std/secg/secp192r1
|
||||||
|
export const P192 = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
||||||
|
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
||||||
|
// Field over which we'll do calculations. Verify with: 2n ** 192n - 2n ** 64n - 1n
|
||||||
|
P: BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
||||||
|
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
||||||
|
lowS: false,
|
||||||
|
// Default options
|
||||||
|
...getHash(sha256),
|
||||||
|
} as const);
|
||||||
|
export const secp192r1 = P192;
|
||||||
|
// https://neuromancer.sk/std/nist/P-224
|
||||||
|
export const P224 = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
||||||
|
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
P: 2n ** 224n - 2n ** 96n + 1n,
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
|
||||||
|
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
|
||||||
|
lowS: false,
|
||||||
|
// Default options
|
||||||
|
...getHash(sha256),
|
||||||
|
} as const);
|
||||||
|
export const secp224r1 = P224;
|
||||||
|
// https://neuromancer.sk/std/nist/P-256
|
||||||
|
export const P256 = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
||||||
|
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
// 2n ** 224n * (2n ** 32n - 1n) + 2n ** 192n + 2n ** 96n - 1n,
|
||||||
|
P: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
||||||
|
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
||||||
|
lowS: false,
|
||||||
|
// Default options
|
||||||
|
...getHash(sha256),
|
||||||
|
} as const);
|
||||||
|
export const secp256r1 = P256;
|
||||||
|
// https://neuromancer.sk/std/nist/P-384
|
||||||
|
// prettier-ignore
|
||||||
|
export const P384 = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc'),
|
||||||
|
b: BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'),
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
// 2n ** 384n - 2n ** 128n - 2n ** 96n + 2n ** 32n - 1n
|
||||||
|
P: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
|
||||||
|
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
||||||
|
lowS: false,
|
||||||
|
// Default options
|
||||||
|
...getHash(sha384),
|
||||||
|
} as const);
|
||||||
|
export const secp384r1 = P384;
|
||||||
|
// https://neuromancer.sk/std/nist/P-521
|
||||||
|
// prettier-ignore
|
||||||
|
export const P521 = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc'),
|
||||||
|
b: BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'),
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
// 2n ** 521n - 1n,
|
||||||
|
P: BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
|
||||||
|
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
||||||
|
lowS: false,
|
||||||
|
// Default options
|
||||||
|
...getHash(sha512),
|
||||||
|
} as const);
|
||||||
|
export const secp521r1 = P521;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* secp256k1 definition with efficient square root and endomorphism.
|
||||||
|
* Endomorphism works only for Koblitz curves with a == 0.
|
||||||
|
* It improves efficiency:
|
||||||
|
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
||||||
|
* Should always be used for Jacobian'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 secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const _2n = BigInt(2);
|
||||||
|
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||||
|
export const secp256k1 = weierstrass({
|
||||||
|
a: 0n,
|
||||||
|
b: 7n,
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
// 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n
|
||||||
|
P: secp256k1P,
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: secp256k1N,
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
||||||
|
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
||||||
|
...getHash(sha256),
|
||||||
|
// noble-secp256k1 compat
|
||||||
|
lowS: true,
|
||||||
|
// Used to calculate y - the square root of y².
|
||||||
|
// Exponentiates it to very big number (P+1)/4.
|
||||||
|
// We are unwrapping the loop because it's 2x faster.
|
||||||
|
// (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||||
|
// We are multiplying it bit-by-bit
|
||||||
|
sqrtMod: (x: bigint): bigint => {
|
||||||
|
const P = secp256k1P;
|
||||||
|
const _3n = BigInt(3);
|
||||||
|
const _6n = BigInt(6);
|
||||||
|
const _11n = BigInt(11);
|
||||||
|
const _22n = BigInt(22);
|
||||||
|
const _23n = BigInt(23);
|
||||||
|
const _44n = BigInt(44);
|
||||||
|
const _88n = BigInt(88);
|
||||||
|
const b2 = (x * x * x) % P; // x^3, 11
|
||||||
|
const b3 = (b2 * b2 * x) % P; // x^7
|
||||||
|
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||||
|
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
||||||
|
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
||||||
|
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
||||||
|
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
||||||
|
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
||||||
|
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
||||||
|
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
||||||
|
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
||||||
|
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
||||||
|
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
||||||
|
return pow2(t2, _2n, P);
|
||||||
|
},
|
||||||
|
endo: {
|
||||||
|
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||||
|
splitScalar: (k: bigint) => {
|
||||||
|
const n = secp256k1N;
|
||||||
|
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
||||||
|
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
||||||
|
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
||||||
|
const b2 = a1;
|
||||||
|
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
||||||
|
|
||||||
|
const c1 = divNearest(b2 * k, n);
|
||||||
|
const c2 = divNearest(-b1 * k, n);
|
||||||
|
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
||||||
|
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
||||||
|
const k1neg = k1 > POW_2_128;
|
||||||
|
const k2neg = k2 > POW_2_128;
|
||||||
|
if (k1neg) k1 = n - k1;
|
||||||
|
if (k2neg) k2 = n - k2;
|
||||||
|
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
||||||
|
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
||||||
|
}
|
||||||
|
return { k1neg, k1, k2neg, k2 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
35
curve-definitions/src/pasta.ts
Normal file
35
curve-definitions/src/pasta.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { weierstrass, CHash } from '@noble/curves/shortw';
|
||||||
|
|
||||||
|
function getHash(hash: CHash) {
|
||||||
|
return {
|
||||||
|
hash,
|
||||||
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||||
|
randomBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const p = BigInt('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001');
|
||||||
|
const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001');
|
||||||
|
|
||||||
|
export const pallas = weierstrass({
|
||||||
|
a: BigInt(0),
|
||||||
|
b: BigInt(5),
|
||||||
|
P: p,
|
||||||
|
n: q,
|
||||||
|
Gx: BigInt(-1),
|
||||||
|
Gy: BigInt(2),
|
||||||
|
...getHash(sha256),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const vesta = weierstrass({
|
||||||
|
a: BigInt(0),
|
||||||
|
b: BigInt(5),
|
||||||
|
P: q,
|
||||||
|
n: p,
|
||||||
|
Gx: BigInt(-1),
|
||||||
|
Gy: BigInt(2),
|
||||||
|
...getHash(sha256),
|
||||||
|
});
|
||||||
264
curve-definitions/src/starknet.ts
Normal file
264
curve-definitions/src/starknet.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { keccak_256 } from '@noble/hashes/sha3';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { weierstrass, CHash, JacobianPointType } from '@noble/curves/shortw';
|
||||||
|
import * as cutils from '@noble/curves/utils';
|
||||||
|
|
||||||
|
function getHash(hash: CHash) {
|
||||||
|
return {
|
||||||
|
hash,
|
||||||
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||||
|
randomBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CURVE_N = 3618502788666131213697322783095070105526743751716087489154079457884512865583n;
|
||||||
|
const nBitLength = 252;
|
||||||
|
// https://docs.starkware.co/starkex/stark-curve.html
|
||||||
|
export const starkCurve = weierstrass({
|
||||||
|
// Params: a, b
|
||||||
|
a: 1n,
|
||||||
|
b: 3141592653589793238462643383279502884197169399375105820974944592307816406665n,
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
// NOTE: there is no efficient sqrt for field (P%4==1)
|
||||||
|
P: 2n ** 251n + 17n * 2n ** 192n + 1n,
|
||||||
|
// Curve order, total count of valid points in the field. Verify with:
|
||||||
|
n: CURVE_N,
|
||||||
|
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: 874739451078007766457464989774322083649278607533249481151382481072868806602n,
|
||||||
|
Gy: 152666792071518830868575557812948353041420400780739481342941381225525861407n,
|
||||||
|
// Default options
|
||||||
|
lowS: false,
|
||||||
|
...getHash(sha256),
|
||||||
|
truncateHash: (hash: Uint8Array, truncateOnly = false): bigint => {
|
||||||
|
// TODO: cleanup, ugly code
|
||||||
|
// Fix truncation
|
||||||
|
if (!truncateOnly) {
|
||||||
|
let hashS = bytesToNumber0x(hash).toString(16);
|
||||||
|
if (hashS.length === 63) {
|
||||||
|
hashS += '0';
|
||||||
|
hash = hexToBytes0x(hashS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Truncate zero bytes on left (compat with elliptic)
|
||||||
|
while (hash[0] === 0) hash = hash.subarray(1);
|
||||||
|
const byteLength = hash.length;
|
||||||
|
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
|
||||||
|
let h = hash.length ? bytesToNumber0x(hash) : 0n;
|
||||||
|
if (delta > 0) h = h >> BigInt(delta);
|
||||||
|
if (!truncateOnly && h >= CURVE_N) h -= CURVE_N;
|
||||||
|
return h;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom Starknet type conversion functions that can handle 0x and unpadded hex
|
||||||
|
function hexToBytes0x(hex: string): Uint8Array {
|
||||||
|
if (typeof hex !== 'string') {
|
||||||
|
throw new TypeError('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 TypeError('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 sign0x(msgHash: Hex, privKey: Hex, opts: any) {
|
||||||
|
return starkCurve.sign(ensureBytes0x(msgHash), ensureBytes0x(privKey), opts);
|
||||||
|
}
|
||||||
|
function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) {
|
||||||
|
const sig = signature instanceof Signature ? signature : ensureBytes0x(signature);
|
||||||
|
return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { CURVE, Point, JacobianPoint, Signature, getPublicKey, getSharedSecret } = starkCurve;
|
||||||
|
export const utils = starkCurve.utils;
|
||||||
|
export {
|
||||||
|
CURVE,
|
||||||
|
Point,
|
||||||
|
Signature,
|
||||||
|
JacobianPoint,
|
||||||
|
getPublicKey,
|
||||||
|
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 bytesToNumber0x(sha256(cutils.concatBytes(key, hexToBytes0x(indexHex))));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function grindKey(seed: Hex) {
|
||||||
|
const _seed = ensureBytes0x(seed);
|
||||||
|
const sha256mask = 2n ** 256n;
|
||||||
|
const limit = sha256mask - starkCurve.utils.mod(sha256mask, starkCurve.CURVE.n);
|
||||||
|
for (let i = 0; ; i++) {
|
||||||
|
const key = hashKeyWithIndex(_seed, i);
|
||||||
|
// key should be in [0, limit)
|
||||||
|
if (key < limit) return starkCurve.utils.mod(key, starkCurve.CURVE.n).toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStarkKey(privateKey: Hex) {
|
||||||
|
const priv = typeof privateKey === 'string' ? strip0x(privateKey) : privateKey;
|
||||||
|
return bytesToHexEth(Point.fromPrivateKey(priv).toRawBytes(true).slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ethSigToPrivate(signature: string) {
|
||||||
|
signature = strip0x(signature.replace(/^0x/, ''));
|
||||||
|
if (signature.length !== 130) throw new Error('Wrong ethereum signature');
|
||||||
|
return grindKey(signature.substring(0, 64));
|
||||||
|
}
|
||||||
|
|
||||||
|
const MASK_31 = 2n ** 31n - 1n;
|
||||||
|
const int31 = (n: bigint) => Number(n & MASK_31);
|
||||||
|
export function getAccountPath(
|
||||||
|
layer: string,
|
||||||
|
application: string,
|
||||||
|
ethereumAddress: string,
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
const layerNum = int31(bytesToNumber0x(sha256(layer)));
|
||||||
|
const applicationNum = int31(bytesToNumber0x(sha256(application)));
|
||||||
|
const eth = hexToNumber0x(ethereumAddress);
|
||||||
|
return `m/2645'/${layerNum}'/${applicationNum}'/${int31(eth)}'/${int31(eth >> 31n)}'/${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.starkware.co/starkex/pedersen-hash-function.html
|
||||||
|
const PEDERSEN_POINTS = [
|
||||||
|
new Point(
|
||||||
|
2089986280348253421170679821480865132823066470938446095505822317253594081284n,
|
||||||
|
1713931329540660377023406109199410414810705867260802078187082345529207694986n
|
||||||
|
),
|
||||||
|
new Point(
|
||||||
|
996781205833008774514500082376783249102396023663454813447423147977397232763n,
|
||||||
|
1668503676786377725805489344771023921079126552019160156920634619255970485781n
|
||||||
|
),
|
||||||
|
new Point(
|
||||||
|
2251563274489750535117886426533222435294046428347329203627021249169616184184n,
|
||||||
|
1798716007562728905295480679789526322175868328062420237419143593021674992973n
|
||||||
|
),
|
||||||
|
new Point(
|
||||||
|
2138414695194151160943305727036575959195309218611738193261179310511854807447n,
|
||||||
|
113410276730064486255102093846540133784865286929052426931474106396135072156n
|
||||||
|
),
|
||||||
|
new Point(
|
||||||
|
2379962749567351885752724891227938183011949129833673362440656643086021394946n,
|
||||||
|
776496453633298175483985398648758586525933812536653089401905292063708816422n
|
||||||
|
),
|
||||||
|
];
|
||||||
|
// for (const p of PEDERSEN_POINTS) p._setWindowSize(8);
|
||||||
|
const PEDERSEN_POINTS_JACOBIAN = PEDERSEN_POINTS.map(JacobianPoint.fromAffine);
|
||||||
|
|
||||||
|
function pedersenPrecompute(p1: JacobianPointType, p2: JacobianPointType): JacobianPointType[] {
|
||||||
|
const out: JacobianPointType[] = [];
|
||||||
|
let p = p1;
|
||||||
|
for (let i = 0; i < 248; i++) {
|
||||||
|
out.push(p);
|
||||||
|
p = p.double();
|
||||||
|
}
|
||||||
|
p = p2;
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
out.push(p);
|
||||||
|
p = p.double();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
const PEDERSEN_POINTS1 = pedersenPrecompute(
|
||||||
|
PEDERSEN_POINTS_JACOBIAN[1],
|
||||||
|
PEDERSEN_POINTS_JACOBIAN[2]
|
||||||
|
);
|
||||||
|
const PEDERSEN_POINTS2 = pedersenPrecompute(
|
||||||
|
PEDERSEN_POINTS_JACOBIAN[3],
|
||||||
|
PEDERSEN_POINTS_JACOBIAN[4]
|
||||||
|
);
|
||||||
|
|
||||||
|
type PedersenArg = Hex | bigint | number;
|
||||||
|
function pedersenArg(arg: PedersenArg): bigint {
|
||||||
|
let value: bigint;
|
||||||
|
if (typeof arg === 'bigint') value = arg;
|
||||||
|
else if (typeof arg === 'number') {
|
||||||
|
if (!Number.isSafeInteger(arg)) throw new Error(`Invalid pedersenArg: ${arg}`);
|
||||||
|
value = BigInt(arg);
|
||||||
|
} else value = bytesToNumber0x(ensureBytes0x(arg));
|
||||||
|
// [0..Fp)
|
||||||
|
if (!(0n <= value && value < starkCurve.CURVE.P))
|
||||||
|
throw new Error(`PedersenArg should be 0 <= value < CURVE.P: ${value}`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pedersenSingle(
|
||||||
|
point: JacobianPointType,
|
||||||
|
value: PedersenArg,
|
||||||
|
constants: JacobianPointType[]
|
||||||
|
) {
|
||||||
|
let x = pedersenArg(value);
|
||||||
|
for (let j = 0; j < 252; j++) {
|
||||||
|
const pt = constants[j];
|
||||||
|
if (pt.x === point.x) throw new Error('Same point');
|
||||||
|
if ((x & 1n) !== 0n) point = point.add(pt);
|
||||||
|
x >>= 1n;
|
||||||
|
}
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3
|
||||||
|
export function pedersen(x: PedersenArg, y: PedersenArg) {
|
||||||
|
let point: JacobianPointType = PEDERSEN_POINTS_JACOBIAN[0];
|
||||||
|
point = pedersenSingle(point, x, PEDERSEN_POINTS1);
|
||||||
|
point = pedersenSingle(point, y, PEDERSEN_POINTS2);
|
||||||
|
return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashChain(data: PedersenArg[], fn = pedersen) {
|
||||||
|
if (!Array.isArray(data) || data.length < 1)
|
||||||
|
throw new Error('data should be array of at least 1 element');
|
||||||
|
if (data.length === 1) return numberToHexEth(pedersenArg(data[0]));
|
||||||
|
return Array.from(data)
|
||||||
|
.reverse()
|
||||||
|
.reduce((acc, i) => fn(i, acc));
|
||||||
|
}
|
||||||
|
// Same as hashChain, but computes hash even for single element and order is not revesed
|
||||||
|
export const computeHashOnElements = (data: PedersenArg[], fn = pedersen) =>
|
||||||
|
[0, ...data, data.length].reduce((x, y) => fn(x, y));
|
||||||
|
|
||||||
|
const MASK_250 = 2n ** 250n - 1n;
|
||||||
|
export const keccak = (data: Uint8Array) => bytesToNumber0x(keccak_256(data)) & MASK_250;
|
||||||
97
curve-definitions/test/basic.test.js
Normal file
97
curve-definitions/test/basic.test.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as nist from '../lib/nist.js';
|
||||||
|
import { hexToBytes } from '@noble/curves/utils';
|
||||||
|
import { default as ecdsa } from './fixtures/ecdsa_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh } from './fixtures/ecdh_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
// import { hexToBytes } from '@noble/curves';
|
||||||
|
|
||||||
|
should('Curve Fields', () => {
|
||||||
|
const vectors = {
|
||||||
|
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
|
||||||
|
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
|
||||||
|
secp256r1: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
|
||||||
|
secp256k1: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
|
||||||
|
secp384r1:
|
||||||
|
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
|
||||||
|
secp521r1:
|
||||||
|
0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
|
||||||
|
};
|
||||||
|
for (const n in vectors) deepStrictEqual(nist[n].CURVE.P, vectors[n]);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('wychenproof ECDSA vectors', () => {
|
||||||
|
for (const group of ecdsa.testGroups) {
|
||||||
|
// Tested in secp256k1.test.js
|
||||||
|
if (group.key.curve === 'secp256k1') continue;
|
||||||
|
// We don't have SHA-224
|
||||||
|
if (group.key.curve === 'secp224r1' && group.sha === 'SHA-224') continue;
|
||||||
|
const CURVE = nist[group.key.curve];
|
||||||
|
if (!CURVE) continue;
|
||||||
|
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
|
||||||
|
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
|
||||||
|
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
|
||||||
|
for (const test of group.tests) {
|
||||||
|
if (['Hash weaker than DL-group'].includes(test.comment)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
CURVE.Signature.fromDER(test.sig);
|
||||||
|
} catch (e) {
|
||||||
|
// Some test has invalid signature which we don't accept
|
||||||
|
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const verified = CURVE.verify(test.sig, m, pubKey);
|
||||||
|
deepStrictEqual(verified, true, 'valid');
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
failed = !CURVE.verify(test.sig, m, pubKey);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, 'invalid');
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('wychenproof ECDH vectors', () => {
|
||||||
|
for (const group of ecdh.testGroups) {
|
||||||
|
// // Tested in secp256k1.test.js
|
||||||
|
// if (group.key.curve === 'secp256k1') continue;
|
||||||
|
// We don't have SHA-224
|
||||||
|
const CURVE = nist[group.curve];
|
||||||
|
if (!CURVE) continue;
|
||||||
|
for (const test of group.tests) {
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const pub = CURVE.Point.fromHex(test.public);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const shared = CURVE.getSharedSecret(test.private, test.public);
|
||||||
|
deepStrictEqual(shared, test.shared, 'valid');
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
CURVE.getSharedSecret(test.private, test.public);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, 'invalid');
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
898
curve-definitions/test/fixtures/ecdh_secp224r1_ecpoint_test.json
vendored
Normal file
898
curve-definitions/test/fixtures/ecdh_secp224r1_ecpoint_test.json
vendored
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
{
|
||||||
|
"algorithm" : "ECDH",
|
||||||
|
"generatorVersion" : "0.8r12",
|
||||||
|
"numberOfTests" : 96,
|
||||||
|
"header" : [
|
||||||
|
"Test vectors of type EcdhWebTest are intended for",
|
||||||
|
"testing an ECDH implementations where the public key",
|
||||||
|
"is just an ASN encoded point."
|
||||||
|
],
|
||||||
|
"notes" : {
|
||||||
|
"AddSubChain" : "The private key has a special value. Implementations using addition subtraction chains for the point multiplication may get the point at infinity as an intermediate result. See CVE_2017_10176",
|
||||||
|
"CompressedPoint" : "The point in the public key is compressed. Not every library supports points in compressed format."
|
||||||
|
},
|
||||||
|
"schema" : "ecdh_ecpoint_test_schema.json",
|
||||||
|
"testGroups" : [
|
||||||
|
{
|
||||||
|
"curve" : "secp224r1",
|
||||||
|
"encoding" : "ecpoint",
|
||||||
|
"type" : "EcdhEcpointTest",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 1,
|
||||||
|
"comment" : "normal case",
|
||||||
|
"public" : "047d8ac211e1228eb094e285a957d9912e93deee433ed777440ae9fc719b01d050dfbe653e72f39491be87fb1a2742daa6e0a2aada98bb1aca",
|
||||||
|
"private" : "565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328",
|
||||||
|
"shared" : "b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 2,
|
||||||
|
"comment" : "compressed public key",
|
||||||
|
"public" : "027d8ac211e1228eb094e285a957d9912e93deee433ed777440ae9fc71",
|
||||||
|
"private" : "565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328",
|
||||||
|
"shared" : "b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f",
|
||||||
|
"result" : "acceptable",
|
||||||
|
"flags" : [
|
||||||
|
"CompressedPoint"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 3,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04e73a6ca72f3a2fae6e0a01a0ed03bfa3058b04576942eaf063095e62ca16fd31fa0f38eeb592cbeea1147751fdd2a5b6cc0ead404467a5b6",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "00000000000000000000000000000000000000000000000000000003",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 4,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "045763fa2ae16367ad23d471cc9a52466f0d81d864e5640cefe384114594d9fecfbed4f254505ac8b41d2532055a07f0241c4818b552cbb636",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "00000000000000000000000100000000000000000000000000000001",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 5,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04142c1fd80fa2121a59aa898144084ec033f7a56a34eee0b499e29ae51c6d8c1bbb1ef2a76d565899fe44ffc1207d530d7f598fb77f4bb76b",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "00000000000000ffffffffffffff0000000000000100000000000000",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 6,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04ed6f793e10c80d12d871cf8988399c4898a9bf9ffd8f27399f63de25f0051cdf4eec7f368f922cfcd948893ceca0c92e540cc4367a99a66a",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "00000000ffffffffffffffff00000000000000010000000000000000",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 7,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "0408fcfc1a63c82860be12e4137433dfc40be9acdd245f9a8c4e56be61a385fc09f808383383f4b1d0d5365b6e5dcfacdc19bc7bcfed221274",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 8,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04d883ed77f1861e8712800d31df67888fe39f150c79a27aa88caeda6b180f3f623e2ff3ab5370cf8179165b085af3dd4502850c0104caed9a",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "0003fffffff00000003fffffff00000003fffffff000000040000000",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 9,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "042b8b279b85ee3f3d2c0abeb36fdfc5aad6157d652d26489381a32cd73224bd757ef794acc92b0b3b9e7990618bb343a9a09bdb9d3616eff6",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "01fffffffc00000007fffffff00000001fffffffc000000080000001",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 10,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "048bd5f03391eeeae1744e8fc53d314efffafa4d3fa4f1b95c3388a9cd7c86358b273119c537133eb55e79c6ac510b10980b379b919ccf2e2f",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "0a15c112ff784b1445e889f955be7e3ffdf451a2c0e76ab5cb32cf41",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 11,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04ce9631b6a16227778625c8e5421ae083cdd913abefde01dbe69f6c2b95386aff2b483b2c47151cfaabfd000614c683ce2e1778221ae42c1b",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "62989eaaa26a16f07330c3c51e0a4631fd016bfcede26552816aee39",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 12,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "041f441c98eda956a6a7fdbfd8d21910860ab59d16c3e52f8e7fad6ca5df61a55fc508fc0499c55492f1e87bb2faa0cb4170b79f3a85ec2f3d",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "661ac958c0febbc718ccf39cefc6b66c4231fbb9a76f35228a3bf5c3",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 13,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04be74583cb9d3a05ae54923624e478a329a697d842dfae33141c844d7d9ba4fc96e0fe716ac0542e87368662fc2f0cb9b0ae57936ddec7190",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "6d7e41821abe1094d430237923d2a50de31768ab51b12dce8a09e34c",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 14,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04a281ad992b363597ac93ff0de8ab1f7e51a6672dcbb58f9d739ba430ce0192874038daefc3130eec65811c7255da70fea65c1003f6892faa",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 15,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04be3e22133f51203f631b81dde8c020cdea5daa1f99cfc05c88fad2dc0f243798d6e72d1de9e3cdca4144e0a6c0f2a584d07589006972c197",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "fffc0007fff0001fffc0007fff0001fffc0007fff0001fffc0008001",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 16,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04af14547c20afbd91bfe64ea03d45a76a71241f23520ef897ff91eff1b54ca6ca8c25fd73852ec6654617434eff7f0225684d4dea7a4f8a97",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "ffff0000003ffffff0000003ffffff0000003ffffff0000003ffffff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 17,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04b1e484925018729926acda56ff3e2f6c1e7e8f162b178d8e8afb45564fceaa6da5d998fe26b6b26a055169063a5ab6908852ca8b54e2de6c",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "fffff0000007fffffe000000ffffffc000001ffffff8000003ffffff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 18,
|
||||||
|
"comment" : "edge case for shared secret",
|
||||||
|
"public" : "04937eb09fb145c8829cb7df20a4cbeed396791373de277871d6c5f9cc3b5b4fd56464a71fc4a2a6af3bd251952bffa829489e68a8d06f96b6",
|
||||||
|
"private" : "00a2b6442a37f9201b56758034d2009be64b0ab7c02d7e398cac9665d6",
|
||||||
|
"shared" : "ffffffff00000000ffffffff00000000ffffffff00000000ffffffff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 19,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04000000000000000000000000000000000000000000000000000000037cac269c67bd55ea14efff4eadefe5e74978514af14c88fab46ec046",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "3fa0b9ff70b884f9f57bb84f7a9532d93f6ba803f89dd8ff008177d7",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 20,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04000000000000000000000001000000000000000000000000000000012ea2f4917bdfdb008306cc10a18e2557633ba861001829dcbfb96fba",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "be1ded8cb7ff8a585181f96d681e31b332fe27dcae922dca2310300d",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 21,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "0400000000000000ffffffffffffff000000000000010000000000000073ca5f8f104997a2399e0c7f25e72a75ec29fc4542533d3fea89a33a",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "a2e86a260e13515918a0cafdd87855f231b5624c560f976159e06a75",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 22,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "0400000000ffffffffffffffff000000000000000100000000000000006fe6805f59b19b0dd389452a1d4a420bfeb6c369cf6fed5b12e6e654",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "31ef7c8d10404a0046994f313a70574b027e87f9028eca242c1b5bf5",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 23,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "040000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff77c5cfa4e2c384938d48bd8dd98f54c86b279f1df8c0a1f6692439c9",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "d1976a8ef5f54f24f5a269ad504fdca849fc9c28587ba294ef267396",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 24,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "040003fffffff00000003fffffff00000003fffffff00000004000000001f0828136016bb97445461bc59f2175d8d23557d6b9381f26136e3d",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "ce7890d108ddb2e5474e6417fcf7a9f2b3bd018816062f4835260dc8",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 25,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "0401fffffffc00000007fffffff00000001fffffffc0000000800000012d8acca6f199d4a94b933ba1aa713a7debde8ac57b928f596ae66a66",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "30b6ff6e8051dae51e4fe34b2d9a0b1879153e007eb0b5bdf1791a9c",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 26,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "040a15c112ff784b1445e889f955be7e3ffdf451a2c0e76ab5cb32cf413d4df973c563c6decdd435e4f864557e4c273096d9941ca4260a266e",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "77ec668a00f72d85aa527624abb16c039fe490d17dd6c455a1ed7fd8",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 27,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "0462989eaaa26a16f07330c3c51e0a4631fd016bfcede26552816aee39389ee9436d616cab90032931aa7fbbfcfc13309f61e2423cc8dab93c",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "a3f432f6aba9a92f49a5ea64ffe7059a9d9b487a0b5223ddc988208b",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 28,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04661ac958c0febbc718ccf39cefc6b66c4231fbb9a76f35228a3bf5c3103b8040e3cb41966fc64a68cacb0c14053f87d27e8ed7bf2d7fe51b",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "1530fd9caf03737af34a4ba716b558cbecbc35d18402535a0a142313",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 29,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "046d7e41821abe1094d430237923d2a50de31768ab51b12dce8a09e34c276cf273d75d367820dd556182def0957af0a314f48fed227c298dc0",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "cfc39ccacb94ad0e0552b2e47112f60fbbe7ae0dc32230b9273dd210",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 30,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "047fffffffffffffffffffffffffffffffffffffffffffffffffffffff7d8dbca36c56bcaae92e3475f799294f30768038e816a7d5f7f07d77",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "73bd63bd384a0faafb75cfed3e95d3892cbacf0db10f282c3b644771",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 31,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04fffc0007fff0001fffc0007fff0001fffc0007fff0001fffc000800174f1ff5ea7fbc72b92f61e06556c26bab84c0b082dd6400ca1c1eb6d",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "85b079c62e1f5b0fd6841dfa16026e15b641f65e13a14042567166bb",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 32,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04ffff0000003ffffff0000003ffffff0000003ffffff0000003ffffff0126fdd5fccd0b5aa7fd5bb5b1308584b30556248cec80208a2fe962",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "8a834ff40e3fc9f9d412a481e18537ea799536c5520c6c7baaf12166",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 33,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04fffff0000007fffffe000000ffffffc000001ffffff8000003ffffff20cfa23077acc9fbcb71339c65880cd0b966b8a9497e65abed17f0b5",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "a0887269766e6efcbc81d2b38f2d4638663f12377468a23421044188",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 34,
|
||||||
|
"comment" : "edge cases for ephemeral key",
|
||||||
|
"public" : "04ffffffff00000000ffffffff00000000ffffffff00000000ffffffff1c05ac2d4f10b69877c3243d51f887277b7bf735c326ab2f0d70da8c",
|
||||||
|
"private" : "2bc15cf3981f4e15bbad387b506df647989e5478160be862f8c26969",
|
||||||
|
"shared" : "c65d1911bc076a74588d8793ce7a0dcabf5793460cd2ebb02754a1be",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 35,
|
||||||
|
"comment" : "point with coordinate y = 1",
|
||||||
|
"public" : "043b5889352ddf7468bf8c0729212aa1b2a3fcb1a844b8be91abb753d500000000000000000000000000000000000000000000000000000001",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "e973c413cc7dd34d4e3637522b2e033c20815412b67574a1f2f6bdd7",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 36,
|
||||||
|
"comment" : "point with coordinate y = 1",
|
||||||
|
"public" : "04bf09e268942555c73ce9e00d272c9b12bf0c3fc13a639acc791167f6b05df0023c9bd41d0b0c461854582d0601182213f2219d44ea44914a",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "ec856e807808a9c5332e886759e03f01be02437cfe0214613e4e7dc7",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 37,
|
||||||
|
"comment" : "point with coordinate y = 1",
|
||||||
|
"public" : "047b664cff2eef0a4f7dce24780113432f66feb25cb0931d033d63910f548ee514f6fdf1cb6f5709581c197d76a5eb218afaed19f205f4ab80",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "91d424e122c9c01720bbed6b53ec1b37a86996fa4fcf74bfd30f723d",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 38,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "045a2b3ec1053390550b587557712bcc0bf85654d23099420154877ec4138322ca02e5fceae870227a43ae8982b67276f6d8f1dd7e12692474",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "012879a1ff456acb8726455836bc4f504c1bd799a4d96f514b3730c6",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 39,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04fc229bb1df3e11351e7e4224f68f40c0d0e194023c6e0840cd45ee5ca242112fbab5736e821dad26493e4006e2c6125342e7d9bc25272856",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "fd6e5edb54d7dd554f8747ec87b8031258fc0bf1d2404b64db4540d4",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 40,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "0469a65f62d4159235801a246f2d13e45c8983a3362da480e7a51d42a65b7047abfc2a179d943bb196fede7ac3ad8a4fcacd4c4caa717b6b26",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "164e95bfa2a9c3a1f959feb88720bb7a37f988a08124639d8adf86df",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 41,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04dc68eb945528af0051cbf23e3eea43b2bc4c728976231e7031e63a2744ba65a4e1e34e8ec50cf7e8df4458582b16413ab83f568508c59037",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "b0ffd55fa112aa48eddc960db4a1200d406e144aac9e109ad9892b2d",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 42,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "0481c89369d7be252920e08e2d6c6841b887efb4fc747db31dd1030b1919bf8ccb629b58fea6234e39812083fb0833a0c937e348eda22ea0c0",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "d6ab4567eff21277284be082d9e09eb08bb80685f4929dc3dca4b333",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 43,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "0451d830f792795409f1ee972d3b94289f59206fe09e12166920739a73d2f1831b26677901bfaf8323f82b81e1012d9d3f1c9296c59c97970f",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "b43de12912b40cbdd56e30fdfe9a2c24fb72687168c9cfe6b7476966",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 44,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04ab63ce55145842149f99023f37a0a89b9fc4ae6a878fdae8caf31d17ffd0d55830eed46f8255f94b6dcf98a22f1ff26dabf773d556788881",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "588ee0af3bc60118a715325c6d56c850f73067dcb37b7596d0cfda5f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 45,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "041d64535d54bfcccb38165acbfac01ae33db20e802c5687343cb21b7eb59d86f1892a974741925624477eef21f4e72fa04ee6ce35dfffe5f2",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "7219ef73ac9e47ac2e03dead23fa8382ae898e2415017cdeb4739f0f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 46,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04d9d78436a3f9c1fa20e8c2318e61e62b94623e23a0ab746c5ac0cbc38262bd66c17515d3048944dae43b2bd6dd9d7c7a0f7042de2d1001c6",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "267b069aac5d768a720acc62c92f20b786fc48c7da42f1f5677424ee",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 47,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "0465eb3750c6401339caa69ebe6dec86dfc4d79bf657d68bbdd082c5a03eb81e85931352ff338ccbc3a1d332e2d8bc84342d516da06bef220f",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "bbdd4ac5890b9c0412e4ef3135f666e5b3ddb658ec837691e8129be8",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 48,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04e92d3be1614555ae17a90647979fbb37468c55a1fff9e15f376d49994e470f515b7b3fe50cb55def16142df594c3e46d9d1354730778f9e8",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "f793ff0d14bd7690840c733162b589cd3413d8c41f4488b427da496f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 49,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "043c92710c9a7f6f98bbec9d2a4fa617cc70e96bc96ecd4597e329143f4750a027c6972459c091ab02c0e2a3082fccec429a38d3596e7aff2b",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "56c703d4716239c954109b9b841db75b04a790f1f72aa966aece3494",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 50,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04568dfbfa42efc94ce207322e637b4c94f37a5668ad230e987a91d048dcadd244fc059cffab5fa8820a969353620e708e85bd5eec8a0c68ec",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "7823fe7eb642d50984fb32f911ef289419d85330c3398423d0eda05f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 51,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04ec10837e495b644904dba58d8dd82133c905a285ae7c2a06d5ccaf6bf0fbf00d13e21a399dc95ae5524a1a37044193e94e3300259b70e058",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "f7014d38f460836a51075cce9667b56b8851ba19011c8b0274b74a4b",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 52,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04bee2f9352f42ceeb3bf3109e90e6578d0bd4888458df7d179d746977e50e53503dee83eca1824a290566588fa3591645b1a2d56861bda760",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "777f99f2bdaa72a1185388465ddda1d059872ad043c7cb85b94e28bb",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 53,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "04546facbcaa8b551c51715a9add5edc3c8a66dcc47a6223f605614cf7af6d92f5bdebea738658a42c6231e53c08237ccf52f79399579b2dcc",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "a1db178b716e51e0fa46c1d74a2603005326bca7e81170d4b33a3d2a",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 54,
|
||||||
|
"comment" : "point with coordinate y = 1 in left to right addition chain",
|
||||||
|
"public" : "0423b1811fee891adb33c8bfee289964e92a9d3358daf975d0efb73e229a3332668b7d6da290a2edc941e8bd6f2e33745fc606756eddc013bb",
|
||||||
|
"private" : "00938f3dbe37135cdbdb9993a187a0e9b9f0def035fbc52ad59fc50421",
|
||||||
|
"shared" : "f455c8273416199505019861266ddb9bcde7bee3c3f15a98ee54607b",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 55,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "0458f53d67332415fe5b4b81999f8332fb6dcdb965d96dbcbab0fac375f29efef7ab4d94bb2d25d25205eae29fe8d9a85b811114a50f6c6859",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "d3af1857aca1689514fcfee8d8c40b8637d40452ae35c404f9e67494",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 56,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "04f2d6e58fcd3ed3f656a9bc687fe4c789ba9614d0359967bc0468eabfa1658a14ef0633f2485e29141e2c4a13bd328ec9bf6af4c7a774131b",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "933c385d5fadb57de53e4a5d385118fce830430703c3f585a5d4d0b5",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 57,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "0402ca5d1b7638b7b88ad02176bd10ff1cfe8812a62f9769a6d62e0c6c787b3e3b2a063940911bf987fc38deebf542400b8bbd9dfeb7d90a8a",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "75aea79d99e5c7edaab0284443b548843371d1d9b55f2d73a1a9092f",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 58,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "04a394d8bf9b479ec3c7ac3fc6a631d01d57d338b9fb5a0ed6e5130e050cfc600cfb08e67727ac5a33345ec1d48d4a9a18516c2203acbd2667",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "8c1d0850691cda7523ffccf1cba44b4d472193e6a3bb0727e490a8b5",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 59,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "04642e26421e96fa88f956d098ac26f02f1d6faa80e460e701a3789a66c38dd95c6b33de8768c85cbe6879d0d77e29fe5a18b26a35cb60c0b6",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "50b9ed4d99e2f24e0096eaeded0b552cf8deff5ca8f976964ae47e92",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 60,
|
||||||
|
"comment" : "point with coordinate y = 1 in precomputation or right to left addition chain",
|
||||||
|
"public" : "04f974d1cbbf4171d4773c3e84eab80bc3c6c2858dadcfbd11d64316905df36fbe345f28a3ef663125649474c6fc1ebe175c3865c4469e192b",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "5616ee3e63dfb424d329c2b9b50cf378bb77a8bd7e314a241b5942c7",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 61,
|
||||||
|
"comment" : "point with coordinate y = 1 in right to left addition chain",
|
||||||
|
"public" : "0455561db3cc8fb08a71654ee9573a1a36a44f0913ca8ad7582cfafbfc62b31e5e78be98ad8c8ceab4bb82e8efc0acb29f1a8d031ed044046c",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "b1da14507b5c05159e15f77d085c017acd89f158011357a97802855d",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 62,
|
||||||
|
"comment" : "point with coordinate y = 1 in right to left addition chain",
|
||||||
|
"public" : "04a363bcb9bddd5de84a2f4433c039f7be3fce6057b0d3b4a3459e54a2ba32302871e7ba5c3dd7ec9b76946cdc702c15a8d9ec0f4a04e7afb6",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "2f1bd4a5a497481c4a21222320ff61f32674a95d540cc3f4f3ca5849",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 63,
|
||||||
|
"comment" : "point with coordinate y = 1 in right to left addition chain",
|
||||||
|
"public" : "043a656d0e25bce27282f256b121fbfcde0a180ccd7aa601a5929fc74002f89e45b4dcb873c56da5d1a28fbca33a126177b217a098e0952e62",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "8c807d65ba7b9fd3061dffef26c025a89524a26b942edd3a984fe51d",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 64,
|
||||||
|
"comment" : "point with coordinate y = 1 in right to left addition chain",
|
||||||
|
"public" : "04bf5f49ba0086eec289b068b783438ef24b6f28130bb1ed969ef8b041f11b0de95f15edcd835f01bab1f5faaa1749c2ca4f16a7d99d916ff4",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "8fda76f4d124e6727f855e5f4921cc05c48e2a8ed0fee7c75d6a8047",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 65,
|
||||||
|
"comment" : "point with coordinate y = 1 in right to left addition chain",
|
||||||
|
"public" : "04a57232560d9d604655181f775859b0723d4e01a4c867844eb9d81dabb5d19507bbe9cda3346bad7c184daa432e7f794a5b9b8b8d4e55be3a",
|
||||||
|
"private" : "00c1781d86cac2c0af3fb50d54c554a67bd75d25ca796f0486e3fa84f9",
|
||||||
|
"shared" : "daf35bb7bf3a056bb62bb01ba00f581c107f64de85842b3a49bc2a4a",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 66,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "03",
|
||||||
|
"shared" : "e71f2157bfe37697ea5193d4732dcc6e5412fa9d38387eacd391c1c6",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 67,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"shared" : "fa2664717c7fa0161ec2c669b2c0986cdc20456a6e5406302bb53c77",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 68,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "01000000000000000000000000000000000000000000000000000000",
|
||||||
|
"shared" : "af6e5ad34497bae0745f53ad78ce8b285d79f400d5c6e6a071f8e6bd",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 69,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"shared" : "12fd302ff8c13c55a9c111f8bb6b0a13ecf88299c0ae3032ce2bcaff",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 70,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "0080000000000000000000000000000000000000000000000000000000",
|
||||||
|
"shared" : "73f1a395b842f1a6752ae417e2c3dc90cafc4476d1d861b7e68ad030",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 71,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03d13dd29455c5c2a3d",
|
||||||
|
"shared" : "b329c20ddb7c78ee4e622bb23a984c0d273ba34b6269f3d9e8f89f8e",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 72,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13cd29455c5c2a3d",
|
||||||
|
"shared" : "6f48345209b290ffc5abbe754a201479e5d667a209468080d06197b4",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 73,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13d529455c5c2a3d",
|
||||||
|
"shared" : "9f6e30c1c9dad42a153aacd4b49a8e5c721d085cd07b5d5aec244fc1",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 74,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29445c5c2a3d",
|
||||||
|
"shared" : "8cadfb19a80949e61bd5b829ad0e76d18a5bb2eeb9ed7fe2b901cecd",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 75,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c29b7",
|
||||||
|
"shared" : "475fd96e0eb8cb8f100a5d7fe043a7a6851d1d611da2643a3c6ae708",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : [
|
||||||
|
"AddSubChain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 76,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a37",
|
||||||
|
"shared" : "41ef931d669d1f57d8bb95a01a92321da74be8c6cbc3bbe0b2e73ebd",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : [
|
||||||
|
"AddSubChain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 77,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3a",
|
||||||
|
"shared" : "e71f2157bfe37697ea5193d4732dcc6e5412fa9d38387eacd391c1c6",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 78,
|
||||||
|
"comment" : "edge case private key",
|
||||||
|
"public" : "04478e73465bb1183583f4064e67e8b4343af4a05d29dfc04eb60ac2302e5b9a3a1b32e4208d4c284ff26822e09c3a9a4683443e4a35175504",
|
||||||
|
"private" : "00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3b",
|
||||||
|
"shared" : "11ff15126411299cbd49e2b7542e69e91ef132e2551a16ecfebb23a3",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : [
|
||||||
|
"AddSubChain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 79,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 80,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 81,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "0400000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 82,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "0400000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 83,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "040000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 84,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "040000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 85,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "0400000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffff000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 86,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "0400000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffff000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 87,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 88,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 89,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 90,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 91,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff00000000000000000000000100000000000000000000000000000000000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 92,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff00000000000000000000000100000000000000000000000000000000000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 93,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff000000000000000000000001ffffffffffffffffffffffffffffffff000000000000000000000000",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 94,
|
||||||
|
"comment" : "point is not on curve",
|
||||||
|
"public" : "04ffffffffffffffffffffffffffffffff000000000000000000000001ffffffffffffffffffffffffffffffff000000000000000000000001",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 95,
|
||||||
|
"comment" : "",
|
||||||
|
"public" : "",
|
||||||
|
"private" : "00c6cafb74e2a5b5ed4b991cbbfbc28c18f6df208b6d05e7a2e6668014",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 96,
|
||||||
|
"comment" : "invalid public key",
|
||||||
|
"public" : "020ca753db5ddeca474241f8d2dafc0844343fd0e37eded2f0192d51b2",
|
||||||
|
"private" : "00fc28a0ca0f8e36b0d4f71421845135a22aef543b9fddf8c775b2d18f",
|
||||||
|
"shared" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"CompressedPoint"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3581
curve-definitions/test/fixtures/ecdh_secp224r1_test.json
vendored
Normal file
3581
curve-definitions/test/fixtures/ecdh_secp224r1_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4545
curve-definitions/test/fixtures/ecdh_secp256k1_test.json
vendored
Normal file
4545
curve-definitions/test/fixtures/ecdh_secp256k1_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1994
curve-definitions/test/fixtures/ecdh_secp256r1_ecpoint_test.json
vendored
Normal file
1994
curve-definitions/test/fixtures/ecdh_secp256r1_ecpoint_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1672
curve-definitions/test/fixtures/ecdh_secp384r1_ecpoint_test.json
vendored
Normal file
1672
curve-definitions/test/fixtures/ecdh_secp384r1_ecpoint_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4366
curve-definitions/test/fixtures/ecdh_secp384r1_test.json
vendored
Normal file
4366
curve-definitions/test/fixtures/ecdh_secp384r1_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2196
curve-definitions/test/fixtures/ecdh_secp521r1_ecpoint_test.json
vendored
Normal file
2196
curve-definitions/test/fixtures/ecdh_secp521r1_ecpoint_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4868
curve-definitions/test/fixtures/ecdh_secp521r1_test.json
vendored
Normal file
4868
curve-definitions/test/fixtures/ecdh_secp521r1_test.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,16 +28,16 @@
|
|||||||
"Uy": "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
|
"Uy": "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"k": "C1D1F2F10881088301880506805FEB4825FE09ACB6816C36991AA06D",
|
"k": "AD3029E0278F80643DE33917CE6908C70A8FF50A411F06E41DEDFCDC",
|
||||||
"message": "sample",
|
"message": "sample",
|
||||||
"r": "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
|
"r": "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
|
||||||
"s": "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC"
|
"s": "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"k": "DF8B38D40DCA3E077D0AC520BF56B6D565134D9B5F2EAE0D34900524",
|
"k": "FF86F57924DA248D6E44E8154EB69F0AE2AEBAEE9931D0B5A969F904",
|
||||||
"message": "test",
|
"message": "test",
|
||||||
"r": "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
|
"r": "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
|
||||||
"s": "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4"
|
"s": "178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
8
curve-definitions/test/index.test.js
Normal file
8
curve-definitions/test/index.test.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { should } from 'micro-should';
|
||||||
|
|
||||||
|
import './basic.test.js';
|
||||||
|
import './rfc6979.test.js';
|
||||||
|
import './secp256k1.test.js';
|
||||||
|
import './starknet/starknet.test.js';
|
||||||
|
|
||||||
|
should.run();
|
||||||
33
curve-definitions/test/rfc6979.test.js
Normal file
33
curve-definitions/test/rfc6979.test.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { deepStrictEqual } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as nist from '../lib/nist.js';
|
||||||
|
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
|
||||||
|
function hexToBigint(hex) {
|
||||||
|
return BigInt('0x' + hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
should('RFC6979', () => {
|
||||||
|
for (const v of rfc6979) {
|
||||||
|
const curve = nist[v.curve];
|
||||||
|
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
|
||||||
|
const pubKey = curve.getPublicKey(v.private);
|
||||||
|
const pubPoint = curve.Point.fromHex(pubKey);
|
||||||
|
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
|
||||||
|
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
|
||||||
|
for (const c of v.cases) {
|
||||||
|
const h = curve.CURVE.hash(c.message);
|
||||||
|
const sigObj = curve.sign(h, v.private);
|
||||||
|
// const sigObj = curve.Signature.fromDER(sig);
|
||||||
|
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
|
||||||
|
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
|
||||||
|
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
|
||||||
|
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
559
curve-definitions/test/secp256k1.test.js
Normal file
559
curve-definitions/test/secp256k1.test.js
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
import * as fc from 'fast-check';
|
||||||
|
import * as nist from '../lib/nist.js';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
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 privates } from './vectors/privates.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 { should } from 'micro-should';
|
||||||
|
import { deepStrictEqual, throws, rejects } from 'assert';
|
||||||
|
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||||
|
|
||||||
|
const hex = bytesToHex;
|
||||||
|
const secp = nist.secp256k1;
|
||||||
|
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);
|
||||||
|
// prettier-ignore
|
||||||
|
const INVALID_ITEMS = ['deadbeef', Math.pow(2, 53), [1], 'xyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxyxyzxyzxy', secp.CURVE.n + 2n];
|
||||||
|
|
||||||
|
const toBEHex = (n) => n.toString(16).padStart(64, '0');
|
||||||
|
|
||||||
|
function hexToNumber(hex) {
|
||||||
|
if (typeof hex !== 'string') {
|
||||||
|
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
|
||||||
|
}
|
||||||
|
// Big Endian
|
||||||
|
return BigInt(`0x${hex}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
should('secp256k1.getPublicKey()', () => {
|
||||||
|
const data = privatesTxt
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line)
|
||||||
|
.map((line) => line.split(':'));
|
||||||
|
for (let [priv, x, y] of data) {
|
||||||
|
const point = secp.Point.fromPrivateKey(BigInt(priv));
|
||||||
|
deepStrictEqual(toBEHex(point.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point.y), y);
|
||||||
|
|
||||||
|
const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
|
||||||
|
deepStrictEqual(toBEHex(point2.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point2.y), y);
|
||||||
|
|
||||||
|
const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
|
||||||
|
deepStrictEqual(toBEHex(point3.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point3.y), y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.getPublicKey() rejects invalid keys', () => {
|
||||||
|
// for (const item of INVALID_ITEMS) {
|
||||||
|
// throws(() => secp.getPublicKey(item));
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
should('secp256k1.precompute', () => {
|
||||||
|
secp.utils.precompute(4);
|
||||||
|
const data = privatesTxt
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line)
|
||||||
|
.map((line) => line.split(':'));
|
||||||
|
for (let [priv, x, y] of data) {
|
||||||
|
const point = secp.Point.fromPrivateKey(BigInt(priv));
|
||||||
|
deepStrictEqual(toBEHex(point.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point.y), y);
|
||||||
|
|
||||||
|
const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv))));
|
||||||
|
deepStrictEqual(toBEHex(point2.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point2.y), y);
|
||||||
|
|
||||||
|
const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv)))));
|
||||||
|
deepStrictEqual(toBEHex(point3.x), x);
|
||||||
|
deepStrictEqual(toBEHex(point3.y), y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point.isValidPoint()', () => {
|
||||||
|
for (const vector of points.valid.isPoint) {
|
||||||
|
const { P, expected } = vector;
|
||||||
|
if (expected) {
|
||||||
|
secp.Point.fromHex(P);
|
||||||
|
} else {
|
||||||
|
throws(() => secp.Point.fromHex(P));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point.fromPrivateKey()', () => {
|
||||||
|
for (const vector of points.valid.pointFromScalar) {
|
||||||
|
const { d, expected } = vector;
|
||||||
|
let p = secp.Point.fromPrivateKey(d);
|
||||||
|
deepStrictEqual(p.toHex(true), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point#toHex(compressed)', () => {
|
||||||
|
for (const vector of points.valid.pointCompress) {
|
||||||
|
const { P, compress, expected } = vector;
|
||||||
|
let p = secp.Point.fromHex(P);
|
||||||
|
deepStrictEqual(p.toHex(compress), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point#toHex() roundtrip (failed case)', () => {
|
||||||
|
const point1 =
|
||||||
|
secp.Point.fromPrivateKey(
|
||||||
|
88572218780422190464634044548753414301110513745532121983949500266768436236425n
|
||||||
|
);
|
||||||
|
// const hex = point1.toHex(true);
|
||||||
|
// deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point#toHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (x) => {
|
||||||
|
const point1 = secp.Point.fromPrivateKey(x);
|
||||||
|
const hex = point1.toHex(true);
|
||||||
|
deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point#add(other)', () => {
|
||||||
|
for (const vector of points.valid.pointAdd) {
|
||||||
|
const { P, Q, expected } = vector;
|
||||||
|
let p = secp.Point.fromHex(P);
|
||||||
|
let q = secp.Point.fromHex(Q);
|
||||||
|
if (expected) {
|
||||||
|
deepStrictEqual(p.add(q).toHex(true), expected);
|
||||||
|
} else {
|
||||||
|
if (!p.equals(q.negate())) {
|
||||||
|
throws(() => p.add(q).toHex(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Point#multiply(privateKey)', () => {
|
||||||
|
for (const vector of points.valid.pointMultiply) {
|
||||||
|
const { P, d, expected } = vector;
|
||||||
|
const p = secp.Point.fromHex(P);
|
||||||
|
if (expected) {
|
||||||
|
deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected);
|
||||||
|
} else {
|
||||||
|
throws(() => {
|
||||||
|
p.multiply(hexToNumber(d)).toHex(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const vector of points.invalid.pointMultiply) {
|
||||||
|
const { P, d } = vector;
|
||||||
|
if (hexToNumber(d) < secp.CURVE.n) {
|
||||||
|
throws(() => {
|
||||||
|
const p = secp.Point.fromHex(P);
|
||||||
|
p.multiply(hexToNumber(d)).toHex(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const num of [0n, 0, -1n, -1, 1.1]) {
|
||||||
|
throws(() => secp.Point.BASE.multiply(num));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// multiply() should equal multiplyUnsafe()
|
||||||
|
// should('JacobianPoint#multiplyUnsafe', () => {
|
||||||
|
// const p0 = new secp.JacobianPoint(
|
||||||
|
// 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
||||||
|
// 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
||||||
|
// 1n
|
||||||
|
// );
|
||||||
|
// const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n;
|
||||||
|
// console.log(p0.multiply(z));
|
||||||
|
// console.log(secp.JacobianPoint.normalizeZ([p0.multiplyUnsafe(z)])[0])
|
||||||
|
// });
|
||||||
|
|
||||||
|
should('secp256k1.Signature.fromCompactHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
|
const sig = new secp.Signature(r, s);
|
||||||
|
deepStrictEqual(secp.Signature.fromCompact(sig.toCompactHex()), sig);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.Signature.fromDERHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
|
const sig = new secp.Signature(r, s);
|
||||||
|
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', async () => {
|
||||||
|
for (const vector of ecdsa.valid) {
|
||||||
|
let usig = await secp.sign(vector.m, vector.d);
|
||||||
|
let sig = (usig.toCompactHex());
|
||||||
|
const vsig = vector.signature;
|
||||||
|
deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
|
||||||
|
deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should(
|
||||||
|
'secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979',
|
||||||
|
async () => {
|
||||||
|
for (const vector of ecdsa.invalid.sign) {
|
||||||
|
throws(() => {
|
||||||
|
return secp.sign(vector.m, vector.d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
should('secp256k1.sign()/edge cases', () => {
|
||||||
|
// @ts-ignore
|
||||||
|
rejects(async () => await secp.sign());
|
||||||
|
// @ts-ignore
|
||||||
|
rejects(async () => await secp.sign(''));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', async () => {
|
||||||
|
const CASES = [
|
||||||
|
[
|
||||||
|
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
|
||||||
|
'304402203de2559fccb00c148574997f660e4d6f40605acc71267ee38101abf15ff467af02200950abdf40628fd13f547792ba2fc544681a485f2fdafb5c3b909a4df7350e6b',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'5f97983254982546d3976d905c6165033976ee449d300d0e382099fa74deaf82',
|
||||||
|
'3045022100c046d9ff0bd2845b9aa9dff9f997ecebb31e52349f80fe5a5a869747d31dcb88022011f72be2a6d48fe716b825e4117747b397783df26914a58139c3f4c5cbb0e66c',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'0d7017a96b97cd9be21cf28aada639827b2814a654a478c81945857196187808',
|
||||||
|
'3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
|
||||||
|
for (let [msg, exp] of CASES) {
|
||||||
|
const res = await secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||||
|
deepStrictEqual((res.toDERHex()), exp);
|
||||||
|
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
||||||
|
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.sign()/sign ecdsa extraData', async () => {
|
||||||
|
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
|
||||||
|
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
|
||||||
|
const ent4 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
||||||
|
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
||||||
|
|
||||||
|
for (const e of ecdsa.extraEntropy) {
|
||||||
|
const sign = async (extraEntropy) => {
|
||||||
|
const s = secp.sign(e.m, e.d, {extraEntropy }).toCompactHex();
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
deepStrictEqual(await sign(), e.signature);
|
||||||
|
deepStrictEqual(await sign(ent1), e.extraEntropy0);
|
||||||
|
deepStrictEqual(await sign(ent2), e.extraEntropy1);
|
||||||
|
deepStrictEqual(await sign(ent3), e.extraEntropyRand);
|
||||||
|
deepStrictEqual(await sign(ent4), e.extraEntropyN);
|
||||||
|
deepStrictEqual(await sign(ent5), e.extraEntropyMax);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.verify()/should verify signature', async () => {
|
||||||
|
const MSG = '01'.repeat(32);
|
||||||
|
const PRIV_KEY = 0x2n;
|
||||||
|
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||||
|
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||||
|
deepStrictEqual(publicKey.length, 65);
|
||||||
|
deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
|
||||||
|
});
|
||||||
|
should('secp256k1.verify()/should not verify signature with wrong public key', async () => {
|
||||||
|
const MSG = '01'.repeat(32);
|
||||||
|
const PRIV_KEY = 0x2n;
|
||||||
|
const WRONG_PRIV_KEY = 0x22n;
|
||||||
|
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||||
|
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
|
||||||
|
deepStrictEqual(publicKey.length, 130);
|
||||||
|
deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
|
||||||
|
});
|
||||||
|
should('secp256k1.verify()/should not verify signature with wrong hash', async () => {
|
||||||
|
const MSG = '01'.repeat(32);
|
||||||
|
const PRIV_KEY = 0x2n;
|
||||||
|
const WRONG_MSG = '11'.repeat(32);
|
||||||
|
const signature = await secp.sign(MSG, PRIV_KEY);
|
||||||
|
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||||
|
deepStrictEqual(publicKey.length, 65);
|
||||||
|
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
|
||||||
|
});
|
||||||
|
should('secp256k1.verify()/should verify random signatures', async () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.asyncProperty(
|
||||||
|
FC_BIGINT,
|
||||||
|
fc.hexaString({ minLength: 64, maxLength: 64 }),
|
||||||
|
async (privKey, msg) => {
|
||||||
|
const pub = secp.getPublicKey(privKey);
|
||||||
|
const sig = await secp.sign(msg, privKey);
|
||||||
|
deepStrictEqual(secp.verify(sig, msg, pub), true);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should('secp256k1.verify()/should not verify signature with invalid r/s', () => {
|
||||||
|
const msg = new Uint8Array([
|
||||||
|
0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1, 0xe3,
|
||||||
|
0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60, 0x50, 0x23,
|
||||||
|
]);
|
||||||
|
const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n;
|
||||||
|
const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n;
|
||||||
|
const r = 1n;
|
||||||
|
const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n;
|
||||||
|
|
||||||
|
const pub = new secp.Point(x, y);
|
||||||
|
const signature = new secp.Signature(2n, 2n);
|
||||||
|
// @ts-ignore
|
||||||
|
signature.r = r;
|
||||||
|
// @ts-ignore
|
||||||
|
signature.s = s;
|
||||||
|
|
||||||
|
const verified = secp.verify(signature, msg, pub);
|
||||||
|
// Verifies, but it shouldn't, because signature S > curve order
|
||||||
|
deepStrictEqual(verified, false);
|
||||||
|
});
|
||||||
|
should('secp256k1.verify()/should not verify msg = curve order', async () => {
|
||||||
|
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
||||||
|
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
|
||||||
|
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
|
||||||
|
const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n;
|
||||||
|
const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n;
|
||||||
|
const pub = new secp.Point(x, y);
|
||||||
|
const sig = new secp.Signature(r, s);
|
||||||
|
deepStrictEqual(secp.verify(sig, msg, pub), false);
|
||||||
|
});
|
||||||
|
should('secp256k1.verify()/should verify non-strict msg bb5a...', async () => {
|
||||||
|
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
|
||||||
|
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
|
||||||
|
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
|
||||||
|
const r = 432420386565659656852420866390673177323n;
|
||||||
|
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
||||||
|
const pub = new secp.Point(x, y);
|
||||||
|
const sig = new secp.Signature(r, s);
|
||||||
|
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
|
||||||
|
});
|
||||||
|
should(
|
||||||
|
'secp256k1.verify()/should not verify invalid deterministic signatures with RFC 6979',
|
||||||
|
() => {
|
||||||
|
for (const vector of ecdsa.invalid.verify) {
|
||||||
|
const res = secp.verify(vector.signature, vector.m, vector.Q);
|
||||||
|
deepStrictEqual(res, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// describe('schnorr', () => {
|
||||||
|
// // index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||||
|
// const vectors = schCsv
|
||||||
|
// .split('\n')
|
||||||
|
// .map((line: string) => line.split(','))
|
||||||
|
// .slice(1, -1);
|
||||||
|
// for (let vec of vectors) {
|
||||||
|
// const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||||
|
// it(`should sign with Schnorr scheme vector ${index}`, async () => {
|
||||||
|
// if (sec) {
|
||||||
|
// expect(hex(secp.schnorr.getPublicKey(sec))).toBe(pub.toLowerCase());
|
||||||
|
// const sig = await secp.schnorr.sign(msg, sec, rnd);
|
||||||
|
// const sigS = secp.schnorr.signSync(msg, sec, rnd);
|
||||||
|
// expect(hex(sig)).toBe(expSig.toLowerCase());
|
||||||
|
// expect(hex(sigS)).toBe(expSig.toLowerCase());
|
||||||
|
// expect(await secp.schnorr.verify(sigS, msg, pub)).toBe(true);
|
||||||
|
// expect(secp.schnorr.verifySync(sig, msg, pub)).toBe(true);
|
||||||
|
// } else {
|
||||||
|
// const passed = await secp.schnorr.verify(expSig, msg, pub);
|
||||||
|
// const passedS = secp.schnorr.verifySync(expSig, msg, pub);
|
||||||
|
// if (passes === 'TRUE') {
|
||||||
|
// expect(passed).toBeTruthy();
|
||||||
|
// expect(passedS).toBeTruthy();
|
||||||
|
// } else {
|
||||||
|
// expect(passed).toBeFalsy();
|
||||||
|
// expect(passedS).toBeFalsy();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', async () => {
|
||||||
|
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||||
|
const privateKey = 123456789n;
|
||||||
|
const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
|
||||||
|
const sig = await secp.sign(message, privateKey);
|
||||||
|
const recoveredPubkey = sig.recoverPublicKey(message);
|
||||||
|
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
|
||||||
|
deepStrictEqual(recoveredPubkey !== null, true);
|
||||||
|
deepStrictEqual((recoveredPubkey).toHex(), publicKey);
|
||||||
|
deepStrictEqual(secp.verify(sig, message, publicKey), true);
|
||||||
|
});
|
||||||
|
should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
||||||
|
const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
|
||||||
|
const sig =
|
||||||
|
'79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9';
|
||||||
|
const recovery = 0;
|
||||||
|
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
|
||||||
|
});
|
||||||
|
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', async () => {
|
||||||
|
const privKey = secp.utils.randomPrivateKey();
|
||||||
|
const pub = secp.getPublicKey(privKey);
|
||||||
|
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
const sig = await secp.sign(zeros, privKey, { recovered: true });
|
||||||
|
const recoveredKey = sig.recoverPublicKey(zeros);
|
||||||
|
deepStrictEqual(recoveredKey.toRawBytes(), pub);
|
||||||
|
});
|
||||||
|
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', async () => {
|
||||||
|
for (const vector of ecdsa.valid) {
|
||||||
|
if (secp.utils.mod(hexToNumber(vector.m), secp.CURVE.n) === 0n) continue;
|
||||||
|
let usig = secp.sign(vector.m, vector.d);
|
||||||
|
let sig = (usig).toDERHex();
|
||||||
|
const vpub = secp.getPublicKey(vector.d);
|
||||||
|
const recovered = usig.recoverPublicKey(vector.m);
|
||||||
|
deepStrictEqual((recovered).toHex(), hex(vpub));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Real implementation.
|
||||||
|
function derToPub(der) {
|
||||||
|
return der.slice(46);
|
||||||
|
}
|
||||||
|
should('secp256k1.getSharedSecret()/should produce correct results', () => {
|
||||||
|
// TODO: Once der is there, run all tests.
|
||||||
|
for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) {
|
||||||
|
if (vector.result === 'invalid' || vector.private.length !== 64) {
|
||||||
|
// We support eth-like hexes
|
||||||
|
if (vector.private.length < 64) continue;
|
||||||
|
throws(() => {
|
||||||
|
secp.getSharedSecret(vector.private, derToPub(vector.public), true);
|
||||||
|
});
|
||||||
|
} else if (vector.result === 'valid') {
|
||||||
|
const res = secp.getSharedSecret(vector.private, derToPub(vector.public), true);
|
||||||
|
deepStrictEqual(hex(res.slice(1)), `${vector.shared}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.getSharedSecret()/priv/pub order matters', () => {
|
||||||
|
for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) {
|
||||||
|
if (vector.result === 'valid') {
|
||||||
|
let priv = vector.private;
|
||||||
|
priv = priv.length === 66 ? priv.slice(2) : priv;
|
||||||
|
throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.getSharedSecret()/rejects invalid keys', () => {
|
||||||
|
throws(() => secp.getSharedSecret('01', '02'));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.utils.isValidPrivateKey()', () => {
|
||||||
|
for (const vector of privates.valid.isPrivate) {
|
||||||
|
const { d, expected } = vector;
|
||||||
|
deepStrictEqual(secp.utils.isValidPrivateKey(d), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const normal = secp.utils._normalizePrivateKey;
|
||||||
|
const tweakUtils = {
|
||||||
|
privateAdd: (privateKey, tweak) => {
|
||||||
|
const p = normal(privateKey);
|
||||||
|
const t = normal(tweak);
|
||||||
|
return secp.utils._bigintToBytes(secp.utils.mod(p + t, secp.CURVE.n));
|
||||||
|
},
|
||||||
|
|
||||||
|
privateNegate: (privateKey) => {
|
||||||
|
const p = normal(privateKey);
|
||||||
|
return secp.utils._bigintToBytes(secp.CURVE.n - p);
|
||||||
|
},
|
||||||
|
|
||||||
|
pointAddScalar: (p, tweak, isCompressed) => {
|
||||||
|
const P = secp.Point.fromHex(p);
|
||||||
|
const t = normal(tweak);
|
||||||
|
const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n);
|
||||||
|
if (!Q) throw new Error('Tweaked point at infinity');
|
||||||
|
return Q.toRawBytes(isCompressed);
|
||||||
|
},
|
||||||
|
|
||||||
|
pointMultiply: (p, tweak, isCompressed) => {
|
||||||
|
const P = secp.Point.fromHex(p);
|
||||||
|
const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak);
|
||||||
|
const t = BigInt(`0x${h}`);
|
||||||
|
return P.multiply(t).toRawBytes(isCompressed);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
should('secp256k1.privateAdd()', () => {
|
||||||
|
for (const vector of privates.valid.add) {
|
||||||
|
const { a, b, expected } = vector;
|
||||||
|
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.privateNegate()', () => {
|
||||||
|
for (const vector of privates.valid.negate) {
|
||||||
|
const { a, expected } = vector;
|
||||||
|
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.pointAddScalar()', () => {
|
||||||
|
for (const vector of points.valid.pointAddScalar) {
|
||||||
|
const { description, P, d, expected } = vector;
|
||||||
|
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
|
||||||
|
deepStrictEqual(bytesToHex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.pointAddScalar() invalid', () => {
|
||||||
|
for (const vector of points.invalid.pointAddScalar) {
|
||||||
|
const { P, d, exception } = vector;
|
||||||
|
throws(() => tweakUtils.pointAddScalar(P, d));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.pointMultiply()', () => {
|
||||||
|
for (const vector of points.valid.pointMultiply) {
|
||||||
|
const { P, d, expected } = vector;
|
||||||
|
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('secp256k1.pointMultiply() invalid', () => {
|
||||||
|
for (const vector of points.invalid.pointMultiply) {
|
||||||
|
const { P, d, exception } = vector;
|
||||||
|
throws(() => tweakUtils.pointMultiply(P, d));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.wychenproof vectors', () => {
|
||||||
|
for (let group of wp.testGroups) {
|
||||||
|
const pubKey = secp.Point.fromHex(group.key.uncompressed);
|
||||||
|
for (let test of group.tests) {
|
||||||
|
const m = secp.CURVE.hash(hexToBytes(test.msg));
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
const verified = secp.verify(test.sig, m, pubKey);
|
||||||
|
if (secp.Signature.fromDER(test.sig).hasHighS()) {
|
||||||
|
deepStrictEqual(verified, false);
|
||||||
|
} else {
|
||||||
|
deepStrictEqual(verified, true);
|
||||||
|
}
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
const verified = secp.verify(test.sig, m, pubKey);
|
||||||
|
if (!verified) failed = true;
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true);
|
||||||
|
} else {
|
||||||
|
deepStrictEqual(false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should.run();
|
||||||
200
curve-definitions/test/starknet/basic.test.js
Normal file
200
curve-definitions/test/starknet/basic.test.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as starknet from '../../lib/starknet.js';
|
||||||
|
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
should('Basic elliptic sanity check', () => {
|
||||||
|
const g1 = starknet.Point.BASE;
|
||||||
|
deepStrictEqual(
|
||||||
|
g1.x.toString(16),
|
||||||
|
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
g1.y.toString(16),
|
||||||
|
'5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f'
|
||||||
|
);
|
||||||
|
const g2 = g1.double();
|
||||||
|
deepStrictEqual(
|
||||||
|
g2.x.toString(16),
|
||||||
|
'759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
g2.y.toString(16),
|
||||||
|
'6f524a3400e7708d5c01a28598ad272e7455aa88778b19f93b562d7a9646c41'
|
||||||
|
);
|
||||||
|
const g3 = g2.add(g1);
|
||||||
|
deepStrictEqual(
|
||||||
|
g3.x.toString(16),
|
||||||
|
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
g3.y.toString(16),
|
||||||
|
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
|
||||||
|
);
|
||||||
|
const g32 = g1.multiply(3);
|
||||||
|
deepStrictEqual(
|
||||||
|
g32.x.toString(16),
|
||||||
|
'411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
g32.y.toString(16),
|
||||||
|
'7e1b3ebac08924d2c26f409549191fcf94f3bf6f301ed3553e22dfb802f0686'
|
||||||
|
);
|
||||||
|
const minus1 = g1.multiply(starknet.CURVE.n - 1n);
|
||||||
|
deepStrictEqual(
|
||||||
|
minus1.x.toString(16),
|
||||||
|
'1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
minus1.y.toString(16),
|
||||||
|
'7a997f9f55b68e04841b7fe20b9139d21ac132ee541bc5cd78cfff3c91723e2'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Pedersen', () => {
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.pedersen(2, 3),
|
||||||
|
'0x5774fa77b3d843ae9167abd61cf80365a9b2b02218fc2f628494b5bdc9b33b8'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.pedersen(1, 2),
|
||||||
|
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.pedersen(3, 4),
|
||||||
|
'0x262697b88544f733e5c6907c3e1763131e9f14c51ee7951258abbfb29415fbf'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Hash chain', () => {
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.hashChain([1, 2, 3]),
|
||||||
|
'0x5d9d62d4040b977c3f8d2389d494e4e89a96a8b45c44b1368f1cc6ec5418915'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Pedersen hash edgecases', () => {
|
||||||
|
// >>> pedersen_hash(0,0)
|
||||||
|
const zero = '0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804';
|
||||||
|
deepStrictEqual(starknet.pedersen(0, 0), zero);
|
||||||
|
deepStrictEqual(starknet.pedersen(0n, 0n), zero);
|
||||||
|
deepStrictEqual(starknet.pedersen('0', '0'), zero);
|
||||||
|
deepStrictEqual(starknet.pedersen('0x0', '0x0'), zero);
|
||||||
|
// >>> pedersen_hash(3618502788666131213697322783095070105623107215331596699973092056135872020475,3618502788666131213697322783095070105623107215331596699973092056135872020475)
|
||||||
|
// 3226051580231087455100099637526672350308978851161639703631919449959447036451
|
||||||
|
const big = 3618502788666131213697322783095070105623107215331596699973092056135872020475n;
|
||||||
|
const bigExp = '0x721e167a36655994e88efa865e2ed8a0488d36db4d988fec043cda755728223';
|
||||||
|
deepStrictEqual(starknet.pedersen(big, big), bigExp);
|
||||||
|
// >= FIELD
|
||||||
|
const big2 = 36185027886661312136973227830950701056231072153315966999730920561358720204751n;
|
||||||
|
throws(() => starknet.pedersen(big2, big2), 'big2');
|
||||||
|
|
||||||
|
// FIELD -1
|
||||||
|
const big3 = 3618502788666131213697322783095070105623107215331596699973092056135872020480n;
|
||||||
|
const big3exp = '0x7258fccaf3371fad51b117471d9d888a1786c5694c3e6099160477b593a576e';
|
||||||
|
deepStrictEqual(starknet.pedersen(big3, big3), big3exp, 'big3');
|
||||||
|
// FIELD
|
||||||
|
const big4 = 3618502788666131213697322783095070105623107215331596699973092056135872020481n;
|
||||||
|
throws(() => starknet.pedersen(big4, big4), 'big4');
|
||||||
|
throws(() => starknet.pedersen(-1, -1), 'neg');
|
||||||
|
throws(() => starknet.pedersen(false, false), 'false');
|
||||||
|
throws(() => starknet.pedersen(true, true), 'true');
|
||||||
|
throws(() => starknet.pedersen(10.1, 10.1), 'float');
|
||||||
|
});
|
||||||
|
|
||||||
|
should('hashChain edgecases', () => {
|
||||||
|
deepStrictEqual(starknet.hashChain([32312321312321312312312321n]), '0x1aba6672c014b4838cc201');
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.hashChain([1n, 2n]),
|
||||||
|
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.hashChain([1, 2]),
|
||||||
|
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
||||||
|
);
|
||||||
|
throws(() => starknet.hashChain([]));
|
||||||
|
throws(() => starknet.hashChain('123'));
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.hashChain([1, 2]),
|
||||||
|
'0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Pedersen hash, issue #2', () => {
|
||||||
|
// Verified with starnet.js
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.computeHashOnElements(issue2),
|
||||||
|
'0x22064462ea33a6ce5272a295e0f551c5da3834f80d8444e7a4df68190b1bc42'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.computeHashOnElements([]),
|
||||||
|
'0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.computeHashOnElements([1]),
|
||||||
|
'0x78d74f61aeaa8286418fd34b3a12a610445eba11d00ecc82ecac2542d55f7a4'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
import * as bip32 from '@scure/bip32';
|
||||||
|
import * as bip39 from '@scure/bip39';
|
||||||
|
|
||||||
|
should('Seed derivation (example)', () => {
|
||||||
|
const layer = 'starkex';
|
||||||
|
const application = 'starkdeployement';
|
||||||
|
const mnemonic =
|
||||||
|
'range mountain blast problem vibrant void vivid doctor cluster enough melody ' +
|
||||||
|
'salt layer language laptop boat major space monkey unit glimpse pause change vibrant';
|
||||||
|
const ethAddress = '0xa4864d977b944315389d1765ffa7e66F74ee8cd7';
|
||||||
|
const hdKey = bip32.HDKey.fromMasterSeed(bip39.mnemonicToSeedSync(mnemonic)).derive(
|
||||||
|
starknet.getAccountPath(layer, application, ethAddress, 0)
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.grindKey(hdKey.privateKey),
|
||||||
|
'6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Compressed keys', () => {
|
||||||
|
const G = starknet.Point.BASE;
|
||||||
|
const half = starknet.CURVE.n / 2n;
|
||||||
|
const last = starknet.CURVE.n;
|
||||||
|
const vectors = [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
half - 5n,
|
||||||
|
half - 4n,
|
||||||
|
half - 3n,
|
||||||
|
half - 2n,
|
||||||
|
half - 1n,
|
||||||
|
half,
|
||||||
|
half + 1n,
|
||||||
|
half + 2n,
|
||||||
|
half + 3n,
|
||||||
|
half + 4n,
|
||||||
|
half + 5n,
|
||||||
|
last - 5n,
|
||||||
|
last - 4n,
|
||||||
|
last - 3n,
|
||||||
|
last - 2n,
|
||||||
|
last - 1n,
|
||||||
|
].map((i) => G.multiply(i));
|
||||||
|
const fixPoint = (pt) => ({ ...pt, _WINDOW_SIZE: undefined });
|
||||||
|
for (const v of vectors) {
|
||||||
|
const uncompressed = v.toHex();
|
||||||
|
const compressed = v.toHex(true);
|
||||||
|
const exp = fixPoint(v);
|
||||||
|
deepStrictEqual(fixPoint(starknet.Point.fromHex(uncompressed)), exp);
|
||||||
|
deepStrictEqual(fixPoint(starknet.Point.fromHex(compressed)), exp);
|
||||||
|
deepStrictEqual(starknet.Point.fromHex(compressed).toHex(), uncompressed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
57
curve-definitions/test/starknet/benchmark/index.js
Normal file
57
curve-definitions/test/starknet/benchmark/index.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as microStark from '../../../lib/starknet.js';
|
||||||
|
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
||||||
|
import * as bench from 'micro-bmark';
|
||||||
|
const { run, mark } = bench; // or bench.mark
|
||||||
|
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
const publicKeyMicro = microStark.getPublicKey(privateKey);
|
||||||
|
|
||||||
|
const FNS = {
|
||||||
|
pedersenHash: {
|
||||||
|
samples: 250,
|
||||||
|
starkware: () =>
|
||||||
|
starkwareCrypto.default.pedersen([
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
|
||||||
|
]),
|
||||||
|
'micro-starknet': () =>
|
||||||
|
microStark.pedersen(
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
signVerify: {
|
||||||
|
samples: 500,
|
||||||
|
starkware: () =>
|
||||||
|
starkwareCrypto.default.verify(
|
||||||
|
publicKeyStark,
|
||||||
|
msgHash,
|
||||||
|
starkwareCrypto.default.sign(keyPair, msgHash)
|
||||||
|
),
|
||||||
|
'micro-starknet': () =>
|
||||||
|
microStark.verify(microStark.sign(msgHash, privateKey), msgHash, publicKeyMicro),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = () =>
|
||||||
|
run(async () => {
|
||||||
|
for (let [k, libs] of Object.entries(FNS)) {
|
||||||
|
console.log(`==== ${k} ====`);
|
||||||
|
for (const [lib, fn] of Object.entries(libs)) {
|
||||||
|
if (lib === 'samples') continue;
|
||||||
|
let title = `${k} (${lib})`;
|
||||||
|
await mark(title, libs.samples, () => fn());
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
// Log current RAM
|
||||||
|
bench.logMem();
|
||||||
|
});
|
||||||
|
|
||||||
|
main();
|
||||||
19
curve-definitions/test/starknet/benchmark/package.json
Normal file
19
curve-definitions/test/starknet/benchmark/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "benchmark",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "benchmarks",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"bench": "node index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@starkware-industries/starkware-crypto-utils": "^0.0.2",
|
||||||
|
"micro-bmark": "0.2.0",
|
||||||
|
"micro-should": "0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1306
curve-definitions/test/starknet/fixtures/issue2.json
Normal file
1306
curve-definitions/test/starknet/fixtures/issue2.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"0x1": "0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca",
|
||||||
|
"0x2": "0x759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5",
|
||||||
|
"0x3": "0x411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20",
|
||||||
|
"0x4": "0xa7da05a4d664859ccd6e567b935cdfbfe3018c7771cb980892ef38878ae9bc",
|
||||||
|
"0x5": "0x788435d61046d3eec54d77d25bd194525f4fa26ebe6575536bc6f656656b74c",
|
||||||
|
"0x6": "0x1efc3d7c9649900fcbd03f578a8248d095bc4b6a13b3c25f9886ef971ff96fa",
|
||||||
|
"0x7": "0x743829e0a179f8afe223fc8112dfc8d024ab6b235fd42283c4f5970259ce7b7",
|
||||||
|
"0x8": "0x6eeee2b0c71d681692559735e08a2c3ba04e7347c0c18d4d49b83bb89771591",
|
||||||
|
"0x9": "0x216b4f076ff47e03a05032d1c6ee17933d8de8b2b4c43eb5ad5a7e1b25d3849",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000000": "0x5c79074e7f7b834c12c81a9bb0d46691a5e7517767a849d9d98cb84e2176ed2",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000001": "0x1c4f24e3bd16db0e2457bc005a9d61965105a535554c6b338871e34cb8e2d3a",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000002": "0xdfbb89b39288a9ddacf3942b4481b04d4fa2f8ed3c424757981cc6357f27ac",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000003": "0x41bef28265fd750b102f4f2d1e0231de7f4a33900a214f191a63d4fec4e72f4",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000004": "0x24de66eb164797d4b414e81ded0cfa1a592ef0a9363ebbcb440d4d03cb18af1",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000005": "0x5efb18c3bc9b69003746acc85fb6ee0cfbdc6adfb982f089cc63e1e5495daad",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000006": "0x10dc71f00918a8ebfe4085c834d41dd22b251b9f81eef8b9a4fab77e7e1afe9",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000007": "0x4267ebfd379b1c8caae73febc5920b0c95bd6f9f3536f47c5ddad1259c332ff",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000008": "0x6da515118c8e01fd5b2e96b814ee95bad7d60be4d2ba6b47e0d283f579d9671",
|
||||||
|
"0x800000000000000000000000000000000000000000000000000000000000009": "0x7a5b4797f4e56ed1473876bc2693fbe3f2fef7e050717cbae924ff23d426052",
|
||||||
|
"0x2e9c99d8382fa004dcbbee720aef8a97002de0e991f6a8344e6dc636a71b59e": "0x1ff6803ae740e7e596504ac5c6afbea472e53679361e214f12be0155b13e25d",
|
||||||
|
"0x8620458785138df8722214e073a91b8f55076ea78197cf41007692dd27fd90": "0x5967da40b90d7ca1e36dc4024381d7d4b403c6ac1a0ab358b0743984934a805",
|
||||||
|
"0x1b920e7dfb49ba5ada673882af5342e7448d3e9335e0ac37feb6280cd7289ce": "0x78c7ab46333968fbde3201cf512c1eeb5529360259072c459a158dee4449b57",
|
||||||
|
"0x704170dbfd5dc63caef69d2ce6dfc2b2dbb2af6e75851242bbe79fb6e62a118": "0x534bd8d6ebe4bb2f6992e2d7c19ef3146247e10c2849f357e44eddd283b2af6",
|
||||||
|
"0x4b58bf4228f39550eca59b5c96a0cb606036cc9495eef9a546f24f01b1b7829": "0x1097a8c5a46d94596f1c8e70ca66941f2bb11e3c8d4fd58fdc4589f09965be8",
|
||||||
|
"0x2e93226c90fb7a2381a24e940a94b98433e3553dcbf745d3f54d62963c75604": "0x369f0e8c8e984f244290267393a004dba435a4df091767ad5063fece7b1884c",
|
||||||
|
"0x4615f94598cd756ad1a551d7e57fd725916adfd0054eb773ceb482eef87d0b2": "0x1ee5b8d612102a2408cde59ce52a6498d2e38fe8789bb26d400dea310684ec9",
|
||||||
|
"0x6ade54b7debd7ca1d4e8e932f9545f8fa4024d73be1efcc86df86367fc333f8": "0x37de3bf52412b2fb9b0030d232ca9dd921cd8f71fd67975cdc62546826e121",
|
||||||
|
"0x618e7467dd24c2a3449c4df640439c12cdd0f8ea779afcee6e252b2cf494354": "0x71c2b578c432f2d305d3808bb645ecc46dd670cb43d4f4a076f75ccbff74fbc",
|
||||||
|
"0x7eae185e1f41ec76d214d763f0592f194933622a9dd5f3d52d0209f71619c1a": "0x2b0160052e70176e5b0ff2a6eff90896ae07b732fc27219e36e077735abd57e",
|
||||||
|
"0x178047D3869489C055D7EA54C014FFB834A069C9595186ABE04EA4D1223A03F": "0x1895a6a77ae14e7987b9cb51329a5adfb17bd8e7c638f92d6892d76e51cebcf"
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"private_key": "0x3c1e9550e66958296d11b60f8e8e7a7ad990d07fa65d5f7652c4a6c87d4e3cc",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"hash": "0x1",
|
||||||
|
"r": "3162358736122783857144396205516927012128897537504463716197279730251407200037",
|
||||||
|
"s": "1447067116407676619871126378936374427636662490882969509559888874644844560850"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hash": "0x11",
|
||||||
|
"r": "2282960348362869237018441985726545922711140064809058182483721438101695251648",
|
||||||
|
"s": "2905868291002627709651322791912000820756370440695830310841564989426104902684"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x223",
|
||||||
|
"r": "2851492577225522862152785068304516872062840835882746625971400995051610132955",
|
||||||
|
"s": "2227464623243182122770469099770977514100002325017609907274766387592987135410"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x9999",
|
||||||
|
"r": "3551214266795401081823453828727326248401688527835302880992409448142527576296",
|
||||||
|
"s": "2580950807716503852408066180369610390914312729170066679103651110985466032285"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x387e76d1667c4454bfb835144120583af836f8e32a516765497d23eabe16b3f",
|
||||||
|
"r": "3518448914047769356425227827389998721396724764083236823647519654917215164512",
|
||||||
|
"s": "3042321032945513635364267149196358883053166552342928199041742035443537684462"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x3a7e76d1697c4455bfb835144120283af236f8e32a516765497d23eabe16b2",
|
||||||
|
"r": "2261926635950780594216378185339927576862772034098248230433352748057295357217",
|
||||||
|
"s": "2708700003762962638306717009307430364534544393269844487939098184375356178572"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0xfa5f0cd1ebff93c9e6474379a213ba111f9e42f2f1cb361b0327e0737203",
|
||||||
|
"r": "3016953906936760149710218073693613509330129567629289734816320774638425763370",
|
||||||
|
"s": "306146275372136078470081798635201810092238376869367156373203048583896337506"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x4c1e9550e66958296d11b60f8e8e7f7ae99dd0cfa6bd5fa652c1a6c87d4e2cc",
|
||||||
|
"r": "3562728603055564208884290243634917206833465920158600288670177317979301056463",
|
||||||
|
"s": "1958799632261808501999574190111106370256896588537275453140683641951899459876"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"hash": "0x6362b40c218fb4c8a8bd42ca482145e8513b78e00faa0de76a98ba14fc37ae8",
|
||||||
|
"r": "3485557127492692423490706790022678621438670833185864153640824729109010175518",
|
||||||
|
"s": "897592218067946175671768586886915961592526001156186496738437723857225288280"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
curve-definitions/test/starknet/index.test.js
Normal file
5
curve-definitions/test/starknet/index.test.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
import './basic.test.js';
|
||||||
|
import './starknet.test.js';
|
||||||
|
import './property.test.js';
|
||||||
|
|
||||||
51
curve-definitions/test/starknet/property.test.js
Normal file
51
curve-definitions/test/starknet/property.test.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as starknet from '../../lib/starknet.js';
|
||||||
|
import * as fc from 'fast-check';
|
||||||
|
|
||||||
|
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
|
||||||
|
|
||||||
|
should('Point#toHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (x) => {
|
||||||
|
const point1 = starknet.Point.fromPrivateKey(x);
|
||||||
|
const hex = point1.toHex(true);
|
||||||
|
deepStrictEqual(starknet.Point.fromHex(hex).toHex(true), hex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Signature.fromCompactHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
|
const sig = new starknet.Signature(r, s);
|
||||||
|
deepStrictEqual(starknet.Signature.fromCompact(sig.toCompactHex()), sig);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Signature.fromDERHex() roundtrip', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
|
const sig = new starknet.Signature(r, s);
|
||||||
|
deepStrictEqual(starknet.Signature.fromDER(sig.toDERHex()), sig);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('verify()/should verify random signatures', () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.asyncProperty(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privNum, msg) => {
|
||||||
|
const privKey = privNum.toString(16).padStart(64, '0');
|
||||||
|
const pub = starknet.getPublicKey(privKey);
|
||||||
|
const sig = starknet.sign(msg, privKey);
|
||||||
|
deepStrictEqual(starknet.verify(sig, msg, pub), true);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
286
curve-definitions/test/starknet/starknet.test.js
Normal file
286
curve-definitions/test/starknet/starknet.test.js
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import { hex, utf8 } from '@scure/base';
|
||||||
|
import * as bip32 from '@scure/bip32';
|
||||||
|
import * as bip39 from '@scure/bip39';
|
||||||
|
import * as starknet from '../../lib/starknet.js';
|
||||||
|
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' };
|
||||||
|
|
||||||
|
should('Starknet keccak', () => {
|
||||||
|
const value = starknet.keccak(utf8.decode('hello'));
|
||||||
|
deepStrictEqual(value, 0x8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8n);
|
||||||
|
deepStrictEqual(value < 2n ** 250n, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('RFC6979', () => {
|
||||||
|
for (const msg of sigVec.messages) {
|
||||||
|
const { r, s } = starknet.sign(msg.hash, sigVec.private_key);
|
||||||
|
// const { r, s } = starknet.Signature.fromDER(sig);
|
||||||
|
deepStrictEqual(r.toString(10), msg.r);
|
||||||
|
deepStrictEqual(s.toString(10), msg.s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Signatures', () => {
|
||||||
|
const vectors = [
|
||||||
|
{
|
||||||
|
// Message hash of length 61.
|
||||||
|
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
|
||||||
|
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
|
||||||
|
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Message hash of length 61, with leading zeros.
|
||||||
|
msg: '00c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47',
|
||||||
|
r: '5f496f6f210b5810b2711c74c15c05244dad43d18ecbbdbe6ed55584bc3b0a2',
|
||||||
|
s: '4e8657b153787f741a67c0666bad6426c3741b478c8eaa3155196fc571416f3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Message hash of length 62.
|
||||||
|
msg: 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a',
|
||||||
|
r: '233b88c4578f0807b4a7480c8076eca5cfefa29980dd8e2af3c46a253490e9c',
|
||||||
|
s: '28b055e825bc507349edfb944740a35c6f22d377443c34742c04e0d82278cf1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Message hash of length 63.
|
||||||
|
msg: '7465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47a1',
|
||||||
|
r: 'b6bee8010f96a723f6de06b5fa06e820418712439c93850dd4e9bde43ddf',
|
||||||
|
s: '1a3d2bc954ed77e22986f507d68d18115fa543d1901f5b4620db98e2f6efd80',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
||||||
|
const publicKey = starknet.getPublicKey(privateKey);
|
||||||
|
for (const v of vectors) {
|
||||||
|
const sig = starknet.sign(v.msg, privateKey);
|
||||||
|
const { r, s } = sig;
|
||||||
|
// const { r, s } = starknet.Signature.fromDER(sig);
|
||||||
|
deepStrictEqual(r.toString(16), v.r, 'r equality');
|
||||||
|
deepStrictEqual(s.toString(16), v.s, 's equality');
|
||||||
|
deepStrictEqual(starknet.verify(sig, v.msg, publicKey), true, 'verify');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Invalid signatures', () => {
|
||||||
|
/*
|
||||||
|
|
||||||
|
it('should not verify invalid signature inputs lengths', () => {
|
||||||
|
const ecOrder = starkwareCrypto.ec.n;
|
||||||
|
const {maxEcdsaVal} = starkwareCrypto;
|
||||||
|
const maxMsgHash = maxEcdsaVal.sub(oneBn);
|
||||||
|
const maxR = maxEcdsaVal.sub(oneBn);
|
||||||
|
const maxS = ecOrder.sub(oneBn).sub(oneBn);
|
||||||
|
const maxStarkKey = maxEcdsaVal.sub(oneBn);
|
||||||
|
|
||||||
|
// Test invalid message length.
|
||||||
|
expect(() =>
|
||||||
|
starkwareCrypto.verify(maxStarkKey, maxMsgHash.add(oneBn).toString(16), {
|
||||||
|
r: maxR,
|
||||||
|
s: maxS
|
||||||
|
})
|
||||||
|
).to.throw('Message not signable, invalid msgHash length.');
|
||||||
|
// Test invalid r length.
|
||||||
|
expect(() =>
|
||||||
|
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
|
||||||
|
r: maxR.add(oneBn),
|
||||||
|
s: maxS
|
||||||
|
})
|
||||||
|
).to.throw('Message not signable, invalid r length.');
|
||||||
|
// Test invalid w length.
|
||||||
|
expect(() =>
|
||||||
|
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
|
||||||
|
r: maxR,
|
||||||
|
s: maxS.add(oneBn)
|
||||||
|
})
|
||||||
|
).to.throw('Message not signable, invalid w length.');
|
||||||
|
// Test invalid s length.
|
||||||
|
expect(() =>
|
||||||
|
starkwareCrypto.verify(maxStarkKey, maxMsgHash.toString(16), {
|
||||||
|
r: maxR,
|
||||||
|
s: maxS.add(oneBn).add(oneBn)
|
||||||
|
})
|
||||||
|
).to.throw('Message not signable, invalid s length.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not verify invalid signatures', () => {
|
||||||
|
const privKey = generateRandomStarkPrivateKey();
|
||||||
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privKey, 'hex');
|
||||||
|
const keyPairPub = starkwareCrypto.ec.keyFromPublic(
|
||||||
|
keyPair.getPublic(),
|
||||||
|
'BN'
|
||||||
|
);
|
||||||
|
const msgHash = new BN(randomHexString(61));
|
||||||
|
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
|
||||||
|
|
||||||
|
// Test invalid public key.
|
||||||
|
const invalidKeyPairPub = starkwareCrypto.ec.keyFromPublic(
|
||||||
|
{x: keyPairPub.pub.getX().add(oneBn), y: keyPairPub.pub.getY()},
|
||||||
|
'BN'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
starkwareCrypto.verify(
|
||||||
|
invalidKeyPairPub,
|
||||||
|
msgHash.toString(16),
|
||||||
|
msgSignature
|
||||||
|
)
|
||||||
|
).to.be.false;
|
||||||
|
// Test invalid message.
|
||||||
|
expect(
|
||||||
|
starkwareCrypto.verify(
|
||||||
|
keyPair,
|
||||||
|
msgHash.add(oneBn).toString(16),
|
||||||
|
msgSignature
|
||||||
|
)
|
||||||
|
).to.be.false;
|
||||||
|
expect(
|
||||||
|
starkwareCrypto.verify(
|
||||||
|
keyPairPub,
|
||||||
|
msgHash.add(oneBn).toString(16),
|
||||||
|
msgSignature
|
||||||
|
)
|
||||||
|
).to.be.false;
|
||||||
|
// Test invalid r.
|
||||||
|
msgSignature.r.iadd(oneBn);
|
||||||
|
expect(starkwareCrypto.verify(keyPair, msgHash.toString(16), msgSignature))
|
||||||
|
.to.be.false;
|
||||||
|
expect(
|
||||||
|
starkwareCrypto.verify(keyPairPub, msgHash.toString(16), msgSignature)
|
||||||
|
).to.be.false;
|
||||||
|
// Test invalid s.
|
||||||
|
msgSignature.r.isub(oneBn);
|
||||||
|
msgSignature.s.iadd(oneBn);
|
||||||
|
expect(starkwareCrypto.verify(keyPair, msgHash.toString(16), msgSignature))
|
||||||
|
.to.be.false;
|
||||||
|
expect(
|
||||||
|
starkwareCrypto.verify(keyPairPub, msgHash.toString(16), msgSignature)
|
||||||
|
).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Pedersen', () => {
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.pedersen(
|
||||||
|
'0x3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'0x208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
|
||||||
|
),
|
||||||
|
'0x30e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.pedersen(
|
||||||
|
'0x58f580910a6ca59b28927c08fe6c43e2e303ca384badc365795fc645d479d45',
|
||||||
|
'0x78734f65a067be9bdb39de18434d71e79f7b6466a4b66bbd979ab9e7515fe0b'
|
||||||
|
),
|
||||||
|
'0x68cc0b76cddd1dd4ed2301ada9b7c872b23875d5ff837b3a87993e0d9996b87'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Hash chain', () => {
|
||||||
|
deepStrictEqual(starknet.hashChain([1, 2, 3]), starknet.pedersen(1, starknet.pedersen(2, 3)));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Key grinding', () => {
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.grindKey('86F3E7293141F20A8BAFF320E8EE4ACCB9D4A4BF2B4D295E8CEE784DB46E0519'),
|
||||||
|
'5c8c8683596c732541a59e03007b2d30dbbbb873556fe65b5fb63c16688f941'
|
||||||
|
);
|
||||||
|
// Loops more than once (verified manually)
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.grindKey('94F3E7293141F20A8BAFF320E8EE4ACCB9D4A4BF2B4D295E8CEE784DB46E0595'),
|
||||||
|
'33880b9aba464c1c01c9f8f5b4fc1134698f9b0a8d18505cab6cdd34d93dc02'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Private to stark key', () => {
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.getStarkKey('0x178047D3869489C055D7EA54C014FFB834A069C9595186ABE04EA4D1223A03F'),
|
||||||
|
'0x1895a6a77ae14e7987b9cb51329a5adfb17bd8e7c638f92d6892d76e51cebcf'
|
||||||
|
);
|
||||||
|
for (const [privKey, expectedPubKey] of Object.entries(precomputedKeys)) {
|
||||||
|
deepStrictEqual(starknet.getStarkKey(privKey), expectedPubKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Private stark key from eth signature', () => {
|
||||||
|
const ethSignature =
|
||||||
|
'0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a43' +
|
||||||
|
'70854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c';
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.ethSigToPrivate(ethSignature),
|
||||||
|
'766f11e90cd7c7b43085b56da35c781f8c067ac0d578eabdceebc4886435bda'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Key derivation', () => {
|
||||||
|
const layer = 'starkex';
|
||||||
|
const application = 'starkdeployement';
|
||||||
|
const mnemonic =
|
||||||
|
'range mountain blast problem vibrant void vivid doctor cluster enough melody ' +
|
||||||
|
'salt layer language laptop boat major space monkey unit glimpse pause change vibrant';
|
||||||
|
const ethAddress = '0xa4864d977b944315389d1765ffa7e66F74ee8cd7';
|
||||||
|
const VECTORS = [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/0",
|
||||||
|
privateKey: '6cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 7,
|
||||||
|
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/7",
|
||||||
|
privateKey: '341751bdc42841da35ab74d13a1372c1f0250617e8a2ef96034d9f46e6847af',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 598,
|
||||||
|
path: "m/2645'/579218131'/891216374'/1961790679'/2135936222'/598",
|
||||||
|
privateKey: '41a4d591a868353d28b7947eb132aa4d00c4a022743689ffd20a3628d6ca28c',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const hd = bip32.HDKey.fromMasterSeed(bip39.mnemonicToSeedSync(mnemonic));
|
||||||
|
for (const { index, path, privateKey } of VECTORS) {
|
||||||
|
const realPath = starknet.getAccountPath(layer, application, ethAddress, index);
|
||||||
|
deepStrictEqual(realPath, path);
|
||||||
|
deepStrictEqual(starknet.grindKey(hd.derive(realPath).privateKey), privateKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verified against starknet.js
|
||||||
|
should('Starknet.js cross-tests', () => {
|
||||||
|
const privateKey = '0x019800ea6a9a73f94aee6a3d2edf018fc770443e90c7ba121e8303ec6b349279';
|
||||||
|
// NOTE: there is no compressed keys here, getPubKey returns stark-key (which is schnorr-like X coordinate)
|
||||||
|
// But it is not used in signing/verifying
|
||||||
|
deepStrictEqual(
|
||||||
|
starknet.getStarkKey(privateKey),
|
||||||
|
'0x33f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d99745'
|
||||||
|
);
|
||||||
|
const msgHash = '0x6d1706bd3d1ba7c517be2a2a335996f63d4738e2f182144d078a1dd9997062e';
|
||||||
|
const sig = starknet.sign(msgHash, privateKey);
|
||||||
|
const { r, s } = (sig);
|
||||||
|
|
||||||
|
deepStrictEqual(
|
||||||
|
r.toString(),
|
||||||
|
'1427981024487605678086498726488552139932400435436186597196374630267616399345'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
s.toString(),
|
||||||
|
'1853664302719670721837677288395394946745467311923401353018029119631574115563'
|
||||||
|
);
|
||||||
|
const hashMsg2 = starknet.pedersen(
|
||||||
|
'0x33f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d99745',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
deepStrictEqual(hashMsg2, '0x2b0d4d43acce8ff68416f667f92ec7eab2b96f1d2224abd4d9d4d1e7fa4bb00');
|
||||||
|
const pubKey =
|
||||||
|
'04033f45f07e1bd1a51b45fc24ec8c8c9908db9e42191be9e169bfcac0c0d997450319d0f53f6ca077c4fa5207819144a2a4165daef6ee47a7c1d06c0dcaa3e456';
|
||||||
|
const sig2 = new starknet.Signature(
|
||||||
|
558858382392827003930138586379728730695763862039474863361948210004201119180n,
|
||||||
|
2440689354481625417078677634625227600823892606910345662891037256374285369343n
|
||||||
|
);
|
||||||
|
deepStrictEqual(starknet.verify(sig2.toDERHex(), hashMsg2, pubKey), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
4545
curve-definitions/test/vectors/ecdh.json
Normal file
4545
curve-definitions/test/vectors/ecdh.json
Normal file
File diff suppressed because it is too large
Load Diff
4474
curve-definitions/test/vectors/wychenproof.json
Normal file
4474
curve-definitions/test/vectors/wychenproof.json
Normal file
File diff suppressed because it is too large
Load Diff
25
curve-definitions/tsconfig.json
Normal file
25
curve-definitions/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"target": "es2020",
|
||||||
|
"lib": [
|
||||||
|
"es2020",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"module": "es6",
|
||||||
|
"moduleResolution": "node16",
|
||||||
|
"outDir": "lib",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module",
|
|
||||||
"sideEffects": false
|
|
||||||
}
|
|
||||||
1
index.js
Normal file
1
index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
throw new Error('Incorrect usage. Import submodules instead');
|
||||||
119
package-lock.json
generated
119
package-lock.json
generated
@ -1,119 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@tornado/noble-curves",
|
|
||||||
"version": "1.4.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "@tornado/noble-curves",
|
|
||||||
"version": "1.4.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "1.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@paulmillr/jsbt": "0.1.0",
|
|
||||||
"fast-check": "3.0.0",
|
|
||||||
"micro-bmark": "0.3.1",
|
|
||||||
"micro-should": "0.4.0",
|
|
||||||
"prettier": "3.1.1",
|
|
||||||
"typescript": "5.3.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@noble/hashes": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@paulmillr/jsbt": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@paulmillr/jsbt/-/jsbt-0.1.0.tgz",
|
|
||||||
"integrity": "sha512-TdowoHD36hkZARv6LW4jenkVTdK2vP0sy4ZM8E9MxaqAAIRdwmn3RlB+zWkEHi4hKTgLqMGkURfNkFtt0STX2Q==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"jsbt": "jsbt.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"prettier": "bin/prettier.cjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"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": "5.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
|
|
||||||
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
package.json
191
package.json
@ -1,182 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "@tornado/noble-curves",
|
"name": "@noble/curves",
|
||||||
"version": "1.4.0",
|
"version": "0.1.0",
|
||||||
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
"description": "Minimal, zero-dependency JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"abstract",
|
"index.js",
|
||||||
"esm",
|
"lib",
|
||||||
"src",
|
"lib/esm"
|
||||||
"*.js",
|
|
||||||
"*.js.map",
|
|
||||||
"*.d.ts",
|
|
||||||
"*.d.ts.map"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node hash-to-curve.js; node modular.js; node bls.js; node ristretto255.js; node decaf448.js",
|
"bench": "node test/benchmark/index.js",
|
||||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||||
"build:release": "cd build && npm i && npm run build",
|
"build:release": "rollup -c rollup.config.js",
|
||||||
"build:clean": "rm *.{js,d.ts,d.ts.map,js.map} esm/*.{js,d.ts,d.ts.map,js.map} 2> /dev/null",
|
"lint": "prettier --check 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
||||||
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
|
"format": "prettier --write 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
||||||
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
|
"test": "cd curve-definitions; node test/index.test.js"
|
||||||
"test": "node test/index.test.js"
|
|
||||||
},
|
},
|
||||||
"author": "Paul Miller (https://paulmillr.com)",
|
"author": "Paul Miller (https://paulmillr.com)",
|
||||||
"homepage": "https://paulmillr.com/noble/",
|
"homepage": "https://paulmillr.com/noble/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.tornado.ws/tornado-packages/noble-curvest"
|
"url": "https://github.com/paulmillr/noble-curves.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "1.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@paulmillr/jsbt": "0.1.0",
|
"@rollup/plugin-node-resolve": "13.3.0",
|
||||||
"fast-check": "3.0.0",
|
"micro-bmark": "0.2.0",
|
||||||
"micro-bmark": "0.3.1",
|
"micro-should": "0.2.0",
|
||||||
"micro-should": "0.4.0",
|
"prettier": "2.6.2",
|
||||||
"prettier": "3.1.1",
|
"rollup": "2.75.5",
|
||||||
"typescript": "5.3.2"
|
"typescript": "4.7.3"
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
"./modular": {
|
||||||
"types": "./index.d.ts",
|
"types": "./lib/modular.d.ts",
|
||||||
"import": "./esm/index.js",
|
"import": "./lib/esm/modular.js",
|
||||||
"default": "./index.js"
|
"default": "./lib/modular.js"
|
||||||
},
|
},
|
||||||
"./abstract/edwards": {
|
"./shortw": {
|
||||||
"types": "./abstract/edwards.d.ts",
|
"types": "./lib/shortw.d.ts",
|
||||||
"import": "./esm/abstract/edwards.js",
|
"import": "./lib/esm/shortw.js",
|
||||||
"default": "./abstract/edwards.js"
|
"default": "./lib/shortw.js"
|
||||||
},
|
},
|
||||||
"./abstract/modular": {
|
"./utils": {
|
||||||
"types": "./abstract/modular.d.ts",
|
"types": "./lib/utils.d.ts",
|
||||||
"import": "./esm/abstract/modular.js",
|
"import": "./lib/esm/utils.js",
|
||||||
"default": "./abstract/modular.js"
|
"default": "./lib/utils.js"
|
||||||
},
|
|
||||||
"./abstract/montgomery": {
|
|
||||||
"types": "./abstract/montgomery.d.ts",
|
|
||||||
"import": "./esm/abstract/montgomery.js",
|
|
||||||
"default": "./abstract/montgomery.js"
|
|
||||||
},
|
|
||||||
"./abstract/weierstrass": {
|
|
||||||
"types": "./abstract/weierstrass.d.ts",
|
|
||||||
"import": "./esm/abstract/weierstrass.js",
|
|
||||||
"default": "./abstract/weierstrass.js"
|
|
||||||
},
|
|
||||||
"./abstract/bls": {
|
|
||||||
"types": "./abstract/bls.d.ts",
|
|
||||||
"import": "./esm/abstract/bls.js",
|
|
||||||
"default": "./abstract/bls.js"
|
|
||||||
},
|
|
||||||
"./abstract/hash-to-curve": {
|
|
||||||
"types": "./abstract/hash-to-curve.d.ts",
|
|
||||||
"import": "./esm/abstract/hash-to-curve.js",
|
|
||||||
"default": "./abstract/hash-to-curve.js"
|
|
||||||
},
|
|
||||||
"./abstract/curve": {
|
|
||||||
"types": "./abstract/curve.d.ts",
|
|
||||||
"import": "./esm/abstract/curve.js",
|
|
||||||
"default": "./abstract/curve.js"
|
|
||||||
},
|
|
||||||
"./abstract/utils": {
|
|
||||||
"types": "./abstract/utils.d.ts",
|
|
||||||
"import": "./esm/abstract/utils.js",
|
|
||||||
"default": "./abstract/utils.js"
|
|
||||||
},
|
|
||||||
"./abstract/poseidon": {
|
|
||||||
"types": "./abstract/poseidon.d.ts",
|
|
||||||
"import": "./esm/abstract/poseidon.js",
|
|
||||||
"default": "./abstract/poseidon.js"
|
|
||||||
},
|
|
||||||
"./_shortw_utils": {
|
|
||||||
"types": "./_shortw_utils.d.ts",
|
|
||||||
"import": "./esm/_shortw_utils.js",
|
|
||||||
"default": "./_shortw_utils.js"
|
|
||||||
},
|
|
||||||
"./bls12-381": {
|
|
||||||
"types": "./bls12-381.d.ts",
|
|
||||||
"import": "./esm/bls12-381.js",
|
|
||||||
"default": "./bls12-381.js"
|
|
||||||
},
|
|
||||||
"./bn254": {
|
|
||||||
"types": "./bn254.d.ts",
|
|
||||||
"import": "./esm/bn254.js",
|
|
||||||
"default": "./bn254.js"
|
|
||||||
},
|
|
||||||
"./ed25519": {
|
|
||||||
"types": "./ed25519.d.ts",
|
|
||||||
"import": "./esm/ed25519.js",
|
|
||||||
"default": "./ed25519.js"
|
|
||||||
},
|
|
||||||
"./ed448": {
|
|
||||||
"types": "./ed448.d.ts",
|
|
||||||
"import": "./esm/ed448.js",
|
|
||||||
"default": "./ed448.js"
|
|
||||||
},
|
|
||||||
"./index": {
|
|
||||||
"types": "./index.d.ts",
|
|
||||||
"import": "./esm/index.js",
|
|
||||||
"default": "./index.js"
|
|
||||||
},
|
|
||||||
"./jubjub": {
|
|
||||||
"types": "./jubjub.d.ts",
|
|
||||||
"import": "./esm/jubjub.js",
|
|
||||||
"default": "./jubjub.js"
|
|
||||||
},
|
|
||||||
"./p256": {
|
|
||||||
"types": "./p256.d.ts",
|
|
||||||
"import": "./esm/p256.js",
|
|
||||||
"default": "./p256.js"
|
|
||||||
},
|
|
||||||
"./p384": {
|
|
||||||
"types": "./p384.d.ts",
|
|
||||||
"import": "./esm/p384.js",
|
|
||||||
"default": "./p384.js"
|
|
||||||
},
|
|
||||||
"./p521": {
|
|
||||||
"types": "./p521.d.ts",
|
|
||||||
"import": "./esm/p521.js",
|
|
||||||
"default": "./p521.js"
|
|
||||||
},
|
|
||||||
"./pasta": {
|
|
||||||
"types": "./pasta.d.ts",
|
|
||||||
"import": "./esm/pasta.js",
|
|
||||||
"default": "./pasta.js"
|
|
||||||
},
|
|
||||||
"./secp256k1": {
|
|
||||||
"types": "./secp256k1.d.ts",
|
|
||||||
"import": "./esm/secp256k1.js",
|
|
||||||
"default": "./secp256k1.js"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"elliptic",
|
"elliptic",
|
||||||
"curve",
|
"curve",
|
||||||
"cryptography",
|
"cryptography",
|
||||||
"weierstrass",
|
"hyperelliptic",
|
||||||
"montgomery",
|
|
||||||
"edwards",
|
|
||||||
"p256",
|
"p256",
|
||||||
"p384",
|
"p384",
|
||||||
"p521",
|
"p521",
|
||||||
"secp256r1",
|
"nist",
|
||||||
"secp256k1",
|
"weierstrass",
|
||||||
"ed25519",
|
"edwards",
|
||||||
"ed448",
|
"montgomery",
|
||||||
"x25519",
|
"hashes",
|
||||||
"ed25519",
|
|
||||||
"bls12-381",
|
|
||||||
"bn254",
|
|
||||||
"pasta",
|
|
||||||
"bls",
|
|
||||||
"noble",
|
|
||||||
"ecc",
|
"ecc",
|
||||||
"ecdsa",
|
"ecdsa",
|
||||||
"eddsa",
|
"eddsa",
|
||||||
"schnorr"
|
"schnorr"
|
||||||
],
|
],
|
||||||
"funding": "https://paulmillr.com/funding/"
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
|
||||||
import { weierstrass, CurveType } from './abstract/weierstrass.js';
|
|
||||||
import { CHash } from './abstract/utils.js';
|
|
||||||
|
|
||||||
// connects noble-curves to noble-hashes
|
|
||||||
export function getHash(hash: CHash) {
|
|
||||||
return {
|
|
||||||
hash,
|
|
||||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
|
||||||
randomBytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Same API as @noble/hashes, with ability to create curve with custom hash
|
|
||||||
type CurveDef = Readonly<Omit<CurveType, 'hash' | 'hmac' | 'randomBytes'>>;
|
|
||||||
export function createCurve(curveDef: CurveDef, defHash: CHash) {
|
|
||||||
const create = (hash: CHash) => weierstrass({ ...curveDef, ...getHash(hash) });
|
|
||||||
return Object.freeze({ ...create(defHash), create });
|
|
||||||
}
|
|
||||||
@ -1,496 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
/**
|
|
||||||
* BLS (Barreto-Lynn-Scott) family of pairing-friendly curves.
|
|
||||||
* Implements BLS (Boneh-Lynn-Shacham) signatures.
|
|
||||||
* Consists of two curves: G1 and G2:
|
|
||||||
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
|
|
||||||
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
|
|
||||||
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
|
|
||||||
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
|
|
||||||
* Pairing is used to aggregate and verify signatures.
|
|
||||||
* We are using Fp for private keys (shorter) and Fp₂ for signatures (longer).
|
|
||||||
* Some projects may prefer to swap this relation, it is not supported for now.
|
|
||||||
*/
|
|
||||||
import { AffinePoint } from './curve.js';
|
|
||||||
import { IField, getMinHashLength, mapHashToField } from './modular.js';
|
|
||||||
import { Hex, PrivKey, CHash, bitLen, bitGet, ensureBytes } from './utils.js';
|
|
||||||
// prettier-ignore
|
|
||||||
import {
|
|
||||||
MapToCurve, Opts as HTFOpts, H2CPointConstructor, htfBasicOpts,
|
|
||||||
createHasher
|
|
||||||
} from './hash-to-curve.js';
|
|
||||||
import {
|
|
||||||
CurvePointsType,
|
|
||||||
ProjPointType as ProjPointType,
|
|
||||||
CurvePointsRes,
|
|
||||||
weierstrassPoints,
|
|
||||||
} from './weierstrass.js';
|
|
||||||
|
|
||||||
type Fp = bigint; // Can be different field?
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const _2n = BigInt(2), _3n = BigInt(3);
|
|
||||||
|
|
||||||
export type ShortSignatureCoder<Fp> = {
|
|
||||||
fromHex(hex: Hex): ProjPointType<Fp>;
|
|
||||||
toRawBytes(point: ProjPointType<Fp>): Uint8Array;
|
|
||||||
toHex(point: ProjPointType<Fp>): string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SignatureCoder<Fp2> = {
|
|
||||||
fromHex(hex: Hex): ProjPointType<Fp2>;
|
|
||||||
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
|
|
||||||
toHex(point: ProjPointType<Fp2>): string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CurveType<Fp, Fp2, Fp6, Fp12> = {
|
|
||||||
G1: Omit<CurvePointsType<Fp>, 'n'> & {
|
|
||||||
ShortSignature: SignatureCoder<Fp>;
|
|
||||||
mapToCurve: MapToCurve<Fp>;
|
|
||||||
htfDefaults: HTFOpts;
|
|
||||||
};
|
|
||||||
G2: Omit<CurvePointsType<Fp2>, 'n'> & {
|
|
||||||
Signature: SignatureCoder<Fp2>;
|
|
||||||
mapToCurve: MapToCurve<Fp2>;
|
|
||||||
htfDefaults: HTFOpts;
|
|
||||||
};
|
|
||||||
fields: {
|
|
||||||
Fp: IField<Fp>;
|
|
||||||
Fr: IField<bigint>;
|
|
||||||
Fp2: IField<Fp2> & {
|
|
||||||
reim: (num: Fp2) => { re: bigint; im: bigint };
|
|
||||||
multiplyByB: (num: Fp2) => Fp2;
|
|
||||||
frobeniusMap(num: Fp2, power: number): Fp2;
|
|
||||||
};
|
|
||||||
Fp6: IField<Fp6>;
|
|
||||||
Fp12: IField<Fp12> & {
|
|
||||||
frobeniusMap(num: Fp12, power: number): Fp12;
|
|
||||||
multiplyBy014(num: Fp12, o0: Fp2, o1: Fp2, o4: Fp2): Fp12;
|
|
||||||
conjugate(num: Fp12): Fp12;
|
|
||||||
finalExponentiate(num: Fp12): Fp12;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
params: {
|
|
||||||
x: bigint;
|
|
||||||
r: bigint;
|
|
||||||
};
|
|
||||||
htfDefaults: HTFOpts;
|
|
||||||
hash: CHash; // Because we need outputLen for DRBG
|
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
|
|
||||||
getPublicKey: (privateKey: PrivKey) => Uint8Array;
|
|
||||||
getPublicKeyForShortSignatures: (privateKey: PrivKey) => Uint8Array;
|
|
||||||
sign: {
|
|
||||||
(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
|
|
||||||
(message: ProjPointType<Fp2>, privateKey: PrivKey, htfOpts?: htfBasicOpts): ProjPointType<Fp2>;
|
|
||||||
};
|
|
||||||
signShortSignature: {
|
|
||||||
(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
|
|
||||||
(message: ProjPointType<Fp>, privateKey: PrivKey, htfOpts?: htfBasicOpts): ProjPointType<Fp>;
|
|
||||||
};
|
|
||||||
verify: (
|
|
||||||
signature: Hex | ProjPointType<Fp2>,
|
|
||||||
message: Hex | ProjPointType<Fp2>,
|
|
||||||
publicKey: Hex | ProjPointType<Fp>,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
) => boolean;
|
|
||||||
verifyShortSignature: (
|
|
||||||
signature: Hex | ProjPointType<Fp>,
|
|
||||||
message: Hex | ProjPointType<Fp>,
|
|
||||||
publicKey: Hex | ProjPointType<Fp2>,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
) => boolean;
|
|
||||||
verifyBatch: (
|
|
||||||
signature: Hex | ProjPointType<Fp2>,
|
|
||||||
messages: (Hex | ProjPointType<Fp2>)[],
|
|
||||||
publicKeys: (Hex | ProjPointType<Fp>)[],
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
) => boolean;
|
|
||||||
aggregatePublicKeys: {
|
|
||||||
(publicKeys: Hex[]): Uint8Array;
|
|
||||||
(publicKeys: ProjPointType<Fp>[]): ProjPointType<Fp>;
|
|
||||||
};
|
|
||||||
aggregateSignatures: {
|
|
||||||
(signatures: Hex[]): Uint8Array;
|
|
||||||
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
|
|
||||||
};
|
|
||||||
aggregateShortSignatures: {
|
|
||||||
(signatures: Hex[]): Uint8Array;
|
|
||||||
(signatures: ProjPointType<Fp>[]): ProjPointType<Fp>;
|
|
||||||
};
|
|
||||||
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
|
|
||||||
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
|
|
||||||
G1: CurvePointsRes<Fp> & ReturnType<typeof createHasher<Fp>>;
|
|
||||||
G2: CurvePointsRes<Fp2> & ReturnType<typeof createHasher<Fp2>>;
|
|
||||||
Signature: SignatureCoder<Fp2>;
|
|
||||||
ShortSignature: ShortSignatureCoder<Fp>;
|
|
||||||
params: {
|
|
||||||
x: bigint;
|
|
||||||
r: bigint;
|
|
||||||
G1b: bigint;
|
|
||||||
G2b: Fp2;
|
|
||||||
};
|
|
||||||
fields: {
|
|
||||||
Fp: IField<Fp>;
|
|
||||||
Fp2: IField<Fp2>;
|
|
||||||
Fp6: IField<Fp6>;
|
|
||||||
Fp12: IField<Fp12>;
|
|
||||||
Fr: IField<bigint>;
|
|
||||||
};
|
|
||||||
utils: {
|
|
||||||
randomPrivateKey: () => Uint8Array;
|
|
||||||
calcPairingPrecomputes: (p: AffinePoint<Fp2>) => [Fp2, Fp2, Fp2][];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function bls<Fp2, Fp6, Fp12>(
|
|
||||||
CURVE: CurveType<Fp, Fp2, Fp6, Fp12>
|
|
||||||
): CurveFn<Fp, Fp2, Fp6, Fp12> {
|
|
||||||
// Fields are specific for curve, so for now we'll need to pass them with opts
|
|
||||||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
|
|
||||||
const BLS_X_LEN = bitLen(CURVE.params.x);
|
|
||||||
|
|
||||||
// Pre-compute coefficients for sparse multiplication
|
|
||||||
// Point addition and point double calculations is reused for coefficients
|
|
||||||
function calcPairingPrecomputes(p: AffinePoint<Fp2>) {
|
|
||||||
const { x, y } = p;
|
|
||||||
// prettier-ignore
|
|
||||||
const Qx = x, Qy = y, Qz = Fp2.ONE;
|
|
||||||
// prettier-ignore
|
|
||||||
let Rx = Qx, Ry = Qy, Rz = Qz;
|
|
||||||
let ell_coeff: [Fp2, Fp2, Fp2][] = [];
|
|
||||||
for (let i = BLS_X_LEN - 2; i >= 0; i--) {
|
|
||||||
// Double
|
|
||||||
let t0 = Fp2.sqr(Ry); // Ry²
|
|
||||||
let t1 = Fp2.sqr(Rz); // Rz²
|
|
||||||
let t2 = Fp2.multiplyByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
|
|
||||||
let t3 = Fp2.mul(t2, _3n); // 3 * T2
|
|
||||||
let t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
|
|
||||||
ell_coeff.push([
|
|
||||||
Fp2.sub(t2, t0), // T2 - T0
|
|
||||||
Fp2.mul(Fp2.sqr(Rx), _3n), // 3 * Rx²
|
|
||||||
Fp2.neg(t4), // -T4
|
|
||||||
]);
|
|
||||||
Rx = Fp2.div(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), _2n); // ((T0 - T3) * Rx * Ry) / 2
|
|
||||||
Ry = Fp2.sub(Fp2.sqr(Fp2.div(Fp2.add(t0, t3), _2n)), Fp2.mul(Fp2.sqr(t2), _3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
|
||||||
Rz = Fp2.mul(t0, t4); // T0 * T4
|
|
||||||
if (bitGet(CURVE.params.x, i)) {
|
|
||||||
// Addition
|
|
||||||
let t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
|
||||||
let t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
|
|
||||||
ell_coeff.push([
|
|
||||||
Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)), // T0 * Qx - T1 * Qy
|
|
||||||
Fp2.neg(t0), // -T0
|
|
||||||
t1, // T1
|
|
||||||
]);
|
|
||||||
let t2 = Fp2.sqr(t1); // T1²
|
|
||||||
let t3 = Fp2.mul(t2, t1); // T2 * T1
|
|
||||||
let t4 = Fp2.mul(t2, Rx); // T2 * Rx
|
|
||||||
let t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, _2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
|
|
||||||
Rx = Fp2.mul(t1, t5); // T1 * T5
|
|
||||||
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
|
|
||||||
Rz = Fp2.mul(Rz, t3); // Rz * T3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ell_coeff;
|
|
||||||
}
|
|
||||||
|
|
||||||
function millerLoop(ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]): Fp12 {
|
|
||||||
const { x } = CURVE.params;
|
|
||||||
const Px = g1[0];
|
|
||||||
const Py = g1[1];
|
|
||||||
let f12 = Fp12.ONE;
|
|
||||||
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
|
|
||||||
const E = ell[j];
|
|
||||||
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.mul(E[1], Px), Fp2.mul(E[2], Py));
|
|
||||||
if (bitGet(x, i)) {
|
|
||||||
j += 1;
|
|
||||||
const F = ell[j];
|
|
||||||
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.mul(F[1], Px), Fp2.mul(F[2], Py));
|
|
||||||
}
|
|
||||||
if (i !== 0) f12 = Fp12.sqr(f12);
|
|
||||||
}
|
|
||||||
return Fp12.conjugate(f12);
|
|
||||||
}
|
|
||||||
|
|
||||||
const utils = {
|
|
||||||
randomPrivateKey: (): Uint8Array => {
|
|
||||||
const length = getMinHashLength(Fr.ORDER);
|
|
||||||
return mapHashToField(CURVE.randomBytes(length), Fr.ORDER);
|
|
||||||
},
|
|
||||||
calcPairingPrecomputes,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Point on G1 curve: (x, y)
|
|
||||||
const G1_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G1 });
|
|
||||||
const G1 = Object.assign(
|
|
||||||
G1_,
|
|
||||||
createHasher(G1_.ProjectivePoint, CURVE.G1.mapToCurve, {
|
|
||||||
...CURVE.htfDefaults,
|
|
||||||
...CURVE.G1.htfDefaults,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sparse multiplication against precomputed coefficients
|
|
||||||
// TODO: replace with weakmap?
|
|
||||||
type withPairingPrecomputes = { _PPRECOMPUTES: [Fp2, Fp2, Fp2][] | undefined };
|
|
||||||
function pairingPrecomputes(point: G2): [Fp2, Fp2, Fp2][] {
|
|
||||||
const p = point as G2 & withPairingPrecomputes;
|
|
||||||
if (p._PPRECOMPUTES) return p._PPRECOMPUTES;
|
|
||||||
p._PPRECOMPUTES = calcPairingPrecomputes(point.toAffine());
|
|
||||||
return p._PPRECOMPUTES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: export
|
|
||||||
// function clearPairingPrecomputes(point: G2) {
|
|
||||||
// const p = point as G2 & withPairingPrecomputes;
|
|
||||||
// p._PPRECOMPUTES = undefined;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
|
|
||||||
const G2_ = weierstrassPoints({ n: Fr.ORDER, ...CURVE.G2 });
|
|
||||||
const G2 = Object.assign(
|
|
||||||
G2_,
|
|
||||||
createHasher(G2_.ProjectivePoint as H2CPointConstructor<Fp2>, CURVE.G2.mapToCurve, {
|
|
||||||
...CURVE.htfDefaults,
|
|
||||||
...CURVE.G2.htfDefaults,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const { ShortSignature } = CURVE.G1;
|
|
||||||
const { Signature } = CURVE.G2;
|
|
||||||
|
|
||||||
// Calculates bilinear pairing
|
|
||||||
function pairing(Q: G1, P: G2, withFinalExponent: boolean = true): Fp12 {
|
|
||||||
if (Q.equals(G1.ProjectivePoint.ZERO) || P.equals(G2.ProjectivePoint.ZERO))
|
|
||||||
throw new Error('pairing is not available for ZERO point');
|
|
||||||
Q.assertValidity();
|
|
||||||
P.assertValidity();
|
|
||||||
// Performance: 9ms for millerLoop and ~14ms for exp.
|
|
||||||
const Qa = Q.toAffine();
|
|
||||||
const looped = millerLoop(pairingPrecomputes(P), [Qa.x, Qa.y]);
|
|
||||||
return withFinalExponent ? Fp12.finalExponentiate(looped) : looped;
|
|
||||||
}
|
|
||||||
type G1 = typeof G1.ProjectivePoint.BASE;
|
|
||||||
type G2 = typeof G2.ProjectivePoint.BASE;
|
|
||||||
|
|
||||||
type G1Hex = Hex | G1;
|
|
||||||
type G2Hex = Hex | G2;
|
|
||||||
function normP1(point: G1Hex): G1 {
|
|
||||||
return point instanceof G1.ProjectivePoint ? (point as G1) : G1.ProjectivePoint.fromHex(point);
|
|
||||||
}
|
|
||||||
function normP1Hash(point: G1Hex, htfOpts?: htfBasicOpts): G1 {
|
|
||||||
return point instanceof G1.ProjectivePoint
|
|
||||||
? point
|
|
||||||
: (G1.hashToCurve(ensureBytes('point', point), htfOpts) as G1);
|
|
||||||
}
|
|
||||||
function normP2(point: G2Hex): G2 {
|
|
||||||
return point instanceof G2.ProjectivePoint ? point : Signature.fromHex(point);
|
|
||||||
}
|
|
||||||
function normP2Hash(point: G2Hex, htfOpts?: htfBasicOpts): G2 {
|
|
||||||
return point instanceof G2.ProjectivePoint
|
|
||||||
? point
|
|
||||||
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplies generator (G1) by private key.
|
|
||||||
// P = pk x G
|
|
||||||
function getPublicKey(privateKey: PrivKey): Uint8Array {
|
|
||||||
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplies generator (G2) by private key.
|
|
||||||
// P = pk x G
|
|
||||||
function getPublicKeyForShortSignatures(privateKey: PrivKey): Uint8Array {
|
|
||||||
return G2.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executes `hashToCurve` on the message and then multiplies the result by private key.
|
|
||||||
// S = pk x H(m)
|
|
||||||
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
|
|
||||||
function sign(message: G2, privateKey: PrivKey, htfOpts?: htfBasicOpts): G2;
|
|
||||||
function sign(message: G2Hex, privateKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array | G2 {
|
|
||||||
const msgPoint = normP2Hash(message, htfOpts);
|
|
||||||
msgPoint.assertValidity();
|
|
||||||
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
|
|
||||||
if (message instanceof G2.ProjectivePoint) return sigPoint;
|
|
||||||
return Signature.toRawBytes(sigPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
function signShortSignature(
|
|
||||||
message: Hex,
|
|
||||||
privateKey: PrivKey,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
): Uint8Array;
|
|
||||||
function signShortSignature(message: G1, privateKey: PrivKey, htfOpts?: htfBasicOpts): G1;
|
|
||||||
function signShortSignature(
|
|
||||||
message: G1Hex,
|
|
||||||
privateKey: PrivKey,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
): Uint8Array | G1 {
|
|
||||||
const msgPoint = normP1Hash(message, htfOpts);
|
|
||||||
msgPoint.assertValidity();
|
|
||||||
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
|
|
||||||
if (message instanceof G1.ProjectivePoint) return sigPoint;
|
|
||||||
return ShortSignature.toRawBytes(sigPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
|
||||||
// e(P, H(m)) == e(G, S)
|
|
||||||
function verify(
|
|
||||||
signature: G2Hex,
|
|
||||||
message: G2Hex,
|
|
||||||
publicKey: G1Hex,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
): boolean {
|
|
||||||
const P = normP1(publicKey);
|
|
||||||
const Hm = normP2Hash(message, htfOpts);
|
|
||||||
const G = G1.ProjectivePoint.BASE;
|
|
||||||
const S = normP2(signature);
|
|
||||||
// Instead of doing 2 exponentiations, we use property of billinear maps
|
|
||||||
// and do one exp after multiplying 2 points.
|
|
||||||
const ePHm = pairing(P.negate(), Hm, false);
|
|
||||||
const eGS = pairing(G, S, false);
|
|
||||||
const exp = Fp12.finalExponentiate(Fp12.mul(eGS, ePHm));
|
|
||||||
return Fp12.eql(exp, Fp12.ONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
|
||||||
// e(S, G) == e(H(m), P)
|
|
||||||
function verifyShortSignature(
|
|
||||||
signature: G1Hex,
|
|
||||||
message: G1Hex,
|
|
||||||
publicKey: G2Hex,
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
): boolean {
|
|
||||||
const P = normP2(publicKey);
|
|
||||||
const Hm = normP1Hash(message, htfOpts);
|
|
||||||
const G = G2.ProjectivePoint.BASE;
|
|
||||||
const S = normP1(signature);
|
|
||||||
// Instead of doing 2 exponentiations, we use property of billinear maps
|
|
||||||
// and do one exp after multiplying 2 points.
|
|
||||||
const eHmP = pairing(Hm, P, false);
|
|
||||||
const eSG = pairing(S, G.negate(), false);
|
|
||||||
const exp = Fp12.finalExponentiate(Fp12.mul(eSG, eHmP));
|
|
||||||
return Fp12.eql(exp, Fp12.ONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a bunch of public key points together.
|
|
||||||
// pk1 + pk2 + pk3 = pkA
|
|
||||||
function aggregatePublicKeys(publicKeys: Hex[]): Uint8Array;
|
|
||||||
function aggregatePublicKeys(publicKeys: G1[]): G1;
|
|
||||||
function aggregatePublicKeys(publicKeys: G1Hex[]): Uint8Array | G1 {
|
|
||||||
if (!publicKeys.length) throw new Error('Expected non-empty array');
|
|
||||||
const agg = publicKeys.map(normP1).reduce((sum, p) => sum.add(p), G1.ProjectivePoint.ZERO);
|
|
||||||
const aggAffine = agg; //.toAffine();
|
|
||||||
if (publicKeys[0] instanceof G1.ProjectivePoint) {
|
|
||||||
aggAffine.assertValidity();
|
|
||||||
return aggAffine;
|
|
||||||
}
|
|
||||||
// toRawBytes ensures point validity
|
|
||||||
return aggAffine.toRawBytes(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a bunch of signature points together.
|
|
||||||
function aggregateSignatures(signatures: Hex[]): Uint8Array;
|
|
||||||
function aggregateSignatures(signatures: G2[]): G2;
|
|
||||||
function aggregateSignatures(signatures: G2Hex[]): Uint8Array | G2 {
|
|
||||||
if (!signatures.length) throw new Error('Expected non-empty array');
|
|
||||||
const agg = signatures.map(normP2).reduce((sum, s) => sum.add(s), G2.ProjectivePoint.ZERO);
|
|
||||||
const aggAffine = agg; //.toAffine();
|
|
||||||
if (signatures[0] instanceof G2.ProjectivePoint) {
|
|
||||||
aggAffine.assertValidity();
|
|
||||||
return aggAffine;
|
|
||||||
}
|
|
||||||
return Signature.toRawBytes(aggAffine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a bunch of signature points together.
|
|
||||||
function aggregateShortSignatures(signatures: Hex[]): Uint8Array;
|
|
||||||
function aggregateShortSignatures(signatures: G1[]): G1;
|
|
||||||
function aggregateShortSignatures(signatures: G1Hex[]): Uint8Array | G1 {
|
|
||||||
if (!signatures.length) throw new Error('Expected non-empty array');
|
|
||||||
const agg = signatures.map(normP1).reduce((sum, s) => sum.add(s), G1.ProjectivePoint.ZERO);
|
|
||||||
const aggAffine = agg; //.toAffine();
|
|
||||||
if (signatures[0] instanceof G1.ProjectivePoint) {
|
|
||||||
aggAffine.assertValidity();
|
|
||||||
return aggAffine;
|
|
||||||
}
|
|
||||||
return ShortSignature.toRawBytes(aggAffine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
|
||||||
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
|
|
||||||
function verifyBatch(
|
|
||||||
signature: G2Hex,
|
|
||||||
messages: G2Hex[],
|
|
||||||
publicKeys: G1Hex[],
|
|
||||||
htfOpts?: htfBasicOpts
|
|
||||||
): boolean {
|
|
||||||
// @ts-ignore
|
|
||||||
// console.log('verifyBatch', bytesToHex(signature as any), messages, publicKeys.map(bytesToHex));
|
|
||||||
|
|
||||||
if (!messages.length) throw new Error('Expected non-empty messages array');
|
|
||||||
if (publicKeys.length !== messages.length)
|
|
||||||
throw new Error('Pubkey count should equal msg count');
|
|
||||||
const sig = normP2(signature);
|
|
||||||
const nMessages = messages.map((i) => normP2Hash(i, htfOpts));
|
|
||||||
const nPublicKeys = publicKeys.map(normP1);
|
|
||||||
try {
|
|
||||||
const paired = [];
|
|
||||||
for (const message of new Set(nMessages)) {
|
|
||||||
const groupPublicKey = nMessages.reduce(
|
|
||||||
(groupPublicKey, subMessage, i) =>
|
|
||||||
subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey,
|
|
||||||
G1.ProjectivePoint.ZERO
|
|
||||||
);
|
|
||||||
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message);
|
|
||||||
// Possible to batch pairing for same msg with different groupPublicKey here
|
|
||||||
paired.push(pairing(groupPublicKey, message, false));
|
|
||||||
}
|
|
||||||
paired.push(pairing(G1.ProjectivePoint.BASE.negate(), sig, false));
|
|
||||||
const product = paired.reduce((a, b) => Fp12.mul(a, b), Fp12.ONE);
|
|
||||||
const exp = Fp12.finalExponentiate(product);
|
|
||||||
return Fp12.eql(exp, Fp12.ONE);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
G1.ProjectivePoint.BASE._setWindowSize(4);
|
|
||||||
|
|
||||||
return {
|
|
||||||
getPublicKey,
|
|
||||||
getPublicKeyForShortSignatures,
|
|
||||||
sign,
|
|
||||||
signShortSignature,
|
|
||||||
verify,
|
|
||||||
verifyBatch,
|
|
||||||
verifyShortSignature,
|
|
||||||
aggregatePublicKeys,
|
|
||||||
aggregateSignatures,
|
|
||||||
aggregateShortSignatures,
|
|
||||||
millerLoop,
|
|
||||||
pairing,
|
|
||||||
G1,
|
|
||||||
G2,
|
|
||||||
Signature,
|
|
||||||
ShortSignature,
|
|
||||||
fields: {
|
|
||||||
Fr,
|
|
||||||
Fp,
|
|
||||||
Fp2,
|
|
||||||
Fp6,
|
|
||||||
Fp12,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
x: CURVE.params.x,
|
|
||||||
r: CURVE.params.r,
|
|
||||||
G1b: CURVE.G1.b,
|
|
||||||
G2b: CURVE.G2.b,
|
|
||||||
},
|
|
||||||
utils,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,203 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
// Abelian group utilities
|
|
||||||
import { IField, validateField, nLength } from './modular.js';
|
|
||||||
import { validateObject } from './utils.js';
|
|
||||||
const _0n = BigInt(0);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
|
|
||||||
export type AffinePoint<T> = {
|
|
||||||
x: T;
|
|
||||||
y: T;
|
|
||||||
} & { z?: never; t?: never };
|
|
||||||
|
|
||||||
export interface Group<T extends Group<T>> {
|
|
||||||
double(): T;
|
|
||||||
negate(): T;
|
|
||||||
add(other: T): T;
|
|
||||||
subtract(other: T): T;
|
|
||||||
equals(other: T): boolean;
|
|
||||||
multiply(scalar: bigint): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GroupConstructor<T> = {
|
|
||||||
BASE: T;
|
|
||||||
ZERO: T;
|
|
||||||
};
|
|
||||||
export type Mapper<T> = (i: T[]) => T[];
|
|
||||||
|
|
||||||
// Elliptic curve multiplication of Point by scalar. Fragile.
|
|
||||||
// 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) {
|
|
||||||
const constTimeNegate = (condition: boolean, item: T): T => {
|
|
||||||
const neg = item.negate();
|
|
||||||
return condition ? neg : item;
|
|
||||||
};
|
|
||||||
const opts = (W: number) => {
|
|
||||||
const windows = Math.ceil(bits / W) + 1; // +1, because
|
|
||||||
const windowSize = 2 ** (W - 1); // -1 because we skip zero
|
|
||||||
return { windows, windowSize };
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
constTimeNegate,
|
|
||||||
// non-const time multiplication ladder
|
|
||||||
unsafeLadder(elm: T, n: bigint) {
|
|
||||||
let p = c.ZERO;
|
|
||||||
let d: T = elm;
|
|
||||||
while (n > _0n) {
|
|
||||||
if (n & _1n) p = p.add(d);
|
|
||||||
d = d.double();
|
|
||||||
n >>= _1n;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a wNAF precomputation window. Used for caching.
|
|
||||||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
|
||||||
* Number of precomputed points depends on the curve size:
|
|
||||||
* 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>[] {
|
|
||||||
const { windows, windowSize } = opts(W);
|
|
||||||
const points: T[] = [];
|
|
||||||
let p: T = elm;
|
|
||||||
let base = p;
|
|
||||||
for (let window = 0; window < windows; window++) {
|
|
||||||
base = p;
|
|
||||||
points.push(base);
|
|
||||||
// =1, because we skip zero
|
|
||||||
for (let i = 1; i < windowSize; i++) {
|
|
||||||
base = base.add(p);
|
|
||||||
points.push(base);
|
|
||||||
}
|
|
||||||
p = base.double();
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
|
||||||
* @param W window size
|
|
||||||
* @param precomputes precomputed tables
|
|
||||||
* @param n scalar (we don't check here, but should be less than curve order)
|
|
||||||
* @returns real and fake (for const-time) points
|
|
||||||
*/
|
|
||||||
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
|
|
||||||
// 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
|
|
||||||
const { windows, windowSize } = opts(W);
|
|
||||||
|
|
||||||
let p = c.ZERO;
|
|
||||||
let f = c.BASE;
|
|
||||||
|
|
||||||
const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc.
|
|
||||||
const maxNumber = 2 ** W;
|
|
||||||
const shiftBy = BigInt(W);
|
|
||||||
|
|
||||||
for (let window = 0; window < windows; window++) {
|
|
||||||
const offset = window * windowSize;
|
|
||||||
// Extract W bits.
|
|
||||||
let wbits = Number(n & mask);
|
|
||||||
|
|
||||||
// Shift number by W bits.
|
|
||||||
n >>= shiftBy;
|
|
||||||
|
|
||||||
// If the bits are bigger than max size, we'll split those.
|
|
||||||
// +224 => 256 - 32
|
|
||||||
if (wbits > windowSize) {
|
|
||||||
wbits -= maxNumber;
|
|
||||||
n += _1n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code was first written with assumption that 'f' and 'p' will never be infinity point:
|
|
||||||
// since each addition is multiplied by 2 ** W, it cannot cancel each other. However,
|
|
||||||
// there is negate now: it is possible that negated element from low value
|
|
||||||
// would be the same as high element, which will create carry into next window.
|
|
||||||
// It's not obvious how this can fail, but still worth investigating later.
|
|
||||||
|
|
||||||
// Check if we're onto Zero point.
|
|
||||||
// Add random point inside current window to f.
|
|
||||||
const offset1 = offset;
|
|
||||||
const offset2 = offset + Math.abs(wbits) - 1; // -1 because we skip zero
|
|
||||||
const cond1 = window % 2 !== 0;
|
|
||||||
const cond2 = wbits < 0;
|
|
||||||
if (wbits === 0) {
|
|
||||||
// The most important part for const-time getPublicKey
|
|
||||||
f = f.add(constTimeNegate(cond1, precomputes[offset1]));
|
|
||||||
} else {
|
|
||||||
p = p.add(constTimeNegate(cond2, precomputes[offset2]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// JIT-compiler should not eliminate f here, since it will later be used in normalizeZ()
|
|
||||||
// Even if the variable is still unused, there are some checks which will
|
|
||||||
// throw an exception, so compiler needs to prove they won't happen, which is hard.
|
|
||||||
// At this point there is a way to F be infinity-point even if p is not,
|
|
||||||
// which makes it less const-time: around 1 bigint multiply.
|
|
||||||
return { p, f };
|
|
||||||
},
|
|
||||||
|
|
||||||
wNAFCached(P: T, precomputesMap: Map<T, T[]>, n: bigint, transform: Mapper<T>): { p: T; f: T } {
|
|
||||||
// @ts-ignore
|
|
||||||
const W: number = P._WINDOW_SIZE || 1;
|
|
||||||
// Calculate precomputes on a first run, reuse them after
|
|
||||||
let comp = precomputesMap.get(P);
|
|
||||||
if (!comp) {
|
|
||||||
comp = this.precomputeWindow(P, W) as T[];
|
|
||||||
if (W !== 1) {
|
|
||||||
precomputesMap.set(P, transform(comp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.wNAF(W, comp, n);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
|
|
||||||
// Though generator can be different (Fp2 / Fp6 for BLS).
|
|
||||||
export type BasicCurve<T> = {
|
|
||||||
Fp: IField<T>; // Field over which we'll do calculations (Fp)
|
|
||||||
n: bigint; // Curve order, total count of valid points in the field
|
|
||||||
nBitLength?: number; // bit length of curve order
|
|
||||||
nByteLength?: number; // byte length of curve order
|
|
||||||
h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation
|
|
||||||
hEff?: bigint; // Number to multiply to clear cofactor
|
|
||||||
Gx: T; // base point X coordinate
|
|
||||||
Gy: T; // base point Y coordinate
|
|
||||||
allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey
|
|
||||||
};
|
|
||||||
|
|
||||||
export function validateBasic<FP, T>(curve: BasicCurve<FP> & T) {
|
|
||||||
validateField(curve.Fp);
|
|
||||||
validateObject(
|
|
||||||
curve,
|
|
||||||
{
|
|
||||||
n: 'bigint',
|
|
||||||
h: 'bigint',
|
|
||||||
Gx: 'field',
|
|
||||||
Gy: 'field',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nBitLength: 'isSafeInteger',
|
|
||||||
nByteLength: 'isSafeInteger',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Set defaults
|
|
||||||
return Object.freeze({
|
|
||||||
...nLength(curve.n, curve.nBitLength),
|
|
||||||
...curve,
|
|
||||||
...{ p: curve.Fp.ORDER },
|
|
||||||
} as const);
|
|
||||||
}
|
|
||||||
@ -1,513 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
// Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
|
||||||
import { mod } from './modular.js';
|
|
||||||
import * as ut from './utils.js';
|
|
||||||
import { ensureBytes, FHash, Hex } from './utils.js';
|
|
||||||
import { Group, GroupConstructor, wNAF, BasicCurve, validateBasic, AffinePoint } from './curve.js';
|
|
||||||
|
|
||||||
// Be friendly to bad ECMAScript parsers by not using bigint literals
|
|
||||||
// prettier-ignore
|
|
||||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _8n = BigInt(8);
|
|
||||||
|
|
||||||
// Edwards curves must declare params a & d.
|
|
||||||
export type CurveType = BasicCurve<bigint> & {
|
|
||||||
a: bigint; // curve param a
|
|
||||||
d: bigint; // curve param d
|
|
||||||
hash: FHash; // Hashing
|
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array; // CSPRNG
|
|
||||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
|
|
||||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
|
|
||||||
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
|
|
||||||
prehash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
|
|
||||||
mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard
|
|
||||||
};
|
|
||||||
|
|
||||||
// verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex:
|
|
||||||
const VERIFY_DEFAULT = { zip215: true };
|
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
|
||||||
const opts = validateBasic(curve);
|
|
||||||
ut.validateObject(
|
|
||||||
curve,
|
|
||||||
{
|
|
||||||
hash: 'function',
|
|
||||||
a: 'bigint',
|
|
||||||
d: 'bigint',
|
|
||||||
randomBytes: 'function',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
adjustScalarBytes: 'function',
|
|
||||||
domain: 'function',
|
|
||||||
uvRatio: 'function',
|
|
||||||
mapToCurve: 'function',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Set defaults
|
|
||||||
return Object.freeze({ ...opts } as const);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance of Extended Point with coordinates in X, Y, Z, T
|
|
||||||
export interface ExtPointType extends Group<ExtPointType> {
|
|
||||||
readonly ex: bigint;
|
|
||||||
readonly ey: bigint;
|
|
||||||
readonly ez: bigint;
|
|
||||||
readonly et: bigint;
|
|
||||||
get x(): bigint;
|
|
||||||
get y(): bigint;
|
|
||||||
assertValidity(): void;
|
|
||||||
multiply(scalar: bigint): ExtPointType;
|
|
||||||
multiplyUnsafe(scalar: bigint): ExtPointType;
|
|
||||||
isSmallOrder(): boolean;
|
|
||||||
isTorsionFree(): boolean;
|
|
||||||
clearCofactor(): ExtPointType;
|
|
||||||
toAffine(iz?: bigint): AffinePoint<bigint>;
|
|
||||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
|
||||||
toHex(isCompressed?: boolean): string;
|
|
||||||
}
|
|
||||||
// Static methods of Extended Point with coordinates in X, Y, Z, T
|
|
||||||
export interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
|
|
||||||
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
|
|
||||||
fromAffine(p: AffinePoint<bigint>): ExtPointType;
|
|
||||||
fromHex(hex: Hex): ExtPointType;
|
|
||||||
fromPrivateKey(privateKey: Hex): ExtPointType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CurveFn = {
|
|
||||||
CURVE: ReturnType<typeof validateOpts>;
|
|
||||||
getPublicKey: (privateKey: Hex) => Uint8Array;
|
|
||||||
sign: (message: Hex, privateKey: Hex, options?: { context?: Hex }) => Uint8Array;
|
|
||||||
verify: (
|
|
||||||
sig: Hex,
|
|
||||||
message: Hex,
|
|
||||||
publicKey: Hex,
|
|
||||||
options?: { context?: Hex; zip215: boolean }
|
|
||||||
) => boolean;
|
|
||||||
ExtendedPoint: ExtPointConstructor;
|
|
||||||
utils: {
|
|
||||||
randomPrivateKey: () => Uint8Array;
|
|
||||||
getExtendedPublicKey: (key: Hex) => {
|
|
||||||
head: Uint8Array;
|
|
||||||
prefix: Uint8Array;
|
|
||||||
scalar: bigint;
|
|
||||||
point: ExtPointType;
|
|
||||||
pointBytes: Uint8Array;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// It is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
|
||||||
export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|
||||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
|
||||||
const {
|
|
||||||
Fp,
|
|
||||||
n: CURVE_ORDER,
|
|
||||||
prehash: prehash,
|
|
||||||
hash: cHash,
|
|
||||||
randomBytes,
|
|
||||||
nByteLength,
|
|
||||||
h: cofactor,
|
|
||||||
} = CURVE;
|
|
||||||
const MASK = _2n << (BigInt(nByteLength * 8) - _1n);
|
|
||||||
const modP = Fp.create; // Function overrides
|
|
||||||
|
|
||||||
// sqrt(u/v)
|
|
||||||
const uvRatio =
|
|
||||||
CURVE.uvRatio ||
|
|
||||||
((u: bigint, v: bigint) => {
|
|
||||||
try {
|
|
||||||
return { isValid: true, value: Fp.sqrt(u * Fp.inv(v)) };
|
|
||||||
} catch (e) {
|
|
||||||
return { isValid: false, value: _0n };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes); // NOOP
|
|
||||||
const domain =
|
|
||||||
CURVE.domain ||
|
|
||||||
((data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
|
|
||||||
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
|
|
||||||
return data;
|
|
||||||
}); // NOOP
|
|
||||||
const inBig = (n: bigint) => typeof n === 'bigint' && _0n < n; // n in [1..]
|
|
||||||
const inRange = (n: bigint, max: bigint) => inBig(n) && inBig(max) && n < max; // n in [1..max-1]
|
|
||||||
const in0MaskRange = (n: bigint) => n === _0n || inRange(n, MASK); // n in [0..MASK-1]
|
|
||||||
function assertInRange(n: bigint, max: bigint) {
|
|
||||||
// n in [1..max-1]
|
|
||||||
if (inRange(n, max)) return n;
|
|
||||||
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`);
|
|
||||||
}
|
|
||||||
function assertGE0(n: bigint) {
|
|
||||||
// n in [0..CURVE_ORDER-1]
|
|
||||||
return n === _0n ? n : assertInRange(n, CURVE_ORDER); // GE = prime subgroup, not full group
|
|
||||||
}
|
|
||||||
const pointPrecomputes = new Map<Point, Point[]>();
|
|
||||||
function isPoint(other: unknown) {
|
|
||||||
if (!(other instanceof Point)) throw new Error('ExtendedPoint expected');
|
|
||||||
}
|
|
||||||
// Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
|
||||||
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
|
||||||
class Point implements ExtPointType {
|
|
||||||
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
|
|
||||||
static readonly ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly ex: bigint,
|
|
||||||
readonly ey: bigint,
|
|
||||||
readonly ez: bigint,
|
|
||||||
readonly et: bigint
|
|
||||||
) {
|
|
||||||
if (!in0MaskRange(ex)) throw new Error('x required');
|
|
||||||
if (!in0MaskRange(ey)) throw new Error('y required');
|
|
||||||
if (!in0MaskRange(ez)) throw new Error('z required');
|
|
||||||
if (!in0MaskRange(et)) throw new Error('t required');
|
|
||||||
}
|
|
||||||
|
|
||||||
get x(): bigint {
|
|
||||||
return this.toAffine().x;
|
|
||||||
}
|
|
||||||
get y(): bigint {
|
|
||||||
return this.toAffine().y;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromAffine(p: AffinePoint<bigint>): Point {
|
|
||||||
if (p instanceof Point) throw new Error('extended point not allowed');
|
|
||||||
const { x, y } = p || {};
|
|
||||||
if (!in0MaskRange(x) || !in0MaskRange(y)) throw new Error('invalid affine point');
|
|
||||||
return new Point(x, y, _1n, modP(x * y));
|
|
||||||
}
|
|
||||||
static normalizeZ(points: Point[]): Point[] {
|
|
||||||
const toInv = Fp.invertBatch(points.map((p) => p.ez));
|
|
||||||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We calculate precomputes for elliptic curve point multiplication
|
|
||||||
// using windowed method. This specifies window size and
|
|
||||||
// stores precomputed values. Usually only base point would be precomputed.
|
|
||||||
_WINDOW_SIZE?: number;
|
|
||||||
|
|
||||||
// "Private method", don't use it directly
|
|
||||||
_setWindowSize(windowSize: number) {
|
|
||||||
this._WINDOW_SIZE = windowSize;
|
|
||||||
pointPrecomputes.delete(this);
|
|
||||||
}
|
|
||||||
// Not required for fromHex(), which always creates valid points.
|
|
||||||
// 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.
|
|
||||||
equals(other: Point): boolean {
|
|
||||||
isPoint(other);
|
|
||||||
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
||||||
const { ex: X2, ey: Y2, ez: Z2 } = other;
|
|
||||||
const X1Z2 = modP(X1 * Z2);
|
|
||||||
const X2Z1 = modP(X2 * Z1);
|
|
||||||
const Y1Z2 = modP(Y1 * Z2);
|
|
||||||
const Y2Z1 = modP(Y2 * Z1);
|
|
||||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected is0(): boolean {
|
|
||||||
return this.equals(Point.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
negate(): Point {
|
|
||||||
// Flips point sign to a negative one (-x, y in affine coords)
|
|
||||||
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast algo for doubling Extended Point.
|
|
||||||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
|
||||||
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
|
||||||
double(): Point {
|
|
||||||
const { a } = CURVE;
|
|
||||||
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
||||||
const A = modP(X1 * X1); // A = X12
|
|
||||||
const B = modP(Y1 * Y1); // B = Y12
|
|
||||||
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
|
|
||||||
const D = modP(a * A); // D = a*A
|
|
||||||
const x1y1 = X1 + Y1;
|
|
||||||
const E = modP(modP(x1y1 * x1y1) - A - B); // E = (X1+Y1)2-A-B
|
|
||||||
const G = D + B; // G = D+B
|
|
||||||
const F = G - C; // F = G-C
|
|
||||||
const H = D - B; // H = D-B
|
|
||||||
const X3 = modP(E * F); // X3 = E*F
|
|
||||||
const Y3 = modP(G * H); // Y3 = G*H
|
|
||||||
const T3 = modP(E * H); // T3 = E*H
|
|
||||||
const Z3 = modP(F * G); // Z3 = F*G
|
|
||||||
return new Point(X3, Y3, Z3, T3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast algo for adding 2 Extended Points.
|
|
||||||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
|
||||||
// Cost: 9M + 1*a + 1*d + 7add.
|
|
||||||
add(other: Point) {
|
|
||||||
isPoint(other);
|
|
||||||
const { a, d } = CURVE;
|
|
||||||
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
|
||||||
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;
|
|
||||||
// Faster algo for adding 2 Extended Points when curve's a=-1.
|
|
||||||
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
|
|
||||||
// Cost: 8M + 8add + 2*2.
|
|
||||||
// Note: It does not check whether the `other` point is valid.
|
|
||||||
if (a === BigInt(-1)) {
|
|
||||||
const A = modP((Y1 - X1) * (Y2 + X2));
|
|
||||||
const B = modP((Y1 + X1) * (Y2 - X2));
|
|
||||||
const F = modP(B - A);
|
|
||||||
if (F === _0n) return this.double(); // Same point. Tests say it doesn't affect timing
|
|
||||||
const C = modP(Z1 * _2n * T2);
|
|
||||||
const D = modP(T1 * _2n * Z2);
|
|
||||||
const E = D + C;
|
|
||||||
const G = B + A;
|
|
||||||
const H = D - C;
|
|
||||||
const X3 = modP(E * F);
|
|
||||||
const Y3 = modP(G * H);
|
|
||||||
const T3 = modP(E * H);
|
|
||||||
const Z3 = modP(F * G);
|
|
||||||
return new Point(X3, Y3, Z3, T3);
|
|
||||||
}
|
|
||||||
const A = modP(X1 * X2); // A = X1*X2
|
|
||||||
const B = modP(Y1 * Y2); // B = Y1*Y2
|
|
||||||
const C = modP(T1 * d * T2); // C = T1*d*T2
|
|
||||||
const D = modP(Z1 * Z2); // D = Z1*Z2
|
|
||||||
const E = modP((X1 + Y1) * (X2 + Y2) - A - B); // E = (X1+Y1)*(X2+Y2)-A-B
|
|
||||||
const F = D - C; // F = D-C
|
|
||||||
const G = D + C; // G = D+C
|
|
||||||
const H = modP(B - a * A); // H = B-a*A
|
|
||||||
const X3 = modP(E * F); // X3 = E*F
|
|
||||||
const Y3 = modP(G * H); // Y3 = G*H
|
|
||||||
const T3 = modP(E * H); // T3 = E*H
|
|
||||||
const Z3 = modP(F * G); // Z3 = F*G
|
|
||||||
|
|
||||||
return new Point(X3, Y3, Z3, T3);
|
|
||||||
}
|
|
||||||
|
|
||||||
subtract(other: Point): Point {
|
|
||||||
return this.add(other.negate());
|
|
||||||
}
|
|
||||||
|
|
||||||
private wNAF(n: bigint): { p: Point; f: Point } {
|
|
||||||
return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant-time multiplication.
|
|
||||||
multiply(scalar: bigint): Point {
|
|
||||||
const { p, f } = this.wNAF(assertInRange(scalar, CURVE_ORDER));
|
|
||||||
return Point.normalizeZ([p, f])[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
|
||||||
// It's faster, but should only be used when you don't care about
|
|
||||||
// an exposed private key e.g. sig verification.
|
|
||||||
// Does NOT allow scalars higher than CURVE.n.
|
|
||||||
multiplyUnsafe(scalar: bigint): Point {
|
|
||||||
let n = assertGE0(scalar); // 0 <= scalar < CURVE.n
|
|
||||||
if (n === _0n) return I;
|
|
||||||
if (this.equals(I) || n === _1n) return this;
|
|
||||||
if (this.equals(G)) return this.wNAF(n).p;
|
|
||||||
return wnaf.unsafeLadder(this, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if point is of small order.
|
|
||||||
// If you add something to small order point, you will have "dirty"
|
|
||||||
// point with torsion component.
|
|
||||||
// Multiplies point by cofactor and checks if the result is 0.
|
|
||||||
isSmallOrder(): boolean {
|
|
||||||
return this.multiplyUnsafe(cofactor).is0();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplies point by curve order and checks if the result is 0.
|
|
||||||
// Returns `false` is the point is dirty.
|
|
||||||
isTorsionFree(): boolean {
|
|
||||||
return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts Extended point to default (x, y) coordinates.
|
|
||||||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
|
||||||
toAffine(iz?: bigint): AffinePoint<bigint> {
|
|
||||||
const { ex: x, ey: y, ez: z } = this;
|
|
||||||
const is0 = this.is0();
|
|
||||||
if (iz == null) iz = is0 ? _8n : (Fp.inv(z) as bigint); // 8 was chosen arbitrarily
|
|
||||||
const ax = modP(x * iz);
|
|
||||||
const ay = modP(y * iz);
|
|
||||||
const zz = modP(z * iz);
|
|
||||||
if (is0) return { x: _0n, y: _1n };
|
|
||||||
if (zz !== _1n) throw new Error('invZ was invalid');
|
|
||||||
return { x: ax, y: ay };
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCofactor(): Point {
|
|
||||||
const { h: cofactor } = CURVE;
|
|
||||||
if (cofactor === _1n) return this;
|
|
||||||
return this.multiplyUnsafe(cofactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts hash string or Uint8Array to Point.
|
|
||||||
// Uses algo from RFC8032 5.1.3.
|
|
||||||
static fromHex(hex: Hex, zip215 = false): Point {
|
|
||||||
const { d, a } = CURVE;
|
|
||||||
const len = Fp.BYTES;
|
|
||||||
hex = ensureBytes('pointHex', hex, len); // copy hex to a new array
|
|
||||||
const normed = hex.slice(); // copy again, we'll manipulate it
|
|
||||||
const lastByte = hex[len - 1]; // select last byte
|
|
||||||
normed[len - 1] = lastByte & ~0x80; // clear last bit
|
|
||||||
const y = ut.bytesToNumberLE(normed);
|
|
||||||
if (y === _0n) {
|
|
||||||
// y=0 is allowed
|
|
||||||
} else {
|
|
||||||
// RFC8032 prohibits >= p, but ZIP215 doesn't
|
|
||||||
if (zip215) assertInRange(y, MASK); // zip215=true [1..P-1] (2^255-19-1 for ed25519)
|
|
||||||
else assertInRange(y, Fp.ORDER); // zip215=false [1..MASK-1] (2^256-1 for ed25519)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
|
|
||||||
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)
|
|
||||||
const y2 = modP(y * y); // denominator is always non-0 mod p.
|
|
||||||
const u = modP(y2 - _1n); // u = y² - 1
|
|
||||||
const v = modP(d * y2 - a); // v = d y² + 1.
|
|
||||||
let { isValid, value: x } = uvRatio(u, v); // √(u/v)
|
|
||||||
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
|
|
||||||
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
|
|
||||||
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
|
|
||||||
if (!zip215 && x === _0n && isLastByteOdd)
|
|
||||||
// if x=0 and x_0 = 1, fail
|
|
||||||
throw new Error('Point.fromHex: x=0 and x_0=1');
|
|
||||||
if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x
|
|
||||||
return Point.fromAffine({ x, y });
|
|
||||||
}
|
|
||||||
static fromPrivateKey(privKey: Hex) {
|
|
||||||
return getExtendedPublicKey(privKey).point;
|
|
||||||
}
|
|
||||||
toRawBytes(): Uint8Array {
|
|
||||||
const { x, y } = this.toAffine();
|
|
||||||
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
|
|
||||||
return bytes; // and use the last byte to encode sign of x
|
|
||||||
}
|
|
||||||
toHex(): string {
|
|
||||||
return ut.bytesToHex(this.toRawBytes()); // Same as toRawBytes, but returns string.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { BASE: G, ZERO: I } = Point;
|
|
||||||
const wnaf = wNAF(Point, nByteLength * 8);
|
|
||||||
|
|
||||||
function modN(a: bigint) {
|
|
||||||
return mod(a, CURVE_ORDER);
|
|
||||||
}
|
|
||||||
// Little-endian SHA512 with modulo n
|
|
||||||
function modN_LE(hash: Uint8Array): bigint {
|
|
||||||
return modN(ut.bytesToNumberLE(hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
|
||||||
function getExtendedPublicKey(key: Hex) {
|
|
||||||
const len = nByteLength;
|
|
||||||
key = ensureBytes('private key', key, len);
|
|
||||||
// Hash private key with curve's hash function to produce uniformingly random input
|
|
||||||
// Check byte lengths: ensure(64, h(ensure(32, key)))
|
|
||||||
const hashed = ensureBytes('hashed private key', cHash(key), 2 * len);
|
|
||||||
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 scalar = modN_LE(head); // The actual private scalar
|
|
||||||
const point = G.multiply(scalar); // Point on Edwards curve aka public key
|
|
||||||
const pointBytes = point.toRawBytes(); // Uint8Array representation
|
|
||||||
return { head, prefix, scalar, point, pointBytes };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared
|
|
||||||
function getPublicKey(privKey: Hex): Uint8Array {
|
|
||||||
return getExtendedPublicKey(privKey).pointBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
|
|
||||||
function hashDomainToScalar(context: Hex = new Uint8Array(), ...msgs: Uint8Array[]) {
|
|
||||||
const msg = ut.concatBytes(...msgs);
|
|
||||||
return modN_LE(cHash(domain(msg, ensureBytes('context', context), !!prehash)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
|
||||||
function sign(msg: Hex, privKey: Hex, options: { context?: Hex } = {}): Uint8Array {
|
|
||||||
msg = ensureBytes('message', msg);
|
|
||||||
if (prehash) msg = prehash(msg); // for ed25519ph etc.
|
|
||||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
|
||||||
const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
|
||||||
const R = G.multiply(r).toRawBytes(); // R = rG
|
|
||||||
const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)
|
|
||||||
const s = modN(r + k * scalar); // S = (r + k * s) mod L
|
|
||||||
assertGE0(s); // 0 <= s < l
|
|
||||||
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
|
|
||||||
return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyOpts: { context?: Hex; zip215?: boolean } = VERIFY_DEFAULT;
|
|
||||||
function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {
|
|
||||||
const { context, zip215 } = options;
|
|
||||||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
|
||||||
sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
|
|
||||||
msg = ensureBytes('message', msg);
|
|
||||||
if (prehash) msg = prehash(msg); // for ed25519ph, etc
|
|
||||||
|
|
||||||
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
|
|
||||||
// zip215: true is good for consensus-critical apps and allows points < 2^256
|
|
||||||
// zip215: false follows RFC8032 / NIST186-5 and restricts points to CURVE.p
|
|
||||||
let A, R, SB;
|
|
||||||
try {
|
|
||||||
A = Point.fromHex(publicKey, zip215);
|
|
||||||
R = Point.fromHex(sig.slice(0, len), zip215);
|
|
||||||
SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!zip215 && A.isSmallOrder()) return false;
|
|
||||||
|
|
||||||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
|
||||||
const RkA = R.add(A.multiplyUnsafe(k));
|
|
||||||
// [8][S]B = [8]R + [8][k]A'
|
|
||||||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
G._setWindowSize(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
|
|
||||||
|
|
||||||
const utils = {
|
|
||||||
getExtendedPublicKey,
|
|
||||||
// ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.
|
|
||||||
randomPrivateKey: (): Uint8Array => randomBytes(Fp.BYTES),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT
|
|
||||||
* values. This slows down first getPublicKey() by milliseconds (see Speed section),
|
|
||||||
* but allows to speed-up subsequent getPublicKey() calls up to 20x.
|
|
||||||
* @param windowSize 2, 4, 8, 16
|
|
||||||
*/
|
|
||||||
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
|
|
||||||
point._setWindowSize(windowSize);
|
|
||||||
point.multiply(BigInt(3));
|
|
||||||
return point;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
CURVE,
|
|
||||||
getPublicKey,
|
|
||||||
sign,
|
|
||||||
verify,
|
|
||||||
ExtendedPoint: Point,
|
|
||||||
utils,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
|
|
||||||
import { mod, IField } from './modular.js';
|
|
||||||
import type { CHash } from './utils.js';
|
|
||||||
import { bytesToNumberBE, abytes, 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 = {
|
|
||||||
DST: UnicodeOrBytes;
|
|
||||||
p: bigint;
|
|
||||||
m: number;
|
|
||||||
k: number;
|
|
||||||
expand: 'xmd' | 'xof';
|
|
||||||
hash: CHash;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
|
|
||||||
const os2ip = bytesToNumberBE;
|
|
||||||
|
|
||||||
// Integer to Octet Stream (numberToBytesBE)
|
|
||||||
function i2osp(value: number, length: number): Uint8Array {
|
|
||||||
if (value < 0 || value >= 1 << (8 * length)) {
|
|
||||||
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
|
|
||||||
}
|
|
||||||
const res = Array.from({ length }).fill(0) as number[];
|
|
||||||
for (let i = length - 1; i >= 0; i--) {
|
|
||||||
res[i] = value & 0xff;
|
|
||||||
value >>>= 8;
|
|
||||||
}
|
|
||||||
return new Uint8Array(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
||||||
const arr = new Uint8Array(a.length);
|
|
||||||
for (let i = 0; i < a.length; i++) {
|
|
||||||
arr[i] = a[i] ^ b[i];
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function anum(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
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1
|
|
||||||
export function expand_message_xmd(
|
|
||||||
msg: Uint8Array,
|
|
||||||
DST: Uint8Array,
|
|
||||||
lenInBytes: number,
|
|
||||||
H: CHash
|
|
||||||
): Uint8Array {
|
|
||||||
abytes(msg);
|
|
||||||
abytes(DST);
|
|
||||||
anum(lenInBytes);
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
|
|
||||||
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
|
||||||
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
|
|
||||||
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
|
||||||
if (ell > 255) throw new Error('Invalid xmd length');
|
|
||||||
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
|
|
||||||
const Z_pad = i2osp(0, r_in_bytes);
|
|
||||||
const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
|
|
||||||
const b = new Array<Uint8Array>(ell);
|
|
||||||
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));
|
|
||||||
for (let i = 1; i <= ell; i++) {
|
|
||||||
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
|
|
||||||
b[i] = H(concatBytes(...args));
|
|
||||||
}
|
|
||||||
const pseudo_random_bytes = concatBytes(...b);
|
|
||||||
return pseudo_random_bytes.slice(0, lenInBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces a uniformly random byte string using an extendable-output function (XOF) H.
|
|
||||||
// 1. The collision resistance of H MUST be at least k bits.
|
|
||||||
// 2. H MUST be an XOF that has been proved indifferentiable from
|
|
||||||
// a random oracle under a reasonable cryptographic assumption.
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2
|
|
||||||
export function expand_message_xof(
|
|
||||||
msg: Uint8Array,
|
|
||||||
DST: Uint8Array,
|
|
||||||
lenInBytes: number,
|
|
||||||
k: number,
|
|
||||||
H: CHash
|
|
||||||
): Uint8Array {
|
|
||||||
abytes(msg);
|
|
||||||
abytes(DST);
|
|
||||||
anum(lenInBytes);
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
|
|
||||||
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
|
||||||
if (DST.length > 255) {
|
|
||||||
const dkLen = Math.ceil((2 * k) / 8);
|
|
||||||
DST = H.create({ dkLen }).update(utf8ToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
|
|
||||||
}
|
|
||||||
if (lenInBytes > 65535 || DST.length > 255)
|
|
||||||
throw new Error('expand_message_xof: invalid lenInBytes');
|
|
||||||
return (
|
|
||||||
H.create({ dkLen: lenInBytes })
|
|
||||||
.update(msg)
|
|
||||||
.update(i2osp(lenInBytes, 2))
|
|
||||||
// 2. DST_prime = DST || I2OSP(len(DST), 1)
|
|
||||||
.update(DST)
|
|
||||||
.update(i2osp(DST.length, 1))
|
|
||||||
.digest()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
|
||||||
* https://www.rfc-editor.org/rfc/rfc9380#section-5.2
|
|
||||||
* @param msg a byte string containing the message to hash
|
|
||||||
* @param count the number of elements of F to output
|
|
||||||
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
|
||||||
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
|
||||||
*/
|
|
||||||
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
|
||||||
validateObject(options, {
|
|
||||||
DST: 'stringOrUint8Array',
|
|
||||||
p: 'bigint',
|
|
||||||
m: 'isSafeInteger',
|
|
||||||
k: 'isSafeInteger',
|
|
||||||
hash: 'hash',
|
|
||||||
});
|
|
||||||
const { p, k, m, hash, expand, DST: _DST } = options;
|
|
||||||
abytes(msg);
|
|
||||||
anum(count);
|
|
||||||
const DST = typeof _DST === 'string' ? utf8ToBytes(_DST) : _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 === '_internal_pass') {
|
|
||||||
// for internal tests only
|
|
||||||
prb = msg;
|
|
||||||
} else {
|
|
||||||
throw new Error('expand must be "xmd" or "xof"');
|
|
||||||
}
|
|
||||||
const u = new Array(count);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const e = new Array(m);
|
|
||||||
for (let j = 0; j < m; j++) {
|
|
||||||
const elm_offset = L * (j + i * m);
|
|
||||||
const tv = prb.subarray(elm_offset, elm_offset + L);
|
|
||||||
e[j] = mod(os2ip(tv), p);
|
|
||||||
}
|
|
||||||
u[i] = e;
|
|
||||||
}
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isogenyMap<T, F extends IField<T>>(field: F, map: [T[], T[], T[], T[]]) {
|
|
||||||
// Make same order as in spec
|
|
||||||
const COEFF = map.map((i) => Array.from(i).reverse());
|
|
||||||
return (x: T, y: T) => {
|
|
||||||
const [xNum, xDen, yNum, yDen] = COEFF.map((val) =>
|
|
||||||
val.reduce((acc, i) => field.add(field.mul(acc, x), i))
|
|
||||||
);
|
|
||||||
x = field.div(xNum, xDen); // xNum / xDen
|
|
||||||
y = field.mul(y, field.div(yNum, yDen)); // y * (yNum / yDev)
|
|
||||||
return { x, y };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface H2CPoint<T> extends Group<H2CPoint<T>> {
|
|
||||||
add(rhs: H2CPoint<T>): H2CPoint<T>;
|
|
||||||
toAffine(iz?: bigint): AffinePoint<T>;
|
|
||||||
clearCofactor(): H2CPoint<T>;
|
|
||||||
assertValidity(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface H2CPointConstructor<T> extends GroupConstructor<H2CPoint<T>> {
|
|
||||||
fromAffine(ap: AffinePoint<T>): H2CPoint<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!)
|
|
||||||
export type htfBasicOpts = { DST: UnicodeOrBytes };
|
|
||||||
|
|
||||||
export function createHasher<T>(
|
|
||||||
Point: H2CPointConstructor<T>,
|
|
||||||
mapToCurve: MapToCurve<T>,
|
|
||||||
def: Opts & { encodeDST?: UnicodeOrBytes }
|
|
||||||
) {
|
|
||||||
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
|
|
||||||
return {
|
|
||||||
// Encodes byte string to elliptic curve.
|
|
||||||
// hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
|
|
||||||
hashToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
|
||||||
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options } as Opts);
|
|
||||||
const u0 = Point.fromAffine(mapToCurve(u[0]));
|
|
||||||
const u1 = Point.fromAffine(mapToCurve(u[1]));
|
|
||||||
const P = u0.add(u1).clearCofactor();
|
|
||||||
P.assertValidity();
|
|
||||||
return P;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Encodes byte string to elliptic curve.
|
|
||||||
// encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3
|
|
||||||
encodeToCurve(msg: Uint8Array, options?: htfBasicOpts) {
|
|
||||||
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options } as Opts);
|
|
||||||
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
|
||||||
P.assertValidity();
|
|
||||||
return P;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,484 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
// Utilities for modular arithmetics and finite fields
|
|
||||||
import {
|
|
||||||
bitMask,
|
|
||||||
numberToBytesBE,
|
|
||||||
numberToBytesLE,
|
|
||||||
bytesToNumberBE,
|
|
||||||
bytesToNumberLE,
|
|
||||||
ensureBytes,
|
|
||||||
validateObject,
|
|
||||||
} from './utils.js';
|
|
||||||
// prettier-ignore
|
|
||||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
|
|
||||||
// prettier-ignore
|
|
||||||
const _4n = BigInt(4), _5n = BigInt(5), _8n = BigInt(8);
|
|
||||||
// prettier-ignore
|
|
||||||
const _9n = BigInt(9), _16n = BigInt(16);
|
|
||||||
|
|
||||||
// Calculates a modulo b
|
|
||||||
export function mod(a: bigint, b: bigint): bigint {
|
|
||||||
const result = a % b;
|
|
||||||
return result >= _0n ? result : b + result;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Efficiently raise num to power and do modular division.
|
|
||||||
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
|
|
||||||
* @example
|
|
||||||
* pow(2n, 6n, 11n) // 64n % 11n == 9n
|
|
||||||
*/
|
|
||||||
// TODO: use field version && remove
|
|
||||||
export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
|
|
||||||
if (modulo <= _0n || power < _0n) throw new Error('Expected power/modulo > 0');
|
|
||||||
if (modulo === _1n) return _0n;
|
|
||||||
let res = _1n;
|
|
||||||
while (power > _0n) {
|
|
||||||
if (power & _1n) res = (res * num) % modulo;
|
|
||||||
num = (num * num) % modulo;
|
|
||||||
power >>= _1n;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
|
|
||||||
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
|
|
||||||
let res = x;
|
|
||||||
while (power-- > _0n) {
|
|
||||||
res *= res;
|
|
||||||
res %= modulo;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inverses number over modulo
|
|
||||||
export function invert(number: bigint, modulo: bigint): bigint {
|
|
||||||
if (number === _0n || modulo <= _0n) {
|
|
||||||
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
|
|
||||||
}
|
|
||||||
// Euclidean 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 b = modulo;
|
|
||||||
// prettier-ignore
|
|
||||||
let x = _0n, y = _1n, u = _1n, v = _0n;
|
|
||||||
while (a !== _0n) {
|
|
||||||
// JIT applies optimization if those two lines follow each other
|
|
||||||
const q = b / a;
|
|
||||||
const r = b % a;
|
|
||||||
const m = x - u * q;
|
|
||||||
const n = y - v * q;
|
|
||||||
// prettier-ignore
|
|
||||||
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
||||||
}
|
|
||||||
const gcd = b;
|
|
||||||
if (gcd !== _1n) throw new Error('invert: does not exist');
|
|
||||||
return mod(x, modulo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tonelli-Shanks square root search algorithm.
|
|
||||||
* 1. https://eprint.iacr.org/2012/685.pdf (page 12)
|
|
||||||
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
|
|
||||||
* Will start an infinite loop if field order P is not prime.
|
|
||||||
* @param P field order
|
|
||||||
* @returns function that takes field Fp (created from P) and number n
|
|
||||||
*/
|
|
||||||
export function tonelliShanks(P: bigint) {
|
|
||||||
// Legendre constant: used to calculate Legendre symbol (a | p),
|
|
||||||
// which denotes the value of a^((p-1)/2) (mod p).
|
|
||||||
// (a | p) ≡ 1 if a is a square (mod p)
|
|
||||||
// (a | p) ≡ -1 if a is not a square (mod p)
|
|
||||||
// (a | p) ≡ 0 if a ≡ 0 (mod p)
|
|
||||||
const legendreC = (P - _1n) / _2n;
|
|
||||||
|
|
||||||
let Q: bigint, S: number, Z: bigint;
|
|
||||||
// Step 1: By factoring out powers of 2 from p - 1,
|
|
||||||
// find q and s such that p - 1 = q*(2^s) with q odd
|
|
||||||
for (Q = P - _1n, S = 0; Q % _2n === _0n; Q /= _2n, S++);
|
|
||||||
|
|
||||||
// Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq
|
|
||||||
for (Z = _2n; Z < P && pow(Z, legendreC, P) !== P - _1n; Z++);
|
|
||||||
|
|
||||||
// Fast-path
|
|
||||||
if (S === 1) {
|
|
||||||
const p1div4 = (P + _1n) / _4n;
|
|
||||||
return function tonelliFast<T>(Fp: IField<T>, n: T) {
|
|
||||||
const root = Fp.pow(n, p1div4);
|
|
||||||
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
|
|
||||||
return root;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow-path
|
|
||||||
const Q1div2 = (Q + _1n) / _2n;
|
|
||||||
return function tonelliSlow<T>(Fp: IField<T>, n: T): T {
|
|
||||||
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
|
|
||||||
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root');
|
|
||||||
let r = S;
|
|
||||||
// TODO: will fail at Fp2/etc
|
|
||||||
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
|
|
||||||
let x = Fp.pow(n, Q1div2); // first guess at the square root
|
|
||||||
let b = Fp.pow(n, Q); // first guess at the fudge factor
|
|
||||||
|
|
||||||
while (!Fp.eql(b, Fp.ONE)) {
|
|
||||||
if (Fp.eql(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
|
|
||||||
// Find m such b^(2^m)==1
|
|
||||||
let m = 1;
|
|
||||||
for (let t2 = Fp.sqr(b); m < r; m++) {
|
|
||||||
if (Fp.eql(t2, Fp.ONE)) break;
|
|
||||||
t2 = Fp.sqr(t2); // t2 *= t2
|
|
||||||
}
|
|
||||||
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
|
|
||||||
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
|
|
||||||
g = Fp.sqr(ge); // g = ge * ge
|
|
||||||
x = Fp.mul(x, ge); // x *= ge
|
|
||||||
b = Fp.mul(b, g); // b *= g
|
|
||||||
r = m;
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FpSqrt(P: bigint) {
|
|
||||||
// NOTE: different algorithms can give different roots, it is up to user to decide which one they want.
|
|
||||||
// For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).
|
|
||||||
|
|
||||||
// P ≡ 3 (mod 4)
|
|
||||||
// √n = n^((P+1)/4)
|
|
||||||
if (P % _4n === _3n) {
|
|
||||||
// Not all roots possible!
|
|
||||||
// const ORDER =
|
|
||||||
// 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn;
|
|
||||||
// const NUM = 72057594037927816n;
|
|
||||||
const p1div4 = (P + _1n) / _4n;
|
|
||||||
return function sqrt3mod4<T>(Fp: IField<T>, n: T) {
|
|
||||||
const root = Fp.pow(n, p1div4);
|
|
||||||
// Throw if root**2 != n
|
|
||||||
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
|
|
||||||
return root;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10)
|
|
||||||
if (P % _8n === _5n) {
|
|
||||||
const c1 = (P - _5n) / _8n;
|
|
||||||
return function sqrt5mod8<T>(Fp: IField<T>, n: T) {
|
|
||||||
const n2 = Fp.mul(n, _2n);
|
|
||||||
const v = Fp.pow(n2, c1);
|
|
||||||
const nv = Fp.mul(n, v);
|
|
||||||
const i = Fp.mul(Fp.mul(nv, _2n), v);
|
|
||||||
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
|
|
||||||
if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');
|
|
||||||
return root;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// P ≡ 9 (mod 16)
|
|
||||||
if (P % _16n === _9n) {
|
|
||||||
// NOTE: tonelli is too slow for bls-Fp2 calculations even on start
|
|
||||||
// Means we cannot use sqrt for constants at all!
|
|
||||||
//
|
|
||||||
// const c1 = Fp.sqrt(Fp.negate(Fp.ONE)); // 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F
|
|
||||||
// const c2 = Fp.sqrt(c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F
|
|
||||||
// const c3 = Fp.sqrt(Fp.negate(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F
|
|
||||||
// const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic
|
|
||||||
// sqrt = (x) => {
|
|
||||||
// let tv1 = Fp.pow(x, c4); // 1. tv1 = x^c4
|
|
||||||
// let tv2 = Fp.mul(c1, tv1); // 2. tv2 = c1 * tv1
|
|
||||||
// const tv3 = Fp.mul(c2, tv1); // 3. tv3 = c2 * tv1
|
|
||||||
// let tv4 = Fp.mul(c3, tv1); // 4. tv4 = c3 * tv1
|
|
||||||
// const e1 = Fp.equals(Fp.square(tv2), x); // 5. e1 = (tv2^2) == x
|
|
||||||
// const e2 = Fp.equals(Fp.square(tv3), x); // 6. e2 = (tv3^2) == x
|
|
||||||
// tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x
|
|
||||||
// tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x
|
|
||||||
// const e3 = Fp.equals(Fp.square(tv2), x); // 9. e3 = (tv2^2) == x
|
|
||||||
// return Fp.cmov(tv1, tv2, e3); // 10. z = CMOV(tv1, tv2, e3) # Select the sqrt from tv1 and tv2
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other cases: Tonelli-Shanks algorithm
|
|
||||||
return tonelliShanks(P);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Little-endian check for first LE bit (last BE bit);
|
|
||||||
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
|
|
||||||
|
|
||||||
// Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
|
|
||||||
export interface IField<T> {
|
|
||||||
ORDER: bigint;
|
|
||||||
BYTES: number;
|
|
||||||
BITS: number;
|
|
||||||
MASK: bigint;
|
|
||||||
ZERO: T;
|
|
||||||
ONE: T;
|
|
||||||
// 1-arg
|
|
||||||
create: (num: T) => T;
|
|
||||||
isValid: (num: T) => boolean;
|
|
||||||
is0: (num: T) => boolean;
|
|
||||||
neg(num: T): T;
|
|
||||||
inv(num: T): T;
|
|
||||||
sqrt(num: T): T;
|
|
||||||
sqr(num: T): T;
|
|
||||||
// 2-args
|
|
||||||
eql(lhs: T, rhs: T): boolean;
|
|
||||||
add(lhs: T, rhs: T): T;
|
|
||||||
sub(lhs: T, rhs: T): T;
|
|
||||||
mul(lhs: T, rhs: T | bigint): T;
|
|
||||||
pow(lhs: T, power: bigint): T;
|
|
||||||
div(lhs: T, rhs: T | bigint): T;
|
|
||||||
// N for NonNormalized (for now)
|
|
||||||
addN(lhs: T, rhs: T): T;
|
|
||||||
subN(lhs: T, rhs: T): T;
|
|
||||||
mulN(lhs: T, rhs: T | bigint): T;
|
|
||||||
sqrN(num: T): T;
|
|
||||||
|
|
||||||
// Optional
|
|
||||||
// Should be same as sgn0 function in
|
|
||||||
// [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
|
|
||||||
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
|
|
||||||
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
|
|
||||||
// legendre?(num: T): T;
|
|
||||||
pow(lhs: T, power: bigint): T;
|
|
||||||
invertBatch: (lst: T[]) => T[];
|
|
||||||
toBytes(num: T): Uint8Array;
|
|
||||||
fromBytes(bytes: Uint8Array): T;
|
|
||||||
// If c is False, CMOV returns a, otherwise it returns b.
|
|
||||||
cmov(a: T, b: T, c: boolean): T;
|
|
||||||
}
|
|
||||||
// prettier-ignore
|
|
||||||
const FIELD_FIELDS = [
|
|
||||||
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
|
|
||||||
'eql', 'add', 'sub', 'mul', 'pow', 'div',
|
|
||||||
'addN', 'subN', 'mulN', 'sqrN'
|
|
||||||
] as const;
|
|
||||||
export function validateField<T>(field: IField<T>) {
|
|
||||||
const initial = {
|
|
||||||
ORDER: 'bigint',
|
|
||||||
MASK: 'bigint',
|
|
||||||
BYTES: 'isSafeInteger',
|
|
||||||
BITS: 'isSafeInteger',
|
|
||||||
} as Record<string, string>;
|
|
||||||
const opts = FIELD_FIELDS.reduce((map, val: string) => {
|
|
||||||
map[val] = 'function';
|
|
||||||
return map;
|
|
||||||
}, initial);
|
|
||||||
return validateObject(field, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic field functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as `pow` but for Fp: non-constant-time.
|
|
||||||
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
|
|
||||||
*/
|
|
||||||
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
|
||||||
// Should have same speed as pow for bigints
|
|
||||||
// TODO: benchmark!
|
|
||||||
if (power < _0n) throw new Error('Expected power > 0');
|
|
||||||
if (power === _0n) return f.ONE;
|
|
||||||
if (power === _1n) return num;
|
|
||||||
let p = f.ONE;
|
|
||||||
let d = num;
|
|
||||||
while (power > _0n) {
|
|
||||||
if (power & _1n) p = f.mul(p, d);
|
|
||||||
d = f.sqr(d);
|
|
||||||
power >>= _1n;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Efficiently invert an array of Field elements.
|
|
||||||
* `inv(0)` will return `undefined` here: make sure to throw an error.
|
|
||||||
*/
|
|
||||||
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
|
|
||||||
const tmp = new Array(nums.length);
|
|
||||||
// Walk from first to last, multiply them by each other MOD p
|
|
||||||
const lastMultiplied = nums.reduce((acc, num, i) => {
|
|
||||||
if (f.is0(num)) return acc;
|
|
||||||
tmp[i] = acc;
|
|
||||||
return f.mul(acc, num);
|
|
||||||
}, f.ONE);
|
|
||||||
// Invert last element
|
|
||||||
const inverted = f.inv(lastMultiplied);
|
|
||||||
// Walk from last to first, multiply them by inverted each other MOD p
|
|
||||||
nums.reduceRight((acc, num, i) => {
|
|
||||||
if (f.is0(num)) return acc;
|
|
||||||
tmp[i] = f.mul(acc, tmp[i]);
|
|
||||||
return f.mul(acc, num);
|
|
||||||
}, inverted);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FpDiv<T>(f: IField<T>, lhs: T, rhs: T | bigint): T {
|
|
||||||
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function returns True whenever the value x is a square in the field F.
|
|
||||||
export function FpIsSquare<T>(f: IField<T>) {
|
|
||||||
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
|
|
||||||
return (x: T): boolean => {
|
|
||||||
const p = f.pow(x, legendreConst);
|
|
||||||
return f.eql(p, f.ZERO) || f.eql(p, f.ONE);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// CURVE.n lengths
|
|
||||||
export function nLength(n: bigint, nBitLength?: number) {
|
|
||||||
// Bit size, byte size of CURVE.n
|
|
||||||
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
||||||
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
||||||
return { nBitLength: _nBitLength, nByteLength };
|
|
||||||
}
|
|
||||||
|
|
||||||
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
|
|
||||||
/**
|
|
||||||
* Initializes a finite field over prime. **Non-primes are not supported.**
|
|
||||||
* Do not init in loop: slow. Very fragile: always run a benchmark on a change.
|
|
||||||
* Major performance optimizations:
|
|
||||||
* * a) denormalized operations like mulN instead of mul
|
|
||||||
* * b) same object shape: never add or remove keys
|
|
||||||
* * c) Object.freeze
|
|
||||||
* @param ORDER prime positive bigint
|
|
||||||
* @param bitLen how many bits the field consumes
|
|
||||||
* @param isLE (def: false) if encoding / decoding should be in little-endian
|
|
||||||
* @param redef optional faster redefinitions of sqrt and other methods
|
|
||||||
*/
|
|
||||||
export function Field(
|
|
||||||
ORDER: bigint,
|
|
||||||
bitLen?: number,
|
|
||||||
isLE = false,
|
|
||||||
redef: Partial<IField<bigint>> = {}
|
|
||||||
): Readonly<FpField> {
|
|
||||||
if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
|
|
||||||
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
|
||||||
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
|
|
||||||
const sqrtP = FpSqrt(ORDER);
|
|
||||||
const f: Readonly<FpField> = Object.freeze({
|
|
||||||
ORDER,
|
|
||||||
BITS,
|
|
||||||
BYTES,
|
|
||||||
MASK: bitMask(BITS),
|
|
||||||
ZERO: _0n,
|
|
||||||
ONE: _1n,
|
|
||||||
create: (num) => mod(num, ORDER),
|
|
||||||
isValid: (num) => {
|
|
||||||
if (typeof num !== 'bigint')
|
|
||||||
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
|
||||||
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
|
|
||||||
},
|
|
||||||
is0: (num) => num === _0n,
|
|
||||||
isOdd: (num) => (num & _1n) === _1n,
|
|
||||||
neg: (num) => mod(-num, ORDER),
|
|
||||||
eql: (lhs, rhs) => lhs === rhs,
|
|
||||||
|
|
||||||
sqr: (num) => mod(num * num, ORDER),
|
|
||||||
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
|
||||||
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
|
|
||||||
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
|
|
||||||
pow: (num, power) => FpPow(f, num, power),
|
|
||||||
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
|
|
||||||
|
|
||||||
// Same as above, but doesn't normalize
|
|
||||||
sqrN: (num) => num * num,
|
|
||||||
addN: (lhs, rhs) => lhs + rhs,
|
|
||||||
subN: (lhs, rhs) => lhs - rhs,
|
|
||||||
mulN: (lhs, rhs) => lhs * rhs,
|
|
||||||
|
|
||||||
inv: (num) => invert(num, ORDER),
|
|
||||||
sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
|
|
||||||
invertBatch: (lst) => FpInvertBatch(f, lst),
|
|
||||||
// TODO: do we really need constant cmov?
|
|
||||||
// We don't have const-time bigints anyway, so probably will be not very useful
|
|
||||||
cmov: (a, b, c) => (c ? b : a),
|
|
||||||
toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
|
|
||||||
fromBytes: (bytes) => {
|
|
||||||
if (bytes.length !== BYTES)
|
|
||||||
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
|
|
||||||
return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
|
|
||||||
},
|
|
||||||
} as FpField);
|
|
||||||
return Object.freeze(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FpSqrtOdd<T>(Fp: IField<T>, elm: T) {
|
|
||||||
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
|
|
||||||
const root = Fp.sqrt(elm);
|
|
||||||
return Fp.isOdd(root) ? root : Fp.neg(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
|
|
||||||
if (!Fp.isOdd) throw new Error(`Field doesn't have isOdd`);
|
|
||||||
const root = Fp.sqrt(elm);
|
|
||||||
return Fp.isOdd(root) ? Fp.neg(root) : root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Constant-time" private key generation utility.
|
|
||||||
* Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
|
|
||||||
* Which makes it slightly more biased, less secure.
|
|
||||||
* @deprecated use mapKeyToField instead
|
|
||||||
*/
|
|
||||||
export function hashToPrivateScalar(
|
|
||||||
hash: string | Uint8Array,
|
|
||||||
groupOrder: bigint,
|
|
||||||
isLE = false
|
|
||||||
): bigint {
|
|
||||||
hash = ensureBytes('privateHash', hash);
|
|
||||||
const hashLen = hash.length;
|
|
||||||
const minLen = nLength(groupOrder).nByteLength + 8;
|
|
||||||
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
|
||||||
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
|
|
||||||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
||||||
return mod(num, groupOrder - _1n) + _1n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns total number of bytes consumed by the field element.
|
|
||||||
* For example, 32 bytes for usual 256-bit weierstrass curve.
|
|
||||||
* @param fieldOrder number of field elements, usually CURVE.n
|
|
||||||
* @returns byte length of field
|
|
||||||
*/
|
|
||||||
export function getFieldBytesLength(fieldOrder: bigint): number {
|
|
||||||
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
|
|
||||||
const bitLength = fieldOrder.toString(2).length;
|
|
||||||
return Math.ceil(bitLength / 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns minimal amount of bytes that can be safely reduced
|
|
||||||
* by field order.
|
|
||||||
* Should be 2^-128 for 128-bit curve such as P256.
|
|
||||||
* @param fieldOrder number of field elements, usually CURVE.n
|
|
||||||
* @returns byte length of target hash
|
|
||||||
*/
|
|
||||||
export function getMinHashLength(fieldOrder: bigint): number {
|
|
||||||
const length = getFieldBytesLength(fieldOrder);
|
|
||||||
return length + Math.ceil(length / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Constant-time" private key generation utility.
|
|
||||||
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
||||||
* and convert them into private scalar, with the modulo bias being negligible.
|
|
||||||
* Needs at least 48 bytes of input for 32-byte private key.
|
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
|
|
||||||
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
|
|
||||||
* @param hash hash output from SHA3 or a similar function
|
|
||||||
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
|
|
||||||
* @param isLE interpret hash bytes as LE num
|
|
||||||
* @returns valid private scalar
|
|
||||||
*/
|
|
||||||
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
|
|
||||||
const len = key.length;
|
|
||||||
const fieldLen = getFieldBytesLength(fieldOrder);
|
|
||||||
const minLen = getMinHashLength(fieldOrder);
|
|
||||||
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
|
|
||||||
if (len < 16 || len < minLen || len > 1024)
|
|
||||||
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
|
|
||||||
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
|
|
||||||
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
|
|
||||||
const reduced = mod(num, fieldOrder - _1n) + _1n;
|
|
||||||
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
||||||
}
|
|
||||||
@ -1,187 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { mod, pow } from './modular.js';
|
|
||||||
import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
|
|
||||||
|
|
||||||
const _0n = BigInt(0);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
type Hex = string | Uint8Array;
|
|
||||||
|
|
||||||
export type CurveType = {
|
|
||||||
P: bigint; // finite field prime
|
|
||||||
nByteLength: number;
|
|
||||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
|
||||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
|
||||||
a: bigint;
|
|
||||||
montgomeryBits: number;
|
|
||||||
powPminus2?: (x: bigint) => bigint;
|
|
||||||
xyToU?: (x: bigint, y: bigint) => bigint;
|
|
||||||
Gu: bigint;
|
|
||||||
randomBytes?: (bytesLength?: number) => Uint8Array;
|
|
||||||
};
|
|
||||||
export type CurveFn = {
|
|
||||||
scalarMult: (scalar: Hex, u: Hex) => Uint8Array;
|
|
||||||
scalarMultBase: (scalar: Hex) => Uint8Array;
|
|
||||||
getSharedSecret: (privateKeyA: Hex, publicKeyB: Hex) => Uint8Array;
|
|
||||||
getPublicKey: (privateKey: Hex) => Uint8Array;
|
|
||||||
utils: { randomPrivateKey: () => Uint8Array };
|
|
||||||
GuBytes: Uint8Array;
|
|
||||||
};
|
|
||||||
|
|
||||||
function validateOpts(curve: CurveType) {
|
|
||||||
validateObject(
|
|
||||||
curve,
|
|
||||||
{
|
|
||||||
a: 'bigint',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
montgomeryBits: 'isSafeInteger',
|
|
||||||
nByteLength: 'isSafeInteger',
|
|
||||||
adjustScalarBytes: 'function',
|
|
||||||
domain: 'function',
|
|
||||||
powPminus2: 'function',
|
|
||||||
Gu: 'bigint',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Set defaults
|
|
||||||
return Object.freeze({ ...curve } as const);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
|
||||||
// Uses only one coordinate instead of two
|
|
||||||
export function montgomery(curveDef: CurveType): CurveFn {
|
|
||||||
const CURVE = validateOpts(curveDef);
|
|
||||||
const { P } = CURVE;
|
|
||||||
const modP = (n: bigint) => mod(n, P);
|
|
||||||
const montgomeryBits = CURVE.montgomeryBits;
|
|
||||||
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
|
|
||||||
const fieldLen = CURVE.nByteLength;
|
|
||||||
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
|
|
||||||
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => pow(x, P - BigInt(2), P));
|
|
||||||
|
|
||||||
// cswap from RFC7748. But it is not from RFC7748!
|
|
||||||
/*
|
|
||||||
cswap(swap, x_2, x_3):
|
|
||||||
dummy = mask(swap) AND (x_2 XOR x_3)
|
|
||||||
x_2 = x_2 XOR dummy
|
|
||||||
x_3 = x_3 XOR dummy
|
|
||||||
Return (x_2, x_3)
|
|
||||||
Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
|
||||||
and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
|
||||||
*/
|
|
||||||
function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] {
|
|
||||||
const dummy = modP(swap * (x_2 - x_3));
|
|
||||||
x_2 = modP(x_2 - dummy);
|
|
||||||
x_3 = modP(x_3 + dummy);
|
|
||||||
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
|
|
||||||
// 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 scalar by which the point would be multiplied
|
|
||||||
* @returns new Point on Montgomery curve
|
|
||||||
*/
|
|
||||||
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
|
|
||||||
const u = assertFieldElement(pointU);
|
|
||||||
// Section 5: Implementations MUST accept non-canonical values and process them as
|
|
||||||
// if they had been reduced modulo the field prime.
|
|
||||||
const k = assertFieldElement(scalar);
|
|
||||||
const x_1 = u;
|
|
||||||
let x_2 = _1n;
|
|
||||||
let z_2 = _0n;
|
|
||||||
let x_3 = u;
|
|
||||||
let z_3 = _1n;
|
|
||||||
let swap = _0n;
|
|
||||||
let sw: [bigint, bigint];
|
|
||||||
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
|
|
||||||
const k_t = (k >> t) & _1n;
|
|
||||||
swap ^= k_t;
|
|
||||||
sw = cswap(swap, x_2, x_3);
|
|
||||||
x_2 = sw[0];
|
|
||||||
x_3 = sw[1];
|
|
||||||
sw = cswap(swap, z_2, z_3);
|
|
||||||
z_2 = sw[0];
|
|
||||||
z_3 = sw[1];
|
|
||||||
swap = k_t;
|
|
||||||
|
|
||||||
const A = x_2 + z_2;
|
|
||||||
const AA = modP(A * A);
|
|
||||||
const B = x_2 - z_2;
|
|
||||||
const BB = modP(B * B);
|
|
||||||
const E = AA - BB;
|
|
||||||
const C = x_3 + z_3;
|
|
||||||
const D = x_3 - z_3;
|
|
||||||
const DA = modP(D * A);
|
|
||||||
const CB = modP(C * B);
|
|
||||||
const dacb = DA + CB;
|
|
||||||
const da_cb = DA - CB;
|
|
||||||
x_3 = modP(dacb * dacb);
|
|
||||||
z_3 = modP(x_1 * modP(da_cb * da_cb));
|
|
||||||
x_2 = modP(AA * BB);
|
|
||||||
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
||||||
}
|
|
||||||
// (x_2, x_3) = cswap(swap, x_2, x_3)
|
|
||||||
sw = cswap(swap, x_2, x_3);
|
|
||||||
x_2 = sw[0];
|
|
||||||
x_3 = sw[1];
|
|
||||||
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
|
||||||
sw = cswap(swap, z_2, z_3);
|
|
||||||
z_2 = sw[0];
|
|
||||||
z_3 = sw[1];
|
|
||||||
// z_2^(p - 2)
|
|
||||||
const z2 = powPminus2(z_2);
|
|
||||||
// Return x_2 * (z_2^(p - 2))
|
|
||||||
return modP(x_2 * z2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeUCoordinate(u: bigint): Uint8Array {
|
|
||||||
return numberToBytesLE(modP(u), montgomeryBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeUCoordinate(uEnc: Hex): bigint {
|
|
||||||
// Section 5: When receiving such an array, implementations of X25519
|
|
||||||
// MUST mask the most significant bit in the final byte.
|
|
||||||
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
|
|
||||||
if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
|
|
||||||
return bytesToNumberLE(u);
|
|
||||||
}
|
|
||||||
function decodeScalar(n: Hex): bigint {
|
|
||||||
const bytes = ensureBytes('scalar', n);
|
|
||||||
const len = bytes.length;
|
|
||||||
if (len !== montgomeryBytes && len !== fieldLen)
|
|
||||||
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
|
|
||||||
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
||||||
}
|
|
||||||
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
|
||||||
const pointU = decodeUCoordinate(u);
|
|
||||||
const _scalar = decodeScalar(scalar);
|
|
||||||
const pu = montgomeryLadder(pointU, _scalar);
|
|
||||||
// The result was not contributory
|
|
||||||
// https://cr.yp.to/ecdh.html#validate
|
|
||||||
if (pu === _0n) throw new Error('Invalid private or public key received');
|
|
||||||
return encodeUCoordinate(pu);
|
|
||||||
}
|
|
||||||
// Computes public key from private. By doing scalar multiplication of base point.
|
|
||||||
const GuBytes = encodeUCoordinate(CURVE.Gu);
|
|
||||||
function scalarMultBase(scalar: Hex): Uint8Array {
|
|
||||||
return scalarMult(scalar, GuBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
scalarMult,
|
|
||||||
scalarMultBase,
|
|
||||||
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
|
|
||||||
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
|
|
||||||
utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
|
|
||||||
GuBytes: GuBytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
|
|
||||||
import { IField, FpPow, validateField } from './modular.js';
|
|
||||||
// We don't provide any constants, since different implementations use different constants.
|
|
||||||
// For reference constants see './test/poseidon.test.js'.
|
|
||||||
export type PoseidonOpts = {
|
|
||||||
Fp: IField<bigint>;
|
|
||||||
t: number;
|
|
||||||
roundsFull: number;
|
|
||||||
roundsPartial: number;
|
|
||||||
sboxPower?: number;
|
|
||||||
reversePartialPowIdx?: boolean; // Hack for stark
|
|
||||||
mds: bigint[][];
|
|
||||||
roundConstants: bigint[][];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function validateOpts(opts: PoseidonOpts) {
|
|
||||||
const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
|
|
||||||
const { roundsFull, roundsPartial, sboxPower, t } = opts;
|
|
||||||
|
|
||||||
validateField(Fp);
|
|
||||||
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
|
||||||
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
||||||
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MDS is TxT matrix
|
|
||||||
if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
|
|
||||||
const _mds = mds.map((mdsRow) => {
|
|
||||||
if (!Array.isArray(mdsRow) || mdsRow.length !== t)
|
|
||||||
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
|
|
||||||
return mdsRow.map((i) => {
|
|
||||||
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
|
|
||||||
return Fp.create(i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rev !== undefined && typeof rev !== 'boolean')
|
|
||||||
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
|
|
||||||
|
|
||||||
if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
|
|
||||||
const rounds = roundsFull + roundsPartial;
|
|
||||||
|
|
||||||
if (!Array.isArray(rc) || rc.length !== rounds)
|
|
||||||
throw new Error('Poseidon: wrong round constants');
|
|
||||||
const roundConstants = rc.map((rc) => {
|
|
||||||
if (!Array.isArray(rc) || rc.length !== t)
|
|
||||||
throw new Error(`Poseidon wrong round constants: ${rc}`);
|
|
||||||
return rc.map((i) => {
|
|
||||||
if (typeof i !== 'bigint' || !Fp.isValid(i))
|
|
||||||
throw new Error(`Poseidon wrong round constant=${i}`);
|
|
||||||
return Fp.create(i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!sboxPower || ![3, 5, 7].includes(sboxPower))
|
|
||||||
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
|
||||||
const _sboxPower = BigInt(sboxPower);
|
|
||||||
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
|
|
||||||
// Unwrapped sbox power for common cases (195->142μs)
|
|
||||||
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
|
|
||||||
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
|
|
||||||
|
|
||||||
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function splitConstants(rc: bigint[], t: number) {
|
|
||||||
if (typeof t !== 'number') throw new Error('poseidonSplitConstants: wrong t');
|
|
||||||
if (!Array.isArray(rc) || rc.length % t) throw new Error('poseidonSplitConstants: wrong rc');
|
|
||||||
const res = [];
|
|
||||||
let tmp = [];
|
|
||||||
for (let i = 0; i < rc.length; i++) {
|
|
||||||
tmp.push(rc[i]);
|
|
||||||
if (tmp.length === t) {
|
|
||||||
res.push(tmp);
|
|
||||||
tmp = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function poseidon(opts: PoseidonOpts) {
|
|
||||||
const _opts = validateOpts(opts);
|
|
||||||
const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
|
|
||||||
const halfRoundsFull = _opts.roundsFull / 2;
|
|
||||||
const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
|
|
||||||
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
|
|
||||||
values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
|
|
||||||
|
|
||||||
if (isFull) values = values.map((i) => sboxFn(i));
|
|
||||||
else values[partialIdx] = sboxFn(values[partialIdx]);
|
|
||||||
// Matrix multiplication
|
|
||||||
values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
const poseidonHash = function poseidonHash(values: bigint[]) {
|
|
||||||
if (!Array.isArray(values) || values.length !== t)
|
|
||||||
throw new Error(`Poseidon: wrong values (expected array of bigints with length ${t})`);
|
|
||||||
values = values.map((i) => {
|
|
||||||
if (typeof i !== 'bigint') throw new Error(`Poseidon: wrong value=${i} (${typeof i})`);
|
|
||||||
return Fp.create(i);
|
|
||||||
});
|
|
||||||
let round = 0;
|
|
||||||
// Apply r_f/2 full rounds.
|
|
||||||
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
|
||||||
// Apply r_p partial rounds.
|
|
||||||
for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
|
|
||||||
// Apply r_f/2 full rounds.
|
|
||||||
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
|
||||||
|
|
||||||
if (round !== rounds)
|
|
||||||
throw new Error(`Poseidon: wrong number of rounds: last round=${round}, total=${rounds}`);
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
// For verification in tests
|
|
||||||
poseidonHash.roundConstants = roundConstants;
|
|
||||||
return poseidonHash;
|
|
||||||
}
|
|
||||||
@ -1,319 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
// 100 lines of code in the file are duplicated from noble-hashes (utils).
|
|
||||||
// This is OK: `abstract` directory does not use noble-hashes.
|
|
||||||
// User may opt-in into using different hashing library. This way, noble-hashes
|
|
||||||
// won't be included into their bundle.
|
|
||||||
const _0n = BigInt(0);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
const _2n = BigInt(2);
|
|
||||||
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
|
|
||||||
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
|
|
||||||
export type CHash = {
|
|
||||||
(message: Uint8Array | string): Uint8Array;
|
|
||||||
blockLen: number;
|
|
||||||
outputLen: number;
|
|
||||||
create(opts?: { dkLen?: number }): any; // For shake
|
|
||||||
};
|
|
||||||
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
|
||||||
|
|
||||||
export function isBytes(a: unknown): a is Uint8Array {
|
|
||||||
return (
|
|
||||||
a instanceof Uint8Array ||
|
|
||||||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function abytes(item: unknown): void {
|
|
||||||
if (!isBytes(item)) throw new Error('Uint8Array expected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
||||||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
|
|
||||||
i.toString(16).padStart(2, '0')
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
||||||
*/
|
|
||||||
export function bytesToHex(bytes: Uint8Array): string {
|
|
||||||
abytes(bytes);
|
|
||||||
// pre-caching improves the speed 6x
|
|
||||||
let hex = '';
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
hex += hexes[bytes[i]];
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberToHexUnpadded(num: number | bigint): string {
|
|
||||||
const hex = num.toString(16);
|
|
||||||
return hex.length & 1 ? `0${hex}` : hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hexToNumber(hex: string): bigint {
|
|
||||||
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
|
||||||
// Big Endian
|
|
||||||
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use optimized technique to convert hex string to byte array
|
|
||||||
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const;
|
|
||||||
function asciiToBase16(char: number): number | undefined {
|
|
||||||
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0;
|
|
||||||
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10);
|
|
||||||
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
||||||
*/
|
|
||||||
export function hexToBytes(hex: string): Uint8Array {
|
|
||||||
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
|
||||||
const hl = hex.length;
|
|
||||||
const al = hl / 2;
|
|
||||||
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
|
|
||||||
const array = new Uint8Array(al);
|
|
||||||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
||||||
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
||||||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
||||||
if (n1 === undefined || n2 === undefined) {
|
|
||||||
const char = hex[hi] + hex[hi + 1];
|
|
||||||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
||||||
}
|
|
||||||
array[ai] = n1 * 16 + n2;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BE: Big Endian, LE: Little Endian
|
|
||||||
export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
|
||||||
return hexToNumber(bytesToHex(bytes));
|
|
||||||
}
|
|
||||||
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
|
||||||
abytes(bytes);
|
|
||||||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
|
|
||||||
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
|
||||||
}
|
|
||||||
export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
|
|
||||||
return numberToBytesBE(n, len).reverse();
|
|
||||||
}
|
|
||||||
// Unpadded, rarely used
|
|
||||||
export function numberToVarBytesBE(n: number | bigint): Uint8Array {
|
|
||||||
return hexToBytes(numberToHexUnpadded(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes hex string or Uint8Array, converts to Uint8Array.
|
|
||||||
* Validates output length.
|
|
||||||
* Will throw error for other types.
|
|
||||||
* @param title descriptive title for an error e.g. 'private key'
|
|
||||||
* @param hex hex string or Uint8Array
|
|
||||||
* @param expectedLength optional, will compare to result array's length
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
|
|
||||||
let res: Uint8Array;
|
|
||||||
if (typeof hex === 'string') {
|
|
||||||
try {
|
|
||||||
res = hexToBytes(hex);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
|
||||||
}
|
|
||||||
} else if (isBytes(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.
|
|
||||||
*/
|
|
||||||
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
abytes(a);
|
|
||||||
sum += a.length;
|
|
||||||
}
|
|
||||||
const res = new Uint8Array(sum);
|
|
||||||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
res.set(a, pad);
|
|
||||||
pad += a.length;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compares 2 u8a-s in kinda constant time
|
|
||||||
export function equalBytes(a: Uint8Array, b: Uint8Array) {
|
|
||||||
if (a.length !== b.length) return false;
|
|
||||||
let diff = 0;
|
|
||||||
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
||||||
return diff === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global symbols in both browsers and Node.js since v11
|
|
||||||
// See https://github.com/microsoft/TypeScript/issues/31535
|
|
||||||
declare const TextEncoder: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
||||||
*/
|
|
||||||
export function utf8ToBytes(str: string): Uint8Array {
|
|
||||||
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
||||||
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bit operations
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates amount of bits in a bigint.
|
|
||||||
* Same as `n.toString(2).length`
|
|
||||||
*/
|
|
||||||
export function bitLen(n: bigint) {
|
|
||||||
let len;
|
|
||||||
for (len = 0; n > _0n; n >>= _1n, len += 1);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets single bit at position.
|
|
||||||
* NOTE: first bit position is 0 (same as arrays)
|
|
||||||
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
|
|
||||||
*/
|
|
||||||
export function bitGet(n: bigint, pos: number) {
|
|
||||||
return (n >> BigInt(pos)) & _1n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets single bit at position.
|
|
||||||
*/
|
|
||||||
export function bitSet(n: bigint, pos: number, value: boolean) {
|
|
||||||
return n | ((value ? _1n : _0n) << BigInt(pos));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
|
|
||||||
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
|
|
||||||
*/
|
|
||||||
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',
|
|
||||||
stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
|
|
||||||
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' });
|
|
||||||
File diff suppressed because it is too large
Load Diff
1408
src/bls12-381.ts
1408
src/bls12-381.ts
File diff suppressed because it is too large
Load Diff
22
src/bn254.ts
22
src/bn254.ts
@ -1,22 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { weierstrass } from './abstract/weierstrass.js';
|
|
||||||
import { getHash } from './_shortw_utils.js';
|
|
||||||
import { Field } from './abstract/modular.js';
|
|
||||||
/**
|
|
||||||
* bn254 pairing-friendly curve.
|
|
||||||
* Previously known as alt_bn_128, when it had 128-bit security.
|
|
||||||
* Barbulescu-Duquesne 2017 shown it's weaker: just about 100 bits,
|
|
||||||
* so the naming has been adjusted to its prime bit count
|
|
||||||
* https://hal.science/hal-01534101/file/main.pdf
|
|
||||||
*/
|
|
||||||
export const bn254 = weierstrass({
|
|
||||||
a: BigInt(0),
|
|
||||||
b: BigInt(3),
|
|
||||||
Fp: Field(BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')),
|
|
||||||
n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'),
|
|
||||||
Gx: BigInt(1),
|
|
||||||
Gy: BigInt(2),
|
|
||||||
h: BigInt(1),
|
|
||||||
...getHash(sha256),
|
|
||||||
});
|
|
||||||
497
src/ed25519.ts
497
src/ed25519.ts
@ -1,497 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
||||||
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
|
|
||||||
import { montgomery } from './abstract/montgomery.js';
|
|
||||||
import { Field, FpSqrtEven, isNegativeLE, mod, pow2 } from './abstract/modular.js';
|
|
||||||
import {
|
|
||||||
bytesToHex,
|
|
||||||
bytesToNumberLE,
|
|
||||||
ensureBytes,
|
|
||||||
equalBytes,
|
|
||||||
Hex,
|
|
||||||
numberToBytesLE,
|
|
||||||
} from './abstract/utils.js';
|
|
||||||
import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
|
|
||||||
import { AffinePoint, Group } from './abstract/curve.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ed25519 Twisted Edwards curve with following addons:
|
|
||||||
* - X25519 ECDH
|
|
||||||
* - Ristretto cofactor elimination
|
|
||||||
* - Elligator hash-to-group / point indistinguishability
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ED25519_P = BigInt(
|
|
||||||
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
|
||||||
);
|
|
||||||
// √(-1) aka √(a) aka 2^((p-1)/4)
|
|
||||||
const ED25519_SQRT_M1 = BigInt(
|
|
||||||
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
|
||||||
);
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5);
|
|
||||||
// prettier-ignore
|
|
||||||
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
|
||||||
|
|
||||||
function ed25519_pow_2_252_3(x: bigint) {
|
|
||||||
const P = ED25519_P;
|
|
||||||
const x2 = (x * x) % P;
|
|
||||||
const b2 = (x2 * x) % P; // x^3, 11
|
|
||||||
const b4 = (pow2(b2, _2n, P) * b2) % P; // x^15, 1111
|
|
||||||
const b5 = (pow2(b4, _1n, P) * x) % P; // x^31
|
|
||||||
const b10 = (pow2(b5, _5n, P) * b5) % P;
|
|
||||||
const b20 = (pow2(b10, _10n, P) * b10) % P;
|
|
||||||
const b40 = (pow2(b20, _20n, P) * b20) % P;
|
|
||||||
const b80 = (pow2(b40, _40n, P) * b40) % P;
|
|
||||||
const b160 = (pow2(b80, _80n, P) * b80) % P;
|
|
||||||
const b240 = (pow2(b160, _80n, P) * b80) % P;
|
|
||||||
const b250 = (pow2(b240, _10n, P) * b10) % P;
|
|
||||||
const pow_p_5_8 = (pow2(b250, _2n, P) * x) % P;
|
|
||||||
// ^ To pow to (p+3)/8, multiply it by x.
|
|
||||||
return { pow_p_5_8, b2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
|
||||||
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
|
|
||||||
// set the three least significant bits of the first byte
|
|
||||||
bytes[0] &= 248; // 0b1111_1000
|
|
||||||
// and the most significant bit of the last to zero,
|
|
||||||
bytes[31] &= 127; // 0b0111_1111
|
|
||||||
// set the second most significant bit of the last byte to 1
|
|
||||||
bytes[31] |= 64; // 0b0100_0000
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sqrt(u/v)
|
|
||||||
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
|
||||||
const P = ED25519_P;
|
|
||||||
const v3 = mod(v * v * v, P); // v³
|
|
||||||
const v7 = mod(v3 * v3 * v, P); // v⁷
|
|
||||||
// (p+3)/8 and (p-5)/8
|
|
||||||
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
|
||||||
let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8
|
|
||||||
const vx2 = mod(v * x * x, P); // vx²
|
|
||||||
const root1 = x; // First root candidate
|
|
||||||
const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate
|
|
||||||
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
|
||||||
const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
|
||||||
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)
|
|
||||||
if (useRoot1) x = root1;
|
|
||||||
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
|
|
||||||
if (isNegativeLE(x, P)) x = mod(-x, P);
|
|
||||||
return { isValid: useRoot1 || useRoot2, value: x };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just in case
|
|
||||||
export const ED25519_TORSION_SUBGROUP = [
|
|
||||||
'0100000000000000000000000000000000000000000000000000000000000000',
|
|
||||||
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',
|
|
||||||
'0000000000000000000000000000000000000000000000000000000000000080',
|
|
||||||
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05',
|
|
||||||
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
|
||||||
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85',
|
|
||||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
|
||||||
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
|
|
||||||
];
|
|
||||||
|
|
||||||
const Fp = Field(ED25519_P, undefined, true);
|
|
||||||
|
|
||||||
const ed25519Defaults = {
|
|
||||||
// Param: a
|
|
||||||
a: BigInt(-1), // Fp.create(-1) is proper; our way still works and is faster
|
|
||||||
// d is equal to -121665/121666 over finite field.
|
|
||||||
// Negative number is P - number, and division is invert(number, P)
|
|
||||||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
|
|
||||||
// Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n
|
|
||||||
Fp,
|
|
||||||
// Subgroup order: how many points curve has
|
|
||||||
// 2n**252n + 27742317777372353535851937790883648493n;
|
|
||||||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
|
||||||
// Cofactor
|
|
||||||
h: BigInt(8),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),
|
|
||||||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),
|
|
||||||
hash: sha512,
|
|
||||||
randomBytes,
|
|
||||||
adjustScalarBytes,
|
|
||||||
// dom2
|
|
||||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
|
||||||
// Constant-time, u/√v
|
|
||||||
uvRatio,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const ed25519 = /* @__PURE__ */ twistedEdwards(ed25519Defaults);
|
|
||||||
|
|
||||||
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
|
||||||
if (ctx.length > 255) throw new Error('Context is too big');
|
|
||||||
return concatBytes(
|
|
||||||
utf8ToBytes('SigEd25519 no Ed25519 collisions'),
|
|
||||||
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
|
||||||
ctx,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ed25519ctx = /* @__PURE__ */ twistedEdwards({
|
|
||||||
...ed25519Defaults,
|
|
||||||
domain: ed25519_domain,
|
|
||||||
});
|
|
||||||
export const ed25519ph = /* @__PURE__ */ twistedEdwards({
|
|
||||||
...ed25519Defaults,
|
|
||||||
domain: ed25519_domain,
|
|
||||||
prehash: sha512,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const x25519 = /* @__PURE__ */ (() =>
|
|
||||||
montgomery({
|
|
||||||
P: ED25519_P,
|
|
||||||
a: BigInt(486662),
|
|
||||||
montgomeryBits: 255, // n is 253 bits
|
|
||||||
nByteLength: 32,
|
|
||||||
Gu: BigInt(9),
|
|
||||||
powPminus2: (x: bigint): bigint => {
|
|
||||||
const P = ED25519_P;
|
|
||||||
// x^(p-2) aka x^(2^255-21)
|
|
||||||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
|
||||||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
|
|
||||||
},
|
|
||||||
adjustScalarBytes,
|
|
||||||
randomBytes,
|
|
||||||
}))();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts ed25519 public key to x25519 public key. Uses formula:
|
|
||||||
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
|
|
||||||
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
|
|
||||||
* @example
|
|
||||||
* const someonesPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
|
|
||||||
* const aPriv = x25519.utils.randomPrivateKey();
|
|
||||||
* x25519.getSharedSecret(aPriv, edwardsToMontgomeryPub(someonesPub))
|
|
||||||
*/
|
|
||||||
export function edwardsToMontgomeryPub(edwardsPub: Hex): Uint8Array {
|
|
||||||
const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
return Fp.toBytes(Fp.create((_1n + y) * Fp.inv(_1n - y)));
|
|
||||||
}
|
|
||||||
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts ed25519 secret key to x25519 secret key.
|
|
||||||
* @example
|
|
||||||
* const someonesPub = x25519.getPublicKey(x25519.utils.randomPrivateKey());
|
|
||||||
* const aPriv = ed25519.utils.randomPrivateKey();
|
|
||||||
* x25519.getSharedSecret(edwardsToMontgomeryPriv(aPriv), someonesPub)
|
|
||||||
*/
|
|
||||||
export function edwardsToMontgomeryPriv(edwardsPriv: Uint8Array): Uint8Array {
|
|
||||||
const hashed = ed25519Defaults.hash(edwardsPriv.subarray(0, 32));
|
|
||||||
return ed25519Defaults.adjustScalarBytes(hashed).subarray(0, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
|
|
||||||
// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
|
|
||||||
// SageMath returns different root first and everything falls apart
|
|
||||||
|
|
||||||
const ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8); // 1. c1 = (q + 3) / 8 # Integer arithmetic
|
|
||||||
|
|
||||||
const ELL2_C2 = Fp.pow(_2n, ELL2_C1); // 2. c2 = 2^c1
|
|
||||||
const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE)); // 3. c3 = sqrt(-1)
|
|
||||||
const ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8); // 4. c4 = (q - 5) / 8 # Integer arithmetic
|
|
||||||
const ELL2_J = BigInt(486662);
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
function map_to_curve_elligator2_curve25519(u: bigint) {
|
|
||||||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
|
|
||||||
tv1 = Fp.mul(tv1, _2n); // 2. tv1 = 2 * tv1
|
|
||||||
let xd = Fp.add(tv1, Fp.ONE); // 3. xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not
|
|
||||||
let x1n = Fp.neg(ELL2_J); // 4. x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2)
|
|
||||||
let tv2 = Fp.sqr(xd); // 5. tv2 = xd^2
|
|
||||||
let gxd = Fp.mul(tv2, xd); // 6. gxd = tv2 * xd # gxd = xd^3
|
|
||||||
let gx1 = Fp.mul(tv1, ELL2_J); // 7. gx1 = J * tv1 # x1n + J * xd
|
|
||||||
gx1 = Fp.mul(gx1, x1n); // 8. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
|
|
||||||
gx1 = Fp.add(gx1, tv2); // 9. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
|
|
||||||
gx1 = Fp.mul(gx1, x1n); // 10. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
|
|
||||||
let tv3 = Fp.sqr(gxd); // 11. tv3 = gxd^2
|
|
||||||
tv2 = Fp.sqr(tv3); // 12. tv2 = tv3^2 # gxd^4
|
|
||||||
tv3 = Fp.mul(tv3, gxd); // 13. tv3 = tv3 * gxd # gxd^3
|
|
||||||
tv3 = Fp.mul(tv3, gx1); // 14. tv3 = tv3 * gx1 # gx1 * gxd^3
|
|
||||||
tv2 = Fp.mul(tv2, tv3); // 15. tv2 = tv2 * tv3 # gx1 * gxd^7
|
|
||||||
let y11 = Fp.pow(tv2, ELL2_C4); // 16. y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8)
|
|
||||||
y11 = Fp.mul(y11, tv3); // 17. y11 = y11 * tv3 # gx1*gxd^3*(gx1*gxd^7)^((p-5)/8)
|
|
||||||
let y12 = Fp.mul(y11, ELL2_C3); // 18. y12 = y11 * c3
|
|
||||||
tv2 = Fp.sqr(y11); // 19. tv2 = y11^2
|
|
||||||
tv2 = Fp.mul(tv2, gxd); // 20. tv2 = tv2 * gxd
|
|
||||||
let e1 = Fp.eql(tv2, gx1); // 21. e1 = tv2 == gx1
|
|
||||||
let y1 = Fp.cmov(y12, y11, e1); // 22. y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt
|
|
||||||
let x2n = Fp.mul(x1n, tv1); // 23. x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd
|
|
||||||
let y21 = Fp.mul(y11, u); // 24. y21 = y11 * u
|
|
||||||
y21 = Fp.mul(y21, ELL2_C2); // 25. y21 = y21 * c2
|
|
||||||
let y22 = Fp.mul(y21, ELL2_C3); // 26. y22 = y21 * c3
|
|
||||||
let gx2 = Fp.mul(gx1, tv1); // 27. gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1)
|
|
||||||
tv2 = Fp.sqr(y21); // 28. tv2 = y21^2
|
|
||||||
tv2 = Fp.mul(tv2, gxd); // 29. tv2 = tv2 * gxd
|
|
||||||
let e2 = Fp.eql(tv2, gx2); // 30. e2 = tv2 == gx2
|
|
||||||
let y2 = Fp.cmov(y22, y21, e2); // 31. y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt
|
|
||||||
tv2 = Fp.sqr(y1); // 32. tv2 = y1^2
|
|
||||||
tv2 = Fp.mul(tv2, gxd); // 33. tv2 = tv2 * gxd
|
|
||||||
let e3 = Fp.eql(tv2, gx1); // 34. e3 = tv2 == gx1
|
|
||||||
let xn = Fp.cmov(x2n, x1n, e3); // 35. xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2
|
|
||||||
let y = Fp.cmov(y2, y1, e3); // 36. y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2
|
|
||||||
let e4 = Fp.isOdd(y); // 37. e4 = sgn0(y) == 1 # Fix sign of y
|
|
||||||
y = Fp.cmov(y, Fp.neg(y), e3 !== e4); // 38. y = CMOV(y, -y, e3 XOR e4)
|
|
||||||
return { xMn: xn, xMd: xd, yMn: y, yMd: _1n }; // 39. return (xn, xd, y, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664))); // sgn0(c1) MUST equal 0
|
|
||||||
function map_to_curve_elligator2_edwards25519(u: bigint) {
|
|
||||||
const { xMn, xMd, yMn, yMd } = map_to_curve_elligator2_curve25519(u); // 1. (xMn, xMd, yMn, yMd) =
|
|
||||||
// map_to_curve_elligator2_curve25519(u)
|
|
||||||
let xn = Fp.mul(xMn, yMd); // 2. xn = xMn * yMd
|
|
||||||
xn = Fp.mul(xn, ELL2_C1_EDWARDS); // 3. xn = xn * c1
|
|
||||||
let xd = Fp.mul(xMd, yMn); // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM
|
|
||||||
let yn = Fp.sub(xMn, xMd); // 5. yn = xMn - xMd
|
|
||||||
let yd = Fp.add(xMn, xMd); // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d)
|
|
||||||
let tv1 = Fp.mul(xd, yd); // 7. tv1 = xd * yd
|
|
||||||
let e = Fp.eql(tv1, Fp.ZERO); // 8. e = tv1 == 0
|
|
||||||
xn = Fp.cmov(xn, Fp.ZERO, e); // 9. xn = CMOV(xn, 0, e)
|
|
||||||
xd = Fp.cmov(xd, Fp.ONE, e); // 10. xd = CMOV(xd, 1, e)
|
|
||||||
yn = Fp.cmov(yn, Fp.ONE, e); // 11. yn = CMOV(yn, 1, e)
|
|
||||||
yd = Fp.cmov(yd, Fp.ONE, e); // 12. yd = CMOV(yd, 1, e)
|
|
||||||
|
|
||||||
const inv = Fp.invertBatch([xd, yd]); // batch division
|
|
||||||
return { x: Fp.mul(xn, inv[0]), y: Fp.mul(yn, inv[1]) }; // 13. return (xn, xd, yn, yd)
|
|
||||||
}
|
|
||||||
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(
|
|
||||||
ed25519.ExtendedPoint,
|
|
||||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards25519(scalars[0]),
|
|
||||||
{
|
|
||||||
DST: 'edwards25519_XMD:SHA-512_ELL2_RO_',
|
|
||||||
encodeDST: 'edwards25519_XMD:SHA-512_ELL2_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha512,
|
|
||||||
}
|
|
||||||
))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
|
|
||||||
function assertRstPoint(other: unknown) {
|
|
||||||
if (!(other instanceof RistPoint)) throw new Error('RistrettoPoint expected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// √(-1) aka √(a) aka 2^((p-1)/4)
|
|
||||||
const SQRT_M1 = ED25519_SQRT_M1;
|
|
||||||
// √(ad - 1)
|
|
||||||
const SQRT_AD_MINUS_ONE = BigInt(
|
|
||||||
'25063068953384623474111414158702152701244531502492656460079210482610430750235'
|
|
||||||
);
|
|
||||||
// 1 / √(a-d)
|
|
||||||
const INVSQRT_A_MINUS_D = BigInt(
|
|
||||||
'54469307008909316920995813868745141605393597292927456921205312896311721017578'
|
|
||||||
);
|
|
||||||
// 1-d²
|
|
||||||
const ONE_MINUS_D_SQ = BigInt(
|
|
||||||
'1159843021668779879193775521855586647937357759715417654439879720876111806838'
|
|
||||||
);
|
|
||||||
// (d-1)²
|
|
||||||
const D_MINUS_ONE_SQ = BigInt(
|
|
||||||
'40440834346308536858101042469323190826248399146238708352240133220865137265952'
|
|
||||||
);
|
|
||||||
// Calculates 1/√(number)
|
|
||||||
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
|
||||||
|
|
||||||
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
|
||||||
const bytes255ToNumberLE = (bytes: Uint8Array) =>
|
|
||||||
ed25519.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_255B);
|
|
||||||
|
|
||||||
type ExtendedPoint = ExtPointType;
|
|
||||||
|
|
||||||
// Computes Elligator map for Ristretto
|
|
||||||
// https://ristretto.group/formulas/elligator.html
|
|
||||||
function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
|
||||||
const { d } = ed25519.CURVE;
|
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
|
||||||
const r = mod(SQRT_M1 * r0 * r0); // 1
|
|
||||||
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
|
|
||||||
let c = BigInt(-1); // 3
|
|
||||||
const D = mod((c - d * r) * mod(r + d)); // 4
|
|
||||||
let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D); // 5
|
|
||||||
let s_ = mod(s * r0); // 6
|
|
||||||
if (!isNegativeLE(s_, P)) s_ = mod(-s_);
|
|
||||||
if (!Ns_D_is_sq) s = s_; // 7
|
|
||||||
if (!Ns_D_is_sq) c = r; // 8
|
|
||||||
const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D); // 9
|
|
||||||
const s2 = s * s;
|
|
||||||
const W0 = mod((s + s) * D); // 10
|
|
||||||
const W1 = mod(Nt * SQRT_AD_MINUS_ONE); // 11
|
|
||||||
const W2 = mod(_1n - s2); // 12
|
|
||||||
const W3 = mod(_1n + s2); // 13
|
|
||||||
return new ed25519.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each ed25519/ExtendedPoint has 8 different equivalent points. This can be
|
|
||||||
* a source of bugs for protocols like ring signatures. Ristretto was created to solve this.
|
|
||||||
* Ristretto point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
|
|
||||||
* but it should work in its own namespace: do not combine those two.
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
|
||||||
*/
|
|
||||||
class RistPoint implements Group<RistPoint> {
|
|
||||||
static BASE: RistPoint;
|
|
||||||
static ZERO: RistPoint;
|
|
||||||
// Private property to discourage combining ExtendedPoint + RistrettoPoint
|
|
||||||
// Always use Ristretto encoding/decoding instead.
|
|
||||||
constructor(private readonly ep: ExtendedPoint) {}
|
|
||||||
|
|
||||||
static fromAffine(ap: AffinePoint<bigint>) {
|
|
||||||
return new RistPoint(ed25519.ExtendedPoint.fromAffine(ap));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes uniform output of 64-byte hash function like sha512 and converts it to `RistrettoPoint`.
|
|
||||||
* The hash-to-group operation applies Elligator twice and adds the results.
|
|
||||||
* **Note:** this is one-way map, there is no conversion from point to hash.
|
|
||||||
* https://ristretto.group/formulas/elligator.html
|
|
||||||
* @param hex 64-byte output of a hash function
|
|
||||||
*/
|
|
||||||
static hashToCurve(hex: Hex): RistPoint {
|
|
||||||
hex = ensureBytes('ristrettoHash', hex, 64);
|
|
||||||
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
|
||||||
const R1 = calcElligatorRistrettoMap(r1);
|
|
||||||
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
|
||||||
const R2 = calcElligatorRistrettoMap(r2);
|
|
||||||
return new RistPoint(R1.add(R2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts ristretto-encoded string to ristretto point.
|
|
||||||
* https://ristretto.group/formulas/decoding.html
|
|
||||||
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
|
||||||
*/
|
|
||||||
static fromHex(hex: Hex): RistPoint {
|
|
||||||
hex = ensureBytes('ristrettoHex', hex, 32);
|
|
||||||
const { a, d } = ed25519.CURVE;
|
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
|
||||||
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
|
|
||||||
const s = bytes255ToNumberLE(hex);
|
|
||||||
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
|
||||||
// 3. Check that s is non-negative, or else abort
|
|
||||||
if (!equalBytes(numberToBytesLE(s, 32), hex) || isNegativeLE(s, P)) throw new Error(emsg);
|
|
||||||
const s2 = mod(s * s);
|
|
||||||
const u1 = mod(_1n + a * s2); // 4 (a is -1)
|
|
||||||
const u2 = mod(_1n - a * s2); // 5
|
|
||||||
const u1_2 = mod(u1 * u1);
|
|
||||||
const u2_2 = mod(u2 * u2);
|
|
||||||
const v = mod(a * d * u1_2 - u2_2); // 6
|
|
||||||
const { isValid, value: I } = invertSqrt(mod(v * u2_2)); // 7
|
|
||||||
const Dx = mod(I * u2); // 8
|
|
||||||
const Dy = mod(I * Dx * v); // 9
|
|
||||||
let x = mod((s + s) * Dx); // 10
|
|
||||||
if (isNegativeLE(x, P)) x = mod(-x); // 10
|
|
||||||
const y = mod(u1 * Dy); // 11
|
|
||||||
const t = mod(x * y); // 12
|
|
||||||
if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);
|
|
||||||
return new RistPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes ristretto point to Uint8Array.
|
|
||||||
* https://ristretto.group/formulas/encoding.html
|
|
||||||
*/
|
|
||||||
toRawBytes(): Uint8Array {
|
|
||||||
let { ex: x, ey: y, ez: z, et: t } = this.ep;
|
|
||||||
const P = ed25519.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
|
||||||
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
|
||||||
const u2 = mod(x * y); // 2
|
|
||||||
// Square root always exists
|
|
||||||
const u2sq = mod(u2 * u2);
|
|
||||||
const { value: invsqrt } = invertSqrt(mod(u1 * u2sq)); // 3
|
|
||||||
const D1 = mod(invsqrt * u1); // 4
|
|
||||||
const D2 = mod(invsqrt * u2); // 5
|
|
||||||
const zInv = mod(D1 * D2 * t); // 6
|
|
||||||
let D: bigint; // 7
|
|
||||||
if (isNegativeLE(t * zInv, P)) {
|
|
||||||
let _x = mod(y * SQRT_M1);
|
|
||||||
let _y = mod(x * SQRT_M1);
|
|
||||||
x = _x;
|
|
||||||
y = _y;
|
|
||||||
D = mod(D1 * INVSQRT_A_MINUS_D);
|
|
||||||
} else {
|
|
||||||
D = D2; // 8
|
|
||||||
}
|
|
||||||
if (isNegativeLE(x * zInv, P)) y = mod(-y); // 9
|
|
||||||
let s = mod((z - y) * D); // 10 (check footer's note, no sqrt(-a))
|
|
||||||
if (isNegativeLE(s, P)) s = mod(-s);
|
|
||||||
return numberToBytesLE(s, 32); // 11
|
|
||||||
}
|
|
||||||
|
|
||||||
toHex(): string {
|
|
||||||
return bytesToHex(this.toRawBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return this.toHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare one point to another.
|
|
||||||
equals(other: RistPoint): boolean {
|
|
||||||
assertRstPoint(other);
|
|
||||||
const { ex: X1, ey: Y1 } = this.ep;
|
|
||||||
const { ex: X2, ey: Y2 } = other.ep;
|
|
||||||
const mod = ed25519.CURVE.Fp.create;
|
|
||||||
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
|
||||||
const one = mod(X1 * Y2) === mod(Y1 * X2);
|
|
||||||
const two = mod(Y1 * Y2) === mod(X1 * X2);
|
|
||||||
return one || two;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(other: RistPoint): RistPoint {
|
|
||||||
assertRstPoint(other);
|
|
||||||
return new RistPoint(this.ep.add(other.ep));
|
|
||||||
}
|
|
||||||
|
|
||||||
subtract(other: RistPoint): RistPoint {
|
|
||||||
assertRstPoint(other);
|
|
||||||
return new RistPoint(this.ep.subtract(other.ep));
|
|
||||||
}
|
|
||||||
|
|
||||||
multiply(scalar: bigint): RistPoint {
|
|
||||||
return new RistPoint(this.ep.multiply(scalar));
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplyUnsafe(scalar: bigint): RistPoint {
|
|
||||||
return new RistPoint(this.ep.multiplyUnsafe(scalar));
|
|
||||||
}
|
|
||||||
|
|
||||||
double(): RistPoint {
|
|
||||||
return new RistPoint(this.ep.double());
|
|
||||||
}
|
|
||||||
|
|
||||||
negate(): RistPoint {
|
|
||||||
return new RistPoint(this.ep.negate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const RistrettoPoint = /* @__PURE__ */ (() => {
|
|
||||||
if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);
|
|
||||||
if (!RistPoint.ZERO) RistPoint.ZERO = new RistPoint(ed25519.ExtendedPoint.ZERO);
|
|
||||||
return RistPoint;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Hashing to ristretto255. https://www.rfc-editor.org/rfc/rfc9380#appendix-B
|
|
||||||
export const hashToRistretto255 = (msg: Uint8Array, options: htfBasicOpts) => {
|
|
||||||
const d = options.DST;
|
|
||||||
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
|
||||||
const uniform_bytes = expand_message_xmd(msg, DST, 64, sha512);
|
|
||||||
const P = RistPoint.hashToCurve(uniform_bytes);
|
|
||||||
return P;
|
|
||||||
};
|
|
||||||
export const hash_to_ristretto255 = hashToRistretto255; // legacy
|
|
||||||
480
src/ed448.ts
480
src/ed448.ts
@ -1,480 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { shake256 } from '@noble/hashes/sha3';
|
|
||||||
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
|
||||||
import { ExtPointType, twistedEdwards } from './abstract/edwards.js';
|
|
||||||
import { mod, pow2, Field, isNegativeLE } from './abstract/modular.js';
|
|
||||||
import { montgomery } from './abstract/montgomery.js';
|
|
||||||
import { createHasher, htfBasicOpts, expand_message_xof } from './abstract/hash-to-curve.js';
|
|
||||||
import {
|
|
||||||
bytesToHex,
|
|
||||||
bytesToNumberLE,
|
|
||||||
ensureBytes,
|
|
||||||
equalBytes,
|
|
||||||
Hex,
|
|
||||||
numberToBytesLE,
|
|
||||||
} from './abstract/utils.js';
|
|
||||||
import { AffinePoint, Group } from './abstract/curve.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
|
||||||
* - X448 ECDH
|
|
||||||
* - Decaf cofactor elimination
|
|
||||||
* - Elligator hash-to-group / point indistinguishability
|
|
||||||
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
|
|
||||||
*/
|
|
||||||
|
|
||||||
const shake256_114 = wrapConstructor(() => shake256.create({ dkLen: 114 }));
|
|
||||||
const shake256_64 = wrapConstructor(() => shake256.create({ dkLen: 64 }));
|
|
||||||
const ed448P = BigInt(
|
|
||||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
|
||||||
);
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4), _11n = BigInt(11);
|
|
||||||
// prettier-ignore
|
|
||||||
const _22n = BigInt(22), _44n = BigInt(44), _88n = BigInt(88), _223n = BigInt(223);
|
|
||||||
|
|
||||||
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
|
||||||
// Used for efficient square root calculation.
|
|
||||||
// ((P-3)/4).toString(2) would produce bits [223x 1, 0, 222x 1]
|
|
||||||
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
|
||||||
const P = ed448P;
|
|
||||||
const b2 = (x * x * x) % P;
|
|
||||||
const b3 = (b2 * b2 * x) % P;
|
|
||||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
|
||||||
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
|
||||||
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
|
||||||
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
|
||||||
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
|
||||||
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
|
||||||
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
|
||||||
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
|
||||||
const b222 = (pow2(b220, _2n, P) * b2) % P;
|
|
||||||
const b223 = (pow2(b222, _1n, P) * x) % P;
|
|
||||||
return (pow2(b223, _223n, P) * b222) % P;
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
|
||||||
// Section 5: Likewise, for X448, set the two least significant bits of the first byte to 0, and the most
|
|
||||||
// significant bit of the last byte to 1.
|
|
||||||
bytes[0] &= 252; // 0b11111100
|
|
||||||
// and the most significant bit of the last byte to 1.
|
|
||||||
bytes[55] |= 128; // 0b10000000
|
|
||||||
// NOTE: is is NOOP for 56 bytes scalars (X25519/X448)
|
|
||||||
bytes[56] = 0; // Byte outside of group (456 buts vs 448 bits)
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
|
|
||||||
// Uses algo from RFC8032 5.1.3.
|
|
||||||
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
|
||||||
const P = ed448P;
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc8032#section-5.2.3
|
|
||||||
// To compute the square root of (u/v), the first step is to compute the
|
|
||||||
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
|
||||||
// following trick, to use a single modular powering for both the
|
|
||||||
// inversion of v and the square root:
|
|
||||||
// x = (u/v)^((p+1)/4) = u³v(u⁵v³)^((p-3)/4) (mod p)
|
|
||||||
const u2v = mod(u * u * v, P); // u²v
|
|
||||||
const u3v = mod(u2v * u, P); // u³v
|
|
||||||
const u5v3 = mod(u3v * u2v * v, P); // u⁵v³
|
|
||||||
const root = ed448_pow_Pminus3div4(u5v3);
|
|
||||||
const x = mod(u3v * root, P);
|
|
||||||
// Verify that root is exists
|
|
||||||
const x2 = mod(x * x, P); // x²
|
|
||||||
// If vx² = u, the recovered x-coordinate is x. Otherwise, no
|
|
||||||
// square root exists, and the decoding fails.
|
|
||||||
return { isValid: mod(x2 * v, P) === u, value: x };
|
|
||||||
}
|
|
||||||
|
|
||||||
const Fp = Field(ed448P, 456, true);
|
|
||||||
|
|
||||||
const ED448_DEF = {
|
|
||||||
// Param: a
|
|
||||||
a: BigInt(1),
|
|
||||||
// -39081. Negative number is P - number
|
|
||||||
d: BigInt(
|
|
||||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
|
|
||||||
),
|
|
||||||
// Finite field 𝔽p over which we'll do calculations; 2n**448n - 2n**224n - 1n
|
|
||||||
Fp,
|
|
||||||
// Subgroup order: how many points curve has;
|
|
||||||
// 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
|
||||||
n: BigInt(
|
|
||||||
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
|
||||||
),
|
|
||||||
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
|
|
||||||
nBitLength: 456,
|
|
||||||
// Cofactor
|
|
||||||
h: BigInt(4),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt(
|
|
||||||
'224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710'
|
|
||||||
),
|
|
||||||
Gy: BigInt(
|
|
||||||
'298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660'
|
|
||||||
),
|
|
||||||
// SHAKE256(dom4(phflag,context)||x, 114)
|
|
||||||
hash: shake256_114,
|
|
||||||
randomBytes,
|
|
||||||
adjustScalarBytes,
|
|
||||||
// dom4
|
|
||||||
domain: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
|
|
||||||
if (ctx.length > 255) throw new Error(`Context is too big: ${ctx.length}`);
|
|
||||||
return concatBytes(
|
|
||||||
utf8ToBytes('SigEd448'),
|
|
||||||
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
|
||||||
ctx,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
},
|
|
||||||
uvRatio,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const ed448 = /* @__PURE__ */ twistedEdwards(ED448_DEF);
|
|
||||||
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
|
||||||
export const ed448ph = /* @__PURE__ */ twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
|
|
||||||
|
|
||||||
export const x448 = /* @__PURE__ */ (() =>
|
|
||||||
montgomery({
|
|
||||||
a: BigInt(156326),
|
|
||||||
// RFC 7748 has 56-byte keys, RFC 8032 has 57-byte keys
|
|
||||||
montgomeryBits: 448,
|
|
||||||
nByteLength: 56,
|
|
||||||
P: ed448P,
|
|
||||||
Gu: BigInt(5),
|
|
||||||
powPminus2: (x: bigint): bigint => {
|
|
||||||
const P = ed448P;
|
|
||||||
const Pminus3div4 = ed448_pow_Pminus3div4(x);
|
|
||||||
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
|
|
||||||
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
|
|
||||||
},
|
|
||||||
adjustScalarBytes,
|
|
||||||
randomBytes,
|
|
||||||
}))();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts edwards448 public key to x448 public key. Uses formula:
|
|
||||||
* * `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
|
|
||||||
* * `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
|
|
||||||
* @example
|
|
||||||
* const aPub = ed448.getPublicKey(utils.randomPrivateKey());
|
|
||||||
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
|
|
||||||
*/
|
|
||||||
export function edwardsToMontgomeryPub(edwardsPub: string | Uint8Array): Uint8Array {
|
|
||||||
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
export const edwardsToMontgomery = edwardsToMontgomeryPub; // deprecated
|
|
||||||
// TODO: add edwardsToMontgomeryPriv, similar to ed25519 version
|
|
||||||
|
|
||||||
// Hash To Curve Elligator2 Map
|
|
||||||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
|
|
||||||
const ELL2_J = BigInt(156326);
|
|
||||||
|
|
||||||
function map_to_curve_elligator2_curve448(u: bigint) {
|
|
||||||
let tv1 = Fp.sqr(u); // 1. tv1 = u^2
|
|
||||||
let e1 = Fp.eql(tv1, Fp.ONE); // 2. e1 = tv1 == 1
|
|
||||||
tv1 = Fp.cmov(tv1, Fp.ZERO, e1); // 3. tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0
|
|
||||||
let xd = Fp.sub(Fp.ONE, tv1); // 4. xd = 1 - tv1
|
|
||||||
let x1n = Fp.neg(ELL2_J); // 5. x1n = -J
|
|
||||||
let tv2 = Fp.sqr(xd); // 6. tv2 = xd^2
|
|
||||||
let gxd = Fp.mul(tv2, xd); // 7. gxd = tv2 * xd # gxd = xd^3
|
|
||||||
let gx1 = Fp.mul(tv1, Fp.neg(ELL2_J)); // 8. gx1 = -J * tv1 # x1n + J * xd
|
|
||||||
gx1 = Fp.mul(gx1, x1n); // 9. gx1 = gx1 * x1n # x1n^2 + J * x1n * xd
|
|
||||||
gx1 = Fp.add(gx1, tv2); // 10. gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2
|
|
||||||
gx1 = Fp.mul(gx1, x1n); // 11. gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2
|
|
||||||
let tv3 = Fp.sqr(gxd); // 12. tv3 = gxd^2
|
|
||||||
tv2 = Fp.mul(gx1, gxd); // 13. tv2 = gx1 * gxd # gx1 * gxd
|
|
||||||
tv3 = Fp.mul(tv3, tv2); // 14. tv3 = tv3 * tv2 # gx1 * gxd^3
|
|
||||||
let y1 = Fp.pow(tv3, ELL2_C1); // 15. y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4)
|
|
||||||
y1 = Fp.mul(y1, tv2); // 16. y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4)
|
|
||||||
let x2n = Fp.mul(x1n, Fp.neg(tv1)); // 17. x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd
|
|
||||||
let y2 = Fp.mul(y1, u); // 18. y2 = y1 * u
|
|
||||||
y2 = Fp.cmov(y2, Fp.ZERO, e1); // 19. y2 = CMOV(y2, 0, e1)
|
|
||||||
tv2 = Fp.sqr(y1); // 20. tv2 = y1^2
|
|
||||||
tv2 = Fp.mul(tv2, gxd); // 21. tv2 = tv2 * gxd
|
|
||||||
let e2 = Fp.eql(tv2, gx1); // 22. e2 = tv2 == gx1
|
|
||||||
let xn = Fp.cmov(x2n, x1n, e2); // 23. xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2
|
|
||||||
let y = Fp.cmov(y2, y1, e2); // 24. y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2
|
|
||||||
let e3 = Fp.isOdd(y); // 25. e3 = sgn0(y) == 1 # Fix sign of y
|
|
||||||
y = Fp.cmov(y, Fp.neg(y), e2 !== e3); // 26. y = CMOV(y, -y, e2 XOR e3)
|
|
||||||
return { xn, xd, yn: y, yd: Fp.ONE }; // 27. return (xn, xd, y, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function map_to_curve_elligator2_edwards448(u: bigint) {
|
|
||||||
let { xn, xd, yn, yd } = map_to_curve_elligator2_curve448(u); // 1. (xn, xd, yn, yd) = map_to_curve_elligator2_curve448(u)
|
|
||||||
let xn2 = Fp.sqr(xn); // 2. xn2 = xn^2
|
|
||||||
let xd2 = Fp.sqr(xd); // 3. xd2 = xd^2
|
|
||||||
let xd4 = Fp.sqr(xd2); // 4. xd4 = xd2^2
|
|
||||||
let yn2 = Fp.sqr(yn); // 5. yn2 = yn^2
|
|
||||||
let yd2 = Fp.sqr(yd); // 6. yd2 = yd^2
|
|
||||||
let xEn = Fp.sub(xn2, xd2); // 7. xEn = xn2 - xd2
|
|
||||||
let tv2 = Fp.sub(xEn, xd2); // 8. tv2 = xEn - xd2
|
|
||||||
xEn = Fp.mul(xEn, xd2); // 9. xEn = xEn * xd2
|
|
||||||
xEn = Fp.mul(xEn, yd); // 10. xEn = xEn * yd
|
|
||||||
xEn = Fp.mul(xEn, yn); // 11. xEn = xEn * yn
|
|
||||||
xEn = Fp.mul(xEn, _4n); // 12. xEn = xEn * 4
|
|
||||||
tv2 = Fp.mul(tv2, xn2); // 13. tv2 = tv2 * xn2
|
|
||||||
tv2 = Fp.mul(tv2, yd2); // 14. tv2 = tv2 * yd2
|
|
||||||
let tv3 = Fp.mul(yn2, _4n); // 15. tv3 = 4 * yn2
|
|
||||||
let tv1 = Fp.add(tv3, yd2); // 16. tv1 = tv3 + yd2
|
|
||||||
tv1 = Fp.mul(tv1, xd4); // 17. tv1 = tv1 * xd4
|
|
||||||
let xEd = Fp.add(tv1, tv2); // 18. xEd = tv1 + tv2
|
|
||||||
tv2 = Fp.mul(tv2, xn); // 19. tv2 = tv2 * xn
|
|
||||||
let tv4 = Fp.mul(xn, xd4); // 20. tv4 = xn * xd4
|
|
||||||
let yEn = Fp.sub(tv3, yd2); // 21. yEn = tv3 - yd2
|
|
||||||
yEn = Fp.mul(yEn, tv4); // 22. yEn = yEn * tv4
|
|
||||||
yEn = Fp.sub(yEn, tv2); // 23. yEn = yEn - tv2
|
|
||||||
tv1 = Fp.add(xn2, xd2); // 24. tv1 = xn2 + xd2
|
|
||||||
tv1 = Fp.mul(tv1, xd2); // 25. tv1 = tv1 * xd2
|
|
||||||
tv1 = Fp.mul(tv1, xd); // 26. tv1 = tv1 * xd
|
|
||||||
tv1 = Fp.mul(tv1, yn2); // 27. tv1 = tv1 * yn2
|
|
||||||
tv1 = Fp.mul(tv1, BigInt(-2)); // 28. tv1 = -2 * tv1
|
|
||||||
let yEd = Fp.add(tv2, tv1); // 29. yEd = tv2 + tv1
|
|
||||||
tv4 = Fp.mul(tv4, yd2); // 30. tv4 = tv4 * yd2
|
|
||||||
yEd = Fp.add(yEd, tv4); // 31. yEd = yEd + tv4
|
|
||||||
tv1 = Fp.mul(xEd, yEd); // 32. tv1 = xEd * yEd
|
|
||||||
let e = Fp.eql(tv1, Fp.ZERO); // 33. e = tv1 == 0
|
|
||||||
xEn = Fp.cmov(xEn, Fp.ZERO, e); // 34. xEn = CMOV(xEn, 0, e)
|
|
||||||
xEd = Fp.cmov(xEd, Fp.ONE, e); // 35. xEd = CMOV(xEd, 1, e)
|
|
||||||
yEn = Fp.cmov(yEn, Fp.ONE, e); // 36. yEn = CMOV(yEn, 1, e)
|
|
||||||
yEd = Fp.cmov(yEd, Fp.ONE, e); // 37. yEd = CMOV(yEd, 1, e)
|
|
||||||
|
|
||||||
const inv = Fp.invertBatch([xEd, yEd]); // batch division
|
|
||||||
return { x: Fp.mul(xEn, inv[0]), y: Fp.mul(yEn, inv[1]) }; // 38. return (xEn, xEd, yEn, yEd)
|
|
||||||
}
|
|
||||||
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(
|
|
||||||
ed448.ExtendedPoint,
|
|
||||||
(scalars: bigint[]) => map_to_curve_elligator2_edwards448(scalars[0]),
|
|
||||||
{
|
|
||||||
DST: 'edwards448_XOF:SHAKE256_ELL2_RO_',
|
|
||||||
encodeDST: 'edwards448_XOF:SHAKE256_ELL2_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 224,
|
|
||||||
expand: 'xof',
|
|
||||||
hash: shake256,
|
|
||||||
}
|
|
||||||
))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
|
|
||||||
function assertDcfPoint(other: unknown) {
|
|
||||||
if (!(other instanceof DcfPoint)) throw new Error('DecafPoint expected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1-d
|
|
||||||
const ONE_MINUS_D = BigInt('39082');
|
|
||||||
// 1-2d
|
|
||||||
const ONE_MINUS_TWO_D = BigInt('78163');
|
|
||||||
// √(-d)
|
|
||||||
const SQRT_MINUS_D = BigInt(
|
|
||||||
'98944233647732219769177004876929019128417576295529901074099889598043702116001257856802131563896515373927712232092845883226922417596214'
|
|
||||||
);
|
|
||||||
// 1 / √(-d)
|
|
||||||
const INVSQRT_MINUS_D = BigInt(
|
|
||||||
'315019913931389607337177038330951043522456072897266928557328499619017160722351061360252776265186336876723201881398623946864393857820716'
|
|
||||||
);
|
|
||||||
// Calculates 1/√(number)
|
|
||||||
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
|
||||||
|
|
||||||
const MAX_448B = BigInt(
|
|
||||||
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
|
||||||
);
|
|
||||||
const bytes448ToNumberLE = (bytes: Uint8Array) =>
|
|
||||||
ed448.CURVE.Fp.create(bytesToNumberLE(bytes) & MAX_448B);
|
|
||||||
|
|
||||||
type ExtendedPoint = ExtPointType;
|
|
||||||
|
|
||||||
// Computes Elligator map for Decaf
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
|
|
||||||
function calcElligatorDecafMap(r0: bigint): ExtendedPoint {
|
|
||||||
const { d } = ed448.CURVE;
|
|
||||||
const P = ed448.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed448.CURVE.Fp.create;
|
|
||||||
|
|
||||||
const r = mod(-(r0 * r0)); // 1
|
|
||||||
const u0 = mod(d * (r - _1n)); // 2
|
|
||||||
const u1 = mod((u0 + _1n) * (u0 - r)); // 3
|
|
||||||
|
|
||||||
const { isValid: was_square, value: v } = uvRatio(ONE_MINUS_TWO_D, mod((r + _1n) * u1)); // 4
|
|
||||||
|
|
||||||
let v_prime = v; // 5
|
|
||||||
if (!was_square) v_prime = mod(r0 * v);
|
|
||||||
|
|
||||||
let sgn = _1n; // 6
|
|
||||||
if (!was_square) sgn = mod(-_1n);
|
|
||||||
|
|
||||||
const s = mod(v_prime * (r + _1n)); // 7
|
|
||||||
let s_abs = s;
|
|
||||||
if (isNegativeLE(s, P)) s_abs = mod(-s);
|
|
||||||
|
|
||||||
const s2 = s * s;
|
|
||||||
const W0 = mod(s_abs * _2n); // 8
|
|
||||||
const W1 = mod(s2 + _1n); // 9
|
|
||||||
const W2 = mod(s2 - _1n); // 10
|
|
||||||
const W3 = mod(v_prime * s * (r - _1n) * ONE_MINUS_TWO_D + sgn); // 11
|
|
||||||
return new ed448.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each ed448/ExtendedPoint has 4 different equivalent points. This can be
|
|
||||||
* a source of bugs for protocols like ring signatures. Decaf was created to solve this.
|
|
||||||
* Decaf point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
|
|
||||||
* but it should work in its own namespace: do not combine those two.
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
|
||||||
*/
|
|
||||||
class DcfPoint implements Group<DcfPoint> {
|
|
||||||
static BASE: DcfPoint;
|
|
||||||
static ZERO: DcfPoint;
|
|
||||||
// Private property to discourage combining ExtendedPoint + DecafPoint
|
|
||||||
// Always use Decaf encoding/decoding instead.
|
|
||||||
constructor(private readonly ep: ExtendedPoint) {}
|
|
||||||
|
|
||||||
static fromAffine(ap: AffinePoint<bigint>) {
|
|
||||||
return new DcfPoint(ed448.ExtendedPoint.fromAffine(ap));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes uniform output of 112-byte hash function like shake256 and converts it to `DecafPoint`.
|
|
||||||
* The hash-to-group operation applies Elligator twice and adds the results.
|
|
||||||
* **Note:** this is one-way map, there is no conversion from point to hash.
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-element-derivation-2
|
|
||||||
* @param hex 112-byte output of a hash function
|
|
||||||
*/
|
|
||||||
static hashToCurve(hex: Hex): DcfPoint {
|
|
||||||
hex = ensureBytes('decafHash', hex, 112);
|
|
||||||
const r1 = bytes448ToNumberLE(hex.slice(0, 56));
|
|
||||||
const R1 = calcElligatorDecafMap(r1);
|
|
||||||
const r2 = bytes448ToNumberLE(hex.slice(56, 112));
|
|
||||||
const R2 = calcElligatorDecafMap(r2);
|
|
||||||
return new DcfPoint(R1.add(R2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts decaf-encoded string to decaf point.
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-decode-2
|
|
||||||
* @param hex Decaf-encoded 56 bytes. Not every 56-byte string is valid decaf encoding
|
|
||||||
*/
|
|
||||||
static fromHex(hex: Hex): DcfPoint {
|
|
||||||
hex = ensureBytes('decafHex', hex, 56);
|
|
||||||
const { d } = ed448.CURVE;
|
|
||||||
const P = ed448.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed448.CURVE.Fp.create;
|
|
||||||
const emsg = 'DecafPoint.fromHex: the hex is not valid encoding of DecafPoint';
|
|
||||||
const s = bytes448ToNumberLE(hex);
|
|
||||||
|
|
||||||
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
|
||||||
// 2. Check that s is non-negative, or else abort
|
|
||||||
if (!equalBytes(numberToBytesLE(s, 56), hex) || isNegativeLE(s, P)) throw new Error(emsg);
|
|
||||||
|
|
||||||
const s2 = mod(s * s); // 1
|
|
||||||
const u1 = mod(_1n + s2); // 2
|
|
||||||
const u1sq = mod(u1 * u1);
|
|
||||||
const u2 = mod(u1sq - _4n * d * s2); // 3
|
|
||||||
|
|
||||||
const { isValid, value: invsqrt } = invertSqrt(mod(u2 * u1sq)); // 4
|
|
||||||
|
|
||||||
let u3 = mod((s + s) * invsqrt * u1 * SQRT_MINUS_D); // 5
|
|
||||||
if (isNegativeLE(u3, P)) u3 = mod(-u3);
|
|
||||||
|
|
||||||
const x = mod(u3 * invsqrt * u2 * INVSQRT_MINUS_D); // 6
|
|
||||||
const y = mod((_1n - s2) * invsqrt * u1); // 7
|
|
||||||
const t = mod(x * y); // 8
|
|
||||||
|
|
||||||
if (!isValid) throw new Error(emsg);
|
|
||||||
return new DcfPoint(new ed448.ExtendedPoint(x, y, _1n, t));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes decaf point to Uint8Array.
|
|
||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-encode-2
|
|
||||||
*/
|
|
||||||
toRawBytes(): Uint8Array {
|
|
||||||
let { ex: x, ey: _y, ez: z, et: t } = this.ep;
|
|
||||||
const P = ed448.CURVE.Fp.ORDER;
|
|
||||||
const mod = ed448.CURVE.Fp.create;
|
|
||||||
|
|
||||||
const u1 = mod(mod(x + t) * mod(x - t)); // 1
|
|
||||||
const x2 = mod(x * x);
|
|
||||||
const { value: invsqrt } = invertSqrt(mod(u1 * ONE_MINUS_D * x2)); // 2
|
|
||||||
|
|
||||||
let ratio = mod(invsqrt * u1 * SQRT_MINUS_D); // 3
|
|
||||||
if (isNegativeLE(ratio, P)) ratio = mod(-ratio);
|
|
||||||
|
|
||||||
const u2 = mod(INVSQRT_MINUS_D * ratio * z - t); // 4
|
|
||||||
|
|
||||||
let s = mod(ONE_MINUS_D * invsqrt * x * u2); // 5
|
|
||||||
if (isNegativeLE(s, P)) s = mod(-s);
|
|
||||||
|
|
||||||
return numberToBytesLE(s, 56);
|
|
||||||
}
|
|
||||||
|
|
||||||
toHex(): string {
|
|
||||||
return bytesToHex(this.toRawBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return this.toHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare one point to another.
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-07#name-equals-2
|
|
||||||
equals(other: DcfPoint): boolean {
|
|
||||||
assertDcfPoint(other);
|
|
||||||
const { ex: X1, ey: Y1 } = this.ep;
|
|
||||||
const { ex: X2, ey: Y2 } = other.ep;
|
|
||||||
const mod = ed448.CURVE.Fp.create;
|
|
||||||
// (x1 * y2 == y1 * x2)
|
|
||||||
return mod(X1 * Y2) === mod(Y1 * X2);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(other: DcfPoint): DcfPoint {
|
|
||||||
assertDcfPoint(other);
|
|
||||||
return new DcfPoint(this.ep.add(other.ep));
|
|
||||||
}
|
|
||||||
|
|
||||||
subtract(other: DcfPoint): DcfPoint {
|
|
||||||
assertDcfPoint(other);
|
|
||||||
return new DcfPoint(this.ep.subtract(other.ep));
|
|
||||||
}
|
|
||||||
|
|
||||||
multiply(scalar: bigint): DcfPoint {
|
|
||||||
return new DcfPoint(this.ep.multiply(scalar));
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplyUnsafe(scalar: bigint): DcfPoint {
|
|
||||||
return new DcfPoint(this.ep.multiplyUnsafe(scalar));
|
|
||||||
}
|
|
||||||
|
|
||||||
double(): DcfPoint {
|
|
||||||
return new DcfPoint(this.ep.double());
|
|
||||||
}
|
|
||||||
|
|
||||||
negate(): DcfPoint {
|
|
||||||
return new DcfPoint(this.ep.negate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DecafPoint = /* @__PURE__ */ (() => {
|
|
||||||
// decaf448 base point is ed448 base x 2
|
|
||||||
// https://github.com/dalek-cryptography/curve25519-dalek/blob/59837c6ecff02b77b9d5ff84dbc239d0cf33ef90/vendor/ristretto.sage#L699
|
|
||||||
if (!DcfPoint.BASE) DcfPoint.BASE = new DcfPoint(ed448.ExtendedPoint.BASE).multiply(_2n);
|
|
||||||
if (!DcfPoint.ZERO) DcfPoint.ZERO = new DcfPoint(ed448.ExtendedPoint.ZERO);
|
|
||||||
return DcfPoint;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Hashing to decaf448. https://www.rfc-editor.org/rfc/rfc9380#appendix-C
|
|
||||||
export const hashToDecaf448 = (msg: Uint8Array, options: htfBasicOpts) => {
|
|
||||||
const d = options.DST;
|
|
||||||
const DST = typeof d === 'string' ? utf8ToBytes(d) : d;
|
|
||||||
const uniform_bytes = expand_message_xof(msg, DST, 112, 224, shake256);
|
|
||||||
const P = DcfPoint.hashToCurve(uniform_bytes);
|
|
||||||
return P;
|
|
||||||
};
|
|
||||||
export const hash_to_decaf448 = hashToDecaf448; // legacy
|
|
||||||
@ -1 +0,0 @@
|
|||||||
throw new Error('Incorrect usage. Import submodules instead');
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
||||||
import { twistedEdwards } from './abstract/edwards.js';
|
|
||||||
import { blake2s } from '@noble/hashes/blake2s';
|
|
||||||
import { Field } from './abstract/modular.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* jubjub Twisted Edwards curve.
|
|
||||||
* https://neuromancer.sk/std/other/JubJub
|
|
||||||
* jubjub does not use EdDSA, so `hash`/sha512 params are passed because interface expects them.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const jubjub = /* @__PURE__ */ twistedEdwards({
|
|
||||||
// Params: a, d
|
|
||||||
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
|
||||||
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
|
||||||
// Finite field 𝔽p over which we'll do calculations
|
|
||||||
// Same value as bls12-381 Fr (not Fp)
|
|
||||||
Fp: Field(BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001')),
|
|
||||||
// Subgroup order: how many points curve has
|
|
||||||
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
|
||||||
// Cofactor
|
|
||||||
h: BigInt(8),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
|
||||||
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
|
||||||
hash: sha512,
|
|
||||||
randomBytes,
|
|
||||||
} as const);
|
|
||||||
|
|
||||||
const GH_FIRST_BLOCK = utf8ToBytes(
|
|
||||||
'096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns point at JubJub curve which is prime order and not zero
|
|
||||||
export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
|
|
||||||
const h = blake2s.create({ personalization, dkLen: 32 });
|
|
||||||
h.update(GH_FIRST_BLOCK);
|
|
||||||
h.update(tag);
|
|
||||||
// NOTE: returns ExtendedPoint, in case it will be multiplied later
|
|
||||||
let p = jubjub.ExtendedPoint.fromHex(h.digest());
|
|
||||||
// NOTE: cannot replace with isSmallOrder, returns Point*8
|
|
||||||
p = p.multiply(jubjub.CURVE.h);
|
|
||||||
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findGroupHash(m: Uint8Array, personalization: Uint8Array) {
|
|
||||||
const tag = concatBytes(m, new Uint8Array([0]));
|
|
||||||
for (let i = 0; i < 256; i++) {
|
|
||||||
tag[tag.length - 1] = i;
|
|
||||||
try {
|
|
||||||
return groupHash(tag, personalization);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
throw new Error('findGroupHash tag overflow');
|
|
||||||
}
|
|
||||||
146
src/modular.ts
Normal file
146
src/modular.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const _2n = BigInt(2);
|
||||||
|
const _3n = BigInt(3);
|
||||||
|
const _4n = BigInt(4);
|
||||||
|
const _5n = BigInt(5);
|
||||||
|
const _8n = BigInt(8);
|
||||||
|
|
||||||
|
// Calculates a modulo b
|
||||||
|
export function mod(a: bigint, b: bigint): bigint {
|
||||||
|
const result = a % b;
|
||||||
|
return result >= _0n ? result : b + result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Efficiently exponentiate num to power and do modular division.
|
||||||
|
* @example
|
||||||
|
* powMod(2n, 6n, 11n) // 64n % 11n == 9n
|
||||||
|
*/
|
||||||
|
export function pow(num: bigint, power: bigint, modulo: bigint): bigint {
|
||||||
|
if (modulo <= _0n || power < _0n) throw new Error('Expected power/modulo > 0');
|
||||||
|
if (modulo === _1n) return _0n;
|
||||||
|
let res = _1n;
|
||||||
|
while (power > _0n) {
|
||||||
|
if (power & _1n) res = (res * num) % modulo;
|
||||||
|
num = (num * num) % modulo;
|
||||||
|
power >>= _1n;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
|
||||||
|
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
|
||||||
|
let res = x;
|
||||||
|
while (power-- > _0n) {
|
||||||
|
res *= res;
|
||||||
|
res %= modulo;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverses number over modulo
|
||||||
|
export function invert(number: bigint, modulo: bigint): bigint {
|
||||||
|
if (number === _0n || modulo <= _0n) {
|
||||||
|
throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`);
|
||||||
|
}
|
||||||
|
// Eucledian GCD https://brilliant.org/wiki/extended-euclidean-algorithm/
|
||||||
|
let a = mod(number, modulo);
|
||||||
|
let b = modulo;
|
||||||
|
// prettier-ignore
|
||||||
|
let x = _0n, y = _1n, u = _1n, v = _0n;
|
||||||
|
while (a !== _0n) {
|
||||||
|
const q = b / a;
|
||||||
|
const r = b % a;
|
||||||
|
const m = x - u * q;
|
||||||
|
const n = y - v * q;
|
||||||
|
// prettier-ignore
|
||||||
|
b = a, a = r, x = u, y = v, u = m, v = n;
|
||||||
|
}
|
||||||
|
const gcd = b;
|
||||||
|
if (gcd !== _1n) throw new Error('invert: does not exist');
|
||||||
|
return mod(x, modulo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a list of numbers, efficiently inverts all of them.
|
||||||
|
* @param nums list of bigints
|
||||||
|
* @param p modulo
|
||||||
|
* @returns list of inverted bigints
|
||||||
|
* @example
|
||||||
|
* invertBatch([1n, 2n, 4n], 21n);
|
||||||
|
* // => [1n, 11n, 16n]
|
||||||
|
*/
|
||||||
|
export function invertBatch(nums: bigint[], modulo: bigint): bigint[] {
|
||||||
|
const scratch = new Array(nums.length);
|
||||||
|
// Walk from first to last, multiply them by each other MOD p
|
||||||
|
const lastMultiplied = nums.reduce((acc, num, i) => {
|
||||||
|
if (num === _0n) return acc;
|
||||||
|
scratch[i] = acc;
|
||||||
|
return mod(acc * num, modulo);
|
||||||
|
}, _1n);
|
||||||
|
// Invert last element
|
||||||
|
const inverted = invert(lastMultiplied, modulo);
|
||||||
|
// Walk from last to first, multiply them by inverted each other MOD p
|
||||||
|
nums.reduceRight((acc, num, i) => {
|
||||||
|
if (num === _0n) return acc;
|
||||||
|
scratch[i] = mod(acc * scratch[i], modulo);
|
||||||
|
return mod(acc * num, modulo);
|
||||||
|
}, inverted);
|
||||||
|
return scratch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates Legendre symbol: num^((P-1)/2)
|
||||||
|
export function legendre(num: bigint, fieldPrime: bigint): bigint {
|
||||||
|
return pow(num, (fieldPrime - _1n) / _2n, fieldPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates square root of a number in a finite field.
|
||||||
|
* Used to calculate y - the square root of y².
|
||||||
|
*/
|
||||||
|
export function sqrt(number: bigint, modulo: bigint): bigint {
|
||||||
|
const n = number;
|
||||||
|
const P = modulo;
|
||||||
|
const p1div4 = (P + _1n) / _4n;
|
||||||
|
|
||||||
|
// P = 3 (mod 4)
|
||||||
|
// sqrt n = n^((P+1)/4)
|
||||||
|
if (P % _4n === _3n) return pow(n, p1div4, P);
|
||||||
|
// P = 5 (mod 8)
|
||||||
|
if (P % _8n === _5n) {
|
||||||
|
const n2 = mod(n * _2n, P);
|
||||||
|
const v = pow(n2, (P - _5n) / _8n, P);
|
||||||
|
const nv = mod(n * v, P);
|
||||||
|
const i = mod(_2n * nv * v, P);
|
||||||
|
const r = mod(nv * (i - _1n), P);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other cases: Tonelli-Shanks algorithm
|
||||||
|
if (legendre(n, P) !== _1n) throw new Error('Cannot find square root');
|
||||||
|
let q: bigint, s: number, z: bigint;
|
||||||
|
for (q = P - _1n, s = 0; q % _2n === _0n; q /= _2n, s++);
|
||||||
|
if (s === 1) return pow(n, p1div4, P);
|
||||||
|
for (z = _2n; z < P && legendre(z, P) !== P - _1n; z++);
|
||||||
|
|
||||||
|
let c = pow(z, q, P);
|
||||||
|
let r = pow(n, (q + _1n) / _2n, P);
|
||||||
|
let t = pow(n, q, P);
|
||||||
|
|
||||||
|
let t2 = _0n;
|
||||||
|
while (mod(t - _1n, P) !== _0n) {
|
||||||
|
t2 = mod(t * t, P);
|
||||||
|
let i;
|
||||||
|
for (i = 1; i < s; i++) {
|
||||||
|
if (mod(t2 - _1n, P) === _0n) break;
|
||||||
|
t2 = mod(t2 * t2, P);
|
||||||
|
}
|
||||||
|
let b = pow(c, BigInt(1 << (s - i - 1)), P);
|
||||||
|
r = mod(r * b, P);
|
||||||
|
c = mod(b * b, P);
|
||||||
|
t = mod(t * c, P);
|
||||||
|
s = i;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
48
src/p256.ts
48
src/p256.ts
@ -1,48 +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 { Field } from './abstract/modular.js';
|
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
|
||||||
import { createHasher } from './abstract/hash-to-curve.js';
|
|
||||||
|
|
||||||
// NIST secp256r1 aka p256
|
|
||||||
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
|
|
||||||
|
|
||||||
const Fp = Field(BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'));
|
|
||||||
const CURVE_A = Fp.create(BigInt('-3'));
|
|
||||||
const CURVE_B = BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b');
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const p256 = createCurve({
|
|
||||||
a: CURVE_A, // Equation params: a, b
|
|
||||||
b: CURVE_B,
|
|
||||||
Fp, // Field: 2n**224n * (2n**32n-1n) + 2n**192n + 2n**96n-1n
|
|
||||||
// Curve order, total count of valid points in the field
|
|
||||||
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
|
||||||
// Base (generator) point (x, y)
|
|
||||||
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
|
||||||
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
|
||||||
h: BigInt(1),
|
|
||||||
lowS: false,
|
|
||||||
} as const, sha256);
|
|
||||||
export const secp256r1 = p256;
|
|
||||||
|
|
||||||
const mapSWU = /* @__PURE__ */ (() =>
|
|
||||||
mapToCurveSimpleSWU(Fp, {
|
|
||||||
A: CURVE_A,
|
|
||||||
B: CURVE_B,
|
|
||||||
Z: Fp.create(BigInt('-10')),
|
|
||||||
}))();
|
|
||||||
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(secp256r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
|
|
||||||
DST: 'P256_XMD:SHA-256_SSWU_RO_',
|
|
||||||
encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha256,
|
|
||||||
}))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
52
src/p384.ts
52
src/p384.ts
@ -1,52 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { createCurve } from './_shortw_utils.js';
|
|
||||||
import { sha384 } from '@noble/hashes/sha512';
|
|
||||||
import { Field } from './abstract/modular.js';
|
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
|
||||||
import { createHasher } from './abstract/hash-to-curve.js';
|
|
||||||
|
|
||||||
// NIST secp384r1 aka p384
|
|
||||||
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
|
|
||||||
|
|
||||||
// Field over which we'll do calculations.
|
|
||||||
// prettier-ignore
|
|
||||||
const P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff');
|
|
||||||
const Fp = Field(P);
|
|
||||||
const CURVE_A = Fp.create(BigInt('-3'));
|
|
||||||
// prettier-ignore
|
|
||||||
const CURVE_B = BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef');
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const p384 = createCurve({
|
|
||||||
a: CURVE_A, // Equation params: a, b
|
|
||||||
b: CURVE_B,
|
|
||||||
Fp, // Field: 2n**384n - 2n**128n - 2n**96n + 2n**32n - 1n
|
|
||||||
// Curve order, total count of valid points in the field.
|
|
||||||
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
|
|
||||||
// Base (generator) point (x, y)
|
|
||||||
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
|
|
||||||
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
|
||||||
h: BigInt(1),
|
|
||||||
lowS: false,
|
|
||||||
} as const, sha384);
|
|
||||||
export const secp384r1 = p384;
|
|
||||||
|
|
||||||
const mapSWU = /* @__PURE__ */ (() =>
|
|
||||||
mapToCurveSimpleSWU(Fp, {
|
|
||||||
A: CURVE_A,
|
|
||||||
B: CURVE_B,
|
|
||||||
Z: Fp.create(BigInt('-12')),
|
|
||||||
}))();
|
|
||||||
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(secp384r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
|
|
||||||
DST: 'P384_XMD:SHA-384_SSWU_RO_',
|
|
||||||
encodeDST: 'P384_XMD:SHA-384_SSWU_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 192,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha384,
|
|
||||||
}))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
68
src/p521.ts
68
src/p521.ts
@ -1,68 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { createCurve } from './_shortw_utils.js';
|
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
|
||||||
import { Field } from './abstract/modular.js';
|
|
||||||
import { mapToCurveSimpleSWU } from './abstract/weierstrass.js';
|
|
||||||
import { createHasher } from './abstract/hash-to-curve.js';
|
|
||||||
|
|
||||||
// NIST secp521r1 aka p521
|
|
||||||
// Note that it's 521, which differs from 512 of its hash function.
|
|
||||||
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-521
|
|
||||||
|
|
||||||
// Field over which we'll do calculations.
|
|
||||||
// prettier-ignore
|
|
||||||
const P = BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
|
||||||
const Fp = Field(P);
|
|
||||||
|
|
||||||
const CURVE = {
|
|
||||||
a: Fp.create(BigInt('-3')),
|
|
||||||
b: BigInt(
|
|
||||||
'0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'
|
|
||||||
),
|
|
||||||
Fp,
|
|
||||||
n: BigInt(
|
|
||||||
'0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'
|
|
||||||
),
|
|
||||||
Gx: BigInt(
|
|
||||||
'0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'
|
|
||||||
),
|
|
||||||
Gy: BigInt(
|
|
||||||
'0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'
|
|
||||||
),
|
|
||||||
h: BigInt(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const p521 = createCurve({
|
|
||||||
a: CURVE.a, // Equation params: a, b
|
|
||||||
b: CURVE.b,
|
|
||||||
Fp, // Field: 2n**521n - 1n
|
|
||||||
// Curve order, total count of valid points in the field
|
|
||||||
n: CURVE.n,
|
|
||||||
Gx: CURVE.Gx, // Base point (x, y) aka generator point
|
|
||||||
Gy: CURVE.Gy,
|
|
||||||
h: CURVE.h,
|
|
||||||
lowS: false,
|
|
||||||
allowedPrivateKeyLengths: [130, 131, 132] // P521 keys are variable-length. Normalize to 132b
|
|
||||||
} as const, sha512);
|
|
||||||
export const secp521r1 = p521;
|
|
||||||
|
|
||||||
const mapSWU = /* @__PURE__ */ (() =>
|
|
||||||
mapToCurveSimpleSWU(Fp, {
|
|
||||||
A: CURVE.a,
|
|
||||||
B: CURVE.b,
|
|
||||||
Z: Fp.create(BigInt('-4')),
|
|
||||||
}))();
|
|
||||||
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(secp521r1.ProjectivePoint, (scalars: bigint[]) => mapSWU(scalars[0]), {
|
|
||||||
DST: 'P521_XMD:SHA-512_SSWU_RO_',
|
|
||||||
encodeDST: 'P521_XMD:SHA-512_SSWU_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 256,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha512,
|
|
||||||
}))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
31
src/pasta.ts
31
src/pasta.ts
@ -1,31 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { weierstrass } from './abstract/weierstrass.js';
|
|
||||||
import { getHash } from './_shortw_utils.js';
|
|
||||||
import * as mod from './abstract/modular.js';
|
|
||||||
|
|
||||||
export const p = BigInt('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001');
|
|
||||||
export const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001');
|
|
||||||
|
|
||||||
// https://neuromancer.sk/std/other/Pallas
|
|
||||||
export const pallas = weierstrass({
|
|
||||||
a: BigInt(0),
|
|
||||||
b: BigInt(5),
|
|
||||||
Fp: mod.Field(p),
|
|
||||||
n: q,
|
|
||||||
Gx: mod.mod(BigInt(-1), p),
|
|
||||||
Gy: BigInt(2),
|
|
||||||
h: BigInt(1),
|
|
||||||
...getHash(sha256),
|
|
||||||
});
|
|
||||||
// https://neuromancer.sk/std/other/Vesta
|
|
||||||
export const vesta = weierstrass({
|
|
||||||
a: BigInt(0),
|
|
||||||
b: BigInt(5),
|
|
||||||
Fp: mod.Field(q),
|
|
||||||
n: p,
|
|
||||||
Gx: mod.mod(BigInt(-1), q),
|
|
||||||
Gy: BigInt(2),
|
|
||||||
h: BigInt(1),
|
|
||||||
...getHash(sha256),
|
|
||||||
});
|
|
||||||
274
src/secp256k1.ts
274
src/secp256k1.ts
@ -1,274 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { randomBytes } from '@noble/hashes/utils';
|
|
||||||
import { 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 { createHasher, isogenyMap } from './abstract/hash-to-curve.js';
|
|
||||||
import { createCurve } from './_shortw_utils.js';
|
|
||||||
|
|
||||||
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
|
||||||
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
const _2n = BigInt(2);
|
|
||||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.
|
|
||||||
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
|
||||||
*/
|
|
||||||
function sqrtMod(y: bigint): bigint {
|
|
||||||
const P = secp256k1P;
|
|
||||||
// prettier-ignore
|
|
||||||
const _3n = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);
|
|
||||||
// prettier-ignore
|
|
||||||
const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
|
|
||||||
const b2 = (y * y * y) % P; // x^3, 11
|
|
||||||
const b3 = (b2 * b2 * y) % P; // x^7
|
|
||||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
|
||||||
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
|
||||||
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
|
||||||
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
|
||||||
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
|
||||||
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
|
||||||
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
|
||||||
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
|
||||||
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
|
||||||
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
|
||||||
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
|
||||||
const root = pow2(t2, _2n, P);
|
|
||||||
if (!Fp.eql(Fp.sqr(root), y)) throw new Error('Cannot find square root');
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Fp = Field(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
|
|
||||||
|
|
||||||
export const secp256k1 = createCurve(
|
|
||||||
{
|
|
||||||
a: BigInt(0), // equation params: a, b
|
|
||||||
b: BigInt(7), // Seem to be rigid: bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
|
|
||||||
Fp, // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
|
|
||||||
n: secp256k1N, // Curve order, total count of valid points in the field
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
|
||||||
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
|
||||||
h: BigInt(1), // Cofactor
|
|
||||||
lowS: true, // Allow only low-S signatures by default in sign() and verify()
|
|
||||||
/**
|
|
||||||
* 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: {
|
|
||||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
|
||||||
splitScalar: (k: bigint) => {
|
|
||||||
const n = secp256k1N;
|
|
||||||
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
|
||||||
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
|
||||||
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
|
||||||
const b2 = a1;
|
|
||||||
const POW_2_128 = BigInt('0x100000000000000000000000000000000'); // (2n**128n).toString(16)
|
|
||||||
|
|
||||||
const c1 = divNearest(b2 * k, n);
|
|
||||||
const c2 = divNearest(-b1 * k, n);
|
|
||||||
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
|
||||||
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
|
||||||
const k1neg = k1 > POW_2_128;
|
|
||||||
const k2neg = k2 > POW_2_128;
|
|
||||||
if (k1neg) k1 = n - k1;
|
|
||||||
if (k2neg) k2 = n - k2;
|
|
||||||
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
|
||||||
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
|
||||||
}
|
|
||||||
return { k1neg, k1, k2neg, k2 };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sha256
|
|
||||||
);
|
|
||||||
|
|
||||||
// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
|
||||||
const _0n = BigInt(0);
|
|
||||||
const fe = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1P;
|
|
||||||
const ge = (x: bigint) => typeof x === 'bigint' && _0n < x && x < secp256k1N;
|
|
||||||
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
|
||||||
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
|
||||||
function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
|
|
||||||
let tagP = TAGGED_HASH_PREFIXES[tag];
|
|
||||||
if (tagP === undefined) {
|
|
||||||
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
|
|
||||||
tagP = concatBytes(tagH, tagH);
|
|
||||||
TAGGED_HASH_PREFIXES[tag] = tagP;
|
|
||||||
}
|
|
||||||
return sha256(concatBytes(tagP, ...messages));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 modP = (x: bigint) => mod(x, secp256k1P);
|
|
||||||
const modN = (x: bigint) => mod(x, secp256k1N);
|
|
||||||
const Point = secp256k1.ProjectivePoint;
|
|
||||||
const GmulAdd = (Q: PointType<bigint>, a: bigint, b: bigint) =>
|
|
||||||
Point.BASE.multiplyAndAddUnsafe(Q, a, b);
|
|
||||||
|
|
||||||
// Calculate point, scalar and bytes
|
|
||||||
function schnorrGetExtPubKey(priv: PrivKey) {
|
|
||||||
let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); // same method executed in fromPrivateKey
|
|
||||||
let p = Point.fromPrivateKey(d_); // P = d'⋅G; 0 < d' < n check is done inside
|
|
||||||
const scalar = p.hasEvenY() ? d_ : modN(-d_);
|
|
||||||
return { scalar: scalar, bytes: pointToBytes(p) };
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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> {
|
|
||||||
if (!fe(x)) throw new Error('bad x: need 0 < x < p'); // Fail if x ≥ 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.
|
|
||||||
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.
|
|
||||||
p.assertValidity();
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create tagged hash, convert it to bigint, reduce modulo-n.
|
|
||||||
*/
|
|
||||||
function challenge(...args: Uint8Array[]): bigint {
|
|
||||||
return modN(bytesToNumberBE(taggedHash('BIP0340/challenge', ...args)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schnorr public key is just `x` coordinate of Point as per BIP340.
|
|
||||||
*/
|
|
||||||
function schnorrGetPublicKey(privateKey: Hex): Uint8Array {
|
|
||||||
return schnorrGetExtPubKey(privateKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
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 sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
|
|
||||||
sig.set(rx, 0);
|
|
||||||
sig.set(numTo32b(modN(k + e * d)), 32);
|
|
||||||
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
|
|
||||||
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
|
||||||
return sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies Schnorr signature.
|
|
||||||
* Will swallow errors & return false except for initial type validation of arguments.
|
|
||||||
*/
|
|
||||||
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 {
|
|
||||||
const P = lift_x(bytesToNumberBE(pub)); // P = lift_x(int(pk)); fail if that fails
|
|
||||||
const r = bytesToNumberBE(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
|
|
||||||
if (!fe(r)) return false;
|
|
||||||
const s = bytesToNumberBE(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
|
|
||||||
if (!ge(s)) return false;
|
|
||||||
const e = challenge(numTo32b(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
|
|
||||||
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
|
|
||||||
return true; // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const schnorr = /* @__PURE__ */ (() => ({
|
|
||||||
getPublicKey: schnorrGetPublicKey,
|
|
||||||
sign: schnorrSign,
|
|
||||||
verify: schnorrVerify,
|
|
||||||
utils: {
|
|
||||||
randomPrivateKey: secp256k1.utils.randomPrivateKey,
|
|
||||||
lift_x,
|
|
||||||
pointToBytes,
|
|
||||||
numberToBytesBE,
|
|
||||||
bytesToNumberBE,
|
|
||||||
taggedHash,
|
|
||||||
mod,
|
|
||||||
},
|
|
||||||
}))();
|
|
||||||
|
|
||||||
const isoMap = /* @__PURE__ */ (() =>
|
|
||||||
isogenyMap(
|
|
||||||
Fp,
|
|
||||||
[
|
|
||||||
// xNum
|
|
||||||
[
|
|
||||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',
|
|
||||||
'0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',
|
|
||||||
'0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',
|
|
||||||
'0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',
|
|
||||||
],
|
|
||||||
// xDen
|
|
||||||
[
|
|
||||||
'0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',
|
|
||||||
'0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',
|
|
||||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
|
||||||
],
|
|
||||||
// yNum
|
|
||||||
[
|
|
||||||
'0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',
|
|
||||||
'0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',
|
|
||||||
'0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',
|
|
||||||
'0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',
|
|
||||||
],
|
|
||||||
// yDen
|
|
||||||
[
|
|
||||||
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',
|
|
||||||
'0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',
|
|
||||||
'0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',
|
|
||||||
'0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1
|
|
||||||
],
|
|
||||||
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
|
|
||||||
))();
|
|
||||||
const mapSWU = /* @__PURE__ */ (() =>
|
|
||||||
mapToCurveSimpleSWU(Fp, {
|
|
||||||
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
|
|
||||||
B: BigInt('1771'),
|
|
||||||
Z: Fp.create(BigInt('-11')),
|
|
||||||
}))();
|
|
||||||
const htf = /* @__PURE__ */ (() =>
|
|
||||||
createHasher(
|
|
||||||
secp256k1.ProjectivePoint,
|
|
||||||
(scalars: bigint[]) => {
|
|
||||||
const { x, y } = mapSWU(Fp.create(scalars[0]));
|
|
||||||
return isoMap(x, y);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',
|
|
||||||
encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',
|
|
||||||
p: Fp.ORDER,
|
|
||||||
m: 1,
|
|
||||||
k: 128,
|
|
||||||
expand: 'xmd',
|
|
||||||
hash: sha256,
|
|
||||||
}
|
|
||||||
))();
|
|
||||||
export const hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)();
|
|
||||||
export const encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)();
|
|
||||||
1262
src/shortw.ts
Normal file
1262
src/shortw.ts
Normal file
File diff suppressed because it is too large
Load Diff
69
src/utils.ts
Normal file
69
src/utils.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
// Convert between types
|
||||||
|
// ---------------------
|
||||||
|
|
||||||
|
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
||||||
|
export function bytesToHex(uint8a: Uint8Array): string {
|
||||||
|
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array');
|
||||||
|
// pre-caching improves the speed 6x
|
||||||
|
let hex = '';
|
||||||
|
for (let i = 0; i < uint8a.length; i++) {
|
||||||
|
hex += hexes[uint8a[i]];
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numberToHexUnpadded(num: number | bigint): string {
|
||||||
|
const hex = num.toString(16);
|
||||||
|
return hex.length & 1 ? `0${hex}` : hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hexToNumber(hex: string): bigint {
|
||||||
|
if (typeof hex !== 'string') {
|
||||||
|
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
|
||||||
|
}
|
||||||
|
// Big Endian
|
||||||
|
return BigInt(`0x${hex}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caching slows it down 2-3x
|
||||||
|
export function hexToBytes(hex: string): Uint8Array {
|
||||||
|
if (typeof hex !== 'string') {
|
||||||
|
throw new TypeError('hexToBytes: expected string, got ' + typeof hex);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Big Endian
|
||||||
|
export function bytesToNumber(bytes: Uint8Array): bigint {
|
||||||
|
return hexToNumber(bytesToHex(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureBytes(hex: string | Uint8Array): 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) : hexToBytes(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies several Uint8Arrays into one.
|
||||||
|
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
||||||
|
if (!arrays.every((b) => b instanceof Uint8Array)) throw new Error('Uint8Array list expected');
|
||||||
|
if (arrays.length === 1) return arrays[0];
|
||||||
|
const length = arrays.reduce((a, arr) => a + arr.length, 0);
|
||||||
|
const result = new Uint8Array(length);
|
||||||
|
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
||||||
|
const arr = arrays[i];
|
||||||
|
result.set(arr, pad);
|
||||||
|
pad += arr.length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { createCurve } from '../esm/_shortw_utils.js';
|
|
||||||
import { sha224, sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { Field as Fp } from '../esm/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;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user