Merge pull request #3 from dgcoffman/dgc/nodejs-bindings
NodeJS bindings
This commit is contained in:
commit
7e779004f0
|
@ -4,7 +4,6 @@
|
||||||
inc/blst.h*
|
inc/blst.h*
|
||||||
inc/blst_aux.h*
|
inc/blst_aux.h*
|
||||||
.vscode/
|
.vscode/
|
||||||
*.json
|
|
||||||
.clang-format
|
.clang-format
|
||||||
*bindings/*/*.so
|
*bindings/*/*.so
|
||||||
*bindings/csharp/*.exe
|
*bindings/csharp/*.exe
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "blst"]
|
||||||
|
path = blst
|
||||||
|
url = https://github.com/supranational/blst
|
||||||
|
ignore = dirty # because we apply a patch
|
|
@ -0,0 +1,3 @@
|
||||||
|
build
|
||||||
|
*.node
|
||||||
|
node_modules
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Ignore artifacts:
|
||||||
|
build
|
||||||
|
dist
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "binding.gyp",
|
||||||
|
"options": { "parser": "json" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
all: clean build format test bundle
|
||||||
|
|
||||||
|
clean:
|
||||||
|
yarn node-gyp clean
|
||||||
|
rm -rf build
|
||||||
|
rm -f *.node
|
||||||
|
rm -f dist/kzg.node
|
||||||
|
rm -f *.a
|
||||||
|
rm -f *.o
|
||||||
|
|
||||||
|
build: kzg.cxx kzg.ts package.json binding.gyp Makefile
|
||||||
|
cd ../../src; make lib
|
||||||
|
yarn node-gyp rebuild
|
||||||
|
|
||||||
|
test: build
|
||||||
|
yarn jest
|
||||||
|
|
||||||
|
format:
|
||||||
|
yarn prettier --write .
|
||||||
|
|
||||||
|
bundle:
|
||||||
|
yarn rollup --config rollup.config.js --bundleConfigAsCjs
|
|
@ -0,0 +1,57 @@
|
||||||
|
This directory contains the code necessary to generate NodeJS bindings for C-KZG.
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
loadTrustedSetup: (filePath: string) => SetupHandle;
|
||||||
|
|
||||||
|
freeTrustedSetup: (setupHandle: SetupHandle) => void;
|
||||||
|
|
||||||
|
blobToKzgCommitment: (blob: Blob, setupHandle: SetupHandle) => KZGCommitment;
|
||||||
|
|
||||||
|
computeAggregateKzgProof: (
|
||||||
|
blobs: Blob[],
|
||||||
|
setupHandle: SetupHandle
|
||||||
|
) => KZGProof;
|
||||||
|
|
||||||
|
verifyAggregateKzgProof: (
|
||||||
|
blobs: Blob[],
|
||||||
|
expectedKzgCommitments: KZGCommitment[],
|
||||||
|
kzgAggregatedProof: KZGProof,
|
||||||
|
setupHandle: SetupHandle
|
||||||
|
) => boolean;
|
||||||
|
```
|
||||||
|
|
||||||
|
Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/polynomial-commitments.md
|
||||||
|
|
||||||
|
First,
|
||||||
|
`npm install -g yarn` if you don't have it.
|
||||||
|
|
||||||
|
Install the blst submodule
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
Build blst and c_kzg_4844.c
|
||||||
|
|
||||||
|
```
|
||||||
|
cd src && make blst lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate NodeJS bindings and run the TypeScript tests against them
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ../bindings/node.js && yarn install && make test
|
||||||
|
```
|
||||||
|
|
||||||
|
After doing this once, you can re-build (if necessary) and re-run the tests with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make build test
|
||||||
|
```
|
||||||
|
|
||||||
|
After making changes, regenerate the distributable JS and type defs
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make bundle
|
||||||
|
```
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
["@babel/preset-env", { targets: { node: "current" } }],
|
||||||
|
"@babel/preset-typescript",
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "kzg",
|
||||||
|
"cflags!": ["-fno-exceptions"],
|
||||||
|
"cflags_cc!": ["-fno-exceptions"],
|
||||||
|
"xcode_settings": {
|
||||||
|
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
||||||
|
"CLANG_CXX_LIBRARY": "libc++",
|
||||||
|
"MACOSX_DEPLOYMENT_TARGET": "13.0"
|
||||||
|
},
|
||||||
|
"sources": ["kzg.cxx"],
|
||||||
|
"include_dirs": [
|
||||||
|
"../../inc",
|
||||||
|
"../../src",
|
||||||
|
"<!@(node -p \"require('node-addon-api').include\")"
|
||||||
|
],
|
||||||
|
"libraries": [
|
||||||
|
"<(module_root_dir)/c_kzg_4844.o",
|
||||||
|
"<(module_root_dir)/../../lib/libblst.a"
|
||||||
|
],
|
||||||
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
||||||
|
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target_name": "action_after_build",
|
||||||
|
"type": "none",
|
||||||
|
"dependencies": ["kzg"],
|
||||||
|
"copies": [
|
||||||
|
{
|
||||||
|
"files": ["./build/Release/kzg.node"],
|
||||||
|
"destination": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["./build/Release/kzg.node"],
|
||||||
|
"destination": "./dist"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export declare type BLSFieldElement = Uint8Array;
|
||||||
|
export declare type KZGProof = Uint8Array;
|
||||||
|
export declare type KZGCommitment = Uint8Array;
|
||||||
|
export declare type Blob = Uint8Array;
|
||||||
|
export declare const FIELD_ELEMENTS_PER_BLOB: number;
|
||||||
|
export declare const BYTES_PER_FIELD_ELEMENT: number;
|
||||||
|
export declare function loadTrustedSetup(filePath: string): void;
|
||||||
|
export declare function freeTrustedSetup(): void;
|
||||||
|
export declare function blobToKzgCommitment(blob: Blob): KZGCommitment;
|
||||||
|
export declare function computeAggregateKzgProof(blobs: Blob[]): KZGProof;
|
||||||
|
export declare function verifyAggregateKzgProof(blobs: Blob[], expectedKzgCommitments: KZGCommitment[], kzgAggregatedProof: KZGProof): boolean;
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The public interface of this module exposes the functions as specified by
|
||||||
|
* https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/polynomial-commitments.md#kzg
|
||||||
|
*/
|
||||||
|
const kzg = require("./kzg.node");
|
||||||
|
const FIELD_ELEMENTS_PER_BLOB = kzg.FIELD_ELEMENTS_PER_BLOB;
|
||||||
|
const BYTES_PER_FIELD_ELEMENT = kzg.BYTES_PER_FIELD_ELEMENT;
|
||||||
|
// Stored as internal state
|
||||||
|
let setupHandle;
|
||||||
|
function requireSetupHandle() {
|
||||||
|
if (!setupHandle) {
|
||||||
|
throw new Error("You must call loadTrustedSetup to initialize KZG.");
|
||||||
|
}
|
||||||
|
return setupHandle;
|
||||||
|
}
|
||||||
|
function loadTrustedSetup(filePath) {
|
||||||
|
if (setupHandle) {
|
||||||
|
throw new Error("Call freeTrustedSetup before loading a new trusted setup.");
|
||||||
|
}
|
||||||
|
setupHandle = kzg.loadTrustedSetup(filePath);
|
||||||
|
}
|
||||||
|
function freeTrustedSetup() {
|
||||||
|
kzg.freeTrustedSetup(requireSetupHandle());
|
||||||
|
setupHandle = undefined;
|
||||||
|
}
|
||||||
|
function blobToKzgCommitment(blob) {
|
||||||
|
return kzg.blobToKzgCommitment(blob, requireSetupHandle());
|
||||||
|
}
|
||||||
|
function computeAggregateKzgProof(blobs) {
|
||||||
|
return kzg.computeAggregateKzgProof(blobs, requireSetupHandle());
|
||||||
|
}
|
||||||
|
function verifyAggregateKzgProof(blobs, expectedKzgCommitments, kzgAggregatedProof) {
|
||||||
|
return kzg.verifyAggregateKzgProof(blobs, expectedKzgCommitments, kzgAggregatedProof, requireSetupHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.BYTES_PER_FIELD_ELEMENT = BYTES_PER_FIELD_ELEMENT;
|
||||||
|
exports.FIELD_ELEMENTS_PER_BLOB = FIELD_ELEMENTS_PER_BLOB;
|
||||||
|
exports.blobToKzgCommitment = blobToKzgCommitment;
|
||||||
|
exports.computeAggregateKzgProof = computeAggregateKzgProof;
|
||||||
|
exports.freeTrustedSetup = freeTrustedSetup;
|
||||||
|
exports.loadTrustedSetup = loadTrustedSetup;
|
||||||
|
exports.verifyAggregateKzgProof = verifyAggregateKzgProof;
|
|
@ -0,0 +1,5 @@
|
||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "node",
|
||||||
|
};
|
|
@ -0,0 +1,353 @@
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sstream> // std::ostringstream
|
||||||
|
#include <algorithm> // std::copy
|
||||||
|
#include <iterator> // std::ostream_iterator
|
||||||
|
#include <napi.h>
|
||||||
|
#include "c_kzg_4844.h"
|
||||||
|
#include "blst.h"
|
||||||
|
|
||||||
|
Napi::Value throw_invalid_arguments_count(
|
||||||
|
const uint expected,
|
||||||
|
const uint actual,
|
||||||
|
const Napi::Env env
|
||||||
|
) {
|
||||||
|
Napi::RangeError::New(
|
||||||
|
env,
|
||||||
|
"Wrong number of arguments. Expected: "
|
||||||
|
+ std::to_string(expected)
|
||||||
|
+ ", received " + std::to_string(actual)
|
||||||
|
).ThrowAsJavaScriptException();
|
||||||
|
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Napi::Value throw_invalid_argument_type(const Napi::Env env, std::string name, std::string expectedType) {
|
||||||
|
Napi::TypeError::New(
|
||||||
|
env,
|
||||||
|
"Invalid parameter type: " + name + ". Expected " + expectedType
|
||||||
|
).ThrowAsJavaScriptException();
|
||||||
|
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Napi::TypedArrayOf<uint8_t> napi_typed_array_from_bytes(uint8_t* array, size_t length, Napi::Env env) {
|
||||||
|
// Create std::vector<uint8_t> out of array.
|
||||||
|
// We allocate it on the heap to allow wrapping it up into ArrayBuffer.
|
||||||
|
std::unique_ptr<std::vector<uint8_t>> vector =
|
||||||
|
std::make_unique<std::vector<uint8_t>>(length, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
(*vector)[i] = array[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap up the std::vector into the ArrayBuffer.
|
||||||
|
Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(
|
||||||
|
env,
|
||||||
|
vector->data(),
|
||||||
|
length /* size in bytes */,
|
||||||
|
[](Napi::Env /*env*/, void* /*data*/, std::vector<uint8_t>* hint) {
|
||||||
|
std::unique_ptr<std::vector<uint8_t>> vectorPtrToDelete(hint);
|
||||||
|
},
|
||||||
|
vector.get());
|
||||||
|
|
||||||
|
// The finalizer is responsible for deleting the vector: release the
|
||||||
|
// unique_ptr ownership.
|
||||||
|
vector.release();
|
||||||
|
|
||||||
|
return Napi::Uint8Array::New(env, length, buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTrustedSetup: (filePath: string) => SetupHandle;
|
||||||
|
Napi::Value LoadTrustedSetup(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 1;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info[0].IsString()) {
|
||||||
|
return throw_invalid_argument_type(env, "filePath", "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string file_path = info[0].ToString().Utf8Value();
|
||||||
|
|
||||||
|
KZGSettings* kzg_settings = (KZGSettings*)malloc(sizeof(KZGSettings));
|
||||||
|
|
||||||
|
if (kzg_settings == NULL) {
|
||||||
|
Napi::Error::New(env, "Error while allocating memory for KZG settings").ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
};
|
||||||
|
|
||||||
|
FILE* f = fopen(file_path.c_str(), "r");
|
||||||
|
|
||||||
|
if (f == NULL) {
|
||||||
|
free(kzg_settings);
|
||||||
|
Napi::Error::New(env, "Error opening trusted setup file").ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_trusted_setup(kzg_settings, f) != C_KZG_OK) {
|
||||||
|
free(kzg_settings);
|
||||||
|
Napi::Error::New(env, "Error loading trusted setup file").ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Napi::External<KZGSettings>::New(info.Env(), kzg_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// freeTrustedSetup: (setupHandle: SetupHandle) => void;
|
||||||
|
Napi::Value FreeTrustedSetup(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 1;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto kzg_settings = info[0].As<Napi::External<KZGSettings>>().Data();
|
||||||
|
free_trusted_setup(kzg_settings);
|
||||||
|
free(kzg_settings);
|
||||||
|
return env.Undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// blobToKzgCommitment: (blob: Blob, setupHandle: SetupHandle) => KZGCommitment;
|
||||||
|
Napi::Value BlobToKzgCommitment(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 2;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto blob_param = info[0].As<Napi::TypedArray>();
|
||||||
|
if (blob_param.TypedArrayType() != napi_uint8_array) {
|
||||||
|
return throw_invalid_argument_type(env, "blob", "UInt8Array");
|
||||||
|
}
|
||||||
|
auto blob = blob_param.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
|
||||||
|
|
||||||
|
KZGCommitment commitment;
|
||||||
|
blob_to_kzg_commitment(&commitment, blob, kzg_settings);
|
||||||
|
|
||||||
|
uint8_t commitment_bytes[BYTES_PER_COMMITMENT];
|
||||||
|
bytes_from_g1(commitment_bytes, &commitment);
|
||||||
|
return napi_typed_array_from_bytes(commitment_bytes, BYTES_PER_COMMITMENT, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeAggregateKzgProof: (blobs: Blob[], setupHandle: SetupHandle) => KZGProof;
|
||||||
|
Napi::Value ComputeAggregateKzgProof(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 2;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto blobs_param = info[0].As<Napi::Array>();
|
||||||
|
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
|
||||||
|
|
||||||
|
auto blobs_count = blobs_param.Length();
|
||||||
|
auto blobs = (Blob*)calloc(blobs_count, sizeof(Blob));
|
||||||
|
|
||||||
|
for (uint32_t blob_index = 0; blob_index < blobs_count; blob_index++) {
|
||||||
|
Napi::Value blob = blobs_param[blob_index];
|
||||||
|
auto blob_bytes = blob.As<Napi::Uint8Array>().Data();
|
||||||
|
memcpy(blobs[blob_index], blob_bytes, BYTES_PER_BLOB);
|
||||||
|
}
|
||||||
|
|
||||||
|
KZGProof proof;
|
||||||
|
C_KZG_RET ret = compute_aggregate_kzg_proof(
|
||||||
|
&proof,
|
||||||
|
blobs,
|
||||||
|
blobs_count,
|
||||||
|
kzg_settings
|
||||||
|
);
|
||||||
|
free(blobs);
|
||||||
|
|
||||||
|
if (ret != C_KZG_OK) {
|
||||||
|
Napi::Error::New(env, "Failed to compute proof")
|
||||||
|
.ThrowAsJavaScriptException();
|
||||||
|
return env.Undefined();
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t proof_bytes[BYTES_PER_PROOF];
|
||||||
|
bytes_from_g1(proof_bytes, &proof);
|
||||||
|
return napi_typed_array_from_bytes(proof_bytes, BYTES_PER_PROOF, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyAggregateKzgProof: (blobs: Blob[], expectedKzgCommitments: KZGCommitment[], kzgAggregatedProof: KZGProof, setupHandle: SetupHandle) => boolean;
|
||||||
|
Napi::Value VerifyAggregateKzgProof(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 4;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto blobs_param = info[0].As<Napi::Array>();
|
||||||
|
auto comittments_param = info[1].As<Napi::Array>();
|
||||||
|
auto proof_param = info[2].As<Napi::TypedArray>();
|
||||||
|
auto kzg_settings = info[3].As<Napi::External<KZGSettings>>().Data();
|
||||||
|
|
||||||
|
auto proof_bytes = proof_param.As<Napi::Uint8Array>().Data();
|
||||||
|
auto blobs_count = blobs_param.Length();
|
||||||
|
|
||||||
|
auto blobs = (Blob*)calloc(blobs_count, sizeof(Blob));
|
||||||
|
auto commitments = (KZGCommitment*)calloc(blobs_count, sizeof(KZGCommitment));
|
||||||
|
|
||||||
|
C_KZG_RET ret;
|
||||||
|
|
||||||
|
for (uint32_t blob_index = 0; blob_index < blobs_count; blob_index++) {
|
||||||
|
// Extract blob bytes from parameter
|
||||||
|
Napi::Value blob = blobs_param[blob_index];
|
||||||
|
auto blob_bytes = blob.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
memcpy(blobs[blob_index], blob_bytes, BYTES_PER_BLOB);
|
||||||
|
|
||||||
|
// Extract a G1 point for each commitment
|
||||||
|
Napi::Value commitment = comittments_param[blob_index];
|
||||||
|
auto commitment_bytes = commitment.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
ret = bytes_to_g1(&commitments[blob_index], commitment_bytes);
|
||||||
|
if (ret != C_KZG_OK) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
std::copy(commitment_bytes, commitment_bytes + BYTES_PER_COMMITMENT, std::ostream_iterator<int>(ss, ","));
|
||||||
|
|
||||||
|
Napi::TypeError::New(
|
||||||
|
env,
|
||||||
|
"Invalid commitment data"
|
||||||
|
).ThrowAsJavaScriptException();
|
||||||
|
|
||||||
|
free(commitments);
|
||||||
|
free(blobs);
|
||||||
|
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KZGProof proof;
|
||||||
|
ret = bytes_to_g1(&proof, proof_bytes);
|
||||||
|
if (ret != C_KZG_OK) {
|
||||||
|
free(commitments);
|
||||||
|
free(blobs);
|
||||||
|
|
||||||
|
Napi::Error::New(env, "Invalid proof data")
|
||||||
|
.ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verification_result;
|
||||||
|
ret = verify_aggregate_kzg_proof(
|
||||||
|
&verification_result,
|
||||||
|
blobs,
|
||||||
|
commitments,
|
||||||
|
blobs_count,
|
||||||
|
&proof,
|
||||||
|
kzg_settings
|
||||||
|
);
|
||||||
|
|
||||||
|
free(commitments);
|
||||||
|
free(blobs);
|
||||||
|
|
||||||
|
if (ret != C_KZG_OK) {
|
||||||
|
Napi::Error::New(
|
||||||
|
env,
|
||||||
|
"verify_aggregate_kzg_proof failed with error code: " + std::to_string(ret)
|
||||||
|
).ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Napi::Boolean::New(env, verification_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyKzgProof: (polynomialKzg: KZGCommitment, z: BLSFieldElement, y: BLSFieldElement, kzgProof: KZGProof, setupHandle: SetupHandle) => boolean;
|
||||||
|
Napi::Value VerifyKzgProof(const Napi::CallbackInfo& info) {
|
||||||
|
auto env = info.Env();
|
||||||
|
|
||||||
|
size_t argument_count = info.Length();
|
||||||
|
size_t expected_argument_count = 5;
|
||||||
|
if (argument_count != expected_argument_count) {
|
||||||
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c_param = info[0].As<Napi::TypedArray>();
|
||||||
|
if (c_param.TypedArrayType() != napi_uint8_array) {
|
||||||
|
return throw_invalid_argument_type(env, "polynomialKzg", "UInt8Array");
|
||||||
|
}
|
||||||
|
auto polynomial_kzg = c_param.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
auto z_param = info[1].As<Napi::TypedArray>();
|
||||||
|
if (z_param.TypedArrayType() != napi_uint8_array) {
|
||||||
|
return throw_invalid_argument_type(env, "z", "UInt8Array");
|
||||||
|
}
|
||||||
|
auto z = z_param.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
auto y_param = info[2].As<Napi::TypedArray>();
|
||||||
|
if (y_param.TypedArrayType() != napi_uint8_array) {
|
||||||
|
return throw_invalid_argument_type(env, "y", "UInt8Array");
|
||||||
|
}
|
||||||
|
auto y = y_param.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
auto proof_param = info[3].As<Napi::TypedArray>();
|
||||||
|
if (proof_param.TypedArrayType() != napi_uint8_array) {
|
||||||
|
return throw_invalid_argument_type(env, "kzgProof", "UInt8Array");
|
||||||
|
}
|
||||||
|
auto kzg_proof = proof_param.As<Napi::Uint8Array>().Data();
|
||||||
|
|
||||||
|
auto kzg_settings = info[4].As<Napi::External<KZGSettings>>().Data();
|
||||||
|
|
||||||
|
BLSFieldElement fz, fy;
|
||||||
|
bytes_to_bls_field(&fz, z);
|
||||||
|
bytes_to_bls_field(&fy, y);
|
||||||
|
|
||||||
|
KZGCommitment commitment;
|
||||||
|
auto ret = bytes_to_g1(&commitment, polynomial_kzg);
|
||||||
|
if (ret != C_KZG_OK) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
std::copy(polynomial_kzg, polynomial_kzg + BYTES_PER_COMMITMENT, std::ostream_iterator<int>(ss, ","));
|
||||||
|
|
||||||
|
Napi::TypeError::New(env, "Failed to parse argument commitment: " + ss.str() + " Return code was: " + std::to_string(ret)).ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
};
|
||||||
|
|
||||||
|
KZGProof proof;
|
||||||
|
if (bytes_to_g1(&proof, kzg_proof) != C_KZG_OK) {
|
||||||
|
Napi::TypeError::New(env, "Invalid kzgProof").ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool out;
|
||||||
|
if (verify_kzg_proof(&out, &commitment, &fz, &fy, &proof, kzg_settings) != C_KZG_OK) {
|
||||||
|
Napi::TypeError::New(env, "Failed to verify KZG proof").ThrowAsJavaScriptException();
|
||||||
|
return env.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Napi::Boolean::New(env, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
||||||
|
// Functions
|
||||||
|
exports["loadTrustedSetup"] = Napi::Function::New(env, LoadTrustedSetup);
|
||||||
|
exports["freeTrustedSetup"] = Napi::Function::New(env, FreeTrustedSetup);
|
||||||
|
exports["verifyKzgProof"] = Napi::Function::New(env, VerifyKzgProof);
|
||||||
|
exports["blobToKzgCommitment"] = Napi::Function::New(env, BlobToKzgCommitment);
|
||||||
|
exports["computeAggregateKzgProof"] = Napi::Function::New(env, ComputeAggregateKzgProof);
|
||||||
|
exports["verifyAggregateKzgProof"] = Napi::Function::New(env, VerifyAggregateKzgProof);
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
exports["FIELD_ELEMENTS_PER_BLOB"] = Napi::Number::New(env, FIELD_ELEMENTS_PER_BLOB);
|
||||||
|
exports["BYTES_PER_FIELD_ELEMENT"] = Napi::Number::New(env, BYTES_PER_FIELD_ELEMENT);
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_API_MODULE(addon, Init)
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* The public interface of this module exposes the functions as specified by
|
||||||
|
* https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/polynomial-commitments.md#kzg
|
||||||
|
*/
|
||||||
|
const kzg: KZG = require("./kzg.node");
|
||||||
|
|
||||||
|
export type BLSFieldElement = Uint8Array; // 32 bytes
|
||||||
|
export type KZGProof = Uint8Array; // 48 bytes
|
||||||
|
export type KZGCommitment = Uint8Array; // 48 bytes
|
||||||
|
export type Blob = Uint8Array; // 4096 * 32 bytes
|
||||||
|
|
||||||
|
type SetupHandle = Object;
|
||||||
|
|
||||||
|
// The C++ native addon interface
|
||||||
|
type KZG = {
|
||||||
|
FIELD_ELEMENTS_PER_BLOB: number;
|
||||||
|
BYTES_PER_FIELD_ELEMENT: number;
|
||||||
|
|
||||||
|
loadTrustedSetup: (filePath: string) => SetupHandle;
|
||||||
|
|
||||||
|
freeTrustedSetup: (setupHandle: SetupHandle) => void;
|
||||||
|
|
||||||
|
blobToKzgCommitment: (blob: Blob, setupHandle: SetupHandle) => KZGCommitment;
|
||||||
|
|
||||||
|
computeAggregateKzgProof: (
|
||||||
|
blobs: Blob[],
|
||||||
|
setupHandle: SetupHandle,
|
||||||
|
) => KZGProof;
|
||||||
|
|
||||||
|
verifyAggregateKzgProof: (
|
||||||
|
blobs: Blob[],
|
||||||
|
expectedKzgCommitments: KZGCommitment[],
|
||||||
|
kzgAggregatedProof: KZGProof,
|
||||||
|
setupHandle: SetupHandle,
|
||||||
|
) => boolean;
|
||||||
|
|
||||||
|
// Currently unused -- not exported
|
||||||
|
verifyKzgProof: (
|
||||||
|
polynomialKzg: KZGCommitment,
|
||||||
|
z: BLSFieldElement,
|
||||||
|
y: BLSFieldElement,
|
||||||
|
kzgProof: KZGProof,
|
||||||
|
setupHandle: SetupHandle,
|
||||||
|
) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FIELD_ELEMENTS_PER_BLOB = kzg.FIELD_ELEMENTS_PER_BLOB;
|
||||||
|
export const BYTES_PER_FIELD_ELEMENT = kzg.BYTES_PER_FIELD_ELEMENT;
|
||||||
|
|
||||||
|
// Stored as internal state
|
||||||
|
let setupHandle: SetupHandle | undefined;
|
||||||
|
|
||||||
|
function requireSetupHandle(): SetupHandle {
|
||||||
|
if (!setupHandle) {
|
||||||
|
throw new Error("You must call loadTrustedSetup to initialize KZG.");
|
||||||
|
}
|
||||||
|
return setupHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTrustedSetup(filePath: string): void {
|
||||||
|
if (setupHandle) {
|
||||||
|
throw new Error(
|
||||||
|
"Call freeTrustedSetup before loading a new trusted setup.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setupHandle = kzg.loadTrustedSetup(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function freeTrustedSetup(): void {
|
||||||
|
kzg.freeTrustedSetup(requireSetupHandle());
|
||||||
|
setupHandle = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blobToKzgCommitment(blob: Blob): KZGCommitment {
|
||||||
|
return kzg.blobToKzgCommitment(blob, requireSetupHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeAggregateKzgProof(blobs: Blob[]): KZGProof {
|
||||||
|
return kzg.computeAggregateKzgProof(blobs, requireSetupHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyAggregateKzgProof(
|
||||||
|
blobs: Blob[],
|
||||||
|
expectedKzgCommitments: KZGCommitment[],
|
||||||
|
kzgAggregatedProof: KZGProof,
|
||||||
|
): boolean {
|
||||||
|
return kzg.verifyAggregateKzgProof(
|
||||||
|
blobs,
|
||||||
|
expectedKzgCommitments,
|
||||||
|
kzgAggregatedProof,
|
||||||
|
requireSetupHandle(),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "c-kzg",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "NodeJS bindings for C-KZG",
|
||||||
|
"author": "Dan Coffman",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/kzg.js",
|
||||||
|
"types": "dist/dts/kzg.d.ts",
|
||||||
|
"gypfile": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
|
"@rollup/plugin-typescript": "^9.0.2",
|
||||||
|
"@types/jest": "^29.2.1",
|
||||||
|
"jest": "^29.2.2",
|
||||||
|
"node-addon-api": "^5.0.0",
|
||||||
|
"node-gyp": "^9.3.0",
|
||||||
|
"prettier": "2.7.1",
|
||||||
|
"rollup": "^3.2.5",
|
||||||
|
"ts-jest": "^29.0.3",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import typescript from "@rollup/plugin-typescript";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "kzg.ts",
|
||||||
|
output: {
|
||||||
|
dir: "dist",
|
||||||
|
format: "cjs",
|
||||||
|
},
|
||||||
|
plugins: [typescript()],
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import {
|
||||||
|
loadTrustedSetup,
|
||||||
|
freeTrustedSetup,
|
||||||
|
blobToKzgCommitment,
|
||||||
|
computeAggregateKzgProof,
|
||||||
|
verifyAggregateKzgProof,
|
||||||
|
BYTES_PER_FIELD_ELEMENT,
|
||||||
|
FIELD_ELEMENTS_PER_BLOB,
|
||||||
|
} from "./kzg";
|
||||||
|
|
||||||
|
const SETUP_FILE_PATH = "../../src/trusted_setup.txt";
|
||||||
|
const BLOB_BYTE_COUNT = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT;
|
||||||
|
|
||||||
|
const generateRandomBlob = () => new Uint8Array(randomBytes(BLOB_BYTE_COUNT));
|
||||||
|
|
||||||
|
describe("C-KZG", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
loadTrustedSetup(SETUP_FILE_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
freeTrustedSetup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("computes the correct commitments and aggregate proofs from blobs", () => {
|
||||||
|
const blobs = new Array(2).fill(0).map(generateRandomBlob);
|
||||||
|
const commitments = blobs.map(blobToKzgCommitment);
|
||||||
|
const proof = computeAggregateKzgProof(blobs);
|
||||||
|
expect(verifyAggregateKzgProof(blobs, commitments, proof)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails when given incorrect commitments", () => {
|
||||||
|
const blobs = new Array(2).fill(0).map(generateRandomBlob);
|
||||||
|
const commitments = blobs.map(blobToKzgCommitment);
|
||||||
|
commitments[0][0] = commitments[0][0] === 0 ? 1 : 0; // Mutate the commitment
|
||||||
|
const proof = computeAggregateKzgProof(blobs);
|
||||||
|
expect(() =>
|
||||||
|
verifyAggregateKzgProof(blobs, commitments, proof),
|
||||||
|
).toThrowError("Invalid commitment data");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist/dts",
|
||||||
|
"target": "esnext",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6382d67c72119d563975892ed49ba32e92d3d0da
|
|
@ -0,0 +1,31 @@
|
||||||
|
diff --git a/src/sha256.h b/src/sha256.h
|
||||||
|
index 77ddb6d..67ccf7a 100644
|
||||||
|
--- a/src/sha256.h
|
||||||
|
+++ b/src/sha256.h
|
||||||
|
@@ -49,7 +49,7 @@ static void sha256_init_h(unsigned int h[8])
|
||||||
|
h[7] = 0x5be0cd19U;
|
||||||
|
}
|
||||||
|
|
||||||
|
-static void sha256_init(SHA256_CTX *ctx)
|
||||||
|
+void sha256_init(SHA256_CTX *ctx)
|
||||||
|
{
|
||||||
|
sha256_init_h(ctx->h);
|
||||||
|
ctx->N = 0;
|
||||||
|
@@ -57,7 +57,7 @@ static void sha256_init(SHA256_CTX *ctx)
|
||||||
|
ctx->off = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
-static void sha256_update(SHA256_CTX *ctx, const void *_inp, size_t len)
|
||||||
|
+void sha256_update(SHA256_CTX *ctx, const void *_inp, size_t len)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
const unsigned char *inp = _inp;
|
||||||
|
@@ -116,7 +116,7 @@ static void sha256_emit(unsigned char md[32], const unsigned int h[8])
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-static void sha256_final(unsigned char md[32], SHA256_CTX *ctx)
|
||||||
|
+void sha256_final(unsigned char md[32], SHA256_CTX *ctx)
|
||||||
|
{
|
||||||
|
unsigned long long bits = ctx->N * 8;
|
||||||
|
size_t n = ctx->off;
|
15
src/Makefile
15
src/Makefile
|
@ -1,5 +1,20 @@
|
||||||
INCLUDE_DIRS = ../inc
|
INCLUDE_DIRS = ../inc
|
||||||
CFLAGS += -O2
|
CFLAGS += -O2
|
||||||
|
|
||||||
|
all: c_kzg_4844.o lib
|
||||||
|
|
||||||
c_kzg_4844.o: c_kzg_4844.c Makefile
|
c_kzg_4844.o: c_kzg_4844.c Makefile
|
||||||
clang -Wall -I$(INCLUDE_DIRS) $(CFLAGS) -c $<
|
clang -Wall -I$(INCLUDE_DIRS) $(CFLAGS) -c $<
|
||||||
|
|
||||||
|
# Will fail with "patch does not apply" if it has already been patched.
|
||||||
|
# Safe to ignore.
|
||||||
|
blst:
|
||||||
|
cd ../blst; \
|
||||||
|
git apply < ../blst_sha.patch; \
|
||||||
|
./build.sh && \
|
||||||
|
cp libblst.a ../lib && \
|
||||||
|
cp bindings/*.h ../inc
|
||||||
|
|
||||||
|
# Copy make sure c_kzg_4844.o is built and copy it for the NodeJS bindings
|
||||||
|
lib: c_kzg_4844.o Makefile
|
||||||
|
cp *.o ../bindings/node.js
|
||||||
|
|
|
@ -29,8 +29,16 @@
|
||||||
|
|
||||||
#include "blst.h"
|
#include "blst.h"
|
||||||
|
|
||||||
|
// Allow a library built from this code to be used from C++
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BYTES_PER_COMMITMENT 48
|
||||||
|
#define BYTES_PER_PROOF 48
|
||||||
#define FIELD_ELEMENTS_PER_BLOB 4096
|
#define FIELD_ELEMENTS_PER_BLOB 4096
|
||||||
#define BYTES_PER_FIELD_ELEMENT 32
|
#define BYTES_PER_FIELD_ELEMENT 32
|
||||||
|
#define BYTES_PER_BLOB FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT
|
||||||
static const uint8_t FIAT_SHAMIR_PROTOCOL_DOMAIN[] = {70, 83, 66, 76, 79, 66, 86, 69, 82, 73, 70, 89, 95, 86, 49, 95}; // "FSBLOBVERIFY_V1_"
|
static const uint8_t FIAT_SHAMIR_PROTOCOL_DOMAIN[] = {70, 83, 66, 76, 79, 66, 86, 69, 82, 73, 70, 89, 95, 86, 49, 95}; // "FSBLOBVERIFY_V1_"
|
||||||
|
|
||||||
typedef blst_p1 g1_t; /**< Internal G1 group element type */
|
typedef blst_p1 g1_t; /**< Internal G1 group element type */
|
||||||
|
@ -114,4 +122,8 @@ C_KZG_RET verify_kzg_proof(bool *out,
|
||||||
const KZGProof *kzg_proof,
|
const KZGProof *kzg_proof,
|
||||||
const KZGSettings *s);
|
const KZGSettings *s);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // C_KZG_4844_H
|
#endif // C_KZG_4844_H
|
||||||
|
|
Loading…
Reference in New Issue