forked from tornado-packages/noble-curves
weierstrass: rename normPrivKey util. tests: prepare for unification w old noble pkg
This commit is contained in:
parent
debb9d9709
commit
1b6071cabd
@ -636,7 +636,7 @@ export type CurveFn = {
|
||||
ProjectivePoint: ProjConstructor<bigint>;
|
||||
Signature: SignatureConstructor;
|
||||
utils: {
|
||||
_normalizePrivateKey: (key: PrivKey) => bigint;
|
||||
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||
randomPrivateKey: () => Uint8Array;
|
||||
@ -858,7 +858,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
_normalizePrivateKey: normalizePrivateKey,
|
||||
normPrivateKeyToScalar: normalizePrivateKey,
|
||||
|
||||
/**
|
||||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
||||
@ -993,7 +993,16 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
||||
const q = Point.BASE.multiply(k).toAffine(); // q = Gk
|
||||
const r = modN(q.x); // r = q.x mod n
|
||||
if (r === _0n) return;
|
||||
const s = modN(ik * modN(m + modN(d * r))); // s = k^-1(m + rd) mod n
|
||||
// X blinding according to https://tches.iacr.org/index.php/TCHES/article/view/7337/6509
|
||||
// b * m + b * r * d ∈ [0,q−1] exposed via side-channel, but d (private scalar) is not.
|
||||
// NOTE: there is still probable some leak in multiplication, since it is not constant-time
|
||||
const b = ut.bytesToNumberBE(utils.randomPrivateKey()); // random scalar, b ∈ [1,q−1]
|
||||
const bi = invN(b); // b^-1
|
||||
const bdr = modN(b * d * r); // b * d * r
|
||||
const bm = modN(b * m); // b * m
|
||||
const mrx = modN(bi * modN(bdr + bm)); // b^-1(bm + bdr) -> m + rd
|
||||
|
||||
const s = modN(ik * mrx); // s = k^-1(m + rd) mod n
|
||||
if (s === _0n) return;
|
||||
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
|
||||
let normS = s;
|
||||
|
@ -6,6 +6,7 @@ import './nist.test.js';
|
||||
import './ed448.test.js';
|
||||
import './ed25519.test.js';
|
||||
import './secp256k1.test.js';
|
||||
import './secp256k1-schnorr.test.js';
|
||||
import './stark/index.test.js';
|
||||
import './jubjub.test.js';
|
||||
import './bls12-381.test.js';
|
||||
|
34
test/secp256k1-schnorr.test.js
Normal file
34
test/secp256k1-schnorr.test.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { readFileSync } from 'fs';
|
||||
import { should, describe } from 'micro-should';
|
||||
import { bytesToHex as hex } from '@noble/hashes/utils';
|
||||
import { schnorr } from '../lib/esm/secp256k1.js';
|
||||
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
||||
|
||||
describe('schnorr.sign()', () => {
|
||||
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||
const vectors = schCsv
|
||||
.split('\n')
|
||||
.map((line) => line.split(','))
|
||||
.slice(1, -1);
|
||||
for (let vec of vectors) {
|
||||
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||
should(`${comment || 'vector ' + index}`, () => {
|
||||
if (sec) {
|
||||
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
||||
const sig = schnorr.sign(msg, sec, rnd);
|
||||
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
||||
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
||||
} else {
|
||||
const passed = schnorr.verify(expSig, msg, pub);
|
||||
deepStrictEqual(passed, passes === 'TRUE');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
14
test/secp256k1.helpers.js
Normal file
14
test/secp256k1.helpers.js
Normal file
@ -0,0 +1,14 @@
|
||||
// @ts-ignore
|
||||
export { secp256k1 as secp } from '../lib/esm/secp256k1.js';
|
||||
import { secp256k1 as _secp } from '../lib/esm/secp256k1.js';
|
||||
export { bytesToNumberBE, numberToBytesBE } from '../lib/esm/abstract/utils.js';
|
||||
export { mod } from '../lib/esm/abstract/modular.js';
|
||||
export const sigFromDER = (der) => {
|
||||
return _secp.Signature.fromDER(der);
|
||||
};
|
||||
export const sigToDER = (sig) => sig.toDERHex();
|
||||
export const selectHash = (secp) => secp.CURVE.hash;
|
||||
export const normVerifySig = (s) => _secp.Signature.fromDER(s);
|
||||
// export const bytesToNumberBE = secp256k1.utils.bytesToNumberBE;
|
||||
// export const numberToBytesBE = secp256k1.utils.numberToBytesBE;
|
||||
// export const mod = mod_;
|
@ -1,22 +1,21 @@
|
||||
import { hexToBytes, bytesToHex as hex } from '@noble/hashes/utils';
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import * as fc from 'fast-check';
|
||||
import { secp256k1, schnorr } from '../lib/esm/secp256k1.js';
|
||||
import { Fp } from '../lib/esm/abstract/modular.js';
|
||||
import { bytesToNumberBE, ensureBytes, numberToBytesBE } from '../lib/esm/abstract/utils.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { should, describe } from 'micro-should';
|
||||
// prettier-ignore
|
||||
import {
|
||||
secp, sigFromDER, sigToDER, selectHash, normVerifySig, mod, bytesToNumberBE, numberToBytesBE
|
||||
} from './secp256k1.helpers.js';
|
||||
|
||||
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
||||
import { default as 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, describe } from 'micro-should';
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||
|
||||
const hex = bytesToHex;
|
||||
const secp = secp256k1;
|
||||
const Point = secp.ProjectivePoint;
|
||||
const 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
|
||||
@ -193,7 +192,7 @@ describe('secp256k1', () => {
|
||||
fc.assert(
|
||||
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||
const sig = new secp.Signature(r, s);
|
||||
deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig);
|
||||
deepStrictEqual(sigFromDER(sigToDER(sig)), sig);
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -241,9 +240,9 @@ describe('secp256k1', () => {
|
||||
);
|
||||
for (const [msg, exp] of CASES) {
|
||||
const res = 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);
|
||||
deepStrictEqual(sigToDER(res), exp);
|
||||
const rs = sigFromDER(sigToDER(res)).toCompactHex();
|
||||
deepStrictEqual(sigToDER(secp.Signature.fromCompact(rs)), exp);
|
||||
}
|
||||
});
|
||||
should('handle {extraData} option', () => {
|
||||
@ -342,7 +341,7 @@ describe('secp256k1', () => {
|
||||
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
||||
const pub = new Point(x, y, 1n).toRawBytes();
|
||||
const sig = new secp.Signature(r, s);
|
||||
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
|
||||
deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true);
|
||||
});
|
||||
should('not verify invalid deterministic signatures with RFC 6979', () => {
|
||||
for (const vector of ecdsa.invalid.verify) {
|
||||
@ -351,29 +350,6 @@ describe('secp256k1', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('schnorr.sign()', () => {
|
||||
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||
const vectors = schCsv
|
||||
.split('\n')
|
||||
.map((line) => line.split(','))
|
||||
.slice(1, -1);
|
||||
for (let vec of vectors) {
|
||||
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||
should(`${comment || 'vector ' + index}`, () => {
|
||||
if (sec) {
|
||||
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
||||
const sig = schnorr.sign(msg, sec, rnd);
|
||||
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
||||
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
||||
} else {
|
||||
const passed = schnorr.verify(expSig, msg, pub);
|
||||
deepStrictEqual(passed, passes === 'TRUE');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('recoverPublicKey()', () => {
|
||||
should('recover public key from recovery bit', () => {
|
||||
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||
@ -404,7 +380,7 @@ describe('secp256k1', () => {
|
||||
should('handle RFC 6979 vectors', () => {
|
||||
for (const vector of ecdsa.valid) {
|
||||
let usig = secp.sign(vector.m, vector.d);
|
||||
let sig = usig.toDERHex();
|
||||
let sig = sigToDER(usig);
|
||||
const vpub = secp.getPublicKey(vector.d);
|
||||
const recovered = usig.recoverPublicKey(vector.m);
|
||||
deepStrictEqual(recovered.toHex(), hex(vpub));
|
||||
@ -459,24 +435,25 @@ describe('secp256k1', () => {
|
||||
});
|
||||
|
||||
describe('tweak utilities (legacy)', () => {
|
||||
const Fn = Fp(secp.CURVE.n);
|
||||
const normal = secp.utils._normalizePrivateKey;
|
||||
const normal = secp.utils.normPrivateKeyToScalar;
|
||||
const tweakUtils = {
|
||||
privateAdd: (privateKey, tweak) => {
|
||||
return numberToBytesBE(Fn.add(normal(privateKey), normal(tweak)), 32);
|
||||
return numberToBytesBE(mod(normal(privateKey) + normal(tweak), secp.CURVE.n), 32);
|
||||
},
|
||||
|
||||
privateNegate: (privateKey) => {
|
||||
return numberToBytesBE(Fn.neg(normal(privateKey)), 32);
|
||||
return numberToBytesBE(mod(-normal(privateKey), secp.CURVE.n), 32);
|
||||
},
|
||||
|
||||
pointAddScalar: (p, tweak, isCompressed) => {
|
||||
// Will throw if tweaked point is at infinity
|
||||
return Point.fromHex(p).add(Point.fromPrivateKey(tweak)).toRawBytes(isCompressed);
|
||||
const tweaked = Point.fromHex(p).add(Point.fromPrivateKey(tweak));
|
||||
if (tweaked.equals(Point.ZERO)) throw new Error('Tweaked point at infinity');
|
||||
return tweaked.toRawBytes(isCompressed);
|
||||
},
|
||||
|
||||
pointMultiply: (p, tweak, isCompressed) => {
|
||||
const t = bytesToNumberBE(ensureBytes(tweak));
|
||||
if (typeof tweak === 'string') tweak = hexToBytes(tweak);
|
||||
const t = bytesToNumberBE(tweak);
|
||||
return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
|
||||
},
|
||||
};
|
||||
@ -484,20 +461,20 @@ describe('secp256k1', () => {
|
||||
should('privateAdd()', () => {
|
||||
for (const vector of privates.valid.add) {
|
||||
const { a, b, expected } = vector;
|
||||
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
|
||||
deepStrictEqual(hex(tweakUtils.privateAdd(a, b)), expected);
|
||||
}
|
||||
});
|
||||
should('privateNegate()', () => {
|
||||
for (const vector of privates.valid.negate) {
|
||||
const { a, expected } = vector;
|
||||
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
|
||||
deepStrictEqual(hex(tweakUtils.privateNegate(a)), expected);
|
||||
}
|
||||
});
|
||||
should('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);
|
||||
deepStrictEqual(hex(tweakUtils.pointAddScalar(P, d, compressed)), expected);
|
||||
}
|
||||
});
|
||||
should('pointAddScalar() invalid', () => {
|
||||
@ -509,7 +486,7 @@ describe('secp256k1', () => {
|
||||
should('pointMultiply()', () => {
|
||||
for (const vector of points.valid.pointMultiply) {
|
||||
const { P, d, expected } = vector;
|
||||
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
|
||||
deepStrictEqual(hex(tweakUtils.pointMultiply(P, d, true)), expected);
|
||||
}
|
||||
});
|
||||
should('pointMultiply() invalid', () => {
|
||||
@ -525,10 +502,12 @@ describe('secp256k1', () => {
|
||||
// const pubKey = Point.fromHex().toRawBytes();
|
||||
const pubKey = group.key.uncompressed;
|
||||
for (let test of group.tests) {
|
||||
const m = secp.CURVE.hash(hexToBytes(test.msg));
|
||||
const h = selectHash(secp);
|
||||
|
||||
const m = h(hexToBytes(test.msg));
|
||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||
const verified = secp.verify(test.sig, m, pubKey);
|
||||
if (secp.Signature.fromDER(test.sig).hasHighS()) {
|
||||
const verified = secp.verify(normVerifySig(test.sig), m, pubKey);
|
||||
if (sigFromDER(test.sig).hasHighS()) {
|
||||
deepStrictEqual(verified, false);
|
||||
} else {
|
||||
deepStrictEqual(verified, true);
|
||||
|
Loading…
Reference in New Issue
Block a user