#include #include #include #define NAPI_EXPERIMENTAL #include #include "c_kzg_4844.h" #include "blst.h" #include // std::ostringstream #include // std::copy #include // std::ostream_iterator Napi::TypedArrayOf napiTypedArrayFromByteArray(uint8_t* array, size_t arrayLength, Napi::Env env) { // Create std::vector out of array. // We allocate it on the heap to allow wrapping it up into ArrayBuffer. std::unique_ptr> nativeArray = std::make_unique>(arrayLength, 0); for (size_t i = 0; i < arrayLength; ++i) { (*nativeArray)[i] = array[i]; } // Wrap up the std::vector into the ArrayBuffer. Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( env, nativeArray->data(), arrayLength /* size in bytes */, [](Napi::Env /*env*/, void* /*data*/, std::vector* hint) { std::unique_ptr> vectorPtrToDelete(hint); }, nativeArray.get()); // The finalizer is responsible for deleting the vector: release the // unique_ptr ownership. nativeArray.release(); return Napi::Uint8Array::New(env, arrayLength, arrayBuffer, 0); } // loadTrustedSetup: (filePath: string) => SetupHandle; Napi::Value LoadTrustedSetup(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); if (info.Length() != 1) { Napi::TypeError::New(env, "Wrong number of arguments") .ThrowAsJavaScriptException(); return env.Null(); } if (!info[0].IsString()) { Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); return env.Null(); } const std::string filePath = info[0].ToString().Utf8Value(); KZGSettings* kzgSettings = (KZGSettings*)malloc(sizeof(KZGSettings)); if (kzgSettings == NULL) { Napi::TypeError::New(env, "Error while allocating memory for KZG settings").ThrowAsJavaScriptException(); return env.Null(); }; FILE* f = fopen(filePath.c_str(), "r"); if (f == NULL) { free(kzgSettings); Napi::TypeError::New(env, "Error opening trusted setup file").ThrowAsJavaScriptException(); return env.Null(); } if (load_trusted_setup(kzgSettings, f) != C_KZG_OK) { free(kzgSettings); Napi::TypeError::New(env, "Error loading trusted setup file").ThrowAsJavaScriptException(); return env.Null(); } // Consider making this internal state intead return Napi::External::New(info.Env(), kzgSettings); } // freeTrustedSetup: (setupHandle: SetupHandle) => void; void FreeTrustedSetup(const Napi::CallbackInfo& info) { // Maybe this can be done with a finalizer on the thing returned by LoadTrustedSetup, and then the JS garbage collector can just sort it out. auto kzgSettings = info[0].As>().Data(); free_trusted_setup(kzgSettings); free(kzgSettings); } // blobToKzgCommitment: (blob: Blob, setupHandle: SetupHandle) => KZGCommitment; Napi::Value BlobToKzgCommitment(const Napi::CallbackInfo& info) { auto env = info.Env(); if (info.Length() != 2) { Napi::TypeError::New(env, "Wrong number of arguments") .ThrowAsJavaScriptException(); return env.Null(); } Napi::TypedArray typedArray = info[0].As(); if (typedArray.TypedArrayType() != napi_uint8_array) { Napi::Error::New(env, "Expected an Uint8Array") .ThrowAsJavaScriptException(); return env.Undefined(); } auto blob = typedArray.As().Data(); auto kzgSettings = info[1].As>().Data(); Polynomial polynomial; for (size_t i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) bytes_to_bls_field(&polynomial[i], &blob[i * 32]); KZGCommitment commitment; blob_to_kzg_commitment(&commitment, polynomial, kzgSettings); // Turn it into a byte array uint8_t commitmentBytes[BYTES_PER_COMMITMENT]; bytes_from_g1(commitmentBytes, &commitment); return napiTypedArrayFromByteArray(commitmentBytes, BYTES_PER_COMMITMENT, env); } // computeAggregateKzgProof: (blobs: Blob[], setupHandle: SetupHandle) => KZGProof; Napi::Value ComputeAggregateKzgProof(const Napi::CallbackInfo& info) { auto env = info.Env(); if (info.Length() != 2) { Napi::TypeError::New(env, "Wrong number of arguments") .ThrowAsJavaScriptException(); return env.Null(); } auto blobs_param = info[0].As(); auto kzgSettings = info[1].As>().Data(); auto numberOfBlobs = blobs_param.Length(); printf("ComputeAggregateKzgProof called with %i blob(s)\n", numberOfBlobs); auto polynomial = (Polynomial*)calloc(numberOfBlobs, sizeof(Polynomial)); for (uint32_t blobIndex = 0; blobIndex < numberOfBlobs; blobIndex++) { Napi::Value blob = blobs_param[blobIndex]; auto blobBytes = blob.As().Data(); printf("Iterating blob index: %i\n", blobIndex); for (uint32_t fieldIndex = 0; fieldIndex < FIELD_ELEMENTS_PER_BLOB; fieldIndex++) { bytes_to_bls_field( &polynomial[blobIndex][fieldIndex], &blobBytes[fieldIndex * BYTES_PER_FIELD] ); } } KZGProof proof; C_KZG_RET ret = compute_aggregate_kzg_proof( &proof, polynomial, numberOfBlobs, kzgSettings ); free(polynomial); if (ret != C_KZG_OK) { Napi::TypeError::New(env, "Failed to compute proof") .ThrowAsJavaScriptException(); return env.Undefined(); }; printf("proof generated: %llu y: %llu z: %llu\n", proof.x, proof.y, proof.z); printf("compute_aggregate_kzg_proof ret was %i\n", ret); uint8_t array[48]; bytes_from_g1(array, &proof); printf("Turned proof into bytes: ["); for (int i = 0; i < sizeof(array); i++) { printf("%x ", array[i]); } printf("]\n"); return napiTypedArrayFromByteArray(array, sizeof(array), env); } // verifyAggregateKzgProof: (blobs: Blob[], expectedKzgCommitments: KZGCommitment[], kzgAggregatedProof: KZGProof) => boolean; Napi::Value VerifyAggregateKzgProof(const Napi::CallbackInfo& info) { auto env = info.Env(); if (info.Length() != 4) { Napi::TypeError::New(env, "Wrong number of arguments") .ThrowAsJavaScriptException(); return env.Null(); } auto blobs_param = info[0].As(); auto comittments_param = info[1].As(); auto proof_param = info[2].As(); auto proofBytes = proof_param.As().Data(); auto kzgSettings = info[3].As>().Data(); auto numberOfBlobs = blobs_param.Length(); auto polynomial = (Polynomial*)calloc(numberOfBlobs, sizeof(Polynomial)); auto commitments = (KZGCommitment*)calloc(numberOfBlobs, sizeof(KZGCommitment)); C_KZG_RET ret; for (uint32_t blobIndex = 0; blobIndex < numberOfBlobs; blobIndex++) { Napi::Value blob = blobs_param[blobIndex]; auto blobBytes = blob.As().Data(); Napi::Value commitment = comittments_param[blobIndex]; auto commitmentBytes = commitment.As().Data(); for (uint32_t fieldIndex = 0; fieldIndex < FIELD_ELEMENTS_PER_BLOB; fieldIndex++) { bytes_to_bls_field(&polynomial[blobIndex][fieldIndex], &blobBytes[fieldIndex * BYTES_PER_FIELD]); } ret = bytes_to_g1(&commitments[blobIndex], &commitmentBytes[blobIndex * BYTES_PER_COMMITMENT]); if (ret != C_KZG_OK) { free(commitments); free(polynomial); Napi::TypeError::New(env, "Error parsing blobs and commitments") .ThrowAsJavaScriptException(); return env.Null(); } } KZGProof proof; ret = bytes_to_g1(&proof, proofBytes); if (ret != C_KZG_OK) { free(commitments); free(polynomial); Napi::TypeError::New(env, "Error converting proof parameter to KZGProof") .ThrowAsJavaScriptException(); return env.Null(); } bool verificationResult; ret = verify_aggregate_kzg_proof( &verificationResult, polynomial, commitments, numberOfBlobs, &proof, kzgSettings ); if (ret != C_KZG_OK) { free(commitments); free(polynomial); Napi::TypeError::New(env, "Error calling verify_aggregate_kzg_proof") .ThrowAsJavaScriptException(); return env.Null(); } free(commitments); free(polynomial); return Napi::Boolean::New(env, verificationResult); } // verifyKzgProof: (polynomialKzg: KZGCommitment, z: BLSFieldElement, y: BLSFieldElement, kzgProof: KZGProof, setupHandle: SetupHandle) => boolean; Napi::Value VerifyKzgProof(const Napi::CallbackInfo& info) { auto env = info.Env(); if (info.Length() != 5) { Napi::TypeError::New(env, "Wrong number of arguments") .ThrowAsJavaScriptException(); return env.Null(); } // const uint8_t c[48] auto c_param = info[0].As(); if (c_param.TypedArrayType() != napi_uint8_array) { Napi::Error::New(env, "Expected an Uint8Array") .ThrowAsJavaScriptException(); return env.Undefined(); } auto c = c_param.As().Data(); // const uint8_t x[32] auto x_param = info[0].As(); if (x_param.TypedArrayType() != napi_uint8_array) { Napi::Error::New(env, "Expected an Uint8Array") .ThrowAsJavaScriptException(); return env.Undefined(); } auto x = x_param.As().Data(); // const uint8_t y[32] auto y_param = info[0].As(); if (y_param.TypedArrayType() != napi_uint8_array) { Napi::Error::New(env, "Expected an Uint8Array") .ThrowAsJavaScriptException(); return env.Undefined(); } auto y = y_param.As().Data(); // const uint8_t p[48] auto p_param = info[0].As(); if (p_param.TypedArrayType() != napi_uint8_array) { Napi::Error::New(info.Env(), "Expected an Uint8Array") .ThrowAsJavaScriptException(); return info.Env().Undefined(); } auto p = p_param.As().Data(); // KZGSettings *s auto kzgSettings = info[4].As>().Data(); KZGCommitment commitment; KZGProof proof; BLSFieldElement fx, fy; bool out; bytes_to_bls_field(&fx, x); bytes_to_bls_field(&fy, y); auto ret = bytes_to_g1(&commitment, c); if (ret != C_KZG_OK) { std::ostringstream ss; std::copy(c, c+sizeof(c), std::ostream_iterator(ss, ",")); Napi::TypeError::New(env, "Failed to parse argument commitment: " + ss.str() + " Return code was: " + std::to_string(ret)).ThrowAsJavaScriptException(); return env.Null(); }; if (bytes_to_g1(&proof, p) != C_KZG_OK) { Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); return env.Null(); } if (verify_kzg_proof(&out, &commitment, &fx, &fy, &proof, kzgSettings) != C_KZG_OK) { return Napi::Boolean::New(env, false); } return Napi::Boolean::New(env, true); } Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set(Napi::String::New(env, "loadTrustedSetup"), Napi::Function::New(env, LoadTrustedSetup)); exports.Set(Napi::String::New(env, "freeTrustedSetup"), Napi::Function::New(env, FreeTrustedSetup)); exports.Set(Napi::String::New(env, "verifyKzgProof"), Napi::Function::New(env, VerifyKzgProof)); exports.Set(Napi::String::New(env, "blobToKzgCommitment"), Napi::Function::New(env, BlobToKzgCommitment)); exports.Set(Napi::String::New(env, "verifyAggregateKzgProof"), Napi::Function::New(env, VerifyAggregateKzgProof)); exports.Set(Napi::String::New(env, "computeAggregateKzgProof"), Napi::Function::New(env, ComputeAggregateKzgProof)); return exports; } NODE_API_MODULE(addon, Init)