mirror of
https://github.com/status-im/c-kzg-4844.git
synced 2025-01-21 15:38:49 +00:00
541 lines
17 KiB
C++
541 lines
17 KiB
C++
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sstream> // std::ostringstream
|
|
#include <algorithm> // std::copy
|
|
#include <iterator> // std::ostream_iterator
|
|
#include <string_view>
|
|
#include <napi.h>
|
|
#include "c_kzg_4844.h"
|
|
#include "blst.h"
|
|
|
|
Napi::Value throw_invalid_arguments_count(
|
|
const unsigned int expected,
|
|
const unsigned int 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 argument type: " + name + ". Expected " + expectedType
|
|
).ThrowAsJavaScriptException();
|
|
|
|
return env.Null();
|
|
}
|
|
|
|
/**
|
|
* Get kzg_settings from a Napi::External
|
|
*
|
|
* Checks for:
|
|
* - arg IsExternal
|
|
*
|
|
* Built to pass in a raw Napi::Value so it can be used like
|
|
* `get_kzg_settings(env, info[0])`.
|
|
*
|
|
* Designed to raise the correct javascript exception and return a
|
|
* valid pointer to the calling context to avoid native stack-frame
|
|
* unwinds. Calling context can check for `nullptr` to see if an
|
|
* exception was raised or a valid pointer was returned from V8.
|
|
*
|
|
* @param[in] env Passed from calling context
|
|
* @param[in] val Napi::Value to validate and get pointer from
|
|
*
|
|
* @return - Pointer to the KZGSettings
|
|
*/
|
|
KZGSettings *get_kzg_settings(const Napi::Env &env, const Napi::Value &val) {
|
|
if (!val.IsExternal()) {
|
|
Napi::TypeError::New(env, "Must pass setupHandle as the last function argument").ThrowAsJavaScriptException();
|
|
return nullptr;
|
|
}
|
|
return val.As<Napi::External<KZGSettings>>().Data();
|
|
}
|
|
|
|
/**
|
|
* Checks for:
|
|
* - arg is Uint8Array or Buffer (inherits from Uint8Array)
|
|
* - underlying ArrayBuffer length is correct
|
|
*
|
|
* Internal function for argument validation. Prefer to use
|
|
* the helpers below that already have the reinterpreted casts:
|
|
* - get_blob
|
|
* - get_bytes32
|
|
* - get_bytes48
|
|
*
|
|
* Built to pass in a raw Napi::Value so it can be used like
|
|
* `get_bytes(env, info[0])` or can also be used to pull props from
|
|
* arrays like `get_bytes(env, passed_napi_array[2])`.
|
|
*
|
|
* Designed to raise the correct javascript exception and return a
|
|
* valid pointer to the calling context to avoid native stack-frame
|
|
* unwinds. Calling context can check for `nullptr` to see if an
|
|
* exception was raised or a valid pointer was returned from V8.
|
|
*
|
|
* @param[in] env Passed from calling context
|
|
* @param[in] val Napi::Value to validate and get pointer from
|
|
* @param[in] length Byte length to validate Uint8Array data against
|
|
* @param[in] name Name of prop being validated for error reporting
|
|
*
|
|
* @return - native pointer to first byte in ArrayBuffer
|
|
*/
|
|
inline uint8_t *get_bytes(
|
|
const Napi::Env &env,
|
|
const Napi::Value &val,
|
|
size_t length,
|
|
std::string_view name)
|
|
{
|
|
if (!val.IsTypedArray() || val.As<Napi::TypedArray>().TypedArrayType() != napi_uint8_array) {
|
|
std::ostringstream msg;
|
|
msg << "Expected " << name << " to be a Uint8Array";
|
|
Napi::TypeError::New(env, msg.str()).ThrowAsJavaScriptException();
|
|
return nullptr;
|
|
}
|
|
Napi::Uint8Array array = val.As<Napi::Uint8Array>();
|
|
if (array.ByteLength() != length) {
|
|
std::ostringstream msg;
|
|
msg << "Expected " << name << " to be " << length << " bytes";
|
|
Napi::TypeError::New(env, msg.str()).ThrowAsJavaScriptException();
|
|
return nullptr;
|
|
}
|
|
return array.Data();
|
|
}
|
|
inline Blob *get_blob(const Napi::Env &env, const Napi::Value &val) {
|
|
return reinterpret_cast<Blob *>(get_bytes(env, val, BYTES_PER_BLOB, "blob"));
|
|
}
|
|
inline Bytes32 *get_bytes32(const Napi::Env &env, const Napi::Value &val, std::string_view name) {
|
|
return reinterpret_cast<Bytes32 *>(get_bytes(env, val, BYTES_PER_FIELD_ELEMENT, name));
|
|
}
|
|
inline Bytes48 *get_bytes48(const Napi::Env &env, const Napi::Value &val, std::string_view name) {
|
|
return reinterpret_cast<Bytes48 *>(get_bytes(env, val, BYTES_PER_COMMITMENT, name));
|
|
}
|
|
|
|
// 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: " + file_path).ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
if (load_trusted_setup_file(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);
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[0]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
free_trusted_setup(kzg_settings);
|
|
free(kzg_settings);
|
|
return env.Undefined();
|
|
}
|
|
|
|
/**
|
|
* Convert a blob to a KZG commitment.
|
|
*
|
|
* @param[in] {Blob} blob - The blob representing the polynomial to be committed to
|
|
*
|
|
* @return {KZGCommitment} - The resulting commitment
|
|
*
|
|
* @throws {TypeError} - For invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value BlobToKzgCommitment(const Napi::CallbackInfo& info) {
|
|
Napi::Env 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);
|
|
}
|
|
Blob *blob = get_blob(env, info[0]);
|
|
if (blob == nullptr) {
|
|
return env.Null();
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[1]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
KZGCommitment commitment;
|
|
C_KZG_RET ret = blob_to_kzg_commitment(&commitment, blob, kzg_settings);
|
|
if (ret != C_KZG_OK) {
|
|
Napi::Error::New(env, "Failed to convert blob to commitment")
|
|
.ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&commitment), BYTES_PER_COMMITMENT);
|
|
}
|
|
|
|
/**
|
|
* Compute KZG proof for polynomial in Lagrange form at position z.
|
|
*
|
|
* @param[in] {Blob} blob - The blob (polynomial) to generate a proof for
|
|
* @param[in] {Bytes32} zBytes - The generator z-value for the evaluation points
|
|
*
|
|
* @return {KZGProof} - The resulting proof
|
|
*
|
|
* @throws {TypeError} - for invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) {
|
|
Napi::Env env = info.Env();
|
|
size_t argument_count = info.Length();
|
|
size_t expected_argument_count = 3;
|
|
if (argument_count != expected_argument_count) {
|
|
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
|
|
}
|
|
Blob *blob = get_blob(env, info[0]);
|
|
if (blob == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes32 *z_bytes = get_bytes32(env, info[1], "zBytes");
|
|
if (z_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[2]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
KZGProof proof;
|
|
C_KZG_RET ret = compute_kzg_proof(
|
|
&proof,
|
|
blob,
|
|
z_bytes,
|
|
kzg_settings
|
|
);
|
|
|
|
if (ret != C_KZG_OK) {
|
|
Napi::Error::New(env, "Failed to compute proof")
|
|
.ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&proof), BYTES_PER_PROOF);
|
|
}
|
|
|
|
|
|
/**
|
|
* Given a blob, return the KZG proof that is used to verify it against the
|
|
* commitment.
|
|
*
|
|
* @param[in] {Blob} blob - The blob (polynomial) to generate a proof for
|
|
*
|
|
* @return {KZGProof} - The resulting proof
|
|
*
|
|
* @throws {TypeError} - for invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value ComputeBlobKzgProof(const Napi::CallbackInfo& info) {
|
|
Napi::Env 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);
|
|
}
|
|
Blob *blob = get_blob(env, info[0]);
|
|
if (blob == nullptr) {
|
|
return env.Null();
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[1]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
KZGProof proof;
|
|
C_KZG_RET ret = compute_blob_kzg_proof(
|
|
&proof,
|
|
blob,
|
|
kzg_settings
|
|
);
|
|
|
|
if (ret != C_KZG_OK) {
|
|
Napi::Error::New(env, "Error in computeBlobKzgProof")
|
|
.ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&proof), BYTES_PER_PROOF);
|
|
}
|
|
|
|
/**
|
|
* Verify a KZG poof claiming that `p(z) == y`.
|
|
*
|
|
* @param[in] {Bytes48} commitmentBytes - The serialized commitment corresponding to polynomial p(x)
|
|
* @param[in] {Bytes32} zBytes - The serialized evaluation point
|
|
* @param[in] {Bytes32} yBytes - The serialized claimed evaluation result
|
|
* @param[in] {Bytes48} proofBytes - The serialized KZG proof
|
|
*
|
|
* @return {boolean} - true/false depending on proof validity
|
|
*
|
|
* @throws {TypeError} - for invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value VerifyKzgProof(const Napi::CallbackInfo& info) {
|
|
Napi::Env 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);
|
|
}
|
|
Bytes48 *commitment_bytes = get_bytes48(env, info[0], "commitmentBytes");
|
|
if (commitment_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes32 *z_bytes = get_bytes32(env, info[1], "zBytes");
|
|
if (z_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes32 *y_bytes = get_bytes32(env, info[2], "yBytes");
|
|
if (y_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes48 *proof_bytes = get_bytes48(env, info[3], "proofBytes");
|
|
if (proof_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[4]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
bool out;
|
|
C_KZG_RET ret = verify_kzg_proof(
|
|
&out,
|
|
commitment_bytes,
|
|
z_bytes,
|
|
y_bytes,
|
|
proof_bytes,
|
|
kzg_settings
|
|
);
|
|
|
|
if (ret != C_KZG_OK) {
|
|
Napi::TypeError::New(env, "Failed to verify KZG proof").ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
return Napi::Boolean::New(env, out);
|
|
}
|
|
|
|
/**
|
|
* Given a blob and its proof, verify that it corresponds to the provided
|
|
* commitment.
|
|
*
|
|
* @param[in] {Blob} blob - The serialized blob to verify
|
|
* @param[in] {Bytes48} commitmentBytes - The serialized commitment to verify
|
|
* @param[in] {Bytes48} proofBytes - The serialized KZG proof for verification
|
|
*
|
|
* @return {boolean} - true/false depending on proof validity
|
|
*
|
|
* @throws {TypeError} - for invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value VerifyBlobKzgProof(const Napi::CallbackInfo& info) {
|
|
Napi::Env 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);
|
|
}
|
|
Blob *blob_bytes = get_blob(env, info[0]);
|
|
if (blob_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes48 *commitment_bytes = get_bytes48(env, info[1], "commitmentBytes");
|
|
if (commitment_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
Bytes48 *proof_bytes = get_bytes48(env, info[2], "proofBytes");
|
|
if (proof_bytes == nullptr) {
|
|
return env.Null();
|
|
}
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[3]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
|
|
bool out;
|
|
C_KZG_RET ret = verify_blob_kzg_proof(
|
|
&out,
|
|
blob_bytes,
|
|
commitment_bytes,
|
|
proof_bytes,
|
|
kzg_settings);
|
|
|
|
if (ret != C_KZG_OK) {
|
|
Napi::TypeError::New(env, "Error in verifyBlobKzgProof").ThrowAsJavaScriptException();
|
|
return env.Null();
|
|
}
|
|
|
|
return Napi::Boolean::New(env, out);
|
|
}
|
|
|
|
/**
|
|
* Given an array of blobs and their proofs, verify that they corresponds to their
|
|
* provided commitment.
|
|
*
|
|
* @remark blobs[0] relates to commitmentBytes[0] and proofBytes[0]
|
|
*
|
|
* @param[in] {Blob} blobs - An array of serialized blobs to verify
|
|
* @param[in] {Bytes48} commitmentBytes - An array of serialized commitments to verify
|
|
* @param[in] {Bytes48} proofBytes - An array of serialized KZG proofs for verification
|
|
*
|
|
* @return {boolean} - true/false depending on batch validity
|
|
*
|
|
* @throws {TypeError} - for invalid arguments or failure of the native library
|
|
*/
|
|
Napi::Value VerifyBlobKzgProofBatch(const Napi::CallbackInfo& info) {
|
|
Napi::Env 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);
|
|
}
|
|
C_KZG_RET ret;
|
|
Blob *blobs = NULL;
|
|
Bytes48 *commitments = NULL;
|
|
Bytes48 *proofs = NULL;
|
|
Napi::Value result = env.Null();
|
|
if (!(info[0].IsArray() && info[1].IsArray() && info[2].IsArray())) {
|
|
Napi::Error::New(env, "blobs, commitments, and proofs must all be arrays").ThrowAsJavaScriptException();
|
|
return result;
|
|
}
|
|
Napi::Array blobs_param = info[0].As<Napi::Array>();
|
|
Napi::Array commitments_param = info[1].As<Napi::Array>();
|
|
Napi::Array proofs_param = info[2].As<Napi::Array>();
|
|
KZGSettings *kzg_settings = get_kzg_settings(env, info[3]);
|
|
if (kzg_settings == nullptr) {
|
|
return env.Null();
|
|
}
|
|
uint32_t count = blobs_param.Length();
|
|
if (count != commitments_param.Length() || count != proofs_param.Length()) {
|
|
Napi::Error::New(env, "requires equal number of blobs/commitments/proofs").ThrowAsJavaScriptException();
|
|
return result;
|
|
}
|
|
blobs = (Blob *)calloc(count, sizeof(Blob));
|
|
if (blobs == nullptr) {
|
|
Napi::Error::New(env, "Error while allocating memory for blobs").ThrowAsJavaScriptException();
|
|
goto out;
|
|
}
|
|
commitments = (Bytes48 *)calloc(count, sizeof(Bytes48));
|
|
if (commitments == nullptr) {
|
|
Napi::Error::New(env, "Error while allocating memory for commitments").ThrowAsJavaScriptException();
|
|
goto out;
|
|
}
|
|
proofs = (Bytes48 *)calloc(count, sizeof(Bytes48));
|
|
if (proofs == nullptr) {
|
|
Napi::Error::New(env, "Error while allocating memory for proofs").ThrowAsJavaScriptException();
|
|
goto out;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
// add HandleScope here to release reference to temp values
|
|
// after each iteration since data is being memcpy
|
|
Napi::HandleScope scope{env};
|
|
Blob *blob = get_blob(env, blobs_param[index]);
|
|
if (blob == nullptr) {
|
|
goto out;
|
|
}
|
|
memcpy(&blobs[index], blob, BYTES_PER_BLOB);
|
|
Bytes48 *commitment = get_bytes48(env, commitments_param[index], "commitmentBytes");
|
|
if (commitment == nullptr) {
|
|
goto out;
|
|
}
|
|
memcpy(&commitments[index], commitment, BYTES_PER_COMMITMENT);
|
|
Bytes48 *proof = get_bytes48(env, proofs_param[index], "proofBytes");
|
|
if (proof == nullptr) {
|
|
goto out;
|
|
}
|
|
memcpy(&proofs[index], proof, BYTES_PER_PROOF);
|
|
}
|
|
|
|
bool out;
|
|
ret = verify_blob_kzg_proof_batch(
|
|
&out,
|
|
blobs,
|
|
commitments,
|
|
proofs,
|
|
count,
|
|
kzg_settings
|
|
);
|
|
|
|
if (ret != C_KZG_OK) {
|
|
Napi::TypeError::New(env, "Error in verifyBlobKzgProofBatch").ThrowAsJavaScriptException();
|
|
goto out;
|
|
}
|
|
|
|
result = Napi::Boolean::New(env, out);
|
|
|
|
out:
|
|
free(blobs);
|
|
free(commitments);
|
|
free(proofs);
|
|
return result;
|
|
}
|
|
|
|
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["blobToKzgCommitment"] = Napi::Function::New(env, BlobToKzgCommitment);
|
|
exports["computeKzgProof"] = Napi::Function::New(env, ComputeKzgProof);
|
|
exports["computeBlobKzgProof"] = Napi::Function::New(env, ComputeBlobKzgProof);
|
|
exports["verifyKzgProof"] = Napi::Function::New(env, VerifyKzgProof);
|
|
exports["verifyBlobKzgProof"] = Napi::Function::New(env, VerifyBlobKzgProof);
|
|
exports["verifyBlobKzgProofBatch"] = Napi::Function::New(env, VerifyBlobKzgProofBatch);
|
|
|
|
// Constants
|
|
exports["BYTES_PER_BLOB"] = Napi::Number::New(env, BYTES_PER_BLOB);
|
|
exports["BYTES_PER_COMMITMENT"] = Napi::Number::New(env, BYTES_PER_COMMITMENT);
|
|
exports["BYTES_PER_FIELD_ELEMENT"] = Napi::Number::New(env, BYTES_PER_FIELD_ELEMENT);
|
|
exports["BYTES_PER_PROOF"] = Napi::Number::New(env, BYTES_PER_PROOF);
|
|
exports["FIELD_ELEMENTS_PER_BLOB"] = Napi::Number::New(env, FIELD_ELEMENTS_PER_BLOB);
|
|
|
|
return exports;
|
|
}
|
|
|
|
NODE_API_MODULE(addon, Init)
|