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
|
||||
# custom: https://paulmillr.com/funding/
|
||||
21
.github/workflows/nodejs.yml
vendored
21
.github/workflows/nodejs.yml
vendored
@ -1,23 +1,18 @@
|
||||
name: Run node.js tests
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
name: Node CI
|
||||
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
name: v${{ matrix.node }} @ ubuntu-latest
|
||||
name: v18 @ ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node:
|
||||
- 18
|
||||
- 20
|
||||
steps:
|
||||
- uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
node-version: 18
|
||||
- run: npm install
|
||||
- run: npm run build --if-present
|
||||
- run: cd curve-definitions; npm install; npm run build --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
|
||||
/*.js
|
||||
/esm/*.js
|
||||
*.d.ts
|
||||
*.d.ts.map
|
||||
*.js.map
|
||||
/build
|
||||
/abstract
|
||||
/esm/abstract
|
||||
build/
|
||||
node_modules/
|
||||
coverage/
|
||||
/lib/**/*.js
|
||||
/lib/**/*.ts
|
||||
/lib/**/*.d.ts.map
|
||||
/curve-definitions/lib
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
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",
|
||||
"cases": [
|
||||
{
|
||||
"k": "C1D1F2F10881088301880506805FEB4825FE09ACB6816C36991AA06D",
|
||||
"k": "AD3029E0278F80643DE33917CE6908C70A8FF50A411F06E41DEDFCDC",
|
||||
"message": "sample",
|
||||
"r": "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
|
||||
"s": "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC"
|
||||
"r": "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
|
||||
"s": "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101"
|
||||
},
|
||||
{
|
||||
"k": "DF8B38D40DCA3E077D0AC520BF56B6D565134D9B5F2EAE0D34900524",
|
||||
"k": "FF86F57924DA248D6E44E8154EB69F0AE2AEBAEE9931D0B5A969F904",
|
||||
"message": "test",
|
||||
"r": "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
|
||||
"s": "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4"
|
||||
"r": "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
|
||||
"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",
|
||||
"version": "1.4.0",
|
||||
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
||||
"name": "@noble/curves",
|
||||
"version": "0.1.0",
|
||||
"description": "Minimal, zero-dependency JS implementation of elliptic curve cryptography",
|
||||
"files": [
|
||||
"abstract",
|
||||
"esm",
|
||||
"src",
|
||||
"*.js",
|
||||
"*.js.map",
|
||||
"*.d.ts",
|
||||
"*.d.ts.map"
|
||||
"index.js",
|
||||
"lib",
|
||||
"lib/esm"
|
||||
],
|
||||
"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:release": "cd build && npm i && npm run build",
|
||||
"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}' 'test/*.js'",
|
||||
"format": "prettier --write 'src/**/*.{js,ts}' 'test/*.js'",
|
||||
"test": "node test/index.test.js"
|
||||
"build:release": "rollup -c rollup.config.js",
|
||||
"lint": "prettier --check 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
||||
"format": "prettier --write 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
||||
"test": "cd curve-definitions; node test/index.test.js"
|
||||
},
|
||||
"author": "Paul Miller (https://paulmillr.com)",
|
||||
"homepage": "https://paulmillr.com/noble/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.tornado.ws/tornado-packages/noble-curvest"
|
||||
"url": "https://github.com/paulmillr/noble-curves.git"
|
||||
},
|
||||
"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"
|
||||
"@rollup/plugin-node-resolve": "13.3.0",
|
||||
"micro-bmark": "0.2.0",
|
||||
"micro-should": "0.2.0",
|
||||
"prettier": "2.6.2",
|
||||
"rollup": "2.75.5",
|
||||
"typescript": "4.7.3"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./esm/index.js",
|
||||
"default": "./index.js"
|
||||
"./modular": {
|
||||
"types": "./lib/modular.d.ts",
|
||||
"import": "./lib/esm/modular.js",
|
||||
"default": "./lib/modular.js"
|
||||
},
|
||||
"./abstract/edwards": {
|
||||
"types": "./abstract/edwards.d.ts",
|
||||
"import": "./esm/abstract/edwards.js",
|
||||
"default": "./abstract/edwards.js"
|
||||
"./shortw": {
|
||||
"types": "./lib/shortw.d.ts",
|
||||
"import": "./lib/esm/shortw.js",
|
||||
"default": "./lib/shortw.js"
|
||||
},
|
||||
"./abstract/modular": {
|
||||
"types": "./abstract/modular.d.ts",
|
||||
"import": "./esm/abstract/modular.js",
|
||||
"default": "./abstract/modular.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"
|
||||
"./utils": {
|
||||
"types": "./lib/utils.d.ts",
|
||||
"import": "./lib/esm/utils.js",
|
||||
"default": "./lib/utils.js"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"elliptic",
|
||||
"curve",
|
||||
"cryptography",
|
||||
"weierstrass",
|
||||
"montgomery",
|
||||
"edwards",
|
||||
"hyperelliptic",
|
||||
"p256",
|
||||
"p384",
|
||||
"p521",
|
||||
"secp256r1",
|
||||
"secp256k1",
|
||||
"ed25519",
|
||||
"ed448",
|
||||
"x25519",
|
||||
"ed25519",
|
||||
"bls12-381",
|
||||
"bn254",
|
||||
"pasta",
|
||||
"bls",
|
||||
"noble",
|
||||
"nist",
|
||||
"weierstrass",
|
||||
"edwards",
|
||||
"montgomery",
|
||||
"hashes",
|
||||
"ecc",
|
||||
"ecdsa",
|
||||
"eddsa",
|
||||
"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