[nodejs] Bundle and auto-load trusted setup (#422)

This commit is contained in:
Matthew Keil 2024-04-30 12:38:48 -04:00 committed by GitHub
parent 58b09bdafe
commit 8ab57f44db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 155 additions and 25 deletions

View File

@ -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

View File

@ -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
@ -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;
```

View File

@ -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.

View File

@ -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
@ -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;

View File

@ -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";
@ -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,
@ -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;
@ -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", () => {