Skip to content

Commit

Permalink
test: Add more util tests. Add affine tests
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Nov 21, 2024
1 parent 8617276 commit 2b08ed5
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 96 deletions.
87 changes: 51 additions & 36 deletions test/secp256k1.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()', () => {
Expand All @@ -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));
Expand Down Expand Up @@ -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()', () => {
Expand Down
69 changes: 69 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -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}`;
}
136 changes: 76 additions & 60 deletions test/utils.test.js
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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));
}
Expand All @@ -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();
}

0 comments on commit 2b08ed5

Please sign in to comment.