diff --git a/package.json b/package.json index 3d34be4..39abab9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@types/node": "18.11.3", "fast-check": "3.0.0", "micro-bmark": "0.2.0", - "micro-should": "0.2.0", + "micro-should": "0.3.0", "prettier": "2.6.2", "rollup": "2.75.5", "typescript": "4.7.3" diff --git a/test/basic.test.js b/test/basic.test.js index a7243b4..0ed5929 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -1,5 +1,5 @@ import { deepStrictEqual, throws } from 'assert'; -import { should } from 'micro-should'; +import { should, describe } from 'micro-should'; import * as fc from 'fast-check'; import * as mod from '../lib/esm/abstract/modular.js'; import { bytesToHex as toHex } from '../lib/esm/abstract/utils.js'; @@ -62,233 +62,235 @@ for (const c in FIELDS) { 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); - should(`${name} equality`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - const b = create(num); - deepStrictEqual(Fp.equals(a, b), true); - deepStrictEqual(Fp.equals(b, a), true); - }) - ); - }); - should(`${name} non-equality`, () => { - fc.assert( - fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { - const a = create(num1); - const b = create(num2); - deepStrictEqual(Fp.equals(a, b), num1 === num2); - deepStrictEqual(Fp.equals(b, a), num1 === num2); - }) - ); - }); - should(`${name} 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(`${name} 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(`${name} add/subtract/x+0=x`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - deepStrictEqual(Fp.add(a, Fp.ZERO), a); - }) - ); - }); - should(`${name} 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(`${name} add/subtract/negate equality`, () => { - fc.assert( - fc.property(FC_BIGINT, (num1) => { - const a = create(num1); - const b = create(num1); - deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a)); - deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b))); - deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.mul(b, Fp.create(-1n)))); - }) - ); - }); - should(`${name} add/subtract/negate`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a)); - deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n))); - }) - ); - }); - should(`${name} negate(0)`, () => { - deepStrictEqual(Fp.negate(Fp.ZERO), Fp.ZERO); - }); - - should(`${name} 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(`${name} 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(`${name} 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(`${name} 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(`${name} multiply/square equality`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - deepStrictEqual(Fp.square(a), Fp.mul(a, a)); - }) - ); - }); - should(`${name} 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)); - }) - ); - }); - - should(`${name} square(0)`, () => { - deepStrictEqual(Fp.square(Fp.ZERO), Fp.ZERO); - deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO); - }); - - should(`${name} square(1)`, () => { - deepStrictEqual(Fp.square(Fp.ONE), Fp.ONE); - deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE); - }); - - should(`${name} square(-1)`, () => { - const minus1 = Fp.negate(Fp.ONE); - deepStrictEqual(Fp.square(minus1), Fp.ONE); - deepStrictEqual(Fp.mul(minus1, minus1), Fp.ONE); - }); - - const isSquare = mod.FpIsSquare(Fp); - // Not implemented - if (Fp !== bls12_381.CURVE.Fp12) { - should(`${name} multiply/sqrt`, () => { + describe(name, () => { + should('equality', () => { 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); - deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a'); - deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a'); + const b = create(num); + deepStrictEqual(Fp.equals(a, b), true); + deepStrictEqual(Fp.equals(b, a), true); + }) + ); + }); + should('non-equality', () => { + fc.assert( + fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { + const a = create(num1); + const b = create(num2); + deepStrictEqual(Fp.equals(a, b), num1 === num2); + deepStrictEqual(Fp.equals(b, a), num1 === num2); + }) + ); + }); + 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); + deepStrictEqual(Fp.sub(Fp.ZERO, a), Fp.negate(a)); + deepStrictEqual(Fp.sub(a, b), Fp.add(a, Fp.negate(b))); + 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); + deepStrictEqual(Fp.negate(a), Fp.sub(Fp.ZERO, a)); + deepStrictEqual(Fp.negate(a), Fp.mul(a, Fp.create(-1n))); + }) + ); + }); + should('negate(0)', () => { + deepStrictEqual(Fp.negate(Fp.ZERO), Fp.ZERO); + }); + + 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); + deepStrictEqual(Fp.square(a), Fp.mul(a, a)); + }) + ); + }); + 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)); }) ); }); - should(`${name} sqrt(0)`, () => { - deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO); - const sqrt1 = Fp.sqrt(Fp.ONE); - deepStrictEqual( - Fp.equals(sqrt1, Fp.ONE) || Fp.equals(sqrt1, Fp.negate(Fp.ONE)), - true, - 'sqrt(1) = 1 or -1' + should('square(0)', () => { + deepStrictEqual(Fp.square(Fp.ZERO), Fp.ZERO); + deepStrictEqual(Fp.mul(Fp.ZERO, Fp.ZERO), Fp.ZERO); + }); + + should('square(1)', () => { + deepStrictEqual(Fp.square(Fp.ONE), Fp.ONE); + deepStrictEqual(Fp.mul(Fp.ONE, Fp.ONE), Fp.ONE); + }); + + should('square(-1)', () => { + const minus1 = Fp.negate(Fp.ONE); + deepStrictEqual(Fp.square(minus1), Fp.ONE); + 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); + deepStrictEqual(Fp.equals(Fp.square(root), a), true, 'sqrt(a)^2 == a'); + deepStrictEqual(Fp.equals(Fp.square(Fp.negate(root)), a), true, '(-sqrt(a))^2 == a'); + }) + ); + }); + + should('sqrt(0)', () => { + deepStrictEqual(Fp.sqrt(Fp.ZERO), Fp.ZERO); + const sqrt1 = Fp.sqrt(Fp.ONE); + deepStrictEqual( + Fp.equals(sqrt1, Fp.ONE) || Fp.equals(sqrt1, Fp.negate(Fp.ONE)), + true, + 'sqrt(1) = 1 or -1' + ); + }); + } + + should('div/division by one equality', () => { + fc.assert( + fc.property(FC_BIGINT, (num) => { + const a = create(num); + if (Fp.equals(a, Fp.ZERO)) return; // No division by zero + deepStrictEqual(Fp.div(a, Fp.ONE), a); + deepStrictEqual(Fp.div(a, a), Fp.ONE); + }) + ); + }); + should('zero division equality', () => { + fc.assert( + fc.property(FC_BIGINT, (num) => { + const a = create(num); + deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO); + }) + ); + }); + 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))); + }) + ); + }); + should('div/division and multiplication equality', () => { + fc.assert( + fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { + const a = create(num1); + const b = create(num2); + deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b))); + }) ); }); - } - - should(`${name} div/division by one equality`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - if (Fp.equals(a, Fp.ZERO)) return; // No division by zero - deepStrictEqual(Fp.div(a, Fp.ONE), a); - deepStrictEqual(Fp.div(a, a), Fp.ONE); - }) - ); - }); - should(`${name} zero division equality`, () => { - fc.assert( - fc.property(FC_BIGINT, (num) => { - const a = create(num); - deepStrictEqual(Fp.div(Fp.ZERO, a), Fp.ZERO); - }) - ); - }); - should(`${name} 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))); - }) - ); - }); - should(`${name} div/division and multiplication equality`, () => { - fc.assert( - fc.property(FC_BIGINT, FC_BIGINT, (num1, num2) => { - const a = create(num1); - const b = create(num2); - deepStrictEqual(Fp.div(a, b), Fp.mul(a, Fp.invert(b))); - }) - ); }); } } @@ -311,12 +313,12 @@ const NUM_RUNS = 5; const getXY = (p) => ({ x: p.x, y: p.y }); function equal(a, b, comment) { - deepStrictEqual(a.equals(b), true, `eq(${comment})`); + deepStrictEqual(a.equals(b), true, 'eq(${comment})'); if (a.toAffine && b.toAffine) { - deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`); + deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), 'eqToAffine(${comment})'); } else if (!a.toAffine && !b.toAffine) { // Already affine - deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`); + deepStrictEqual(getXY(a), getXY(b), 'eqAffine(${comment})'); } else throw new Error('Different point types'); } @@ -341,256 +343,270 @@ for (const name in CURVES) { const G = [p.ZERO, p.BASE]; for (let i = 2; i < 10; i++) G.push(G[1].multiply(i)); - // Here we check basic group laws, to verify that points works as group - should(`${name}/${pointName}/Basic group laws (zero)`, () => { - 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]; - equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`); - equal(G[0].multiply(i + 1), G[0], `${i + 1}*0 = 0`); + const title = `${name}/${pointName}`; + describe(title, () => { + describe('basic group laws', () => { + // Here we check basic group laws, to verify that points works as group + should('(zero)', () => { + 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]; + equal(p, p.add(G[0]), '${i}*G + 0 = ${i}*G'); + equal(G[0].multiply(i + 1), G[0], '${i + 1}*0 = 0'); + } + }); + should('(one)', () => { + 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'); + }); + should('(sanity tests)', () => { + 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'); + }); + should('(addition commutativity)', () => { + 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'); + }); + should('(double)', () => { + equal(G[3].double(), G[6], '(3*G).double() = 6*G'); + }); + should('(multiply)', () => { + equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G'); + }); + should('(same point addition)', () => { + equal(G[3].add(G[3]), G[6], '3*G + 3*G = 6*G'); + }); + should('(same point (negative) addition)', () => { + 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'); + }); + should('(curve order)', () => { + 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'); + }); + should('(inversion)', () => { + 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'); + }); + should('(multiply, rand)', () => + 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 } + ) + ); + should('(multiply2, rand)', () => + 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](123.456), '123.456'); + throws(() => G[1][op](true), 'true'); + throws(() => G[1][op]('1'), "'1'"); + 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])'); + if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), 'Point ${op} ${pointName}'); + throws(() => G[1][op](o.BASE), '${op}/other curve point'); + }); + }); + } + + 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])'); + if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), 'Point.equals(${pointName})'); + 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?) + if (p.BASE.toAffine) { + should('toAffine()', () => { + equal(p.ZERO.toAffine(), C.Point.ZERO, '0 = 0'); + equal(p.BASE.toAffine(), C.Point.BASE, '1 = 1'); + }); + } + if (p.fromAffine) { + should('fromAffine()', () => { + equal(p.ZERO, p.fromAffine(C.Point.ZERO), '0 = 0'); + equal(p.BASE, p.fromAffine(C.Point.BASE), '1 = 1'); + }); + } + // toHex/fromHex (if available) + if (p.fromHex && p.BASE.toHex) { + should('fromHex(toHex()) roundtrip', () => { + fc.assert( + fc.property(FC_BIGINT, (x) => { + const hex = p.BASE.multiply(x).toHex(); + deepStrictEqual(p.fromHex(hex).toHex(), hex); + }) + ); + }); } }); - should(`${name}/${pointName}/Basic group laws (one)`, () => { - 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'); + } + describe(name, () => { + // Generic complex things (getPublicKey/sign/verify/getSharedSecret) + should('getPublicKey type check', () => { + throws(() => C.getPublicKey(0), '0'); + throws(() => C.getPublicKey(0n), '0n'); + throws(() => C.getPublicKey(false), 'false'); + throws(() => C.getPublicKey(123.456), '123.456'); + throws(() => C.getPublicKey(true), 'true'); + throws(() => C.getPublicKey(''), "''"); + // NOTE: passes because of disabled hex padding checks for starknet, maybe enable? + //throws(() => C.getPublicKey('1'), "'1'"); + throws(() => C.getPublicKey('key'), "'key'"); + 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))); }); - should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => { - 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`); - }); - should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => { - 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`); - }); - should(`${name}/${pointName}/Basic group laws (double)`, () => { - equal(G[3].double(), G[6], '(3*G).double() = 6*G'); - }); - should(`${name}/${pointName}/Basic group laws (multiply)`, () => { - equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G'); - }); - should(`${name}/${pointName}/Basic group laws (same point addition)`, () => { - equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`); - }); - should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => { - 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'); - }); - should(`${name}/${pointName}/Basic group laws (curve order)`, () => { - 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'); - }); - should(`${name}/${pointName}/Basic group laws (inversion)`, () => { - 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'); - }); - should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () => + should('.verify() should verify random signatures', () => 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`); + 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, + 'priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}' + ); }), { numRuns: NUM_RUNS } ) ); - should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () => - 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']) { - should(`${name}/${pointName}/${op} 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](123.456), '123.456'); - throws(() => G[1][op](true), 'true'); - throws(() => G[1][op]('1'), "'1'"); - 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])'); - if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`); - throws(() => G[1][op](o.BASE), `${op}/other curve point`); - }); - } - - should(`${name}/${pointName}/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])'); - if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`); - throws(() => G[1].equals(o.BASE), 'other curve point'); + should('.sign() edge cases', () => { + throws(() => C.sign()); + throws(() => C.sign('')); }); - for (const op of ['multiply', 'multiplyUnsafe']) { - if (!p.BASE[op]) continue; - should(`${name}/${pointName}/${op} 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?) - if (p.BASE.toAffine) { - should(`${name}/${pointName}/toAffine()`, () => { - equal(p.ZERO.toAffine(), C.Point.ZERO, `0 = 0`); - equal(p.BASE.toAffine(), C.Point.BASE, `1 = 1`); - }); - } - if (p.fromAffine) { - should(`${name}/${pointName}/fromAffine()`, () => { - equal(p.ZERO, p.fromAffine(C.Point.ZERO), `0 = 0`); - equal(p.BASE, p.fromAffine(C.Point.BASE), `1 = 1`); - }); - } - // toHex/fromHex (if available) - if (p.fromHex && p.BASE.toHex) { - should(`${name}/${pointName}/fromHex(toHex()) roundtrip`, () => { - fc.assert( - fc.property(FC_BIGINT, (x) => { - const hex = p.BASE.multiply(x).toHex(); - deepStrictEqual(p.fromHex(hex).toHex(), hex); - }) - ); - }); - } - } - // Generic complex things (getPublicKey/sign/verify/getSharedSecret) - should(`${name}/getPublicKey type check`, () => { - throws(() => C.getPublicKey(0), '0'); - throws(() => C.getPublicKey(0n), '0n'); - throws(() => C.getPublicKey(false), 'false'); - throws(() => C.getPublicKey(123.456), '123.456'); - throws(() => C.getPublicKey(true), 'true'); - throws(() => C.getPublicKey(''), "''"); - // NOTE: passes because of disabled hex padding checks for starknet, maybe enable? - //throws(() => C.getPublicKey('1'), "'1'"); - throws(() => C.getPublicKey('key'), "'key'"); - 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))); - }); - should(`${name}.verify()/should verify random signatures`, () => - 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( - C.verify(sig, msg, pub), - true, - `priv=${toHex(priv)},pub=${toHex(pub)},msg=${msg}` - ); - }), - { numRuns: NUM_RUNS } - ) - ); - should(`${name}.sign()/edge cases`, () => { - throws(() => C.sign()); - throws(() => C.sign('')); - }); - - should(`${name}.verify()/should not verify signature with wrong hash`, () => { - const MSG = '01'.repeat(32); - const PRIV_KEY = 0x2n; - const WRONG_MSG = '11'.repeat(32); - const signature = C.sign(MSG, PRIV_KEY); - const publicKey = C.getPublicKey(PRIV_KEY); - deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false); - }); - // 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(`${name}/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(`${name}/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; - } - } + should('.verify() should not verify signature with wrong hash', () => { + const MSG = '01'.repeat(32); + const PRIV_KEY = 0x2n; + const WRONG_MSG = '11'.repeat(32); + const signature = C.sign(MSG, PRIV_KEY); + const publicKey = C.getPublicKey(PRIV_KEY); + deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false); }); - } + // 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; + } + } + }); + } + }); } should('secp224k1 sqrt bug', () => { diff --git a/test/bls12-381.test.js b/test/bls12-381.test.js index e599b13..ef8b6fb 100644 --- a/test/bls12-381.test.js +++ b/test/bls12-381.test.js @@ -1,5 +1,5 @@ import { bls12_381 } from '../lib/esm/bls12-381.js'; -import { should } from 'micro-should'; +import { describe, should } from 'micro-should'; import { deepStrictEqual, notDeepStrictEqual, throws } from 'assert'; import { sha512 } from '@noble/hashes/sha512'; import * as fc from 'fast-check'; @@ -38,11 +38,11 @@ const B_384_40 = '40'.padEnd(384, '0'); // [0x40, 0, 0...] const getPubKey = (priv) => bls.getPublicKey(priv); // Fp -{ +describe('bls12-381 Fp', () => { const Fp = bls.Fp; const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); - should('bls12-381/Fp/multiply/sqrt', () => { + should('multiply/sqrt', () => { let sqr1 = Fp.sqrt(Fp.create(300855555557n)); deepStrictEqual( sqr1 && sqr1.toString(), @@ -50,16 +50,16 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ); throws(() => Fp.sqrt(Fp.create(72057594037927816n))); }); -} +}); // Fp2 -{ +describe('bls12-381 Fp2', () => { const Fp = bls.Fp; const Fp2 = bls.Fp2; const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const FC_BIGINT_2 = fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }); - should('bls12-381 Fp2/non-equality', () => { + should('non-equality', () => { fc.assert( fc.property(FC_BIGINT_2, FC_BIGINT_2, (num1, num2) => { const a = Fp2.fromBigTuple([num1[0], num1[1]]); @@ -70,7 +70,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ); }); - should('bls12-381 Fp2/div/x/1=x', () => { + should('div/x/1=x', () => { fc.assert( fc.property(FC_BIGINT_2, (num) => { const a = Fp2.fromBigTuple([num[0], num[1]]); @@ -81,7 +81,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ); }); - should('bls12-381 Fp2/frobenius', () => { + should('frobenius', () => { // expect(Fp2.FROBENIUS_COEFFICIENTS[0].equals(Fp.ONE)).toBe(true); // expect( // Fp2.FROBENIUS_COEFFICIENTS[1].equals( @@ -139,89 +139,88 @@ const getPubKey = (priv) => bls.getPublicKey(priv); true ); }); -} +}); // Point -{ +describe('bls12-381 Point', () => { const Fp = bls.Fp; const FC_BIGINT = fc.bigInt(1n, Fp.ORDER - 1n); const PointG1 = bls.G1.Point; const PointG2 = bls.G2.Point; - should('bls12-381 Point/Point with Fp coordinates/Point equality', () => { - fc.assert( - fc.property( - fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }), - fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }), - ([x1, y1, z1], [x2, y2, z2]) => { - const p1 = new PointG1(Fp.create(x1), Fp.create(y1), Fp.create(z1)); - const p2 = new PointG1(Fp.create(x2), Fp.create(y2), Fp.create(z2)); - deepStrictEqual(p1.equals(p1), true); - deepStrictEqual(p2.equals(p2), true); - deepStrictEqual(p1.equals(p2), false); - deepStrictEqual(p2.equals(p1), false); - } - ) - ); - }); - should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 1', () => { - const a = new PointG1(Fp.create(0n), Fp.create(0n)); - a.assertValidity(); - }); - should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 1', () => { - const a = new PointG1(Fp.create(0n), Fp.create(1n)); - throws(() => a.assertValidity()); - }); + describe('with Fp coordinates', () => { + should('Point equality', () => { + fc.assert( + fc.property( + fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }), + fc.array(FC_BIGINT, { minLength: 3, maxLength: 3 }), + ([x1, y1, z1], [x2, y2, z2]) => { + const p1 = new PointG1(Fp.create(x1), Fp.create(y1), Fp.create(z1)); + const p2 = new PointG1(Fp.create(x2), Fp.create(y2), Fp.create(z2)); + deepStrictEqual(p1.equals(p1), true); + deepStrictEqual(p2.equals(p2), true); + deepStrictEqual(p1.equals(p2), false); + deepStrictEqual(p2.equals(p1), false); + } + ) + ); + }); + should('be placed on curve vector 1', () => { + const a = new PointG1(Fp.create(0n), Fp.create(0n)); + a.assertValidity(); + }); + should('not be placed on curve vector 1', () => { + const a = new PointG1(Fp.create(0n), Fp.create(1n)); + throws(() => a.assertValidity()); + }); - should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 2', () => { - const a = new PointG1( - Fp.create( - 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn - ), - Fp.create( - 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n - ) - ); - a.assertValidity(); - }); - should('bls12-381 Point/Point with Fp coordinates/should be placed on curve vector 3', () => { - const a = new PointG1( - Fp.create( - 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n - ), - Fp.create( - 1120750640227410374130508113691552487207139112596221955734902008063040284119210871734388578113045163251615428544022n - ) - ); + should('be placed on curve vector 2', () => { + const a = new PointG1( + Fp.create( + 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn + ), + Fp.create( + 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n + ) + ); + a.assertValidity(); + }); + should('be placed on curve vector 3', () => { + const a = new PointG1( + Fp.create( + 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n + ), + Fp.create( + 1120750640227410374130508113691552487207139112596221955734902008063040284119210871734388578113045163251615428544022n + ) + ); - a.assertValidity(); - }); - should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 3', () => { - const a = new PointG1( - Fp.create( - 622186380008502900120948444810967255157373993223369845903602988014033704418470621816206856882891545628885272576827n - ), - Fp.create( - 1031339409279989180383920781105371089925712739630078633497696569127911841893478548110664124341123041182605140418539n - ) - ); - throws(() => a.assertValidity()); - }); - should('bls12-381 Point/Point with Fp coordinates/should not be placed on curve vector 2', () => { - const a = new PointG1( - Fp.create( - 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6ban - ), - Fp.create( - 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n - ) - ); - throws(() => a.assertValidity()); - }); + a.assertValidity(); + }); + should('not be placed on curve vector 3', () => { + const a = new PointG1( + Fp.create( + 622186380008502900120948444810967255157373993223369845903602988014033704418470621816206856882891545628885272576827n + ), + Fp.create( + 1031339409279989180383920781105371089925712739630078633497696569127911841893478548110664124341123041182605140418539n + ) + ); + throws(() => a.assertValidity()); + }); + should('not be placed on curve vector 2', () => { + const a = new PointG1( + Fp.create( + 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6ban + ), + Fp.create( + 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1n + ) + ); + throws(() => a.assertValidity()); + }); - should( - 'bls12-381 Point/Point with Fp coordinates/should be doubled and placed on curve vector 1', - () => { + should('be doubled and placed on curve vector 1', () => { const a = new PointG1( Fp.create( 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbn @@ -245,11 +244,8 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ); deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.add(a)); - } - ); - should( - 'bls12-381 Point/Point with Fp coordinates/should be pdoubled and placed on curve vector 2', - () => { + }); + should('be pdoubled and placed on curve vector 2', () => { const a = new PointG1( Fp.create( 3971675556538908004130084773503021351583407620890695272226385332452194486153316625183061567093226342405194446632851n @@ -273,85 +269,89 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ); deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.add(a)); - } - ); - should('bls12-381 Point/Point with Fp coordinates/should not validate incorrect point', () => { - const x = - 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n; - const y = - 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n; + }); + should('not validate incorrect point', () => { + const x = + 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n; + const y = + 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n; - const p = new PointG1(Fp.create(x), Fp.create(y)); - throws(() => p.assertValidity()); + const p = new PointG1(Fp.create(x), Fp.create(y)); + throws(() => p.assertValidity()); + }); }); - should('bls12-381 Point/Point with Fp2 coordinates/Point equality', () => { - fc.assert( - fc.property( - fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), { - minLength: 3, - maxLength: 3, - }), - fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), { - minLength: 3, - maxLength: 3, - }), - ([x1, y1, z1], [x2, y2, z2]) => { - const p1 = new PointG2(Fp2.fromBigTuple(x1), Fp2.fromBigTuple(y1), Fp2.fromBigTuple(z1)); - const p2 = new PointG2(Fp2.fromBigTuple(x2), Fp2.fromBigTuple(y2), Fp2.fromBigTuple(z2)); - deepStrictEqual(p1.equals(p1), true); - deepStrictEqual(p2.equals(p2), true); - deepStrictEqual(p1.equals(p2), false); - deepStrictEqual(p2.equals(p1), false); - } - ) - ); - }); - // should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 1', () => { - // const a = new PointG2(Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([0n, 0n])); - // a.assertValidity(); - // }); - should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 2', () => { - const a = new PointG2( - Fp2.fromBigTuple([ - 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, - 0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7en, - ]), - Fp2.fromBigTuple([ - 0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801n, - 0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79ben, - ]), - Fp2.fromBigTuple([1n, 0n]) - ); - a.assertValidity(); - }); - should('bls12-381 Point/Point with Fp2 coordinates/should be placed on curve vector 3', () => { - const a = new PointG2( - Fp2.fromBigTuple([ - 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, - 1890785404699189181161569277356497622423785178845737858235714310995835974899880469355250933575450045792782146044819n, - ]), - Fp2.fromBigTuple([ - 1215754321684097939278683023199690844646077558342794977283698289191570128272085945598449054373022460634252133664610n, - 2751025411942897795042193940345989612527395984463172615380574492034129474560903255212585680112858672276592527763585n, - ]) - ); - a.assertValidity(); - }); - should( - 'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 1', - () => { + describe('with Fp2 coordinates', () => { + should('Point equality', () => { + fc.assert( + fc.property( + fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), { + minLength: 3, + maxLength: 3, + }), + fc.array(fc.array(FC_BIGINT, { minLength: 2, maxLength: 2 }), { + minLength: 3, + maxLength: 3, + }), + ([x1, y1, z1], [x2, y2, z2]) => { + const p1 = new PointG2( + Fp2.fromBigTuple(x1), + Fp2.fromBigTuple(y1), + Fp2.fromBigTuple(z1) + ); + const p2 = new PointG2( + Fp2.fromBigTuple(x2), + Fp2.fromBigTuple(y2), + Fp2.fromBigTuple(z2) + ); + deepStrictEqual(p1.equals(p1), true); + deepStrictEqual(p2.equals(p2), true); + deepStrictEqual(p1.equals(p2), false); + deepStrictEqual(p2.equals(p1), false); + } + ) + ); + }); + // should('be placed on curve vector 1', () => { + // const a = new PointG2(Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([0n, 0n])); + // a.assertValidity(); + // }); + should('be placed on curve vector 2', () => { + const a = new PointG2( + Fp2.fromBigTuple([ + 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, + 0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7en, + ]), + Fp2.fromBigTuple([ + 0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801n, + 0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79ben, + ]), + Fp2.fromBigTuple([1n, 0n]) + ); + a.assertValidity(); + }); + should('be placed on curve vector 3', () => { + const a = new PointG2( + Fp2.fromBigTuple([ + 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, + 1890785404699189181161569277356497622423785178845737858235714310995835974899880469355250933575450045792782146044819n, + ]), + Fp2.fromBigTuple([ + 1215754321684097939278683023199690844646077558342794977283698289191570128272085945598449054373022460634252133664610n, + 2751025411942897795042193940345989612527395984463172615380574492034129474560903255212585680112858672276592527763585n, + ]) + ); + a.assertValidity(); + }); + should('not be placed on curve vector 1', () => { const a = new PointG2( Fp2.fromBigTuple([0n, 0n]), Fp2.fromBigTuple([1n, 0n]), Fp2.fromBigTuple([1n, 0n]) ); throws(() => a.assertValidity()); - } - ); - should( - 'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 2', - () => { + }); + should('not be placed on curve vector 2', () => { const a = new PointG2( Fp2.fromBigTuple([ 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4410b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, @@ -364,11 +364,8 @@ const getPubKey = (priv) => bls.getPublicKey(priv); Fp2.fromBigTuple([1n, 0n]) ); throws(() => a.assertValidity()); - } - ); - should( - 'bls12-381 Point/Point with Fp2 coordinates/should not be placed on curve vector 3', - () => { + }); + should('not be placed on curve vector 3', () => { const a = new PointG2( Fp2.fromBigTuple([ 0x877d52dd65245f8908a03288adcd396f489ef87ae23fe110c5aa48bc208fbd1a0ed403df5b1ac137922b915f1f38ec37n, @@ -384,10 +381,10 @@ const getPubKey = (priv) => bls.getPublicKey(priv); ]) ); throws(() => a.assertValidity()); - } - ); + }); + }); - should('bls12-381 Point/should be doubled and placed on curve vector 1', () => { + should('be doubled and placed on curve vector 1', () => { const a = new PointG2( Fp2.fromBigTuple([ 0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8n, @@ -417,7 +414,7 @@ const getPubKey = (priv) => bls.getPublicKey(priv); deepStrictEqual(double, a.multiply(2n)); deepStrictEqual(double, a.add(a)); }); - should('bls12-381 Point/should be doubled and placed on curve vector 2', () => { + should('be doubled and placed on curve vector 2', () => { const a = new PointG2( Fp2.fromBigTuple([ 233289878585407360737561818812172281900488265436962145913969074168503452745466655442125797664134009339799716079103n, @@ -455,49 +452,51 @@ const getPubKey = (priv) => bls.getPublicKey(priv); 0x53eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n, 0x63eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000n, ]; - should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=1)', () => { - let G = PointG1.BASE.negate().negate(); // create new point - G._setWindowSize(1); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } + describe('wNAF multiplication same as unsafe', () => { + should('(G1, W=1)', () => { + let G = PointG1.BASE.negate().negate(); // create new point + G._setWindowSize(1); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); + should('(G1, W=4)', () => { + let G = PointG1.BASE.negate().negate(); + G._setWindowSize(4); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); + should('(G1, W=5)', () => { + let G = PointG1.BASE.negate().negate(); + G._setWindowSize(5); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); + should('(G2, W=1)', () => { + let G = PointG2.BASE.negate().negate(); + G._setWindowSize(1); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); + should('(G2, W=4)', () => { + let G = PointG2.BASE.negate().negate(); + G._setWindowSize(4); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); + should('(G2, W=5)', () => { + let G = PointG2.BASE.negate().negate(); + G._setWindowSize(5); + for (let k of wNAF_VECTORS) { + deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); + } + }); }); - should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=4)', () => { - let G = PointG1.BASE.negate().negate(); - G._setWindowSize(4); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } - }); - should('bls12-381 Point/wNAF multiplication same as unsafe (G1, W=5)', () => { - let G = PointG1.BASE.negate().negate(); - G._setWindowSize(5); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } - }); - should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=1)', () => { - let G = PointG2.BASE.negate().negate(); - G._setWindowSize(1); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } - }); - should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=4)', () => { - let G = PointG2.BASE.negate().negate(); - G._setWindowSize(4); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } - }); - should('bls12-381 Point/wNAF multiplication same as unsafe (G2, W=5)', () => { - let G = PointG2.BASE.negate().negate(); - G._setWindowSize(5); - for (let k of wNAF_VECTORS) { - deepStrictEqual(G.multiply(k).equals(G.multiplyUnsafe(k)), true); - } - }); - should('bls12-381 Point/PSI cofactor cleaning same as multiplication', () => { + should('PSI cofactor cleaning same as multiplication', () => { const points = [ new PointG2( Fp2.fromBigTuple([ @@ -558,456 +557,458 @@ const getPubKey = (priv) => bls.getPublicKey(priv); deepStrictEqual(ours.equals(shouldBe), true, 'clearLast'); } }); -} +}); // index.ts // bls.PointG1.BASE.clearMultiplyPrecomputes(); // bls.PointG1.BASE.calcMultiplyPrecomputes(4); -should('bls12-381/basic/should construct point G1 from its uncompressed form (Raw Bytes)', () => { - // Test Zero - const g1 = bls.G1.Point.fromHex(B_192_40); - deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); - deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); - // Test Non-Zero - const x = bls.Fp.create( - BigInt( - '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' +describe('bls12-381/basic', () => { + should('construct point G1 from its uncompressed form (Raw Bytes)', () => { + // Test Zero + const g1 = bls.G1.Point.fromHex(B_192_40); + deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); + deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); + // Test Non-Zero + const x = bls.Fp.create( + BigInt( + '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' + ) + ); + const y = bls.Fp.create( + BigInt( + '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ) + ); + + const g1_ = bls.G1.Point.fromHex( + '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ); + + deepStrictEqual(g1_.x, x); + deepStrictEqual(g1_.y, y); + }); + + should('construct point G1 from its uncompressed form (Hex)', () => { + // Test Zero + const g1 = bls.G1.Point.fromHex(B_192_40); + + deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); + deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); + // Test Non-Zero + const x = bls.Fp.create( + BigInt( + '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' + ) + ); + const y = bls.Fp.create( + BigInt( + '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ) + ); + + const g1_ = bls.G1.Point.fromHex( + '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ); + + deepStrictEqual(g1_.x, x); + deepStrictEqual(g1_.y, y); + }); + + should('construct point G2 from its uncompressed form (Raw Bytes)', () => { + // Test Zero + const g2 = bls.G2.Point.fromHex(B_384_40); + deepStrictEqual(g2.x, bls.G2.Point.ZERO.x, 'zero(x)'); + deepStrictEqual(g2.y, bls.G2.Point.ZERO.y, 'zero(y)'); + // Test Non-Zero + const x = Fp2.fromBigTuple([ + BigInt( + '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' + ), + BigInt( + '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' + ), + ]); + const y = Fp2.fromBigTuple([ + BigInt( + '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ), + BigInt( + '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' + ), + ]); + + const g2_ = bls.G2.Point.fromHex( + '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ); + + deepStrictEqual(g2_.x, x); + deepStrictEqual(g2_.y, y); + }); + + should('construct point G2 from its uncompressed form (Hex)', () => { + // Test Zero + const g2 = bls.G2.Point.fromHex(B_384_40); + + deepStrictEqual(g2.x, bls.G2.Point.ZERO.x); + deepStrictEqual(g2.y, bls.G2.Point.ZERO.y); + // Test Non-Zero + const x = Fp2.fromBigTuple([ + BigInt( + '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' + ), + BigInt( + '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' + ), + ]); + const y = Fp2.fromBigTuple([ + BigInt( + '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ), + BigInt( + '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' + ), + ]); + + const g2_ = bls.G2.Point.fromHex( + '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ); + + deepStrictEqual(g2_.x, x); + deepStrictEqual(g2_.y, y); + }); + + should('get uncompressed form of point G1 (Raw Bytes)', () => { + // Test Zero + deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); + // Test Non-Zero + const x = bls.Fp.create( + BigInt( + '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' + ) + ); + const y = bls.Fp.create( + BigInt( + '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ) + ); + const g1 = new bls.G1.Point(x, y); + deepStrictEqual( + g1.toHex(false), + '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ); + }); + + should('get uncompressed form of point G1 (Hex)', () => { + // Test Zero + deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); + // Test Non-Zero + const x = bls.Fp.create( + BigInt( + '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' + ) + ); + const y = bls.Fp.create( + BigInt( + '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ) + ); + const g1 = new bls.G1.Point(x, y); + deepStrictEqual( + g1.toHex(false), + '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + ); + }); + + should('get uncompressed form of point G2 (Raw Bytes)', () => { + // Test Zero + deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); + // Test Non-Zero + const x = Fp2.fromBigTuple([ + BigInt( + '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' + ), + BigInt( + '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' + ), + ]); + const y = Fp2.fromBigTuple([ + BigInt( + '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ), + BigInt( + '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' + ), + ]); + const g2 = new bls.G2.Point(x, y); + deepStrictEqual( + g2.toHex(false), + '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ); + }); + + should('get uncompressed form of point G2 (Hex)', () => { + // Test Zero + deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); + + // Test Non-Zero + const x = Fp2.fromBigTuple([ + BigInt( + '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' + ), + BigInt( + '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' + ), + ]); + const y = Fp2.fromBigTuple([ + BigInt( + '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ), + BigInt( + '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' + ), + ]); + const g2 = new bls.G2.Point(x, y); + deepStrictEqual( + g2.toHex(false), + '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' + ); + }); + + should('compress and decompress G1 points', async () => { + const priv = bls.G1.Point.fromPrivateKey(42n); + const publicKey = priv.toHex(true); + const decomp = bls.G1.Point.fromHex(publicKey); + deepStrictEqual(publicKey, decomp.toHex(true)); + }); + should('not compress and decompress zero G1 point', () => { + throws(() => bls.G1.Point.fromPrivateKey(0n)); + }); + should('compress and decompress G2 points', () => { + const priv = bls.G2.Point.fromPrivateKey(42n); + const publicKey = priv.toHex(true); + const decomp = bls.G2.Point.fromHex(publicKey); + deepStrictEqual(publicKey, decomp.toHex(true)); + }); + should('not compress and decompress zero G2 point', () => { + throws(() => bls.G2.Point.fromPrivateKey(0n)); + }); + const VALID_G1 = new bls.G1.Point( + bls.Fp.create( + 3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n + ), + bls.Fp.create( + 1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n ) ); - const y = bls.Fp.create( - BigInt( - '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + const VALID_G1_2 = new bls.G1.Point( + bls.Fp.create( + 1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n + ), + bls.Fp.create( + 2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n ) ); - const g1_ = bls.G1.Point.fromHex( - '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ); - - deepStrictEqual(g1_.x, x); - deepStrictEqual(g1_.y, y); -}); - -should('bls12-381/basic/should construct point G1 from its uncompressed form (Hex)', () => { - // Test Zero - const g1 = bls.G1.Point.fromHex(B_192_40); - - deepStrictEqual(g1.x, bls.G1.Point.ZERO.x); - deepStrictEqual(g1.y, bls.G1.Point.ZERO.y); - // Test Non-Zero - const x = bls.Fp.create( - BigInt( - '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' - ) - ); - const y = bls.Fp.create( - BigInt( - '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' + const INVALID_G1 = new bls.G1.Point( + bls.Fp.create( + 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n + ), + bls.Fp.create( + 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n ) ); - const g1_ = bls.G1.Point.fromHex( - '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ); + should('aggregate pubkeys', () => { + const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]); + deepStrictEqual( + agg.x, + 2636337749883017793009944726560363863546595464242083394883491066895536780554574413337005575305023872925406746684807n + ); + deepStrictEqual( + agg.y, + 2200256264293372104833346444532839112556752874984721583125881868863625579979779052307146195064914375388929781136724n + ); + }); - deepStrictEqual(g1_.x, x); - deepStrictEqual(g1_.y, y); -}); + should('not aggregate invalid pubkeys', () => { + throws(() => bls.aggregatePublicKeys([VALID_G1, INVALID_G1])); + }); + // should aggregate signatures -should('bls12-381/basic/should construct point G2 from its uncompressed form (Raw Bytes)', () => { - // Test Zero - const g2 = bls.G2.Point.fromHex(B_384_40); - deepStrictEqual(g2.x, bls.G2.Point.ZERO.x, 'zero(x)'); - deepStrictEqual(g2.y, bls.G2.Point.ZERO.y, 'zero(y)'); - // Test Non-Zero - const x = Fp2.fromBigTuple([ - BigInt( - '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' - ), - BigInt( - '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' - ), - ]); - const y = Fp2.fromBigTuple([ - BigInt( - '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ), - BigInt( - '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' - ), - ]); - - const g2_ = bls.G2.Point.fromHex( - '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ); - - deepStrictEqual(g2_.x, x); - deepStrictEqual(g2_.y, y); -}); - -should('bls12-381/basic/should construct point G2 from its uncompressed form (Hex)', () => { - // Test Zero - const g2 = bls.G2.Point.fromHex(B_384_40); - - deepStrictEqual(g2.x, bls.G2.Point.ZERO.x); - deepStrictEqual(g2.y, bls.G2.Point.ZERO.y); - // Test Non-Zero - const x = Fp2.fromBigTuple([ - BigInt( - '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' - ), - BigInt( - '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' - ), - ]); - const y = Fp2.fromBigTuple([ - BigInt( - '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ), - BigInt( - '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' - ), - ]); - - const g2_ = bls.G2.Point.fromHex( - '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ); - - deepStrictEqual(g2_.x, x); - deepStrictEqual(g2_.y, y); -}); - -should('bls12-381/basic/should get uncompressed form of point G1 (Raw Bytes)', () => { - // Test Zero - deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); - // Test Non-Zero - const x = bls.Fp.create( - BigInt( - '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' - ) - ); - const y = bls.Fp.create( - BigInt( - '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ) - ); - const g1 = new bls.G1.Point(x, y); - deepStrictEqual( - g1.toHex(false), - '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ); -}); - -should('bls12-381/basic/should get uncompressed form of point G1 (Hex)', () => { - // Test Zero - deepStrictEqual(bls.G1.Point.ZERO.toHex(false), B_192_40); - // Test Non-Zero - const x = bls.Fp.create( - BigInt( - '0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb' - ) - ); - const y = bls.Fp.create( - BigInt( - '0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ) - ); - const g1 = new bls.G1.Point(x, y); - deepStrictEqual( - g1.toHex(false), - '17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1' - ); -}); - -should('bls12-381/basic/should get uncompressed form of point G2 (Raw Bytes)', () => { - // Test Zero - deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); - // Test Non-Zero - const x = Fp2.fromBigTuple([ - BigInt( - '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' - ), - BigInt( - '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' - ), - ]); - const y = Fp2.fromBigTuple([ - BigInt( - '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ), - BigInt( - '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' - ), - ]); - const g2 = new bls.G2.Point(x, y); - deepStrictEqual( - g2.toHex(false), - '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ); -}); - -should('bls12-381/basic/should get uncompressed form of point G2 (Hex)', () => { - // Test Zero - deepStrictEqual(bls.G2.Point.ZERO.toHex(false), B_384_40); - - // Test Non-Zero - const x = Fp2.fromBigTuple([ - BigInt( - '0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8' - ), - BigInt( - '0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e' - ), - ]); - const y = Fp2.fromBigTuple([ - BigInt( - '0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ), - BigInt( - '0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be' - ), - ]); - const g2 = new bls.G2.Point(x, y); - deepStrictEqual( - g2.toHex(false), - '13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801' - ); -}); - -should('bls12-381/basic/should compress and decompress G1 points', async () => { - const priv = bls.G1.Point.fromPrivateKey(42n); - const publicKey = priv.toHex(true); - const decomp = bls.G1.Point.fromHex(publicKey); - deepStrictEqual(publicKey, decomp.toHex(true)); -}); -should('bls12-381/basic/should not compress and decompress zero G1 point', () => { - throws(() => bls.G1.Point.fromPrivateKey(0n)); -}); -should('bls12-381/basic/should compress and decompress G2 points', () => { - const priv = bls.G2.Point.fromPrivateKey(42n); - const publicKey = priv.toHex(true); - const decomp = bls.G2.Point.fromHex(publicKey); - deepStrictEqual(publicKey, decomp.toHex(true)); -}); -should('bls12-381/basic/should not compress and decompress zero G2 point', () => { - throws(() => bls.G2.Point.fromPrivateKey(0n)); -}); -const VALID_G1 = new bls.G1.Point( - bls.Fp.create( - 3609742242174788176010452839163620388872641749536604986743596621604118973777515189035770461528205168143692110933639n - ), - bls.Fp.create( - 1619277690257184054444116778047375363103842303863153349133480657158810226683757397206929105479676799650932070320089n - ) -); -const VALID_G1_2 = new bls.G1.Point( - bls.Fp.create( - 1206972466279728255044019580914616126536509750250979180256809997983196363639429409634110400978470384566664128085207n - ), - bls.Fp.create( - 2991142246317096160788653339959532007292638191110818490939476869616372888657136539642598243964263069435065725313423n - ) -); - -const INVALID_G1 = new bls.G1.Point( - bls.Fp.create( - 499001545268060011619089734015590154568173930614466321429631711131511181286230338880376679848890024401335766847607n - ), - bls.Fp.create( - 3934582309586258715640230772291917282844636728991757779640464479794033391537662970190753981664259511166946374555673n - ) -); - -should('bls12-381/basic/should aggregate pubkeys', () => { - const agg = bls.aggregatePublicKeys([VALID_G1, VALID_G1_2]); - deepStrictEqual( - agg.x, - 2636337749883017793009944726560363863546595464242083394883491066895536780554574413337005575305023872925406746684807n - ); - deepStrictEqual( - agg.y, - 2200256264293372104833346444532839112556752874984721583125881868863625579979779052307146195064914375388929781136724n - ); -}); - -should('bls12-381/basic/should not aggregate invalid pubkeys', () => { - throws(() => bls.aggregatePublicKeys([VALID_G1, INVALID_G1])); -}); -// should aggregate signatures - -should(`should produce correct signatures (${G2_VECTORS.length} vectors)`, async () => { - for (let vector of G2_VECTORS) { - const [priv, msg, expected] = vector; - const sig = await bls.sign(msg, priv); - deepStrictEqual(bls.utils.bytesToHex(sig), expected); - } -}); -should(`should produce correct scalars (${SCALAR_VECTORS.length} vectors)`, async () => { - const options = { - p: bls.CURVE.r, - m: 1, - expand: false, - }; - for (let vector of SCALAR_VECTORS) { - const [okmAscii, expectedHex] = vector; - const expected = BigInt('0x' + expectedHex); - const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); - const scalars = await bls.utils.hashToField(okm, 1, options); - deepStrictEqual(scalars[0][0], expected); - } -}); -should('bls12-381/basic/should verify signed message', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const sig = await bls.sign(msg, priv); - const pub = bls.getPublicKey(priv); - const res = await bls.verify(sig, msg, pub); - deepStrictEqual(res, true); - } -}); -should('bls12-381/basic/should not verify signature with wrong message', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const invMsg = G2_VECTORS[i + 1][1]; - const sig = await bls.sign(msg, priv); - const pub = bls.getPublicKey(priv); - const res = await bls.verify(sig, invMsg, pub); - deepStrictEqual(res, false); - } -}); -should('bls12-381/basic/should not verify signature with wrong key', async () => { - for (let i = 0; i < NUM_RUNS; i++) { - const [priv, msg] = G2_VECTORS[i]; - const sig = await bls.sign(msg, priv); - const invPriv = G2_VECTORS[i + 1][1].padStart(64, '0'); - const invPub = bls.getPublicKey(invPriv); - const res = await bls.verify(sig, msg, invPub); - deepStrictEqual(res, false); - } -}); -should('bls12-381/basic/should verify multi-signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => { - privateKeys = privateKeys.slice(0, messages.length); - messages = messages.slice(0, privateKeys.length); - const publicKey = privateKeys.map(getPubKey); - const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - deepStrictEqual(await bls.verifyBatch(aggregatedSignature, messages, publicKey), true); - }) - ); -}); -should('bls12-381/basic/should batch verify multi-signatures', async () => { - await fc.assert( - fc.asyncProperty( - FC_MSG_5, - FC_MSG_5, - FC_BIGINT_5, - async (messages, wrongMessages, privateKeys) => { + should(`produce correct signatures (${G2_VECTORS.length} vectors)`, async () => { + for (let vector of G2_VECTORS) { + const [priv, msg, expected] = vector; + const sig = await bls.sign(msg, priv); + deepStrictEqual(bls.utils.bytesToHex(sig), expected); + } + }); + should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, async () => { + const options = { + p: bls.CURVE.r, + m: 1, + expand: false, + }; + for (let vector of SCALAR_VECTORS) { + const [okmAscii, expectedHex] = vector; + const expected = BigInt('0x' + expectedHex); + const okm = new Uint8Array(okmAscii.split('').map((c) => c.charCodeAt(0))); + const scalars = await bls.utils.hashToField(okm, 1, options); + deepStrictEqual(scalars[0][0], expected); + } + }); + should('verify signed message', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const sig = await bls.sign(msg, priv); + const pub = bls.getPublicKey(priv); + const res = await bls.verify(sig, msg, pub); + deepStrictEqual(res, true); + } + }); + should('not verify signature with wrong message', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const invMsg = G2_VECTORS[i + 1][1]; + const sig = await bls.sign(msg, priv); + const pub = bls.getPublicKey(priv); + const res = await bls.verify(sig, invMsg, pub); + deepStrictEqual(res, false); + } + }); + should('not verify signature with wrong key', async () => { + for (let i = 0; i < NUM_RUNS; i++) { + const [priv, msg] = G2_VECTORS[i]; + const sig = await bls.sign(msg, priv); + const invPriv = G2_VECTORS[i + 1][1].padStart(64, '0'); + const invPub = bls.getPublicKey(invPriv); + const res = await bls.verify(sig, msg, invPub); + deepStrictEqual(res, false); + } + }); + should('verify multi-signature', async () => { + await fc.assert( + fc.asyncProperty(FC_MSG_5, FC_BIGINT_5, async (messages, privateKeys) => { privateKeys = privateKeys.slice(0, messages.length); messages = messages.slice(0, privateKeys.length); - wrongMessages = messages.map((a, i) => - typeof wrongMessages[i] === 'undefined' ? a : wrongMessages[i] + const publicKey = privateKeys.map(getPubKey); + const signatures = await Promise.all( + messages.map((message, i) => bls.sign(message, privateKeys[i])) ); + const aggregatedSignature = await bls.aggregateSignatures(signatures); + deepStrictEqual(await bls.verifyBatch(aggregatedSignature, messages, publicKey), true); + }) + ); + }); + should('batch verify multi-signatures', async () => { + await fc.assert( + fc.asyncProperty( + FC_MSG_5, + FC_MSG_5, + FC_BIGINT_5, + async (messages, wrongMessages, privateKeys) => { + privateKeys = privateKeys.slice(0, messages.length); + messages = messages.slice(0, privateKeys.length); + wrongMessages = messages.map((a, i) => + typeof wrongMessages[i] === 'undefined' ? a : wrongMessages[i] + ); + const publicKey = await Promise.all(privateKeys.map(getPubKey)); + const signatures = await Promise.all( + messages.map((message, i) => bls.sign(message, privateKeys[i])) + ); + const aggregatedSignature = await bls.aggregateSignatures(signatures); + deepStrictEqual( + await bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey), + messages.every((m, i) => m === wrongMessages[i]) + ); + } + ) + ); + }); + should('not verify multi-signature with wrong public keys', async () => { + await fc.assert( + fc.asyncProperty( + FC_MSG_5, + FC_BIGINT_5, + FC_BIGINT_5, + async (messages, privateKeys, wrongPrivateKeys) => { + privateKeys = privateKeys.slice(0, messages.length); + wrongPrivateKeys = privateKeys.map((a, i) => + wrongPrivateKeys[i] !== undefined ? wrongPrivateKeys[i] : a + ); + messages = messages.slice(0, privateKeys.length); + const wrongPublicKeys = await Promise.all(wrongPrivateKeys.map(getPubKey)); + const signatures = await Promise.all( + messages.map((message, i) => bls.sign(message, privateKeys[i])) + ); + const aggregatedSignature = await bls.aggregateSignatures(signatures); + deepStrictEqual( + await bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys), + wrongPrivateKeys.every((p, i) => p === privateKeys[i]) + ); + } + ) + ); + }); + should('verify multi-signature as simple signature', async () => { + await fc.assert( + fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => { const publicKey = await Promise.all(privateKeys.map(getPubKey)); const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) + privateKeys.map((privateKey) => bls.sign(message, privateKey)) ); const aggregatedSignature = await bls.aggregateSignatures(signatures); - deepStrictEqual( - await bls.verifyBatch(aggregatedSignature, wrongMessages, publicKey), - messages.every((m, i) => m === wrongMessages[i]) - ); - } - ) - ); -}); -should('bls12-381/basic/should not verify multi-signature with wrong public keys', async () => { - await fc.assert( - fc.asyncProperty( - FC_MSG_5, - FC_BIGINT_5, - FC_BIGINT_5, - async (messages, privateKeys, wrongPrivateKeys) => { - privateKeys = privateKeys.slice(0, messages.length); - wrongPrivateKeys = privateKeys.map((a, i) => - wrongPrivateKeys[i] !== undefined ? wrongPrivateKeys[i] : a - ); - messages = messages.slice(0, privateKeys.length); - const wrongPublicKeys = await Promise.all(wrongPrivateKeys.map(getPubKey)); + const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); + deepStrictEqual(await bls.verify(aggregatedSignature, message, aggregatedPublicKey), true); + }) + ); + }); + should('not verify wrong multi-signature as simple signature', async () => { + await fc.assert( + fc.asyncProperty(FC_MSG, FC_MSG, FC_BIGINT_5, async (message, wrongMessage, privateKeys) => { + const publicKey = await Promise.all(privateKeys.map(getPubKey)); const signatures = await Promise.all( - messages.map((message, i) => bls.sign(message, privateKeys[i])) + privateKeys.map((privateKey) => bls.sign(message, privateKey)) ); const aggregatedSignature = await bls.aggregateSignatures(signatures); + const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); deepStrictEqual( - await bls.verifyBatch(aggregatedSignature, messages, wrongPublicKeys), - wrongPrivateKeys.every((p, i) => p === privateKeys[i]) + await bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey), + message === wrongMessage ); - } - ) - ); -}); -should('bls12-381/basic/should verify multi-signature as simple signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG, FC_BIGINT_5, async (message, privateKeys) => { - const publicKey = await Promise.all(privateKeys.map(getPubKey)); - const signatures = await Promise.all( - privateKeys.map((privateKey) => bls.sign(message, privateKey)) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); - deepStrictEqual(await bls.verify(aggregatedSignature, message, aggregatedPublicKey), true); - }) - ); -}); -should('bls12-381/basic/should not verify wrong multi-signature as simple signature', async () => { - await fc.assert( - fc.asyncProperty(FC_MSG, FC_MSG, FC_BIGINT_5, async (message, wrongMessage, privateKeys) => { - const publicKey = await Promise.all(privateKeys.map(getPubKey)); - const signatures = await Promise.all( - privateKeys.map((privateKey) => bls.sign(message, privateKey)) - ); - const aggregatedSignature = await bls.aggregateSignatures(signatures); - const aggregatedPublicKey = await bls.aggregatePublicKeys(publicKey); - deepStrictEqual( - await bls.verify(aggregatedSignature, wrongMessage, aggregatedPublicKey), - message === wrongMessage - ); - }) - ); + }) + ); + }); }); // Pairing -{ +describe('pairing', () => { const { pairing, Fp12 } = bls; const G1 = bls.G1.Point.BASE; const G2 = bls.G2.Point.BASE; - should('pairing/creates negative G1 pairing', () => { + should('creates negative G1 pairing', () => { const p1 = pairing(G1, G2); const p2 = pairing(G1.negate(), G2); deepStrictEqual(Fp12.mul(p1, p2), Fp12.ONE); }); - should('pairing/creates negative G2 pairing', () => { + should('creates negative G2 pairing', () => { const p2 = pairing(G1.negate(), G2); const p3 = pairing(G1, G2.negate()); deepStrictEqual(p2, p3); }); - should('pairing/creates proper pairing output order', () => { + should('creates proper pairing output order', () => { const p1 = pairing(G1, G2); const p2 = Fp12.pow(p1, CURVE_ORDER); deepStrictEqual(p2, Fp12.ONE); }); - should('pairing/G1 billinearity', () => { + should('G1 billinearity', () => { const p1 = pairing(G1, G2); const p2 = pairing(G1.multiply(2n), G2); deepStrictEqual(Fp12.mul(p1, p1), p2); }); - should('pairing/should not degenerate', () => { + should('should not degenerate', () => { const p1 = pairing(G1, G2); const p2 = pairing(G1.multiply(2n), G2); const p3 = pairing(G1, G2.negate()); @@ -1015,17 +1016,17 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat notDeepStrictEqual(p1, p3); notDeepStrictEqual(p2, p3); }); - should('pairing/G2 billinearity', () => { + should('G2 billinearity', () => { const p1 = pairing(G1, G2); const p2 = pairing(G1, G2.multiply(2n)); deepStrictEqual(Fp12.mul(p1, p1), p2); }); - should('pairing/proper pairing composite check', () => { + should('proper pairing composite check', () => { const p1 = pairing(G1.multiply(37n), G2.multiply(27n)); const p2 = pairing(G1.multiply(999n), G2); deepStrictEqual(p1, p2); }); - should('pairing/vectors from https://github.com/zkcrypto/pairing', () => { + should('vectors from https://github.com/zkcrypto/pairing', () => { const p1 = pairing(G1, G2); deepStrictEqual( p1, @@ -1045,7 +1046,7 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat ]) ); }); - should('pairing/finalExponentiate is correct', () => { + should('finalExponentiate is correct', () => { const p1 = Fp12.fromBigTwelve([ 690392658038414015999440694435086329841032295415825549843130960252222448232974816207293269712691075396080336239827n, 1673244384695948045466836192250093912021245353707563547917201356526057153141766171738038843400145227470982267854187n, @@ -1078,9 +1079,9 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat ]) ); }); -} +}); // hashToCurve -{ +describe('hash-to-curve', () => { const DST = 'QUUX-V01-CS02-with-expander-SHA256-128'; const VECTORS = [ { @@ -1824,9 +1825,9 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat deepStrictEqual(p.toHex(), t.expected); }); } -} +}); // Deterministic -{ +describe('bls12-381 deterministic', () => { // NOTE: Killic returns all items in reversed order, which looks strange: // instead of `Fp2(${this.c0} + ${this.c1}×i)`; it returns `Fp2(${this.c0}×i + ${this.c1})`; const killicHex = (lst) => @@ -1934,7 +1935,7 @@ should('bls12-381/basic/should not verify wrong multi-signature as simple signat } } }); -} +}); // ESM is broken. import url from 'url'; diff --git a/test/ed25519.test.js b/test/ed25519.test.js index 42ae307..f41fc72 100644 --- a/test/ed25519.test.js +++ b/test/ed25519.test.js @@ -1,7 +1,14 @@ -import { deepStrictEqual, throws } from 'assert'; -import { should } from 'micro-should'; +import { deepEqual, deepStrictEqual, strictEqual, throws } from 'assert'; +import { describe, should } from 'micro-should'; import * as fc from 'fast-check'; -import { ed25519, ed25519ctx, ed25519ph, x25519, RistrettoPoint } from '../lib/esm/ed25519.js'; +import { + ed25519, + ed25519ctx, + ed25519ph, + x25519, + RistrettoPoint, + ED25519_TORSION_SUBGROUP, +} from '../lib/esm/ed25519.js'; import { readFileSync } from 'fs'; import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' }; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; @@ -10,645 +17,660 @@ import { sha512 } from '@noble/hashes/sha512'; import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' }; import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' }; -const ed = ed25519; -const hex = bytesToHex; +describe('ed25519', () => { + const ed = ed25519; + const hex = bytesToHex; -function to32Bytes(numOrStr) { - let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16); - return hexToBytes(hex.padStart(64, '0')); -} - -function utf8ToBytes(str) { - if (typeof str !== 'string') { - throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`); + function to32Bytes(numOrStr) { + let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16); + return hexToBytes(hex.padStart(64, '0')); } - return new TextEncoder().encode(str); -} -ed.utils.precompute(8); + function utf8ToBytes(str) { + if (typeof str !== 'string') { + throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`); + } + return new TextEncoder().encode(str); + } -should('ed25519/should not accept >32byte private keys', () => { - const invalidPriv = - 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; - throws(() => ed.getPublicKey(invalidPriv)); -}); -should('ed25519/should verify recent signature', () => { - fc.assert( - fc.property( - fc.hexaString({ minLength: 2, maxLength: 32 }), - fc.bigInt(2n, ed.CURVE.n), - (message, privateKey) => { - const publicKey = ed.getPublicKey(to32Bytes(privateKey)); - const signature = ed.sign(to32Bytes(message), to32Bytes(privateKey)); - deepStrictEqual(publicKey.length, 32); - deepStrictEqual(signature.length, 64); - deepStrictEqual(ed.verify(signature, to32Bytes(message), publicKey), true); + ed.utils.precompute(8); + + should('not accept >32byte private keys', () => { + const invalidPriv = + 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; + throws(() => ed.getPublicKey(invalidPriv)); + }); + should('verify recent signature', () => { + fc.assert( + fc.property( + fc.hexaString({ minLength: 2, maxLength: 32 }), + fc.bigInt(2n, ed.CURVE.n), + (message, privateKey) => { + const publicKey = ed.getPublicKey(to32Bytes(privateKey)); + const signature = ed.sign(to32Bytes(message), to32Bytes(privateKey)); + deepStrictEqual(publicKey.length, 32); + deepStrictEqual(signature.length, 64); + deepStrictEqual(ed.verify(signature, to32Bytes(message), publicKey), true); + } + ), + { numRuns: 5 } + ); + }); + 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 })), + fc.bigInt(1n, ed.CURVE.n), + (bytes, wrongBytes, privateKey) => { + const privKey = to32Bytes(privateKey); + const message = new Uint8Array(bytes); + const wrongMessage = new Uint8Array(wrongBytes); + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(message, privKey); + deepStrictEqual( + ed.verify(signature, wrongMessage, publicKey), + bytes.toString() === wrongBytes.toString() + ); + } + ), + { numRuns: 5 } + ); + }); + const privKey = to32Bytes('a665a45920422f9d417e4867ef'); + const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); + const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); + describe('basic methods', () => { + should('sign and verify', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), true); + }); + should('not verify signature with wrong public key', () => { + const publicKey = ed.getPublicKey(12); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), false); + }); + should('not verify signature with wrong hash', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); + }); + }); + describe('sync methods', () => { + should('sign and verify', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), true); + }); + should('not verify signature with wrong public key', () => { + const publicKey = ed.getPublicKey(12); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), false); + }); + should('not verify signature with wrong hash', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); + }); + }); + // https://xmr.llcoins.net/addresstests.html + should( + 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1', + () => { + const publicKey = + ed.Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n); + deepStrictEqual( + publicKey.toHex(), + '0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d' + ); + } + ); + should( + 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 2', + () => { + const publicKey = + ed.Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n); + deepStrictEqual( + publicKey.toHex(), + 'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349' + ); + } + ); + should( + 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 3', + () => { + const publicKey = + ed.Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n); + deepStrictEqual( + publicKey.toHex(), + 'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c' + ); + } + ); + should( + 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 4', + () => { + const publicKey = + ed.Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en); + deepStrictEqual( + publicKey.toHex(), + 'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d' + ); + } + ); + should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => { + for (const num of [0n, 0, -1n, -1, 1.1]) { + throws(() => ed.Point.BASE.multiply(num)); + } + }); + + // https://ed25519.cr.yp.to/python/sign.py + // https://ed25519.cr.yp.to/python/sign.input + const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8'); + const vectors = data + .trim() + .split('\n') + .map((line) => line.split(':')); + should('ed25519 official vectors/should match 1024 official vectors', () => { + for (let i = 0; i < vectors.length; i++) { + const vector = vectors[i]; + // Extract. + const priv = vector[0].slice(0, 64); + const expectedPub = vector[1]; + const msg = vector[2]; + const expectedSignature = vector[3].slice(0, 128); + + // Calculate + const pub = ed.getPublicKey(to32Bytes(priv)); + deepStrictEqual(hex(pub), expectedPub); + deepStrictEqual(pub, ed.Point.fromHex(pub).toRawBytes()); + + const signature = hex(ed.sign(msg, priv)); + // console.log('vector', i); + // expect(pub).toBe(expectedPub); + deepStrictEqual(signature, expectedSignature); + } + }); + + // https://tools.ietf.org/html/rfc8032#section-7 + should('rfc8032 vectors/should create right signature for 0x9d and empty string', () => { + const privateKey = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'; + const publicKey = ed.getPublicKey(privateKey); + const message = ''; + const signature = ed.sign(message, privateKey); + deepStrictEqual( + hex(publicKey), + 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a' + ); + deepStrictEqual( + hex(signature), + 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b' + ); + }); + should('rfc8032 vectors/should create right signature for 0x4c and 72', () => { + const privateKey = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb'; + const publicKey = ed.getPublicKey(privateKey); + const message = '72'; + const signature = ed.sign(message, privateKey); + deepStrictEqual( + hex(publicKey), + '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' + ); + deepStrictEqual( + hex(signature), + '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00' + ); + }); + should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => { + const privateKey = '002fdd1f7641793ab064bb7aa848f762e7ec6e332ffc26eeacda141ae33b1783'; + const publicKey = ed.getPublicKey(privateKey); + const message = + '5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b'; + const signature = ed.sign(message, privateKey); + deepStrictEqual( + hex(publicKey), + '77d1d8ebacd13f4e2f8a40e28c4a63bc9ce3bfb69716334bcb28a33eb134086c' + ); + deepStrictEqual( + hex(signature), + '0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c' + ); + }); + should('rfc8032 vectors/should create right signature for 0xf5 and long msg', () => { + const privateKey = 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5'; + const publicKey = ed.getPublicKey(privateKey); + const message = + '08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0'; + const signature = ed.sign(message, privateKey); + deepStrictEqual( + hex(publicKey), + '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e' + ); + deepStrictEqual( + hex(signature), + '0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03' + ); + }); + + // const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn; + // const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103])); + // prettier-ignore + // const MESSAGE = new Uint8Array([ + // 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58, + // ]); + // const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103])); + // prettier-ignore + // const WRONG_MESSAGE = new Uint8Array([ + // 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28, + // ]); + // // it("should verify just signed message", async () => { + // // await fc.assert(fc.asyncProperty( + // // fc.hexa(), + // // fc.bigInt(2n, ristretto25519.PRIME_ORDER), + // // async (message, privateKey) => { + // // const publicKey = await ristretto25519.getPublicKey(privateKey); + // // const signature = await ristretto25519.sign(message, privateKey); + // // expect(publicKey.length).toBe(32); + // // expect(signature.length).toBe(64); + // // expect(await ristretto25519.verify(signature, message, publicKey)).toBe(true); + // // }), + // // { numRuns: 1 } + // // ); + // // }); + // // it("should not verify sign with wrong message", async () => { + // // await fc.assert(fc.asyncProperty( + // // fc.array(fc.integer(0x00, 0xff)), + // // fc.array(fc.integer(0x00, 0xff)), + // // fc.bigInt(2n, ristretto25519.PRIME_ORDER), + // // async (bytes, wrongBytes, privateKey) => { + // // const message = new Uint8Array(bytes); + // // const wrongMessage = new Uint8Array(wrongBytes); + // // const publicKey = await ristretto25519.getPublicKey(privateKey); + // // const signature = await ristretto25519.sign(message, privateKey); + // // expect(await ristretto25519.verify(signature, wrongMessage, publicKey)).toBe( + // // bytes.toString() === wrongBytes.toString() + // // ); + // // }), + // // { numRuns: 1 } + // // ); + // // }); + // // it("should sign and verify", async () => { + // // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY); + // // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); + // // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(true); + // // }); + // // it("should not verify signature with wrong public key", async () => { + // // const publicKey = await ristretto25519.getPublicKey(12); + // // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); + // // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(false); + // // }); + // // it("should not verify signature with wrong hash", async () => { + // // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY); + // // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); + // // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false); + // // }); + should('ristretto255/should follow the byte encodings of small multiples', () => { + const encodingsOfSmallMultiples = [ + // This is the identity point + '0000000000000000000000000000000000000000000000000000000000000000', + // This is the basepoint + 'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76', + // These are small multiples of the basepoint + '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919', + '94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259', + 'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57', + 'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e', + 'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403', + '44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d', + '903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c', + '02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031', + '20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f', + 'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42', + 'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460', + 'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f', + '46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e', + 'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e', + ]; + let B = RistrettoPoint.BASE; + let P = RistrettoPoint.ZERO; + for (const encoded of encodingsOfSmallMultiples) { + deepStrictEqual(P.toHex(), encoded); + deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded); + P = P.add(B); + } + }); + should('ristretto255/should not convert bad bytes encoding', () => { + const badEncodings = [ + // These are all bad because they're non-canonical field encodings. + '00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', + 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', + 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', + // These are all bad because they're negative field elements. + '0100000000000000000000000000000000000000000000000000000000000000', + '01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', + 'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20', + 'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562', + 'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78', + '47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24', + 'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72', + '87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309', + // These are all bad because they give a nonsquare x². + '26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371', + '4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f', + 'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b', + 'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042', + '2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08', + 'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22', + '8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731', + '2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b', + // These are all bad because they give a negative xy value. + '3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e', + 'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220', + 'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e', + '8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32', + '32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b', + '227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165', + '5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e', + '445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b', + // This is s = -1, which causes y = 0. + 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', + ]; + for (const badBytes of badEncodings) { + const b = hexToBytes(badBytes); + throws(() => RistrettoPoint.fromHex(b), badBytes); + } + }); + should('ristretto255/should create right points from uniform hash', async () => { + const labels = [ + 'Ristretto is traditionally a short shot of espresso coffee', + 'made with the normal amount of ground coffee but extracted with', + 'about half the amount of water in the same amount of time', + 'by using a finer grind.', + 'This produces a concentrated shot of coffee per volume.', + 'Just pulling a normal shot short will produce a weaker shot', + 'and is not a Ristretto as some believe.', + ]; + const encodedHashToPoints = [ + '3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46', + 'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b', + '006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826', + 'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a', + 'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179', + 'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628', + '80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065', + ]; + + for (let i = 0; i < labels.length; i++) { + const hash = sha512(utf8ToBytes(labels[i])); + const point = RistrettoPoint.hashToCurve(hash); + deepStrictEqual(point.toHex(), encodedHashToPoints[i]); + } + }); + + should('input immutability: sign/verify are immutable', () => { + const privateKey = ed.utils.randomPrivateKey(); + const publicKey = ed.getPublicKey(privateKey); + + for (let i = 0; i < 100; i++) { + let payload = randomBytes(100); + let signature = ed.sign(payload, privateKey); + if (!ed.verify(signature, payload, publicKey)) { + throw new Error('Signature verification failed'); } - ), - { numRuns: 5 } - ); -}); -should('ed25519/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 })), - fc.bigInt(1n, ed.CURVE.n), - (bytes, wrongBytes, privateKey) => { - const privKey = to32Bytes(privateKey); - const message = new Uint8Array(bytes); - const wrongMessage = new Uint8Array(wrongBytes); - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(message, privKey); - deepStrictEqual( - ed.verify(signature, wrongMessage, publicKey), - bytes.toString() === wrongBytes.toString() - ); + const signatureCopy = Buffer.alloc(signature.byteLength); + signatureCopy.set(signature, 0); // <-- breaks + payload = payload.slice(); + signature = signature.slice(); + + if (!ed.verify(signatureCopy, payload, publicKey)) + throw new Error('Copied signature verification failed'); + } + }); + + // https://zips.z.cash/zip-0215 + // Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7 + should('ZIP-215 compliance tests/should pass all of them', () => { + const str = utf8ToBytes('Zcash'); + for (let v of zip215) { + let noble = false; + try { + noble = ed.verify(v.sig_bytes, str, v.vk_bytes); + } catch (e) { + noble = false; } - ), - { numRuns: 5 } - ); -}); -const privKey = to32Bytes('a665a45920422f9d417e4867ef'); -const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); -const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); -should('ed25519/basic methods/should sign and verify', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), true); -}); -should('ed25519/basic methods/should not verify signature with wrong public key', () => { - const publicKey = ed.getPublicKey(12); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), false); -}); -should('ed25519/basic methods/should not verify signature with wrong hash', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); -}); - -should('ed25519/sync methods/should sign and verify', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), true); -}); -should('ed25519/sync methods/should not verify signature with wrong public key', () => { - const publicKey = ed.getPublicKey(12); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), false); -}); -should('ed25519/sync methods/should not verify signature with wrong hash', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); -}); - -// https://xmr.llcoins.net/addresstests.html -should( - 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1', - () => { - const publicKey = - ed.Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n); - deepStrictEqual( - publicKey.toHex(), - '0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d' - ); - } -); -should( - 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 2', - () => { - const publicKey = - ed.Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n); - deepStrictEqual( - publicKey.toHex(), - 'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349' - ); - } -); -should( - 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 3', - () => { - const publicKey = - ed.Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n); - deepStrictEqual( - publicKey.toHex(), - 'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c' - ); - } -); -should( - 'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 4', - () => { - const publicKey = - ed.Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en); - deepStrictEqual( - publicKey.toHex(), - 'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d' - ); - } -); -should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => { - for (const num of [0n, 0, -1n, -1, 1.1]) { - throws(() => ed.Point.BASE.multiply(num)); - } -}); - -// https://ed25519.cr.yp.to/python/sign.py -// https://ed25519.cr.yp.to/python/sign.input -const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8'); -const vectors = data - .trim() - .split('\n') - .map((line) => line.split(':')); -should('ed25519 official vectors/should match 1024 official vectors', () => { - for (let i = 0; i < vectors.length; i++) { - const vector = vectors[i]; - // Extract. - const priv = vector[0].slice(0, 64); - const expectedPub = vector[1]; - const msg = vector[2]; - const expectedSignature = vector[3].slice(0, 128); - - // Calculate - const pub = ed.getPublicKey(to32Bytes(priv)); - deepStrictEqual(hex(pub), expectedPub); - deepStrictEqual(pub, ed.Point.fromHex(pub).toRawBytes()); - - const signature = hex(ed.sign(msg, priv)); - // console.log('vector', i); - // expect(pub).toBe(expectedPub); - deepStrictEqual(signature, expectedSignature); - } -}); - -// https://tools.ietf.org/html/rfc8032#section-7 -should('rfc8032 vectors/should create right signature for 0x9d and empty string', () => { - const privateKey = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'; - const publicKey = ed.getPublicKey(privateKey); - const message = ''; - const signature = ed.sign(message, privateKey); - deepStrictEqual( - hex(publicKey), - 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a' - ); - deepStrictEqual( - hex(signature), - 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b' - ); -}); -should('rfc8032 vectors/should create right signature for 0x4c and 72', () => { - const privateKey = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb'; - const publicKey = ed.getPublicKey(privateKey); - const message = '72'; - const signature = ed.sign(message, privateKey); - deepStrictEqual( - hex(publicKey), - '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' - ); - deepStrictEqual( - hex(signature), - '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00' - ); -}); -should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => { - const privateKey = '002fdd1f7641793ab064bb7aa848f762e7ec6e332ffc26eeacda141ae33b1783'; - const publicKey = ed.getPublicKey(privateKey); - const message = - '5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b'; - const signature = ed.sign(message, privateKey); - deepStrictEqual( - hex(publicKey), - '77d1d8ebacd13f4e2f8a40e28c4a63bc9ce3bfb69716334bcb28a33eb134086c' - ); - deepStrictEqual( - hex(signature), - '0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c' - ); -}); -should('rfc8032 vectors/should create right signature for 0xf5 and long msg', () => { - const privateKey = 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5'; - const publicKey = ed.getPublicKey(privateKey); - const message = - '08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0'; - const signature = ed.sign(message, privateKey); - deepStrictEqual( - hex(publicKey), - '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e' - ); - deepStrictEqual( - hex(signature), - '0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03' - ); -}); - -// const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn; -// const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103])); -// prettier-ignore -// const MESSAGE = new Uint8Array([ -// 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58, -// ]); -// const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103])); -// prettier-ignore -// const WRONG_MESSAGE = new Uint8Array([ -// 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28, -// ]); -// // it("should verify just signed message", async () => { -// // await fc.assert(fc.asyncProperty( -// // fc.hexa(), -// // fc.bigInt(2n, ristretto25519.PRIME_ORDER), -// // async (message, privateKey) => { -// // const publicKey = await ristretto25519.getPublicKey(privateKey); -// // const signature = await ristretto25519.sign(message, privateKey); -// // expect(publicKey.length).toBe(32); -// // expect(signature.length).toBe(64); -// // expect(await ristretto25519.verify(signature, message, publicKey)).toBe(true); -// // }), -// // { numRuns: 1 } -// // ); -// // }); -// // it("should not verify sign with wrong message", async () => { -// // await fc.assert(fc.asyncProperty( -// // fc.array(fc.integer(0x00, 0xff)), -// // fc.array(fc.integer(0x00, 0xff)), -// // fc.bigInt(2n, ristretto25519.PRIME_ORDER), -// // async (bytes, wrongBytes, privateKey) => { -// // const message = new Uint8Array(bytes); -// // const wrongMessage = new Uint8Array(wrongBytes); -// // const publicKey = await ristretto25519.getPublicKey(privateKey); -// // const signature = await ristretto25519.sign(message, privateKey); -// // expect(await ristretto25519.verify(signature, wrongMessage, publicKey)).toBe( -// // bytes.toString() === wrongBytes.toString() -// // ); -// // }), -// // { numRuns: 1 } -// // ); -// // }); -// // it("should sign and verify", async () => { -// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY); -// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); -// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(true); -// // }); -// // it("should not verify signature with wrong public key", async () => { -// // const publicKey = await ristretto25519.getPublicKey(12); -// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); -// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(false); -// // }); -// // it("should not verify signature with wrong hash", async () => { -// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY); -// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY); -// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false); -// // }); -should('ristretto255/should follow the byte encodings of small multiples', () => { - const encodingsOfSmallMultiples = [ - // This is the identity point - '0000000000000000000000000000000000000000000000000000000000000000', - // This is the basepoint - 'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76', - // These are small multiples of the basepoint - '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919', - '94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259', - 'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57', - 'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e', - 'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403', - '44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d', - '903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c', - '02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031', - '20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f', - 'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42', - 'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460', - 'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f', - '46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e', - 'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e', - ]; - let B = RistrettoPoint.BASE; - let P = RistrettoPoint.ZERO; - for (const encoded of encodingsOfSmallMultiples) { - deepStrictEqual(P.toHex(), encoded); - deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded); - P = P.add(B); - } -}); -should('ristretto255/should not convert bad bytes encoding', () => { - const badEncodings = [ - // These are all bad because they're non-canonical field encodings. - '00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', - 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', - 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', - // These are all bad because they're negative field elements. - '0100000000000000000000000000000000000000000000000000000000000000', - '01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', - 'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20', - 'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562', - 'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78', - '47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24', - 'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72', - '87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309', - // These are all bad because they give a nonsquare x². - '26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371', - '4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f', - 'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b', - 'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042', - '2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08', - 'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22', - '8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731', - '2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b', - // These are all bad because they give a negative xy value. - '3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e', - 'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220', - 'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e', - '8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32', - '32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b', - '227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165', - '5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e', - '445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b', - // This is s = -1, which causes y = 0. - 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', - ]; - for (const badBytes of badEncodings) { - const b = hexToBytes(badBytes); - throws(() => RistrettoPoint.fromHex(b), badBytes); - } -}); -should('ristretto255/should create right points from uniform hash', async () => { - const labels = [ - 'Ristretto is traditionally a short shot of espresso coffee', - 'made with the normal amount of ground coffee but extracted with', - 'about half the amount of water in the same amount of time', - 'by using a finer grind.', - 'This produces a concentrated shot of coffee per volume.', - 'Just pulling a normal shot short will produce a weaker shot', - 'and is not a Ristretto as some believe.', - ]; - const encodedHashToPoints = [ - '3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46', - 'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b', - '006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826', - 'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a', - 'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179', - 'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628', - '80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065', - ]; - - for (let i = 0; i < labels.length; i++) { - const hash = sha512(utf8ToBytes(labels[i])); - const point = RistrettoPoint.hashToCurve(hash); - deepStrictEqual(point.toHex(), encodedHashToPoints[i]); - } -}); - -should('input immutability: sign/verify are immutable', () => { - const privateKey = ed.utils.randomPrivateKey(); - const publicKey = ed.getPublicKey(privateKey); - - for (let i = 0; i < 100; i++) { - let payload = randomBytes(100); - let signature = ed.sign(payload, privateKey); - if (!ed.verify(signature, payload, publicKey)) { - throw new Error('Signature verification failed'); - } - const signatureCopy = Buffer.alloc(signature.byteLength); - signatureCopy.set(signature, 0); // <-- breaks - payload = payload.slice(); - signature = signature.slice(); - - if (!ed.verify(signatureCopy, payload, publicKey)) - throw new Error('Copied signature verification failed'); - } -}); - -// https://zips.z.cash/zip-0215 -// Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7 -should('ZIP-215 compliance tests/should pass all of them', () => { - const str = utf8ToBytes('Zcash'); - for (let v of zip215) { - let noble = false; - try { - noble = ed.verify(v.sig_bytes, str, v.vk_bytes); - } catch (e) { - noble = false; - } - deepStrictEqual(noble, v.valid_zip215); - } -}); -should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => { - const sig = new ed.Signature(ed.Point.BASE, 1n); - sig.s = ed.CURVE.n + 1n; - throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE)); -}); - -const rfc7748Mul = [ - { - scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', - u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c', - outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552', - }, - { - scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d', - u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493', - outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957', - }, -]; -for (let i = 0; i < rfc7748Mul.length; i++) { - const v = rfc7748Mul[i]; - should(`RFC7748: scalarMult (${i})`, () => { - deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU); - }); -} - -const rfc7748Iter = [ - { scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 }, - { scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 }, - // { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 }, -]; -for (let i = 0; i < rfc7748Iter.length; i++) { - const { scalar, iters } = rfc7748Iter[i]; - should(`RFC7748: scalarMult iteration (${i})`, () => { - let k = x25519.Gu; - for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k]; - deepStrictEqual(hex(k), scalar); - }); -} - -should('RFC7748 getSharedKey', () => { - const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'; - const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a'; - const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'; - const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f'; - const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742'; - deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate))); - deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate))); - deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared); - deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared); -}); - -// should('X25519/getSharedSecret() should be commutative', () => { -// for (let i = 0; i < 512; i++) { -// const asec = ed.utils.randomPrivateKey(); -// const apub = ed.getPublicKey(asec); -// const bsec = ed.utils.randomPrivateKey(); -// const bpub = ed.getPublicKey(bsec); -// try { -// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub)); -// } catch (error) { -// console.error('not commutative', { asec, apub, bsec, bpub }); -// throw error; -// } -// } -// }); - -// should('X25519: should convert base point to montgomery using fromPoint', () => { -// deepStrictEqual( -// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), -// ed.montgomeryCurve.BASE_POINT_U -// ); -// }); - -{ - const group = x25519vectors.testGroups[0]; - should(`Wycheproof/X25519`, () => { - for (let i = 0; i < group.tests.length; i++) { - const v = group.tests[i]; - const comment = `(${i}, ${v.result}) ${v.comment}`; - if (v.result === 'valid' || v.result === 'acceptable') { - try { - const shared = hex(x25519.scalarMult(v.private, v.public)); - deepStrictEqual(shared, v.shared, comment); - } catch (e) { - // We are more strict - if (e.message.includes('Expected valid scalar')) return; - if (e.message.includes('Invalid private or public key received')) return; - throw e; - } - } else if (v.result === 'invalid') { - let failed = false; - try { - x25519.scalarMult(v.private, v.public); - } catch (error) { - failed = true; - } - deepStrictEqual(failed, true, comment); - } else throw new Error('unknown test result'); + deepStrictEqual(noble, v.valid_zip215); } }); -} + should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => { + const sig = new ed.Signature(ed.Point.BASE, 1n); + sig.s = ed.CURVE.n + 1n; + throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE)); + }); -should(`Wycheproof/ED25519`, () => { - for (let g = 0; g < ed25519vectors.testGroups.length; g++) { - const group = ed25519vectors.testGroups[g]; - const key = group.key; - deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`); - for (let i = 0; i < group.tests.length; i++) { - const v = group.tests[i]; - const comment = `(${g}/${i}, ${v.result}): ${v.comment}`; - if (v.result === 'valid' || v.result === 'acceptable') { - deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, comment); - deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, comment); - } else if (v.result === 'invalid') { - let failed = false; - try { - failed = !ed.verify(v.sig, v.msg, key.pk); - } catch (error) { - failed = true; - } - deepStrictEqual(failed, true, comment); - } else throw new Error('unknown test result'); - } + const rfc7748Mul = [ + { + scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', + u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c', + outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552', + }, + { + scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d', + u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493', + outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957', + }, + ]; + for (let i = 0; i < rfc7748Mul.length; i++) { + const v = rfc7748Mul[i]; + should(`RFC7748: scalarMult (${i})`, () => { + deepStrictEqual(hex(x25519.scalarMult(v.scalar, v.u)), v.outputU); + }); } -}); -should('Property test issue #1', () => { - const message = new Uint8Array([12, 12, 12]); - const signature = ed.sign(message, to32Bytes(1n)); - const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n - deepStrictEqual(ed.verify(signature, message, publicKey), true); -}); + const rfc7748Iter = [ + { scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 }, + { scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 }, + // { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 }, + ]; + for (let i = 0; i < rfc7748Iter.length; i++) { + const { scalar, iters } = rfc7748Iter[i]; + should(`RFC7748: scalarMult iteration (${i})`, () => { + let k = x25519.Gu; + for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k]; + deepStrictEqual(hex(k), scalar); + }); + } -const VECTORS_RFC8032_CTX = [ - { - secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', - publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', - message: 'f726936d19c800494e3fdaff20b276a8', - context: '666f6f', - signature: - '55a4cc2f70a54e04288c5f4cd1e45a7b' + - 'b520b36292911876cada7323198dd87a' + - '8b36950b95130022907a7fb7c4e9b2d5' + - 'f6cca685a587b4b21f4b888e4e7edb0d', - }, - { - secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', - publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', - message: 'f726936d19c800494e3fdaff20b276a8', - context: '626172', - signature: - 'fc60d5872fc46b3aa69f8b5b4351d580' + - '8f92bcc044606db097abab6dbcb1aee3' + - '216c48e8b3b66431b5b186d1d28f8ee1' + - '5a5ca2df6668346291c2043d4eb3e90d', - }, - { - secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', - publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', - message: '508e9e6882b979fea900f62adceaca35', - context: '666f6f', - signature: - '8b70c1cc8310e1de20ac53ce28ae6e72' + - '07f33c3295e03bb5c0732a1d20dc6490' + - '8922a8b052cf99b7c4fe107a5abb5b2c' + - '4085ae75890d02df26269d8945f84b0b', - }, - { - secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560', - publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772', - message: 'f726936d19c800494e3fdaff20b276a8', - context: '666f6f', - signature: - '21655b5f1aa965996b3f97b3c849eafb' + - 'a922a0a62992f73b3d1b73106a84ad85' + - 'e9b86a7b6005ea868337ff2d20a7f5fb' + - 'd4cd10b0be49a68da2b2e0dc0ad8960f', - }, -]; - -for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) { - const v = VECTORS_RFC8032_CTX[i]; - should(`RFC8032ctx/${i}`, () => { - deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey); - deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature); - deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true); + should('RFC7748 getSharedKey', () => { + const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'; + const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a'; + const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'; + const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f'; + const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742'; + deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate))); + deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate))); + deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared); + deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared); }); -} -const VECTORS_RFC8032_PH = [ + // should('X25519/getSharedSecret() should be commutative', () => { + // for (let i = 0; i < 512; i++) { + // const asec = ed.utils.randomPrivateKey(); + // const apub = ed.getPublicKey(asec); + // const bsec = ed.utils.randomPrivateKey(); + // const bpub = ed.getPublicKey(bsec); + // try { + // deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub)); + // } catch (error) { + // console.error('not commutative', { asec, apub, bsec, bpub }); + // throw error; + // } + // } + // }); + + // should('X25519: should convert base point to montgomery using fromPoint', () => { + // deepStrictEqual( + // hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), + // ed.montgomeryCurve.BASE_POINT_U + // ); + // }); + { - secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42', - publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf', - message: '616263', - signature: - '98a70222f0b8121aa9d30f813d683f80' + - '9e462b469c7ff87639499bb94e6dae41' + - '31f85042463c2a355a2003d062adf5aa' + - 'a10b8c61e636062aaad11c2a26083406', - }, -]; + const group = x25519vectors.testGroups[0]; + should(`Wycheproof/X25519`, () => { + for (let i = 0; i < group.tests.length; i++) { + const v = group.tests[i]; + const comment = `(${i}, ${v.result}) ${v.comment}`; + if (v.result === 'valid' || v.result === 'acceptable') { + try { + const shared = hex(x25519.scalarMult(v.private, v.public)); + deepStrictEqual(shared, v.shared, comment); + } catch (e) { + // We are more strict + if (e.message.includes('Expected valid scalar')) return; + if (e.message.includes('Invalid private or public key received')) return; + throw e; + } + } else if (v.result === 'invalid') { + let failed = false; + try { + x25519.scalarMult(v.private, v.public); + } catch (error) { + failed = true; + } + deepStrictEqual(failed, true, comment); + } else throw new Error('unknown test result'); + } + }); + } -for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { - const v = VECTORS_RFC8032_PH[i]; - should(`RFC8032ph/${i}`, () => { - deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey); - deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature); - deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true); + should(`Wycheproof/ED25519`, () => { + for (let g = 0; g < ed25519vectors.testGroups.length; g++) { + const group = ed25519vectors.testGroups[g]; + const key = group.key; + deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`); + for (let i = 0; i < group.tests.length; i++) { + const v = group.tests[i]; + const comment = `(${g}/${i}, ${v.result}): ${v.comment}`; + if (v.result === 'valid' || v.result === 'acceptable') { + deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, comment); + deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, comment); + } else if (v.result === 'invalid') { + let failed = false; + try { + failed = !ed.verify(v.sig, v.msg, key.pk); + } catch (error) { + failed = true; + } + deepStrictEqual(failed, true, comment); + } else throw new Error('unknown test result'); + } + } }); -} -should('X25519 base point', () => { - const { y } = ed25519.Point.BASE; - const { Fp } = ed25519.CURVE; - const u = Fp.create((y + 1n) * Fp.invert(1n - y)); - deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); + should('Property test issue #1', () => { + const message = new Uint8Array([12, 12, 12]); + const signature = ed.sign(message, to32Bytes(1n)); + const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n + deepStrictEqual(ed.verify(signature, message, publicKey), true); + }); + + const VECTORS_RFC8032_CTX = [ + { + secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', + publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', + message: 'f726936d19c800494e3fdaff20b276a8', + context: '666f6f', + signature: + '55a4cc2f70a54e04288c5f4cd1e45a7b' + + 'b520b36292911876cada7323198dd87a' + + '8b36950b95130022907a7fb7c4e9b2d5' + + 'f6cca685a587b4b21f4b888e4e7edb0d', + }, + { + secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', + publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', + message: 'f726936d19c800494e3fdaff20b276a8', + context: '626172', + signature: + 'fc60d5872fc46b3aa69f8b5b4351d580' + + '8f92bcc044606db097abab6dbcb1aee3' + + '216c48e8b3b66431b5b186d1d28f8ee1' + + '5a5ca2df6668346291c2043d4eb3e90d', + }, + { + secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6', + publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292', + message: '508e9e6882b979fea900f62adceaca35', + context: '666f6f', + signature: + '8b70c1cc8310e1de20ac53ce28ae6e72' + + '07f33c3295e03bb5c0732a1d20dc6490' + + '8922a8b052cf99b7c4fe107a5abb5b2c' + + '4085ae75890d02df26269d8945f84b0b', + }, + { + secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560', + publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772', + message: 'f726936d19c800494e3fdaff20b276a8', + context: '666f6f', + signature: + '21655b5f1aa965996b3f97b3c849eafb' + + 'a922a0a62992f73b3d1b73106a84ad85' + + 'e9b86a7b6005ea868337ff2d20a7f5fb' + + 'd4cd10b0be49a68da2b2e0dc0ad8960f', + }, + ]; + + for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) { + const v = VECTORS_RFC8032_CTX[i]; + should(`RFC8032ctx/${i}`, () => { + deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey); + deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature); + deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true); + }); + } + + const VECTORS_RFC8032_PH = [ + { + secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42', + publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf', + message: '616263', + signature: + '98a70222f0b8121aa9d30f813d683f80' + + '9e462b469c7ff87639499bb94e6dae41' + + '31f85042463c2a355a2003d062adf5aa' + + 'a10b8c61e636062aaad11c2a26083406', + }, + ]; + + for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { + const v = VECTORS_RFC8032_PH[i]; + should(`RFC8032ph/${i}`, () => { + deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey); + deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature); + deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true); + }); + } + + should('X25519 base point', () => { + const { y } = ed25519.Point.BASE; + const { Fp } = ed25519.CURVE; + const u = Fp.create((y + 1n) * Fp.invert(1n - y)); + deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu); + }); + + should('isTorsionFree()', () => { + const orig = ed.utils.getExtendedPublicKey(ed.utils.randomPrivateKey()).point; + for (const hex of ED25519_TORSION_SUBGROUP.slice(1)) { + const dirty = orig.add(ed.Point.fromHex(hex)); + const cleared = dirty.clearCofactor(); + strictEqual(orig.isTorsionFree(), true, `orig must be torsionFree: ${hex}`); + strictEqual(dirty.isTorsionFree(), false, `dirty must not be torsionFree: ${hex}`); + strictEqual(cleared.isTorsionFree(), true, `cleared must be torsionFree: ${hex}`); + } + }); }); // ESM is broken. diff --git a/test/ed448.test.js b/test/ed448.test.js index b9c5218..1661ddd 100644 --- a/test/ed448.test.js +++ b/test/ed448.test.js @@ -1,5 +1,5 @@ import { deepStrictEqual, throws } from 'assert'; -import { should } from 'micro-should'; +import { describe, should } from 'micro-should'; import * as fc from 'fast-check'; import { ed448, ed448ph, x448 } from '../lib/esm/ed448.js'; import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils'; @@ -7,449 +7,548 @@ import { numberToBytesLE } from '../lib/esm/abstract/utils.js'; import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' }; import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' }; -const ed = ed448; -const hex = bytesToHex; -ed.utils.precompute(4); +describe('ed448', () => { + const ed = ed448; + const hex = bytesToHex; + ed.utils.precompute(4); -should(`Basic`, () => { - const G1 = ed.Point.BASE; - deepStrictEqual( - G1.x, - 224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n - ); - deepStrictEqual( - G1.y, - 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n - ); - const G2 = ed.Point.BASE.multiply(2n); - deepStrictEqual( - G2.x, - 484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n - ); - deepStrictEqual( - G2.y, - 494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n - ); - const G3 = ed.Point.BASE.multiply(3n); - deepStrictEqual( - G3.x, - 23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n - ); - deepStrictEqual( - G3.y, - 636046652612779686502873775776967954190574036985351036782021535703553242737829645273154208057988851307101009474686328623630835377952508n - ); -}); - -should('Basic/decompress', () => { - const G1 = ed.Point.BASE; - const G2 = ed.Point.BASE.multiply(2n); - const G3 = ed.Point.BASE.multiply(3n); - const points = [G1, G2, G3]; - const getXY = (p) => ({ x: p.x, y: p.y }); - for (const p of points) deepStrictEqual(getXY(ed.Point.fromHex(p.toHex())), getXY(p)); -}); - -const VECTORS_RFC8032 = [ - { - secretKey: - '6c82a562cb808d10d632be89c8513ebf' + - '6c929f34ddfa8c9f63c9960ef6e348a3' + - '528c8a3fcc2f044e39a3fc5b94492f8f' + - '032e7549a20098f95b', - publicKey: - '5fd7449b59b461fd2ce787ec616ad46a' + - '1da1342485a70e1f8a0ea75d80e96778' + - 'edf124769b46c7061bd6783df1e50f6c' + - 'd1fa1abeafe8256180', - message: '', - signature: - '533a37f6bbe457251f023c0d88f976ae' + - '2dfb504a843e34d2074fd823d41a591f' + - '2b233f034f628281f2fd7a22ddd47d78' + - '28c59bd0a21bfd3980ff0d2028d4b18a' + - '9df63e006c5d1c2d345b925d8dc00b41' + - '04852db99ac5c7cdda8530a113a0f4db' + - 'b61149f05a7363268c71d95808ff2e65' + - '2600', - }, - { - secretKey: - 'c4eab05d357007c632f3dbb48489924d' + - '552b08fe0c353a0d4a1f00acda2c463a' + - 'fbea67c5e8d2877c5e3bc397a659949e' + - 'f8021e954e0a12274e', - publicKey: - '43ba28f430cdff456ae531545f7ecd0a' + - 'c834a55d9358c0372bfa0c6c6798c086' + - '6aea01eb00742802b8438ea4cb82169c' + - '235160627b4c3a9480', - - message: '03', - signature: - '26b8f91727bd62897af15e41eb43c377' + - 'efb9c610d48f2335cb0bd0087810f435' + - '2541b143c4b981b7e18f62de8ccdf633' + - 'fc1bf037ab7cd779805e0dbcc0aae1cb' + - 'cee1afb2e027df36bc04dcecbf154336' + - 'c19f0af7e0a6472905e799f1953d2a0f' + - 'f3348ab21aa4adafd1d234441cf807c0' + - '3a00', - }, - { - secretKey: - 'cd23d24f714274e744343237b93290f5' + - '11f6425f98e64459ff203e8985083ffd' + - 'f60500553abc0e05cd02184bdb89c4cc' + - 'd67e187951267eb328', - publicKey: - 'dcea9e78f35a1bf3499a831b10b86c90' + - 'aac01cd84b67a0109b55a36e9328b1e3' + - '65fce161d71ce7131a543ea4cb5f7e9f' + - '1d8b00696447001400', - message: '0c3e544074ec63b0265e0c', - signature: - '1f0a8888ce25e8d458a21130879b840a' + - '9089d999aaba039eaf3e3afa090a09d3' + - '89dba82c4ff2ae8ac5cdfb7c55e94d5d' + - '961a29fe0109941e00b8dbdeea6d3b05' + - '1068df7254c0cdc129cbe62db2dc957d' + - 'bb47b51fd3f213fb8698f064774250a5' + - '028961c9bf8ffd973fe5d5c206492b14' + - '0e00', - }, - { - secretKey: - '258cdd4ada32ed9c9ff54e63756ae582' + - 'fb8fab2ac721f2c8e676a72768513d93' + - '9f63dddb55609133f29adf86ec9929dc' + - 'cb52c1c5fd2ff7e21b', - publicKey: - '3ba16da0c6f2cc1f30187740756f5e79' + - '8d6bc5fc015d7c63cc9510ee3fd44adc' + - '24d8e968b6e46e6f94d19b945361726b' + - 'd75e149ef09817f580', - message: '64a65f3cdedcdd66811e2915', - signature: - '7eeeab7c4e50fb799b418ee5e3197ff6' + - 'bf15d43a14c34389b59dd1a7b1b85b4a' + - 'e90438aca634bea45e3a2695f1270f07' + - 'fdcdf7c62b8efeaf00b45c2c96ba457e' + - 'b1a8bf075a3db28e5c24f6b923ed4ad7' + - '47c3c9e03c7079efb87cb110d3a99861' + - 'e72003cbae6d6b8b827e4e6c143064ff' + - '3c00', - }, - { - secretKey: - '7ef4e84544236752fbb56b8f31a23a10' + - 'e42814f5f55ca037cdcc11c64c9a3b29' + - '49c1bb60700314611732a6c2fea98eeb' + - 'c0266a11a93970100e', - publicKey: - 'b3da079b0aa493a5772029f0467baebe' + - 'e5a8112d9d3a22532361da294f7bb381' + - '5c5dc59e176b4d9f381ca0938e13c6c0' + - '7b174be65dfa578e80', - message: '64a65f3cdedcdd66811e2915e7', - signature: - '6a12066f55331b6c22acd5d5bfc5d712' + - '28fbda80ae8dec26bdd306743c5027cb' + - '4890810c162c027468675ecf645a8317' + - '6c0d7323a2ccde2d80efe5a1268e8aca' + - '1d6fbc194d3f77c44986eb4ab4177919' + - 'ad8bec33eb47bbb5fc6e28196fd1caf5' + - '6b4e7e0ba5519234d047155ac727a105' + - '3100', - }, - { - secretKey: - 'd65df341ad13e008567688baedda8e9d' + - 'cdc17dc024974ea5b4227b6530e339bf' + - 'f21f99e68ca6968f3cca6dfe0fb9f4fa' + - 'b4fa135d5542ea3f01', - publicKey: - 'df9705f58edbab802c7f8363cfe5560a' + - 'b1c6132c20a9f1dd163483a26f8ac53a' + - '39d6808bf4a1dfbd261b099bb03b3fb5' + - '0906cb28bd8a081f00', - message: - 'bd0f6a3747cd561bdddf4640a332461a' + - '4a30a12a434cd0bf40d766d9c6d458e5' + - '512204a30c17d1f50b5079631f64eb31' + - '12182da3005835461113718d1a5ef944', - signature: - '554bc2480860b49eab8532d2a533b7d5' + - '78ef473eeb58c98bb2d0e1ce488a98b1' + - '8dfde9b9b90775e67f47d4a1c3482058' + - 'efc9f40d2ca033a0801b63d45b3b722e' + - 'f552bad3b4ccb667da350192b61c508c' + - 'f7b6b5adadc2c8d9a446ef003fb05cba' + - '5f30e88e36ec2703b349ca229c267083' + - '3900', - }, - { - secretKey: - '2ec5fe3c17045abdb136a5e6a913e32a' + - 'b75ae68b53d2fc149b77e504132d3756' + - '9b7e766ba74a19bd6162343a21c8590a' + - 'a9cebca9014c636df5', - publicKey: - '79756f014dcfe2079f5dd9e718be4171' + - 'e2ef2486a08f25186f6bff43a9936b9b' + - 'fe12402b08ae65798a3d81e22e9ec80e' + - '7690862ef3d4ed3a00', - message: - '15777532b0bdd0d1389f636c5f6b9ba7' + - '34c90af572877e2d272dd078aa1e567c' + - 'fa80e12928bb542330e8409f31745041' + - '07ecd5efac61ae7504dabe2a602ede89' + - 'e5cca6257a7c77e27a702b3ae39fc769' + - 'fc54f2395ae6a1178cab4738e543072f' + - 'c1c177fe71e92e25bf03e4ecb72f47b6' + - '4d0465aaea4c7fad372536c8ba516a60' + - '39c3c2a39f0e4d832be432dfa9a706a6' + - 'e5c7e19f397964ca4258002f7c0541b5' + - '90316dbc5622b6b2a6fe7a4abffd9610' + - '5eca76ea7b98816af0748c10df048ce0' + - '12d901015a51f189f3888145c03650aa' + - '23ce894c3bd889e030d565071c59f409' + - 'a9981b51878fd6fc110624dcbcde0bf7' + - 'a69ccce38fabdf86f3bef6044819de11', - signature: - 'c650ddbb0601c19ca11439e1640dd931' + - 'f43c518ea5bea70d3dcde5f4191fe53f' + - '00cf966546b72bcc7d58be2b9badef28' + - '743954e3a44a23f880e8d4f1cfce2d7a' + - '61452d26da05896f0a50da66a239a8a1' + - '88b6d825b3305ad77b73fbac0836ecc6' + - '0987fd08527c1a8e80d5823e65cafe2a' + - '3d00', - }, - { - secretKey: - '872d093780f5d3730df7c212664b37b8' + - 'a0f24f56810daa8382cd4fa3f77634ec' + - '44dc54f1c2ed9bea86fafb7632d8be19' + - '9ea165f5ad55dd9ce8', - publicKey: - 'a81b2e8a70a5ac94ffdbcc9badfc3feb' + - '0801f258578bb114ad44ece1ec0e799d' + - 'a08effb81c5d685c0c56f64eecaef8cd' + - 'f11cc38737838cf400', - message: - '6ddf802e1aae4986935f7f981ba3f035' + - '1d6273c0a0c22c9c0e8339168e675412' + - 'a3debfaf435ed651558007db4384b650' + - 'fcc07e3b586a27a4f7a00ac8a6fec2cd' + - '86ae4bf1570c41e6a40c931db27b2faa' + - '15a8cedd52cff7362c4e6e23daec0fbc' + - '3a79b6806e316efcc7b68119bf46bc76' + - 'a26067a53f296dafdbdc11c77f7777e9' + - '72660cf4b6a9b369a6665f02e0cc9b6e' + - 'dfad136b4fabe723d2813db3136cfde9' + - 'b6d044322fee2947952e031b73ab5c60' + - '3349b307bdc27bc6cb8b8bbd7bd32321' + - '9b8033a581b59eadebb09b3c4f3d2277' + - 'd4f0343624acc817804728b25ab79717' + - '2b4c5c21a22f9c7839d64300232eb66e' + - '53f31c723fa37fe387c7d3e50bdf9813' + - 'a30e5bb12cf4cd930c40cfb4e1fc6225' + - '92a49588794494d56d24ea4b40c89fc0' + - '596cc9ebb961c8cb10adde976a5d602b' + - '1c3f85b9b9a001ed3c6a4d3b1437f520' + - '96cd1956d042a597d561a596ecd3d173' + - '5a8d570ea0ec27225a2c4aaff26306d1' + - '526c1af3ca6d9cf5a2c98f47e1c46db9' + - 'a33234cfd4d81f2c98538a09ebe76998' + - 'd0d8fd25997c7d255c6d66ece6fa56f1' + - '1144950f027795e653008f4bd7ca2dee' + - '85d8e90f3dc315130ce2a00375a318c7' + - 'c3d97be2c8ce5b6db41a6254ff264fa6' + - '155baee3b0773c0f497c573f19bb4f42' + - '40281f0b1f4f7be857a4e59d416c06b4' + - 'c50fa09e1810ddc6b1467baeac5a3668' + - 'd11b6ecaa901440016f389f80acc4db9' + - '77025e7f5924388c7e340a732e554440' + - 'e76570f8dd71b7d640b3450d1fd5f041' + - '0a18f9a3494f707c717b79b4bf75c984' + - '00b096b21653b5d217cf3565c9597456' + - 'f70703497a078763829bc01bb1cbc8fa' + - '04eadc9a6e3f6699587a9e75c94e5bab' + - '0036e0b2e711392cff0047d0d6b05bd2' + - 'a588bc109718954259f1d86678a579a3' + - '120f19cfb2963f177aeb70f2d4844826' + - '262e51b80271272068ef5b3856fa8535' + - 'aa2a88b2d41f2a0e2fda7624c2850272' + - 'ac4a2f561f8f2f7a318bfd5caf969614' + - '9e4ac824ad3460538fdc25421beec2cc' + - '6818162d06bbed0c40a387192349db67' + - 'a118bada6cd5ab0140ee273204f628aa' + - 'd1c135f770279a651e24d8c14d75a605' + - '9d76b96a6fd857def5e0b354b27ab937' + - 'a5815d16b5fae407ff18222c6d1ed263' + - 'be68c95f32d908bd895cd76207ae7264' + - '87567f9a67dad79abec316f683b17f2d' + - '02bf07e0ac8b5bc6162cf94697b3c27c' + - 'd1fea49b27f23ba2901871962506520c' + - '392da8b6ad0d99f7013fbc06c2c17a56' + - '9500c8a7696481c1cd33e9b14e40b82e' + - '79a5f5db82571ba97bae3ad3e0479515' + - 'bb0e2b0f3bfcd1fd33034efc6245eddd' + - '7ee2086ddae2600d8ca73e214e8c2b0b' + - 'db2b047c6a464a562ed77b73d2d841c4' + - 'b34973551257713b753632efba348169' + - 'abc90a68f42611a40126d7cb21b58695' + - '568186f7e569d2ff0f9e745d0487dd2e' + - 'b997cafc5abf9dd102e62ff66cba87', - signature: - 'e301345a41a39a4d72fff8df69c98075' + - 'a0cc082b802fc9b2b6bc503f926b65bd' + - 'df7f4c8f1cb49f6396afc8a70abe6d8a' + - 'ef0db478d4c6b2970076c6a0484fe76d' + - '76b3a97625d79f1ce240e7c576750d29' + - '5528286f719b413de9ada3e8eb78ed57' + - '3603ce30d8bb761785dc30dbc320869e' + - '1a00', - }, -]; - -for (let i = 0; i < VECTORS_RFC8032.length; i++) { - const v = VECTORS_RFC8032[i]; - should(`RFC8032/${i}`, () => { - deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); - deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature); - deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true); + should(`Basic`, () => { + const G1 = ed.Point.BASE; + deepStrictEqual( + G1.x, + 224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n + ); + deepStrictEqual( + G1.y, + 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n + ); + const G2 = ed.Point.BASE.multiply(2n); + deepStrictEqual( + G2.x, + 484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n + ); + deepStrictEqual( + G2.y, + 494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n + ); + const G3 = ed.Point.BASE.multiply(3n); + deepStrictEqual( + G3.x, + 23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n + ); + deepStrictEqual( + G3.y, + 636046652612779686502873775776967954190574036985351036782021535703553242737829645273154208057988851307101009474686328623630835377952508n + ); }); -} -should('ed448/should not accept >57byte private keys', async () => { - const invalidPriv = - 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; - throws(() => ed.getPublicKey(invalidPriv)); -}); + should('Basic/decompress', () => { + const G1 = ed.Point.BASE; + const G2 = ed.Point.BASE.multiply(2n); + const G3 = ed.Point.BASE.multiply(3n); + const points = [G1, G2, G3]; + const getXY = (p) => ({ x: p.x, y: p.y }); + for (const p of points) deepStrictEqual(getXY(ed.Point.fromHex(p.toHex())), getXY(p)); + }); -function to57Bytes(numOrStr) { - let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16); - return hexToBytes(hex.padStart(114, '0')); -} + const VECTORS_RFC8032 = [ + { + secretKey: + '6c82a562cb808d10d632be89c8513ebf' + + '6c929f34ddfa8c9f63c9960ef6e348a3' + + '528c8a3fcc2f044e39a3fc5b94492f8f' + + '032e7549a20098f95b', + publicKey: + '5fd7449b59b461fd2ce787ec616ad46a' + + '1da1342485a70e1f8a0ea75d80e96778' + + 'edf124769b46c7061bd6783df1e50f6c' + + 'd1fa1abeafe8256180', + message: '', + signature: + '533a37f6bbe457251f023c0d88f976ae' + + '2dfb504a843e34d2074fd823d41a591f' + + '2b233f034f628281f2fd7a22ddd47d78' + + '28c59bd0a21bfd3980ff0d2028d4b18a' + + '9df63e006c5d1c2d345b925d8dc00b41' + + '04852db99ac5c7cdda8530a113a0f4db' + + 'b61149f05a7363268c71d95808ff2e65' + + '2600', + }, + { + secretKey: + 'c4eab05d357007c632f3dbb48489924d' + + '552b08fe0c353a0d4a1f00acda2c463a' + + 'fbea67c5e8d2877c5e3bc397a659949e' + + 'f8021e954e0a12274e', + publicKey: + '43ba28f430cdff456ae531545f7ecd0a' + + 'c834a55d9358c0372bfa0c6c6798c086' + + '6aea01eb00742802b8438ea4cb82169c' + + '235160627b4c3a9480', -should('ed448/should verify recent signature', () => { - fc.assert( - fc.property( - fc.hexaString({ minLength: 2, maxLength: 57 }), - fc.bigInt(2n, ed.CURVE.n), - (message, privateKey) => { - const publicKey = ed.getPublicKey(to57Bytes(privateKey)); - const signature = ed.sign(to57Bytes(message), to57Bytes(privateKey)); - deepStrictEqual(publicKey.length, 57); - deepStrictEqual(signature.length, 114); - deepStrictEqual(ed.verify(signature, to57Bytes(message), publicKey), true); - } - ), - { numRuns: 5 } - ); -}); -should('ed448/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 })), - fc.bigInt(1n, ed.CURVE.n), - (bytes, wrongBytes, privateKey) => { - const message = new Uint8Array(bytes); - const wrongMessage = new Uint8Array(wrongBytes); - const priv = to57Bytes(privateKey); - const publicKey = ed.getPublicKey(priv); - const signature = ed.sign(message, priv); - deepStrictEqual( - ed.verify(signature, wrongMessage, publicKey), - bytes.toString() === wrongBytes.toString() - ); - } - ), - { numRuns: 5 } - ); -}); -const privKey = to57Bytes('a665a45920422f9d417e4867ef'); -const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); -const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); -should('ed25519/basic methods/should sign and verify', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), true); -}); -should('ed25519/basic methods/should not verify signature with wrong public key', () => { - const publicKey = ed.getPublicKey(12); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), false); -}); -should('ed25519/basic methods/should not verify signature with wrong hash', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); -}); + message: '03', + signature: + '26b8f91727bd62897af15e41eb43c377' + + 'efb9c610d48f2335cb0bd0087810f435' + + '2541b143c4b981b7e18f62de8ccdf633' + + 'fc1bf037ab7cd779805e0dbcc0aae1cb' + + 'cee1afb2e027df36bc04dcecbf154336' + + 'c19f0af7e0a6472905e799f1953d2a0f' + + 'f3348ab21aa4adafd1d234441cf807c0' + + '3a00', + }, + { + secretKey: + 'cd23d24f714274e744343237b93290f5' + + '11f6425f98e64459ff203e8985083ffd' + + 'f60500553abc0e05cd02184bdb89c4cc' + + 'd67e187951267eb328', + publicKey: + 'dcea9e78f35a1bf3499a831b10b86c90' + + 'aac01cd84b67a0109b55a36e9328b1e3' + + '65fce161d71ce7131a543ea4cb5f7e9f' + + '1d8b00696447001400', + message: '0c3e544074ec63b0265e0c', + signature: + '1f0a8888ce25e8d458a21130879b840a' + + '9089d999aaba039eaf3e3afa090a09d3' + + '89dba82c4ff2ae8ac5cdfb7c55e94d5d' + + '961a29fe0109941e00b8dbdeea6d3b05' + + '1068df7254c0cdc129cbe62db2dc957d' + + 'bb47b51fd3f213fb8698f064774250a5' + + '028961c9bf8ffd973fe5d5c206492b14' + + '0e00', + }, + { + secretKey: + '258cdd4ada32ed9c9ff54e63756ae582' + + 'fb8fab2ac721f2c8e676a72768513d93' + + '9f63dddb55609133f29adf86ec9929dc' + + 'cb52c1c5fd2ff7e21b', + publicKey: + '3ba16da0c6f2cc1f30187740756f5e79' + + '8d6bc5fc015d7c63cc9510ee3fd44adc' + + '24d8e968b6e46e6f94d19b945361726b' + + 'd75e149ef09817f580', + message: '64a65f3cdedcdd66811e2915', + signature: + '7eeeab7c4e50fb799b418ee5e3197ff6' + + 'bf15d43a14c34389b59dd1a7b1b85b4a' + + 'e90438aca634bea45e3a2695f1270f07' + + 'fdcdf7c62b8efeaf00b45c2c96ba457e' + + 'b1a8bf075a3db28e5c24f6b923ed4ad7' + + '47c3c9e03c7079efb87cb110d3a99861' + + 'e72003cbae6d6b8b827e4e6c143064ff' + + '3c00', + }, + { + secretKey: + '7ef4e84544236752fbb56b8f31a23a10' + + 'e42814f5f55ca037cdcc11c64c9a3b29' + + '49c1bb60700314611732a6c2fea98eeb' + + 'c0266a11a93970100e', + publicKey: + 'b3da079b0aa493a5772029f0467baebe' + + 'e5a8112d9d3a22532361da294f7bb381' + + '5c5dc59e176b4d9f381ca0938e13c6c0' + + '7b174be65dfa578e80', + message: '64a65f3cdedcdd66811e2915e7', + signature: + '6a12066f55331b6c22acd5d5bfc5d712' + + '28fbda80ae8dec26bdd306743c5027cb' + + '4890810c162c027468675ecf645a8317' + + '6c0d7323a2ccde2d80efe5a1268e8aca' + + '1d6fbc194d3f77c44986eb4ab4177919' + + 'ad8bec33eb47bbb5fc6e28196fd1caf5' + + '6b4e7e0ba5519234d047155ac727a105' + + '3100', + }, + { + secretKey: + 'd65df341ad13e008567688baedda8e9d' + + 'cdc17dc024974ea5b4227b6530e339bf' + + 'f21f99e68ca6968f3cca6dfe0fb9f4fa' + + 'b4fa135d5542ea3f01', + publicKey: + 'df9705f58edbab802c7f8363cfe5560a' + + 'b1c6132c20a9f1dd163483a26f8ac53a' + + '39d6808bf4a1dfbd261b099bb03b3fb5' + + '0906cb28bd8a081f00', + message: + 'bd0f6a3747cd561bdddf4640a332461a' + + '4a30a12a434cd0bf40d766d9c6d458e5' + + '512204a30c17d1f50b5079631f64eb31' + + '12182da3005835461113718d1a5ef944', + signature: + '554bc2480860b49eab8532d2a533b7d5' + + '78ef473eeb58c98bb2d0e1ce488a98b1' + + '8dfde9b9b90775e67f47d4a1c3482058' + + 'efc9f40d2ca033a0801b63d45b3b722e' + + 'f552bad3b4ccb667da350192b61c508c' + + 'f7b6b5adadc2c8d9a446ef003fb05cba' + + '5f30e88e36ec2703b349ca229c267083' + + '3900', + }, + { + secretKey: + '2ec5fe3c17045abdb136a5e6a913e32a' + + 'b75ae68b53d2fc149b77e504132d3756' + + '9b7e766ba74a19bd6162343a21c8590a' + + 'a9cebca9014c636df5', + publicKey: + '79756f014dcfe2079f5dd9e718be4171' + + 'e2ef2486a08f25186f6bff43a9936b9b' + + 'fe12402b08ae65798a3d81e22e9ec80e' + + '7690862ef3d4ed3a00', + message: + '15777532b0bdd0d1389f636c5f6b9ba7' + + '34c90af572877e2d272dd078aa1e567c' + + 'fa80e12928bb542330e8409f31745041' + + '07ecd5efac61ae7504dabe2a602ede89' + + 'e5cca6257a7c77e27a702b3ae39fc769' + + 'fc54f2395ae6a1178cab4738e543072f' + + 'c1c177fe71e92e25bf03e4ecb72f47b6' + + '4d0465aaea4c7fad372536c8ba516a60' + + '39c3c2a39f0e4d832be432dfa9a706a6' + + 'e5c7e19f397964ca4258002f7c0541b5' + + '90316dbc5622b6b2a6fe7a4abffd9610' + + '5eca76ea7b98816af0748c10df048ce0' + + '12d901015a51f189f3888145c03650aa' + + '23ce894c3bd889e030d565071c59f409' + + 'a9981b51878fd6fc110624dcbcde0bf7' + + 'a69ccce38fabdf86f3bef6044819de11', + signature: + 'c650ddbb0601c19ca11439e1640dd931' + + 'f43c518ea5bea70d3dcde5f4191fe53f' + + '00cf966546b72bcc7d58be2b9badef28' + + '743954e3a44a23f880e8d4f1cfce2d7a' + + '61452d26da05896f0a50da66a239a8a1' + + '88b6d825b3305ad77b73fbac0836ecc6' + + '0987fd08527c1a8e80d5823e65cafe2a' + + '3d00', + }, + { + secretKey: + '872d093780f5d3730df7c212664b37b8' + + 'a0f24f56810daa8382cd4fa3f77634ec' + + '44dc54f1c2ed9bea86fafb7632d8be19' + + '9ea165f5ad55dd9ce8', + publicKey: + 'a81b2e8a70a5ac94ffdbcc9badfc3feb' + + '0801f258578bb114ad44ece1ec0e799d' + + 'a08effb81c5d685c0c56f64eecaef8cd' + + 'f11cc38737838cf400', + message: + '6ddf802e1aae4986935f7f981ba3f035' + + '1d6273c0a0c22c9c0e8339168e675412' + + 'a3debfaf435ed651558007db4384b650' + + 'fcc07e3b586a27a4f7a00ac8a6fec2cd' + + '86ae4bf1570c41e6a40c931db27b2faa' + + '15a8cedd52cff7362c4e6e23daec0fbc' + + '3a79b6806e316efcc7b68119bf46bc76' + + 'a26067a53f296dafdbdc11c77f7777e9' + + '72660cf4b6a9b369a6665f02e0cc9b6e' + + 'dfad136b4fabe723d2813db3136cfde9' + + 'b6d044322fee2947952e031b73ab5c60' + + '3349b307bdc27bc6cb8b8bbd7bd32321' + + '9b8033a581b59eadebb09b3c4f3d2277' + + 'd4f0343624acc817804728b25ab79717' + + '2b4c5c21a22f9c7839d64300232eb66e' + + '53f31c723fa37fe387c7d3e50bdf9813' + + 'a30e5bb12cf4cd930c40cfb4e1fc6225' + + '92a49588794494d56d24ea4b40c89fc0' + + '596cc9ebb961c8cb10adde976a5d602b' + + '1c3f85b9b9a001ed3c6a4d3b1437f520' + + '96cd1956d042a597d561a596ecd3d173' + + '5a8d570ea0ec27225a2c4aaff26306d1' + + '526c1af3ca6d9cf5a2c98f47e1c46db9' + + 'a33234cfd4d81f2c98538a09ebe76998' + + 'd0d8fd25997c7d255c6d66ece6fa56f1' + + '1144950f027795e653008f4bd7ca2dee' + + '85d8e90f3dc315130ce2a00375a318c7' + + 'c3d97be2c8ce5b6db41a6254ff264fa6' + + '155baee3b0773c0f497c573f19bb4f42' + + '40281f0b1f4f7be857a4e59d416c06b4' + + 'c50fa09e1810ddc6b1467baeac5a3668' + + 'd11b6ecaa901440016f389f80acc4db9' + + '77025e7f5924388c7e340a732e554440' + + 'e76570f8dd71b7d640b3450d1fd5f041' + + '0a18f9a3494f707c717b79b4bf75c984' + + '00b096b21653b5d217cf3565c9597456' + + 'f70703497a078763829bc01bb1cbc8fa' + + '04eadc9a6e3f6699587a9e75c94e5bab' + + '0036e0b2e711392cff0047d0d6b05bd2' + + 'a588bc109718954259f1d86678a579a3' + + '120f19cfb2963f177aeb70f2d4844826' + + '262e51b80271272068ef5b3856fa8535' + + 'aa2a88b2d41f2a0e2fda7624c2850272' + + 'ac4a2f561f8f2f7a318bfd5caf969614' + + '9e4ac824ad3460538fdc25421beec2cc' + + '6818162d06bbed0c40a387192349db67' + + 'a118bada6cd5ab0140ee273204f628aa' + + 'd1c135f770279a651e24d8c14d75a605' + + '9d76b96a6fd857def5e0b354b27ab937' + + 'a5815d16b5fae407ff18222c6d1ed263' + + 'be68c95f32d908bd895cd76207ae7264' + + '87567f9a67dad79abec316f683b17f2d' + + '02bf07e0ac8b5bc6162cf94697b3c27c' + + 'd1fea49b27f23ba2901871962506520c' + + '392da8b6ad0d99f7013fbc06c2c17a56' + + '9500c8a7696481c1cd33e9b14e40b82e' + + '79a5f5db82571ba97bae3ad3e0479515' + + 'bb0e2b0f3bfcd1fd33034efc6245eddd' + + '7ee2086ddae2600d8ca73e214e8c2b0b' + + 'db2b047c6a464a562ed77b73d2d841c4' + + 'b34973551257713b753632efba348169' + + 'abc90a68f42611a40126d7cb21b58695' + + '568186f7e569d2ff0f9e745d0487dd2e' + + 'b997cafc5abf9dd102e62ff66cba87', + signature: + 'e301345a41a39a4d72fff8df69c98075' + + 'a0cc082b802fc9b2b6bc503f926b65bd' + + 'df7f4c8f1cb49f6396afc8a70abe6d8a' + + 'ef0db478d4c6b2970076c6a0484fe76d' + + '76b3a97625d79f1ce240e7c576750d29' + + '5528286f719b413de9ada3e8eb78ed57' + + '3603ce30d8bb761785dc30dbc320869e' + + '1a00', + }, + ]; -should('ed25519/sync methods/should sign and verify', () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), true); -}); -should('ed25519/sync methods/should not verify signature with wrong public key', async () => { - const publicKey = ed.getPublicKey(12); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, msg, publicKey), false); -}); -should('ed25519/sync methods/should not verify signature with wrong hash', async () => { - const publicKey = ed.getPublicKey(privKey); - const signature = ed.sign(msg, privKey); - deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); -}); - -should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => { - for (const num of [0n, 0, -1n, -1, 1.1]) { - throws(() => ed.Point.BASE.multiply(num)); - } -}); - -should('input immutability: sign/verify are immutable', () => { - const privateKey = ed.utils.randomPrivateKey(); - const publicKey = ed.getPublicKey(privateKey); - - for (let i = 0; i < 100; i++) { - let payload = randomBytes(100); - let signature = ed.sign(payload, privateKey); - if (!ed.verify(signature, payload, publicKey)) { - throw new Error('Signature verification failed'); - } - const signatureCopy = Buffer.alloc(signature.byteLength); - signatureCopy.set(signature, 0); // <-- breaks - payload = payload.slice(); - signature = signature.slice(); - - if (!ed.verify(signatureCopy, payload, publicKey)) - throw new Error('Copied signature verification failed'); - } -}); - -{ - for (let g = 0; g < ed448vectors.testGroups.length; g++) { - const group = ed448vectors.testGroups[g]; - const key = group.key; - should(`Wycheproof/ED448(${g}, public)`, () => { - deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk); + for (let i = 0; i < VECTORS_RFC8032.length; i++) { + const v = VECTORS_RFC8032[i]; + should(`RFC8032/${i}`, () => { + deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); + deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature); + deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true); }); - should(`Wycheproof/ED448`, () => { + } + + should('not accept >57byte private keys', async () => { + const invalidPriv = + 100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; + throws(() => ed.getPublicKey(invalidPriv)); + }); + + function to57Bytes(numOrStr) { + let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16); + return hexToBytes(hex.padStart(114, '0')); + } + + should('verify recent signature', () => { + fc.assert( + fc.property( + fc.hexaString({ minLength: 2, maxLength: 57 }), + fc.bigInt(2n, ed.CURVE.n), + (message, privateKey) => { + const publicKey = ed.getPublicKey(to57Bytes(privateKey)); + const signature = ed.sign(to57Bytes(message), to57Bytes(privateKey)); + deepStrictEqual(publicKey.length, 57); + deepStrictEqual(signature.length, 114); + deepStrictEqual(ed.verify(signature, to57Bytes(message), publicKey), true); + } + ), + { numRuns: 5 } + ); + }); + 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 })), + fc.bigInt(1n, ed.CURVE.n), + (bytes, wrongBytes, privateKey) => { + const message = new Uint8Array(bytes); + const wrongMessage = new Uint8Array(wrongBytes); + const priv = to57Bytes(privateKey); + const publicKey = ed.getPublicKey(priv); + const signature = ed.sign(message, priv); + deepStrictEqual( + ed.verify(signature, wrongMessage, publicKey), + bytes.toString() === wrongBytes.toString() + ); + } + ), + { numRuns: 5 } + ); + }); + const privKey = to57Bytes('a665a45920422f9d417e4867ef'); + const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a'); + const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c'); + describe('basic methods', () => { + should('sign and verify', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), true); + }); + should('not verify signature with wrong public key', () => { + const publicKey = ed.getPublicKey(12); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), false); + }); + should('not verify signature with wrong hash', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); + }); + }); + + describe('sync methods', () => { + should('sign and verify', () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), true); + }); + should('not verify signature with wrong public key', async () => { + const publicKey = ed.getPublicKey(12); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, msg, publicKey), false); + }); + should('not verify signature with wrong hash', async () => { + const publicKey = ed.getPublicKey(privKey); + const signature = ed.sign(msg, privKey); + deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false); + }); + }); + + should('BASE_POINT.multiply() throws in Point#multiply on TEST 5', () => { + for (const num of [0n, 0, -1n, -1, 1.1]) { + throws(() => ed.Point.BASE.multiply(num)); + } + }); + + should('input immutability: sign/verify are immutable', () => { + const privateKey = ed.utils.randomPrivateKey(); + const publicKey = ed.getPublicKey(privateKey); + + for (let i = 0; i < 100; i++) { + let payload = randomBytes(100); + let signature = ed.sign(payload, privateKey); + if (!ed.verify(signature, payload, publicKey)) { + throw new Error('Signature verification failed'); + } + const signatureCopy = Buffer.alloc(signature.byteLength); + signatureCopy.set(signature, 0); // <-- breaks + payload = payload.slice(); + signature = signature.slice(); + + if (!ed.verify(signatureCopy, payload, publicKey)) + throw new Error('Copied signature verification failed'); + } + }); + + { + for (let g = 0; g < ed448vectors.testGroups.length; g++) { + const group = ed448vectors.testGroups[g]; + const key = group.key; + should(`Wycheproof/ED448(${g}, public)`, () => { + deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk); + }); + should(`Wycheproof/ED448`, () => { + for (let i = 0; i < group.tests.length; i++) { + const v = group.tests[i]; + const index = `${g}/${i} ${v.comment}`; + if (v.result === 'valid' || v.result === 'acceptable') { + deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, index); + deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, index); + } else if (v.result === 'invalid') { + let failed = false; + try { + failed = !ed.verify(v.sig, v.msg, key.pk); + } catch (error) { + failed = true; + } + deepStrictEqual(failed, true, index); + } else throw new Error('unknown test result'); + } + }); + } + } + + // ECDH + const rfc7748Mul = [ + { + scalar: + '3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3', + u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086', + outputU: + 'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f', + }, + { + scalar: + '203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f', + u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db', + outputU: + '884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d', + }, + ]; + for (let i = 0; i < rfc7748Mul.length; i++) { + const v = rfc7748Mul[i]; + should(`RFC7748: scalarMult (${i})`, () => { + deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU); + }); + } + + const rfc7748Iter = [ + { + scalar: + '3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113', + iters: 1, + }, + { + scalar: + 'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38', + iters: 1000, + }, + // { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 }, + ]; + for (let i = 0; i < rfc7748Iter.length; i++) { + const { scalar, iters } = rfc7748Iter[i]; + should(`RFC7748: scalarMult iteration (${i})`, () => { + let k = x448.Gu; + for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k]; + deepStrictEqual(hex(k), scalar); + }); + } + + should('RFC7748 getSharedKey', () => { + const alicePrivate = + '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b'; + const alicePublic = + '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0'; + const bobPrivate = + '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d'; + const bobPublic = + '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609'; + const shared = + '07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d'; + deepStrictEqual(alicePublic, hex(x448.getPublicKey(alicePrivate))); + deepStrictEqual(bobPublic, hex(x448.getPublicKey(bobPrivate))); + deepStrictEqual(hex(x448.scalarMult(alicePrivate, bobPublic)), shared); + deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared); + }); + + { + const group = x448vectors.testGroups[0]; + should(`Wycheproof/X448`, () => { for (let i = 0; i < group.tests.length; i++) { const v = group.tests[i]; - const index = `${g}/${i} ${v.comment}`; + const index = `(${i}, ${v.result}) ${v.comment}`; if (v.result === 'valid' || v.result === 'acceptable') { - deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, index); - deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, index); + try { + const shared = hex(x448.scalarMult(v.private, v.public)); + deepStrictEqual(shared, v.shared, index); + } catch (e) { + // We are more strict + if (e.message.includes('Expected valid scalar')) return; + if (e.message.includes('Invalid private or public key received')) return; + if (e.message.includes('Expected 56 bytes')) return; + throw e; + } } else if (v.result === 'invalid') { let failed = false; try { - failed = !ed.verify(v.sig, v.msg, key.pk); + x448.scalarMult(v.private, v.public); } catch (error) { failed = true; } @@ -458,204 +557,111 @@ should('input immutability: sign/verify are immutable', () => { } }); } -} -// ECDH -const rfc7748Mul = [ - { - scalar: - '3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3', - u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086', - outputU: - 'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f', - }, - { - scalar: - '203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f', - u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db', - outputU: - '884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d', - }, -]; -for (let i = 0; i < rfc7748Mul.length; i++) { - const v = rfc7748Mul[i]; - should(`RFC7748: scalarMult (${i})`, () => { - deepStrictEqual(hex(x448.scalarMult(v.scalar, v.u)), v.outputU); + // should('X448: should convert base point to montgomery using fromPoint', () => { + // deepStrictEqual( + // hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), + // ed.montgomeryCurve.BASE_POINT_U + // ); + // }); + + // should('X448/getSharedSecret() should be commutative', async () => { + // for (let i = 0; i < 512; i++) { + // const asec = ed.utils.randomPrivateKey(); + // const apub = ed.getPublicKey(asec); + // const bsec = ed.utils.randomPrivateKey(); + // const bpub = ed.getPublicKey(bsec); + // try { + // deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub)); + // } catch (error) { + // console.error('not commutative', { asec, apub, bsec, bpub }); + // throw error; + // } + // } + // }); + + const VECTORS_RFC8032_CTX = [ + { + secretKey: + 'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e', + publicKey: + '43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480', + message: '03', + context: '666f6f', + signature: + 'd4f8f6131770dd46f40867d6fd5d5055' + + 'de43541f8c5e35abbcd001b32a89f7d2' + + '151f7647f11d8ca2ae279fb842d60721' + + '7fce6e042f6815ea000c85741de5c8da' + + '1144a6a1aba7f96de42505d7a7298524' + + 'fda538fccbbb754f578c1cad10d54d0d' + + '5428407e85dcbc98a49155c13764e66c' + + '3c00', + }, + ]; + + for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) { + const v = VECTORS_RFC8032_CTX[i]; + should(`RFC8032ctx/${i}`, () => { + deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); + deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature); + deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true); + }); + } + + const VECTORS_RFC8032_PH = [ + { + secretKey: + '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49', + publicKey: + '259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880', + message: '616263', + signature: + '822f6901f7480f3d5f562c592994d969' + + '3602875614483256505600bbc281ae38' + + '1f54d6bce2ea911574932f52a4e6cadd' + + '78769375ec3ffd1b801a0d9b3f4030cd' + + '433964b6457ea39476511214f97469b5' + + '7dd32dbc560a9a94d00bff07620464a3' + + 'ad203df7dc7ce360c3cd3696d9d9fab9' + + '0f00', + }, + { + secretKey: + '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49', + publicKey: + '259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880', + message: '616263', + context: '666f6f', + signature: + 'c32299d46ec8ff02b54540982814dce9' + + 'a05812f81962b649d528095916a2aa48' + + '1065b1580423ef927ecf0af5888f90da' + + '0f6a9a85ad5dc3f280d91224ba9911a3' + + '653d00e484e2ce232521481c8658df30' + + '4bb7745a73514cdb9bf3e15784ab7128' + + '4f8d0704a608c54a6b62d97beb511d13' + + '2100', + }, + ]; + + for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { + const v = VECTORS_RFC8032_PH[i]; + should(`RFC8032ph/${i}`, () => { + deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey); + deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature); + deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true); + }); + } + + should('X448 base point', () => { + const { x, y } = ed448.Point.BASE; + const { Fp } = ed448.CURVE; + // const invX = Fp.invert(x * x); // x² + const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) + // const u = Fp.create(y * y * invX); + deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); }); -} - -const rfc7748Iter = [ - { - scalar: - '3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113', - iters: 1, - }, - { - scalar: - 'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38', - iters: 1000, - }, - // { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 }, -]; -for (let i = 0; i < rfc7748Iter.length; i++) { - const { scalar, iters } = rfc7748Iter[i]; - should(`RFC7748: scalarMult iteration (${i})`, () => { - let k = x448.Gu; - for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(k, u), k]; - deepStrictEqual(hex(k), scalar); - }); -} - -should('RFC7748 getSharedKey', () => { - const alicePrivate = - '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b'; - const alicePublic = - '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0'; - const bobPrivate = - '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d'; - const bobPublic = - '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609'; - const shared = - '07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d'; - deepStrictEqual(alicePublic, hex(x448.getPublicKey(alicePrivate))); - deepStrictEqual(bobPublic, hex(x448.getPublicKey(bobPrivate))); - deepStrictEqual(hex(x448.scalarMult(alicePrivate, bobPublic)), shared); - deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared); -}); - -{ - const group = x448vectors.testGroups[0]; - should(`Wycheproof/X448`, () => { - for (let i = 0; i < group.tests.length; i++) { - const v = group.tests[i]; - const index = `(${i}, ${v.result}) ${v.comment}`; - if (v.result === 'valid' || v.result === 'acceptable') { - try { - const shared = hex(x448.scalarMult(v.private, v.public)); - deepStrictEqual(shared, v.shared, index); - } catch (e) { - // We are more strict - if (e.message.includes('Expected valid scalar')) return; - if (e.message.includes('Invalid private or public key received')) return; - if (e.message.includes('Expected 56 bytes')) return; - throw e; - } - } else if (v.result === 'invalid') { - let failed = false; - try { - x448.scalarMult(v.private, v.public); - } catch (error) { - failed = true; - } - deepStrictEqual(failed, true, index); - } else throw new Error('unknown test result'); - } - }); -} - -// should('X448: should convert base point to montgomery using fromPoint', () => { -// deepStrictEqual( -// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)), -// ed.montgomeryCurve.BASE_POINT_U -// ); -// }); - -// should('X448/getSharedSecret() should be commutative', async () => { -// for (let i = 0; i < 512; i++) { -// const asec = ed.utils.randomPrivateKey(); -// const apub = ed.getPublicKey(asec); -// const bsec = ed.utils.randomPrivateKey(); -// const bpub = ed.getPublicKey(bsec); -// try { -// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub)); -// } catch (error) { -// console.error('not commutative', { asec, apub, bsec, bpub }); -// throw error; -// } -// } -// }); - -const VECTORS_RFC8032_CTX = [ - { - secretKey: - 'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e', - publicKey: - '43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480', - message: '03', - context: '666f6f', - signature: - 'd4f8f6131770dd46f40867d6fd5d5055' + - 'de43541f8c5e35abbcd001b32a89f7d2' + - '151f7647f11d8ca2ae279fb842d60721' + - '7fce6e042f6815ea000c85741de5c8da' + - '1144a6a1aba7f96de42505d7a7298524' + - 'fda538fccbbb754f578c1cad10d54d0d' + - '5428407e85dcbc98a49155c13764e66c' + - '3c00', - }, -]; - -for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) { - const v = VECTORS_RFC8032_CTX[i]; - should(`RFC8032ctx/${i}`, () => { - deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey); - deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature); - deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true); - }); -} - -const VECTORS_RFC8032_PH = [ - { - secretKey: - '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49', - publicKey: - '259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880', - message: '616263', - signature: - '822f6901f7480f3d5f562c592994d969' + - '3602875614483256505600bbc281ae38' + - '1f54d6bce2ea911574932f52a4e6cadd' + - '78769375ec3ffd1b801a0d9b3f4030cd' + - '433964b6457ea39476511214f97469b5' + - '7dd32dbc560a9a94d00bff07620464a3' + - 'ad203df7dc7ce360c3cd3696d9d9fab9' + - '0f00', - }, - { - secretKey: - '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49', - publicKey: - '259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880', - message: '616263', - context: '666f6f', - signature: - 'c32299d46ec8ff02b54540982814dce9' + - 'a05812f81962b649d528095916a2aa48' + - '1065b1580423ef927ecf0af5888f90da' + - '0f6a9a85ad5dc3f280d91224ba9911a3' + - '653d00e484e2ce232521481c8658df30' + - '4bb7745a73514cdb9bf3e15784ab7128' + - '4f8d0704a608c54a6b62d97beb511d13' + - '2100', - }, -]; - -for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) { - const v = VECTORS_RFC8032_PH[i]; - should(`RFC8032ph/${i}`, () => { - deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey); - deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature); - deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true); - }); -} - -should('X448 base point', () => { - const { x, y } = ed448.Point.BASE; - const { Fp } = ed448.CURVE; - // const invX = Fp.invert(x * x); // x² - const u = Fp.div(Fp.create(y * y), Fp.create(x * x)); // (y²/x²) - // const u = Fp.create(y * y * invX); - deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu); }); // ESM is broken. diff --git a/test/hash-to-curve.test.js b/test/hash-to-curve.test.js index 5bdda88..3400767 100644 --- a/test/hash-to-curve.test.js +++ b/test/hash-to-curve.test.js @@ -1,5 +1,5 @@ import { deepStrictEqual } from 'assert'; -import { should } from 'micro-should'; +import { describe, should } from 'micro-should'; import { bytesToHex } from '@noble/hashes/utils'; // Generic tests for all curves in package import { sha256 } from '@noble/hashes/sha256'; @@ -51,43 +51,51 @@ import { default as ed448_ro } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL import { default as ed448_nu } from './hash-to-curve/edwards448_XOF:SHAKE256_ELL2_NU_.json' assert { type: 'json' }; function testExpandXMD(hash, vectors) { - for (let i = 0; i < vectors.tests.length; i++) { - const t = vectors.tests[i]; - should(`expand_message_xmd/${vectors.hash}/${vectors.DST.length}/${i}`, () => { - const p = expand_message_xmd( - stringToBytes(t.msg), - stringToBytes(vectors.DST), - t.len_in_bytes, - hash - ); - deepStrictEqual(bytesToHex(p), t.uniform_bytes); - }); - } + describe(`${vectors.hash}/${vectors.DST.length}`, () => { + for (let i = 0; i < vectors.tests.length; i++) { + const t = vectors.tests[i]; + should(`${vectors.hash}/${vectors.DST.length}/${i}`, () => { + const p = expand_message_xmd( + stringToBytes(t.msg), + stringToBytes(vectors.DST), + t.len_in_bytes, + hash + ); + deepStrictEqual(bytesToHex(p), t.uniform_bytes); + }); + } + }); } -testExpandXMD(sha256, xmd_sha256_38); -testExpandXMD(sha256, xmd_sha256_256); -testExpandXMD(sha512, xmd_sha512_38); +describe('expand_message_xmd', () => { + testExpandXMD(sha256, xmd_sha256_38); + testExpandXMD(sha256, xmd_sha256_256); + testExpandXMD(sha512, xmd_sha512_38); +}); function testExpandXOF(hash, vectors) { - for (let i = 0; i < vectors.tests.length; i++) { - const t = vectors.tests[i]; - should(`expand_message_xof/${vectors.hash}/${vectors.DST.length}/${i}`, () => { - const p = expand_message_xof( - stringToBytes(t.msg), - stringToBytes(vectors.DST), - +t.len_in_bytes, - vectors.k, - hash - ); - deepStrictEqual(bytesToHex(p), t.uniform_bytes); - }); - } + describe(`${vectors.hash}/${vectors.DST.length}`, () => { + for (let i = 0; i < vectors.tests.length; i++) { + const t = vectors.tests[i]; + should(`${i}`, () => { + const p = expand_message_xof( + stringToBytes(t.msg), + stringToBytes(vectors.DST), + +t.len_in_bytes, + vectors.k, + hash + ); + deepStrictEqual(bytesToHex(p), t.uniform_bytes); + }); + } + }); } -testExpandXOF(shake128, xof_shake128_36); -testExpandXOF(shake128, xof_shake128_256); -testExpandXOF(shake256, xof_shake256_36); +describe('expand_message_xof', () => { + testExpandXOF(shake128, xof_shake128_36); + testExpandXOF(shake128, xof_shake128_256); + testExpandXOF(shake256, xof_shake256_36); +}); function stringToFp(s) { // bls-G2 support @@ -99,26 +107,30 @@ function stringToFp(s) { } function testCurve(curve, ro, nu) { - for (let i = 0; i < ro.vectors.length; i++) { - const t = ro.vectors[i]; - should(`${ro.curve}/${ro.ciphersuite}(${i})`, () => { - const p = curve.Point.hashToCurve(stringToBytes(t.msg), { - DST: ro.dst, + describe(`${ro.curve}/${ro.ciphersuite}`, () => { + for (let i = 0; i < ro.vectors.length; i++) { + const t = ro.vectors[i]; + should(`(${i})`, () => { + const p = curve.Point.hashToCurve(stringToBytes(t.msg), { + DST: ro.dst, + }); + deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); + deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); }); - deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); - deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); - }); - } - for (let i = 0; i < nu.vectors.length; i++) { - const t = nu.vectors[i]; - should(`${nu.curve}/${nu.ciphersuite}(${i})`, () => { - const p = curve.Point.encodeToCurve(stringToBytes(t.msg), { - DST: nu.dst, + } + }); + describe(`${nu.curve}/${nu.ciphersuite}`, () => { + for (let i = 0; i < nu.vectors.length; i++) { + const t = nu.vectors[i]; + should(`(${i})`, () => { + const p = curve.Point.encodeToCurve(stringToBytes(t.msg), { + DST: nu.dst, + }); + deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); + deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); }); - deepStrictEqual(p.x, stringToFp(t.P.x), 'Px'); - deepStrictEqual(p.y, stringToFp(t.P.y), 'Py'); - }); - } + } + }); } testCurve(secp256r1, p256_ro, p256_nu); diff --git a/test/jubjub.test.js b/test/jubjub.test.js index dd42027..6bdd48c 100644 --- a/test/jubjub.test.js +++ b/test/jubjub.test.js @@ -1,5 +1,5 @@ import { jubjub, findGroupHash } from '../lib/esm/jubjub.js'; -import { should } from 'micro-should'; +import { describe, should } from 'micro-should'; import { deepStrictEqual, throws } from 'assert'; import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; @@ -18,53 +18,55 @@ const G_PROOF = new jubjub.ExtendedPoint( const getXY = (p) => ({ x: p.x, y: p.y }); -should('toHex/fromHex', () => { - // More than field - throws(() => - jubjub.Point.fromHex( +describe('jubjub', () => { + should('toHex/fromHex', () => { + // More than field + throws(() => + jubjub.Point.fromHex( + new Uint8Array([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]) + ) + ); + // Multiplicative generator (sqrt == null), not on curve. + throws(() => + jubjub.Point.fromHex( + new Uint8Array([ + 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ]) + ) + ); + const tmp = jubjub.Point.fromHex( new Uint8Array([ - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - ]) - ) - ); - // Multiplicative generator (sqrt == null), not on curve. - throws(() => - jubjub.Point.fromHex( - new Uint8Array([ - 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]) - ) - ); - const tmp = jubjub.Point.fromHex( - new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ]) - ); - deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n); - deepStrictEqual(tmp.y, 0n); + ); + deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n); + deepStrictEqual(tmp.y, 0n); - const S = G_SPEND.toAffine().toRawBytes(); - const S2 = G_SPEND.double().toAffine().toRawBytes(); - const P = G_PROOF.toAffine().toRawBytes(); - const P2 = G_PROOF.double().toAffine().toRawBytes(); - const S_exp = jubjub.Point.fromHex(S); - const S2_exp = jubjub.Point.fromHex(S2); - const P_exp = jubjub.Point.fromHex(P); - const P2_exp = jubjub.Point.fromHex(P2); - deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp)); - deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp)); - deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp)); - deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp)); -}); + const S = G_SPEND.toAffine().toRawBytes(); + const S2 = G_SPEND.double().toAffine().toRawBytes(); + const P = G_PROOF.toAffine().toRawBytes(); + const P2 = G_PROOF.double().toAffine().toRawBytes(); + const S_exp = jubjub.Point.fromHex(S); + const S2_exp = jubjub.Point.fromHex(S2); + const P_exp = jubjub.Point.fromHex(P); + const P2_exp = jubjub.Point.fromHex(P2); + deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp)); + deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp)); + deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp)); + deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp)); + }); -should('Find generators', () => { - const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])); - const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95])); - deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine())); - deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine())); + should('Find generators', () => { + const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95])); + const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95])); + deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine())); + deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine())); + }); }); // ESM is broken. diff --git a/test/nist.test.js b/test/nist.test.js index d56325a..4aef6c1 100644 --- a/test/nist.test.js +++ b/test/nist.test.js @@ -1,5 +1,5 @@ import { deepStrictEqual, throws } from 'assert'; -import { should } from 'micro-should'; +import { describe, should } from 'micro-should'; import { secp192r1, P192 } from '../lib/esm/p192.js'; import { secp224r1, P224 } from '../lib/esm/p224.js'; import { secp256r1, P256 } from '../lib/esm/p256.js'; @@ -344,19 +344,21 @@ function runWycheproof(name, CURVE, group, index) { for (const name in WYCHEPROOF_ECDSA) { const { curve, hashes } = WYCHEPROOF_ECDSA[name]; - for (const hName in hashes) { - const { hash, tests } = hashes[hName]; - const CURVE = curve.create(hash); - should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName}`, () => { - for (let i = 0; i < tests.length; i++) { - const groups = tests[i].testGroups; - for (let j = 0; j < groups.length; j++) { - const group = groups[j]; - runWycheproof(name, CURVE, group, `${i}/${j}`); + describe('Wycheproof/WYCHEPROOF_ECDSA', () => { + for (const hName in hashes) { + const { hash, tests } = hashes[hName]; + const CURVE = curve.create(hash); + should(`${name}/${hName}`, () => { + for (let i = 0; i < tests.length; i++) { + const groups = tests[i].testGroups; + for (let j = 0; j < groups.length; j++) { + const group = groups[j]; + runWycheproof(name, CURVE, group, `${i}/${j}`); + } } - } - }); - } + }); + } + }); } const hexToBigint = (hex) => BigInt(`0x${hex}`); diff --git a/test/secp256k1.test.js b/test/secp256k1.test.js index 7427399..2dc1ff7 100644 --- a/test/secp256k1.test.js +++ b/test/secp256k1.test.js @@ -7,7 +7,7 @@ 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 } from 'micro-should'; +import { should, describe } from 'micro-should'; import { deepStrictEqual, throws } from 'assert'; import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; @@ -30,512 +30,518 @@ function hexToNumber(hex) { return BigInt(`0x${hex}`); } -should('secp256k1.getPublicKey()', () => { - const data = privatesTxt - .split('\n') - .filter((line) => line) - .map((line) => line.split(':')); - for (let [priv, x, y] of data) { - const point = secp.Point.fromPrivateKey(BigInt(priv)); - deepStrictEqual(toBEHex(point.x), x); - deepStrictEqual(toBEHex(point.y), y); +describe('secp256k1', () => { + should('getPublicKey()', () => { + const data = privatesTxt + .split('\n') + .filter((line) => line) + .map((line) => line.split(':')); + for (let [priv, x, y] of data) { + const point = secp.Point.fromPrivateKey(BigInt(priv)); + deepStrictEqual(toBEHex(point.x), x); + deepStrictEqual(toBEHex(point.y), y); - const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); - deepStrictEqual(toBEHex(point2.x), x); - deepStrictEqual(toBEHex(point2.y), y); + const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); + deepStrictEqual(toBEHex(point2.x), x); + deepStrictEqual(toBEHex(point2.y), y); - const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); - deepStrictEqual(toBEHex(point3.x), x); - deepStrictEqual(toBEHex(point3.y), y); - } -}); -should('secp256k1.getPublicKey() rejects invalid keys', () => { - for (const item of INVALID_ITEMS) { - throws(() => secp.getPublicKey(item)); - } -}); -should('secp256k1.precompute', () => { - secp.utils.precompute(4); - const data = privatesTxt - .split('\n') - .filter((line) => line) - .map((line) => line.split(':')); - for (let [priv, x, y] of data) { - const point = secp.Point.fromPrivateKey(BigInt(priv)); - deepStrictEqual(toBEHex(point.x), x); - deepStrictEqual(toBEHex(point.y), y); - - const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); - deepStrictEqual(toBEHex(point2.x), x); - deepStrictEqual(toBEHex(point2.y), y); - - const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); - deepStrictEqual(toBEHex(point3.x), x); - deepStrictEqual(toBEHex(point3.y), y); - } -}); - -should('secp256k1.Point.isValidPoint()', () => { - for (const vector of points.valid.isPoint) { - const { P, expected } = vector; - if (expected) { - secp.Point.fromHex(P); - } else { - throws(() => secp.Point.fromHex(P)); - } - } -}); - -should('secp256k1.Point.fromPrivateKey()', () => { - for (const vector of points.valid.pointFromScalar) { - const { d, expected } = vector; - let p = secp.Point.fromPrivateKey(d); - deepStrictEqual(p.toHex(true), expected); - } -}); - -should('secp256k1.Point#toHex(compressed)', () => { - for (const vector of points.valid.pointCompress) { - const { P, compress, expected } = vector; - let p = secp.Point.fromHex(P); - deepStrictEqual(p.toHex(compress), expected); - } -}); - -should('secp256k1.Point#toHex() roundtrip (failed case)', () => { - const point1 = - secp.Point.fromPrivateKey( - 88572218780422190464634044548753414301110513745532121983949500266768436236425n - ); - // const hex = point1.toHex(true); - // deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); -}); - -should('secp256k1.Point#toHex() roundtrip', () => { - fc.assert( - fc.property(FC_BIGINT, (x) => { - const point1 = secp.Point.fromPrivateKey(x); - const hex = point1.toHex(true); - deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); - }) - ); -}); - -should('secp256k1.Point#add(other)', () => { - for (const vector of points.valid.pointAdd) { - const { P, Q, expected } = vector; - let p = secp.Point.fromHex(P); - let q = secp.Point.fromHex(Q); - if (expected) { - deepStrictEqual(p.add(q).toHex(true), expected); - } else { - if (!p.equals(q.negate())) { - throws(() => p.add(q).toHex(true)); - } - } - } -}); - -should('secp256k1.Point#multiply(privateKey)', () => { - for (const vector of points.valid.pointMultiply) { - const { P, d, expected } = vector; - const p = secp.Point.fromHex(P); - if (expected) { - deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected); - } else { - throws(() => { - p.multiply(hexToNumber(d)).toHex(true); - }); - } - } - - for (const vector of points.invalid.pointMultiply) { - const { P, d } = vector; - if (hexToNumber(d) < secp.CURVE.n) { - throws(() => { - const p = secp.Point.fromHex(P); - p.multiply(hexToNumber(d)).toHex(true); - }); - } - } - for (const num of [0n, 0, -1n, -1, 1.1]) { - throws(() => secp.Point.BASE.multiply(num)); - } -}); - -// multiply() should equal multiplyUnsafe() -// should('ProjectivePoint#multiplyUnsafe', () => { -// const p0 = new secp.ProjectivePoint( -// 55066263022277343669578718895168534326250603453777594175500187360389116729240n, -// 32670510020758816978083085130507043184471273380659243275938904335757337482424n, -// 1n -// ); -// const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n; -// console.log(p0.multiply(z)); -// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) -// }); - -should('secp256k1.Signature.fromCompactHex() roundtrip', () => { - fc.assert( - fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { - const sig = new secp.Signature(r, s); - deepStrictEqual(secp.Signature.fromCompact(sig.toCompactHex()), sig); - }) - ); -}); - -should('secp256k1.Signature.fromDERHex() roundtrip', () => { - fc.assert( - fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { - const sig = new secp.Signature(r, s); - deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig); - }) - ); -}); - -should('secp256k1.sign()/should create deterministic signatures with RFC 6979', () => { - for (const vector of ecdsa.valid) { - let usig = secp.sign(vector.m, vector.d); - let sig = usig.toCompactHex(); - const vsig = vector.signature; - deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64)); - deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128)); - } -}); - -should('secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', () => { - for (const vector of ecdsa.invalid.sign) { - throws(() => secp.sign(vector.m, vector.d)); - } -}); - -should('secp256k1.sign()/edge cases', () => { - throws(() => secp.sign()); - throws(() => secp.sign('')); -}); - -should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', () => { - const CASES = [ - [ - 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b', - '304402203de2559fccb00c148574997f660e4d6f40605acc71267ee38101abf15ff467af02200950abdf40628fd13f547792ba2fc544681a485f2fdafb5c3b909a4df7350e6b', - ], - [ - '5f97983254982546d3976d905c6165033976ee449d300d0e382099fa74deaf82', - '3045022100c046d9ff0bd2845b9aa9dff9f997ecebb31e52349f80fe5a5a869747d31dcb88022011f72be2a6d48fe716b825e4117747b397783df26914a58139c3f4c5cbb0e66c', - ], - [ - '0d7017a96b97cd9be21cf28aada639827b2814a654a478c81945857196187808', - '3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73', - ], - ]; - const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'); - 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); - } -}); -should('secp256k1.sign()/sign ecdsa extraData', () => { - const ent1 = '0000000000000000000000000000000000000000000000000000000000000000'; - const ent2 = '0000000000000000000000000000000000000000000000000000000000000001'; - const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33'; - const ent4 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; - const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - - for (const e of ecdsa.extraEntropy) { - const sign = (extraEntropy) => { - const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex(); - return s; - }; - deepStrictEqual(sign(), e.signature); - deepStrictEqual(sign(ent1), e.extraEntropy0); - deepStrictEqual(sign(ent2), e.extraEntropy1); - deepStrictEqual(sign(ent3), e.extraEntropyRand); - deepStrictEqual(sign(ent4), e.extraEntropyN); - deepStrictEqual(sign(ent5), e.extraEntropyMax); - } -}); - -should('secp256k1.verify()/should verify signature', () => { - const MSG = '01'.repeat(32); - const PRIV_KEY = 0x2n; - const signature = secp.sign(MSG, PRIV_KEY); - const publicKey = secp.getPublicKey(PRIV_KEY); - deepStrictEqual(publicKey.length, 65); - deepStrictEqual(secp.verify(signature, MSG, publicKey), true); -}); -should('secp256k1.verify()/should not verify signature with wrong public key', () => { - const MSG = '01'.repeat(32); - const PRIV_KEY = 0x2n; - const WRONG_PRIV_KEY = 0x22n; - const signature = secp.sign(MSG, PRIV_KEY); - const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex(); - deepStrictEqual(publicKey.length, 130); - deepStrictEqual(secp.verify(signature, MSG, publicKey), false); -}); -should('secp256k1.verify()/should not verify signature with wrong hash', () => { - const MSG = '01'.repeat(32); - const PRIV_KEY = 0x2n; - const WRONG_MSG = '11'.repeat(32); - const signature = secp.sign(MSG, PRIV_KEY); - const publicKey = secp.getPublicKey(PRIV_KEY); - deepStrictEqual(publicKey.length, 65); - deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false); -}); -should('secp256k1.verify()/should verify random signatures', () => - fc.assert( - fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => { - const pub = secp.getPublicKey(privKey); - const sig = secp.sign(msg, privKey); - deepStrictEqual(secp.verify(sig, msg, pub), true); - }) - ) -); -should('secp256k1.verify()/should not verify signature with invalid r/s', () => { - const msg = new Uint8Array([ - 0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1, 0xe3, - 0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60, 0x50, 0x23, - ]); - const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n; - const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n; - const r = 1n; - const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n; - - const pub = new secp.Point(x, y); - const signature = new secp.Signature(2n, 2n); - signature.r = r; - signature.s = s; - - const verified = secp.verify(signature, msg, pub); - // Verifies, but it shouldn't, because signature S > curve order - deepStrictEqual(verified, false); -}); -should('secp256k1.verify()/should not verify msg = curve order', () => { - const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; - const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n; - const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; - const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n; - const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n; - const pub = new secp.Point(x, y); - const sig = new secp.Signature(r, s); - deepStrictEqual(secp.verify(sig, msg, pub), false); -}); -should('secp256k1.verify()/should verify non-strict msg bb5a...', () => { - const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023'; - const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n; - const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; - const r = 432420386565659656852420866390673177323n; - const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; - const pub = new secp.Point(x, y); - const sig = new secp.Signature(r, s); - deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); -}); -should( - 'secp256k1.verify()/should not verify invalid deterministic signatures with RFC 6979', - () => { - for (const vector of ecdsa.invalid.verify) { - const res = secp.verify(vector.signature, vector.m, vector.Q); - deepStrictEqual(res, false); - } - } -); - -// 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(`sign with Schnorr scheme 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'); + const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); + deepStrictEqual(toBEHex(point3.x), x); + deepStrictEqual(toBEHex(point3.y), y); } }); -} - -should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', () => { - const message = '00000000000000000000000000000000000000000000000000000000deadbeef'; - const privateKey = 123456789n; - const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false); - const sig = secp.sign(message, privateKey); - const recoveredPubkey = sig.recoverPublicKey(message); - // const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery); - deepStrictEqual(recoveredPubkey !== null, true); - deepStrictEqual(recoveredPubkey.toHex(false), publicKey); - deepStrictEqual(secp.verify(sig, message, publicKey), true); -}); -should('secp256k1.recoverPublicKey()/should not recover zero points', () => { - const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; - const sig = - '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; - const recovery = 0; - throws(() => secp.recoverPublicKey(msgHash, sig, recovery)); -}); -should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => { - const privKey = secp.utils.randomPrivateKey(); - const pub = secp.getPublicKey(privKey); - const zeros = '0000000000000000000000000000000000000000000000000000000000000000'; - const sig = secp.sign(zeros, privKey, { recovered: true }); - const recoveredKey = sig.recoverPublicKey(zeros); - deepStrictEqual(recoveredKey.toRawBytes(), pub); -}); -should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => { - for (const vector of ecdsa.valid) { - let usig = secp.sign(vector.m, vector.d); - let sig = usig.toDERHex(); - const vpub = secp.getPublicKey(vector.d); - const recovered = usig.recoverPublicKey(vector.m); - deepStrictEqual(recovered.toHex(), hex(vpub)); - } -}); - -// TODO: Real implementation. -function derToPub(der) { - return der.slice(46); -} -should('secp256k1.getSharedSecret()/should produce correct results', () => { - // TODO: Once der is there, run all tests. - for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) { - if (vector.result === 'invalid' || vector.private.length !== 64) { - throws(() => { - secp.getSharedSecret(vector.private, derToPub(vector.public), true); - }); - } else if (vector.result === 'valid') { - const res = secp.getSharedSecret(vector.private, derToPub(vector.public), true); - deepStrictEqual(hex(res.slice(1)), `${vector.shared}`); + should('getPublicKey() rejects invalid keys', () => { + for (const item of INVALID_ITEMS) { + throws(() => secp.getPublicKey(item)); } - } -}); -should('secp256k1.getSharedSecret()/priv/pub order matters', () => { - for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) { - if (vector.result === 'valid') { - let priv = vector.private; - priv = priv.length === 66 ? priv.slice(2) : priv; - throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true)); - } - } -}); -should('secp256k1.getSharedSecret()/rejects invalid keys', () => { - throws(() => secp.getSharedSecret('01', '02')); -}); - -should('secp256k1.utils.isValidPrivateKey()', () => { - for (const vector of privates.valid.isPrivate) { - const { d, expected } = vector; - deepStrictEqual(secp.utils.isValidPrivateKey(d), expected); - } -}); -should('have proper curve equation in assertValidity()', () => { - throws(() => { - const { Fp } = secp.CURVE; - let point = new secp.Point(Fp.create(-2n), Fp.create(-1n)); - point.assertValidity(); }); -}); + should('precompute', () => { + secp.utils.precompute(4); + const data = privatesTxt + .split('\n') + .filter((line) => line) + .map((line) => line.split(':')); + for (let [priv, x, y] of data) { + const point = secp.Point.fromPrivateKey(BigInt(priv)); + deepStrictEqual(toBEHex(point.x), x); + deepStrictEqual(toBEHex(point.y), y); -const Fn = Fp(secp.CURVE.n); -const normal = secp.utils._normalizePrivateKey; -const tweakUtils = { - privateAdd: (privateKey, tweak) => { - const p = normal(privateKey); - const t = normal(tweak); - return secp.utils._bigintToBytes(Fn.create(p + t)); - }, + const point2 = secp.Point.fromHex(secp.getPublicKey(toBEHex(BigInt(priv)))); + deepStrictEqual(toBEHex(point2.x), x); + deepStrictEqual(toBEHex(point2.y), y); - privateNegate: (privateKey) => { - const p = normal(privateKey); - return secp.utils._bigintToBytes(Fn.negate(p)); - }, + const point3 = secp.Point.fromHex(secp.getPublicKey(hexToBytes(toBEHex(BigInt(priv))))); + deepStrictEqual(toBEHex(point3.x), x); + deepStrictEqual(toBEHex(point3.y), y); + } + }); - pointAddScalar: (p, tweak, isCompressed) => { - const P = secp.Point.fromHex(p); - const t = normal(tweak); - const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n); - if (!Q) throw new Error('Tweaked point at infinity'); - return Q.toRawBytes(isCompressed); - }, - - pointMultiply: (p, tweak, isCompressed) => { - const P = secp.Point.fromHex(p); - const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak); - const t = BigInt(`0x${h}`); - return P.multiply(t).toRawBytes(isCompressed); - }, -}; - -should('secp256k1.privateAdd()', () => { - for (const vector of privates.valid.add) { - const { a, b, expected } = vector; - deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected); - } -}); -should('secp256k1.privateNegate()', () => { - for (const vector of privates.valid.negate) { - const { a, expected } = vector; - deepStrictEqual(bytesToHex(tweakUtils.privateNegate(a)), expected); - } -}); -should('secp256k1.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); - } -}); -should('secp256k1.pointAddScalar() invalid', () => { - for (const vector of points.invalid.pointAddScalar) { - const { P, d, exception } = vector; - throws(() => tweakUtils.pointAddScalar(P, d)); - } -}); -should('secp256k1.pointMultiply()', () => { - for (const vector of points.valid.pointMultiply) { - const { P, d, expected } = vector; - deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected); - } -}); -should('secp256k1.pointMultiply() invalid', () => { - for (const vector of points.invalid.pointMultiply) { - const { P, d, exception } = vector; - throws(() => tweakUtils.pointMultiply(P, d)); - } -}); - -should('secp256k1.wychenproof vectors', () => { - for (let group of wp.testGroups) { - const pubKey = secp.Point.fromHex(group.key.uncompressed); - for (let test of group.tests) { - const m = secp.CURVE.hash(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()) { - deepStrictEqual(verified, false); - } else { - deepStrictEqual(verified, true); - } - } else if (test.result === 'invalid') { - let failed = false; - try { - const verified = secp.verify(test.sig, m, pubKey); - if (!verified) failed = true; - } catch (error) { - failed = true; - } - deepStrictEqual(failed, true); + should('Point.isValidPoint()', () => { + for (const vector of points.valid.isPoint) { + const { P, expected } = vector; + if (expected) { + secp.Point.fromHex(P); } else { - deepStrictEqual(false, true); + throws(() => secp.Point.fromHex(P)); } } + }); + + should('Point.fromPrivateKey()', () => { + for (const vector of points.valid.pointFromScalar) { + const { d, expected } = vector; + let p = secp.Point.fromPrivateKey(d); + deepStrictEqual(p.toHex(true), expected); + } + }); + + should('Point#toHex(compressed)', () => { + for (const vector of points.valid.pointCompress) { + const { P, compress, expected } = vector; + let p = secp.Point.fromHex(P); + deepStrictEqual(p.toHex(compress), expected); + } + }); + + should('Point#toHex() roundtrip (failed case)', () => { + const point1 = + secp.Point.fromPrivateKey( + 88572218780422190464634044548753414301110513745532121983949500266768436236425n + ); + // const hex = point1.toHex(true); + // deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); + }); + + should('Point#toHex() roundtrip', () => { + fc.assert( + fc.property(FC_BIGINT, (x) => { + const point1 = secp.Point.fromPrivateKey(x); + const hex = point1.toHex(true); + deepStrictEqual(secp.Point.fromHex(hex).toHex(true), hex); + }) + ); + }); + + should('Point#add(other)', () => { + for (const vector of points.valid.pointAdd) { + const { P, Q, expected } = vector; + let p = secp.Point.fromHex(P); + let q = secp.Point.fromHex(Q); + if (expected) { + deepStrictEqual(p.add(q).toHex(true), expected); + } else { + if (!p.equals(q.negate())) { + throws(() => p.add(q).toHex(true)); + } + } + } + }); + + should('Point#multiply(privateKey)', () => { + for (const vector of points.valid.pointMultiply) { + const { P, d, expected } = vector; + const p = secp.Point.fromHex(P); + if (expected) { + deepStrictEqual(p.multiply(hexToNumber(d)).toHex(true), expected); + } else { + throws(() => { + p.multiply(hexToNumber(d)).toHex(true); + }); + } + } + + for (const vector of points.invalid.pointMultiply) { + const { P, d } = vector; + if (hexToNumber(d) < secp.CURVE.n) { + throws(() => { + const p = secp.Point.fromHex(P); + p.multiply(hexToNumber(d)).toHex(true); + }); + } + } + for (const num of [0n, 0, -1n, -1, 1.1]) { + throws(() => secp.Point.BASE.multiply(num)); + } + }); + + // multiply() should equal multiplyUnsafe() + // should('ProjectivePoint#multiplyUnsafe', () => { + // const p0 = new secp.ProjectivePoint( + // 55066263022277343669578718895168534326250603453777594175500187360389116729240n, + // 32670510020758816978083085130507043184471273380659243275938904335757337482424n, + // 1n + // ); + // const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n; + // console.log(p0.multiply(z)); + // console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) + // }); + + should('Signature.fromCompactHex() roundtrip', () => { + fc.assert( + fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { + const sig = new secp.Signature(r, s); + deepStrictEqual(secp.Signature.fromCompact(sig.toCompactHex()), sig); + }) + ); + }); + + should('Signature.fromDERHex() roundtrip', () => { + fc.assert( + fc.property(FC_BIGINT, FC_BIGINT, (r, s) => { + const sig = new secp.Signature(r, s); + deepStrictEqual(secp.Signature.fromDER(sig.toDERHex()), sig); + }) + ); + }); + + should('sign()/should create deterministic signatures with RFC 6979', () => { + for (const vector of ecdsa.valid) { + let usig = secp.sign(vector.m, vector.d); + let sig = usig.toCompactHex(); + const vsig = vector.signature; + deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64)); + deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128)); + } + }); + + should( + 'secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', + () => { + for (const vector of ecdsa.invalid.sign) { + throws(() => secp.sign(vector.m, vector.d)); + } + } + ); + + should('sign()/edge cases', () => { + throws(() => secp.sign()); + throws(() => secp.sign('')); + }); + + should('sign()/should create correct DER encoding against libsecp256k1', () => { + const CASES = [ + [ + 'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b', + '304402203de2559fccb00c148574997f660e4d6f40605acc71267ee38101abf15ff467af02200950abdf40628fd13f547792ba2fc544681a485f2fdafb5c3b909a4df7350e6b', + ], + [ + '5f97983254982546d3976d905c6165033976ee449d300d0e382099fa74deaf82', + '3045022100c046d9ff0bd2845b9aa9dff9f997ecebb31e52349f80fe5a5a869747d31dcb88022011f72be2a6d48fe716b825e4117747b397783df26914a58139c3f4c5cbb0e66c', + ], + [ + '0d7017a96b97cd9be21cf28aada639827b2814a654a478c81945857196187808', + '3045022100d18990bba7832bb283e3ecf8700b67beb39acc73f4200ed1c331247c46edccc602202e5c8bbfe47ae159512c583b30a3fa86575cddc62527a03de7756517ae4c6c73', + ], + ]; + const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'); + 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); + } + }); + should('sign()/sign ecdsa extraData', () => { + const ent1 = '0000000000000000000000000000000000000000000000000000000000000000'; + const ent2 = '0000000000000000000000000000000000000000000000000000000000000001'; + const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33'; + const ent4 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; + const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + + for (const e of ecdsa.extraEntropy) { + const sign = (extraEntropy) => { + const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex(); + return s; + }; + deepStrictEqual(sign(), e.signature); + deepStrictEqual(sign(ent1), e.extraEntropy0); + deepStrictEqual(sign(ent2), e.extraEntropy1); + deepStrictEqual(sign(ent3), e.extraEntropyRand); + deepStrictEqual(sign(ent4), e.extraEntropyN); + deepStrictEqual(sign(ent5), e.extraEntropyMax); + } + }); + + should('verify()/should verify signature', () => { + const MSG = '01'.repeat(32); + const PRIV_KEY = 0x2n; + const signature = secp.sign(MSG, PRIV_KEY); + const publicKey = secp.getPublicKey(PRIV_KEY); + deepStrictEqual(publicKey.length, 65); + deepStrictEqual(secp.verify(signature, MSG, publicKey), true); + }); + should('verify()/should not verify signature with wrong public key', () => { + const MSG = '01'.repeat(32); + const PRIV_KEY = 0x2n; + const WRONG_PRIV_KEY = 0x22n; + const signature = secp.sign(MSG, PRIV_KEY); + const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex(); + deepStrictEqual(publicKey.length, 130); + deepStrictEqual(secp.verify(signature, MSG, publicKey), false); + }); + should('verify()/should not verify signature with wrong hash', () => { + const MSG = '01'.repeat(32); + const PRIV_KEY = 0x2n; + const WRONG_MSG = '11'.repeat(32); + const signature = secp.sign(MSG, PRIV_KEY); + const publicKey = secp.getPublicKey(PRIV_KEY); + deepStrictEqual(publicKey.length, 65); + deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false); + }); + should('verify()/should verify random signatures', () => + fc.assert( + fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => { + const pub = secp.getPublicKey(privKey); + const sig = secp.sign(msg, privKey); + deepStrictEqual(secp.verify(sig, msg, pub), true); + }) + ) + ); + should('verify()/should not verify signature with invalid r/s', () => { + const msg = new Uint8Array([ + 0xbb, 0x5a, 0x52, 0xf4, 0x2f, 0x9c, 0x92, 0x61, 0xed, 0x43, 0x61, 0xf5, 0x94, 0x22, 0xa1, + 0xe3, 0x00, 0x36, 0xe7, 0xc3, 0x2b, 0x27, 0x0c, 0x88, 0x07, 0xa4, 0x19, 0xfe, 0xca, 0x60, + 0x50, 0x23, + ]); + const x = 100260381870027870612475458630405506840396644859280795015145920502443964769584n; + const y = 41096923727651821103518389640356553930186852801619204169823347832429067794568n; + const r = 1n; + const s = 115792089237316195423570985008687907852837564279074904382605163141518162728904n; + + const pub = new secp.Point(x, y); + const signature = new secp.Signature(2n, 2n); + signature.r = r; + signature.s = s; + + const verified = secp.verify(signature, msg, pub); + // Verifies, but it shouldn't, because signature S > curve order + deepStrictEqual(verified, false); + }); + should('verify()/should not verify msg = curve order', () => { + const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'; + const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n; + const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n; + const r = 104546003225722045112039007203142344920046999340768276760147352389092131869133n; + const s = 96900796730960181123786672629079577025401317267213807243199432755332205217369n; + const pub = new secp.Point(x, y); + const sig = new secp.Signature(r, s); + deepStrictEqual(secp.verify(sig, msg, pub), false); + }); + should('verify()/should verify non-strict msg bb5a...', () => { + const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023'; + const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n; + const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n; + const r = 432420386565659656852420866390673177323n; + const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n; + const pub = new secp.Point(x, y); + const sig = new secp.Signature(r, s); + deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true); + }); + should( + 'secp256k1.verify()/should not verify invalid deterministic signatures with RFC 6979', + () => { + for (const vector of ecdsa.invalid.verify) { + const res = secp.verify(vector.signature, vector.m, vector.Q); + deepStrictEqual(res, false); + } + } + ); + + // 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(`sign with Schnorr scheme 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'); + } + }); } + + should('recoverPublicKey()/should recover public key from recovery bit', () => { + const message = '00000000000000000000000000000000000000000000000000000000deadbeef'; + const privateKey = 123456789n; + const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false); + const sig = secp.sign(message, privateKey); + const recoveredPubkey = sig.recoverPublicKey(message); + // const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery); + deepStrictEqual(recoveredPubkey !== null, true); + deepStrictEqual(recoveredPubkey.toHex(false), publicKey); + deepStrictEqual(secp.verify(sig, message, publicKey), true); + }); + should('recoverPublicKey()/should not recover zero points', () => { + const msgHash = '6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; + const sig = + '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9'; + const recovery = 0; + throws(() => secp.recoverPublicKey(msgHash, sig, recovery)); + }); + should('recoverPublicKey()/should handle all-zeros msghash', () => { + const privKey = secp.utils.randomPrivateKey(); + const pub = secp.getPublicKey(privKey); + const zeros = '0000000000000000000000000000000000000000000000000000000000000000'; + const sig = secp.sign(zeros, privKey, { recovered: true }); + const recoveredKey = sig.recoverPublicKey(zeros); + deepStrictEqual(recoveredKey.toRawBytes(), pub); + }); + should('recoverPublicKey()/should handle RFC 6979 vectors', () => { + for (const vector of ecdsa.valid) { + let usig = secp.sign(vector.m, vector.d); + let sig = usig.toDERHex(); + const vpub = secp.getPublicKey(vector.d); + const recovered = usig.recoverPublicKey(vector.m); + deepStrictEqual(recovered.toHex(), hex(vpub)); + } + }); + + // TODO: Real implementation. + function derToPub(der) { + return der.slice(46); + } + should('getSharedSecret()/should produce correct results', () => { + // TODO: Once der is there, run all tests. + for (const vector of ecdh.testGroups[0].tests.slice(0, 230)) { + if (vector.result === 'invalid' || vector.private.length !== 64) { + throws(() => { + secp.getSharedSecret(vector.private, derToPub(vector.public), true); + }); + } else if (vector.result === 'valid') { + const res = secp.getSharedSecret(vector.private, derToPub(vector.public), true); + deepStrictEqual(hex(res.slice(1)), `${vector.shared}`); + } + } + }); + should('getSharedSecret()/priv/pub order matters', () => { + for (const vector of ecdh.testGroups[0].tests.slice(0, 100)) { + if (vector.result === 'valid') { + let priv = vector.private; + priv = priv.length === 66 ? priv.slice(2) : priv; + throws(() => secp.getSharedSecret(derToPub(vector.public), priv, true)); + } + } + }); + should('getSharedSecret()/rejects invalid keys', () => { + throws(() => secp.getSharedSecret('01', '02')); + }); + + should('utils.isValidPrivateKey()', () => { + for (const vector of privates.valid.isPrivate) { + const { d, expected } = vector; + deepStrictEqual(secp.utils.isValidPrivateKey(d), expected); + } + }); + should('have proper curve equation in assertValidity()', () => { + throws(() => { + const { Fp } = secp.CURVE; + let point = new secp.Point(Fp.create(-2n), Fp.create(-1n)); + point.assertValidity(); + }); + }); + + const Fn = Fp(secp.CURVE.n); + const normal = secp.utils._normalizePrivateKey; + const tweakUtils = { + privateAdd: (privateKey, tweak) => { + const p = normal(privateKey); + const t = normal(tweak); + return secp.utils._bigintToBytes(Fn.create(p + t)); + }, + + privateNegate: (privateKey) => { + const p = normal(privateKey); + return secp.utils._bigintToBytes(Fn.negate(p)); + }, + + pointAddScalar: (p, tweak, isCompressed) => { + const P = secp.Point.fromHex(p); + const t = normal(tweak); + const Q = secp.Point.BASE.multiplyAndAddUnsafe(P, t, 1n); + if (!Q) throw new Error('Tweaked point at infinity'); + return Q.toRawBytes(isCompressed); + }, + + pointMultiply: (p, tweak, isCompressed) => { + const P = secp.Point.fromHex(p); + const h = typeof tweak === 'string' ? tweak : bytesToHex(tweak); + const t = BigInt(`0x${h}`); + return P.multiply(t).toRawBytes(isCompressed); + }, + }; + + should('privateAdd()', () => { + for (const vector of privates.valid.add) { + const { a, b, expected } = vector; + deepStrictEqual(bytesToHex(tweakUtils.privateAdd(a, b)), expected); + } + }); + should('privateNegate()', () => { + for (const vector of privates.valid.negate) { + const { a, expected } = vector; + deepStrictEqual(bytesToHex(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); + } + }); + should('pointAddScalar() invalid', () => { + for (const vector of points.invalid.pointAddScalar) { + const { P, d, exception } = vector; + throws(() => tweakUtils.pointAddScalar(P, d)); + } + }); + should('pointMultiply()', () => { + for (const vector of points.valid.pointMultiply) { + const { P, d, expected } = vector; + deepStrictEqual(bytesToHex(tweakUtils.pointMultiply(P, d, true)), expected); + } + }); + should('pointMultiply() invalid', () => { + for (const vector of points.invalid.pointMultiply) { + const { P, d, exception } = vector; + throws(() => tweakUtils.pointMultiply(P, d)); + } + }); + + should('wychenproof vectors', () => { + for (let group of wp.testGroups) { + const pubKey = secp.Point.fromHex(group.key.uncompressed); + for (let test of group.tests) { + const m = secp.CURVE.hash(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()) { + deepStrictEqual(verified, false); + } else { + deepStrictEqual(verified, true); + } + } else if (test.result === 'invalid') { + let failed = false; + try { + const verified = secp.verify(test.sig, m, pubKey); + if (!verified) failed = true; + } catch (error) { + failed = true; + } + deepStrictEqual(failed, true); + } else { + deepStrictEqual(false, true); + } + } + } + }); }); // ESM is broken.