Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bundle and auto-load trusted setup #422

Merged
merged 12 commits into from
Apr 30, 2024
1 change: 1 addition & 0 deletions bindings/node.js/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ build: install clean
@cp -r ../../blst deps
@cp ../../src/c_kzg_4844.c deps/c-kzg
@cp ../../src/c_kzg_4844.h deps/c-kzg
@cp ../../src/trusted_setup.txt deps/c-kzg
@# Build the bindings
@$(YARN) node-gyp --loglevel=warn configure
@$(YARN) node-gyp --loglevel=warn build
Expand Down
22 changes: 15 additions & 7 deletions bindings/node.js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ API. The core functionality was originally a stripped-down copy of
since then. This package wraps that native `c-kzg` C code in C/C++ NAPI
bindings for use in node.js applications.

Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/polynomial-commitments.md
Spec: <https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/polynomial-commitments.md>

## Prerequisites

Expand Down Expand Up @@ -56,14 +56,22 @@ const isValid = verifyBlobKzgProofBatch(blobs, commitments, proofs);

```ts
/**
* Sets up the c-kzg library. Pass in a properly formatted trusted setup file
* to configure the library. File must be in json format, see TrustedSetupJson
* interface for more details, or as a properly formatted utf-8 encoded file.
* Initialize the library with a trusted setup file.
*
* @remark This function must be run before any other functions in this
* library can be run.
* Can pass either a .txt or a .json file with setup configuration. Converts
* JSON formatted trusted setup into the native format that the base library
* requires. The created file will be in the same as the origin file but with a
* ".txt" extension.
*
* @param {string} filePath - The absolute path of the trusted setup
* Uses user provided location first. If one is not provided then defaults to
* the official Ethereum mainnet setup from the KZG ceremony. Should only be
* used for cases where the Ethereum official mainnet KZG setup is acceptable.
*
* @param {string | undefined} filePath - .txt/.json file with setup configuration
* @default - If no string is passed the default trusted setup from the Ethereum KZG ceremony is used
*
* @throws {TypeError} - Non-String input
* @throws {Error} - For all other errors. See error message for more info
*/
loadTrustedSetup(filePath: string): void;
```
Expand Down
16 changes: 13 additions & 3 deletions bindings/node.js/lib/kzg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,24 @@ export const BYTES_PER_PROOF: number;
export const FIELD_ELEMENTS_PER_BLOB: number;

/**
* Factory function that passes trusted setup to the bindings
* Initialize the library with a trusted setup file.
*
* @param {string} filePath
* Can pass either a .txt or a .json file with setup configuration. Converts
* JSON formatted trusted setup into the native format that the base library
* requires. The created file will be in the same as the origin file but with a
* ".txt" extension.
*
* Uses user provided location first. If one is not provided then defaults to
* the official Ethereum mainnet setup from the KZG ceremony. Should only be
* used for cases where the Ethereum official mainnet KZG setup is acceptable.
*
* @param {string | undefined} filePath
* @default - If no string is passed the default trusted setup from the Ethereum KZG ceremony is used
*
* @throws {TypeError} - Non-String input
* @throws {Error} - For all other errors. See error message for more info
*/
export function loadTrustedSetup(filePath: string): void;
export function loadTrustedSetup(filePath?: string): void;

/**
* Convert a blob to a KZG commitment.
Expand Down
92 changes: 83 additions & 9 deletions bindings/node.js/lib/kzg.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,50 @@ const fs = require("fs");
const path = require("path");
const bindings = require("bindings")("kzg");

/**
* NOTE: These two paths are only exported for testing purposes. They are not
* announced in the type file.
*
* It is critical that these paths are kept in sync with where the trusted setup
* files will be found. The root setup is in the base src directory with the
* primary library code. The dist version is dictated by the `build` command in
* the Makefile in the bindings/node.js folder.
*/
/**
* Check the production bundle case first.
* - this file in BUNDLE_ROOT/dist/lib/kzg.js
* - trusted_setup in BUNDLE_ROOT/dist/deps/c-kzg/trusted_setup.txt
*/
bindings.TRUSTED_SETUP_PATH_IN_DIST = path.resolve(__dirname, "..", "deps", "c-kzg", "trusted_setup.txt");
/**
* Check the development case second.
* - this file in REPO_ROOT/bindings/node.js/lib/kzg.js
* - trusted_setup in REPO_ROOT/src/trusted_setup.txt
*/
bindings.TRUSTED_SETUP_PATH_IN_SRC = path.resolve(__dirname, "..", "..", "..", "src", "trusted_setup.txt");

/**
* Looks in the default locations for the trusted setup file. This is for cases
* where the library is loaded without passing a trusted setup. Should only be
* used for cases where the Ethereum official mainnet KZG setup is acceptable.
*
* @returns {string | undefined} - Filepath for trusted_setup.txt if found
*/
function getDefaultTrustedSetupFilepath() {
const locationsToSearch = [
// check the production case first
bindings.TRUSTED_SETUP_PATH_IN_DIST,
// check the development in-repo case second
bindings.TRUSTED_SETUP_PATH_IN_SRC,
];

for (const filepath of locationsToSearch) {
if (fs.existsSync(filepath)) {
return filepath;
}
}
}

/**
* Converts JSON formatted trusted setup into the native format that
* the native library requires. Returns the absolute file path to
Expand All @@ -31,19 +75,49 @@ function transformTrustedSetupJson(filePath) {
return outputPath;
}

const originalLoadTrustedSetup = bindings.loadTrustedSetup;
// docstring in ./kzg.d.ts with exported definition
bindings.loadTrustedSetup = function loadTrustedSetup(filePath) {
if (!(filePath && typeof filePath === "string")) {
throw new TypeError("must initialize kzg with the filePath to a txt/json trusted setup");
}
if (!fs.existsSync(filePath)) {
throw new Error(`no trusted setup found: ${filePath}`);
/**
* Gets location for trusted setup file. Uses user provided location first. If
* one is not provided then defaults to the official Ethereum mainnet setup from
* the KZG ceremony.
*
* @param {string} filePath - User provided filePath to check for trusted setup
*
* @returns {string} - Location of a trusted setup file. Validity is checked by
* the native bindings.loadTrustedSetup
*
* @throws {TypeError} - Invalid file type
* @throws {Error} - Invalid location or no default trusted setup found
*
* @remarks - This function is only exported for testing purposes. It should
* not be used directly. Not included in the kzg.d.ts types for that
* reason.
*/
bindings.getTrustedSetupFilepath = function getTrustedSetupFilepath(filePath) {
if (filePath) {
if (typeof filePath !== "string") {
throw new TypeError("Must initialize kzg with the filePath to a txt/json trusted setup");
}
if (!fs.existsSync(filePath)) {
throw new Error(`No trusted setup found: ${filePath}`);
}
} else {
filePath = getDefaultTrustedSetupFilepath();
if (!filePath) {
throw new Error("Default trusted setup not found. Must pass a valid filepath to load c-kzg library");
}
}

if (path.parse(filePath).ext === ".json") {
filePath = transformTrustedSetupJson(filePath);
}
originalLoadTrustedSetup(filePath);

return filePath;
};

const originalLoadTrustedSetup = bindings.loadTrustedSetup;
// docstring in ./kzg.d.ts with exported definition
bindings.loadTrustedSetup = function loadTrustedSetup(filePath) {
originalLoadTrustedSetup(bindings.getTrustedSetupFilepath(filePath));
};

module.exports = exports = bindings;
49 changes: 43 additions & 6 deletions bindings/node.js/test/kzg.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {randomBytes} from "crypto";
import {readFileSync} from "fs";
import {readFileSync, existsSync, cpSync, rmSync} from "fs";
import {resolve} from "path";
import {globSync} from "glob";

Expand All @@ -10,7 +10,9 @@ interface TestMeta<I extends Record<string, any>, O extends boolean | string | s
output: O;
}

import {
import kzg from "../lib/kzg";
import type {ProofResult} from "../lib/kzg";
const {
loadTrustedSetup,
blobToKzgCommitment,
computeKzgProof,
Expand All @@ -22,10 +24,14 @@ import {
BYTES_PER_COMMITMENT,
BYTES_PER_PROOF,
BYTES_PER_FIELD_ELEMENT,
ProofResult,
} from "../lib/kzg";
} = kzg;
// not exported by types, only exported for testing purposes
const getTrustedSetupFilepath = (kzg as any).getTrustedSetupFilepath as (filePath?: string) => string;
const TRUSTED_SETUP_PATH_IN_DIST = (kzg as any).TRUSTED_SETUP_PATH_IN_DIST as string;
const TRUSTED_SETUP_PATH_IN_SRC = (kzg as any).TRUSTED_SETUP_PATH_IN_SRC as string;

const SETUP_FILE_PATH = resolve(__dirname, "__fixtures__", "trusted_setup.json");
const TEST_SETUP_FILE_PATH_JSON = resolve(__dirname, "__fixtures__", "trusted_setup.json");
const TEST_SETUP_FILE_PATH_TXT = resolve(__dirname, "__fixtures__", "trusted_setup.txt");

const MAX_TOP_BYTE = 114;

Expand Down Expand Up @@ -166,7 +172,38 @@ function testArgCount(fn: (...args: any[]) => any, validArgs: any[]): void {

describe("C-KZG", () => {
beforeAll(async () => {
loadTrustedSetup(SETUP_FILE_PATH);
loadTrustedSetup(TEST_SETUP_FILE_PATH_JSON);
});

describe("locating trusted setup file", () => {
it("should return a txt path if a json file is provided and exists", () => {
expect(getTrustedSetupFilepath(TEST_SETUP_FILE_PATH_JSON)).toEqual(TEST_SETUP_FILE_PATH_TXT);
});
/**
* No guarantee that the test above runs first, however the json file should
* have already been loaded by the beforeAll so a valid .txt test setup
* should be available to expect
*/
it("should return the same txt path if provided and exists", () => {
expect(getTrustedSetupFilepath(TEST_SETUP_FILE_PATH_TXT)).toEqual(TEST_SETUP_FILE_PATH_TXT);
});
describe("default setups", () => {
beforeEach(() => {
if (!existsSync(TRUSTED_SETUP_PATH_IN_DIST)) {
cpSync(TRUSTED_SETUP_PATH_IN_SRC, TRUSTED_SETUP_PATH_IN_DIST);
}
});
it("should return dist setup first", () => {
// both files should be preset right now
expect(getTrustedSetupFilepath()).toEqual(TRUSTED_SETUP_PATH_IN_DIST);
});
it("should return src setup if dist is missing", () => {
// both files should be preset right now
rmSync(TRUSTED_SETUP_PATH_IN_DIST);
expect(getTrustedSetupFilepath()).toEqual(TRUSTED_SETUP_PATH_IN_SRC);
cpSync(TRUSTED_SETUP_PATH_IN_SRC, TRUSTED_SETUP_PATH_IN_DIST);
});
});
});

describe("reference tests should pass", () => {
Expand Down
Loading