diff --git a/scripts/test-bls12377-msm.ts b/scripts/test-bls12377-msm.ts new file mode 100644 index 00000000..e7deb1dd --- /dev/null +++ b/scripts/test-bls12377-msm.ts @@ -0,0 +1,54 @@ +import "../src/extra/fix-webcrypto.js"; +import { tic, toc } from "../src/extra/tictoc.js"; +import { + msm, + msmUtil, + Field, + CurveAffine, + Random, + Scalar, + Bigint, +} from "../src/concrete/bls12-377.js"; +import { bigintScalarsToMemory } from "../src/msm.js"; +import { checkOnCurve, msmDumbAffine } from "../src/extra/dumb-curve-affine.js"; +import assert from "node:assert/strict"; + +let n = Number(process.argv[2] ?? 8); +let N = 1 << n; +console.log(`running msm with 2^${n} = ${2 ** n} inputs`); + +tic("random points"); +let points = Field.getZeroPointers(N, CurveAffine.sizeAffine); +let scratch = Field.getPointers(40); +CurveAffine.randomPoints(scratch, points); + +let scalars = Random.randomScalars(N); +let scalarPtr = bigintScalarsToMemory(Scalar, scalars); +toc(); + +tic("convert points to bigint & check"); +let pointsBigint = points.map((gPtr) => { + let g = CurveAffine.toBigint(gPtr); + assert(checkOnCurve(g, Field.p, CurveAffine.b), "point on curve"); + return g; +}); +toc(); + +tic("msm (core)"); +let s0 = msm(scalarPtr, points[0], N); +let s = msmUtil.toAffineOutputBigint(scratch, s0); +toc(); + +tic("msm (bigint)"); +let s1 = Bigint.msm(scalars, pointsBigint); +toc(); + +assert.deepEqual(s, s1, "consistent with bigint version"); + +if (n < 12) { + tic("msm (simple, slow bigint impl)"); + let sBigint = msmDumbAffine(scalars, pointsBigint, Scalar, Field); + toc(); + assert.deepEqual(s, sBigint, "consistent results"); + console.log("results are consistent!"); +} diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts new file mode 100644 index 00000000..ed9d1975 --- /dev/null +++ b/scripts/test-bls12377.ts @@ -0,0 +1,212 @@ +// run with ts-node-esm +import "../src/extra/fix-webcrypto.js"; +import { BLS12_377 } from "../src/index.js"; +import { + assert, + bigintToBits, + extractBitSlice as extractBitSliceJS, +} from "../src/util.js"; +import { mod, modExp, modInverse } from "../src/field-util.js"; +import { G, q } from "../src/concrete/bls12-377.params.js"; + +let { Field, Scalar, CurveAffine, Random } = BLS12_377; +const { p } = Field; + +function toWasm(x0: bigint, x: number) { + Field.writeBigint(x, x0); + Field.toMontgomery(x); +} +function ofWasm([tmp]: number[], x: number) { + Field.multiply(tmp, x, Field.constants.one); + Field.reduce(tmp); + return mod(Field.readBigint(tmp), p); +} + +let [x, y, z, z_hi, ...scratch] = Field.getPointers(40); +let scratchScalar = Scalar.getPointers(10); + +let R = mod(1n << BigInt(Field.w * Field.n), p); +let Rinv = modInverse(R, p); + +function test() { + let x0 = Random.randomFieldx2(); + let y0 = Random.randomFieldx2(); + toWasm(x0, x); + toWasm(y0, y); + + // multiply + let z0 = mod(x0 * y0, p); + Field.multiply(z, x, y); + let z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("multiply"); + z0 = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn; // test overflow resistance + toWasm(z0, z); + z0 = mod(z0 * z0, p); + Field.multiply(z, z, z); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("multiply"); + + // square + z0 = mod(x0 * x0, p); + Field.square(z, x); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("square"); + + // leftShift + let k = 97; + z0 = 1n << BigInt(k); + // computes R^2 * 2^k / R = 2^k R, which is 2^k in montgomery form + Field.leftShift(z, Field.constants.R2, k); + z1 = ofWasm(scratch, z); + if (z1 !== z0) throw Error("leftShift"); + + // add + z0 = mod(x0 + y0, p); + Field.add(z, x, y); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("add"); + + // subtract + z0 = mod(x0 - y0, p); + Field.subtract(z, x, y); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("subtract"); + + // subtract plus 2p + z0 = mod(x0 - y0, p); + Field.subtractPositive(z, x, y); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("subtract"); + + // reduceInPlace + z0 = x0 >= p ? x0 - p : x0; + Field.copy(z, x); + Field.reduce(z); + z1 = ofWasm(scratch, z); + if (z0 !== z1) throw Error("reduceInPlace"); + + // isEqual + if (Field.isEqual(x, x) !== 1) throw Error("isEqual"); + if (Field.isEqual(x, y) !== 0) throw Error("isEqual"); + + // inverse + Field.inverse(scratch[0], z, x); + Field.multiply(z, z, x); + z1 = ofWasm(scratch, z); + if (z1 !== 1n) throw Error("inverse"); + z0 = modInverse(x0, p); + if (mod(z0 * x0, p) !== 1n) throw Error("inverse"); + + // sqrt + let exists0 = modExp(x0, (p - 1n) >> 1n, { p }) === 1n; + let exists1 = Field.sqrt(scratch, z, x); + if (exists0 !== exists1) throw Error("isSquare"); + if (exists0) { + let zsqrt = ofWasm(scratch, z); + Field.square(z, z); + z0 = ofWasm(scratch, z); + if (mod(zsqrt * zsqrt - x0, p) !== 0n) throw Error("sqrt"); + if (mod(z0 - x0, p) !== 0n) throw Error("sqrt"); + } + + // roots + let minus1 = Field.roots.at(-1)!; + if (Field.toBigint(minus1) !== p - 1n) throw Error("roots"); + + // makeOdd + Field.writeBigint(x, 5n << 120n); + Field.writeBigint(z, 3n); + Field.makeOdd(x, z); + x0 = Field.readBigint(x); + z0 = Field.readBigint(z); + if (!(x0 === 5n && z0 === 3n << 120n)) throw Error("makeOdd"); + + // extractBitSlice + let arr = new Uint8Array([0b0010_0110, 0b1101_0101, 0b1111_1111]); + let e = Error("extractBitSlice"); + if (extractBitSliceJS(arr, 2, 4) !== 0b10_01) throw e; + if (extractBitSliceJS(arr, 0, 2) !== 0b10) throw e; + if (extractBitSliceJS(arr, 0, 8) !== 0b0010_0110) throw e; + if (extractBitSliceJS(arr, 3, 9) !== 0b0101_0010_0) throw e; + if (extractBitSliceJS(arr, 8, 8) !== 0b1101_0101) throw e; + if (extractBitSliceJS(arr, 5, 3 + 8 + 2) !== 0b11_1101_0101_001) throw e; + if (extractBitSliceJS(arr, 16, 10) !== 0b1111_1111) throw e; + + // extractBitSlice (wasm) + let s = Scalar.getPointer(); + Scalar.writeBytes(scratchScalar, s, arr); + const { extractBitSlice } = Scalar; + e = Error("extractBitSlice (wasm)"); + if (extractBitSlice(s, 2, 4) !== 0b10_01) throw e; + if (extractBitSlice(s, 0, 2) !== 0b10) throw e; + if (extractBitSlice(s, 0, 8) !== 0b0010_0110) throw e; + if (extractBitSlice(s, 3, 9) !== 0b0101_0010_0) throw e; + if (extractBitSlice(s, 8, 8) !== 0b1101_0101) throw e; + if (extractBitSlice(s, 5, 3 + 8 + 2) !== 0b11_1101_0101_001) throw e; + if (extractBitSlice(s, 16, 10) !== 0b1111_1111) throw e; +} + +for (let i = 0; i < 100; i++) { + test(); + testCurve(); +} +for (let i = 0; i < 100; i++) { + let ok = Scalar.testDecomposeScalar(Random.randomScalar()); + if (!ok) throw Error("scalar decomposition"); +} + +testBatchMontgomery(); + +function testBatchMontgomery() { + let n = 1000; + let X = Field.getPointers(n); + let invX = Field.getPointers(n); + let scratch = Field.getPointers(10); + for (let i = 0; i < n; i++) { + let x0 = Random.randomFieldx2(); + Field.writeBigint(X[i], x0); + // compute inverses normally + Field.inverse(scratch[0], invX[i], X[i]); + } + // compute inverses as batch + let invX1 = Field.getPointers(n); + Field.batchInverse(scratch[0], invX1[0], X[0], n); + + // check that all inverses are equal + for (let i = 0; i < n; i++) { + let z0 = Field.readBigint(invX[i]); + let z1 = Field.readBigint(invX1[i]); + if (mod(z1 - z0, p) !== 0n) throw Error("batch inverse"); + + Field.reduce(invX1[i]); + Field.reduce(invX[i]); + if (!Field.isEqual(invX1[i], invX[i])) { + console.log({ + i, + z0, + z1, + invX0: Field.readBigint(invX[i]), + invX1: Field.readBigint(invX1[i]), + }); + throw Error("batch inverse / reduce"); + } + } +} + +function testCurve() { + // prepare inputs + let [g, qG] = Field.getPointers(2, CurveAffine.sizeAffine); + CurveAffine.writeBigint(g, G); + let qBits = bigintToBits(q); + + // scale and check + CurveAffine.scale(scratch, qG, g, qBits); + assert(CurveAffine.isZeroAffine(qG), "order*G = 0"); + + // create random point and check if it is in the subgroup + let [r, qR] = Field.getPointers(2, CurveAffine.sizeAffine); + CurveAffine.randomPoints(scratch, [r]); + assert(!CurveAffine.isZeroAffine(r), "random point R is not zero"); + CurveAffine.scale(scratch, qR, r, qBits); + assert(CurveAffine.isZeroAffine(qR), "order*h*R = 0"); +} diff --git a/src/concrete/bls12-377.params.ts b/src/concrete/bls12-377.params.ts new file mode 100644 index 00000000..6c315c2e --- /dev/null +++ b/src/concrete/bls12-377.params.ts @@ -0,0 +1,54 @@ +import { scale } from "../extra/dumb-curve-affine.js"; +import { mod, modExp, modInverse } from "../field-util.js"; +import { assert, bigintToBits } from "../util.js"; + +export { p, q, h, b, lambda, beta, nBits, nBytes, G }; + +const p = + 0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001n; +const q = 0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001n; + +// curve equation is y^2 = x^3 + 1 +const b = 1n; + +const nBits = 377; +const nBytes = 48; + +// cofactor +const h = 0x170b5d44300000000000000000000000n; + +// generator +const G = { + x: 0x008848defe740a67c8fc6225bf87ff5485951e2caa9d41bb188282c8bd37cb5cd5481512ffcd394eeab9b16eb21be9efn, + y: 0x01914a69c5102eff1f674f5d30afeec4bd7fb348ca3e52d96d182ad44fb82305c2fe3d3634a9591afd82de55559c8ea6n, + isInfinity: false, +}; + +const lambda = + 0x12ab655e9a2ca55660b44d1e5c37b00114885f32400000000000000000000000n; +const beta = + 0x1ae3a4617c510eabc8756ba8f8c524eb8882a75cc9bc8e359064ee822fb5bffd1e945779fffffffffffffffffffffffn; + +const debug = false; + +if (debug) { + // compute cube root in Fq (endo scalar) as lambda = x^(q - 1)/3 for some small x + const lambda_ = modExp(11n, (q - 1n) / 3n, { p: q }); + + assert(lambda === lambda_, "lambda is correct"); + const lambda2 = mod(lambda * lambda, q); + assert(mod(lambda * lambda2, q) === 1n, "lambda is a cube root"); + + // compute beta such that lambda * (x, y) = (beta * x, y) (endo base) + let lambdaBits = bigintToBits(lambda, 256); + let lambdaG = scale(lambdaBits, G, p); + assert(lambdaG.y === G.y, "multiplication by lambda is a cheap endomorphism"); + + const beta_ = mod(lambdaG.x * modInverse(G.x, p), p); + assert(beta === beta_, "beta is correct"); + assert(modExp(beta, 3n, { p }) === 1n, "beta is a cube root"); + + // note: since both phi1: p -> lambda*p and phi2: p -> (beta*p.x, p.y) are homomorphisms (easy to check), + // and they agree on a single point, they must agree on all points in the same subgroup: + // (phi1 - phi2)(s*G) = s*(phi1 - pgi2)(G) = 0 +} diff --git a/src/concrete/bls12-377.ts b/src/concrete/bls12-377.ts new file mode 100644 index 00000000..b25f7bec --- /dev/null +++ b/src/concrete/bls12-377.ts @@ -0,0 +1,42 @@ +import type * as W from "wasmati"; +import { randomGenerators } from "../field-util.js"; +import { p, q, b, beta, lambda, h } from "./bls12-377.params.js"; +import { createMsmField } from "../field-msm.js"; +import { createGeneralGlvScalar } from "../scalar-glv.js"; +import { createCurveAffine } from "../curve-affine.js"; +import { createCurveProjective } from "../curve-projective.js"; +import { createMsm } from "../msm.js"; +import { createBigintApi } from "../bigint.js"; + +export { Bigint, Field, Scalar, CurveAffine, CurveProjective, Random }; +export { msm, msmUnsafe, msmUtil }; + +const Field = await createMsmField(p, beta, 29); +const Scalar = await createGeneralGlvScalar(q, lambda, 29); +const CurveProjective = createCurveProjective(Field, h); +const CurveAffine = createCurveAffine(Field, CurveProjective, b); + +const { msm, msmUnsafe, msmBigint, ...msmUtil } = createMsm({ + Field, + Scalar, + CurveAffine, + CurveProjective, +}); + +const { randomField: randomScalar, randomFields: randomScalars } = + randomGenerators(q); + +const Random = { ...randomGenerators(p), randomScalar, randomScalars }; + +const Bigint_ = createBigintApi({ + Field, + Scalar, + CurveAffine, + CurveProjective, +}); +const Bigint = { + ...Bigint_, + msm: msmBigint, + randomFields: Random.randomFields, + randomScalars, +}; diff --git a/src/concrete/bls12-381.ts b/src/concrete/bls12-381.ts index 6d353228..357f948e 100644 --- a/src/concrete/bls12-381.ts +++ b/src/concrete/bls12-381.ts @@ -21,8 +21,8 @@ export { msm, msmUnsafe, msmUtil }; const Field = await createMsmField(p, beta, 30); const Scalar = await createGeneralGlvScalar(q, lambda, 29); -const CurveAffine = createCurveAffine(Field, 4n); const CurveProjective = createCurveProjective(Field); +const CurveAffine = createCurveAffine(Field, CurveProjective, 4n); const SpecialScalar = await createGlvScalar(q, lambda, 29); diff --git a/src/concrete/pasta.ts b/src/concrete/pasta.ts index 0863752b..cb664f0d 100644 --- a/src/concrete/pasta.ts +++ b/src/concrete/pasta.ts @@ -13,8 +13,8 @@ export { msm, msmUnsafe, msmUtil }; const Field = await createMsmField(p, beta, 29); const Scalar = await createGeneralGlvScalar(q, lambda, 29); -const CurveAffine = createCurveAffine(Field, b); const CurveProjective = createCurveProjective(Field); +const CurveAffine = createCurveAffine(Field, CurveProjective, b); const { msm, msmUnsafe, msmBigint, ...msmUtil } = createMsm({ Field, diff --git a/src/curve-affine.ts b/src/curve-affine.ts index 08e541c5..3896d93a 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -1,6 +1,7 @@ import type * as W from "wasmati"; // for type names import { MsmField } from "./field-msm.js"; import { randomGenerators } from "./field-util.js"; +import type { CurveProjective } from "./curve-projective.js"; export { createCurveAffine, CurveAffine }; @@ -49,7 +50,16 @@ type CurveAffine = ReturnType; * * over the `Field` */ -function createCurveAffine(Field: MsmField, b: bigint) { +function createCurveAffine( + Field: MsmField, + CurveProjective: CurveProjective, + b: bigint +) { + // write b to memory + let [bPtr] = Field.getStablePointers(1); + Field.writeBigint(bPtr, b); + Field.toMontgomery(bPtr); + const { sizeField, square, multiply, add, subtract, copy, memoryBytes, p } = Field; @@ -93,11 +103,37 @@ function createCurveAffine(Field: MsmField, b: bigint) { copy(yOut, y2); } - let { randomFields } = randomGenerators(p); + function scale( + [ + resultProj, + _ry, + _rz, + _rinf, + pointProj, + _py, + _pz, + _pinf, + ...scratch + ]: number[], + result: number, + point: number, + scalar: boolean[] + ) { + CurveProjective.affineToProjective(pointProj, point); + CurveProjective.scale(scratch, resultProj, pointProj, scalar); + CurveProjective.projectiveToAffine(scratch, result, resultProj); + } - let [bPtr] = Field.getStablePointers(1); - Field.writeBigint(bPtr, b); - Field.toMontgomery(bPtr); + function toSubgroupInPlace( + [tmp, _tmpy, _tmpz, _tmpInf, ...scratch]: number[], + point: number + ) { + if (CurveProjective.cofactor === 1n) return; + copyAffine(tmp, point); + scale(scratch, point, tmp, CurveProjective.cofactorBits); + } + + let { randomFields } = randomGenerators(p); /** * sample random curve points @@ -134,12 +170,18 @@ function createCurveAffine(Field: MsmField, b: bigint) { if (isSquare) break; add(x, x, Field.constants.mg1); } - setIsNonZeroAffine(x, true); + setIsNonZero(x, true); + } + + if (CurveProjective.cofactor !== 1n) { + for (let i = 0; i < n; i++) { + toSubgroupInPlace(scratch, points[i]); + } } return points; } - function isZeroAffine(pointer: number) { + function isZero(pointer: number) { return !memoryBytes[pointer + 2 * sizeField]; } @@ -151,13 +193,12 @@ function createCurveAffine(Field: MsmField, b: bigint) { return [pointer, pointer + sizeField]; } - function setIsNonZeroAffine(pointer: number, isNonZero: boolean) { + function setIsNonZero(pointer: number, isNonZero: boolean) { memoryBytes[pointer + 2 * sizeField] = Number(isNonZero); } function toBigint(point: number): BigintPoint { - let isZero = isZeroAffine(point); - if (isZero) return BigintPoint.zero; + if (isZero(point)) return BigintPoint.zero; let [x, y] = affineCoords(point); Field.fromMontgomery(x); Field.fromMontgomery(y); @@ -171,15 +212,31 @@ function createCurveAffine(Field: MsmField, b: bigint) { return pointBigint; } + function writeBigint(point: number, { x, y, isInfinity }: BigintPoint) { + if (isInfinity) { + setIsNonZero(point, false); + return; + } + let [xPtr, yPtr] = affineCoords(point); + Field.writeBigint(xPtr, x); + Field.writeBigint(yPtr, y); + Field.toMontgomery(xPtr); + Field.toMontgomery(yPtr); + setIsNonZero(point, true); + } + return { b, sizeAffine, doubleAffine, - isZeroAffine, + scale, + toSubgroupInPlace, + isZeroAffine: isZero, copyAffine, affineCoords, - setIsNonZeroAffine, + setIsNonZeroAffine: setIsNonZero, toBigint, + writeBigint, randomPoints, randomPointsBigint(n: number, { montgomery = false } = {}) { let memoryOffset = Field.getOffset(); diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 71ee939e..9cd98761 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -1,10 +1,14 @@ import { MsmField } from "./field-msm.js"; +import { bigintToBits } from "./util.js"; export { createCurveProjective, CurveProjective }; type CurveProjective = ReturnType; -function createCurveProjective(Field: MsmField) { +function createCurveProjective(Field: MsmField, cofactor = 1n) { + // convert the cofactor to bits + let cofactorBits = bigintToBits(cofactor); + const { sizeField, square, @@ -16,24 +20,24 @@ function createCurveProjective(Field: MsmField) { memoryBytes, } = Field; - let sizeProjective = 3 * sizeField + 4; + let size = 3 * sizeField + 4; /** - * projective point addition with assignement, P1 += P2 + * projective point addition with assignment, P1 += P2 * * @param scratch * @param P1 * @param P2 */ - function addAssignProjective(scratch: number[], P1: number, P2: number) { - if (isZeroProjective(P1)) { - copyProjective(P1, P2); + function addAssign(scratch: number[], P1: number, P2: number) { + if (isZero(P1)) { + copy(P1, P2); return; } - if (isZeroProjective(P2)) return; - setNonZeroProjective(P1); - let [X1, Y1, Z1] = projectiveCoords(P1); - let [X2, Y2, Z2] = projectiveCoords(P2); + if (isZero(P2)) return; + setNonZero(P1); + let [X1, Y1, Z1] = coords(P1); + let [X2, Y2, Z2] = coords(P2); let [Y2Z1, Y1Z2, X2Z1, X1Z2, Z1Z2, u, uu, v, vv, vvv, R] = scratch; // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-projective.html#addition-add-1998-cmo-2 // Y1Z2 = Y1*Z2 @@ -48,9 +52,14 @@ function createCurveProjective(Field: MsmField) { // double if the points are equal // x1*z2 = x2*z1 and y1*z2 = y2*z1 // <==> x1/z1 = x2/z2 and y1/z1 = y2/z2 - if (isEqual(X1Z2, X2Z1) && isEqual(Y1Z2, Y2Z1)) { - doubleInPlaceProjective(scratch, P1); - return; + if (isEqual(X1Z2, X2Z1)) { + if (isEqual(Y1Z2, Y2Z1)) { + doubleInPlace(scratch, P1); + return; + } else { + setZero(P1); + return; + } } // Z1Z2 = Z1*Z2 multiply(Z1Z2, Z1, Z2); @@ -89,9 +98,9 @@ function createCurveProjective(Field: MsmField) { * @param scratch * @param P */ - function doubleInPlaceProjective(scratch: number[], P: number) { - if (isZeroProjective(P)) return; - let [X1, Y1, Z1] = projectiveCoords(P); + function doubleInPlace(scratch: number[], P: number) { + if (isZero(P)) return; + let [X1, Y1, Z1] = coords(P); let [tmp, w, s, ss, sss, Rx2, Bx4, h] = scratch; // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-projective.html#doubling-dbl-1998-cmo-2 // w = 3*X1^2 @@ -127,14 +136,38 @@ function createCurveProjective(Field: MsmField) { multiply(Z1, sss, constants.mg8); // TODO efficient doubling } - function isZeroProjective(pointer: number) { + function scale( + scratch: number[], + result: number, + point: number, + scalar: boolean[] + ) { + setZero(result); + let n = scalar.length; + for (let i = n - 1; i >= 0; i--) { + if (scalar[i]) addAssign(scratch, result, point); + if (i === 0) break; + doubleInPlace(scratch, result); + } + } + + function toSubgroupInPlace( + [tmp, _tmpy, _tmpz, _tmpInf, ...scratch]: number[], + point: number + ) { + if (cofactor === 1n) return; + copy(tmp, point); + scale(scratch, point, tmp, cofactorBits); + } + + function isZero(pointer: number) { return !memoryBytes[pointer + 3 * sizeField]; } - function copyProjective(target: number, source: number) { - memoryBytes.copyWithin(target, source, source + sizeProjective); + function copy(target: number, source: number) { + memoryBytes.copyWithin(target, source, source + size); } - function copyAffineToProjective(P: number, A: number) { + function affineToProjective(P: number, A: number) { // x,y = x,y memoryBytes.copyWithin(P, A, A + 2 * sizeField); // z = 1 @@ -147,22 +180,79 @@ function createCurveProjective(Field: MsmField) { memoryBytes[P + 3 * sizeField] = memoryBytes[A + 2 * sizeField]; } - function projectiveCoords(pointer: number) { + function projectiveToAffine( + scratch: number[], + affine: number, + point: number + ) { + if (isZero(point)) { + memoryBytes[affine + 2 * sizeField] = 0; + return; + } + let zinv = scratch[0]; + let [x, y, z] = coords(point); + let xAffine = affine; + let yAffine = affine + sizeField; + // return x/z, y/z + Field.inverse(scratch[1], zinv, z); + multiply(xAffine, x, zinv); + multiply(yAffine, y, zinv); + memoryBytes[xAffine + 2 * sizeField] = 1; + } + + function coords(pointer: number) { return [pointer, pointer + sizeField, pointer + 2 * sizeField]; } - function setNonZeroProjective(pointer: number) { + function setNonZero(pointer: number) { memoryBytes[pointer + 3 * sizeField] = 1; } + function setZero(pointer: number) { + memoryBytes[pointer + 3 * sizeField] = 0; + } + + function toBigint(point: number): BigintPointProjective { + if (isZero(point)) return BigintPointProjective.zero; + let [x, y, z] = coords(point); + Field.fromMontgomery(x); + Field.fromMontgomery(y); + Field.fromMontgomery(z); + let pointBigint = { + x: Field.readBigint(x), + y: Field.readBigint(y), + z: Field.readBigint(z), + isInfinity: false, + }; + Field.toMontgomery(x); + Field.toMontgomery(y); + Field.toMontgomery(z); + return pointBigint; + } return { - addAssignProjective, - doubleInPlaceProjective, - sizeProjective, - isZeroProjective, - copyProjective, - copyAffineToProjective, - projectiveCoords, - setNonZeroProjective, + cofactor, + cofactorBits, + addAssign, + doubleInPlace, + scale, + toSubgroupInPlace, + toBigint, + sizeProjective: size, + isZero, + copy, + affineToProjective, + projectiveToAffine, + projectiveCoords: coords, + setNonZero, }; } + +type BigintPointProjective = { + x: bigint; + y: bigint; + z: bigint; + isInfinity: boolean; +}; +const BigintPointProjective = { + zero: { x: 0n, y: 1n, z: 0n, isInfinity: true }, +}; diff --git a/src/extra/dumb-curve-affine.ts b/src/extra/dumb-curve-affine.ts index 8821d387..324aa684 100644 --- a/src/extra/dumb-curve-affine.ts +++ b/src/extra/dumb-curve-affine.ts @@ -1,6 +1,6 @@ import { mod, modInverse } from "../field-util.js"; import type { BigintPoint } from "../msm.js"; -import { bigintToBits } from "../util.js"; +import { assert, bigintToBits } from "../util.js"; export { msmDumbAffine, doubleAffine, addAffine, scale, checkOnCurve }; @@ -36,7 +36,8 @@ function addAffine(G: BigintPoint, H: BigintPoint, p: bigint): BigintPoint { // G + G --> we double if (y1 === y2) return doubleAffine(G, p); // G - G --> return zero - if (y1 === -y2) return zero; + if (y1 === p - y2) return zero; + assert(false, "unreachable"); } // m = (y2 - y1)/(x2 - x1) let d = modInverse(x2 - x1, p); diff --git a/src/field-sqrt.ts b/src/field-sqrt.ts index 81595263..d4a19bef 100644 --- a/src/field-sqrt.ts +++ b/src/field-sqrt.ts @@ -53,7 +53,7 @@ function createSqrt( wasm.add(z, z, constants.mg1); } - // roots of unity w = z^t, w^2, ..., w^(2^(S-1)) = -1 + // roots of unity w = z^t, w^2, ..., w^(2^(M-1)) = -1 let roots = helpers.getStablePointers(M); pow(scratch, roots[0], z, t); for (let i = 1; i < M; i++) { @@ -107,7 +107,8 @@ function createSqrt( // sqrt implementation that speeds up the discrete log part by caching more roots of unity // after Daniel Bernstein, http://cr.yp.to/papers/sqroot.pdf - // parameters for windowed representation of epxonents in roots subgroup + // parameters for windowed representation of exponents in roots subgroup + let c = Math.min(4, M); // window/limb size let L = 1 << c; let N = Math.ceil(M / c); // number of windows/limbs @@ -116,7 +117,9 @@ function createSqrt( // w_ij = w^(-j*2^(ic)) for i=0,...,N-1 and j=0,...,L-1 = 2^c-1 let inverseRoots = mapRange(N, () => helpers.getStablePointers(L)); - // v_j = w^(j*2^((n-1)c)), j=0,...,L-1 = all Lth roots + // v_j = w^(j*2^((N-1)c)), j=0,...,L-1 = all Lth roots + // TODO: this assumes tnat window size exactly divides M, i.e. M = Nc. if it doesn't, + // LthRoots[j] = w^(2^(j(N-1)c)) will actually be 2^(M-(N-1)c)th roots and won't be L unique values let LthRoots = helpers.getStablePointers(L); // w00 = 1, w_01 = w^(-1) @@ -162,7 +165,10 @@ function createSqrt( } // assert that all the Lth roots have different low limbs (they should be ~random) // console.log(LthRootLookup); - assert(Object.keys(LthRootLookup).length === L); + if (Object.keys(LthRootLookup).length !== L) { + // console.warn("can't use fastSqrt, Lth roots have collisions"); + return { sqrt, t, roots }; + } // lth root v_j --> j function lookupLthRoot(ptr: number) { diff --git a/src/index.ts b/src/index.ts index 5b24ea9d..3ac572c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export * as BLS12_381 from "./concrete/bls12-381.js"; +export * as BLS12_377 from "./concrete/bls12-377.js"; export * as Pallas from "./concrete/pasta.js"; export * as zprize from "./msm-bls12-zprize.js"; diff --git a/src/msm-bls12-zprize.ts b/src/msm-bls12-zprize.ts index 4d8c4472..fd81e30b 100644 --- a/src/msm-bls12-zprize.ts +++ b/src/msm-bls12-zprize.ts @@ -55,11 +55,11 @@ let { sizeAffine, doubleAffine, isZeroAffine, copyAffine, setIsNonZeroAffine } = let { sizeProjective, - addAssignProjective, - doubleInPlaceProjective, - isZeroProjective, - copyProjective, - copyAffineToProjective, + addAssign: addAssignProjective, + doubleInPlace: doubleInPlaceProjective, + isZero: isZeroProjective, + copy: copyProjective, + affineToProjective, projectiveCoords, } = CurveProjective; @@ -686,7 +686,7 @@ function reduceBucketsAffine( let partialSums = getZeroPointers(K, sizeProjective); for (let k = 0; k < K; k++) { if (isZeroAffine(buckets[k][1])) continue; - copyAffineToProjective(partialSums[k], buckets[k][1]); + affineToProjective(partialSums[k], buckets[k][1]); } return partialSums; } diff --git a/src/msm.ts b/src/msm.ts index 2467ba4f..f5f3ffd5 100644 --- a/src/msm.ts +++ b/src/msm.ts @@ -136,11 +136,11 @@ function createMsm({ Field, Scalar, CurveAffine, CurveProjective }: MsmCurve) { let { sizeProjective, - addAssignProjective, - doubleInPlaceProjective, - isZeroProjective, - copyProjective, - copyAffineToProjective, + addAssign: addAssignProjective, + doubleInPlace: doubleInPlaceProjective, + isZero: isZeroProjective, + copy: copyProjective, + affineToProjective, projectiveCoords, } = CurveProjective; @@ -646,7 +646,7 @@ function createMsm({ Field, Scalar, CurveAffine, CurveProjective }: MsmCurve) { let partialSums = getZeroPointers(K, sizeProjective); for (let k = 0; k < K; k++) { if (isZeroAffine(buckets[k][1])) continue; - copyAffineToProjective(partialSums[k], buckets[k][1]); + affineToProjective(partialSums[k], buckets[k][1]); } return partialSums; } diff --git a/src/util.ts b/src/util.ts index 501ae828..15162a2b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -72,7 +72,7 @@ function uint8ArrayToBigUint64(x8: Uint8Array): BigUint64Array { return x; } -function bigintToBits(x: bigint, bitLength: number): boolean[] { +function bigintToBits(x: bigint, bitLength?: number): boolean[] { let bits = Array(bitLength || 0); for (let i = 0; bitLength ? i < bitLength : x > 0n; i++) { bits[i] = !!Number(x & 1n); @@ -230,7 +230,7 @@ function bytesEqual(b1: Uint8Array, b2: Uint8Array) { return true; } -function assert(condition: boolean, message?: string) { +function assert(condition: boolean, message?: string): asserts condition { if (!condition) throw Error( message === undefined