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>;
|
ProjectivePoint: ProjConstructor<bigint>;
|
||||||
Signature: SignatureConstructor;
|
Signature: SignatureConstructor;
|
||||||
utils: {
|
utils: {
|
||||||
_normalizePrivateKey: (key: PrivKey) => bigint;
|
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
@ -858,7 +858,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_normalizePrivateKey: normalizePrivateKey,
|
normPrivateKeyToScalar: normalizePrivateKey,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
* 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 q = Point.BASE.multiply(k).toAffine(); // q = Gk
|
||||||
const r = modN(q.x); // r = q.x mod n
|
const r = modN(q.x); // r = q.x mod n
|
||||||
if (r === _0n) return;
|
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;
|
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 recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
|
||||||
let normS = s;
|
let normS = s;
|
||||||
|
@ -6,6 +6,7 @@ import './nist.test.js';
|
|||||||
import './ed448.test.js';
|
import './ed448.test.js';
|
||||||
import './ed25519.test.js';
|
import './ed25519.test.js';
|
||||||
import './secp256k1.test.js';
|
import './secp256k1.test.js';
|
||||||
|
import './secp256k1-schnorr.test.js';
|
||||||
import './stark/index.test.js';
|
import './stark/index.test.js';
|
||||||
import './jubjub.test.js';
|
import './jubjub.test.js';
|
||||||
import './bls12-381.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 * 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 { 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 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' };
|
||||||
import { default as privates } from './vectors/privates.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 points } from './vectors/points.json' assert { type: 'json' };
|
||||||
import { default as wp } from './vectors/wychenproof.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 Point = secp.ProjectivePoint;
|
||||||
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
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);
|
const FC_BIGINT = fc.bigInt(1n + 1n, secp.CURVE.n - 1n);
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@ -193,7 +192,7 @@ describe('secp256k1', () => {
|
|||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
fc.property(FC_BIGINT, FC_BIGINT, (r, s) => {
|
||||||
const sig = new secp.Signature(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) {
|
for (const [msg, exp] of CASES) {
|
||||||
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
|
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||||
deepStrictEqual(res.toDERHex(), exp);
|
deepStrictEqual(sigToDER(res), exp);
|
||||||
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
const rs = sigFromDER(sigToDER(res)).toCompactHex();
|
||||||
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
deepStrictEqual(sigToDER(secp.Signature.fromCompact(rs)), exp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('handle {extraData} option', () => {
|
should('handle {extraData} option', () => {
|
||||||
@ -342,7 +341,7 @@ describe('secp256k1', () => {
|
|||||||
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
|
||||||
const pub = new Point(x, y, 1n).toRawBytes();
|
const pub = new Point(x, y, 1n).toRawBytes();
|
||||||
const sig = new secp.Signature(r, s);
|
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', () => {
|
should('not verify invalid deterministic signatures with RFC 6979', () => {
|
||||||
for (const vector of ecdsa.invalid.verify) {
|
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()', () => {
|
describe('recoverPublicKey()', () => {
|
||||||
should('recover public key from recovery bit', () => {
|
should('recover public key from recovery bit', () => {
|
||||||
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||||
@ -404,7 +380,7 @@ describe('secp256k1', () => {
|
|||||||
should('handle RFC 6979 vectors', () => {
|
should('handle RFC 6979 vectors', () => {
|
||||||
for (const vector of ecdsa.valid) {
|
for (const vector of ecdsa.valid) {
|
||||||
let usig = secp.sign(vector.m, vector.d);
|
let usig = secp.sign(vector.m, vector.d);
|
||||||
let sig = usig.toDERHex();
|
let sig = sigToDER(usig);
|
||||||
const vpub = secp.getPublicKey(vector.d);
|
const vpub = secp.getPublicKey(vector.d);
|
||||||
const recovered = usig.recoverPublicKey(vector.m);
|
const recovered = usig.recoverPublicKey(vector.m);
|
||||||
deepStrictEqual(recovered.toHex(), hex(vpub));
|
deepStrictEqual(recovered.toHex(), hex(vpub));
|
||||||
@ -459,24 +435,25 @@ describe('secp256k1', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('tweak utilities (legacy)', () => {
|
describe('tweak utilities (legacy)', () => {
|
||||||
const Fn = Fp(secp.CURVE.n);
|
const normal = secp.utils.normPrivateKeyToScalar;
|
||||||
const normal = secp.utils._normalizePrivateKey;
|
|
||||||
const tweakUtils = {
|
const tweakUtils = {
|
||||||
privateAdd: (privateKey, tweak) => {
|
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) => {
|
privateNegate: (privateKey) => {
|
||||||
return numberToBytesBE(Fn.neg(normal(privateKey)), 32);
|
return numberToBytesBE(mod(-normal(privateKey), secp.CURVE.n), 32);
|
||||||
},
|
},
|
||||||
|
|
||||||
pointAddScalar: (p, tweak, isCompressed) => {
|
pointAddScalar: (p, tweak, isCompressed) => {
|
||||||
// Will throw if tweaked point is at infinity
|
const tweaked = Point.fromHex(p).add(Point.fromPrivateKey(tweak));
|
||||||
return Point.fromHex(p).add(Point.fromPrivateKey(tweak)).toRawBytes(isCompressed);
|
if (tweaked.equals(Point.ZERO)) throw new Error('Tweaked point at infinity');
|
||||||
|
return tweaked.toRawBytes(isCompressed);
|
||||||
},
|
},
|
||||||
|
|
||||||
pointMultiply: (p, tweak, 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);
|
return Point.fromHex(p).multiply(t).toRawBytes(isCompressed);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -484,20 +461,20 @@ describe('secp256k1', () => {
|
|||||||
should('privateAdd()', () => {
|
should('privateAdd()', () => {
|
||||||
for (const vector of privates.valid.add) {
|
for (const vector of privates.valid.add) {
|
||||||
const { a, b, expected } = vector;
|
const { a, b, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected);
|
deepStrictEqual(hex(tweakUtils.privateAdd(a, b)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('privateNegate()', () => {
|
should('privateNegate()', () => {
|
||||||
for (const vector of privates.valid.negate) {
|
for (const vector of privates.valid.negate) {
|
||||||
const { a, expected } = vector;
|
const { a, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected);
|
deepStrictEqual(hex(tweakUtils.privateNegate(a)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('pointAddScalar()', () => {
|
should('pointAddScalar()', () => {
|
||||||
for (const vector of points.valid.pointAddScalar) {
|
for (const vector of points.valid.pointAddScalar) {
|
||||||
const { description, P, d, expected } = vector;
|
const { description, P, d, expected } = vector;
|
||||||
const compressed = !!expected && expected.length === 66; // compressed === 33 bytes
|
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', () => {
|
should('pointAddScalar() invalid', () => {
|
||||||
@ -509,7 +486,7 @@ describe('secp256k1', () => {
|
|||||||
should('pointMultiply()', () => {
|
should('pointMultiply()', () => {
|
||||||
for (const vector of points.valid.pointMultiply) {
|
for (const vector of points.valid.pointMultiply) {
|
||||||
const { P, d, expected } = vector;
|
const { P, d, expected } = vector;
|
||||||
deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected);
|
deepStrictEqual(hex(tweakUtils.pointMultiply(P, d, true)), expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('pointMultiply() invalid', () => {
|
should('pointMultiply() invalid', () => {
|
||||||
@ -525,10 +502,12 @@ describe('secp256k1', () => {
|
|||||||
// const pubKey = Point.fromHex().toRawBytes();
|
// const pubKey = Point.fromHex().toRawBytes();
|
||||||
const pubKey = group.key.uncompressed;
|
const pubKey = group.key.uncompressed;
|
||||||
for (let test of group.tests) {
|
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') {
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
const verified = secp.verify(test.sig, m, pubKey);
|
const verified = secp.verify(normVerifySig(test.sig), m, pubKey);
|
||||||
if (secp.Signature.fromDER(test.sig).hasHighS()) {
|
if (sigFromDER(test.sig).hasHighS()) {
|
||||||
deepStrictEqual(verified, false);
|
deepStrictEqual(verified, false);
|
||||||
} else {
|
} else {
|
||||||
deepStrictEqual(verified, true);
|
deepStrictEqual(verified, true);
|
||||||
|
Loading…
Reference in New Issue
Block a user