From d0b850a7ad459d12de0ac725111eaa28afa127f3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 28 Oct 2023 22:03:26 +0200 Subject: [PATCH 01/18] add bls12-377 params including for endomorphism --- src/concrete/bls12-377.params.ts | 54 ++++++++++++++++++++++++++++++++ src/util.ts | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/concrete/bls12-377.params.ts diff --git a/src/concrete/bls12-377.params.ts b/src/concrete/bls12-377.params.ts new file mode 100644 index 00000000..c6f2ee2f --- /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, randomField } from "../field-util.js"; +import { assert, bigintToBits } from "../util.js"; + +export { p, q, 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; + +// 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"); + + // confirm endomorphism with random point + let r = randomField(q, 32, 0b00111111); + let rG = scale(bigintToBits(r, 256), G, p); + let lambdarG = scale(lambdaBits, rG, p); + assert(lambdarG.x === mod(beta * rG.x, p), "confirm endomorphism"); + assert(lambdarG.y === rG.y, "confirm endomorphism"); +} diff --git a/src/util.ts b/src/util.ts index 501ae828..9928502d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -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 From 9d51342bd10a5fe9be0f545473552283766af2e5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 08:37:57 +0100 Subject: [PATCH 02/18] adapt sqrt to not just fail --- src/field-sqrt.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/field-sqrt.ts b/src/field-sqrt.ts index 81595263..26320425 100644 --- a/src/field-sqrt.ts +++ b/src/field-sqrt.ts @@ -162,7 +162,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) { From eff59f2c4d241b0306527b12281fbc08106cef78 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 09:51:40 +0100 Subject: [PATCH 03/18] sqrt comment --- src/field-sqrt.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/field-sqrt.ts b/src/field-sqrt.ts index 26320425..83b53b1a 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) From 3dba656b371ec1ff8e59ed6532a89d2fcb4dbeeb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Oct 2023 09:52:13 +0100 Subject: [PATCH 04/18] start instantiating and testing 277. we're missing subgroup handling --- scripts/test-bls12377-msm.ts | 54 ++++++++++++++++++++++++++++++++++++ src/concrete/bls12-377.ts | 42 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 scripts/test-bls12377-msm.ts create mode 100644 src/concrete/bls12-377.ts diff --git a/scripts/test-bls12377-msm.ts b/scripts/test-bls12377-msm.ts new file mode 100644 index 00000000..3b41810e --- /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(20); +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/src/concrete/bls12-377.ts b/src/concrete/bls12-377.ts new file mode 100644 index 00000000..e6257136 --- /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 } 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 CurveAffine = createCurveAffine(Field, b); +const CurveProjective = createCurveProjective(Field); + +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, +}; From 13c4c19eff1681b2a9d290cbb9d217934786b93c Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 2 Dec 2023 21:55:55 +0100 Subject: [PATCH 05/18] test 377 --- scripts/test-bls12377.ts | 188 +++++++++++++++++++++++++++++++ src/concrete/bls12-377.params.ts | 16 +-- src/field-sqrt.ts | 2 +- src/index.ts | 1 + 4 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 scripts/test-bls12377.ts diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts new file mode 100644 index 00000000..175bb04f --- /dev/null +++ b/scripts/test-bls12377.ts @@ -0,0 +1,188 @@ +// run with ts-node-esm +import "../src/extra/fix-webcrypto.js"; +import { BLS12_377 } from "../src/index.js"; +import { extractBitSlice as extractBitSliceJS } from "../src/util.js"; +import { mod, modExp, modInverse } from "../src/field-util.js"; + +let { Field, Scalar, CurveProjective, msmUtil, Random, Bigint } = 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(10); +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(); +} +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"); + } + } +} diff --git a/src/concrete/bls12-377.params.ts b/src/concrete/bls12-377.params.ts index c6f2ee2f..6c315c2e 100644 --- a/src/concrete/bls12-377.params.ts +++ b/src/concrete/bls12-377.params.ts @@ -1,8 +1,8 @@ import { scale } from "../extra/dumb-curve-affine.js"; -import { mod, modExp, modInverse, randomField } from "../field-util.js"; +import { mod, modExp, modInverse } from "../field-util.js"; import { assert, bigintToBits } from "../util.js"; -export { p, q, b, lambda, beta, nBits, nBytes, G }; +export { p, q, h, b, lambda, beta, nBits, nBytes, G }; const p = 0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001n; @@ -14,6 +14,9 @@ const b = 1n; const nBits = 377; const nBytes = 48; +// cofactor +const h = 0x170b5d44300000000000000000000000n; + // generator const G = { x: 0x008848defe740a67c8fc6225bf87ff5485951e2caa9d41bb188282c8bd37cb5cd5481512ffcd394eeab9b16eb21be9efn, @@ -45,10 +48,7 @@ if (debug) { assert(beta === beta_, "beta is correct"); assert(modExp(beta, 3n, { p }) === 1n, "beta is a cube root"); - // confirm endomorphism with random point - let r = randomField(q, 32, 0b00111111); - let rG = scale(bigintToBits(r, 256), G, p); - let lambdarG = scale(lambdaBits, rG, p); - assert(lambdarG.x === mod(beta * rG.x, p), "confirm endomorphism"); - assert(lambdarG.y === rG.y, "confirm endomorphism"); + // 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/field-sqrt.ts b/src/field-sqrt.ts index 83b53b1a..d4a19bef 100644 --- a/src/field-sqrt.ts +++ b/src/field-sqrt.ts @@ -166,7 +166,7 @@ function createSqrt( // assert that all the Lth roots have different low limbs (they should be ~random) // console.log(LthRootLookup); if (Object.keys(LthRootLookup).length !== L) { - console.warn("can't use fastSqrt, Lth roots have collisions"); + // console.warn("can't use fastSqrt, Lth roots have collisions"); return { sqrt, t, roots }; } 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"; From f4e7aa9b7c56d5c7f2d9d8aaf49aaf8ceb173419 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sat, 2 Dec 2023 22:02:47 +0100 Subject: [PATCH 06/18] failing cofactor test --- scripts/test-bls12377.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 175bb04f..897765d1 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -1,8 +1,14 @@ // run with ts-node-esm import "../src/extra/fix-webcrypto.js"; import { BLS12_377 } from "../src/index.js"; -import { extractBitSlice as extractBitSliceJS } from "../src/util.js"; +import { + assert, + bigintToBits, + extractBitSlice as extractBitSliceJS, +} from "../src/util.js"; import { mod, modExp, modInverse } from "../src/field-util.js"; +import { scale } from "../src/extra/dumb-curve-affine.js"; +import { G, h, nBits } from "../src/concrete/bls12-377.params.js"; let { Field, Scalar, CurveProjective, msmUtil, Random, Bigint } = BLS12_377; const { p } = Field; @@ -186,3 +192,9 @@ function testBatchMontgomery() { } } } + +let hbits = bigintToBits(h, nBits); +let hG = scale(hbits, G, p); +console.log({ G, hG }); +assert(hG.x === G.x, "multiplication by h leaves G unchanged"); +assert(hG.y === G.y, "multiplication by h leaves G unchanged"); From 0a888246912cb2661356f18b44904270dd64f904 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 16:41:15 +0100 Subject: [PATCH 07/18] add working scale test --- scripts/test-bls12377.ts | 12 ++++++------ src/extra/dumb-curve-affine.ts | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 897765d1..f1a744fa 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -5,10 +5,11 @@ import { assert, bigintToBits, extractBitSlice as extractBitSliceJS, + log2, } from "../src/util.js"; import { mod, modExp, modInverse } from "../src/field-util.js"; import { scale } from "../src/extra/dumb-curve-affine.js"; -import { G, h, nBits } from "../src/concrete/bls12-377.params.js"; +import { G, q } from "../src/concrete/bls12-377.params.js"; let { Field, Scalar, CurveProjective, msmUtil, Random, Bigint } = BLS12_377; const { p } = Field; @@ -193,8 +194,7 @@ function testBatchMontgomery() { } } -let hbits = bigintToBits(h, nBits); -let hG = scale(hbits, G, p); -console.log({ G, hG }); -assert(hG.x === G.x, "multiplication by h leaves G unchanged"); -assert(hG.y === G.y, "multiplication by h leaves G unchanged"); +let qbits = bigintToBits(q, log2(q)); +let qG = scale(qbits, G, p); +assert(qG.x === 0n, "order*G = 0"); +assert(qG.y === 0n, "order*G = 0"); 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); From 73c8c39f47dc1eb050c73413eefefdbdecd3a0e9 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 16:42:16 +0100 Subject: [PATCH 08/18] remove imports --- scripts/test-bls12377.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index f1a744fa..a9ec0890 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -11,7 +11,7 @@ import { mod, modExp, modInverse } from "../src/field-util.js"; import { scale } from "../src/extra/dumb-curve-affine.js"; import { G, q } from "../src/concrete/bls12-377.params.js"; -let { Field, Scalar, CurveProjective, msmUtil, Random, Bigint } = BLS12_377; +let { Field, Scalar, Random } = BLS12_377; const { p } = Field; function toWasm(x0: bigint, x: number) { From ee3f3584f32c5749a4209422755856611f4862e4 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 17:14:44 +0100 Subject: [PATCH 09/18] projective scale --- src/curve-projective.ts | 73 ++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 71ee939e..8cd91e2f 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -16,24 +16,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 @@ -49,7 +49,7 @@ function createCurveProjective(Field: MsmField) { // 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); + doubleInPlace(scratch, P1); return; } // Z1Z2 = Z1*Z2 @@ -89,9 +89,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 +127,29 @@ function createCurveProjective(Field: MsmField) { multiply(Z1, sss, constants.mg8); // TODO efficient doubling } - function isZeroProjective(pointer: number) { + function scale( + scratch: number[], + result: number, + scalar: boolean[], + point: number + ) { + 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 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 +162,26 @@ function createCurveProjective(Field: MsmField) { memoryBytes[P + 3 * sizeField] = memoryBytes[A + 2 * sizeField]; } - function projectiveCoords(pointer: number) { + 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; + } return { - addAssignProjective, - doubleInPlaceProjective, - sizeProjective, - isZeroProjective, - copyProjective, - copyAffineToProjective, - projectiveCoords, - setNonZeroProjective, + addAssignProjective: addAssign, + doubleInPlaceProjective: doubleInPlace, + scale, + sizeProjective: size, + isZeroProjective: isZero, + copyProjective: copy, + copyAffineToProjective: affineToProjective, + projectiveCoords: coords, + setNonZeroProjective: setNonZero, }; } From 08d0b06312a561855eecefc8035e68212b797a66 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 17:55:51 +0100 Subject: [PATCH 10/18] debug projective scale --- scripts/test-bls12377.ts | 20 ++++++++++++----- src/curve-affine.ts | 20 ++++++++++++++--- src/curve-projective.ts | 47 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index a9ec0890..68fa9343 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -8,10 +8,9 @@ import { log2, } from "../src/util.js"; import { mod, modExp, modInverse } from "../src/field-util.js"; -import { scale } from "../src/extra/dumb-curve-affine.js"; import { G, q } from "../src/concrete/bls12-377.params.js"; -let { Field, Scalar, Random } = BLS12_377; +let { Field, Scalar, CurveProjective, CurveAffine, Random } = BLS12_377; const { p } = Field; function toWasm(x0: bigint, x: number) { @@ -194,7 +193,16 @@ function testBatchMontgomery() { } } -let qbits = bigintToBits(q, log2(q)); -let qG = scale(qbits, G, p); -assert(qG.x === 0n, "order*G = 0"); -assert(qG.y === 0n, "order*G = 0"); +// convert G to projective +let gAffine = Field.getPointer(CurveAffine.sizeAffine); +let g = Field.getPointer(CurveProjective.sizeProjective); +CurveAffine.writeBigint(gAffine, G); +CurveProjective.copyAffineToProjective(g, gAffine); + +// prepare inputs +let qBits = bigintToBits(q, log2(q)); +let qG = Field.getPointer(CurveProjective.sizeProjective); + +// scale and check +CurveProjective.scale(scratch, qG, qBits, g); +assert(CurveProjective.isZeroProjective(qG), "order*G = 0"); diff --git a/src/curve-affine.ts b/src/curve-affine.ts index 08e541c5..776e48fa 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -134,7 +134,7 @@ function createCurveAffine(Field: MsmField, b: bigint) { if (isSquare) break; add(x, x, Field.constants.mg1); } - setIsNonZeroAffine(x, true); + setIsNonZero(x, true); } return points; } @@ -151,7 +151,7 @@ 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); } @@ -171,6 +171,19 @@ 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, @@ -178,8 +191,9 @@ function createCurveAffine(Field: MsmField, b: bigint) { isZeroAffine, 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 8cd91e2f..d461e226 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -48,9 +48,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)) { - doubleInPlace(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); @@ -112,6 +117,7 @@ function createCurveProjective(Field: MsmField) { add(Bx4, Bx4, Bx4); // h = w^2-8*B = w^2 - Bx4 - Bx4 square(h, w); + console.log("h", Field.readBigint(h)); subtract(h, h, Bx4); // TODO efficient doubling subtract(h, h, Bx4); // X3 = 2*h*s @@ -133,12 +139,16 @@ function createCurveProjective(Field: MsmField) { scalar: boolean[], point: number ) { + console.log("point", toBigint(point)); setZero(result); let n = scalar.length; + console.log(n, toBigint(result)); for (let i = n - 1; i >= 0; i--) { if (scalar[i]) addAssign(scratch, result, point); + if (i > 245) console.log(i, toBigint(result)); if (i === 0) break; - doubleInPlace(scratch, result); + if (i > 245) doubleInPlace(scratch, result); + console.log(i, toBigint(result)); } } @@ -173,10 +183,29 @@ function createCurveProjective(Field: MsmField) { 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: addAssign, doubleInPlaceProjective: doubleInPlace, scale, + toBigint, sizeProjective: size, isZeroProjective: isZero, copyProjective: copy, @@ -185,3 +214,13 @@ function createCurveProjective(Field: MsmField) { setNonZeroProjective: setNonZero, }; } + +type BigintPointProjective = { + x: bigint; + y: bigint; + z: bigint; + isInfinity: boolean; +}; +const BigintPointProjective = { + zero: { x: 0n, y: 1n, z: 0n, isInfinity: true }, +}; From d44982bf18ea121efa7a2864a88041b315435fd1 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 20:20:41 +0100 Subject: [PATCH 11/18] make it work --- scripts/test-bls12377.ts | 2 +- src/curve-projective.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 68fa9343..78ddc742 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -23,7 +23,7 @@ function ofWasm([tmp]: number[], x: number) { return mod(Field.readBigint(tmp), p); } -let [x, y, z, z_hi, ...scratch] = Field.getPointers(10); +let [x, y, z, z_hi, ...scratch] = Field.getPointers(20); let scratchScalar = Scalar.getPointers(10); let R = mod(1n << BigInt(Field.w * Field.n), p); diff --git a/src/curve-projective.ts b/src/curve-projective.ts index d461e226..fe1cb797 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -117,7 +117,6 @@ function createCurveProjective(Field: MsmField) { add(Bx4, Bx4, Bx4); // h = w^2-8*B = w^2 - Bx4 - Bx4 square(h, w); - console.log("h", Field.readBigint(h)); subtract(h, h, Bx4); // TODO efficient doubling subtract(h, h, Bx4); // X3 = 2*h*s @@ -139,16 +138,12 @@ function createCurveProjective(Field: MsmField) { scalar: boolean[], point: number ) { - console.log("point", toBigint(point)); setZero(result); let n = scalar.length; - console.log(n, toBigint(result)); for (let i = n - 1; i >= 0; i--) { if (scalar[i]) addAssign(scratch, result, point); - if (i > 245) console.log(i, toBigint(result)); if (i === 0) break; - if (i > 245) doubleInPlace(scratch, result); - console.log(i, toBigint(result)); + doubleInPlace(scratch, result); } } From 4bb5d90b8afbe84cb9f01ce805e3d5bc5c77316b Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 20:26:36 +0100 Subject: [PATCH 12/18] rename --- scripts/test-bls12377.ts | 4 ++-- src/curve-projective.ts | 12 ++++++------ src/msm-bls12-zprize.ts | 12 ++++++------ src/msm.ts | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 78ddc742..36642a43 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -197,7 +197,7 @@ function testBatchMontgomery() { let gAffine = Field.getPointer(CurveAffine.sizeAffine); let g = Field.getPointer(CurveProjective.sizeProjective); CurveAffine.writeBigint(gAffine, G); -CurveProjective.copyAffineToProjective(g, gAffine); +CurveProjective.affineToProjective(g, gAffine); // prepare inputs let qBits = bigintToBits(q, log2(q)); @@ -205,4 +205,4 @@ let qG = Field.getPointer(CurveProjective.sizeProjective); // scale and check CurveProjective.scale(scratch, qG, qBits, g); -assert(CurveProjective.isZeroProjective(qG), "order*G = 0"); +assert(CurveProjective.isZero(qG), "order*G = 0"); diff --git a/src/curve-projective.ts b/src/curve-projective.ts index fe1cb797..7795c828 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -197,16 +197,16 @@ function createCurveProjective(Field: MsmField) { } return { - addAssignProjective: addAssign, - doubleInPlaceProjective: doubleInPlace, + addAssign, + doubleInPlace, scale, toBigint, sizeProjective: size, - isZeroProjective: isZero, - copyProjective: copy, - copyAffineToProjective: affineToProjective, + isZero, + copy, + affineToProjective, projectiveCoords: coords, - setNonZeroProjective: setNonZero, + setNonZero, }; } 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; } From 7b716de8ca014ee930e9f40dcfbe172583d83fcb Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 20:44:29 +0100 Subject: [PATCH 13/18] pass projective to affine curve to implement scale --- scripts/test-bls12377.ts | 2 +- src/concrete/bls12-377.ts | 2 +- src/concrete/bls12-381.ts | 2 +- src/concrete/pasta.ts | 2 +- src/curve-affine.ts | 29 ++++++++++++++++++++++++++++- src/curve-projective.ts | 21 +++++++++++++++++++++ 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 36642a43..e4d70b1f 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -23,7 +23,7 @@ function ofWasm([tmp]: number[], x: number) { return mod(Field.readBigint(tmp), p); } -let [x, y, z, z_hi, ...scratch] = Field.getPointers(20); +let [x, y, z, z_hi, ...scratch] = Field.getPointers(30); let scratchScalar = Scalar.getPointers(10); let R = mod(1n << BigInt(Field.w * Field.n), p); diff --git a/src/concrete/bls12-377.ts b/src/concrete/bls12-377.ts index e6257136..87d6ffc7 100644 --- a/src/concrete/bls12-377.ts +++ b/src/concrete/bls12-377.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/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 776e48fa..fc33dc1c 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,11 @@ type CurveAffine = ReturnType; * * over the `Field` */ -function createCurveAffine(Field: MsmField, b: bigint) { +function createCurveAffine( + Field: MsmField, + CurveProjective: CurveProjective, + b: bigint +) { const { sizeField, square, multiply, add, subtract, copy, memoryBytes, p } = Field; @@ -93,6 +98,27 @@ function createCurveAffine(Field: MsmField, b: bigint) { copy(yOut, y2); } + function scale( + [ + resultProj, + _ry, + _rz, + _rinf, + pointProj, + _py, + _pz, + _pinf, + ...scratch + ]: number[], + result: number, + scalar: boolean[], + point: number + ) { + CurveProjective.affineToProjective(pointProj, point); + CurveProjective.scale(scratch, resultProj, scalar, resultProj); + CurveProjective.projectiveToAffine(scratch, result, resultProj); + } + let { randomFields } = randomGenerators(p); let [bPtr] = Field.getStablePointers(1); @@ -188,6 +214,7 @@ function createCurveAffine(Field: MsmField, b: bigint) { b, sizeAffine, doubleAffine, + scale, isZeroAffine, copyAffine, affineCoords, diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 7795c828..89a11eab 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -167,6 +167,26 @@ function createCurveProjective(Field: MsmField) { memoryBytes[P + 3 * sizeField] = memoryBytes[A + 2 * sizeField]; } + 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]; } @@ -205,6 +225,7 @@ function createCurveProjective(Field: MsmField) { isZero, copy, affineToProjective, + projectiveToAffine, projectiveCoords: coords, setNonZero, }; From 9333cbd5628f53d97c53c4ea723b17f7a6f00ab6 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 21:08:45 +0100 Subject: [PATCH 14/18] fix, and add test with random point --- scripts/test-bls12377.ts | 25 +++++++++++++++---------- src/curve-affine.ts | 6 +++--- src/curve-projective.ts | 4 ++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index e4d70b1f..1a10f322 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -8,7 +8,7 @@ import { log2, } from "../src/util.js"; import { mod, modExp, modInverse } from "../src/field-util.js"; -import { G, q } from "../src/concrete/bls12-377.params.js"; +import { G, h, q } from "../src/concrete/bls12-377.params.js"; let { Field, Scalar, CurveProjective, CurveAffine, Random } = BLS12_377; const { p } = Field; @@ -193,16 +193,21 @@ function testBatchMontgomery() { } } -// convert G to projective -let gAffine = Field.getPointer(CurveAffine.sizeAffine); -let g = Field.getPointer(CurveProjective.sizeProjective); -CurveAffine.writeBigint(gAffine, G); -CurveProjective.affineToProjective(g, gAffine); - // prepare inputs +let [g, qG] = Field.getPointers(2, CurveAffine.sizeAffine); +CurveAffine.writeBigint(g, G); let qBits = bigintToBits(q, log2(q)); -let qG = Field.getPointer(CurveProjective.sizeProjective); +let hBits = bigintToBits(h, log2(h)); // scale and check -CurveProjective.scale(scratch, qG, qBits, g); -assert(CurveProjective.isZero(qG), "order*G = 0"); +CurveAffine.scale(scratch, qG, g, qBits); +assert(CurveAffine.isZeroAffine(qG), "order*G = 0"); + +// create random point, clear cofactor and check if it is in the subgroup +let [r, hR, qhR] = Field.getPointers(2, CurveAffine.sizeAffine); +CurveAffine.randomPoints(scratch, [r]); +assert(!CurveAffine.isZeroAffine(r), "random point R is not zero"); +CurveAffine.scale(scratch, hR, r, hBits); +assert(!CurveAffine.isZeroAffine(hR), "random point h*R is not zero"); +CurveAffine.scale(scratch, qhR, hR, qBits); +assert(CurveAffine.isZeroAffine(qhR), "order*h*R = 0"); diff --git a/src/curve-affine.ts b/src/curve-affine.ts index fc33dc1c..92ca29e7 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -111,11 +111,11 @@ function createCurveAffine( ...scratch ]: number[], result: number, - scalar: boolean[], - point: number + point: number, + scalar: boolean[] ) { CurveProjective.affineToProjective(pointProj, point); - CurveProjective.scale(scratch, resultProj, scalar, resultProj); + CurveProjective.scale(scratch, resultProj, pointProj, scalar); CurveProjective.projectiveToAffine(scratch, result, resultProj); } diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 89a11eab..1fc7a305 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -135,8 +135,8 @@ function createCurveProjective(Field: MsmField) { function scale( scratch: number[], result: number, - scalar: boolean[], - point: number + point: number, + scalar: boolean[] ) { setZero(result); let n = scalar.length; From ddc4a3776f02ecbddffffbe9d8efdf90bb9d9e76 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 21:29:41 +0100 Subject: [PATCH 15/18] better API for cofactor clearing --- scripts/test-bls12377.ts | 18 ++++++++---------- src/concrete/bls12-377.ts | 4 ++-- src/curve-affine.ts | 19 +++++++++++++++---- src/curve-projective.ts | 18 +++++++++++++++++- src/util.ts | 2 +- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 1a10f322..4b947708 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -5,12 +5,11 @@ import { assert, bigintToBits, extractBitSlice as extractBitSliceJS, - log2, } from "../src/util.js"; import { mod, modExp, modInverse } from "../src/field-util.js"; -import { G, h, q } from "../src/concrete/bls12-377.params.js"; +import { G, q } from "../src/concrete/bls12-377.params.js"; -let { Field, Scalar, CurveProjective, CurveAffine, Random } = BLS12_377; +let { Field, Scalar, CurveAffine, Random } = BLS12_377; const { p } = Field; function toWasm(x0: bigint, x: number) { @@ -23,7 +22,7 @@ function ofWasm([tmp]: number[], x: number) { return mod(Field.readBigint(tmp), p); } -let [x, y, z, z_hi, ...scratch] = Field.getPointers(30); +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); @@ -196,18 +195,17 @@ function testBatchMontgomery() { // prepare inputs let [g, qG] = Field.getPointers(2, CurveAffine.sizeAffine); CurveAffine.writeBigint(g, G); -let qBits = bigintToBits(q, log2(q)); -let hBits = bigintToBits(h, log2(h)); +let qBits = bigintToBits(q); // scale and check CurveAffine.scale(scratch, qG, g, qBits); assert(CurveAffine.isZeroAffine(qG), "order*G = 0"); // create random point, clear cofactor and check if it is in the subgroup -let [r, hR, qhR] = Field.getPointers(2, CurveAffine.sizeAffine); +let [r, qhR] = Field.getPointers(2, CurveAffine.sizeAffine); CurveAffine.randomPoints(scratch, [r]); assert(!CurveAffine.isZeroAffine(r), "random point R is not zero"); -CurveAffine.scale(scratch, hR, r, hBits); -assert(!CurveAffine.isZeroAffine(hR), "random point h*R is not zero"); -CurveAffine.scale(scratch, qhR, hR, qBits); +CurveAffine.clearCofactorInPlace(scratch, r); +assert(!CurveAffine.isZeroAffine(r), "random point h*R is not zero"); +CurveAffine.scale(scratch, qhR, r, qBits); assert(CurveAffine.isZeroAffine(qhR), "order*h*R = 0"); diff --git a/src/concrete/bls12-377.ts b/src/concrete/bls12-377.ts index 87d6ffc7..b25f7bec 100644 --- a/src/concrete/bls12-377.ts +++ b/src/concrete/bls12-377.ts @@ -1,6 +1,6 @@ import type * as W from "wasmati"; import { randomGenerators } from "../field-util.js"; -import { p, q, b, beta, lambda } from "./bls12-377.params.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"; @@ -13,7 +13,7 @@ export { msm, msmUnsafe, msmUtil }; const Field = await createMsmField(p, beta, 29); const Scalar = await createGeneralGlvScalar(q, lambda, 29); -const CurveProjective = createCurveProjective(Field); +const CurveProjective = createCurveProjective(Field, h); const CurveAffine = createCurveAffine(Field, CurveProjective, b); const { msm, msmUnsafe, msmBigint, ...msmUtil } = createMsm({ diff --git a/src/curve-affine.ts b/src/curve-affine.ts index 92ca29e7..237f706b 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -55,6 +55,11 @@ function createCurveAffine( 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; @@ -119,11 +124,16 @@ function createCurveAffine( CurveProjective.projectiveToAffine(scratch, result, resultProj); } - let { randomFields } = randomGenerators(p); + function clearCofactorInPlace( + [tmp, _tmpy, _tmpz, _tmpInf, ...scratch]: number[], + point: number + ) { + if (CurveProjective.cofactor === 1n) return; + copyAffine(tmp, point); + scale(scratch, point, tmp, CurveProjective.cofactorBits); + } - let [bPtr] = Field.getStablePointers(1); - Field.writeBigint(bPtr, b); - Field.toMontgomery(bPtr); + let { randomFields } = randomGenerators(p); /** * sample random curve points @@ -215,6 +225,7 @@ function createCurveAffine( sizeAffine, doubleAffine, scale, + clearCofactorInPlace, isZeroAffine, copyAffine, affineCoords, diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 1fc7a305..057bd0aa 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, @@ -147,6 +151,15 @@ function createCurveProjective(Field: MsmField) { } } + function clearCofactorInPlace( + [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]; } @@ -217,9 +230,12 @@ function createCurveProjective(Field: MsmField) { } return { + cofactor, + cofactorBits, addAssign, doubleInPlace, scale, + clearCofactorInPlace, toBigint, sizeProjective: size, isZero, diff --git a/src/util.ts b/src/util.ts index 9928502d..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); From d000884d884c87400dd1ee6e8b1044753a608b49 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 21:34:59 +0100 Subject: [PATCH 16/18] clear cofactor automatically --- scripts/test-bls12377.ts | 35 ++++++++++++++++++----------------- src/curve-affine.ts | 6 ++++++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/test-bls12377.ts b/scripts/test-bls12377.ts index 4b947708..ed9d1975 100644 --- a/scripts/test-bls12377.ts +++ b/scripts/test-bls12377.ts @@ -148,6 +148,7 @@ function test() { for (let i = 0; i < 100; i++) { test(); + testCurve(); } for (let i = 0; i < 100; i++) { let ok = Scalar.testDecomposeScalar(Random.randomScalar()); @@ -192,20 +193,20 @@ function testBatchMontgomery() { } } -// 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, clear cofactor and check if it is in the subgroup -let [r, qhR] = Field.getPointers(2, CurveAffine.sizeAffine); -CurveAffine.randomPoints(scratch, [r]); -assert(!CurveAffine.isZeroAffine(r), "random point R is not zero"); -CurveAffine.clearCofactorInPlace(scratch, r); -assert(!CurveAffine.isZeroAffine(r), "random point h*R is not zero"); -CurveAffine.scale(scratch, qhR, r, qBits); -assert(CurveAffine.isZeroAffine(qhR), "order*h*R = 0"); +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/curve-affine.ts b/src/curve-affine.ts index 237f706b..7f4da1c9 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -172,6 +172,12 @@ function createCurveAffine( } setIsNonZero(x, true); } + + if (CurveProjective.cofactor !== 1n) { + for (let i = 0; i < n; i++) { + clearCofactorInPlace(scratch, points[i]); + } + } return points; } From 17638a2d1e96b1782ba254b9f3d3f10383cfbfec Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 21:38:39 +0100 Subject: [PATCH 17/18] 377 msm works --- scripts/test-bls12377-msm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-bls12377-msm.ts b/scripts/test-bls12377-msm.ts index 3b41810e..e7deb1dd 100644 --- a/scripts/test-bls12377-msm.ts +++ b/scripts/test-bls12377-msm.ts @@ -19,7 +19,7 @@ console.log(`running msm with 2^${n} = ${2 ** n} inputs`); tic("random points"); let points = Field.getZeroPointers(N, CurveAffine.sizeAffine); -let scratch = Field.getPointers(20); +let scratch = Field.getPointers(40); CurveAffine.randomPoints(scratch, points); let scalars = Random.randomScalars(N); From a27d18687900b24ebdb609a82064652ffafddb8f Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Sun, 3 Dec 2023 21:52:32 +0100 Subject: [PATCH 18/18] better name tosubgroup --- src/curve-affine.ts | 13 ++++++------- src/curve-projective.ts | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/curve-affine.ts b/src/curve-affine.ts index 7f4da1c9..3896d93a 100644 --- a/src/curve-affine.ts +++ b/src/curve-affine.ts @@ -124,7 +124,7 @@ function createCurveAffine( CurveProjective.projectiveToAffine(scratch, result, resultProj); } - function clearCofactorInPlace( + function toSubgroupInPlace( [tmp, _tmpy, _tmpz, _tmpInf, ...scratch]: number[], point: number ) { @@ -175,13 +175,13 @@ function createCurveAffine( if (CurveProjective.cofactor !== 1n) { for (let i = 0; i < n; i++) { - clearCofactorInPlace(scratch, points[i]); + toSubgroupInPlace(scratch, points[i]); } } return points; } - function isZeroAffine(pointer: number) { + function isZero(pointer: number) { return !memoryBytes[pointer + 2 * sizeField]; } @@ -198,8 +198,7 @@ function createCurveAffine( } 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); @@ -231,8 +230,8 @@ function createCurveAffine( sizeAffine, doubleAffine, scale, - clearCofactorInPlace, - isZeroAffine, + toSubgroupInPlace, + isZeroAffine: isZero, copyAffine, affineCoords, setIsNonZeroAffine: setIsNonZero, diff --git a/src/curve-projective.ts b/src/curve-projective.ts index 057bd0aa..9cd98761 100644 --- a/src/curve-projective.ts +++ b/src/curve-projective.ts @@ -151,7 +151,7 @@ function createCurveProjective(Field: MsmField, cofactor = 1n) { } } - function clearCofactorInPlace( + function toSubgroupInPlace( [tmp, _tmpy, _tmpz, _tmpInf, ...scratch]: number[], point: number ) { @@ -235,7 +235,7 @@ function createCurveProjective(Field: MsmField, cofactor = 1n) { addAssign, doubleInPlace, scale, - clearCofactorInPlace, + toSubgroupInPlace, toBigint, sizeProjective: size, isZero,