noble-curves/test/basic.test.js

752 lines
28 KiB
JavaScript
Raw Normal View History

2022-12-03 13:08:49 +03:00
import { deepStrictEqual, throws } from 'assert';
2023-01-13 18:00:13 +03:00
import { should, describe } from 'micro-should';
import * as fc from 'fast-check';
2023-03-03 04:09:50 +03:00
import * as mod from '../abstract/modular.js';
import { bytesToHex as toHex } from '../abstract/utils.js';
// Generic tests for all curves in package
import { secp192r1, secp224r1 } from './_more-curves.helpers.js';
2023-03-03 04:09:50 +03:00
import { secp256r1 } from '../p256.js';
import { secp384r1 } from '../p384.js';
import { secp521r1 } from '../p521.js';
import { secp256k1 } from '../secp256k1.js';
import { ed25519, ed25519ctx, ed25519ph, x25519 } from '../ed25519.js';
import { ed448, ed448ph } from '../ed448.js';
import { _starkCurve as starkCurve } from '../stark.js';
import { pallas, vesta } from '../pasta.js';
import { bn254 } from '../bn.js';
import { jubjub } from '../jubjub.js';
import { bls12_381 } from '../bls12-381.js';
2022-12-03 13:08:49 +03:00
2022-12-31 09:49:09 +03:00
// Fields tests
const FIELDS = {
secp192r1: { Fp: [secp192r1.CURVE.Fp] },
secp224r1: { Fp: [secp224r1.CURVE.Fp] },
secp256r1: { Fp: [secp256r1.CURVE.Fp] },
secp521r1: { Fp: [secp521r1.CURVE.Fp] },
secp256k1: { Fp: [secp256k1.CURVE.Fp] },
stark: { Fp: [starkCurve.CURVE.Fp] },
jubjub: { Fp: [jubjub.CURVE.Fp] },
ed25519: { Fp: [ed25519.CURVE.Fp] },
ed448: { Fp: [ed448.CURVE.Fp] },
bn254: { Fp: [bn254.CURVE.Fp] },
pallas: { Fp: [pallas.CURVE.Fp] },
vesta: { Fp: [vesta.CURVE.Fp] },
bls12: {
Fp: [bls12_381.CURVE.Fp],
Fp2: [
bls12_381.CURVE.Fp2,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
minLength: 2,
maxLength: 2,
}),
(Fp2, num) => Fp2.fromBigTuple([num[0], num[1]]),
],
// Fp6: [bls12_381.CURVE.Fp6],
Fp12: [
bls12_381.CURVE.Fp12,
fc.array(fc.bigInt(1n, bls12_381.CURVE.Fp.ORDER - 1n), {
minLength: 12,
maxLength: 12,
}),
(Fp12, num) => Fp12.fromBigTwelve(num),
],
},
};
for (const c in FIELDS) {
const curve = FIELDS[c];
for (const f in curve) {
const Fp = curve[f][0];
const name = `${c}/${f}:`;
const FC_BIGINT = curve[f][1] ? curve[f][1] : fc.bigInt(1n, Fp.ORDER - 1n);
const create = curve[f][2] ? curve[f][2].bind(null, Fp) : (num) => Fp.create(num);
2023-01-13 18:00:13 +03:00
describe(name, () => {
should('equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
const b = create(num);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.eql(a, b), true);
deepStrictEqual(Fp.eql(b, a), true);
2023-01-13 18:00:13 +03:00
})
);
});
should('non-equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.eql(a, b), num1 === num2);
deepStrictEqual(Fp.eql(b, a), num1 === num2);
2023-01-13 18:00:13 +03:00
})
);
});
should('add/subtract/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.add(a, b), Fp.add(b, a));
})
);
});
should('add/subtract/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.add(a, Fp.add(b, c)), Fp.add(Fp.add(a, b), c));
})
);
});
should('add/subtract/x+0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.add(a, Fp.ZERO), a);
})
);
});
should('add/subtract/x-0=x', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.sub(a, Fp.ZERO), a);
deepStrictEqual(Fp.sub(a, a), Fp.ZERO);
})
);
});
should('add/subtract/negate equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num1) => {
const a = create(num1);
const b = create(num1);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.neg(a));
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.neg(b)));
2023-01-13 18:00:13 +03:00
deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n))));
})
);
});
should('add/subtract/negate', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.neg(a), Fp.sub(Fp.ZERO, a));
deepStrictEqual(Fp.neg(a), Fp.mul(a, Fp.create(-1n)));
2023-01-13 18:00:13 +03:00
})
);
});
should('negate(0)', () => {
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.neg(Fp.ZERO), Fp.ZERO);
2023-01-13 18:00:13 +03:00
});
2022-12-31 09:49:09 +03:00
2023-01-13 18:00:13 +03:00
should('multiply/commutativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
deepStrictEqual(Fp.mul(a, b), Fp.mul(b, a));
})
);
});
should('multiply/associativity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.mul(b, c)), Fp.mul(Fp.mul(a, b), c));
})
);
});
should('multiply/distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.mul(a, Fp.add(b, c)), Fp.add(Fp.mul(b, a), Fp.mul(c, a)));
})
);
});
should('multiply/add equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.mul(a, 0n), Fp.ZERO);
deepStrictEqual(Fp.mul(a, Fp.ZERO), Fp.ZERO);
deepStrictEqual(Fp.mul(a, 1n), a);
deepStrictEqual(Fp.mul(a, Fp.ONE), a);
deepStrictEqual(Fp.mul(a, 2n), Fp.add(a, a));
deepStrictEqual(Fp.mul(a, 3n), Fp.add(Fp.add(a, a), a));
deepStrictEqual(Fp.mul(a, 4n), Fp.add(Fp.add(Fp.add(a, a), a), a));
})
);
});
should('multiply/square equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.sqr(a), Fp.mul(a, a));
2023-01-13 18:00:13 +03:00
})
);
});
should('multiply/pow equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.pow(a, 0n), Fp.ONE);
deepStrictEqual(Fp.pow(a, 1n), a);
deepStrictEqual(Fp.pow(a, 2n), Fp.mul(a, a));
deepStrictEqual(Fp.pow(a, 3n), Fp.mul(Fp.mul(a, a), a));
})
);
});
2023-01-13 03:20:48 +03:00
2023-01-13 18:00:13 +03:00
should('square(0)', () => {
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.sqr(Fp.ZERO), Fp.ZERO);
2023-01-13 18:00:13 +03:00
deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO);
});
2023-01-13 03:20:48 +03:00
2023-01-13 18:00:13 +03:00
should('square(1)', () => {
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.sqr(Fp.ONE), Fp.ONE);
2023-01-13 18:00:13 +03:00
deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE);
});
2022-12-31 09:49:09 +03:00
2023-01-13 18:00:13 +03:00
should('square(-1)', () => {
2023-01-26 05:06:28 +03:00
const minus1 = Fp.neg(Fp.ONE);
deepStrictEqual(Fp.sqr(minus1), Fp.ONE);
2023-01-13 18:00:13 +03:00
deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE);
});
const isSquare = mod.FpIsSquare(Fp);
// Not implemented
if (Fp !== bls12_381.CURVE.Fp12) {
should('multiply/sqrt', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
let root;
try {
root = Fp.sqrt(a);
} catch (e) {
deepStrictEqual(isSquare(a), false);
return;
}
deepStrictEqual(isSquare(a), true);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.eql(Fp.sqr(root), a), true, 'sqrt(a)^2 == a');
deepStrictEqual(Fp.eql(Fp.sqr(Fp.neg(root)), a), true, '(-sqrt(a))^2 == a');
2023-02-09 15:29:19 +03:00
// Returns odd/even element
deepStrictEqual(Fp.isOdd(mod.FpSqrtOdd(Fp, a)), true);
deepStrictEqual(Fp.isOdd(mod.FpSqrtEven(Fp, a)), false);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtOdd(Fp, a)), a), true);
deepStrictEqual(Fp.eql(Fp.sqr(mod.FpSqrtEven(Fp, a)), a), true);
2023-01-13 18:00:13 +03:00
})
);
});
should('sqrt(0)', () => {
deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO);
const sqrt1 = Fp.sqrt(Fp.ONE);
deepStrictEqual(
2023-01-26 05:06:28 +03:00
Fp.eql(sqrt1, Fp.ONE) || Fp.eql(sqrt1, Fp.neg(Fp.ONE)),
2023-01-13 18:00:13 +03:00
true,
'sqrt(1) = 1 or -1'
);
});
}
2023-01-13 03:20:48 +03:00
2023-01-13 18:00:13 +03:00
should('div/division by one equality', () => {
2023-01-13 03:20:48 +03:00
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
2023-01-26 05:06:28 +03:00
if (Fp.eql(a, Fp.ZERO)) return; // No division by zero
2023-01-13 18:00:13 +03:00
deepStrictEqual(Fp.div(a, Fp.ONE), a);
deepStrictEqual(Fp.div(a, a), Fp.ONE);
2023-02-09 15:29:19 +03:00
// FpDiv tests
deepStrictEqual(mod.FpDiv(Fp, a, Fp.ONE), a);
deepStrictEqual(mod.FpDiv(Fp, a, a), Fp.ONE);
2023-01-13 03:20:48 +03:00
})
);
});
2023-01-13 18:00:13 +03:00
should('zero division equality', () => {
fc.assert(
fc.property(FC_BIGINT, (num) => {
const a = create(num);
deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO);
2023-02-09 15:29:19 +03:00
deepStrictEqual(mod.FpDiv(Fp, Fp.ZERO, a), Fp.ZERO);
2023-01-13 18:00:13 +03:00
})
);
});
should('div/division distributivity', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, FC_BIGINT, (num1, num2, num3) => {
const a = create(num1);
const b = create(num2);
const c = create(num3);
deepStrictEqual(Fp.div(Fp.add(a, b), c), Fp.add(Fp.div(a, c), Fp.div(b, c)));
2023-02-09 15:29:19 +03:00
deepStrictEqual(
mod.FpDiv(Fp, Fp.add(a, b), c),
Fp.add(mod.FpDiv(Fp, a, c), mod.FpDiv(Fp, b, c))
);
2023-01-13 18:00:13 +03:00
})
);
});
should('div/division and multiplication equality', () => {
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => {
const a = create(num1);
const b = create(num2);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.inv(b)));
2023-01-13 18:00:13 +03:00
})
2023-01-13 03:20:48 +03:00
);
});
2022-12-31 09:49:09 +03:00
});
}
}
// Group tests
// prettier-ignore
const CURVES = {
2022-12-14 17:21:07 +03:00
secp192r1, secp224r1, secp256r1, secp384r1, secp521r1,
secp256k1,
ed25519, ed25519ctx, ed25519ph,
ed448, ed448ph,
starkCurve,
pallas, vesta,
bn254,
jubjub,
};
const NUM_RUNS = 5;
2022-12-31 09:49:09 +03:00
const getXY = (p) => ({ x: p.x, y: p.y });
function equal(a, b, comment) {
2023-01-24 07:37:53 +03:00
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
if (a.toAffine && b.toAffine) {
2023-01-24 07:37:53 +03:00
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`);
} else if (!a.toAffine && !b.toAffine) {
// Already affine
2023-01-24 07:37:53 +03:00
deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`);
} else throw new Error('Different point types');
}
for (const name in CURVES) {
const C = CURVES[name];
const CURVE_ORDER = C.CURVE.n;
const FC_BIGINT = fc.bigInt(1n + 1n, CURVE_ORDER - 1n);
2022-12-03 13:08:49 +03:00
// Check that curve doesn't accept points from other curves
const O = name === 'secp256k1' ? secp256r1 : secp256k1;
2022-12-14 17:21:07 +03:00
const POINTS = {};
const OTHER_POINTS = {};
for (const name of ['Point', 'ProjectivePoint', 'ExtendedPoint', 'ProjectivePoint']) {
2022-12-14 17:21:07 +03:00
POINTS[name] = C[name];
OTHER_POINTS[name] = O[name];
}
for (const pointName in POINTS) {
const p = POINTS[pointName];
const o = OTHER_POINTS[pointName];
if (!p) continue;
2022-12-03 13:08:49 +03:00
const G = [p.ZERO, p.BASE];
2023-01-24 06:02:45 +03:00
for (let i = 2n; i < 10n; i++) G.push(G[1].multiply(i));
2023-01-13 18:00:13 +03:00
const title = `${name}/${pointName}`;
describe(title, () => {
describe('basic group laws', () => {
// Here we check basic group laws, to verify that points works as group
2023-01-26 05:06:28 +03:00
should('zero', () => {
2023-01-13 18:00:13 +03:00
equal(G[0].double(), G[0], '(0*G).double() = 0');
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
equal(G[0].negate(), G[0], '-0 = 0');
for (let i = 0; i < G.length; i++) {
const p = G[i];
2023-01-24 07:37:53 +03:00
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`);
equal(G[0].multiply(BigInt(i + 1)), G[0], `${i + 1}*0 = 0`);
2023-01-13 18:00:13 +03:00
}
});
2023-01-26 05:06:28 +03:00
should('one', () => {
2023-01-13 18:00:13 +03:00
equal(G[1].double(), G[2], '(1*G).double() = 2*G');
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
});
2023-01-26 05:06:28 +03:00
should('sanity tests', () => {
2023-01-13 18:00:13 +03:00
equal(G[2].double(), G[4], '(2*G).double() = 4*G');
equal(G[2].add(G[2]), G[4], '2*G + 2*G = 4*G');
equal(G[7].add(G[3].negate()), G[4], '7*G - 3*G = 4*G');
});
2023-01-26 05:06:28 +03:00
should('add commutativity', () => {
2023-01-13 18:00:13 +03:00
equal(G[4].add(G[3]), G[3].add(G[4]), '4*G + 3*G = 3*G + 4*G');
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), '4*G + 3*G = 3*G + 2*G + 2*G');
});
2023-01-26 05:06:28 +03:00
should('double', () => {
2023-01-13 18:00:13 +03:00
equal(G[3].double(), G[6], '(3*G).double() = 6*G');
});
2023-01-26 05:06:28 +03:00
should('multiply', () => {
2023-01-24 06:02:45 +03:00
equal(G[2].multiply(3n), G[6], '(2*G).multiply(3) = 6*G');
2023-01-13 18:00:13 +03:00
});
2023-01-26 05:06:28 +03:00
should('add same-point', () => {
2023-01-13 18:00:13 +03:00
equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G');
});
2023-01-26 05:06:28 +03:00
should('add same-point negative', () => {
2023-01-13 18:00:13 +03:00
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
});
2023-01-26 05:06:28 +03:00
should('mul by curve order', () => {
2023-01-13 18:00:13 +03:00
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
const half = CURVE_ORDER / 2n;
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
});
2023-01-26 05:06:28 +03:00
should('inversion', () => {
2023-01-13 18:00:13 +03:00
const a = 1234n;
const b = 5678n;
const c = a * b;
equal(G[1].multiply(a).multiply(b), G[1].multiply(c), 'a*b*G = c*G');
const inv = mod.invert(b, CURVE_ORDER);
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
});
2023-01-26 05:06:28 +03:00
should('multiply, rand', () =>
2023-01-13 18:00:13 +03:00
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a + b, CURVE_ORDER);
if (c === CURVE_ORDER || c < 1n) return;
const pA = G[1].multiply(a);
const pB = G[1].multiply(b);
const pC = G[1].multiply(c);
equal(pA.add(pB), pB.add(pA), 'pA + pB = pB + pA');
equal(pA.add(pB), pC, 'pA + pB = pC');
}),
{ numRuns: NUM_RUNS }
)
);
2023-01-26 05:06:28 +03:00
should('multiply2, rand', () =>
2023-01-13 18:00:13 +03:00
fc.assert(
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
const c = mod.mod(a * b, CURVE_ORDER);
const pA = G[1].multiply(a);
const pB = G[1].multiply(b);
equal(pA.multiply(b), pB.multiply(a), 'b*pA = a*pB');
equal(pA.multiply(b), G[1].multiply(c), 'b*pA = c*G');
}),
{ numRuns: NUM_RUNS }
)
);
});
for (const op of ['add', 'subtract']) {
describe(op, () => {
should('type check', () => {
throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n');
G[1][op](G[2]);
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
throws(() => G[1][op](-123n), '-123n');
throws(() => G[1][op](123), '123');
2023-01-13 18:00:13 +03:00
throws(() => G[1][op](123.456), '123.456');
throws(() => G[1][op](true), 'true');
throws(() => G[1][op](false), 'false');
throws(() => G[1][op](null), 'null');
throws(() => G[1][op](undefined), 'undefined');
2023-01-13 18:00:13 +03:00
throws(() => G[1][op]('1'), "'1'");
throws(() => G[1][op]({ x: 1n, y: 1n }), '{ x: 1n, y: 1n }');
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n }), '{ x: 1n, y: 1n, z: 1n }');
2023-01-13 18:00:13 +03:00
throws(
() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }),
'{ x: 1n, y: 1n, z: 1n, t: 1n }'
);
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
2023-01-24 07:37:53 +03:00
// if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
throws(() => G[1][op](o.BASE), `${op}/other curve point`);
2023-01-13 18:00:13 +03:00
});
});
}
should('equals type check', () => {
throws(() => G[1].equals(0), '0');
throws(() => G[1].equals(0n), '0n');
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
deepStrictEqual(G[1].equals(G[1]), true, '1*G == 1*G');
deepStrictEqual(G[2].equals(G[2]), true, '2*G == 2*G');
throws(() => G[1].equals(CURVE_ORDER), 'CURVE_ORDER');
throws(() => G[1].equals(123.456), '123.456');
throws(() => G[1].equals(true), 'true');
throws(() => G[1].equals('1'), "'1'");
throws(() => G[1].equals({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
throws(() => G[1].equals(new Uint8Array([])), 'ui8a([])');
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
2023-01-24 07:37:53 +03:00
// if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), 'Point.equals(${pointName})');
2023-01-13 18:00:13 +03:00
throws(() => G[1].equals(o.BASE), 'other curve point');
});
for (const op of ['multiply', 'multiplyUnsafe']) {
if (!p.BASE[op]) continue;
describe(op, () => {
should('type check', () => {
if (op !== 'multiplyUnsafe') {
throws(() => G[1][op](0), '0');
throws(() => G[1][op](0n), '0n');
}
G[1][op](1n);
G[1][op](CURVE_ORDER - 1n);
throws(() => G[1][op](G[2]), 'G[2]');
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
throws(() => G[1][op](CURVE_ORDER + 1n), 'CURVE_ORDER+1');
throws(() => G[1][op](123.456), '123.456');
throws(() => G[1][op](true), 'true');
throws(() => G[1][op]('1'), '1');
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
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](o.BASE), 'other curve point');
});
});
}
// Complex point (Extended/Jacobian/Projective?)
2023-01-24 07:37:53 +03:00
// if (p.BASE.toAffine && C.Point) {
// should('toAffine()', () => {
// equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0');
// equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1');
// });
// }
// if (p.fromAffine && C.Point) {
// should('fromAffine()', () => {
// equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0');
// equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1');
// });
// }
2023-01-13 18:00:13 +03:00
// toHex/fromHex (if available)
if (p.fromHex && p.BASE.toHex) {
should('fromHex(toHex()) roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
2023-02-09 15:29:19 +03:00
const point = p.BASE.multiply(x);
const hex = point.toHex();
const bytes = point.toRawBytes();
2023-01-13 18:00:13 +03:00
deepStrictEqual(p.fromHex(hex).toHex(), hex);
2023-02-09 15:29:19 +03:00
deepStrictEqual(p.fromHex(bytes).toHex(), hex);
})
);
});
should('fromHex(toHex(compressed=true)) roundtrip', () => {
fc.assert(
fc.property(FC_BIGINT, (x) => {
const point = p.BASE.multiply(x);
const hex = point.toHex(true);
const bytes = point.toRawBytes(true);
deepStrictEqual(p.fromHex(hex).toHex(true), hex);
deepStrictEqual(p.fromHex(bytes).toHex(true), hex);
2023-01-13 18:00:13 +03:00
})
);
});
2022-12-03 13:08:49 +03:00
}
});
2023-01-13 18:00:13 +03:00
}
describe(name, () => {
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
2023-01-26 05:06:28 +03:00
should('.getPublicKey() type check', () => {
2023-01-13 18:00:13 +03:00
throws(() => C.getPublicKey(0), '0');
throws(() => C.getPublicKey(0n), '0n');
throws(() => C.getPublicKey(-123n), '-123n');
2023-01-26 05:06:28 +03:00
throws(() => C.getPublicKey(123), '123');
2023-01-13 18:00:13 +03:00
throws(() => C.getPublicKey(123.456), '123.456');
throws(() => C.getPublicKey(true), 'true');
throws(() => C.getPublicKey(false), 'false');
throws(() => C.getPublicKey(null), 'null');
throws(() => C.getPublicKey(undefined), 'undefined');
2023-01-13 18:00:13 +03:00
throws(() => C.getPublicKey(''), "''");
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
2023-01-26 05:06:28 +03:00
// throws(() => C.getPublicKey('1'), "'1'");
2023-01-13 18:00:13 +03:00
throws(() => C.getPublicKey('key'), "'key'");
2023-01-26 05:06:28 +03:00
throws(() => C.getPublicKey({}));
2023-01-13 18:00:13 +03:00
throws(() => C.getPublicKey(new Uint8Array([])));
throws(() => C.getPublicKey(new Uint8Array([0])));
throws(() => C.getPublicKey(new Uint8Array([1])));
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
2023-01-26 05:06:28 +03:00
throws(() => C.getPublicKey(Array(32).fill(1)));
2023-01-13 18:00:13 +03:00
});
should('.verify() should verify random signatures', () =>
fc.assert(
2023-01-13 18:00:13 +03:00
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(
C.verify(sig, msg, pub),
true,
2023-02-09 15:29:19 +03:00
`priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}`
2023-01-13 18:00:13 +03:00
);
}),
{ numRuns: NUM_RUNS }
)
);
2023-01-30 08:10:56 +03:00
should('.verify() should verify empty signatures', () => {
const msg = new Uint8Array([]);
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(
C.verify(sig, msg, pub),
true,
'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}'
);
});
2023-01-13 18:00:13 +03:00
should('.sign() edge cases', () => {
throws(() => C.sign());
throws(() => C.sign(''));
throws(() => C.sign('', ''));
throws(() => C.sign(new Uint8Array(), new Uint8Array()));
});
2023-01-26 05:06:28 +03:00
describe('verify()', () => {
const msg = '01'.repeat(32);
2023-01-26 05:06:28 +03:00
should('true for proper signatures', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, msg, pub), true);
});
should('false for wrong messages', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const pub = C.getPublicKey(priv);
deepStrictEqual(C.verify(sig, '11'.repeat(32), pub), false);
});
should('false for wrong keys', () => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
deepStrictEqual(C.verify(sig, msg, C.getPublicKey(C.utils.randomPrivateKey())), false);
});
2023-01-13 18:00:13 +03:00
});
2023-02-09 15:29:19 +03:00
if (C.Signature) {
should('Signature serialization roundtrip', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const sig = C.sign(msg, priv);
const sigRS = (sig) => ({ s: sig.s, r: sig.r });
// Compact
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromCompact(sig.toCompactRawBytes())), sigRS(sig));
// DER
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERHex())), sigRS(sig));
deepStrictEqual(sigRS(C.Signature.fromDER(sig.toDERRawBytes())), sigRS(sig));
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.addRecoveryBit/Signature.recoveryPublicKey', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
deepStrictEqual(sig.recoverPublicKey(msg).toRawBytes(), pub);
const sig2 = C.Signature.fromCompact(sig.toCompactHex());
throws(() => sig2.recoverPublicKey(msg));
const sig3 = sig2.addRecoveryBit(sig.recovery);
deepStrictEqual(sig3.recoverPublicKey(msg).toRawBytes(), pub);
}),
{ numRuns: NUM_RUNS }
)
);
should('Signature.normalizeS', () =>
fc.assert(
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
const priv = C.utils.randomPrivateKey();
const pub = C.getPublicKey(priv);
const sig = C.sign(msg, priv);
const sig2 = sig.normalizeS();
deepStrictEqual(sig2.hasHighS(), false);
}),
{ numRuns: NUM_RUNS }
)
);
}
2023-01-13 18:00:13 +03:00
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
// should('should not verify signature with wrong message', () => {
// fc.assert(
// fc.property(
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
// (bytes, wrongBytes) => {
// const privKey = C.utils.randomPrivateKey();
// const message = new Uint8Array(bytes);
// const wrongMessage = new Uint8Array(wrongBytes);
// const publicKey = C.getPublicKey(privKey);
// const signature = C.sign(message, privKey);
// deepStrictEqual(
// C.verify(signature, wrongMessage, publicKey),
// bytes.toString() === wrongBytes.toString()
// );
// }
// ),
// { numRuns: NUM_RUNS }
// );
// });
if (C.getSharedSecret) {
should('getSharedSecret() should be commutative', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const asec = C.utils.randomPrivateKey();
const apub = C.getPublicKey(asec);
const bsec = C.utils.randomPrivateKey();
const bpub = C.getPublicKey(bsec);
try {
deepStrictEqual(C.getSharedSecret(asec, bpub), C.getSharedSecret(bsec, apub));
} catch (error) {
console.error('not commutative', { asec, apub, bsec, bpub });
throw error;
}
}
});
}
});
}
2023-01-13 03:20:48 +03:00
2023-01-13 03:29:54 +03:00
should('secp224k1 sqrt bug', () => {
2023-01-13 03:20:48 +03:00
const { Fp } = secp224r1.CURVE;
const sqrtMinus1 = Fp.sqrt(-1n);
// Verified against sage
deepStrictEqual(
sqrtMinus1,
23621584063597419797792593680131996961517196803742576047493035507225n
);
deepStrictEqual(
2023-01-26 05:06:28 +03:00
Fp.neg(sqrtMinus1),
2023-01-13 03:20:48 +03:00
3338362603553219996874421406887633712040719456283732096017030791656n
);
2023-01-26 05:06:28 +03:00
deepStrictEqual(Fp.sqr(sqrtMinus1), Fp.create(-1n));
2023-01-13 03:20:48 +03:00
});
2023-01-30 08:10:56 +03:00
should('bigInt private keys', () => {
// Doesn't support bigints anymore
throws(() => ed25519.sign('', 123n));
throws(() => ed25519.getPublicKey(123n));
throws(() => x25519.getPublicKey(123n));
// Weierstrass still supports
secp256k1.getPublicKey(123n);
secp256k1.sign('', 123n);
});
2022-12-03 13:08:49 +03:00
// ESM is broken.
import url from 'url';
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
should.run();
}