Date: Wed, 20 Dec 2023 16:22:56 +0100
Subject: [PATCH 2/2] feat(utils): add new utils package
---
README.md | 56 +++++++
jest.config.ts | 2 +-
package.json | 2 +-
packages/baby-jubjub/README.md | 72 +++++++--
packages/baby-jubjub/package.json | 3 +
packages/baby-jubjub/rollup.config.ts | 8 +-
packages/baby-jubjub/src/baby-jubjub.ts | 26 ++--
packages/baby-jubjub/src/sqrt.ts | 7 +-
packages/baby-jubjub/src/types/index.ts | 9 +-
packages/baby-jubjub/src/utils.ts | 128 ----------------
packages/baby-jubjub/tests/index.test.ts | 22 +--
packages/eddsa-poseidon/package.json | 4 +
packages/eddsa-poseidon/rollup.config.ts | 2 +-
packages/eddsa-poseidon/rollup.iife.config.ts | 2 +-
packages/eddsa-poseidon/src/babyjub.ts | 142 ------------------
packages/eddsa-poseidon/src/eddsa-poseidon.ts | 66 +++++---
packages/eddsa-poseidon/src/field.ts | 121 ---------------
packages/eddsa-poseidon/src/scalar.ts | 36 -----
packages/eddsa-poseidon/src/sqrt.ts | 56 -------
packages/eddsa-poseidon/src/types/index.ts | 7 +-
packages/eddsa-poseidon/src/utils.ts | 98 ++----------
packages/imt/package.json | 7 +-
packages/imt/rollup.config.ts | 3 +-
packages/utils/LICENSE | 21 +++
packages/utils/README.md | 79 ++++++++++
packages/utils/build.tsconfig.json | 8 +
packages/utils/package.json | 41 +++++
packages/utils/rollup.config.ts | 26 ++++
packages/utils/rollup.iife.config.ts | 41 +++++
.../src/field.ts => utils/src/f1-field.ts} | 2 +-
packages/utils/src/index.ts | 7 +
packages/utils/src/number-checks.ts | 25 +++
packages/utils/src/number-conversions.ts | 59 ++++++++
packages/{baby-jubjub => utils}/src/scalar.ts | 0
packages/utils/src/types/index.ts | 3 +
packages/utils/tests/index.test.ts | 27 ++++
packages/utils/tsconfig.json | 4 +
packages/utils/typedoc.json | 3 +
yarn.lock | 16 +-
39 files changed, 578 insertions(+), 663 deletions(-)
delete mode 100644 packages/baby-jubjub/src/utils.ts
delete mode 100644 packages/eddsa-poseidon/src/babyjub.ts
delete mode 100644 packages/eddsa-poseidon/src/field.ts
delete mode 100644 packages/eddsa-poseidon/src/scalar.ts
delete mode 100644 packages/eddsa-poseidon/src/sqrt.ts
create mode 100644 packages/utils/LICENSE
create mode 100644 packages/utils/README.md
create mode 100644 packages/utils/build.tsconfig.json
create mode 100644 packages/utils/package.json
create mode 100644 packages/utils/rollup.config.ts
create mode 100644 packages/utils/rollup.iife.config.ts
rename packages/{baby-jubjub/src/field.ts => utils/src/f1-field.ts} (98%)
create mode 100644 packages/utils/src/index.ts
create mode 100644 packages/utils/src/number-checks.ts
create mode 100644 packages/utils/src/number-conversions.ts
rename packages/{baby-jubjub => utils}/src/scalar.ts (100%)
create mode 100644 packages/utils/src/types/index.ts
create mode 100644 packages/utils/tests/index.test.ts
create mode 100644 packages/utils/tsconfig.json
create mode 100644 packages/utils/typedoc.json
diff --git a/README.md b/README.md
index c70334b74..2b9c00ccf 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,62 @@
+
+
+
+ @zk-kit/baby-jubjub
+
+
+ (docs)
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+ @zk-kit/utils
+
+
+ (docs)
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
diff --git a/jest.config.ts b/jest.config.ts
index 60fc27f68..9b227f8d6 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -1,7 +1,7 @@
import fs from "fs"
import type { Config } from "@jest/types"
-const exclude = ["circuits", "imt.sol", "rollup-plugin-rust", "lazytower.sol", "lazytower.circom"]
+const exclude = ["circuits", "imt.sol", "rollup-plugin-rust", "lazytower.sol", "lazytower.circom", "utils"]
const projects: any = fs
.readdirSync("./packages", { withFileTypes: true })
diff --git a/package.json b/package.json
index 41b648dca..4c4004551 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"private": true,
"scripts": {
"build": "yarn build:libraries && yarn compile:contracts",
- "build:libraries": "yarn workspaces foreach --no-private run build",
+ "build:libraries": "yarn workspaces foreach -t --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",
diff --git a/packages/baby-jubjub/README.md b/packages/baby-jubjub/README.md
index 8069e3b3c..761030e53 100644
--- a/packages/baby-jubjub/README.md
+++ b/packages/baby-jubjub/README.md
@@ -41,17 +41,6 @@
-| WIP |
-| --- |
-
-- Super lightweight: [**~33kB**](https://bundlephobia.com/package/@zk-kit/baby-jubjub) (minified)
-- Compatible with browsers and NodeJS
-- TS type support
-- Comprehensive code [documentation](https://zkkit.pse.dev/modules/_zk_kit_baby_jubjub.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.
@@ -87,4 +76,63 @@ or [JSDelivr](https://www.jsdelivr.com/):
## 📜 Usage
-WIP
+\# **addPoint**(p1: _Point\_, p2: _Point\_): _bigint_
+
+```typescript
+import { addPoint } from "@zk-kit/baby-jubjub"
+
+const p1: Point = [BigInt(0), BigInt(1)]
+
+const newPoint = addPoint(p1, Base8)
+```
+
+\# **mulPointEscalar**(base: _Point\_, e: _bigint_): _Point\_
+
+```typescript
+import { Base8, mulPointEscalar } from "@zk-kit/baby-jubjub"
+
+const secretScalar = BigInt(324)
+
+const publicKey = mulPointEscalar(Base8, secretScalar)
+```
+
+\# **mulPointEscalar**(base: _Point\_, e: _bigint_): _Point\_
+
+```typescript
+import { inCurve, Base8, mulPointEscalar } from "@zk-kit/baby-jubjub"
+
+const secretScalar = BigInt(324)
+
+const publicKey = mulPointEscalar(Base8, secretScalar)
+
+const isInCurve = inCurve(publicKey)
+```
+
+\# **packPoint**(unpackedPoint: _Point\_): _bigint_
+
+```typescript
+import { packPoint, Base8, mulPointEscalar } from "@zk-kit/baby-jubjub"
+
+const secretScalar = BigInt(324)
+
+const publicKey = mulPointEscalar(Base8, secretScalar)
+
+const packedPoint = packPoint(publicKey)
+```
+
+\# **unpackPoint**(packedPoint: _bigint_): _Point\_ | _null_
+
+```typescript
+import { packPoint, unpackPoint, Base8, mulPointEscalar } from "@zk-kit/baby-jubjub"
+
+const secretScalar = BigInt(324)
+
+const publicKey = mulPointEscalar(Base8, secretScalar)
+
+const packedPoint = packPoint(publicKey)
+
+const unpackedPoint = unpackPoint(packedPoint)
+
+console.log(publicKey[0] === unpackedPoint[0]) // true
+console.log(publicKey[1] === unpackedPoint[1]) // true
+```
diff --git a/packages/baby-jubjub/package.json b/packages/baby-jubjub/package.json
index c66a92617..432d963a0 100644
--- a/packages/baby-jubjub/package.json
+++ b/packages/baby-jubjub/package.json
@@ -40,5 +40,8 @@
"rollup-plugin-polyfill-node": "^0.13.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2"
+ },
+ "dependencies": {
+ "@zk-kit/utils": "0.1.0"
}
}
diff --git a/packages/baby-jubjub/rollup.config.ts b/packages/baby-jubjub/rollup.config.ts
index 7d2994bd6..4efe45a74 100644
--- a/packages/baby-jubjub/rollup.config.ts
+++ b/packages/baby-jubjub/rollup.config.ts
@@ -1,5 +1,3 @@
-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"
@@ -20,13 +18,9 @@ export default {
{ file: pkg.exports.require, format: "cjs", banner },
{ file: pkg.exports.import, format: "es", banner }
],
- external: [],
+ external: Object.keys(pkg.dependencies),
plugins: [
typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }),
- commonjs(),
- nodeResolve({
- preferBuiltins: true
- }),
cleanup({ comments: "jsdoc" })
]
}
diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts
index 5cb9e8f2f..75ebe1266 100644
--- a/packages/baby-jubjub/src/baby-jubjub.ts
+++ b/packages/baby-jubjub/src/baby-jubjub.ts
@@ -1,8 +1,6 @@
-import Field from "./field"
-import * as scalar from "./scalar"
-import { Point } from "./types"
-import * as utils from "./utils"
+import { F1Field, bigintToHexadecimal, bufferToBigint, leBigintToBuffer, leBufferToBigint, scalar } from "@zk-kit/utils"
import * as sqrt from "./sqrt"
+import { Point } from "./types"
// Spec: https://eips.ethereum.org/EIPS/eip-2494
@@ -10,7 +8,7 @@ import * as sqrt from "./sqrt"
export const r = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")
// 'F' (F_r) is the prime finite field with r elements.
-export const Fr = new Field(r)
+export const Fr = new F1Field(r)
// Base8 is the base point used to generate other points on the curve.
export const Base8: Point = [
@@ -72,7 +70,7 @@ export function addPoint(p1: Point, p2: Point): Point {
*/
export function mulPointEscalar(base: Point, e: bigint): Point {
let res: Point = [Fr.e(BigInt(0)), Fr.e(BigInt(1))]
- let rem: bigint = Fr.e(e)
+ let rem: bigint = e
let exp: Point = base
while (!scalar.isZero(rem)) {
@@ -87,7 +85,7 @@ export function mulPointEscalar(base: Point, e: bigint): Point {
return res
}
-export function inCurve(p: Point) {
+export function inCurve(p: Point): boolean {
const x1 = BigInt(p[0])
const y1 = BigInt(p[1])
@@ -98,17 +96,17 @@ export function inCurve(p: Point) {
}
export function packPoint(unpackedPoint: Point): bigint {
- const buffer = utils.leInt2Buff(unpackedPoint[1])
+ const buffer = leBigintToBuffer(unpackedPoint[1])
if (Fr.lt(unpackedPoint[0], Fr.zero)) {
buffer[31] |= 0x80
}
- return utils.buff2int(buffer)
+ return bufferToBigint(buffer)
}
-export function unpackPoint(packedPoint: bigint): Point | null {
- const buffer = Buffer.from(utils.int2hex(packedPoint), "hex")
+export function unpackPoint(packedPoint: bigint): Point | null {
+ const buffer = Buffer.from(bigintToHexadecimal(packedPoint), "hex")
const unpackedPoint = new Array(2)
let sign = false
@@ -118,7 +116,7 @@ export function unpackPoint(packedPoint: bigint): Point | null {
buffer[31] &= 0x7f
}
- unpackedPoint[1] = utils.leBuff2int(buffer)
+ unpackedPoint[1] = leBufferToBigint(buffer)
if (scalar.gt(unpackedPoint[1], r)) {
return null
@@ -126,7 +124,7 @@ export function unpackPoint(packedPoint: bigint): Point | null {
const y2 = Fr.square(unpackedPoint[1])
- let x = sqrt.tonelliShanks(Fr.div(Fr.sub(Fr.one, y2), Fr.sub(a, Fr.mul(d, y2))))
+ let x = sqrt.tonelliShanks(Fr.div(Fr.sub(Fr.one, y2), Fr.sub(a, Fr.mul(d, y2))), r)
if (x == null) {
return null
@@ -138,5 +136,5 @@ export function unpackPoint(packedPoint: bigint): Point | null {
unpackedPoint[0] = x
- return unpackedPoint as Point
+ return unpackedPoint as Point
}
diff --git a/packages/baby-jubjub/src/sqrt.ts b/packages/baby-jubjub/src/sqrt.ts
index 5e3a05c6a..66844edc4 100644
--- a/packages/baby-jubjub/src/sqrt.ts
+++ b/packages/baby-jubjub/src/sqrt.ts
@@ -1,5 +1,4 @@
-import { r } from "./baby-jubjub"
-import Field from "./field"
+import { F1Field } from "@zk-kit/utils"
// Based on https://eprint.iacr.org/2012/685.pdf
// and https://github.com/iden3/ffjavascript/blob/6f37a93fabddf45100bf221de6a1399599497e5d/src/fsqrt.js#L38
@@ -11,8 +10,8 @@ import Field from "./field"
* @returns The square root.
*/
// eslint-disable-next-line import/prefer-default-export
-export function tonelliShanks(n: bigint) {
- const Fr = new Field(r)
+export function tonelliShanks(n: bigint, order: bigint) {
+ const Fr = new F1Field(order)
const sqrt_s = 28
const sqrt_z = BigInt("5978345932401256595026418116861078668372907927053715034645334559810731495452")
diff --git a/packages/baby-jubjub/src/types/index.ts b/packages/baby-jubjub/src/types/index.ts
index eefb42be3..b4ce0aab3 100644
--- a/packages/baby-jubjub/src/types/index.ts
+++ b/packages/baby-jubjub/src/types/index.ts
@@ -1,10 +1,3 @@
-export type BigNumber = bigint | string
-
-export type BigNumberish = BigNumber | number | Buffer
+import { BigNumber } from "@zk-kit/utils"
export type Point = [N, N]
-
-export type Signature = {
- R8: Point
- S: N
-}
diff --git a/packages/baby-jubjub/src/utils.ts b/packages/baby-jubjub/src/utils.ts
deleted file mode 100644
index 3427a9b29..000000000
--- a/packages/baby-jubjub/src/utils.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { BigNumber, BigNumberish, Point, Signature } from "./types"
-
-export function pruneBuffer(buff: Buffer): Buffer {
- buff[0] &= 0xf8
- buff[31] &= 0x7f
- buff[31] |= 0x40
-
- return buff
-}
-
-export 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/baby-jubjub/tests/index.test.ts b/packages/baby-jubjub/tests/index.test.ts
index 552a62516..00ccc0a6d 100644
--- a/packages/baby-jubjub/tests/index.test.ts
+++ b/packages/baby-jubjub/tests/index.test.ts
@@ -2,10 +2,13 @@ import { babyjub } from "circomlibjs"
import { addPoint, mulPointEscalar, Base8, inCurve, packPoint, unpackPoint, Point } from "../src"
describe("BabyJubjub", () => {
- const secretScalar = BigInt(2)
- const p1: Point = [BigInt(0), BigInt(1)]
+ const secretScalar = BigInt(324)
+
+ let publicKey: Point
it("Should add 1 point to the curve", async () => {
+ const p1: Point = [BigInt(0), BigInt(1)]
+
const newPoint = addPoint(p1, Base8)
const circomlibNewPoint = babyjub.addPoint(p1, Base8)
@@ -14,7 +17,7 @@ describe("BabyJubjub", () => {
})
it("Should derive a public key from a secret scalar", async () => {
- const publicKey = mulPointEscalar(Base8, secretScalar)
+ publicKey = mulPointEscalar(Base8, secretScalar)
const circomlibPublicKey = babyjub.mulPointEscalar(Base8, secretScalar)
@@ -23,23 +26,24 @@ describe("BabyJubjub", () => {
})
it("Should check if a point is in the curve", async () => {
- expect(inCurve(p1)).toBeTruthy()
+ expect(inCurve(publicKey)).toBeTruthy()
})
it("Should pack a point", async () => {
- const packedPoint = packPoint(p1)
+ const packedPoint = packPoint(publicKey)
- const expectedPackedPoint = babyjub.packPoint(p1)
+ const expectedPackedPoint = babyjub.packPoint(publicKey)
expect(packedPoint).toBe(BigInt(`0x${Buffer.from(expectedPackedPoint).toString("hex")}`))
})
it("Should unpack a packed public key", async () => {
- const packedPoint = packPoint(p1) as bigint
+ const publicKey = mulPointEscalar(Base8, secretScalar)
+ const packedPoint = packPoint(publicKey)
const unpackedPoint = unpackPoint(packedPoint) as Point
expect(unpackedPoint).not.toBeNull()
- expect(unpackedPoint[0]).toBe(p1[0])
- expect(unpackedPoint[1]).toBe(p1[1])
+ expect(unpackedPoint[0]).toBe(publicKey[0])
+ expect(unpackedPoint[1]).toBe(publicKey[1])
})
})
diff --git a/packages/eddsa-poseidon/package.json b/packages/eddsa-poseidon/package.json
index 3689caf1e..40d0a7a8d 100644
--- a/packages/eddsa-poseidon/package.json
+++ b/packages/eddsa-poseidon/package.json
@@ -42,5 +42,9 @@
"rollup-plugin-polyfill-node": "^0.13.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2"
+ },
+ "dependencies": {
+ "@zk-kit/baby-jubjub": "0.1.0",
+ "@zk-kit/utils": "0.1.0"
}
}
diff --git a/packages/eddsa-poseidon/rollup.config.ts b/packages/eddsa-poseidon/rollup.config.ts
index 7d2994bd6..9f2d1f624 100644
--- a/packages/eddsa-poseidon/rollup.config.ts
+++ b/packages/eddsa-poseidon/rollup.config.ts
@@ -20,7 +20,7 @@ export default {
{ file: pkg.exports.require, format: "cjs", banner },
{ file: pkg.exports.import, format: "es", banner }
],
- external: [],
+ external: Object.keys(pkg.dependencies),
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
index f7936620a..321cb34b5 100644
--- a/packages/eddsa-poseidon/rollup.iife.config.ts
+++ b/packages/eddsa-poseidon/rollup.iife.config.ts
@@ -1,8 +1,8 @@
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 nodePolyfills from "rollup-plugin-polyfill-node"
import { terser } from "rollup-plugin-terser"
import typescript from "rollup-plugin-typescript2"
diff --git a/packages/eddsa-poseidon/src/babyjub.ts b/packages/eddsa-poseidon/src/babyjub.ts
deleted file mode 100644
index 007d07b7c..000000000
--- a/packages/eddsa-poseidon/src/babyjub.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import Field from "./field"
-import * as scalar from "./scalar"
-import { Point } from "./types"
-import * as utils from "./utils"
-import * as sqrt from "./sqrt"
-
-// 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) {
- const x1 = BigInt(p[0])
- const y1 = BigInt(p[1])
-
- const x2 = Fr.square(x1)
- const y2 = Fr.square(y1)
-
- return Fr.eq(Fr.add(Fr.mul(a, x2), y2), Fr.add(Fr.one, Fr.mul(Fr.mul(x2, y2), d)))
-}
-
-export function packPoint(unpackedPoint: Point): bigint {
- const buffer = utils.leInt2Buff(unpackedPoint[1])
-
- if (Fr.lt(unpackedPoint[0], Fr.zero)) {
- buffer[31] |= 0x80
- }
-
- return utils.buff2int(buffer)
-}
-
-export function unpackPoint(packedPoint: bigint): Point | null {
- const buffer = Buffer.from(utils.int2hex(packedPoint), "hex")
- const unpackedPoint = new Array(2)
-
- let sign = false
-
- if (buffer[31] & 0x80) {
- sign = true
- buffer[31] &= 0x7f
- }
-
- unpackedPoint[1] = utils.leBuff2int(buffer)
-
- if (scalar.gt(unpackedPoint[1], r)) {
- return null
- }
-
- const y2 = Fr.square(unpackedPoint[1])
-
- let x = sqrt.tonelliShanks(Fr.div(Fr.sub(Fr.one, y2), Fr.sub(a, Fr.mul(d, y2))))
-
- if (x == null) {
- return null
- }
-
- if (sign) {
- x = Fr.neg(x)
- }
-
- unpackedPoint[0] = x
-
- return unpackedPoint as Point
-}
diff --git a/packages/eddsa-poseidon/src/eddsa-poseidon.ts b/packages/eddsa-poseidon/src/eddsa-poseidon.ts
index 1f5d9d7ef..4cc2d0a58 100644
--- a/packages/eddsa-poseidon/src/eddsa-poseidon.ts
+++ b/packages/eddsa-poseidon/src/eddsa-poseidon.ts
@@ -1,9 +1,27 @@
+import {
+ Base8,
+ Fr,
+ Point,
+ addPoint,
+ inCurve,
+ mulPointEscalar,
+ packPoint,
+ subOrder,
+ unpackPoint
+} from "@zk-kit/baby-jubjub"
+import {
+ BigNumber,
+ BigNumberish,
+ F1Field,
+ isHexadecimal,
+ isStringifiedBigint,
+ leBigintToBuffer,
+ leBufferToBigint,
+ scalar
+} from "@zk-kit/utils"
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 { BigNumber, BigNumberish, Point, Signature } from "./types"
+import { Signature } from "./types"
import * as utils from "./utils"
/**
@@ -25,7 +43,7 @@ export function deriveSecretScalar(privateKey: BigNumberish): string {
hash = hash.slice(0, 32)
hash = utils.pruneBuffer(hash)
- return scalar.shiftRight(utils.leBuff2int(hash), BigInt(3)).toString()
+ return scalar.shiftRight(leBufferToBigint(hash), BigInt(3)).toString()
}
/**
@@ -40,7 +58,7 @@ export function deriveSecretScalar(privateKey: BigNumberish): string {
export function derivePublicKey(privateKey: BigNumberish): Point {
const s = deriveSecretScalar(privateKey)
- const publicKey = babyjub.mulPointEscalar(babyjub.Base8, BigInt(s))
+ const publicKey = mulPointEscalar(Base8, BigInt(s))
// Convert the public key values to strings so that it can easily be exported as a JSON.
return [publicKey[0].toString(), publicKey[1].toString()]
@@ -63,17 +81,17 @@ export function signMessage(privateKey: BigNumberish, message: BigNumberish): Si
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 s = leBufferToBigint(sBuff)
+ const A = mulPointEscalar(Base8, scalar.shiftRight(s, BigInt(3)))
- const msgBuff = utils.leInt2Buff(message)
+ const msgBuff = leBigintToBuffer(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 Fr = new F1Field(subOrder)
+ const r = Fr.e(leBufferToBigint(rBuff))
- const R8 = babyjub.mulPointEscalar(babyjub.Base8, r)
+ const R8 = mulPointEscalar(Base8, r)
const hm = poseidon5([R8[0], R8[1], A[0], A[1], message])
const S = Fr.add(r, Fr.mul(hm, s))
@@ -95,9 +113,9 @@ export function verifySignature(message: BigNumberish, signature: Signature, pub
if (
!utils.isPoint(publicKey) ||
!utils.isSignature(signature) ||
- !babyjub.inCurve(signature.R8) ||
- !babyjub.inCurve(publicKey) ||
- BigInt(signature.S) >= babyjub.subOrder
+ !inCurve(signature.R8) ||
+ !inCurve(publicKey) ||
+ BigInt(signature.S) >= subOrder
) {
return false
}
@@ -115,24 +133,24 @@ export function verifySignature(message: BigNumberish, signature: Signature, pub
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)))
+ const pLeft = mulPointEscalar(Base8, BigInt(signature.S))
+ let pRight = mulPointEscalar(_publicKey, scalar.mul(hm, BigInt(8)))
- pRight = babyjub.addPoint(_signature.R8, pRight)
+ pRight = 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])
+ return Fr.eq(BigInt(pLeft[0]), pRight[0]) && Fr.eq(pLeft[1], pRight[1])
}
export function packPublicKey(publicKey: Point): string {
- if (!utils.isPoint(publicKey) || !babyjub.inCurve(publicKey)) {
+ if (!utils.isPoint(publicKey) || !inCurve(publicKey)) {
throw new Error("Invalid public key")
}
// Convert the public key values to big integers for calculations.
const _publicKey: Point = [BigInt(publicKey[0]), BigInt(publicKey[1])]
- const packedPublicKey = babyjub.packPoint(_publicKey)
+ const packedPublicKey = packPoint(_publicKey)
if (packedPublicKey === null) {
throw new Error("Invalid public key")
@@ -144,13 +162,13 @@ export function packPublicKey(publicKey: Point): string {
export function unpackPublicKey(publicKey: BigNumber): Point {
if (
typeof publicKey !== "bigint" &&
- (typeof publicKey !== "string" || !utils.isStringifiedBigint(publicKey)) &&
- (typeof publicKey !== "string" || !utils.isHexadecimal(publicKey))
+ (typeof publicKey !== "string" || !isStringifiedBigint(publicKey)) &&
+ (typeof publicKey !== "string" || !isHexadecimal(publicKey))
) {
throw new TypeError("Invalid public key type")
}
- const unpackedPublicKey = babyjub.unpackPoint(BigInt(publicKey))
+ const unpackedPublicKey = unpackPoint(BigInt(publicKey))
if (unpackedPublicKey === null) {
throw new Error("Invalid public key")
diff --git a/packages/eddsa-poseidon/src/field.ts b/packages/eddsa-poseidon/src/field.ts
deleted file mode 100644
index 4f322b6e2..000000000
--- a/packages/eddsa-poseidon/src/field.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import * as scalar from "./scalar"
-
-export default class Field {
- one = BigInt(1)
- zero = BigInt(0)
-
- _order: bigint
- _half: bigint
- _negone: bigint
-
- constructor(order: bigint) {
- this._order = order
- this._half = order >> this.one
- this._negone = this._order - this.one
- }
-
- 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 {
- 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
- }
-
- lt(a: bigint, b: bigint) {
- const aa = a > this._half ? a - this._order : a
- const bb = b > this._half ? b - this._order : b
-
- return aa < bb
- }
-
- geq(a: bigint, b: bigint) {
- const aa = a > this._half ? a - this._order : a
- const bb = b > this._half ? b - this._order : b
-
- return aa >= bb
- }
-
- neg(a: bigint) {
- return a ? this._order - a : a
- }
-
- isZero(a: bigint) {
- return a === this.zero
- }
-
- pow(base: bigint, e: bigint) {
- if (scalar.isZero(e)) {
- return this.one
- }
-
- const n = scalar.bits(e)
-
- if (n.length === 0) {
- return this.one
- }
-
- let res = base
-
- for (let i = n.length - 2; i >= 0; i -= 1) {
- res = this.square(res)
-
- if (n[i]) {
- res = this.mul(res, base)
- }
- }
-
- return res
- }
-}
diff --git a/packages/eddsa-poseidon/src/scalar.ts b/packages/eddsa-poseidon/src/scalar.ts
deleted file mode 100644
index 0f15bcae8..000000000
--- a/packages/eddsa-poseidon/src/scalar.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-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
-}
-
-export function gt(a: bigint, b: bigint) {
- return a > b
-}
-
-export function bits(n: bigint): number[] {
- const res = []
- let E = n
-
- while (E) {
- if (E & BigInt(1)) {
- res.push(1)
- } else {
- res.push(0)
- }
-
- E >>= BigInt(1)
- }
-
- return res
-}
diff --git a/packages/eddsa-poseidon/src/sqrt.ts b/packages/eddsa-poseidon/src/sqrt.ts
deleted file mode 100644
index 099c59723..000000000
--- a/packages/eddsa-poseidon/src/sqrt.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { r } from "./babyjub"
-import Field from "./field"
-
-// Based on https://eprint.iacr.org/2012/685.pdf
-// and https://github.com/iden3/ffjavascript/blob/6f37a93fabddf45100bf221de6a1399599497e5d/src/fsqrt.js#L38
-
-/**
- * The following is a sqrt function (i.e. tonelliShanks) with some pre-computed
- * constants and it only works with 'r'.
- * @param n - The number for which to calculate the square root.
- * @returns The square root.
- */
-// eslint-disable-next-line import/prefer-default-export
-export function tonelliShanks(n: bigint) {
- const Fr = new Field(r)
-
- const sqrt_s = 28
- const sqrt_z = BigInt("5978345932401256595026418116861078668372907927053715034645334559810731495452")
- const sqrt_tm1d2 = BigInt("40770029410420498293352137776570907027550720424234931066070132305055")
-
- if (Fr.isZero(n)) return Fr.zero
-
- let w = Fr.pow(n, sqrt_tm1d2)
- const a0 = Fr.pow(Fr.mul(Fr.square(w), n), BigInt(2 ** (sqrt_s - 1)))
-
- if (Fr.eq(a0, Fr._negone)) {
- return null
- }
-
- let v = sqrt_s
- let x = Fr.mul(n, w)
- let b = Fr.mul(x, w)
- let z = sqrt_z
-
- while (!Fr.eq(b, Fr.one)) {
- let b2k = Fr.square(b)
- let k = 1
- while (!Fr.eq(b2k, Fr.one)) {
- b2k = Fr.square(b2k)
- k += 1
- }
-
- w = z
-
- for (let i = 0; i < v - k - 1; i += 1) {
- w = Fr.square(w)
- }
-
- z = Fr.square(w)
- b = Fr.mul(b, z)
- x = Fr.mul(x, w)
- v = k
- }
-
- return Fr.geq(x, Fr.zero) ? x : Fr.neg(x)
-}
diff --git a/packages/eddsa-poseidon/src/types/index.ts b/packages/eddsa-poseidon/src/types/index.ts
index eefb42be3..1cbbf41a5 100644
--- a/packages/eddsa-poseidon/src/types/index.ts
+++ b/packages/eddsa-poseidon/src/types/index.ts
@@ -1,8 +1,5 @@
-export type BigNumber = bigint | string
-
-export type BigNumberish = BigNumber | number | Buffer
-
-export type Point = [N, N]
+import { Point } from "@zk-kit/baby-jubjub"
+import { BigNumber } from "@zk-kit/utils"
export type Signature = {
R8: Point
diff --git a/packages/eddsa-poseidon/src/utils.ts b/packages/eddsa-poseidon/src/utils.ts
index 3427a9b29..65ddf4400 100644
--- a/packages/eddsa-poseidon/src/utils.ts
+++ b/packages/eddsa-poseidon/src/utils.ts
@@ -1,4 +1,13 @@
-import { BigNumber, BigNumberish, Point, Signature } from "./types"
+import { Point } from "@zk-kit/baby-jubjub"
+import {
+ BigNumberish,
+ bigNumberishToBigint,
+ bigNumberishToBuffer,
+ bufferToBigint,
+ isBigNumberish,
+ isStringifiedBigint
+} from "@zk-kit/utils"
+import { Signature } from "./types"
export function pruneBuffer(buff: Buffer): Buffer {
buff[0] &= 0xf8
@@ -8,30 +17,6 @@ export function pruneBuffer(buff: Buffer): Buffer {
return buff
}
-export 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])
}
@@ -46,66 +31,9 @@ export function isSignature(signature: Signature): boolean {
)
}
-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)
+ return bigNumberishToBuffer(privateKey)
}
if (typeof privateKey !== "string") {
@@ -117,12 +45,12 @@ export function checkPrivateKey(privateKey: BigNumberish): Buffer {
export function checkMessage(message: BigNumberish): bigint {
if (isBigNumberish(message)) {
- return bigNumberish2BigNumber(message)
+ return bigNumberishToBigint(message)
}
if (typeof message !== "string") {
throw TypeError("Invalid message type. Supported types: number, bigint, buffer, string.")
}
- return buff2int(Buffer.from(message))
+ return bufferToBigint(Buffer.from(message))
}
diff --git a/packages/imt/package.json b/packages/imt/package.json
index ee90e56d3..14668d34a 100644
--- a/packages/imt/package.json
+++ b/packages/imt/package.json
@@ -21,13 +21,10 @@
],
"repository": "git@github.com:privacy-scaling-explorations/zk-kit.git",
"homepage": "https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/imt",
- "author": {
- "name": "Cedoor",
- "email": "me@cedoor.dev",
- "url": "https://cedoor.dev"
+ "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"
},
diff --git a/packages/imt/rollup.config.ts b/packages/imt/rollup.config.ts
index da32af1a2..43545a0f0 100644
--- a/packages/imt/rollup.config.ts
+++ b/packages/imt/rollup.config.ts
@@ -8,10 +8,11 @@ const banner = `/**
* @module ${pkg.name}
* @version ${pkg.version}
* @file ${pkg.description}
- * @copyright ${pkg.author.name} ${new Date().getFullYear()}
+ * @copyright Ethereum Foundation ${new Date().getFullYear()}
* @license ${pkg.license}
* @see [Github]{@link ${pkg.homepage}}
*/`
+
const name = pkg.name.substr(1).replace(/[-/]./g, (x: string) => x.toUpperCase()[1])
export default {
diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE
new file mode 100644
index 000000000..4377091ec
--- /dev/null
+++ b/packages/utils/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/utils/README.md b/packages/utils/README.md
new file mode 100644
index 000000000..849a9fe56
--- /dev/null
+++ b/packages/utils/README.md
@@ -0,0 +1,79 @@
+
+
+ Utils
+
+ Essential zero-knowledge utility library for JavaScript developers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+> [!WARNING]
+> This library has **not** been audited.
+
+## 🛠 Install
+
+### npm or yarn
+
+Install the `@zk-kit/utils` package and its peer dependencies with npm:
+
+```bash
+npm i @zk-kit/utils
+```
+
+or yarn:
+
+```bash
+yarn add @zk-kit/utils
+```
+
+### CDN
+
+You can also load it using a `script` tag using [unpkg](https://unpkg.com/):
+
+```html
+
+```
+
+or [JSDelivr](https://www.jsdelivr.com/):
+
+```html
+
+```
+
+## 📜 Usage
+
+For more information on the functions provided by `@zk-kit/utils`, please refer to the [documentation](https://zkkit.pse.dev/modules/_zk_kit_utils.html).
diff --git a/packages/utils/build.tsconfig.json b/packages/utils/build.tsconfig.json
new file mode 100644
index 000000000..2d4a1d6da
--- /dev/null
+++ b/packages/utils/build.tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "declarationDir": "dist/types"
+ },
+ "include": ["src"]
+}
diff --git a/packages/utils/package.json b/packages/utils/package.json
new file mode 100644
index 000000000..2818697f9
--- /dev/null
+++ b/packages/utils/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "@zk-kit/utils",
+ "version": "0.1.0",
+ "description": "Essential zero-knowledge utility library for JavaScript developers.",
+ "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/utils",
+ "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-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/utils/rollup.config.ts b/packages/utils/rollup.config.ts
new file mode 100644
index 000000000..75b0a7bb4
--- /dev/null
+++ b/packages/utils/rollup.config.ts
@@ -0,0 +1,26 @@
+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 }),
+ cleanup({ comments: "jsdoc" })
+ ]
+}
diff --git a/packages/utils/rollup.iife.config.ts b/packages/utils/rollup.iife.config.ts
new file mode 100644
index 000000000..002d7d9f5
--- /dev/null
+++ b/packages/utils/rollup.iife.config.ts
@@ -0,0 +1,41 @@
+import fs from "fs"
+import cleanup from "rollup-plugin-cleanup"
+import nodePolyfills from "rollup-plugin-polyfill-node"
+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.substr(1).replace(/[-/]./g, (x) => 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 }),
+ nodePolyfills({ include: null }),
+ cleanup({ comments: "jsdoc" })
+ ]
+}
diff --git a/packages/baby-jubjub/src/field.ts b/packages/utils/src/f1-field.ts
similarity index 98%
rename from packages/baby-jubjub/src/field.ts
rename to packages/utils/src/f1-field.ts
index 4f322b6e2..e4ef99db2 100644
--- a/packages/baby-jubjub/src/field.ts
+++ b/packages/utils/src/f1-field.ts
@@ -1,6 +1,6 @@
import * as scalar from "./scalar"
-export default class Field {
+export default class F1Field {
one = BigInt(1)
zero = BigInt(0)
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
new file mode 100644
index 000000000..e5604aac3
--- /dev/null
+++ b/packages/utils/src/index.ts
@@ -0,0 +1,7 @@
+import F1Field from "./f1-field"
+import * as scalar from "./scalar"
+
+export { F1Field, scalar }
+export * from "./types"
+export * from "./number-checks"
+export * from "./number-conversions"
diff --git a/packages/utils/src/number-checks.ts b/packages/utils/src/number-checks.ts
new file mode 100644
index 000000000..ac2e443f1
--- /dev/null
+++ b/packages/utils/src/number-checks.ts
@@ -0,0 +1,25 @@
+import { BigNumber, BigNumberish } from "./types"
+
+export 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)
+ )
+}
diff --git a/packages/utils/src/number-conversions.ts b/packages/utils/src/number-conversions.ts
new file mode 100644
index 000000000..dc1c1e730
--- /dev/null
+++ b/packages/utils/src/number-conversions.ts
@@ -0,0 +1,59 @@
+import { BigNumberish } from "./types"
+import { isHexadecimal, isStringifiedBigint } from "./number-checks"
+
+export function bigintToHexadecimal(n: bigint) {
+ let hex = n.toString(16)
+
+ // Ensure even length.
+ if (hex.length % 2 !== 0) {
+ hex = `0${hex}`
+ }
+
+ return hex
+}
+
+export function bigNumberishToBuffer(value: BigNumberish): Buffer {
+ if (
+ typeof value === "number" ||
+ typeof value === "bigint" ||
+ (typeof value === "string" && isStringifiedBigint(value))
+ ) {
+ const hex = bigintToHexadecimal(BigInt(value))
+
+ return Buffer.from(hex, "hex")
+ }
+
+ return value as Buffer
+}
+
+export function bufferToBigint(buffer: Buffer): bigint {
+ return BigInt(`0x${buffer.toString("hex")}`)
+}
+
+export function bigNumberishToBigint(value: BigNumberish): bigint {
+ if (
+ typeof value === "number" ||
+ typeof value === "bigint" ||
+ (typeof value === "string" && isStringifiedBigint(value)) ||
+ (typeof value === "string" && isHexadecimal(value))
+ ) {
+ return BigInt(value)
+ }
+
+ return bufferToBigint(value as Buffer)
+}
+
+export function leBufferToBigint(buffer: Buffer): bigint {
+ return BigInt(`0x${buffer.reverse().toString("hex")}`)
+}
+
+export function leBigintToBuffer(n: bigint): Buffer {
+ const hex = bigintToHexadecimal(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
+}
diff --git a/packages/baby-jubjub/src/scalar.ts b/packages/utils/src/scalar.ts
similarity index 100%
rename from packages/baby-jubjub/src/scalar.ts
rename to packages/utils/src/scalar.ts
diff --git a/packages/utils/src/types/index.ts b/packages/utils/src/types/index.ts
new file mode 100644
index 000000000..327c62aad
--- /dev/null
+++ b/packages/utils/src/types/index.ts
@@ -0,0 +1,3 @@
+export type BigNumber = bigint | string
+
+export type BigNumberish = BigNumber | number | Buffer
diff --git a/packages/utils/tests/index.test.ts b/packages/utils/tests/index.test.ts
new file mode 100644
index 000000000..73a625252
--- /dev/null
+++ b/packages/utils/tests/index.test.ts
@@ -0,0 +1,27 @@
+import { F1Field } from "../src"
+
+describe("Utils", () => {
+ describe("F1Field", () => {
+ let field: F1Field
+
+ beforeEach(() => {
+ field = new F1Field(BigInt(12))
+ })
+
+ it("Should create a finite field with a specific order", async () => {
+ expect(field.one).toBe(BigInt(1))
+ expect(field.zero).toBe(BigInt(0))
+ expect(field._order).toBe(BigInt(12))
+ expect(field._half).toBe(BigInt(12) >> BigInt(1))
+ expect(field._negone).toBe(BigInt(12) - BigInt(1))
+ })
+
+ it("Should map the value back into the finite field", async () => {
+ const a = field.e(BigInt(24))
+ const b = field.e(BigInt(-2))
+
+ expect(a).toBe(BigInt(0))
+ expect(b).toBe(BigInt(10))
+ })
+ })
+})
diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json
new file mode 100644
index 000000000..71510a096
--- /dev/null
+++ b/packages/utils/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src", "tests", "rollup.config.ts"]
+}
diff --git a/packages/utils/typedoc.json b/packages/utils/typedoc.json
new file mode 100644
index 000000000..77a471c91
--- /dev/null
+++ b/packages/utils/typedoc.json
@@ -0,0 +1,3 @@
+{
+ "entryPoints": ["src/index.ts"]
+}
diff --git a/yarn.lock b/yarn.lock
index 5a5b3e8fa..c273afd9d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4423,12 +4423,13 @@ __metadata:
languageName: node
linkType: hard
-"@zk-kit/baby-jubjub@workspace:packages/baby-jubjub":
+"@zk-kit/baby-jubjub@0.1.0, @zk-kit/baby-jubjub@workspace:packages/baby-jubjub":
version: 0.0.0-use.local
resolution: "@zk-kit/baby-jubjub@workspace:packages/baby-jubjub"
dependencies:
"@rollup/plugin-commonjs": ^25.0.7
"@rollup/plugin-node-resolve": ^15.2.3
+ "@zk-kit/utils": 0.1.0
circomlibjs: 0.0.8
rollup-plugin-cleanup: ^3.2.1
rollup-plugin-polyfill-node: ^0.13.0
@@ -4456,6 +4457,8 @@ __metadata:
dependencies:
"@rollup/plugin-commonjs": ^25.0.7
"@rollup/plugin-node-resolve": ^15.2.3
+ "@zk-kit/baby-jubjub": 0.1.0
+ "@zk-kit/utils": 0.1.0
blake-hash: 2.0.0
circomlibjs: 0.0.8
poseidon-lite: 0.2.0
@@ -4576,6 +4579,17 @@ __metadata:
languageName: unknown
linkType: soft
+"@zk-kit/utils@0.1.0, @zk-kit/utils@workspace:packages/utils":
+ version: 0.0.0-use.local
+ resolution: "@zk-kit/utils@workspace:packages/utils"
+ dependencies:
+ 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
+
"JSONStream@npm:^1.0.4":
version: 1.3.5
resolution: "JSONStream@npm:1.3.5"
|