From 1934d206f3199ce588533476bcd03a8291561cac Mon Sep 17 00:00:00 2001 From: cedoor Date: Thu, 30 Nov 2023 17:56:02 +0000 Subject: [PATCH 01/18] feat(eddsa-poseidon): init eddsa poseidon lib --- packages/eddsa-poseidon/README.md | 58 ++++++++++ packages/eddsa-poseidon/build.tsconfig.json | 8 ++ packages/eddsa-poseidon/package.json | 39 +++++++ packages/eddsa-poseidon/rollup.config.ts | 25 +++++ packages/eddsa-poseidon/src/babyjub.ts | 47 ++++++++ packages/eddsa-poseidon/src/eddsa-poseidon.ts | 106 ++++++++++++++++++ packages/eddsa-poseidon/src/field1.ts | 65 +++++++++++ packages/eddsa-poseidon/src/index.ts | 1 + packages/eddsa-poseidon/src/scalar.ts | 23 ++++ packages/eddsa-poseidon/src/types/index.ts | 1 + packages/eddsa-poseidon/src/utils.ts | 66 +++++++++++ packages/eddsa-poseidon/tests/index.test.ts | 33 ++++++ packages/eddsa-poseidon/tsconfig.json | 4 + packages/eddsa-poseidon/typedoc.json | 3 + yarn.lock | 14 ++- 15 files changed, 492 insertions(+), 1 deletion(-) create mode 100644 packages/eddsa-poseidon/README.md create mode 100644 packages/eddsa-poseidon/build.tsconfig.json create mode 100644 packages/eddsa-poseidon/package.json create mode 100644 packages/eddsa-poseidon/rollup.config.ts create mode 100644 packages/eddsa-poseidon/src/babyjub.ts create mode 100644 packages/eddsa-poseidon/src/eddsa-poseidon.ts create mode 100644 packages/eddsa-poseidon/src/field1.ts create mode 100644 packages/eddsa-poseidon/src/index.ts create mode 100644 packages/eddsa-poseidon/src/scalar.ts create mode 100644 packages/eddsa-poseidon/src/types/index.ts create mode 100644 packages/eddsa-poseidon/src/utils.ts create mode 100644 packages/eddsa-poseidon/tests/index.test.ts create mode 100644 packages/eddsa-poseidon/tsconfig.json create mode 100644 packages/eddsa-poseidon/typedoc.json diff --git a/packages/eddsa-poseidon/README.md b/packages/eddsa-poseidon/README.md new file mode 100644 index 000000000..9a2f5be2f --- /dev/null +++ b/packages/eddsa-poseidon/README.md @@ -0,0 +1,58 @@ +

+

+ EdDSA Poseidon +

+

A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.

+

+ +

+ + + + + NPM license + + + NPM version + + + Downloads + + + npm bundle size (scoped) + + + Linter eslint + + + Code style prettier + +

+ +
+

+ + 🗣️ Chat & Support + +   |   + + 📘 Docs + +

+
+ +## 🛠 Install + +### npm or yarn + +Install the `@zk-kit/eddsa-poseidon` package and its peer dependencies with npm: + +```bash +npm i @zk-kit/eddsa-poseidon +``` + +or yarn: + +```bash +yarn add @zk-kit/eddsa-poseidon +``` diff --git a/packages/eddsa-poseidon/build.tsconfig.json b/packages/eddsa-poseidon/build.tsconfig.json new file mode 100644 index 000000000..2d4a1d6da --- /dev/null +++ b/packages/eddsa-poseidon/build.tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json new file mode 100644 index 000000000..3bc1e27be --- /dev/null +++ b/packages/eddsa-poseidon/package.json @@ -0,0 +1,39 @@ +{ + "name": "@zk-kit/eddsa-poseidon", + "version": "0.1.0", + "description": "A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.", + "license": "MIT", + "main": "dist/index.node.js", + "exports": { + "import": "./dist/index.mjs", + "require": "./dist/index.node.js" + }, + "types": "dist/types/index.d.ts", + "files": [ + "dist/", + "src/", + "README.md" + ], + "repository": "https://github.com/privacy-scaling-explorations/zk-kit", + "homepage": "https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/eddsa-poseidon", + "bugs": { + "url": "https://github.com/privacy-scaling-explorations/zk-kit.git/issues" + }, + "scripts": { + "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", + "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", + "prepublishOnly": "yarn build" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "circomlibjs": "0.0.8", + "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-typescript2": "^0.31.2" + }, + "dependencies": { + "blake-hash": "^2.0.0", + "poseidon-lite": "^0.2.0" + } +} diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts new file mode 100644 index 000000000..4d2e19c49 --- /dev/null +++ b/packages/eddsa-poseidon/rollup.config.ts @@ -0,0 +1,25 @@ +import fs from "fs" +import cleanup from "rollup-plugin-cleanup" +import typescript from "rollup-plugin-typescript2" + +const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8")) +const banner = `/** + * @module ${pkg.name} + * @version ${pkg.version} + * @file ${pkg.description} + * @copyright Ethereum Foundation ${new Date().getFullYear()} + * @license ${pkg.license} + * @see [Github]{@link ${pkg.homepage}} +*/` + +export default { + input: "src/index.ts", + output: [ + { file: pkg.exports.require, format: "cjs", banner }, + { file: pkg.exports.import, format: "es", banner } + ], + plugins: [ + typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), + cleanup({ comments: "jsdoc" }) + ] +} diff --git a/packages/eddsa-poseidon/src/babyjub.ts b/packages/eddsa-poseidon/src/babyjub.ts new file mode 100644 index 000000000..cf42b1fee --- /dev/null +++ b/packages/eddsa-poseidon/src/babyjub.ts @@ -0,0 +1,47 @@ +import Field1 from "./field1" +import * as scalar from "./scalar" +import { Point } from "./types" + +export const F = new Field1(BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + +export const Base8: Point = [ + F.e(BigInt("5299619240641551281634865583518297030282874472190772894086521144482721001553")), + F.e(BigInt("16950150798460657717958625567821834550301663161624707787222815936182638968203")) +] + +export const order = BigInt("21888242871839275222246405745257275088614511777268538073601725287587578984328") +export const subOrder = scalar.shiftRight(order, BigInt(3)) + +const A = F.e(BigInt("168700")) +const D = F.e(BigInt("168696")) + +export function addPoint(a: Point, b: Point): Point { + const beta = F.mul(a[0], b[1]) + const gamma = F.mul(a[1], b[0]) + const delta = F.mul(F.sub(a[1], F.mul(A, a[0])), F.add(b[0], b[1])) + + const tau = F.mul(beta, gamma) + const dtau = F.mul(D, tau) + + const x = F.div(F.add(beta, gamma), F.add(F.one, dtau)) + const y = F.div(F.add(delta, F.sub(F.mul(A, beta), gamma)), F.sub(F.one, dtau)) + + return [x, y] +} + +export function mulPointEscalar(base: Point, e: bigint): Point { + let res: Point = [F.e(BigInt(0)), F.e(BigInt(1))] + let rem: bigint = e + let exp: Point = base + + while (!scalar.isZero(rem)) { + if (scalar.isOdd(rem)) { + res = addPoint(res, exp) + } + + exp = addPoint(exp, exp) + rem = scalar.shiftRight(rem, BigInt(1)) + } + + return res +} diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts new file mode 100644 index 000000000..f44dce5e2 --- /dev/null +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -0,0 +1,106 @@ +// @ts-ignore +import createBlakeHash from "blake-hash" +import { poseidon5 } from "poseidon-lite" +import * as babyjub from "./babyjub" +import * as scalar from "./scalar" +import * as utils from "./utils" +import Field1 from "./field1" + +/** + * Generates a public key from a given private key using the + * {@link https://eips.ethereum.org/EIPS/eip-2494|Baby Jubjub} elliptic curve. + * This function utilizes the Baby Jubjub elliptic curve for cryptographic operations. + * The private key should be securely stored and managed, and it should never be exposed + * or transmitted in an unsecured manner. + * + * @param {String|Buffer|BigInt} privateKey - The private key used for generating the public key. + * This can be a string, buffer, or BigInt representing the private key. + * + * @returns {Buffer} The derived public key, formatted as a buffer. + * + * @example + * import { generatePublicKey } from "@zk-kit/eddsa-poseidon" + * + * const privateKey = "your_private_key_here" + * + * const publicKey = generatePublicKey(privateKey) + * + * @throws {Error} Throws an error if the private key is invalid or if any cryptographic + * operation fails during the public key generation process. + */ +export function generatePublicKey(privateKey: any) { + const blakeHash = createBlakeHash("blake512").update(privateKey).digest() + + const s = utils.leBuff2int(utils.pruneBuffer(blakeHash.slice(0, 32))) + + return babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) +} + +/** + * Signs a message using the provided private key, employing Poseidon hashing and + * EdDSA with the Baby Jubjub elliptic curve. + * + * @param {String|Buffer|BigInt} privateKey - The private key used to sign the message. + * This can be a string, buffer, or BigInt representing the private key. + * @param {String|Buffer} message - The message to be signed. Can be a string or a buffer. + * + * @returns {Object} The signature object, typically containing properties relevant to + * EdDSA signatures, such as 'r' and 's' values. + * + * @example + * import { signMessage } from "@zk-kit/eddsa-poseidon" + * + * const privateKey = "your_private_key_here" + * const message = "Your message to sign" + * + * const signature = signMessage(privateKey, message) + * + * @throws {Error} Throws an error if the private key or message is invalid, if the + * Poseidon hash function encounters any issues, or if the EdDSA signing process fails. + */ +export function signMessage(privateKey: any, message: any) { + const blakeHash = createBlakeHash("blake512").update(privateKey).digest() + + const sBuff = utils.pruneBuffer(blakeHash.slice(0, 32)) + const s = utils.leBuff2int(sBuff) + const A = babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) + + const msgBuff = utils.leInt2Buff(message) + + const rBuff = createBlakeHash("blake512") + .update(Buffer.concat([blakeHash.slice(32, 64), msgBuff])) + .digest() + + const Fr = new Field1(babyjub.subOrder) + const r = Fr.e(utils.leBuff2int(rBuff)) + + const R8 = babyjub.mulPointEscalar(babyjub.Base8, r) + const hm = poseidon5([R8[0], R8[1], A[0], A[1], message]) + const S = Fr.add(r, Fr.mul(hm, s)) + + return { + R8, + S + } +} + +export function verifySignature(message: bigint, signature: any, publicKey: any) { + // if (typeof signature !== "object") return false + // if (!Array.isArray(signature.R8)) return false + // if (signature.R8.length !== 2) return false + // if (!babyjub.inCurve(signature.R8)) return false + // if (!Array.isArray(A)) return false + // if (A.length != 2) return false + // if (!babyJub.inCurve(A)) return false + // if (signature.S >= babyJub.subOrder) return false + + const hm = poseidon5([signature.R8[0], signature.R8[1], publicKey[0], publicKey[1], message]) + + const pLeft = babyjub.mulPointEscalar(babyjub.Base8, signature.S) + let pRight = babyjub.mulPointEscalar(publicKey, scalar.mul(hm, BigInt(8))) + + pRight = babyjub.addPoint(signature.R8, pRight) + + // It returns true if the points match. + return babyjub.F.eq(pLeft[0], pRight[0]) && babyjub.F.eq(pLeft[1], pRight[1]) +} diff --git a/packages/eddsa-poseidon/src/field1.ts b/packages/eddsa-poseidon/src/field1.ts new file mode 100644 index 000000000..2d9f219ed --- /dev/null +++ b/packages/eddsa-poseidon/src/field1.ts @@ -0,0 +1,65 @@ +export default class Field1 { + one = BigInt(1) + zero = BigInt(0) + + _order: bigint + + constructor(order: bigint) { + this._order = order + } + + e(res: bigint): bigint { + // if (res < 0) { + // let nres = -res + + // if (nres >= this._order) nres %= this._order + + // return this._order - nres + // } + + return res >= this._order ? res % this._order : res + } + + mul(a: bigint, b: bigint): bigint { + return (a * b) % this._order + } + + sub(a: bigint, b: bigint): bigint { + return a >= b ? a - b : this._order - b + a + } + + add(a: bigint, b: bigint): bigint { + const res = a + b + + return res >= this._order ? res - this._order : res + } + + inv(a: bigint): bigint { + // if (!a) throw new Error("Division by zero") + + let t = this.zero + let r = this._order + let newt = this.one + let newr = a % this._order + + while (newr) { + const q = r / newr + ;[t, newt] = [newt, t - q * newt] + ;[r, newr] = [newr, r - q * newr] + } + + if (t < this.zero) { + t += this._order + } + + return t + } + + div(a: bigint, b: bigint): bigint { + return this.mul(a, this.inv(b)) + } + + eq(a: bigint, b: bigint): boolean { + return a === b + } +} diff --git a/packages/eddsa-poseidon/src/index.ts b/packages/eddsa-poseidon/src/index.ts new file mode 100644 index 000000000..07e53d209 --- /dev/null +++ b/packages/eddsa-poseidon/src/index.ts @@ -0,0 +1 @@ +export * from "./eddsa-poseidon" diff --git a/packages/eddsa-poseidon/src/scalar.ts b/packages/eddsa-poseidon/src/scalar.ts new file mode 100644 index 000000000..8e54d4eb5 --- /dev/null +++ b/packages/eddsa-poseidon/src/scalar.ts @@ -0,0 +1,23 @@ +// const hexLen = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4] + +export function isZero(a: bigint): boolean { + return !a +} + +export function isOdd(a: bigint): boolean { + return (a & BigInt(1)) === BigInt(1) +} + +export function shiftRight(a: bigint, n: bigint): bigint { + return a >> n +} + +// export function bitLength(a: bigint): number { +// const aS = a.toString(16) + +// return (aS.length - 1) * 4 + hexLen[parseInt(aS[0], 16)] +// } + +export function mul(a: bigint, b: bigint): bigint { + return a * b +} diff --git a/packages/eddsa-poseidon/src/types/index.ts b/packages/eddsa-poseidon/src/types/index.ts new file mode 100644 index 000000000..a8cba0d8d --- /dev/null +++ b/packages/eddsa-poseidon/src/types/index.ts @@ -0,0 +1 @@ +export type Point = [bigint, bigint] diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts new file mode 100644 index 000000000..3a245dfec --- /dev/null +++ b/packages/eddsa-poseidon/src/utils.ts @@ -0,0 +1,66 @@ +export function pruneBuffer(buff: Uint8Array): Uint8Array { + buff[0] &= 0xf8 + buff[31] &= 0x7f + buff[31] |= 0x40 + + return buff +} + +export function leBuff2int(buff: Uint8Array): bigint { + let res = BigInt(0) + let i = 0 + const buffV = new DataView(buff.buffer, buff.byteOffset, buff.byteLength) + + while (i < buff.length) { + // if (i + 4 <= buff.length) { + res += BigInt(buffV.getUint32(i, true)) << BigInt(i * 8) + + i += 4 + // } else { + // res += BigInt(buffV.getUint8(i)) << BigInt(i * 8) + + // i += 1 + // } + } + + return res +} + +export function leInt2Buff(n: bigint): Uint8Array { + let r = n + + // if (len === undefined) { + // len = Math.floor((scalar.bitLength(n) - 1) / 8) + 1 + + // if (len === 0) { + // len = 1 + // } + // } + + const buff = new Uint8Array(32) + const buffV = new DataView(buff.buffer) + + let o = 0 + + while (o < 32) { + // if (o + 4 <= len) { + buffV.setUint32(o, Number(r & BigInt(0xffffffff)), true) + o += 4 + r >>= BigInt(32) + // } else if (o + 2 <= len) { + // buffV.setUint16(o, Number(r & BigInt(0xffff)), true) + // o += 2 + // r >>= BigInt(16) + // } else { + // buffV.setUint8(o, Number(r & BigInt(0xff))) + // o += 1 + // r >>= BigInt(8) + // } + } + + // if (r) { + // throw new Error("Number does not fit in this length") + // } + + return buff +} diff --git a/packages/eddsa-poseidon/tests/index.test.ts b/packages/eddsa-poseidon/tests/index.test.ts new file mode 100644 index 000000000..3ec087486 --- /dev/null +++ b/packages/eddsa-poseidon/tests/index.test.ts @@ -0,0 +1,33 @@ +import { eddsa } from "circomlibjs" +import { generatePublicKey, signMessage, verifySignature } from "../src" + +describe("EdDSAPoseidon", () => { + const privateKey = "secret" + const message = BigInt(2) + + it("Should derive a public key from a private key", async () => { + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(privateKey) + + expect(publicKey[0]).toBe(circomlibPublicKey[0]) + expect(publicKey[1]).toBe(circomlibPublicKey[1]) + }) + + it("Should sign a message", async () => { + const signature = signMessage(privateKey, message) + + const circomlibSignature = eddsa.signPoseidon(privateKey, message) + + expect(signature.R8[0]).toBe(circomlibSignature.R8[0]) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1]) + expect(signature.S).toBe(circomlibSignature.S) + }) + + it("Should verify a signature", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + expect(verifySignature(message, signature, publicKey)).toBeTruthy() + }) +}) diff --git a/packages/eddsa-poseidon/tsconfig.json b/packages/eddsa-poseidon/tsconfig.json new file mode 100644 index 000000000..71510a096 --- /dev/null +++ b/packages/eddsa-poseidon/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "tests", "rollup.config.ts"] +} diff --git a/packages/eddsa-poseidon/typedoc.json b/packages/eddsa-poseidon/typedoc.json new file mode 100644 index 000000000..77a471c91 --- /dev/null +++ b/packages/eddsa-poseidon/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} diff --git a/yarn.lock b/yarn.lock index 614d0c04c..316ee3ab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4401,6 +4401,18 @@ __metadata: languageName: unknown linkType: soft +"@zk-kit/eddsa-poseidon@workspace:packages/eddsa-poseidon": + version: 0.0.0-use.local + resolution: "@zk-kit/eddsa-poseidon@workspace:packages/eddsa-poseidon" + dependencies: + blake-hash: ^2.0.0 + circomlibjs: 0.0.8 + poseidon-lite: ^0.2.0 + rollup-plugin-cleanup: ^3.2.1 + rollup-plugin-typescript2: ^0.31.2 + languageName: unknown + linkType: soft + "@zk-kit/groth16@0.4.0, @zk-kit/groth16@workspace:packages/groth16": version: 0.0.0-use.local resolution: "@zk-kit/groth16@workspace:packages/groth16" @@ -6450,7 +6462,7 @@ __metadata: languageName: node linkType: hard -"circomlibjs@npm:^0.0.8": +"circomlibjs@npm:0.0.8, circomlibjs@npm:^0.0.8": version: 0.0.8 resolution: "circomlibjs@npm:0.0.8" dependencies: From 6ec8da19e373c2b1d800e399e4cf98d7358fb888 Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 12:25:36 +0000 Subject: [PATCH 02/18] chore: update build command names --- .github/workflows/production.yml | 4 ++-- .github/workflows/pull-requests.yml | 4 ++-- package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 0203b9349..c914a7d43 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -35,10 +35,10 @@ jobs: run: yarn - name: Compile contracts - run: yarn compile:sol + run: yarn compile:contracts - name: Build libraries - run: yarn build:js + run: yarn build:libraries - name: Run Prettier run: yarn prettier diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 08df69724..2c2755168 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -33,10 +33,10 @@ jobs: run: yarn - name: Compile contracts - run: yarn compile:sol + run: yarn compile:contracts - name: Build libraries - run: yarn build:js + run: yarn build:libraries - name: Run Prettier run: yarn prettier diff --git a/package.json b/package.json index 5c7f00fad..1db55cece 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "private": true, "scripts": { "build": "yarn build:js && yarn compile:sol", - "build:js": "yarn workspaces foreach --no-private run build", - "compile:sol": "yarn workspaces foreach run compile", + "build:libraries": "yarn workspaces foreach --no-private run build", + "compile:contracts": "yarn workspaces foreach run compile", "test": "yarn test:libraries && yarn test:contracts && yarn test:circuits", "test:libraries": "jest --coverage", "test:circuits": "yarn workspace @zk-kit/circuits test", From c7498bf27e0b6b1d32fff3b5d5c08cfbdf83f754 Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 12:28:46 +0000 Subject: [PATCH 03/18] chore(eddsa-poseidon): update rollup config to get a 0-dep bundle re #87 --- packages/eddsa-poseidon/LICENSE | 21 +++++++++++ packages/eddsa-poseidon/package.json | 8 ++++- packages/eddsa-poseidon/rollup.config.ts | 21 +++++++++++ packages/eddsa-poseidon/src/blake.ts | 24 +++++++++++++ packages/eddsa-poseidon/src/eddsa-poseidon.ts | 19 +++++----- yarn.lock | 36 +++++++++++++++++-- 6 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 packages/eddsa-poseidon/LICENSE create mode 100644 packages/eddsa-poseidon/src/blake.ts diff --git a/packages/eddsa-poseidon/LICENSE b/packages/eddsa-poseidon/LICENSE new file mode 100644 index 000000000..4377091ec --- /dev/null +++ b/packages/eddsa-poseidon/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ethereum Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json index 3bc1e27be..4cec8eecc 100644 --- a/packages/eddsa-poseidon/package.json +++ b/packages/eddsa-poseidon/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "description": "A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.", "license": "MIT", + "iife": "dist/index.js", + "unpkg": "dist/index.min.js", + "jsdelivr": "dist/index.min.js", "main": "dist/index.node.js", "exports": { "import": "./dist/index.mjs", @@ -12,6 +15,7 @@ "files": [ "dist/", "src/", + "LICENSE", "README.md" ], "repository": "https://github.com/privacy-scaling-explorations/zk-kit", @@ -28,12 +32,14 @@ "access": "public" }, "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", "circomlibjs": "0.0.8", "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2" }, "dependencies": { - "blake-hash": "^2.0.0", "poseidon-lite": "^0.2.0" } } diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts index 4d2e19c49..a24eae27e 100644 --- a/packages/eddsa-poseidon/rollup.config.ts +++ b/packages/eddsa-poseidon/rollup.config.ts @@ -1,5 +1,8 @@ +import commonjs from "@rollup/plugin-commonjs" +import { nodeResolve } from "@rollup/plugin-node-resolve" import fs from "fs" import cleanup from "rollup-plugin-cleanup" +import { terser } from "rollup-plugin-terser" import typescript from "rollup-plugin-typescript2" const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8")) @@ -12,14 +15,32 @@ const banner = `/** * @see [Github]{@link ${pkg.homepage}} */` +const name = pkg.name.split("/")[1].replace(/[-/]./g, (x: string) => x.toUpperCase()[1]) + export default { input: "src/index.ts", output: [ + { + file: pkg.iife, + name, + format: "iife", + banner + }, + { + file: pkg.unpkg, + name, + format: "iife", + plugins: [terser({ output: { preamble: banner } })] + }, { file: pkg.exports.require, format: "cjs", banner }, { file: pkg.exports.import, format: "es", banner } ], plugins: [ typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), + commonjs(), + nodeResolve({ + preferBuiltins: true + }), cleanup({ comments: "jsdoc" }) ] } diff --git a/packages/eddsa-poseidon/src/blake.ts b/packages/eddsa-poseidon/src/blake.ts new file mode 100644 index 000000000..b08a01a77 --- /dev/null +++ b/packages/eddsa-poseidon/src/blake.ts @@ -0,0 +1,24 @@ +// @ts-ignore +import { Blake512 } from "blake-hash/lib" + +export default function hash(data: any, encoding?: BufferEncoding) { + const engine = new Blake512() + + if (!Buffer.isBuffer(data) && typeof data !== "string") { + throw new TypeError("Data must be a string or a buffer") + } + + if (!Buffer.isBuffer(data)) { + data = Buffer.from(data, encoding) + } + + engine.update(data) + + let digest = engine.digest() + + if (encoding !== undefined) { + digest = digest.toString(encoding) + } + + return digest +} diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index f44dce5e2..237f7e4cf 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -1,10 +1,9 @@ -// @ts-ignore -import createBlakeHash from "blake-hash" -import { poseidon5 } from "poseidon-lite" +import { poseidon5 } from "poseidon-lite/poseidon5" import * as babyjub from "./babyjub" +import blake from "./blake" +import Field1 from "./field1" import * as scalar from "./scalar" import * as utils from "./utils" -import Field1 from "./field1" /** * Generates a public key from a given private key using the @@ -29,9 +28,9 @@ import Field1 from "./field1" * operation fails during the public key generation process. */ export function generatePublicKey(privateKey: any) { - const blakeHash = createBlakeHash("blake512").update(privateKey).digest() + const hash = blake(privateKey) - const s = utils.leBuff2int(utils.pruneBuffer(blakeHash.slice(0, 32))) + const s = utils.leBuff2int(utils.pruneBuffer(hash.slice(0, 32))) return babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) } @@ -59,17 +58,15 @@ export function generatePublicKey(privateKey: any) { * Poseidon hash function encounters any issues, or if the EdDSA signing process fails. */ export function signMessage(privateKey: any, message: any) { - const blakeHash = createBlakeHash("blake512").update(privateKey).digest() + const hash = blake(privateKey) - const sBuff = utils.pruneBuffer(blakeHash.slice(0, 32)) + const sBuff = utils.pruneBuffer(hash.slice(0, 32)) const s = utils.leBuff2int(sBuff) const A = babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) const msgBuff = utils.leInt2Buff(message) - const rBuff = createBlakeHash("blake512") - .update(Buffer.concat([blakeHash.slice(32, 64), msgBuff])) - .digest() + const rBuff = blake(Buffer.concat([hash.slice(32, 64), msgBuff])) const Fr = new Field1(babyjub.subOrder) const r = Fr.e(utils.leBuff2int(rBuff)) diff --git a/yarn.lock b/yarn.lock index 316ee3ab4..443913e6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2779,7 +2779,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 @@ -3305,6 +3305,25 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-commonjs@npm:^25.0.7": + version: 25.0.7 + resolution: "@rollup/plugin-commonjs@npm:25.0.7" + dependencies: + "@rollup/pluginutils": ^5.0.1 + commondir: ^1.0.1 + estree-walker: ^2.0.2 + glob: ^8.0.3 + is-reference: 1.2.1 + magic-string: ^0.30.3 + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 052e11839a9edc556eda5dcc759ab816dcc57e9f0f905a1e6e14fff954eaa6b1e2d0d544f5bd18d863993c5eba43d8ac9c19d9bb53b1c3b1213f32cfc9d50b2e + languageName: node + linkType: hard + "@rollup/plugin-json@npm:^5.0.1": version: 5.0.2 resolution: "@rollup/plugin-json@npm:5.0.2" @@ -3319,7 +3338,7 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-node-resolve@npm:^15.0.2": +"@rollup/plugin-node-resolve@npm:^15.0.2, @rollup/plugin-node-resolve@npm:^15.2.3": version: 15.2.3 resolution: "@rollup/plugin-node-resolve@npm:15.2.3" dependencies: @@ -4405,10 +4424,12 @@ __metadata: version: 0.0.0-use.local resolution: "@zk-kit/eddsa-poseidon@workspace:packages/eddsa-poseidon" dependencies: - blake-hash: ^2.0.0 + "@rollup/plugin-commonjs": ^25.0.7 + "@rollup/plugin-node-resolve": ^15.2.3 circomlibjs: 0.0.8 poseidon-lite: ^0.2.0 rollup-plugin-cleanup: ^3.2.1 + rollup-plugin-terser: ^7.0.2 rollup-plugin-typescript2: ^0.31.2 languageName: unknown linkType: soft @@ -13470,6 +13491,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.3": + version: 0.30.5 + resolution: "magic-string@npm:0.30.5" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.15 + checksum: da10fecff0c0a7d3faf756913ce62bd6d5e7b0402be48c3b27bfd651b90e29677e279069a63b764bcdc1b8ecdcdb898f29a5c5ec510f2323e8d62ee057a6eb18 + languageName: node + linkType: hard + "make-dir@npm:^1.0.0": version: 1.3.0 resolution: "make-dir@npm:1.3.0" From 2404d23554737d4c7944eeef73ebae9fd88a8a45 Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 12:58:25 +0000 Subject: [PATCH 04/18] chore(eddsa-poseidon): remove iife bundles --- packages/eddsa-poseidon/package.json | 4 ---- packages/eddsa-poseidon/rollup.config.ts | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json index 4cec8eecc..772501184 100644 --- a/packages/eddsa-poseidon/package.json +++ b/packages/eddsa-poseidon/package.json @@ -3,9 +3,6 @@ "version": "0.1.0", "description": "A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.", "license": "MIT", - "iife": "dist/index.js", - "unpkg": "dist/index.min.js", - "jsdelivr": "dist/index.min.js", "main": "dist/index.node.js", "exports": { "import": "./dist/index.mjs", @@ -36,7 +33,6 @@ "@rollup/plugin-node-resolve": "^15.2.3", "circomlibjs": "0.0.8", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2" }, "dependencies": { diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts index a24eae27e..0b77ecf79 100644 --- a/packages/eddsa-poseidon/rollup.config.ts +++ b/packages/eddsa-poseidon/rollup.config.ts @@ -2,7 +2,6 @@ import commonjs from "@rollup/plugin-commonjs" import { nodeResolve } from "@rollup/plugin-node-resolve" import fs from "fs" import cleanup from "rollup-plugin-cleanup" -import { terser } from "rollup-plugin-terser" import typescript from "rollup-plugin-typescript2" const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8")) @@ -15,23 +14,9 @@ const banner = `/** * @see [Github]{@link ${pkg.homepage}} */` -const name = pkg.name.split("/")[1].replace(/[-/]./g, (x: string) => x.toUpperCase()[1]) - export default { input: "src/index.ts", output: [ - { - file: pkg.iife, - name, - format: "iife", - banner - }, - { - file: pkg.unpkg, - name, - format: "iife", - plugins: [terser({ output: { preamble: banner } })] - }, { file: pkg.exports.require, format: "cjs", banner }, { file: pkg.exports.import, format: "es", banner } ], From e51c02947d6bcd4b52aec029fb247e8df8a7a585 Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 15:17:29 +0000 Subject: [PATCH 05/18] refactor(eddsa-poseidon): support more private key types --- packages/eddsa-poseidon/src/blake.ts | 36 +++++++--- packages/eddsa-poseidon/src/eddsa-poseidon.ts | 47 ++++--------- packages/eddsa-poseidon/src/types/index.ts | 7 ++ packages/eddsa-poseidon/src/utils.ts | 67 ++++++------------- packages/eddsa-poseidon/tests/index.test.ts | 11 +++ 5 files changed, 74 insertions(+), 94 deletions(-) diff --git a/packages/eddsa-poseidon/src/blake.ts b/packages/eddsa-poseidon/src/blake.ts index b08a01a77..e6d457497 100644 --- a/packages/eddsa-poseidon/src/blake.ts +++ b/packages/eddsa-poseidon/src/blake.ts @@ -1,24 +1,38 @@ // @ts-ignore import { Blake512 } from "blake-hash/lib" +import { int2hex, isHexadecimal } from "./utils" + +/** + * + */ +export default function hash(data: number | bigint | string | Buffer): Buffer { + if ( + typeof data !== "number" && + typeof data !== "bigint" && + typeof data !== "string" && + typeof data !== "object" && + !Buffer.isBuffer(data) + ) { + throw new TypeError("Data must be a number, a big number, a string or a buffer") + } -export default function hash(data: any, encoding?: BufferEncoding) { const engine = new Blake512() - if (!Buffer.isBuffer(data) && typeof data !== "string") { - throw new TypeError("Data must be a string or a buffer") + if (typeof data === "number" || typeof data === "bigint") { + data = int2hex(BigInt(data)) + + data = Buffer.from(data, "hex") + } + + if (typeof data === "string" && isHexadecimal(data)) { + data = Buffer.from(data.slice(2), "hex") } if (!Buffer.isBuffer(data)) { - data = Buffer.from(data, encoding) + data = Buffer.from(data) } engine.update(data) - let digest = engine.digest() - - if (encoding !== undefined) { - digest = digest.toString(encoding) - } - - return digest + return engine.digest() } diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index 237f7e4cf..084097219 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -3,6 +3,7 @@ import * as babyjub from "./babyjub" import blake from "./blake" import Field1 from "./field1" import * as scalar from "./scalar" +import { Point, PrivateKey, Signature } from "./types" import * as utils from "./utils" /** @@ -11,23 +12,10 @@ import * as utils from "./utils" * This function utilizes the Baby Jubjub elliptic curve for cryptographic operations. * The private key should be securely stored and managed, and it should never be exposed * or transmitted in an unsecured manner. - * - * @param {String|Buffer|BigInt} privateKey - The private key used for generating the public key. - * This can be a string, buffer, or BigInt representing the private key. - * - * @returns {Buffer} The derived public key, formatted as a buffer. - * - * @example - * import { generatePublicKey } from "@zk-kit/eddsa-poseidon" - * - * const privateKey = "your_private_key_here" - * - * const publicKey = generatePublicKey(privateKey) - * - * @throws {Error} Throws an error if the private key is invalid or if any cryptographic - * operation fails during the public key generation process. + * @param privateKey - The private key used for generating the public key. + * @returns The derived public key. */ -export function generatePublicKey(privateKey: any) { +export function generatePublicKey(privateKey: PrivateKey): Point { const hash = blake(privateKey) const s = utils.leBuff2int(utils.pruneBuffer(hash.slice(0, 32))) @@ -38,26 +26,12 @@ export function generatePublicKey(privateKey: any) { /** * Signs a message using the provided private key, employing Poseidon hashing and * EdDSA with the Baby Jubjub elliptic curve. - * - * @param {String|Buffer|BigInt} privateKey - The private key used to sign the message. - * This can be a string, buffer, or BigInt representing the private key. - * @param {String|Buffer} message - The message to be signed. Can be a string or a buffer. - * - * @returns {Object} The signature object, typically containing properties relevant to + * @param privateKey - The private key used to sign the message. + * @param message - The message to be signed. + * @returns The signature object, typically containing properties relevant to * EdDSA signatures, such as 'r' and 's' values. - * - * @example - * import { signMessage } from "@zk-kit/eddsa-poseidon" - * - * const privateKey = "your_private_key_here" - * const message = "Your message to sign" - * - * const signature = signMessage(privateKey, message) - * - * @throws {Error} Throws an error if the private key or message is invalid, if the - * Poseidon hash function encounters any issues, or if the EdDSA signing process fails. */ -export function signMessage(privateKey: any, message: any) { +export function signMessage(privateKey: PrivateKey, message: bigint): Signature { const hash = blake(privateKey) const sBuff = utils.pruneBuffer(hash.slice(0, 32)) @@ -81,7 +55,10 @@ export function signMessage(privateKey: any, message: any) { } } -export function verifySignature(message: bigint, signature: any, publicKey: any) { +/** + * + */ +export function verifySignature(message: bigint, signature: Signature, publicKey: Point): boolean { // if (typeof signature !== "object") return false // if (!Array.isArray(signature.R8)) return false // if (signature.R8.length !== 2) return false diff --git a/packages/eddsa-poseidon/src/types/index.ts b/packages/eddsa-poseidon/src/types/index.ts index a8cba0d8d..53784c416 100644 --- a/packages/eddsa-poseidon/src/types/index.ts +++ b/packages/eddsa-poseidon/src/types/index.ts @@ -1 +1,8 @@ +export type Signature = { + R8: Point + S: bigint +} + +export type PrivateKey = number | bigint | string | Buffer + export type Point = [bigint, bigint] diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts index 3a245dfec..2fd1a6507 100644 --- a/packages/eddsa-poseidon/src/utils.ts +++ b/packages/eddsa-poseidon/src/utils.ts @@ -1,4 +1,4 @@ -export function pruneBuffer(buff: Uint8Array): Uint8Array { +export function pruneBuffer(buff: Buffer): Buffer { buff[0] &= 0xf8 buff[31] &= 0x7f buff[31] |= 0x40 @@ -6,61 +6,32 @@ export function pruneBuffer(buff: Uint8Array): Uint8Array { return buff } -export function leBuff2int(buff: Uint8Array): bigint { - let res = BigInt(0) - let i = 0 - const buffV = new DataView(buff.buffer, buff.byteOffset, buff.byteLength) - - while (i < buff.length) { - // if (i + 4 <= buff.length) { - res += BigInt(buffV.getUint32(i, true)) << BigInt(i * 8) +export function isHexadecimal(s: string) { + return /^(0x|0X)[0-9a-fA-F]+$/.test(s) +} - i += 4 - // } else { - // res += BigInt(buffV.getUint8(i)) << BigInt(i * 8) +export function int2hex(n: bigint) { + let hex = n.toString(16) - // i += 1 - // } + // Ensure even length. + if (hex.length % 2 !== 0) { + hex = `0${hex}` } - return res + return hex } -export function leInt2Buff(n: bigint): Uint8Array { - let r = n - - // if (len === undefined) { - // len = Math.floor((scalar.bitLength(n) - 1) / 8) + 1 - - // if (len === 0) { - // len = 1 - // } - // } - - const buff = new Uint8Array(32) - const buffV = new DataView(buff.buffer) +export function leBuff2int(buffer: Buffer): bigint { + return BigInt(`0x${buffer.reverse().toString("hex")}`) +} - let o = 0 +export function leInt2Buff(n: bigint): Buffer { + const hex = int2hex(n) - while (o < 32) { - // if (o + 4 <= len) { - buffV.setUint32(o, Number(r & BigInt(0xffffffff)), true) - o += 4 - r >>= BigInt(32) - // } else if (o + 2 <= len) { - // buffV.setUint16(o, Number(r & BigInt(0xffff)), true) - // o += 2 - // r >>= BigInt(16) - // } else { - // buffV.setUint8(o, Number(r & BigInt(0xff))) - // o += 1 - // r >>= BigInt(8) - // } - } + // Allocate buffer of the desired size, filled with zeros. + const buffer = Buffer.alloc(32, 0) - // if (r) { - // throw new Error("Number does not fit in this length") - // } + Buffer.from(hex, "hex").reverse().copy(buffer) - return buff + return buffer } diff --git a/packages/eddsa-poseidon/tests/index.test.ts b/packages/eddsa-poseidon/tests/index.test.ts index 3ec087486..1851e380c 100644 --- a/packages/eddsa-poseidon/tests/index.test.ts +++ b/packages/eddsa-poseidon/tests/index.test.ts @@ -14,6 +14,17 @@ describe("EdDSAPoseidon", () => { expect(publicKey[1]).toBe(circomlibPublicKey[1]) }) + it("Should derive a public key from an hexadecimal private key", async () => { + const privateKey = "0x12" + + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.slice(2), "hex")) + + expect(publicKey[0]).toBe(circomlibPublicKey[0]) + expect(publicKey[1]).toBe(circomlibPublicKey[1]) + }) + it("Should sign a message", async () => { const signature = signMessage(privateKey, message) From cc73de4e470067610a3b26428bc1b1c9dde4dbcd Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 20:48:57 +0000 Subject: [PATCH 06/18] refactor(eddsa-poseidon): add types and checks on inputs --- packages/eddsa-poseidon/src/babyjub.ts | 20 +++- packages/eddsa-poseidon/src/blake.ts | 32 +----- packages/eddsa-poseidon/src/eddsa-poseidon.ts | 106 ++++++++++++++---- packages/eddsa-poseidon/src/field1.ts | 4 + packages/eddsa-poseidon/src/index.ts | 1 + packages/eddsa-poseidon/src/types/index.ts | 14 ++- packages/eddsa-poseidon/src/utils.ts | 71 ++++++++++++ packages/eddsa-poseidon/tests/index.test.ts | 14 +-- 8 files changed, 191 insertions(+), 71 deletions(-) diff --git a/packages/eddsa-poseidon/src/babyjub.ts b/packages/eddsa-poseidon/src/babyjub.ts index cf42b1fee..c85b21bb1 100644 --- a/packages/eddsa-poseidon/src/babyjub.ts +++ b/packages/eddsa-poseidon/src/babyjub.ts @@ -4,7 +4,7 @@ import { Point } from "./types" export const F = new Field1(BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")) -export const Base8: Point = [ +export const Base8: Point = [ F.e(BigInt("5299619240641551281634865583518297030282874472190772894086521144482721001553")), F.e(BigInt("16950150798460657717958625567821834550301663161624707787222815936182638968203")) ] @@ -15,7 +15,7 @@ export const subOrder = scalar.shiftRight(order, BigInt(3)) const A = F.e(BigInt("168700")) const D = F.e(BigInt("168696")) -export function addPoint(a: Point, b: Point): Point { +export function addPoint(a: Point, b: Point): Point { const beta = F.mul(a[0], b[1]) const gamma = F.mul(a[1], b[0]) const delta = F.mul(F.sub(a[1], F.mul(A, a[0])), F.add(b[0], b[1])) @@ -29,10 +29,10 @@ export function addPoint(a: Point, b: Point): Point { return [x, y] } -export function mulPointEscalar(base: Point, e: bigint): Point { - let res: Point = [F.e(BigInt(0)), F.e(BigInt(1))] +export function mulPointEscalar(base: Point, e: bigint): Point { + let res: Point = [F.e(BigInt(0)), F.e(BigInt(1))] let rem: bigint = e - let exp: Point = base + let exp: Point = base while (!scalar.isZero(rem)) { if (scalar.isOdd(rem)) { @@ -45,3 +45,13 @@ export function mulPointEscalar(base: Point, e: bigint): Point { return res } + +export function inCurve(p: Point) { + p[0] = BigInt(p[0]) + p[1] = BigInt(p[1]) + + const x2 = F.square(p[0]) + const y2 = F.square(p[1]) + + return F.eq(F.add(F.mul(A, x2), y2), F.add(F.one, F.mul(F.mul(x2, y2), D))) +} diff --git a/packages/eddsa-poseidon/src/blake.ts b/packages/eddsa-poseidon/src/blake.ts index e6d457497..9b258ea8e 100644 --- a/packages/eddsa-poseidon/src/blake.ts +++ b/packages/eddsa-poseidon/src/blake.ts @@ -1,38 +1,10 @@ // @ts-ignore import { Blake512 } from "blake-hash/lib" -import { int2hex, isHexadecimal } from "./utils" - -/** - * - */ -export default function hash(data: number | bigint | string | Buffer): Buffer { - if ( - typeof data !== "number" && - typeof data !== "bigint" && - typeof data !== "string" && - typeof data !== "object" && - !Buffer.isBuffer(data) - ) { - throw new TypeError("Data must be a number, a big number, a string or a buffer") - } +export default function hash(message: Buffer): Buffer { const engine = new Blake512() - if (typeof data === "number" || typeof data === "bigint") { - data = int2hex(BigInt(data)) - - data = Buffer.from(data, "hex") - } - - if (typeof data === "string" && isHexadecimal(data)) { - data = Buffer.from(data.slice(2), "hex") - } - - if (!Buffer.isBuffer(data)) { - data = Buffer.from(data) - } - - engine.update(data) + engine.update(message) return engine.digest() } diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index 084097219..b8c83f5f6 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -3,7 +3,7 @@ import * as babyjub from "./babyjub" import blake from "./blake" import Field1 from "./field1" import * as scalar from "./scalar" -import { Point, PrivateKey, Signature } from "./types" +import { BigNumberish, Point, Signature } from "./types" import * as utils from "./utils" /** @@ -15,12 +15,26 @@ import * as utils from "./utils" * @param privateKey - The private key used for generating the public key. * @returns The derived public key. */ -export function generatePublicKey(privateKey: PrivateKey): Point { +export function generatePublicKey(privateKey: BigNumberish | string): Point { + // Convert the private key to buffer. + if (utils.isBigNumberish(privateKey)) { + privateKey = utils.bigNumberish2Buff(privateKey) + } else { + if (typeof privateKey !== "string") { + throw TypeError("Invalid private key type. Supported types: number, bigint, buffer, string") + } + + privateKey = Buffer.from(privateKey) + } + const hash = blake(privateKey) const s = utils.leBuff2int(utils.pruneBuffer(hash.slice(0, 32))) - return babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) + const publicKey = babyjub.mulPointEscalar(babyjub.Base8, scalar.shiftRight(s, BigInt(3))) + + // Convert the public key values to strings so that it can easily be exported as a JSON. + return [publicKey[0].toString(), publicKey[1].toString()] } /** @@ -28,10 +42,31 @@ export function generatePublicKey(privateKey: PrivateKey): Point { * EdDSA with the Baby Jubjub elliptic curve. * @param privateKey - The private key used to sign the message. * @param message - The message to be signed. - * @returns The signature object, typically containing properties relevant to - * EdDSA signatures, such as 'r' and 's' values. + * @returns The signature object, containing properties relevant to EdDSA signatures, such as 'R8' and 'S' values. */ -export function signMessage(privateKey: PrivateKey, message: bigint): Signature { +export function signMessage(privateKey: BigNumberish, message: BigNumberish): Signature { + // Convert the private key to buffer. + if (utils.isBigNumberish(privateKey)) { + privateKey = utils.bigNumberish2Buff(privateKey) + } else { + if (typeof privateKey !== "string") { + throw TypeError("Invalid private key type. Supported types: number, bigint, buffer, string") + } + + privateKey = Buffer.from(privateKey) + } + + // Convert the message to big integer. + if (utils.isBigNumberish(message)) { + message = utils.bigNumberish2BigNumber(message) + } else { + if (typeof message !== "string") { + throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string") + } + + message = utils.buff2int(Buffer.from(message)) + } + const hash = blake(privateKey) const sBuff = utils.pruneBuffer(hash.slice(0, 32)) @@ -49,32 +84,57 @@ export function signMessage(privateKey: PrivateKey, message: bigint): Signature const hm = poseidon5([R8[0], R8[1], A[0], A[1], message]) const S = Fr.add(r, Fr.mul(hm, s)) + // Convert the signature values to strings so that it can easily be exported as a JSON. return { - R8, - S + R8: [R8[0].toString(), R8[1].toString()], + S: S.toString() } } /** - * + * Verifies an EdDSA signature using the Baby Jubjub elliptic curve and Poseidon hash function. + * @param message - The original message that was be signed. + * @param signature - The EdDSA signature to be verified. + * @param publicKey - The public key associated with the private key used to sign the message. + * @returns Returns true if the signature is valid and corresponds to the message and public key, false otherwise. */ -export function verifySignature(message: bigint, signature: Signature, publicKey: Point): boolean { - // if (typeof signature !== "object") return false - // if (!Array.isArray(signature.R8)) return false - // if (signature.R8.length !== 2) return false - // if (!babyjub.inCurve(signature.R8)) return false - // if (!Array.isArray(A)) return false - // if (A.length != 2) return false - // if (!babyJub.inCurve(A)) return false - // if (signature.S >= babyJub.subOrder) return false +export function verifySignature(message: BigNumberish, signature: Signature, publicKey: Point): boolean { + if ( + !utils.isPoint(publicKey) || + !utils.isSignature(signature) || + !babyjub.inCurve(signature.R8) || + !babyjub.inCurve(publicKey) || + BigInt(signature.S) >= babyjub.subOrder + ) { + return false + } + + // Convert the message to big integer. + if (utils.isBigNumberish(message)) { + message = utils.bigNumberish2BigNumber(message) + } else { + if (typeof message !== "string") { + throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string") + } + + message = utils.buff2int(Buffer.from(message)) + } + + // Convert the signature values to big integers for calculations. + const _signature: Signature = { + R8: [BigInt(signature.R8[0]), BigInt(signature.R8[1])], + S: BigInt(signature.S) + } + // Convert the public key values to big integers for calculations. + const _publicKey: Point = [BigInt(publicKey[0]), BigInt(publicKey[1])] const hm = poseidon5([signature.R8[0], signature.R8[1], publicKey[0], publicKey[1], message]) - const pLeft = babyjub.mulPointEscalar(babyjub.Base8, signature.S) - let pRight = babyjub.mulPointEscalar(publicKey, scalar.mul(hm, BigInt(8))) + const pLeft = babyjub.mulPointEscalar(babyjub.Base8, BigInt(signature.S)) + let pRight = babyjub.mulPointEscalar(_publicKey, scalar.mul(hm, BigInt(8))) - pRight = babyjub.addPoint(signature.R8, pRight) + pRight = babyjub.addPoint(_signature.R8, pRight) - // It returns true if the points match. - return babyjub.F.eq(pLeft[0], pRight[0]) && babyjub.F.eq(pLeft[1], pRight[1]) + // Return true if the points match. + return babyjub.F.eq(BigInt(pLeft[0]), pRight[0]) && babyjub.F.eq(pLeft[1], pRight[1]) } diff --git a/packages/eddsa-poseidon/src/field1.ts b/packages/eddsa-poseidon/src/field1.ts index 2d9f219ed..0457b3baf 100644 --- a/packages/eddsa-poseidon/src/field1.ts +++ b/packages/eddsa-poseidon/src/field1.ts @@ -62,4 +62,8 @@ export default class Field1 { eq(a: bigint, b: bigint): boolean { return a === b } + + square(a: bigint): bigint { + return (a * a) % this._order + } } diff --git a/packages/eddsa-poseidon/src/index.ts b/packages/eddsa-poseidon/src/index.ts index 07e53d209..1ce366761 100644 --- a/packages/eddsa-poseidon/src/index.ts +++ b/packages/eddsa-poseidon/src/index.ts @@ -1 +1,2 @@ export * from "./eddsa-poseidon" +export * from "./types" diff --git a/packages/eddsa-poseidon/src/types/index.ts b/packages/eddsa-poseidon/src/types/index.ts index 53784c416..eefb42be3 100644 --- a/packages/eddsa-poseidon/src/types/index.ts +++ b/packages/eddsa-poseidon/src/types/index.ts @@ -1,8 +1,10 @@ -export type Signature = { - R8: Point - S: bigint -} +export type BigNumber = bigint | string + +export type BigNumberish = BigNumber | number | Buffer -export type PrivateKey = number | bigint | string | Buffer +export type Point = [N, N] -export type Point = [bigint, bigint] +export type Signature = { + R8: Point + S: N +} diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts index 2fd1a6507..520f7e0f9 100644 --- a/packages/eddsa-poseidon/src/utils.ts +++ b/packages/eddsa-poseidon/src/utils.ts @@ -1,3 +1,5 @@ +import { BigNumber, BigNumberish, Point, Signature } from "./types" + export function pruneBuffer(buff: Buffer): Buffer { buff[0] &= 0xf8 buff[31] &= 0x7f @@ -6,10 +8,44 @@ export function pruneBuffer(buff: Buffer): Buffer { return buff } +function isStringifiedBigint(s: BigNumber | string): boolean { + try { + BigInt(s) + + return true + } catch (e) { + return false + } +} + export function isHexadecimal(s: string) { return /^(0x|0X)[0-9a-fA-F]+$/.test(s) } +export function isBigNumberish(value: BigNumberish): boolean { + return ( + typeof value === "number" || + typeof value === "bigint" || + (typeof value === "string" && isStringifiedBigint(value)) || + (typeof value === "string" && isHexadecimal(value)) || + Buffer.isBuffer(value) + ) +} + +export function isPoint(point: Point): boolean { + return Array.isArray(point) && point.length === 2 && isStringifiedBigint(point[0]) && isStringifiedBigint(point[1]) +} + +export function isSignature(signature: Signature): boolean { + return ( + typeof signature === "object" && + Object.prototype.hasOwnProperty.call(signature, "R8") && + Object.prototype.hasOwnProperty.call(signature, "S") && + isPoint(signature.R8) && + isStringifiedBigint(signature.S) + ) +} + export function int2hex(n: bigint) { let hex = n.toString(16) @@ -21,6 +57,41 @@ export function int2hex(n: bigint) { return hex } +export function bigNumberish2Buff(value: BigNumberish): Buffer { + if ( + typeof value === "number" || + typeof value === "bigint" || + (typeof value === "string" && isStringifiedBigint(value)) + ) { + const hex = int2hex(BigInt(value)) + + return Buffer.from(hex, "hex") + } + + if (typeof value === "string" && isHexadecimal(value)) { + value = Buffer.from(value.slice(2), "hex") + } + + return value as Buffer +} + +export function buff2int(buffer: Buffer): bigint { + return BigInt(`0x${buffer.toString("hex")}`) +} + +export function bigNumberish2BigNumber(value: BigNumberish): bigint { + if ( + typeof value === "number" || + typeof value === "bigint" || + (typeof value === "string" && isStringifiedBigint(value)) || + (typeof value === "string" && isHexadecimal(value)) + ) { + return BigInt(value) + } + + return buff2int(value as Buffer) +} + export function leBuff2int(buffer: Buffer): bigint { return BigInt(`0x${buffer.reverse().toString("hex")}`) } diff --git a/packages/eddsa-poseidon/tests/index.test.ts b/packages/eddsa-poseidon/tests/index.test.ts index 1851e380c..8cf749de4 100644 --- a/packages/eddsa-poseidon/tests/index.test.ts +++ b/packages/eddsa-poseidon/tests/index.test.ts @@ -10,8 +10,8 @@ describe("EdDSAPoseidon", () => { const circomlibPublicKey = eddsa.prv2pub(privateKey) - expect(publicKey[0]).toBe(circomlibPublicKey[0]) - expect(publicKey[1]).toBe(circomlibPublicKey[1]) + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) }) it("Should derive a public key from an hexadecimal private key", async () => { @@ -21,8 +21,8 @@ describe("EdDSAPoseidon", () => { const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.slice(2), "hex")) - expect(publicKey[0]).toBe(circomlibPublicKey[0]) - expect(publicKey[1]).toBe(circomlibPublicKey[1]) + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) }) it("Should sign a message", async () => { @@ -30,9 +30,9 @@ describe("EdDSAPoseidon", () => { const circomlibSignature = eddsa.signPoseidon(privateKey, message) - expect(signature.R8[0]).toBe(circomlibSignature.R8[0]) - expect(signature.R8[1]).toBe(circomlibSignature.R8[1]) - expect(signature.S).toBe(circomlibSignature.S) + expect(signature.R8[0]).toBe(circomlibSignature.R8[0].toString()) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1].toString()) + expect(signature.S).toBe(circomlibSignature.S.toString()) }) it("Should verify a signature", async () => { From 9a0f3432ad80b3bd9adbe4484d0f5d733b236427 Mon Sep 17 00:00:00 2001 From: cedoor Date: Fri, 1 Dec 2023 21:27:59 +0000 Subject: [PATCH 07/18] test(eddsa-poseidon): add more tests --- packages/eddsa-poseidon/src/eddsa-poseidon.ts | 40 +---- packages/eddsa-poseidon/src/utils.ts | 28 ++- packages/eddsa-poseidon/tests/index.test.ts | 162 +++++++++++++++++- 3 files changed, 187 insertions(+), 43 deletions(-) diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index b8c83f5f6..6ab1c05a9 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -17,15 +17,7 @@ import * as utils from "./utils" */ export function generatePublicKey(privateKey: BigNumberish | string): Point { // Convert the private key to buffer. - if (utils.isBigNumberish(privateKey)) { - privateKey = utils.bigNumberish2Buff(privateKey) - } else { - if (typeof privateKey !== "string") { - throw TypeError("Invalid private key type. Supported types: number, bigint, buffer, string") - } - - privateKey = Buffer.from(privateKey) - } + privateKey = utils.checkPrivateKey(privateKey) const hash = blake(privateKey) @@ -46,26 +38,10 @@ export function generatePublicKey(privateKey: BigNumberish | string): Point { // Convert the private key to buffer. - if (utils.isBigNumberish(privateKey)) { - privateKey = utils.bigNumberish2Buff(privateKey) - } else { - if (typeof privateKey !== "string") { - throw TypeError("Invalid private key type. Supported types: number, bigint, buffer, string") - } - - privateKey = Buffer.from(privateKey) - } + privateKey = utils.checkPrivateKey(privateKey) // Convert the message to big integer. - if (utils.isBigNumberish(message)) { - message = utils.bigNumberish2BigNumber(message) - } else { - if (typeof message !== "string") { - throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string") - } - - message = utils.buff2int(Buffer.from(message)) - } + message = utils.checkMessage(message) const hash = blake(privateKey) @@ -110,15 +86,7 @@ export function verifySignature(message: BigNumberish, signature: Signature, pub } // Convert the message to big integer. - if (utils.isBigNumberish(message)) { - message = utils.bigNumberish2BigNumber(message) - } else { - if (typeof message !== "string") { - throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string") - } - - message = utils.buff2int(Buffer.from(message)) - } + message = utils.checkMessage(message) // Convert the signature values to big integers for calculations. const _signature: Signature = { diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts index 520f7e0f9..9e652ecc9 100644 --- a/packages/eddsa-poseidon/src/utils.ts +++ b/packages/eddsa-poseidon/src/utils.ts @@ -68,10 +68,6 @@ export function bigNumberish2Buff(value: BigNumberish): Buffer { return Buffer.from(hex, "hex") } - if (typeof value === "string" && isHexadecimal(value)) { - value = Buffer.from(value.slice(2), "hex") - } - return value as Buffer } @@ -106,3 +102,27 @@ export function leInt2Buff(n: bigint): Buffer { return buffer } + +export function checkPrivateKey(privateKey: BigNumberish): Buffer { + if (isBigNumberish(privateKey)) { + return bigNumberish2Buff(privateKey) + } + + if (typeof privateKey !== "string") { + throw TypeError("Invalid private key type. Supported types: number, bigint, buffer, string.") + } + + return Buffer.from(privateKey) +} + +export function checkMessage(message: BigNumberish): bigint { + if (isBigNumberish(message)) { + return bigNumberish2BigNumber(message) + } + + if (typeof message !== "string") { + throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string.") + } + + return buff2int(Buffer.from(message)) +} diff --git a/packages/eddsa-poseidon/tests/index.test.ts b/packages/eddsa-poseidon/tests/index.test.ts index 8cf749de4..3fe01b52d 100644 --- a/packages/eddsa-poseidon/tests/index.test.ts +++ b/packages/eddsa-poseidon/tests/index.test.ts @@ -1,11 +1,12 @@ import { eddsa } from "circomlibjs" +import crypto from "crypto" import { generatePublicKey, signMessage, verifySignature } from "../src" describe("EdDSAPoseidon", () => { const privateKey = "secret" const message = BigInt(2) - it("Should derive a public key from a private key", async () => { + it("Should derive a public key from a private key (string)", async () => { const publicKey = generatePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(privateKey) @@ -14,7 +15,7 @@ describe("EdDSAPoseidon", () => { expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) }) - it("Should derive a public key from an hexadecimal private key", async () => { + it("Should derive a public key from a private key (hexadecimal)", async () => { const privateKey = "0x12" const publicKey = generatePublicKey(privateKey) @@ -25,7 +26,48 @@ describe("EdDSAPoseidon", () => { expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) }) - it("Should sign a message", async () => { + it("Should derive a public key from a private key (buffer)", async () => { + const privateKey = Buffer.from("secret") + + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(privateKey) + + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) + }) + + it("Should derive a public key from a private key (bigint)", async () => { + const privateKey = BigInt(22) + + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex")) + + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) + }) + + it("Should derive a public key from a private key (number)", async () => { + const privateKey = 22 + + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex")) + + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) + }) + + it("Should throw an error if the secret type is not supported", async () => { + const privateKey = true + + const fun = () => generatePublicKey(privateKey as any) + + expect(fun).toThrow("Invalid private key type.") + }) + + it("Should sign a message (bigint)", async () => { const signature = signMessage(privateKey, message) const circomlibSignature = eddsa.signPoseidon(privateKey, message) @@ -35,10 +77,124 @@ describe("EdDSAPoseidon", () => { expect(signature.S).toBe(circomlibSignature.S.toString()) }) + it("Should sign a message (number)", async () => { + const message = 22 + + const signature = signMessage(privateKey, message) + + const circomlibSignature = eddsa.signPoseidon(privateKey, BigInt(message)) + + expect(signature.R8[0]).toBe(circomlibSignature.R8[0].toString()) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1].toString()) + expect(signature.S).toBe(circomlibSignature.S.toString()) + }) + + it("Should sign a message (hexadecimal)", async () => { + const message = "0x12" + + const signature = signMessage(privateKey, message) + + const circomlibSignature = eddsa.signPoseidon(privateKey, BigInt(message)) + + expect(signature.R8[0]).toBe(circomlibSignature.R8[0].toString()) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1].toString()) + expect(signature.S).toBe(circomlibSignature.S.toString()) + }) + + it("Should sign a message (buffer)", async () => { + const message = Buffer.from("message") + + const signature = signMessage(privateKey, message) + + const circomlibSignature = eddsa.signPoseidon(privateKey, BigInt(`0x${message.toString("hex")}`)) + + expect(signature.R8[0]).toBe(circomlibSignature.R8[0].toString()) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1].toString()) + expect(signature.S).toBe(circomlibSignature.S.toString()) + }) + + it("Should sign a message (string)", async () => { + const message = "message" + + const signature = signMessage(privateKey, message) + + const circomlibSignature = eddsa.signPoseidon(privateKey, BigInt(`0x${Buffer.from(message).toString("hex")}`)) + + expect(signature.R8[0]).toBe(circomlibSignature.R8[0].toString()) + expect(signature.R8[1]).toBe(circomlibSignature.R8[1].toString()) + expect(signature.S).toBe(circomlibSignature.S.toString()) + }) + + it("Should throw an error if the message type is not supported", async () => { + const message = true + + const fun = () => signMessage(privateKey, message as any) + + expect(fun).toThrow("Invalid message type.") + }) + it("Should verify a signature", async () => { const publicKey = generatePublicKey(privateKey) const signature = signMessage(privateKey, message) expect(verifySignature(message, signature, publicKey)).toBeTruthy() }) + + it("Should not verify a signature if the public key is malformed", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + publicKey[1] = 3 as any + + expect(verifySignature(message, signature, publicKey)).toBeFalsy() + }) + + it("Should not verify a signature if the signature is malformed", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + signature.S = 3 as any + + expect(verifySignature(message, signature, publicKey)).toBeFalsy() + }) + + it("Should not verify a signature if the signature is not on the curve", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + signature.R8[1] = BigInt(3).toString() + + expect(verifySignature(message, signature, publicKey)).toBeFalsy() + }) + + it("Should not verify a signature if the public key is not on the curve", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + publicKey[1] = BigInt(3).toString() + + expect(verifySignature(message, signature, publicKey)).toBeFalsy() + }) + + it("Should not verify a signature S value exceeds the predefined sub order", async () => { + const publicKey = generatePublicKey(privateKey) + const signature = signMessage(privateKey, message) + + signature.S = "3421888242871839275222246405745257275088614511777268538073601725287587578984328" + + expect(verifySignature(message, signature, publicKey)).toBeFalsy() + }) + + it("Should derive a public key from N random private keys", async () => { + for (let i = 0, len = 10; i < len; i += 1) { + const privateKey = crypto.randomBytes(32) + + const publicKey = generatePublicKey(privateKey) + + const circomlibPublicKey = eddsa.prv2pub(privateKey) + + expect(publicKey[0]).toBe(circomlibPublicKey[0].toString()) + expect(publicKey[1]).toBe(circomlibPublicKey[1].toString()) + } + }) }) From 3d7b45cfb912e0e9c707d2f53032a058e0184c3e Mon Sep 17 00:00:00 2001 From: Cedoor Date: Sat, 2 Dec 2023 10:29:57 +0000 Subject: [PATCH 08/18] docs(eddsa-poseidon): update README.md --- packages/eddsa-poseidon/README.md | 73 ++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/eddsa-poseidon/README.md b/packages/eddsa-poseidon/README.md index 9a2f5be2f..de45a2511 100644 --- a/packages/eddsa-poseidon/README.md +++ b/packages/eddsa-poseidon/README.md @@ -2,7 +2,7 @@

EdDSA Poseidon

-

A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.

+

A JavaScript EdDSA library for secure signing and verification using Poseidon and the Baby Jubjub elliptic curve.

@@ -41,6 +41,18 @@ +| This package offers a simplified JavaScript codebase essential for creating and validating digital signatures using EdDSA and Poseidon. It's built upon the Baby Jubjub elliptic curve, ensuring seamless integration with [Circom](https://github.com/iden3/circom) and enhancing the developer experience. | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + +- Super lightweight: [**~33kB**](https://bundlephobia.com/package/@zk-kit/eddsa-poseidon@0.1.0) (minified) +- Compatible with browsers and NodeJS +- TS type support +- Comprehensive code [documentation](https://zkkit.pse.dev/modules/_zk_kit_eddsa_poseidon.html) +- Full test coverage + +> [!WARNING] +> This library has **not** been audited. + ## 🛠 Install ### npm or yarn @@ -56,3 +68,62 @@ or yarn: ```bash yarn add @zk-kit/eddsa-poseidon ``` + +## 📜 Usage + +\# **derivePublicKey**(privateKey: _BigNumberish_): _Point\_ + +```typescript +import { derivePublicKey } from "@zk-kit/eddsa-poseidon" + +const privateKey = "secret" +const publicKey = derivePublicKey(privateKey) + +console.log(publicKey) +/* +[ + '17191193026255111087474416516591393721975640005415762645730433950079177536248', + '13751717961795090314625781035919035073474308127816403910435238282697898234143' +] +*/ +``` + +\# **signMessage**(privateKey: _BigNumberish_, message: _BigNumberish_): _Signature\_ + +```typescript +import { derivePublicKey, signMessage } from "@zk-kit/eddsa-poseidon" + +const privateKey = "secret" +const publicKey = derivePublicKey(privateKey) + +const message = "message" +const signature = signMessage(privateKey, message) + +console.log(signature) +/* +{ + R8: [ + '12949573675545142400102669657964360005184873166024880859462384824349649539693', + '18253636630408169174294927826710424418689461166073329946402765380454102840608' + ], + S: '701803947557694254685424075312408605924670918868054593580245088593184746870' +} +*/ +``` + + +\# **verifySignature**(message: _BigNumberish_, signature: _Signature_, publicKey: _Point_): _boolean_ + +```typescript +import { derivePublicKey, signMessage, verifySignature } from "@zk-kit/eddsa-poseidon" + +const privateKey = "secret" +const publicKey = derivePublicKey(privateKey) + +const message = "message" +const signature = signMessage(privateKey, message) + +const response = verifySignature(message, signature, publicKey) + +console.log(response) // true +``` From d1a576e584a3ab001ccf29fac7a1e84744444c88 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 10:31:04 +0000 Subject: [PATCH 09/18] chore(eddsa-poseidon): update package description --- packages/eddsa-poseidon/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json index 772501184..549ef3832 100644 --- a/packages/eddsa-poseidon/package.json +++ b/packages/eddsa-poseidon/package.json @@ -1,7 +1,7 @@ { "name": "@zk-kit/eddsa-poseidon", "version": "0.1.0", - "description": "A JavaScript EdDSA library for secure signing and verification using the Baby Jubjub elliptic curve.", + "description": "A JavaScript EdDSA library for secure signing and verification using Poseidon the Baby Jubjub elliptic curve.", "license": "MIT", "main": "dist/index.node.js", "exports": { From 7c424a51ef4aca158a663031890250b5d5048015 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 10:31:23 +0000 Subject: [PATCH 10/18] style(eddsa-poseidon): update function name --- packages/eddsa-poseidon/src/eddsa-poseidon.ts | 4 +-- packages/eddsa-poseidon/tests/index.test.ts | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index 6ab1c05a9..7c1a8096d 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -7,7 +7,7 @@ import { BigNumberish, Point, Signature } from "./types" import * as utils from "./utils" /** - * Generates a public key from a given private key using the + * Derives a public key from a given private key using the * {@link https://eips.ethereum.org/EIPS/eip-2494|Baby Jubjub} elliptic curve. * This function utilizes the Baby Jubjub elliptic curve for cryptographic operations. * The private key should be securely stored and managed, and it should never be exposed @@ -15,7 +15,7 @@ import * as utils from "./utils" * @param privateKey - The private key used for generating the public key. * @returns The derived public key. */ -export function generatePublicKey(privateKey: BigNumberish | string): Point { +export function derivePublicKey(privateKey: BigNumberish): Point { // Convert the private key to buffer. privateKey = utils.checkPrivateKey(privateKey) diff --git a/packages/eddsa-poseidon/tests/index.test.ts b/packages/eddsa-poseidon/tests/index.test.ts index 3fe01b52d..ccd178705 100644 --- a/packages/eddsa-poseidon/tests/index.test.ts +++ b/packages/eddsa-poseidon/tests/index.test.ts @@ -1,13 +1,13 @@ import { eddsa } from "circomlibjs" import crypto from "crypto" -import { generatePublicKey, signMessage, verifySignature } from "../src" +import { derivePublicKey, signMessage, verifySignature } from "../src" describe("EdDSAPoseidon", () => { const privateKey = "secret" const message = BigInt(2) it("Should derive a public key from a private key (string)", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(privateKey) @@ -18,7 +18,7 @@ describe("EdDSAPoseidon", () => { it("Should derive a public key from a private key (hexadecimal)", async () => { const privateKey = "0x12" - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.slice(2), "hex")) @@ -29,7 +29,7 @@ describe("EdDSAPoseidon", () => { it("Should derive a public key from a private key (buffer)", async () => { const privateKey = Buffer.from("secret") - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(privateKey) @@ -40,7 +40,7 @@ describe("EdDSAPoseidon", () => { it("Should derive a public key from a private key (bigint)", async () => { const privateKey = BigInt(22) - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex")) @@ -51,7 +51,7 @@ describe("EdDSAPoseidon", () => { it("Should derive a public key from a private key (number)", async () => { const privateKey = 22 - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex")) @@ -62,7 +62,7 @@ describe("EdDSAPoseidon", () => { it("Should throw an error if the secret type is not supported", async () => { const privateKey = true - const fun = () => generatePublicKey(privateKey as any) + const fun = () => derivePublicKey(privateKey as any) expect(fun).toThrow("Invalid private key type.") }) @@ -134,14 +134,14 @@ describe("EdDSAPoseidon", () => { }) it("Should verify a signature", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) expect(verifySignature(message, signature, publicKey)).toBeTruthy() }) it("Should not verify a signature if the public key is malformed", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) publicKey[1] = 3 as any @@ -150,7 +150,7 @@ describe("EdDSAPoseidon", () => { }) it("Should not verify a signature if the signature is malformed", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) signature.S = 3 as any @@ -159,7 +159,7 @@ describe("EdDSAPoseidon", () => { }) it("Should not verify a signature if the signature is not on the curve", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) signature.R8[1] = BigInt(3).toString() @@ -168,7 +168,7 @@ describe("EdDSAPoseidon", () => { }) it("Should not verify a signature if the public key is not on the curve", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) publicKey[1] = BigInt(3).toString() @@ -177,7 +177,7 @@ describe("EdDSAPoseidon", () => { }) it("Should not verify a signature S value exceeds the predefined sub order", async () => { - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const signature = signMessage(privateKey, message) signature.S = "3421888242871839275222246405745257275088614511777268538073601725287587578984328" @@ -189,7 +189,7 @@ describe("EdDSAPoseidon", () => { for (let i = 0, len = 10; i < len; i += 1) { const privateKey = crypto.randomBytes(32) - const publicKey = generatePublicKey(privateKey) + const publicKey = derivePublicKey(privateKey) const circomlibPublicKey = eddsa.prv2pub(privateKey) From 3619d8182587e78f672cac60ec8c52120e27eb62 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 10:34:23 +0000 Subject: [PATCH 11/18] chore(eddsa-poseidon): remove comments --- packages/eddsa-poseidon/src/field1.ts | 10 ---------- packages/eddsa-poseidon/src/scalar.ts | 8 -------- 2 files changed, 18 deletions(-) diff --git a/packages/eddsa-poseidon/src/field1.ts b/packages/eddsa-poseidon/src/field1.ts index 0457b3baf..945e1d57f 100644 --- a/packages/eddsa-poseidon/src/field1.ts +++ b/packages/eddsa-poseidon/src/field1.ts @@ -9,14 +9,6 @@ export default class Field1 { } e(res: bigint): bigint { - // if (res < 0) { - // let nres = -res - - // if (nres >= this._order) nres %= this._order - - // return this._order - nres - // } - return res >= this._order ? res % this._order : res } @@ -35,8 +27,6 @@ export default class Field1 { } inv(a: bigint): bigint { - // if (!a) throw new Error("Division by zero") - let t = this.zero let r = this._order let newt = this.one diff --git a/packages/eddsa-poseidon/src/scalar.ts b/packages/eddsa-poseidon/src/scalar.ts index 8e54d4eb5..e77d7f02d 100644 --- a/packages/eddsa-poseidon/src/scalar.ts +++ b/packages/eddsa-poseidon/src/scalar.ts @@ -1,5 +1,3 @@ -// const hexLen = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4] - export function isZero(a: bigint): boolean { return !a } @@ -12,12 +10,6 @@ export function shiftRight(a: bigint, n: bigint): bigint { return a >> n } -// export function bitLength(a: bigint): number { -// const aS = a.toString(16) - -// return (aS.length - 1) * 4 + hexLen[parseInt(aS[0], 16)] -// } - export function mul(a: bigint, b: bigint): bigint { return a * b } From 460db7456b4838174163c2c2d30008d4def41cd3 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 11:15:14 +0000 Subject: [PATCH 12/18] chore(eddsa-poseidon): add iife bundle with polyfills --- packages/eddsa-poseidon/package.json | 17 ++++-- packages/eddsa-poseidon/rollup.config.ts | 1 + packages/eddsa-poseidon/rollup.iife.config.ts | 47 +++++++++++++++ packages/eddsa-poseidon/tsconfig.json | 2 +- yarn.lock | 57 ++++++++++++++----- 5 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 packages/eddsa-poseidon/rollup.iife.config.ts diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json index 549ef3832..35020e0c0 100644 --- a/packages/eddsa-poseidon/package.json +++ b/packages/eddsa-poseidon/package.json @@ -3,10 +3,14 @@ "version": "0.1.0", "description": "A JavaScript EdDSA library for secure signing and verification using Poseidon the Baby Jubjub elliptic curve.", "license": "MIT", + "iife": "dist/index.js", + "unpkg": "dist/index.min.js", + "jsdelivr": "dist/index.min.js", "main": "dist/index.node.js", "exports": { "import": "./dist/index.mjs", - "require": "./dist/index.node.js" + "require": "./dist/index.node.js", + "types": "./dist/types/index.d.ts" }, "types": "dist/types/index.d.ts", "files": [ @@ -21,8 +25,8 @@ "url": "https://github.com/privacy-scaling-explorations/zk-kit.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", - "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", + "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript && yarn build:iife", + "build:iife": "rollup -c rollup.iife.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, "publishConfig": { @@ -31,11 +35,12 @@ "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "blake-hash": "2.0.0", "circomlibjs": "0.0.8", + "poseidon-lite": "0.2.0", "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-polyfill-node": "^0.13.0", + "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2" - }, - "dependencies": { - "poseidon-lite": "^0.2.0" } } diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts index 0b77ecf79..7d2994bd6 100644 --- a/packages/eddsa-poseidon/rollup.config.ts +++ b/packages/eddsa-poseidon/rollup.config.ts @@ -20,6 +20,7 @@ export default { { file: pkg.exports.require, format: "cjs", banner }, { file: pkg.exports.import, format: "es", banner } ], + external: [], plugins: [ typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), commonjs(), diff --git a/packages/eddsa-poseidon/rollup.iife.config.ts b/packages/eddsa-poseidon/rollup.iife.config.ts new file mode 100644 index 000000000..f7936620a --- /dev/null +++ b/packages/eddsa-poseidon/rollup.iife.config.ts @@ -0,0 +1,47 @@ +import commonjs from "@rollup/plugin-commonjs" +import { nodeResolve } from "@rollup/plugin-node-resolve" +import fs from "fs" +import nodePolyfills from "rollup-plugin-polyfill-node" +import cleanup from "rollup-plugin-cleanup" +import { terser } from "rollup-plugin-terser" +import typescript from "rollup-plugin-typescript2" + +const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8")) +const banner = `/** + * @module ${pkg.name} + * @version ${pkg.version} + * @file ${pkg.description} + * @copyright Ethereum Foundation ${new Date().getFullYear()} + * @license ${pkg.license} + * @see [Github]{@link ${pkg.homepage}} +*/` + +const name = pkg.name.split("/")[1].replace(/[-/]./g, (x: string) => x.toUpperCase()[1]) + +export default { + input: "src/index.ts", + output: [ + { + file: pkg.iife, + name, + format: "iife", + banner + }, + { + file: pkg.unpkg, + name, + format: "iife", + plugins: [terser({ output: { preamble: banner } })] + } + ], + external: [], + plugins: [ + typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), + commonjs(), + nodeResolve({ + preferBuiltins: true + }), + nodePolyfills({ include: null }), + cleanup({ comments: "jsdoc" }) + ] +} diff --git a/packages/eddsa-poseidon/tsconfig.json b/packages/eddsa-poseidon/tsconfig.json index 71510a096..81e592a16 100644 --- a/packages/eddsa-poseidon/tsconfig.json +++ b/packages/eddsa-poseidon/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "tests", "rollup.config.ts"] + "include": ["src", "tests", "rollup.config.ts", "rollup.iife.config.ts"] } diff --git a/yarn.lock b/yarn.lock index 443913e6d..710c2ee35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3324,6 +3324,22 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-inject@npm:^5.0.4": + version: 5.0.5 + resolution: "@rollup/plugin-inject@npm:5.0.5" + dependencies: + "@rollup/pluginutils": ^5.0.1 + estree-walker: ^2.0.2 + magic-string: ^0.30.3 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 22cb772fd6f7178308b2ece95cdde5f8615f6257197832166294552a7e4c0d3976dc996cbfa6470af3151d8b86c00091aa93da5f4db6ec563f11b6db29fd1b63 + languageName: node + linkType: hard + "@rollup/plugin-json@npm:^5.0.1": version: 5.0.2 resolution: "@rollup/plugin-json@npm:5.0.2" @@ -4426,9 +4442,11 @@ __metadata: dependencies: "@rollup/plugin-commonjs": ^25.0.7 "@rollup/plugin-node-resolve": ^15.2.3 + blake-hash: 2.0.0 circomlibjs: 0.0.8 - poseidon-lite: ^0.2.0 + poseidon-lite: 0.2.0 rollup-plugin-cleanup: ^3.2.1 + rollup-plugin-polyfill-node: ^0.13.0 rollup-plugin-terser: ^7.0.2 rollup-plugin-typescript2: ^0.31.2 languageName: unknown @@ -5526,6 +5544,18 @@ __metadata: languageName: node linkType: hard +"blake-hash@npm:2.0.0, blake-hash@npm:^2.0.0": + version: 2.0.0 + resolution: "blake-hash@npm:2.0.0" + dependencies: + node-addon-api: ^3.0.0 + node-gyp: latest + node-gyp-build: ^4.2.2 + readable-stream: ^3.6.0 + checksum: a0d9a8f3953b986d3b30a741a6c000dedcc9a03b1318f52cc01ae62d18829ba6cb1a4d8cbe74785abfdc952a21db410984523bd457764aca716162cfd3ca8ea4 + languageName: node + linkType: hard + "blake-hash@npm:^1.1.0": version: 1.1.1 resolution: "blake-hash@npm:1.1.1" @@ -5538,18 +5568,6 @@ __metadata: languageName: node linkType: hard -"blake-hash@npm:^2.0.0": - version: 2.0.0 - resolution: "blake-hash@npm:2.0.0" - dependencies: - node-addon-api: ^3.0.0 - node-gyp: latest - node-gyp-build: ^4.2.2 - readable-stream: ^3.6.0 - checksum: a0d9a8f3953b986d3b30a741a6c000dedcc9a03b1318f52cc01ae62d18829ba6cb1a4d8cbe74785abfdc952a21db410984523bd457764aca716162cfd3ca8ea4 - languageName: node - linkType: hard - "blake2b-wasm@git+https://github.com/jbaylina/blake2b-wasm.git": version: 2.1.0 resolution: "blake2b-wasm@https://github.com/jbaylina/blake2b-wasm.git#commit=0d5f024b212429c7f50a7f533aa3a2406b5b42b3" @@ -15235,7 +15253,7 @@ __metadata: languageName: node linkType: hard -"poseidon-lite@npm:^0.2.0": +"poseidon-lite@npm:0.2.0, poseidon-lite@npm:^0.2.0": version: 0.2.0 resolution: "poseidon-lite@npm:0.2.0" checksum: c47c6fd0a29a78ca1f7cf6ccb8b0c4f4e72930d944e63425e36f60c15d37fb0aeca30b8a22a30640ed68d631142282c0b8308da83b1a2b2bb92b87f5a2432c93 @@ -16133,6 +16151,17 @@ __metadata: languageName: node linkType: hard +"rollup-plugin-polyfill-node@npm:^0.13.0": + version: 0.13.0 + resolution: "rollup-plugin-polyfill-node@npm:0.13.0" + dependencies: + "@rollup/plugin-inject": ^5.0.4 + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 + checksum: 73c5b9086955afa108c940c13205fab4cece149d020a3faa696c5711bbb391d11aecd4c913ad2cc5ac24f9d43a4969ad8d087d085dd8d423dece45b6be4039bb + languageName: node + linkType: hard + "rollup-plugin-terser@npm:^7.0.2": version: 7.0.2 resolution: "rollup-plugin-terser@npm:7.0.2" From c77a6033540bafcde95c1fea7aace386c62002d0 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 11:16:27 +0000 Subject: [PATCH 13/18] docs(eddsa-poseidon): add cdn references to readme --- packages/eddsa-poseidon/README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/eddsa-poseidon/README.md b/packages/eddsa-poseidon/README.md index de45a2511..11c7dacb0 100644 --- a/packages/eddsa-poseidon/README.md +++ b/packages/eddsa-poseidon/README.md @@ -42,7 +42,7 @@ | This package offers a simplified JavaScript codebase essential for creating and validating digital signatures using EdDSA and Poseidon. It's built upon the Baby Jubjub elliptic curve, ensuring seamless integration with [Circom](https://github.com/iden3/circom) and enhancing the developer experience. | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | - Super lightweight: [**~33kB**](https://bundlephobia.com/package/@zk-kit/eddsa-poseidon@0.1.0) (minified) - Compatible with browsers and NodeJS @@ -69,6 +69,20 @@ or yarn: yarn add @zk-kit/eddsa-poseidon ``` +### CDN + +You can also load it using a `script` tag using [unpkg](https://unpkg.com/): + +```html + +``` + +or [JSDelivr](https://www.jsdelivr.com/): + +```html + +``` + ## 📜 Usage \# **derivePublicKey**(privateKey: _BigNumberish_): _Point\_ @@ -111,7 +125,6 @@ console.log(signature) */ ``` - \# **verifySignature**(message: _BigNumberish_, signature: _Signature_, publicKey: _Point_): _boolean_ ```typescript From 53772d46d839cd0a825408bd43180deeda62a092 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 11:16:54 +0000 Subject: [PATCH 14/18] chore(eddsa-poseidon): v0.2.0 --- packages/eddsa-poseidon/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json index 35020e0c0..65ec53e8b 100644 --- a/packages/eddsa-poseidon/package.json +++ b/packages/eddsa-poseidon/package.json @@ -1,6 +1,6 @@ { "name": "@zk-kit/eddsa-poseidon", - "version": "0.1.0", + "version": "0.2.0", "description": "A JavaScript EdDSA library for secure signing and verification using Poseidon the Baby Jubjub elliptic curve.", "license": "MIT", "iife": "dist/index.js", From b67c32503d4c0613615015076df5fde4b89bb77c Mon Sep 17 00:00:00 2001 From: Cedoor Date: Sat, 2 Dec 2023 11:34:07 +0000 Subject: [PATCH 15/18] docs: update README.md --- packages/eddsa-poseidon/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eddsa-poseidon/README.md b/packages/eddsa-poseidon/README.md index 11c7dacb0..c18b6d8f1 100644 --- a/packages/eddsa-poseidon/README.md +++ b/packages/eddsa-poseidon/README.md @@ -44,12 +44,14 @@ | This package offers a simplified JavaScript codebase essential for creating and validating digital signatures using EdDSA and Poseidon. It's built upon the Baby Jubjub elliptic curve, ensuring seamless integration with [Circom](https://github.com/iden3/circom) and enhancing the developer experience. | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -- Super lightweight: [**~33kB**](https://bundlephobia.com/package/@zk-kit/eddsa-poseidon@0.1.0) (minified) +- Super lightweight: [**~33kB**](https://bundlephobia.com/package/@zk-kit/eddsa-poseidon) (minified) - Compatible with browsers and NodeJS - TS type support - Comprehensive code [documentation](https://zkkit.pse.dev/modules/_zk_kit_eddsa_poseidon.html) - Full test coverage +👾 Would you like to try it now? Explore it now on [Ceditor](https://ceditor.cedoor.dev/52787e4ad57d2f2076648d509efc3448)! + > [!WARNING] > This library has **not** been audited. From d1b4244bef6d9242665225086a3d1d7eee1cfa1d Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 11:38:40 +0000 Subject: [PATCH 16/18] docs(eddsa-poseidon): add package to root readme --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index f99200140..48c4b0979 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,34 @@ + + + + @zk-kit/eddsa-poseidon + + + (docs) + + + + + + NPM version + + + + + + Downloads + + + + + + npm bundle size (scoped) + + + From 221dacaea7f323b986d1690de06472708bc06817 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 11:46:28 +0000 Subject: [PATCH 17/18] chore: update npm build scripts --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1db55cece..a1216580c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "bugs": "https://github.com/privacy-scaling-explorations/zk-kit/issues", "private": true, "scripts": { - "build": "yarn build:js && yarn compile:sol", + "build": "yarn build:libraries && yarn compile:contracts", "build:libraries": "yarn workspaces foreach --no-private run build", "compile:contracts": "yarn workspaces foreach run compile", "test": "yarn test:libraries && yarn test:contracts && yarn test:circuits", From 23314a7b123fa2ce2ca53d266f9b24478bc2fc36 Mon Sep 17 00:00:00 2001 From: cedoor Date: Sat, 2 Dec 2023 22:38:04 +0000 Subject: [PATCH 18/18] docs(eddsa-poseidon): add doc to baby jubjub functions --- packages/eddsa-poseidon/src/babyjub.ts | 79 ++++++++++++++----- packages/eddsa-poseidon/src/eddsa-poseidon.ts | 6 +- .../src/{field1.ts => field.ts} | 2 +- 3 files changed, 63 insertions(+), 24 deletions(-) rename packages/eddsa-poseidon/src/{field1.ts => field.ts} (97%) diff --git a/packages/eddsa-poseidon/src/babyjub.ts b/packages/eddsa-poseidon/src/babyjub.ts index c85b21bb1..749fb3ee5 100644 --- a/packages/eddsa-poseidon/src/babyjub.ts +++ b/packages/eddsa-poseidon/src/babyjub.ts @@ -1,36 +1,75 @@ -import Field1 from "./field1" +import Field from "./field" import * as scalar from "./scalar" import { Point } from "./types" -export const F = new Field1(BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")) +// Spec: https://eips.ethereum.org/EIPS/eip-2494 +// 'r' is the alt_bn128 prime order. +export const r = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// 'F' (F_r) is the prime finite field with r elements. +export const Fr = new Field(r) + +// Base8 is the base point used to generate other points on the curve. export const Base8: Point = [ - F.e(BigInt("5299619240641551281634865583518297030282874472190772894086521144482721001553")), - F.e(BigInt("16950150798460657717958625567821834550301663161624707787222815936182638968203")) + Fr.e(BigInt("5299619240641551281634865583518297030282874472190772894086521144482721001553")), + Fr.e(BigInt("16950150798460657717958625567821834550301663161624707787222815936182638968203")) ] +// Let E be the twisted Edwards elliptic curve defined over 'F_r' +// described by the equation 'ax^2 + y^2 = 1 + dx^2y^2'. + +// 'a' and 'd' are the parameters of the equation: +const a = Fr.e(BigInt("168700")) +const d = Fr.e(BigInt("168696")) + +// We call Baby Jubjub the curve 'E(F_r)', that is, the subgroup of 'F_r'-rational points of 'E'. + +// 'order' is order of the elliptic curve 'E'. export const order = BigInt("21888242871839275222246405745257275088614511777268538073601725287587578984328") export const subOrder = scalar.shiftRight(order, BigInt(3)) -const A = F.e(BigInt("168700")) -const D = F.e(BigInt("168696")) - -export function addPoint(a: Point, b: Point): Point { - const beta = F.mul(a[0], b[1]) - const gamma = F.mul(a[1], b[0]) - const delta = F.mul(F.sub(a[1], F.mul(A, a[0])), F.add(b[0], b[1])) +/** + * Performs point addition on the Baby Jubjub elliptic curve, + * calculating a third point from two given points. + * Let P1 = (x1, y1) and P2 = (x2, y2) be two arbitrary points of the curve. + * Then P1 + P2 = (x3, y3) is calculated in the following way: + * x3 = (x1*y2 + y1*x2)/(1 + d*x1*x2*y1*y2) + * y3 = (y1*y2 - a*x1*x2)/(1 - d*x1*x2*y1*y2) + * @param p1 - First point on the curve. + * @param p2 - Second point on the curve. + * @returns Resultant third point on the curve. + */ +export function addPoint(p1: Point, p2: Point): Point { + // beta = x1*y2 + const beta = Fr.mul(p1[0], p2[1]) + // gamma = y1*x2 + const gamma = Fr.mul(p1[1], p2[0]) + // delta = (y1-(a*x1))*(x2+y2) + const delta = Fr.mul(Fr.sub(p1[1], Fr.mul(a, p1[0])), Fr.add(p2[0], p2[1])) - const tau = F.mul(beta, gamma) - const dtau = F.mul(D, tau) + // x1*x2*y1*y2 + const tau = Fr.mul(beta, gamma) + // d*x1*x2*y1*y2 + const dtau = Fr.mul(d, tau) - const x = F.div(F.add(beta, gamma), F.add(F.one, dtau)) - const y = F.div(F.add(delta, F.sub(F.mul(A, beta), gamma)), F.sub(F.one, dtau)) + // x3 = (x1*y2 + y1*x2)/(1 + d*x1*x2*y1*y2) + const p3x = Fr.div(Fr.add(beta, gamma), Fr.add(Fr.one, dtau)) + // y3 = (y1*y2 - a*x1*x2)/(1 - d*x1*x2*y1*y2) + const p3y = Fr.div(Fr.add(delta, Fr.sub(Fr.mul(a, beta), gamma)), Fr.sub(Fr.one, dtau)) - return [x, y] + return [p3x, p3y] } +/** + * Performs a scalar multiplication by starting from the 'base' point and 'adding' + * it to itself 'e' times. + * @param base - The base point used as a starting point. + * @param e - A secret number representing the private key. + * @returns The resulting point representing the public key. + */ export function mulPointEscalar(base: Point, e: bigint): Point { - let res: Point = [F.e(BigInt(0)), F.e(BigInt(1))] + let res: Point = [Fr.e(BigInt(0)), Fr.e(BigInt(1))] let rem: bigint = e let exp: Point = base @@ -50,8 +89,8 @@ export function inCurve(p: Point) { p[0] = BigInt(p[0]) p[1] = BigInt(p[1]) - const x2 = F.square(p[0]) - const y2 = F.square(p[1]) + const x2 = Fr.square(p[0]) + const y2 = Fr.square(p[1]) - return F.eq(F.add(F.mul(A, x2), y2), F.add(F.one, F.mul(F.mul(x2, y2), D))) + return Fr.eq(Fr.add(Fr.mul(a, x2), y2), Fr.add(Fr.one, Fr.mul(Fr.mul(x2, y2), d))) } diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts index 7c1a8096d..cc85c4625 100644 --- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts +++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts @@ -1,7 +1,7 @@ import { poseidon5 } from "poseidon-lite/poseidon5" import * as babyjub from "./babyjub" import blake from "./blake" -import Field1 from "./field1" +import Field from "./field" import * as scalar from "./scalar" import { BigNumberish, Point, Signature } from "./types" import * as utils from "./utils" @@ -53,7 +53,7 @@ export function signMessage(privateKey: BigNumberish, message: BigNumberish): Si const rBuff = blake(Buffer.concat([hash.slice(32, 64), msgBuff])) - const Fr = new Field1(babyjub.subOrder) + const Fr = new Field(babyjub.subOrder) const r = Fr.e(utils.leBuff2int(rBuff)) const R8 = babyjub.mulPointEscalar(babyjub.Base8, r) @@ -104,5 +104,5 @@ export function verifySignature(message: BigNumberish, signature: Signature, pub pRight = babyjub.addPoint(_signature.R8, pRight) // Return true if the points match. - return babyjub.F.eq(BigInt(pLeft[0]), pRight[0]) && babyjub.F.eq(pLeft[1], pRight[1]) + return babyjub.Fr.eq(BigInt(pLeft[0]), pRight[0]) && babyjub.Fr.eq(pLeft[1], pRight[1]) } diff --git a/packages/eddsa-poseidon/src/field1.ts b/packages/eddsa-poseidon/src/field.ts similarity index 97% rename from packages/eddsa-poseidon/src/field1.ts rename to packages/eddsa-poseidon/src/field.ts index 945e1d57f..660bdcd74 100644 --- a/packages/eddsa-poseidon/src/field1.ts +++ b/packages/eddsa-poseidon/src/field.ts @@ -1,4 +1,4 @@ -export default class Field1 { +export default class Field { one = BigInt(1) zero = BigInt(0)