10 Commits
0.7.1 ... 0.7.2

Author SHA1 Message Date
Paul Miller
0163b63532 Release 0.7.2. 2023-02-25 10:13:45 +01:00
Paul Miller
7e825520f1 README 2023-02-25 10:05:48 +01:00
Paul Miller
d739297b2c Move p192, p224 from main pkg to tests for now. Reason: not popular 2023-02-25 10:00:24 +01:00
Paul Miller
285aa6375d stark: refactor 2023-02-20 16:50:29 +01:00
Paul Miller
8c77331ef2 add hash-to-curve benchmark 2023-02-20 16:33:05 +01:00
Paul Miller
669641e0a3 README wording 2023-02-16 17:54:17 +01:00
Paul Miller
68dd57ed31 Cryptofuzz 2023-02-16 17:49:48 +01:00
Paul Miller
a9fdd6df9f readme: typo 2023-02-16 12:33:32 +01:00
Paul Miller
d485d8b0e6 Fix prettier 2023-02-16 12:32:32 +01:00
Paul Miller
0fdd763dc7 montgomery: add randomPrivateKey. Add ecdh benchmark. 2023-02-16 12:32:18 +01:00
15 changed files with 223 additions and 215 deletions

View File

@@ -9,21 +9,21 @@ Audited & minimal JS implementation of elliptic curve cryptography.
for encoding or hashing an arbitrary string to an elliptic curve point
- 🧜‍♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
- 🔍 Unique tests ensure correctness. Wycheproof vectors included
- 🔍 Unique tests ensure correctness with Wycheproof vectors and [cryptofuzz](https://github.com/guidovranken/cryptofuzz) differential fuzzing
- 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app
Package consists of two parts:
1. [Abstract](#abstract-api), zero-dependency EC algorithms
2. [Implementations](#implementations), utilizing one dependency `@noble/hashes`, providing ready-to-use:
- NIST curves secp192r1/P192, secp224r1/P224, secp256r1/P256, secp384r1/P384, secp521r1/P521
- NIST curves secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
- ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 [RFC7748](https://www.rfc-editor.org/rfc/rfc7748) / [RFC8032](https://www.rfc-editor.org/rfc/rfc8032) / [ZIP215](https://zips.z.cash/zip-0215) stuff
- pairing-friendly curves bls12-381, bn254
Check out [Upgrading](#upgrading) if you've previously used single-feature noble packages
([secp256k1](https://github.com/paulmillr/noble-secp256k1), [ed25519](https://github.com/paulmillr/noble-ed25519)).
See [Resources](#resouces) for articles and real-world software that uses curves.
See [Resources](#resources) for articles and real-world software that uses curves.
### This library belongs to _noble_ crypto
@@ -66,10 +66,6 @@ secp256k1.verify(sig, msg, pub) === true;
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings
// Follows hash-to-curve specification to encode arbitrary hashes to EC points
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
hashToCurve('0102abcd');
```
All curves:
@@ -180,10 +176,9 @@ const signatures3 = privateKeys.map((p, i) => bls.sign(messages[i], p));
const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// bls.pairing(PointG1, PointG2) // pairings
// Pairings
// bls.pairing(PointG1, PointG2)
// Also, check out hash-to-curve examples below.
// hash-to-curve examples can be seen below
```
## Abstract API
@@ -482,9 +477,11 @@ Every curve has exported `hashToCurve` and `encodeToCurve` methods:
```ts
import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
hashToCurve('0102abcd');
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));
import { bls12_381 } from '@noble/curves/bls12-381';
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
@@ -610,7 +607,7 @@ utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));
## Security
The library had no prior security audit.
The library had no prior security audit. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz): you can run the fuzzer by yourself to check it.
[Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: we are using non-CT bigints. However, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. Even statically typed Rust, a language without GC, [makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
@@ -674,6 +671,14 @@ pedersen x 884 ops/sec @ 1ms/op
poseidon x 8,598 ops/sec @ 116μs/op
verify x 528 ops/sec @ 1ms/op
ecdh
├─x25519 x 1,337 ops/sec @ 747μs/op
├─secp256k1 x 461 ops/sec @ 2ms/op
├─P256 x 441 ops/sec @ 2ms/op
├─P384 x 179 ops/sec @ 5ms/op
├─P521 x 93 ops/sec @ 10ms/op
└─x448 x 496 ops/sec @ 2ms/op
bls12-381
init x 32 ops/sec @ 30ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op

19
benchmark/ecdh.js Normal file
View File

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

View File

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

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@noble/curves",
"version": "0.7.0",
"version": "0.7.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@noble/curves",
"version": "0.7.0",
"version": "0.7.2",
"funding": [
{
"type": "individual",

View File

@@ -1,6 +1,6 @@
{
"name": "@noble/curves",
"version": "0.7.1",
"version": "0.7.2",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [
"abstract",
@@ -12,7 +12,7 @@
"*.d.ts.map"
],
"scripts": {
"bench": "cd benchmark; node secp256k1.js; node curves.js; node stark.js; node bls.js",
"bench": "cd benchmark; node secp256k1.js; node curves.js; node ecdh.js; node stark.js; node bls.js",
"build": "tsc && tsc -p tsconfig.esm.json",
"build:release": "rollup -c rollup.config.js",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/*.js'",
@@ -126,16 +126,6 @@
"import": "./esm/jubjub.js",
"default": "./jubjub.js"
},
"./p192": {
"types": "./p192.d.ts",
"import": "./esm/p192.js",
"default": "./p192.js"
},
"./p224": {
"types": "./p224.d.ts",
"import": "./esm/p224.js",
"default": "./p224.js"
},
"./p256": {
"types": "./p256.d.ts",
"import": "./esm/p256.js",

View File

@@ -16,12 +16,14 @@ export type CurveType = {
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;
};
@@ -181,6 +183,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
scalarMultBase,
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
utils: { randomPrivateKey: () => CURVE.randomBytes!(CURVE.nByteLength) },
GuBytes: GuBytes,
};
}

View File

@@ -149,6 +149,7 @@ export const x25519 = montgomery({
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
},
adjustScalarBytes,
randomBytes,
});
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)

View File

@@ -134,6 +134,7 @@ export const x448 = montgomery({
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
},
adjustScalarBytes,
randomBytes,
// The 4-isogeny maps between the Montgomery curve and this Edwards
// curve are:
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,15 +4,14 @@ import * as fc from 'fast-check';
import * as mod from '../esm/abstract/modular.js';
import { bytesToHex as toHex } from '../esm/abstract/utils.js';
// Generic tests for all curves in package
import { secp192r1 } from '../esm/p192.js';
import { secp224r1 } from '../esm/p224.js';
import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
import { secp256r1 } from '../esm/p256.js';
import { secp384r1 } from '../esm/p384.js';
import { secp521r1 } from '../esm/p521.js';
import { secp256k1 } from '../esm/secp256k1.js';
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../esm/ed25519.js';
import { ed448, ed448ph } from '../esm/ed448.js';
import { starkCurve } from '../esm/stark.js';
import { _starkCurve as starkCurve } from '../esm/stark.js';
import { pallas, vesta } from '../esm/pasta.js';
import { bn254 } from '../esm/bn.js';
import { jubjub } from '../esm/jubjub.js';

View File

@@ -1,7 +1,6 @@
import { deepStrictEqual, throws } from 'assert';
import { deepStrictEqual } from 'assert';
import { describe, should } from 'micro-should';
import { secp192r1, P192 } from '../esm/p192.js';
import { secp224r1, P224 } from '../esm/p224.js';
import { secp192r1, secp224r1, P192, P224 } from './_more-curves.helpers.js';
import { secp256r1, P256 } from '../esm/p256.js';
import { secp384r1, P384 } from '../esm/p384.js';
import { secp521r1, P521 } from '../esm/p521.js';

View File

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