From 2b08ed5434d50e0bf6b31d6da36a6108bf4a0f1d Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 21 Nov 2024 08:01:11 +0000 Subject: [PATCH] test: Add more util tests. Add affine tests --- test/secp256k1.test.js | 87 +++++++++++++++----------- test/utils.js | 69 +++++++++++++++++++++ test/utils.test.js | 136 +++++++++++++++++++++++------------------ 3 files changed, 196 insertions(+), 96 deletions(-) create mode 100644 test/utils.js diff --git a/test/secp256k1.test.js b/test/secp256k1.test.js index 146ee78..3b96e7a 100644 --- a/test/secp256k1.test.js +++ b/test/secp256k1.test.js @@ -168,6 +168,13 @@ describe('secp256k1', () => { throws(() => Point.BASE.multiply(num)); } }); + + should('.fromAffine', () => { + const xy = { x: 0n, y: 0n }; + const p = Point.fromAffine(xy); + deepStrictEqual(p, Point.ZERO); + deepStrictEqual(p.toAffine(), xy); + }); }); // multiply() should equal multiplyUnsafe() @@ -199,42 +206,6 @@ describe('secp256k1', () => { }) ); }); - - should('.hasHighS(), .normalizeS()', () => { - const priv = 'c509ae2138ddca15f6b33062cd3bf76351c79f58c82ee2c2236d835bdea19d13'; - const msg = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; - - const hi = - 'a6bf36d52da4eef85a513a88d81996a47804a2390c9910c0bd35488effca36bf8bf1f9232ab0efe4a93704ae871aa953b34d1000cef59c9d33fcc696f935108d'; - const lo = - 'a6bf36d52da4eef85a513a88d81996a47804a2390c9910c0bd35488effca36bf740e06dcd54f101b56c8fb5178e556ab0761cce5e053039e8bd597f5d70130b4'; - const hi_ = new secp.Signature( - 75421779095773161492578598757717572512754773103551662129966816753283695785663n, - 63299015578620006752099543153250095157058828301739590985992016204296254460045n, - 0 - ); - const lo_ = new secp.Signature( - 75421779095773161492578598757717572512754773103551662129966816753283695785663n, - 52493073658696188671471441855437812695778735977335313396613146937221907034292n, - 0 - ); - - const pub = secp.getPublicKey(priv); - const sig = secp.sign(msg, priv, { lowS: false }); - deepStrictEqual(sig.hasHighS(), true); - deepStrictEqual(sig, hi_); - deepStrictEqual(sig.toCompactHex(), hi); - - const lowSig = sig.normalizeS(); - deepStrictEqual(lowSig.hasHighS(), false); - deepStrictEqual(lowSig, lo_); - deepStrictEqual(lowSig.toCompactHex(), lo); - - deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true); - deepStrictEqual(secp.verify(sig, msg, pub, { lowS: true }), false); - deepStrictEqual(secp.verify(lowSig, msg, pub, { lowS: true }), true); - deepStrictEqual(secp.verify(lowSig, msg, pub, { lowS: false }), true); - }); }); describe('sign()', () => { @@ -256,6 +227,14 @@ describe('secp256k1', () => { } }); + should('sign legacy options', () => { + const msg = '12'.repeat(32); + const priv = '34'.repeat(32); + throws(() => { secp.sign(msg, priv, { der: true }) }); + throws(() => { secp.sign(msg, priv, { canonical: true }) }); + throws(() => { secp.sign(msg, priv, { recovered: true }) }); + }); + should('not create invalid deterministic signatures with RFC 6979', () => { for (const vector of ecdsa.invalid.sign) { throws(() => secp.sign(vector.m, vector.d)); @@ -339,6 +318,42 @@ describe('secp256k1', () => { '2bdf40f42ac0e42ee12750d03bb12b75306dae58eb3c961c5a80d78efae93e595295b66e8eb28f1eb046bb129a976340312159ec0c20b97342667572e4a8379a' ); }); + + should('.hasHighS(), .normalizeS()', () => { + const priv = 'c509ae2138ddca15f6b33062cd3bf76351c79f58c82ee2c2236d835bdea19d13'; + const msg = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; + + const hi = + 'a6bf36d52da4eef85a513a88d81996a47804a2390c9910c0bd35488effca36bf8bf1f9232ab0efe4a93704ae871aa953b34d1000cef59c9d33fcc696f935108d'; + const lo = + 'a6bf36d52da4eef85a513a88d81996a47804a2390c9910c0bd35488effca36bf740e06dcd54f101b56c8fb5178e556ab0761cce5e053039e8bd597f5d70130b4'; + const hi_ = new secp.Signature( + 75421779095773161492578598757717572512754773103551662129966816753283695785663n, + 63299015578620006752099543153250095157058828301739590985992016204296254460045n, + 0 + ); + const lo_ = new secp.Signature( + 75421779095773161492578598757717572512754773103551662129966816753283695785663n, + 52493073658696188671471441855437812695778735977335313396613146937221907034292n, + 0 + ); + + const pub = secp.getPublicKey(priv); + const sig = secp.sign(msg, priv, { lowS: false }); + deepStrictEqual(sig.hasHighS(), true); + deepStrictEqual(sig, hi_); + deepStrictEqual(sig.toCompactHex(), hi); + + const lowSig = sig.normalizeS(); + deepStrictEqual(lowSig.hasHighS(), false); + deepStrictEqual(lowSig, lo_); + deepStrictEqual(lowSig.toCompactHex(), lo); + + deepStrictEqual(secp.verify(sig, msg, pub, { lowS: false }), true); + deepStrictEqual(secp.verify(sig, msg, pub, { lowS: true }), false); + deepStrictEqual(secp.verify(lowSig, msg, pub, { lowS: true }), true); + deepStrictEqual(secp.verify(lowSig, msg, pub, { lowS: false }), true); + }); }); describe('verify()', () => { diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..015a59e --- /dev/null +++ b/test/utils.js @@ -0,0 +1,69 @@ +// Everything except undefined, string, Uint8Array +const TYPE_TEST_BASE = [ + null, + [1, 2, 3], + { a: 1, b: 2, c: 3 }, + NaN, + Infinity, + -Infinity, + 0.1234, + 1.0000000000001, + 10e9999, + new Uint32Array([1, 2, 3]), + 100n, + new Set([1, 2, 3]), + new Map([['aa', 'bb']]), + new Uint8ClampedArray([1, 2, 3]), + new Int16Array([1, 2, 3]), + new Float32Array([1]), + new BigInt64Array([1n, 2n, 3n]), + new ArrayBuffer(100), + new DataView(new ArrayBuffer(100)), + { constructor: { name: 'Uint8Array' }, length: '1e30' }, + () => {}, + async () => {}, + class Test {}, + Symbol.for('a'), + new Proxy(new Uint8Array(), { + get(t, p, r) { + if (p === 'isProxy') return true; + return Reflect.get(t, p, r); + }, + }), +]; + +const TYPE_TEST_OPT = [ + '', + new Uint8Array(), + new (class Test {})(), + class Test {}, + () => {}, + 0, + 0.1234, + NaN, + null, +]; + +const TYPE_TEST_NOT_BOOL = [false, true]; +const TYPE_TEST_NOT_BYTES = ['', 'test', '1', new Uint8Array([]), new Uint8Array([1, 2, 3])]; +const TYPE_TEST_NOT_HEX = [ + '0xbe', + ' 1 2 3 4 5', + '010203040x', + 'abcdefgh', + '1 2 3 4 5 ', + 'bee', + new String('1234'), +]; +const TYPE_TEST_NOT_INT = [-0.0, 0, 1]; + +export const TYPE_TEST = { + bytes: TYPE_TEST_BASE.concat(TYPE_TEST_NOT_INT, TYPE_TEST_NOT_BOOL), + hex: TYPE_TEST_BASE.concat(TYPE_TEST_NOT_INT, TYPE_TEST_NOT_BOOL, TYPE_TEST_NOT_HEX), +}; + +export function repr(item) { + if (item && item.isProxy) return '[proxy]'; + if (typeof item === 'symbol') return item.toString(); + return `${item}`; +} \ No newline at end of file diff --git a/test/utils.test.js b/test/utils.test.js index 92ae6b5..aed238c 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -1,64 +1,9 @@ import { deepStrictEqual, throws } from 'assert'; +import * as fc from 'fast-check'; import { describe, should } from 'micro-should'; -import * as secp256k1 from '../index.js'; - -const { bytesToHex, hexToBytes } = secp256k1.etc; - -// Everything except undefined, string, Uint8Array -const TYPE_TEST_BASE = [ - null, - [1, 2, 3], - { a: 1, b: 2, c: 3 }, - NaN, - 0.1234, - 1.0000000000001, - 10e9999, - new Uint32Array([1, 2, 3]), - 100n, - new Set([1, 2, 3]), - new Map([['aa', 'bb']]), - new Uint8ClampedArray([1, 2, 3]), - new Int16Array([1, 2, 3]), - new Float32Array([1]), - new BigInt64Array([1n, 2n, 3n]), - new ArrayBuffer(100), - new DataView(new ArrayBuffer(100)), - { constructor: { name: 'Uint8Array' }, length: '1e30' }, - () => {}, - async () => {}, - class Test {}, - Symbol.for('a'), - new Proxy(new Uint8Array(), {}) -]; - -const TYPE_TEST_OPT = [ - '', - new Uint8Array(), - new (class Test {})(), - class Test {}, - () => {}, - 0, - 0.1234, - NaN, - null, -]; - -const TYPE_TEST_NOT_BOOL = [false, true]; -const TYPE_TEST_NOT_BYTES = ['', 'test', '1', new Uint8Array([]), new Uint8Array([1, 2, 3])]; -const TYPE_TEST_NOT_HEX = [ - ' 1 2 3 4 5', - '010203040x', - 'abcdefgh', - '1 2 3 4 5 ', - 'bee', - new String('1234'), -]; -const TYPE_TEST_NOT_INT = [-0.0, 0, 1]; - -const TYPE_TEST = { - bytes: TYPE_TEST_BASE.concat(TYPE_TEST_NOT_INT, TYPE_TEST_NOT_BOOL), - hex: TYPE_TEST_BASE.concat(TYPE_TEST_NOT_INT, TYPE_TEST_NOT_BOOL, TYPE_TEST_NOT_HEX), -}; +import * as items from '../index.js'; +import { TYPE_TEST } from './utils.js'; +const { bytesToHex, concatBytes, hexToBytes, mod, invert } = items.etc; describe('utils', () => { const staticHexVectors = [ @@ -69,6 +14,7 @@ describe('utils', () => { ]; should('hexToBytes', () => { for (let v of staticHexVectors) deepStrictEqual(hexToBytes(v.hex), v.bytes); + for (let v of staticHexVectors) deepStrictEqual(hexToBytes(v.hex.toUpperCase()), v.bytes); for (let v of TYPE_TEST.hex) { throws(() => hexToBytes(v)); } @@ -79,6 +25,76 @@ describe('utils', () => { throws(() => bytesToHex(v)); } }); + should('hexToBytes <=> bytesToHex roundtrip', () => + fc.assert( + fc.property(fc.hexaString({ minLength: 2, maxLength: 64 }), (hex) => { + if (hex.length % 2 !== 0) return; + deepStrictEqual(hex, bytesToHex(hexToBytes(hex))); + deepStrictEqual(hex, bytesToHex(hexToBytes(hex.toUpperCase()))); + deepStrictEqual(hexToBytes(hex), Uint8Array.from(Buffer.from(hex, 'hex'))); + }) + ) + ); + should('concatBytes', () => { + const a = 1; + const b = 2; + const c = 0xff; + const aa = Uint8Array.from([a]); + const bb = Uint8Array.from([b]); + const cc = Uint8Array.from([c]); + deepStrictEqual(concatBytes(), new Uint8Array()); + deepStrictEqual(concatBytes(aa, bb), Uint8Array.from([a, b])); + deepStrictEqual(concatBytes(aa, bb, cc), Uint8Array.from([a, b, c])); + for (let v of TYPE_TEST.bytes) + throws(() => { + concatBytes(v); + }); + }); + should('concatBytes random', () => + fc.assert( + fc.property(fc.uint8Array(), fc.uint8Array(), fc.uint8Array(), (a, b, c) => { + const expected = Uint8Array.from(Buffer.concat([a, b, c])); + deepStrictEqual(concatBytes(a.slice(), b.slice(), c.slice()), expected); + }) + ) + ); +}); + +describe('utils math', () => { + should('mod', () => { + deepStrictEqual(mod(11n, 10n), 1n); + deepStrictEqual(mod(-1n, 10n), 9n); + deepStrictEqual(mod(0n, 10n), 0n); + }); + should('invert', () => { + deepStrictEqual(invert(512n, 1023n), 2n); + deepStrictEqual( + invert(2n ** 255n, 2n ** 255n - 19n), + 21330121701610878104342023554231983025602365596302209165163239159352418617876n + ); + throws(() => { + invert(); + }); + throws(() => { + invert(1n); + }); // no default modulus + throws(() => { + invert(0n, 12n); + }); + throws(() => { + invert(1n, -12n); + }); + throws(() => { + invert(512n, 1023); + }); + throws(() => { + invert(512, 1023n); + }); + }); }); -should.run(); +// ESM is broken. +import url from 'node:url'; +if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { + should.run(); +}