forked from tornado-packages/noble-curves
Add ristretto, schnorr
This commit is contained in:
parent
5b305abe85
commit
9e5ad8dc85
@ -46,10 +46,15 @@ export const CURVES = {
|
|||||||
const sig = noble_secp256k1.signSync(msg, priv);
|
const sig = noble_secp256k1.signSync(msg, priv);
|
||||||
return { priv, pub, msg, sig };
|
return { priv, pub, msg, sig };
|
||||||
},
|
},
|
||||||
getPublicKey: {
|
getPublicKey1: {
|
||||||
samples: 10000,
|
samples: 10000,
|
||||||
old: () => noble_secp256k1.getPublicKey(noble_secp256k1.utils.randomPrivateKey()),
|
noble: () => secp256k1.getPublicKey(3n),
|
||||||
noble: () => secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey()),
|
old: () => noble_secp256k1.getPublicKey(3n),
|
||||||
|
},
|
||||||
|
getPublicKey255: {
|
||||||
|
samples: 10000,
|
||||||
|
noble: () => secp256k1.getPublicKey(2n**255n-1n),
|
||||||
|
old: () => noble_secp256k1.getPublicKey(2n**255n-1n),
|
||||||
},
|
},
|
||||||
sign: {
|
sign: {
|
||||||
samples: 5000,
|
samples: 5000,
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||||
import { twistedEdwards } from '@noble/curves/edwards';
|
import { twistedEdwards, ExtendedPointType } from '@noble/curves/edwards';
|
||||||
import { montgomery } from '@noble/curves/montgomery';
|
import { montgomery } from '@noble/curves/montgomery';
|
||||||
import { mod, pow2, isNegativeLE } from '@noble/curves/modular';
|
import { mod, pow2, isNegativeLE } from '@noble/curves/modular';
|
||||||
|
import {
|
||||||
|
ensureBytes,
|
||||||
|
equalBytes,
|
||||||
|
bytesToHex,
|
||||||
|
bytesToNumberLE,
|
||||||
|
numberToBytesLE,
|
||||||
|
Hex,
|
||||||
|
} from '@noble/curves/utils';
|
||||||
|
|
||||||
const ed25519P = BigInt(
|
const ed25519P = BigInt(
|
||||||
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
||||||
@ -47,6 +55,25 @@ function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
|||||||
bytes[31] |= 64; // 0b0100_0000
|
bytes[31] |= 64; // 0b0100_0000
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
// sqrt(u/v)
|
||||||
|
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
||||||
|
const P = ed25519P;
|
||||||
|
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
|
// Just in case
|
||||||
export const ED25519_TORSION_SUBGROUP = [
|
export const ED25519_TORSION_SUBGROUP = [
|
||||||
@ -82,24 +109,7 @@ const ED25519_DEF = {
|
|||||||
// dom2
|
// dom2
|
||||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||||
// Constant-time, u/√v
|
// Constant-time, u/√v
|
||||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
uvRatio,
|
||||||
const P = ed25519P;
|
|
||||||
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 };
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ed25519 = twistedEdwards(ED25519_DEF);
|
export const ed25519 = twistedEdwards(ED25519_DEF);
|
||||||
@ -133,3 +143,192 @@ export const x25519 = montgomery({
|
|||||||
},
|
},
|
||||||
adjustScalarBytes,
|
adjustScalarBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
function assertRstPoint(other: unknown) {
|
||||||
|
if (!(other instanceof RistrettoPoint)) throw new TypeError('RistrettoPoint expected');
|
||||||
|
}
|
||||||
|
// √(-1) aka √(a) aka 2^((p-1)/4)
|
||||||
|
const SQRT_M1 = BigInt(
|
||||||
|
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||||
|
);
|
||||||
|
// √(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 _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
||||||
|
|
||||||
|
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
||||||
|
const bytes255ToNumberLE = (bytes: Uint8Array) =>
|
||||||
|
ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B);
|
||||||
|
|
||||||
|
type ExtendedPoint = ExtendedPointType;
|
||||||
|
export class RistrettoPoint {
|
||||||
|
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE);
|
||||||
|
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO);
|
||||||
|
|
||||||
|
// Private property to discourage combining ExtendedPoint + RistrettoPoint
|
||||||
|
// Always use Ristretto encoding/decoding instead.
|
||||||
|
constructor(private readonly ep: ExtendedPoint) {}
|
||||||
|
|
||||||
|
// Computes Elligator map for Ristretto
|
||||||
|
// https://ristretto.group/formulas/elligator.html
|
||||||
|
private static calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
||||||
|
const { d, P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`.
|
||||||
|
* The hash-to-group operation applies Elligator twice and adds the results.
|
||||||
|
* **Note:** this is one-way map, there is no conversion from point to hash.
|
||||||
|
* https://ristretto.group/formulas/elligator.html
|
||||||
|
* @param hex 64-bit output of a hash function
|
||||||
|
*/
|
||||||
|
static hashToCurve(hex: Hex): RistrettoPoint {
|
||||||
|
hex = ensureBytes(hex, 64);
|
||||||
|
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
||||||
|
const R1 = this.calcElligatorRistrettoMap(r1);
|
||||||
|
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
||||||
|
const R2 = this.calcElligatorRistrettoMap(r2);
|
||||||
|
return new RistrettoPoint(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): RistrettoPoint {
|
||||||
|
hex = ensureBytes(hex, 32);
|
||||||
|
const { a, d, P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
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 RistrettoPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes ristretto point to Uint8Array.
|
||||||
|
* https://ristretto.group/formulas/encoding.html
|
||||||
|
*/
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
let { x, y, z, t } = this.ep;
|
||||||
|
const { P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
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: RistrettoPoint): boolean {
|
||||||
|
assertRstPoint(other);
|
||||||
|
const a = this.ep;
|
||||||
|
const b = other.ep;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
||||||
|
const one = mod(a.x * b.y) === mod(a.y * b.x);
|
||||||
|
const two = mod(a.y * b.y) === mod(a.x * b.x);
|
||||||
|
return one || two;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(other: RistrettoPoint): RistrettoPoint {
|
||||||
|
assertRstPoint(other);
|
||||||
|
return new RistrettoPoint(this.ep.add(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(other: RistrettoPoint): RistrettoPoint {
|
||||||
|
assertRstPoint(other);
|
||||||
|
return new RistrettoPoint(this.ep.subtract(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiply(scalar: number | bigint): RistrettoPoint {
|
||||||
|
return new RistrettoPoint(this.ep.multiply(scalar));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplyUnsafe(scalar: number | bigint): RistrettoPoint {
|
||||||
|
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,16 @@
|
|||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { mod, pow2 } from '@noble/curves/modular';
|
import { mod, pow2 } from '@noble/curves/modular';
|
||||||
import { createCurve } from './_shortw_utils.js';
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { PointType } from '@noble/curves/weierstrass';
|
||||||
|
import {
|
||||||
|
ensureBytes,
|
||||||
|
concatBytes,
|
||||||
|
Hex,
|
||||||
|
hexToBytes,
|
||||||
|
bytesToNumberBE,
|
||||||
|
PrivKey,
|
||||||
|
} from '@noble/curves/utils';
|
||||||
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* secp256k1 definition with efficient square root and endomorphism.
|
* secp256k1 definition with efficient square root and endomorphism.
|
||||||
@ -17,6 +27,32 @@ const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd
|
|||||||
const _1n = BigInt(1);
|
const _1n = BigInt(1);
|
||||||
const _2n = BigInt(2);
|
const _2n = BigInt(2);
|
||||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||||
|
|
||||||
|
function 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);
|
||||||
|
}
|
||||||
|
|
||||||
export const secp256k1 = createCurve(
|
export const secp256k1 = createCurve(
|
||||||
{
|
{
|
||||||
a: 0n,
|
a: 0n,
|
||||||
@ -37,30 +73,7 @@ export const secp256k1 = createCurve(
|
|||||||
// We are unwrapping the loop because it's 2x faster.
|
// 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]
|
// (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||||
// We are multiplying it bit-by-bit
|
// We are multiplying it bit-by-bit
|
||||||
sqrtMod: (x: bigint): bigint => {
|
sqrtMod,
|
||||||
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: {
|
endo: {
|
||||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||||
splitScalar: (k: bigint) => {
|
splitScalar: (k: bigint) => {
|
||||||
@ -88,3 +101,160 @@ export const secp256k1 = createCurve(
|
|||||||
},
|
},
|
||||||
sha256
|
sha256
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Schnorr
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const numTo32b = secp256k1.utils._bigintToBytes;
|
||||||
|
const numTo32bStr = secp256k1.utils._bigintToString;
|
||||||
|
const normalizePrivateKey = secp256k1.utils._normalizePrivateKey;
|
||||||
|
|
||||||
|
// TODO: export?
|
||||||
|
function normalizePublicKey(publicKey: Hex | PointType): PointType {
|
||||||
|
if (publicKey instanceof secp256k1.Point) {
|
||||||
|
publicKey.assertValidity();
|
||||||
|
return publicKey;
|
||||||
|
} else {
|
||||||
|
const bytes = ensureBytes(publicKey);
|
||||||
|
// Schnorr is 32 bytes
|
||||||
|
if (bytes.length === 32) {
|
||||||
|
const x = bytesToNumberBE(bytes);
|
||||||
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
|
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
|
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
|
||||||
|
const isYOdd = (y & _1n) === _1n;
|
||||||
|
// Schnorr
|
||||||
|
if (isYOdd) y = mod(-y, secp256k1.CURVE.P);
|
||||||
|
const point = new secp256k1.Point(x, y);
|
||||||
|
point.assertValidity();
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
// Do we need that in schnorr at all?
|
||||||
|
return secp256k1.Point.fromHex(publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
|
||||||
|
const isValidFieldElement = secp256k1.utils._isValidFieldElement;
|
||||||
|
|
||||||
|
const TAGS = {
|
||||||
|
challenge: 'BIP0340/challenge',
|
||||||
|
aux: 'BIP0340/aux',
|
||||||
|
nonce: 'BIP0340/nonce',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
||||||
|
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
||||||
|
export 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRawX = (point: PointType) => point.toRawBytes(true).slice(1);
|
||||||
|
|
||||||
|
// Schnorr signatures are superior to ECDSA from above.
|
||||||
|
// Below is Schnorr-specific code as per BIP0340.
|
||||||
|
function schnorrChallengeFinalize(ch: Uint8Array): bigint {
|
||||||
|
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n);
|
||||||
|
}
|
||||||
|
// Do we need this at all for Schnorr?
|
||||||
|
class SchnorrSignature {
|
||||||
|
constructor(readonly r: bigint, readonly s: bigint) {
|
||||||
|
this.assertValidity();
|
||||||
|
}
|
||||||
|
static fromHex(hex: Hex) {
|
||||||
|
const bytes = ensureBytes(hex);
|
||||||
|
if (bytes.length !== 64)
|
||||||
|
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`);
|
||||||
|
const r = bytesToNumberBE(bytes.subarray(0, 32));
|
||||||
|
const s = bytesToNumberBE(bytes.subarray(32, 64));
|
||||||
|
return new SchnorrSignature(r, s);
|
||||||
|
}
|
||||||
|
assertValidity() {
|
||||||
|
const { r, s } = this;
|
||||||
|
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) throw new Error('Invalid signature');
|
||||||
|
}
|
||||||
|
toHex(): string {
|
||||||
|
return numTo32bStr(this.r) + numTo32bStr(this.s);
|
||||||
|
}
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
return hexToBytes(this.toHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function schnorrGetScalar(priv: bigint) {
|
||||||
|
const point = secp256k1.Point.fromPrivateKey(priv);
|
||||||
|
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv;
|
||||||
|
return { point, scalar, x: toRawX(point) };
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Synchronously creates Schnorr signature. Improved security: verifies itself before
|
||||||
|
* producing an output.
|
||||||
|
* @param msg message (not message hash)
|
||||||
|
* @param privateKey private key
|
||||||
|
* @param auxRand random bytes that would be added to k. Bad RNG won't break it.
|
||||||
|
*/
|
||||||
|
function schnorrSign(
|
||||||
|
message: Hex,
|
||||||
|
privateKey: PrivKey,
|
||||||
|
auxRand: Hex = randomBytes(32)
|
||||||
|
): Uint8Array {
|
||||||
|
if (message == null) throw new TypeError(`sign: Expected valid message, not "${message}"`);
|
||||||
|
const m = ensureBytes(message);
|
||||||
|
// checks for isWithinCurveOrder
|
||||||
|
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey));
|
||||||
|
const rand = ensureBytes(auxRand);
|
||||||
|
if (rand.length !== 32) throw new TypeError('sign: Expected 32 bytes of aux randomness');
|
||||||
|
const tag = taggedHash;
|
||||||
|
const t0h = tag(TAGS.aux, rand);
|
||||||
|
const t = numTo32b(d ^ bytesToNumberBE(t0h));
|
||||||
|
const k0h = tag(TAGS.nonce, t, px, m);
|
||||||
|
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n);
|
||||||
|
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero');
|
||||||
|
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
|
||||||
|
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
|
||||||
|
const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
|
||||||
|
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies Schnorr signature synchronously.
|
||||||
|
*/
|
||||||
|
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
|
||||||
|
try {
|
||||||
|
const raw = signature instanceof SchnorrSignature;
|
||||||
|
const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature);
|
||||||
|
if (raw) sig.assertValidity(); // just in case
|
||||||
|
|
||||||
|
const { r, s } = sig;
|
||||||
|
const m = ensureBytes(message);
|
||||||
|
const P = normalizePublicKey(publicKey);
|
||||||
|
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m));
|
||||||
|
// Finalize
|
||||||
|
// R = s⋅G - e⋅P
|
||||||
|
// -eP == (n-e)P
|
||||||
|
const R = secp256k1.Point.BASE.multiplyAndAddUnsafe(
|
||||||
|
P,
|
||||||
|
normalizePrivateKey(s),
|
||||||
|
mod(-e, secp256k1.CURVE.n)
|
||||||
|
);
|
||||||
|
if (!R || !R.hasEvenY() || R.x !== r) return false;
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const schnorr = {
|
||||||
|
Signature: SchnorrSignature,
|
||||||
|
// Schnorr's pubkey is just `x` of Point (BIP340)
|
||||||
|
getPublicKey: (privateKey: PrivKey): Uint8Array =>
|
||||||
|
toRawX(secp256k1.Point.fromPrivateKey(privateKey)),
|
||||||
|
sign: schnorrSign,
|
||||||
|
verify: schnorrVerify,
|
||||||
|
};
|
||||||
|
@ -19,7 +19,8 @@ import { jubjub } from '../lib/jubjub.js';
|
|||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const CURVES = {
|
const CURVES = {
|
||||||
secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1,
|
secp192r1, secp224r1, secp256r1, secp384r1, secp521r1,
|
||||||
|
secp256k1,
|
||||||
ed25519, ed25519ctx, ed25519ph,
|
ed25519, ed25519ctx, ed25519ph,
|
||||||
ed448, ed448ph,
|
ed448, ed448ph,
|
||||||
starkCurve,
|
starkCurve,
|
||||||
@ -46,14 +47,15 @@ for (const name in CURVES) {
|
|||||||
const CURVE_ORDER = C.CURVE.n;
|
const CURVE_ORDER = C.CURVE.n;
|
||||||
const FC_BIGINT = fc.bigInt(1n + 1n, CURVE_ORDER - 1n);
|
const FC_BIGINT = fc.bigInt(1n + 1n, CURVE_ORDER - 1n);
|
||||||
|
|
||||||
const POINTS = { Point: C.Point, JacobianPoint: C.JacobianPoint, ExtendedPoint: C.ExtendedPoint };
|
|
||||||
// Check that curve doesn't accept points from other curves
|
// Check that curve doesn't accept points from other curves
|
||||||
const O = name === 'secp256k1' ? secp256r1 : secp256k1;
|
const O = name === 'secp256k1' ? secp256r1 : secp256k1;
|
||||||
const OTHER_POINTS = {
|
const POINTS = {};
|
||||||
Point: O.Point,
|
const OTHER_POINTS = {};
|
||||||
JacobianPoint: O.JacobianPoint,
|
for (const name of ['Point', 'JacobianPoint', 'ExtendedPoint', 'ProjectivePoint']) {
|
||||||
ExtendedPoint: O.ExtendedPoint,
|
POINTS[name] = C[name];
|
||||||
};
|
OTHER_POINTS[name] = O[name];
|
||||||
|
}
|
||||||
|
|
||||||
for (const pointName in POINTS) {
|
for (const pointName in POINTS) {
|
||||||
const p = POINTS[pointName];
|
const p = POINTS[pointName];
|
||||||
const o = OTHER_POINTS[pointName];
|
const o = OTHER_POINTS[pointName];
|
||||||
@ -120,6 +122,7 @@ for (const name in CURVES) {
|
|||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||||
const c = mod.mod(a + b, CURVE_ORDER);
|
const c = mod.mod(a + b, CURVE_ORDER);
|
||||||
|
if (c === CURVE_ORDER || c < 1n) return;
|
||||||
const pA = G[1].multiply(a);
|
const pA = G[1].multiply(a);
|
||||||
const pB = G[1].multiply(b);
|
const pB = G[1].multiply(b);
|
||||||
const pC = G[1].multiply(c);
|
const pC = G[1].multiply(c);
|
||||||
@ -157,7 +160,7 @@ for (const name in CURVES) {
|
|||||||
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||||
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
|
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
|
||||||
throws(() => G[1][op](O.BASE), `${op}/other curve point`);
|
throws(() => G[1][op](o.BASE), `${op}/other curve point`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +180,7 @@ for (const name in CURVES) {
|
|||||||
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
|
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
|
||||||
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`);
|
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`);
|
||||||
throws(() => G[1].equals(O.BASE), 'other curve point');
|
throws(() => G[1].equals(o.BASE), 'other curve point');
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const op of ['multiply', 'multiplyUnsafe']) {
|
for (const op of ['multiply', 'multiplyUnsafe']) {
|
||||||
@ -199,7 +202,7 @@ for (const name in CURVES) {
|
|||||||
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||||
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||||
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
throws(() => G[1][op](O.BASE), 'other curve point');
|
throws(() => G[1][op](o.BASE), 'other curve point');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Complex point (Extended/Jacobian/Projective?)
|
// Complex point (Extended/Jacobian/Projective?)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../lib/ed25519.js';
|
import { ed25519, ed25519ctx, ed25519ph, x25519, RistrettoPoint } from '../lib/ed25519.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
||||||
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||||
import { numberToBytesLE } from '@noble/curves/utils';
|
import { numberToBytesLE } from '@noble/curves/utils';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
||||||
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
@ -242,18 +243,17 @@ should('rfc8032 vectors/should create right signature for 0xf5 and long msg', ()
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// const { RistrettoPoint } = ed;
|
// const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
|
||||||
// // const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
|
// const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103]));
|
||||||
// // const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103]));
|
// prettier-ignore
|
||||||
// // prettier-ignore
|
// const MESSAGE = new Uint8Array([
|
||||||
// // const MESSAGE = new Uint8Array([
|
// 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58,
|
||||||
// // 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58,
|
// ]);
|
||||||
// // ]);
|
// const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103]));
|
||||||
// // const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103]));
|
// prettier-ignore
|
||||||
// // prettier-ignore
|
// const WRONG_MESSAGE = new Uint8Array([
|
||||||
// // const WRONG_MESSAGE = new Uint8Array([
|
// 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28,
|
||||||
// // 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28,
|
// ]);
|
||||||
// // ]);
|
|
||||||
// // it("should verify just signed message", async () => {
|
// // it("should verify just signed message", async () => {
|
||||||
// // await fc.assert(fc.asyncProperty(
|
// // await fc.assert(fc.asyncProperty(
|
||||||
// // fc.hexa(),
|
// // fc.hexa(),
|
||||||
@ -300,104 +300,104 @@ should('rfc8032 vectors/should create right signature for 0xf5 and long msg', ()
|
|||||||
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
||||||
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
|
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
|
||||||
// // });
|
// // });
|
||||||
// should('ristretto255/should follow the byte encodings of small multiples', () => {
|
should('ristretto255/should follow the byte encodings of small multiples', () => {
|
||||||
// const encodingsOfSmallMultiples = [
|
const encodingsOfSmallMultiples = [
|
||||||
// // This is the identity point
|
// This is the identity point
|
||||||
// '0000000000000000000000000000000000000000000000000000000000000000',
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
// // This is the basepoint
|
// This is the basepoint
|
||||||
// 'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
|
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
|
||||||
// // These are small multiples of the basepoint
|
// These are small multiples of the basepoint
|
||||||
// '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
|
||||||
// '94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
|
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
|
||||||
// 'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
|
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
|
||||||
// 'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
|
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
|
||||||
// 'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
|
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
|
||||||
// '44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
|
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
|
||||||
// '903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
|
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
|
||||||
// '02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
|
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
|
||||||
// '20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
|
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
|
||||||
// 'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
|
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
|
||||||
// 'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
|
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
|
||||||
// 'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
|
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
|
||||||
// '46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
|
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
|
||||||
// 'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
|
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
|
||||||
// ];
|
];
|
||||||
// let B = RistrettoPoint.BASE;
|
let B = RistrettoPoint.BASE;
|
||||||
// let P = RistrettoPoint.ZERO;
|
let P = RistrettoPoint.ZERO;
|
||||||
// for (const encoded of encodingsOfSmallMultiples) {
|
for (const encoded of encodingsOfSmallMultiples) {
|
||||||
// deepStrictEqual(P.toHex(), encoded);
|
deepStrictEqual(P.toHex(), encoded);
|
||||||
// deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
|
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
|
||||||
// P = P.add(B);
|
P = P.add(B);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// should('ristretto255/should not convert bad bytes encoding', () => {
|
should('ristretto255/should not convert bad bytes encoding', () => {
|
||||||
// const badEncodings = [
|
const badEncodings = [
|
||||||
// // These are all bad because they're non-canonical field encodings.
|
// These are all bad because they're non-canonical field encodings.
|
||||||
// '00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
// 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
// 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
// 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
// // These are all bad because they're negative field elements.
|
// These are all bad because they're negative field elements.
|
||||||
// '0100000000000000000000000000000000000000000000000000000000000000',
|
'0100000000000000000000000000000000000000000000000000000000000000',
|
||||||
// '01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
// 'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
|
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
|
||||||
// 'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
|
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
|
||||||
// 'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
|
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
|
||||||
// '47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
|
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
|
||||||
// 'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
|
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
|
||||||
// '87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
|
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
|
||||||
// // These are all bad because they give a nonsquare x^2.
|
// These are all bad because they give a nonsquare x^2.
|
||||||
// '26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
|
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
|
||||||
// '4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
|
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
|
||||||
// 'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
|
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
|
||||||
// 'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
|
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
|
||||||
// '2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
|
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
|
||||||
// 'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
|
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
|
||||||
// '8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
|
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
|
||||||
// '2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
|
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
|
||||||
// // These are all bad because they give a negative xy value.
|
// These are all bad because they give a negative xy value.
|
||||||
// '3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
|
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
|
||||||
// 'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
|
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
|
||||||
// 'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
|
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
|
||||||
// '8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
|
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
|
||||||
// '32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
|
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
|
||||||
// '227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
|
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
|
||||||
// '5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
|
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
|
||||||
// '445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
|
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
|
||||||
// // This is s = -1, which causes y = 0.
|
// This is s = -1, which causes y = 0.
|
||||||
// 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
// ];
|
];
|
||||||
// for (const badBytes of badEncodings) {
|
for (const badBytes of badEncodings) {
|
||||||
// const b = hexToBytes(badBytes);
|
const b = hexToBytes(badBytes);
|
||||||
// throws(() => RistrettoPoint.fromHex(b));
|
throws(() => RistrettoPoint.fromHex(b), badBytes);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// should('ristretto255/should create right points from uniform hash', async () => {
|
should('ristretto255/should create right points from uniform hash', async () => {
|
||||||
// const labels = [
|
const labels = [
|
||||||
// 'Ristretto is traditionally a short shot of espresso coffee',
|
'Ristretto is traditionally a short shot of espresso coffee',
|
||||||
// 'made with the normal amount of ground coffee but extracted with',
|
'made with the normal amount of ground coffee but extracted with',
|
||||||
// 'about half the amount of water in the same amount of time',
|
'about half the amount of water in the same amount of time',
|
||||||
// 'by using a finer grind.',
|
'by using a finer grind.',
|
||||||
// 'This produces a concentrated shot of coffee per volume.',
|
'This produces a concentrated shot of coffee per volume.',
|
||||||
// 'Just pulling a normal shot short will produce a weaker shot',
|
'Just pulling a normal shot short will produce a weaker shot',
|
||||||
// 'and is not a Ristretto as some believe.',
|
'and is not a Ristretto as some believe.',
|
||||||
// ];
|
];
|
||||||
// const encodedHashToPoints = [
|
const encodedHashToPoints = [
|
||||||
// '3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
|
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
|
||||||
// 'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
|
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
|
||||||
// '006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
|
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
|
||||||
// 'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
|
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
|
||||||
// 'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
|
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
|
||||||
// 'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
|
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
|
||||||
// '80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
|
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
|
||||||
// ];
|
];
|
||||||
|
|
||||||
// for (let i = 0; i < labels.length; i++) {
|
for (let i = 0; i < labels.length; i++) {
|
||||||
// const hash = sha512(utf8ToBytes(labels[i]));
|
const hash = sha512(utf8ToBytes(labels[i]));
|
||||||
// const point = RistrettoPoint.hashToCurve(hash);
|
const point = RistrettoPoint.hashToCurve(hash);
|
||||||
// deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
should('input immutability: sign/verify are immutable', () => {
|
should('input immutability: sign/verify are immutable', () => {
|
||||||
const privateKey = ed.utils.randomPrivateKey();
|
const privateKey = ed.utils.randomPrivateKey();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
import { secp256k1 } from '../lib/secp256k1.js';
|
import { secp256k1, schnorr } from '../lib/secp256k1.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
||||||
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
||||||
@ -341,37 +341,25 @@ should(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// describe('schnorr', () => {
|
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||||
// // index,secret key,public key,aux_rand,message,signature,verification result,comment
|
const vectors = schCsv
|
||||||
// const vectors = schCsv
|
.split('\n')
|
||||||
// .split('\n')
|
.map((line) => line.split(','))
|
||||||
// .map((line: string) => line.split(','))
|
.slice(1, -1);
|
||||||
// .slice(1, -1);
|
for (let vec of vectors) {
|
||||||
// for (let vec of vectors) {
|
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||||
// const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
should(`sign with Schnorr scheme vector ${index}`, () => {
|
||||||
// it(`should sign with Schnorr scheme vector ${index}`, async () => {
|
if (sec) {
|
||||||
// if (sec) {
|
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
||||||
// expect(hex(secp.schnorr.getPublicKey(sec))).toBe(pub.toLowerCase());
|
const sig = schnorr.sign(msg, sec, rnd);
|
||||||
// const sig = await secp.schnorr.sign(msg, sec, rnd);
|
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
||||||
// const sigS = secp.schnorr.signSync(msg, sec, rnd);
|
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
||||||
// expect(hex(sig)).toBe(expSig.toLowerCase());
|
} else {
|
||||||
// expect(hex(sigS)).toBe(expSig.toLowerCase());
|
const passed = schnorr.verify(expSig, msg, pub);
|
||||||
// expect(await secp.schnorr.verify(sigS, msg, pub)).toBe(true);
|
deepStrictEqual(passed, passes === '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', () => {
|
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', () => {
|
||||||
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as microStark from '../../../lib/starknet.js';
|
import * as microStark from '../../../lib/stark.js';
|
||||||
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
||||||
import * as bench from 'micro-bmark';
|
import * as bench from 'micro-bmark';
|
||||||
const { run, mark } = bench; // or bench.mark
|
const { run, mark } = bench; // or bench.mark
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
import './basic.test.js';
|
import './basic.test.js';
|
||||||
import './stark.test.js';
|
import './stark.test.js';
|
||||||
import './property.test.js';
|
import './property.test.js';
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// Implementation of Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
// Implementation of Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
||||||
|
|
||||||
// Differences from @noble/ed25519 1.7:
|
// Differences from @noble/ed25519 1.7:
|
||||||
// 1. EDDSA & ECDH have different field element lengths (for ed448/x448 only):
|
// 1. Different field element lengths in ed448:
|
||||||
// RFC8032 bitLength is 456 bits (57 bytes), RFC7748 bitLength is 448 (56 bytes)
|
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
|
||||||
// 2. Different addition formula (doubling is same)
|
// 2. Different addition formula (doubling is same)
|
||||||
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
||||||
// 4. Point decompression code is different too (unexpected), now using generalized formula
|
// 4. Point decompression code is different too (unexpected), now using generalized formula
|
||||||
@ -19,6 +19,8 @@ import {
|
|||||||
hashToPrivateScalar,
|
hashToPrivateScalar,
|
||||||
BasicCurve,
|
BasicCurve,
|
||||||
validateOpts as utilOpts,
|
validateOpts as utilOpts,
|
||||||
|
Hex,
|
||||||
|
PrivKey,
|
||||||
} from './utils.js'; // TODO: import * as u from './utils.js'?
|
} from './utils.js'; // TODO: import * as u from './utils.js'?
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
@ -48,11 +50,6 @@ export type CurveType = BasicCurve & {
|
|||||||
preHash?: CHash;
|
preHash?: CHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We accept hex strings besides Uint8Array for simplicity
|
|
||||||
type Hex = Uint8Array | string;
|
|
||||||
// Very few implementations accept numbers, we do it to ease learning curve
|
|
||||||
type PrivKey = Hex | bigint | number;
|
|
||||||
|
|
||||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = utilOpts(curve);
|
const opts = utilOpts(curve);
|
||||||
@ -260,7 +257,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
const { a, d } = CURVE;
|
const { a, d } = CURVE;
|
||||||
const { x: X1, y: Y1, z: Z1, t: T1 } = this;
|
const { x: X1, y: Y1, z: Z1, t: T1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2, t: T2 } = other;
|
const { x: X2, y: Y2, z: Z2, t: T2 } = other;
|
||||||
|
|
||||||
// Faster algo for adding 2 Extended Points when curve's a=-1.
|
// 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
|
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
|
||||||
// Cost: 8M + 8add + 2*2.
|
// Cost: 8M + 8add + 2*2.
|
||||||
@ -520,7 +516,6 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Little-endian SHA512 with modulo n
|
// Little-endian SHA512 with modulo n
|
||||||
function modlLE(hash: Uint8Array): bigint {
|
function modlLE(hash: Uint8Array): bigint {
|
||||||
return mod.mod(bytesToNumberLE(hash), CURVE_ORDER);
|
return mod.mod(bytesToNumberLE(hash), CURVE_ORDER);
|
||||||
|
15
src/utils.ts
15
src/utils.ts
@ -1,5 +1,10 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
export type Hex = string | Uint8Array;
|
|
||||||
|
// We accept hex strings besides Uint8Array for simplicity
|
||||||
|
export type Hex = Uint8Array | string;
|
||||||
|
// Very few implementations accept numbers, we do it to ease learning curve
|
||||||
|
export type PrivKey = Hex | bigint | number;
|
||||||
|
|
||||||
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
|
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
|
||||||
// But generator can be different (Fp2/Fp6 for bls?)
|
// But generator can be different (Fp2/Fp6 for bls?)
|
||||||
export type BasicCurve = {
|
export type BasicCurve = {
|
||||||
@ -124,6 +129,7 @@ export function nLength(n: bigint, nBitLength?: number) {
|
|||||||
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
||||||
* and convert them into private scalar, with the modulo bias being neglible.
|
* and convert them into private scalar, with the modulo bias being neglible.
|
||||||
* As per FIPS 186 B.4.1.
|
* As per FIPS 186 B.4.1.
|
||||||
|
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
||||||
* @param hash hash output from sha512, or a similar function
|
* @param hash hash output from sha512, or a similar function
|
||||||
* @returns valid private scalar
|
* @returns valid private scalar
|
||||||
*/
|
*/
|
||||||
@ -137,3 +143,10 @@ export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false
|
|||||||
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
||||||
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
||||||
|
// We don't care about timing attacks here
|
||||||
|
if (b1.length !== b2.length) return false;
|
||||||
|
for (let i = 0; i < b1.length; i++) if (b1[i] !== b2[i]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -21,6 +21,8 @@ import {
|
|||||||
hashToPrivateScalar,
|
hashToPrivateScalar,
|
||||||
BasicCurve,
|
BasicCurve,
|
||||||
validateOpts as utilOpts,
|
validateOpts as utilOpts,
|
||||||
|
Hex,
|
||||||
|
PrivKey,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { Group, GroupConstructor, wNAF } from './group.js';
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
@ -54,11 +56,6 @@ export type CurveType = BasicCurve & {
|
|||||||
endo?: EndomorphismOpts;
|
endo?: EndomorphismOpts;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We accept hex strings besides Uint8Array for simplicity
|
|
||||||
type Hex = Uint8Array | string;
|
|
||||||
// Very few implementations accept numbers, we do it to ease learning curve
|
|
||||||
type PrivKey = Hex | bigint | number;
|
|
||||||
|
|
||||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
const opts = utilOpts(curve);
|
const opts = utilOpts(curve);
|
||||||
@ -244,6 +241,13 @@ export type CurveFn = {
|
|||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint, b?: bigint) => bigint;
|
mod: (a: bigint, b?: bigint) => bigint;
|
||||||
invert: (number: bigint, modulo?: bigint) => bigint;
|
invert: (number: bigint, modulo?: bigint) => bigint;
|
||||||
|
_bigintToBytes: (num: bigint) => Uint8Array;
|
||||||
|
_bigintToString: (num: bigint) => string;
|
||||||
|
_normalizePrivateKey: (key: PrivKey) => bigint;
|
||||||
|
_normalizePublicKey: (publicKey: PubKey) => PointType;
|
||||||
|
_isWithinCurveOrder: (num: bigint) => boolean;
|
||||||
|
_isValidFieldElement: (num: bigint) => boolean;
|
||||||
|
_weierstrassEquation: (x: bigint) => bigint;
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
@ -649,7 +653,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const wnaf = wNAF(JacobianPoint, CURVE.endo ? CURVE.nBitLength / 2 : CURVE.nBitLength);
|
const wnaf = wNAF(JacobianPoint, CURVE.endo ? CURVE.nBitLength / 2 : CURVE.nBitLength);
|
||||||
|
|
||||||
// Stores precomputed values for points.
|
// Stores precomputed values for points.
|
||||||
const pointPrecomputes = new WeakMap<Point, JacobianPoint[]>();
|
const pointPrecomputes = new WeakMap<Point, JacobianPoint[]>();
|
||||||
|
|
||||||
@ -923,20 +926,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
_bigintToBytes: numToField,
|
_bigintToBytes: numToField,
|
||||||
|
_bigintToString: numToFieldStr,
|
||||||
_normalizePrivateKey: normalizePrivateKey,
|
_normalizePrivateKey: normalizePrivateKey,
|
||||||
|
_normalizePublicKey: normalizePublicKey,
|
||||||
|
_isWithinCurveOrder: isWithinCurveOrder,
|
||||||
|
_isValidFieldElement: isValidFieldElement,
|
||||||
|
_weierstrassEquation: weierstrassEquation,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can take (keyLength + 8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
||||||
* and convert them into private key, with the modulo bias being neglible.
|
|
||||||
* As per FIPS 186 B.4.1.
|
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
* @param hash hash output from sha512, or a similar function
|
|
||||||
* @returns valid private key
|
|
||||||
*/
|
*/
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)),
|
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)),
|
||||||
|
|
||||||
// Takes curve order + 64 bits from CSPRNG so that modulo bias is neglible,
|
/**
|
||||||
// matches FIPS 186 B.4.1.
|
* Produces cryptographically secure private key from random of size (nBitLength+64)
|
||||||
|
* as per FIPS 186 B.4.1 with modulo bias being neglible.
|
||||||
|
*/
|
||||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(fieldLen + 8)),
|
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(fieldLen + 8)),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user