diff --git a/package.json b/package.json
index 5c7f00fad..a1216580c 100644
--- a/package.json
+++ b/package.json
@@ -8,9 +8,9 @@
"bugs": "https://github.com/privacy-scaling-explorations/zk-kit/issues",
"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": "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",
"test:libraries": "jest --coverage",
"test:circuits": "yarn workspace @zk-kit/circuits test",
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/README.md b/packages/eddsa-poseidon/README.md
new file mode 100644
index 000000000..c18b6d8f1
--- /dev/null
+++ b/packages/eddsa-poseidon/README.md
@@ -0,0 +1,144 @@
+
+
+ EdDSA Poseidon
+
+ A JavaScript EdDSA library for secure signing and verification using Poseidon and the Baby Jubjub elliptic curve.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| 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) (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.
+
+## 🛠 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
+```
+
+### 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\_
+
+```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
+```
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..65ec53e8b
--- /dev/null
+++ b/packages/eddsa-poseidon/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@zk-kit/eddsa-poseidon",
+ "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",
+ "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",
+ "types": "./dist/types/index.d.ts"
+ },
+ "types": "dist/types/index.d.ts",
+ "files": [
+ "dist/",
+ "src/",
+ "LICENSE",
+ "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": "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": {
+ "access": "public"
+ },
+ "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"
+ }
+}
diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts
new file mode 100644
index 000000000..7d2994bd6
--- /dev/null
+++ b/packages/eddsa-poseidon/rollup.config.ts
@@ -0,0 +1,32 @@
+import commonjs from "@rollup/plugin-commonjs"
+import { nodeResolve } from "@rollup/plugin-node-resolve"
+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 }
+ ],
+ external: [],
+ plugins: [
+ typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }),
+ commonjs(),
+ nodeResolve({
+ preferBuiltins: true
+ }),
+ cleanup({ comments: "jsdoc" })
+ ]
+}
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/src/babyjub.ts b/packages/eddsa-poseidon/src/babyjub.ts
new file mode 100644
index 000000000..749fb3ee5
--- /dev/null
+++ b/packages/eddsa-poseidon/src/babyjub.ts
@@ -0,0 +1,96 @@
+import Field from "./field"
+import * as scalar from "./scalar"
+import { Point } from "./types"
+
+// 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 = [
+ 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))
+
+/**
+ * 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]))
+
+ // x1*x2*y1*y2
+ const tau = Fr.mul(beta, gamma)
+ // d*x1*x2*y1*y2
+ const dtau = Fr.mul(d, tau)
+
+ // 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 [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 = [Fr.e(BigInt(0)), Fr.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
+}
+
+export function inCurve(p: Point) {
+ p[0] = BigInt(p[0])
+ p[1] = BigInt(p[1])
+
+ const x2 = Fr.square(p[0])
+ const y2 = Fr.square(p[1])
+
+ 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/blake.ts b/packages/eddsa-poseidon/src/blake.ts
new file mode 100644
index 000000000..9b258ea8e
--- /dev/null
+++ b/packages/eddsa-poseidon/src/blake.ts
@@ -0,0 +1,10 @@
+// @ts-ignore
+import { Blake512 } from "blake-hash/lib"
+
+export default function hash(message: Buffer): Buffer {
+ const engine = new Blake512()
+
+ engine.update(message)
+
+ return engine.digest()
+}
diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts
new file mode 100644
index 000000000..cc85c4625
--- /dev/null
+++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts
@@ -0,0 +1,108 @@
+import { poseidon5 } from "poseidon-lite/poseidon5"
+import * as babyjub from "./babyjub"
+import blake from "./blake"
+import Field from "./field"
+import * as scalar from "./scalar"
+import { BigNumberish, Point, Signature } from "./types"
+import * as utils from "./utils"
+
+/**
+ * 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
+ * or transmitted in an unsecured manner.
+ * @param privateKey - The private key used for generating the public key.
+ * @returns The derived public key.
+ */
+export function derivePublicKey(privateKey: BigNumberish): Point {
+ // Convert the private key to buffer.
+ privateKey = utils.checkPrivateKey(privateKey)
+
+ const hash = blake(privateKey)
+
+ const s = utils.leBuff2int(utils.pruneBuffer(hash.slice(0, 32)))
+
+ 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()]
+}
+
+/**
+ * Signs a message using the provided private key, employing Poseidon hashing and
+ * 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, containing properties relevant to EdDSA signatures, such as 'R8' and 'S' values.
+ */
+export function signMessage(privateKey: BigNumberish, message: BigNumberish): Signature {
+ // Convert the private key to buffer.
+ privateKey = utils.checkPrivateKey(privateKey)
+
+ // Convert the message to big integer.
+ message = utils.checkMessage(message)
+
+ const hash = blake(privateKey)
+
+ 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 = blake(Buffer.concat([hash.slice(32, 64), msgBuff]))
+
+ const Fr = new Field(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))
+
+ // Convert the signature values to strings so that it can easily be exported as a JSON.
+ return {
+ 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: 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.
+ message = utils.checkMessage(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, BigInt(signature.S))
+ let pRight = babyjub.mulPointEscalar(_publicKey, scalar.mul(hm, BigInt(8)))
+
+ pRight = babyjub.addPoint(_signature.R8, pRight)
+
+ // Return true if the points match.
+ return babyjub.Fr.eq(BigInt(pLeft[0]), pRight[0]) && babyjub.Fr.eq(pLeft[1], pRight[1])
+}
diff --git a/packages/eddsa-poseidon/src/field.ts b/packages/eddsa-poseidon/src/field.ts
new file mode 100644
index 000000000..660bdcd74
--- /dev/null
+++ b/packages/eddsa-poseidon/src/field.ts
@@ -0,0 +1,59 @@
+export default class Field {
+ one = BigInt(1)
+ zero = BigInt(0)
+
+ _order: bigint
+
+ constructor(order: bigint) {
+ this._order = order
+ }
+
+ e(res: bigint): bigint {
+ 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 {
+ 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
+ }
+
+ 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
new file mode 100644
index 000000000..1ce366761
--- /dev/null
+++ b/packages/eddsa-poseidon/src/index.ts
@@ -0,0 +1,2 @@
+export * from "./eddsa-poseidon"
+export * from "./types"
diff --git a/packages/eddsa-poseidon/src/scalar.ts b/packages/eddsa-poseidon/src/scalar.ts
new file mode 100644
index 000000000..e77d7f02d
--- /dev/null
+++ b/packages/eddsa-poseidon/src/scalar.ts
@@ -0,0 +1,15 @@
+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 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..eefb42be3
--- /dev/null
+++ b/packages/eddsa-poseidon/src/types/index.ts
@@ -0,0 +1,10 @@
+export type BigNumber = bigint | string
+
+export type BigNumberish = BigNumber | number | Buffer
+
+export type Point = [N, N]
+
+export type Signature = {
+ R8: Point
+ S: N
+}
diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts
new file mode 100644
index 000000000..9e652ecc9
--- /dev/null
+++ b/packages/eddsa-poseidon/src/utils.ts
@@ -0,0 +1,128 @@
+import { BigNumber, BigNumberish, Point, Signature } from "./types"
+
+export function pruneBuffer(buff: Buffer): Buffer {
+ buff[0] &= 0xf8
+ buff[31] &= 0x7f
+ buff[31] |= 0x40
+
+ 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)
+
+ // Ensure even length.
+ if (hex.length % 2 !== 0) {
+ hex = `0${hex}`
+ }
+
+ 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")
+ }
+
+ 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")}`)
+}
+
+export function leInt2Buff(n: bigint): Buffer {
+ const hex = int2hex(n)
+
+ // Allocate buffer of the desired size, filled with zeros.
+ const buffer = Buffer.alloc(32, 0)
+
+ Buffer.from(hex, "hex").reverse().copy(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
new file mode 100644
index 000000000..ccd178705
--- /dev/null
+++ b/packages/eddsa-poseidon/tests/index.test.ts
@@ -0,0 +1,200 @@
+import { eddsa } from "circomlibjs"
+import crypto from "crypto"
+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 = derivePublicKey(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 (hexadecimal)", async () => {
+ const privateKey = "0x12"
+
+ const publicKey = derivePublicKey(privateKey)
+
+ const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.slice(2), "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 (buffer)", async () => {
+ const privateKey = Buffer.from("secret")
+
+ const publicKey = derivePublicKey(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 = derivePublicKey(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 = derivePublicKey(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 = () => derivePublicKey(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)
+
+ 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 (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 = 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 = derivePublicKey(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 = derivePublicKey(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 = derivePublicKey(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 = derivePublicKey(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 = derivePublicKey(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 = derivePublicKey(privateKey)
+
+ const circomlibPublicKey = eddsa.prv2pub(privateKey)
+
+ expect(publicKey[0]).toBe(circomlibPublicKey[0].toString())
+ expect(publicKey[1]).toBe(circomlibPublicKey[1].toString())
+ }
+ })
+})
diff --git a/packages/eddsa-poseidon/tsconfig.json b/packages/eddsa-poseidon/tsconfig.json
new file mode 100644
index 000000000..81e592a16
--- /dev/null
+++ b/packages/eddsa-poseidon/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src", "tests", "rollup.config.ts", "rollup.iife.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..710c2ee35 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,41 @@ __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-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"
@@ -3319,7 +3354,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:
@@ -4401,6 +4436,22 @@ __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:
+ "@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
+ 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"
@@ -5493,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"
@@ -5505,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"
@@ -6450,7 +6501,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:
@@ -13458,6 +13509,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"
@@ -15193,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
@@ -16091,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"