Node binding argument and return type updates (#170)
This commit is contained in:
parent
7c0bd867d5
commit
ecc668bbe7
|
@ -4,6 +4,7 @@
|
|||
#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"
|
||||
|
@ -32,41 +33,90 @@ Napi::Value throw_invalid_argument_type(const Napi::Env env, std::string name, s
|
|||
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];
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return val.As<Napi::External<KZGSettings>>().Data();
|
||||
}
|
||||
|
||||
const uint8_t * extract_byte_array_from_param(const Napi::CallbackInfo& info, const int index, const std::string name) {
|
||||
auto param = info[index].As<Napi::TypedArray>();
|
||||
if (!param.IsTypedArray() || param.TypedArrayType() != napi_uint8_array) {
|
||||
throw_invalid_argument_type(info.Env(), name, "UInt8Array");
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return param.As<Napi::Uint8Array>().Data();
|
||||
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) {
|
||||
|
@ -89,7 +139,7 @@ Napi::Value LoadTrustedSetup(const Napi::CallbackInfo& info) {
|
|||
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");
|
||||
|
||||
|
@ -117,136 +167,189 @@ Napi::Value FreeTrustedSetup(const Napi::CallbackInfo& info) {
|
|||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* 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) {
|
||||
auto env = info.Env();
|
||||
|
||||
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 = (Blob *)extract_byte_array_from_param(info, 0, "blob");
|
||||
if (env.IsExceptionPending()) {
|
||||
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();
|
||||
}
|
||||
|
||||
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
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.Undefined();
|
||||
};
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
return napi_typed_array_from_bytes((uint8_t *)(&commitment), BYTES_PER_COMMITMENT, env);
|
||||
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&commitment), BYTES_PER_COMMITMENT);
|
||||
}
|
||||
|
||||
// computeKzgProof: (blob: Blob, zBytes: Bytes32, setupHandle: SetupHandle) => KZGProof;
|
||||
/**
|
||||
* 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) {
|
||||
auto env = info.Env();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto blob = extract_byte_array_from_param(info, 0, "blob");
|
||||
auto z_bytes = extract_byte_array_from_param(info, 1, "zBytes");
|
||||
auto kzg_settings = info[2].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
if (env.IsExceptionPending()) {
|
||||
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 *)blob,
|
||||
(Bytes32 *)z_bytes,
|
||||
blob,
|
||||
z_bytes,
|
||||
kzg_settings
|
||||
);
|
||||
|
||||
if (ret != C_KZG_OK) {
|
||||
Napi::Error::New(env, "Failed to compute proof")
|
||||
.ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
};
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
|
||||
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&proof), BYTES_PER_PROOF);
|
||||
}
|
||||
|
||||
// computeBlobKzgProof: (blob: Blob, setupHandle: SetupHandle) => KZGProof;
|
||||
Napi::Value ComputeBlobKzgProof(const Napi::CallbackInfo& info) {
|
||||
auto env = info.Env();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
auto blob = extract_byte_array_from_param(info, 0, "blob");
|
||||
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
if (env.IsExceptionPending()) {
|
||||
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 *)blob,
|
||||
blob,
|
||||
kzg_settings
|
||||
);
|
||||
|
||||
if (ret != C_KZG_OK) {
|
||||
Napi::Error::New(env, "Error in computeBlobKzgProof")
|
||||
.ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
};
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
|
||||
return Napi::Buffer<uint8_t>::Copy(env, reinterpret_cast<uint8_t *>(&proof), BYTES_PER_PROOF);
|
||||
}
|
||||
|
||||
// verifyKzgProof: (commitmentBytes: Bytes48, zBytes: Bytes32, yBytes: Bytes32, proofBytes: Bytes48, setupHandle: SetupHandle) => boolean;
|
||||
/**
|
||||
* 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) {
|
||||
auto env = info.Env();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto commitment_bytes = extract_byte_array_from_param(info, 0, "commitmentBytes");
|
||||
auto z_bytes = extract_byte_array_from_param(info, 1, "zBytes");
|
||||
auto y_bytes = extract_byte_array_from_param(info, 2, "yBytes");
|
||||
auto proof_bytes = extract_byte_array_from_param(info, 3, "proofBytes");
|
||||
auto kzg_settings = info[4].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
if (env.IsExceptionPending()) {
|
||||
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,
|
||||
(Bytes48 *)commitment_bytes,
|
||||
(Bytes32 *)z_bytes,
|
||||
(Bytes32 *)y_bytes,
|
||||
(Bytes48 *)proof_bytes,
|
||||
commitment_bytes,
|
||||
z_bytes,
|
||||
y_bytes,
|
||||
proof_bytes,
|
||||
kzg_settings
|
||||
);
|
||||
|
||||
|
@ -258,33 +361,49 @@ Napi::Value VerifyKzgProof(const Napi::CallbackInfo& info) {
|
|||
return Napi::Boolean::New(env, out);
|
||||
}
|
||||
|
||||
// verifyBlobKzgProof: (blob: Blob, commitmentBytes: Bytes48, proofBytes: Bytes48, setupHandle: SetupHandle) => boolean;
|
||||
/**
|
||||
* 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) {
|
||||
auto env = info.Env();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto blob_bytes = extract_byte_array_from_param(info, 0, "blob");
|
||||
auto commitment_bytes = extract_byte_array_from_param(info, 1, "commitmentBytes");
|
||||
auto proof_bytes = extract_byte_array_from_param(info, 2, "proofBytes");
|
||||
auto kzg_settings = info[3].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
if (env.IsExceptionPending()) {
|
||||
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 *)blob_bytes,
|
||||
(Bytes48 *)commitment_bytes,
|
||||
(Bytes48 *)proof_bytes,
|
||||
kzg_settings
|
||||
);
|
||||
blob_bytes,
|
||||
commitment_bytes,
|
||||
proof_bytes,
|
||||
kzg_settings);
|
||||
|
||||
if (ret != C_KZG_OK) {
|
||||
Napi::TypeError::New(env, "Error in verifyBlobKzgProof").ThrowAsJavaScriptException();
|
||||
|
@ -294,75 +413,83 @@ Napi::Value VerifyBlobKzgProof(const Napi::CallbackInfo& info) {
|
|||
return Napi::Boolean::New(env, out);
|
||||
}
|
||||
|
||||
// verifyBlobKzgProofBatch: (blobs: Blob[], commitmentsBytes: Bytes48[], proofsBytes: Bytes48[], setupHandle: SetupHandle) => boolean;
|
||||
/**
|
||||
* 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) {
|
||||
auto env = info.Env();
|
||||
|
||||
C_KZG_RET ret;
|
||||
Blob *blobs = NULL;
|
||||
Bytes48 *commitments = NULL;
|
||||
Bytes48 *proofs = NULL;
|
||||
Napi::Value result = env.Null();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto blobs_param = info[0].As<Napi::Array>();
|
||||
auto commitments_param = info[1].As<Napi::Array>();
|
||||
auto proofs_param = info[2].As<Napi::Array>();
|
||||
auto kzg_settings = info[3].As<Napi::External<KZGSettings>>().Data();
|
||||
|
||||
auto blobs_count = blobs_param.Length();
|
||||
auto commitments_count = commitments_param.Length();
|
||||
auto proofs_count = proofs_param.Length();
|
||||
|
||||
if (blobs_count != commitments_count || blobs_count != proofs_count) {
|
||||
Napi::Error::New(env, "verifyBlobKzgProofBatch requires equal number of blobs/commitments/proofs")
|
||||
.ThrowAsJavaScriptException();
|
||||
result = env.Null();
|
||||
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;
|
||||
}
|
||||
|
||||
blobs = (Blob *)calloc(blobs_count, sizeof(Blob));
|
||||
if (blobs == NULL) {
|
||||
Napi::Error::New(env, "Error while allocating memory for blobs").ThrowAsJavaScriptException();
|
||||
result = env.Null();
|
||||
goto out;
|
||||
};
|
||||
|
||||
commitments = (Bytes48 *)calloc(commitments_count, sizeof(Bytes48));
|
||||
if (commitments == NULL) {
|
||||
free(blobs);
|
||||
Napi::Error::New(env, "Error while allocating memory for commitments").ThrowAsJavaScriptException();
|
||||
result = env.Null();
|
||||
goto out;
|
||||
};
|
||||
|
||||
proofs = (Bytes48 *)calloc(proofs_count, sizeof(Bytes48));
|
||||
if (proofs == NULL) {
|
||||
Napi::Error::New(env, "Error while allocating memory for proofs").ThrowAsJavaScriptException();
|
||||
result = env.Null();
|
||||
goto out;
|
||||
};
|
||||
|
||||
for (uint32_t index = 0; index < blobs_count; index++) {
|
||||
// Extract blob bytes from parameter
|
||||
Napi::Value blob = blobs_param[index];
|
||||
auto blob_bytes = blob.As<Napi::Uint8Array>().Data();
|
||||
memcpy(blobs[index].bytes, blob_bytes, BYTES_PER_BLOB);
|
||||
|
||||
// Extract commitment from parameter
|
||||
Napi::Value commitment = commitments_param[index];
|
||||
auto commitment_bytes = commitment.As<Napi::Uint8Array>().Data();
|
||||
memcpy(&commitments[index], commitment_bytes, BYTES_PER_COMMITMENT);
|
||||
|
||||
// Extract proof from parameter
|
||||
Napi::Value proof = proofs_param[index];
|
||||
auto proof_bytes = proof.As<Napi::Uint8Array>().Data();
|
||||
memcpy(&proofs[index], proof_bytes, BYTES_PER_PROOF);
|
||||
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;
|
||||
|
@ -371,13 +498,12 @@ Napi::Value VerifyBlobKzgProofBatch(const Napi::CallbackInfo& info) {
|
|||
blobs,
|
||||
commitments,
|
||||
proofs,
|
||||
blobs_count,
|
||||
count,
|
||||
kzg_settings
|
||||
);
|
||||
|
||||
if (ret != C_KZG_OK) {
|
||||
Napi::TypeError::New(env, "Error in verifyBlobKzgProofBatch").ThrowAsJavaScriptException();
|
||||
result = env.Null();
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ const fs = require("fs");
|
|||
|
||||
export type Bytes32 = Uint8Array; // 32 bytes
|
||||
export type Bytes48 = Uint8Array; // 48 bytes
|
||||
export type KZGProof = Uint8Array; // 48 bytes
|
||||
export type KZGCommitment = Uint8Array; // 48 bytes
|
||||
export type KZGProof = Buffer; // 48 bytes
|
||||
export type KZGCommitment = Buffer; // 48 bytes
|
||||
export type Blob = Uint8Array; // 4096 * 32 bytes
|
||||
|
||||
type SetupHandle = Object;
|
||||
|
@ -81,62 +81,6 @@ function requireSetupHandle(): SetupHandle {
|
|||
return setupHandle;
|
||||
}
|
||||
|
||||
function checkBlob(blob: Blob) {
|
||||
if (!(blob instanceof Uint8Array)) {
|
||||
throw new Error("Expected blob to be a UInt8Array.");
|
||||
}
|
||||
if (blob.length != BYTES_PER_BLOB) {
|
||||
throw new Error(`Expected blob to be ${BYTES_PER_BLOB} bytes.`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkBlobs(blobs: Blob[]) {
|
||||
for (let blob of blobs) {
|
||||
checkBlob(blob);
|
||||
}
|
||||
}
|
||||
|
||||
function checkCommitment(commitment: KZGCommitment) {
|
||||
if (!(commitment instanceof Uint8Array)) {
|
||||
throw new Error("Expected commitment to be a UInt8Array.");
|
||||
}
|
||||
if (commitment.length != BYTES_PER_COMMITMENT) {
|
||||
throw new Error(`Expected commitment to be ${BYTES_PER_COMMITMENT} bytes.`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkCommitments(commitments: KZGCommitment[]) {
|
||||
for (let commitment of commitments) {
|
||||
checkCommitment(commitment);
|
||||
}
|
||||
}
|
||||
|
||||
function checkProof(proof: KZGProof) {
|
||||
if (!(proof instanceof Uint8Array)) {
|
||||
throw new Error("Expected proof to be a UInt8Array.");
|
||||
}
|
||||
if (proof.length != BYTES_PER_PROOF) {
|
||||
throw new Error(`Expected proof to be ${BYTES_PER_PROOF} bytes.`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkProofs(proofs: KZGProof[]) {
|
||||
for (let proof of proofs) {
|
||||
checkProof(proof);
|
||||
}
|
||||
}
|
||||
|
||||
function checkFieldElement(field: Bytes32) {
|
||||
if (!(field instanceof Uint8Array)) {
|
||||
throw new Error("Expected field element to be a UInt8Array.");
|
||||
}
|
||||
if (field.length != BYTES_PER_FIELD_ELEMENT) {
|
||||
throw new Error(
|
||||
`Expected field element to be ${BYTES_PER_FIELD_ELEMENT} bytes.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function transformTrustedSetupJSON(
|
||||
filePath: string,
|
||||
): Promise<string> {
|
||||
|
@ -178,32 +122,65 @@ export function freeTrustedSetup(): void {
|
|||
setupHandle = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a blob to a KZG commitment.
|
||||
*
|
||||
* @param {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
|
||||
*/
|
||||
export function blobToKzgCommitment(blob: Blob): KZGCommitment {
|
||||
checkBlob(blob);
|
||||
return kzg.blobToKzgCommitment(blob, requireSetupHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute KZG proof for polynomial in Lagrange form at position z.
|
||||
*
|
||||
* @param {Blob} blob - The blob (polynomial) to generate a proof for
|
||||
* @param {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
|
||||
*/
|
||||
export function computeKzgProof(blob: Blob, zBytes: Bytes32): KZGProof {
|
||||
checkBlob(blob);
|
||||
checkFieldElement(zBytes);
|
||||
return kzg.computeKzgProof(blob, zBytes, requireSetupHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a blob, return the KZG proof that is used to verify it against the
|
||||
* commitment.
|
||||
*
|
||||
* @param {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
|
||||
*/
|
||||
export function computeBlobKzgProof(blob: Blob): KZGProof {
|
||||
checkBlob(blob);
|
||||
return kzg.computeBlobKzgProof(blob, requireSetupHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a KZG poof claiming that `p(z) == y`.
|
||||
*
|
||||
* @param {Bytes48} commitmentBytes - The serialized commitment corresponding to polynomial p(x)
|
||||
* @param {Bytes32} zBytes - The serialized evaluation point
|
||||
* @param {Bytes32} yBytes - The serialized claimed evaluation result
|
||||
* @param {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
|
||||
*/
|
||||
export function verifyKzgProof(
|
||||
commitmentBytes: Bytes48,
|
||||
zBytes: Bytes32,
|
||||
yBytes: Bytes32,
|
||||
proofBytes: Bytes48,
|
||||
): boolean {
|
||||
checkCommitment(commitmentBytes);
|
||||
checkFieldElement(zBytes);
|
||||
checkFieldElement(yBytes);
|
||||
checkProof(proofBytes);
|
||||
return kzg.verifyKzgProof(
|
||||
commitmentBytes,
|
||||
zBytes,
|
||||
|
@ -213,14 +190,23 @@ export function verifyKzgProof(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a blob and its proof, verify that it corresponds to the provided
|
||||
* commitment.
|
||||
*
|
||||
* @param {Blob} blob - The serialized blob to verify
|
||||
* @param {Bytes48} commitmentBytes - The serialized commitment to verify
|
||||
* @param {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
|
||||
*/
|
||||
export function verifyBlobKzgProof(
|
||||
blob: Blob,
|
||||
commitmentBytes: Bytes48,
|
||||
proofBytes: Bytes48,
|
||||
): boolean {
|
||||
checkBlob(blob);
|
||||
checkCommitment(commitmentBytes);
|
||||
checkProof(proofBytes);
|
||||
return kzg.verifyBlobKzgProof(
|
||||
blob,
|
||||
commitmentBytes,
|
||||
|
@ -229,14 +215,25 @@ export function verifyBlobKzgProof(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of blobs and their proofs, verify that they corresponds to their
|
||||
* provided commitment.
|
||||
*
|
||||
* Note: blobs[0] relates to commitmentBytes[0] and proofBytes[0]
|
||||
*
|
||||
* @param {Blob} blobs - An array of serialized blobs to verify
|
||||
* @param {Bytes48} commitmentBytes - An array of serialized commitments to verify
|
||||
* @param {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
|
||||
*/
|
||||
export function verifyBlobKzgProofBatch(
|
||||
blobs: Blob[],
|
||||
commitmentsBytes: Bytes48[],
|
||||
proofsBytes: Bytes48[],
|
||||
): boolean {
|
||||
checkBlobs(blobs);
|
||||
checkCommitments(commitmentsBytes);
|
||||
checkProofs(proofsBytes);
|
||||
return kzg.verifyBlobKzgProofBatch(
|
||||
blobs,
|
||||
commitmentsBytes,
|
||||
|
|
|
@ -64,8 +64,17 @@ const generateRandomBlob = () => {
|
|||
);
|
||||
};
|
||||
|
||||
function bytesFromHex(hexstring: string): Uint8Array {
|
||||
return Uint8Array.from(Buffer.from(hexstring.slice(2), "hex"));
|
||||
const blobValidLength = randomBytes(BYTES_PER_BLOB);
|
||||
const blobBadLength = randomBytes(BYTES_PER_BLOB - 1);
|
||||
const commitmentValidLength = randomBytes(BYTES_PER_COMMITMENT);
|
||||
const commitmentBadLength = randomBytes(BYTES_PER_COMMITMENT - 1);
|
||||
const proofValidLength = randomBytes(BYTES_PER_PROOF);
|
||||
const proofBadLength = randomBytes(BYTES_PER_PROOF - 1);
|
||||
const fieldElementValidLength = randomBytes(BYTES_PER_FIELD_ELEMENT);
|
||||
const fieldElementBadLength = randomBytes(BYTES_PER_FIELD_ELEMENT - 1);
|
||||
|
||||
function bytesFromHex(hexString: string): Buffer {
|
||||
return Buffer.from(hexString.slice(2), "hex");
|
||||
}
|
||||
|
||||
describe("C-KZG", () => {
|
||||
|
@ -84,7 +93,7 @@ describe("C-KZG", () => {
|
|||
tests.forEach((testFile: string) => {
|
||||
const test = yaml.load(readFileSync(testFile, "ascii"));
|
||||
|
||||
let commitment = new Uint8Array();
|
||||
let commitment: Buffer;
|
||||
let blob = bytesFromHex(test.input.blob);
|
||||
|
||||
try {
|
||||
|
@ -105,7 +114,7 @@ describe("C-KZG", () => {
|
|||
tests.forEach((testFile: string) => {
|
||||
const test = yaml.load(readFileSync(testFile, "ascii"));
|
||||
|
||||
let proof = new Uint8Array();
|
||||
let proof: Buffer;
|
||||
let blob = bytesFromHex(test.input.blob);
|
||||
let z = bytesFromHex(test.input.z);
|
||||
|
||||
|
@ -127,7 +136,7 @@ describe("C-KZG", () => {
|
|||
tests.forEach((testFile: string) => {
|
||||
const test = yaml.load(readFileSync(testFile, "ascii"));
|
||||
|
||||
let proof = new Uint8Array();
|
||||
let proof: Buffer;
|
||||
let blob = bytesFromHex(test.input.blob);
|
||||
|
||||
try {
|
||||
|
@ -212,14 +221,14 @@ describe("C-KZG", () => {
|
|||
it("throws as expected when given an argument of invalid type", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => blobToKzgCommitment("wrong type")).toThrowError(
|
||||
"Expected blob to be a UInt8Array",
|
||||
"Expected blob to be a Uint8Array",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() =>
|
||||
blobToKzgCommitment(randomBytes(BYTES_PER_BLOB - 1)),
|
||||
).toThrowError("Expected blob to be 131072 bytes");
|
||||
expect(() => blobToKzgCommitment(blobBadLength)).toThrowError(
|
||||
"Expected blob to be 131072 bytes",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -230,6 +239,14 @@ describe("C-KZG", () => {
|
|||
const zBytes = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
|
||||
computeKzgProof(blob, zBytes);
|
||||
});
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() =>
|
||||
computeKzgProof(blobBadLength, fieldElementValidLength),
|
||||
).toThrowError("Expected blob to be 131072 bytes");
|
||||
expect(() =>
|
||||
computeKzgProof(blobValidLength, fieldElementBadLength),
|
||||
).toThrowError("Expected zBytes to be 32 bytes");
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: add more tests for this function.
|
||||
|
@ -238,6 +255,11 @@ describe("C-KZG", () => {
|
|||
let blob = generateRandomBlob();
|
||||
computeBlobKzgProof(blob);
|
||||
});
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() => computeBlobKzgProof(blobBadLength)).toThrowError(
|
||||
"Expected blob to be 131072 bytes",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases for verifyKzgProof", () => {
|
||||
|
@ -262,6 +284,40 @@ describe("C-KZG", () => {
|
|||
|
||||
expect(verifyKzgProof(commitment, z, y, proof)).toBe(false);
|
||||
});
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() =>
|
||||
verifyKzgProof(
|
||||
commitmentBadLength,
|
||||
fieldElementValidLength,
|
||||
fieldElementValidLength,
|
||||
proofValidLength,
|
||||
),
|
||||
).toThrowError("Expected commitmentBytes to be 48 bytes");
|
||||
expect(() =>
|
||||
verifyKzgProof(
|
||||
commitmentValidLength,
|
||||
fieldElementBadLength,
|
||||
fieldElementValidLength,
|
||||
proofValidLength,
|
||||
),
|
||||
).toThrowError("Expected zBytes to be 32 bytes");
|
||||
expect(() =>
|
||||
verifyKzgProof(
|
||||
commitmentValidLength,
|
||||
fieldElementValidLength,
|
||||
fieldElementBadLength,
|
||||
proofValidLength,
|
||||
),
|
||||
).toThrowError("Expected yBytes to be 32 bytes");
|
||||
expect(() =>
|
||||
verifyKzgProof(
|
||||
commitmentValidLength,
|
||||
fieldElementValidLength,
|
||||
fieldElementValidLength,
|
||||
proofBadLength,
|
||||
),
|
||||
).toThrowError("Expected proofBytes to be 48 bytes");
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases for verifyBlobKzgProof", () => {
|
||||
|
@ -285,14 +341,72 @@ describe("C-KZG", () => {
|
|||
let proof = computeBlobKzgProof(generateRandomBlob());
|
||||
expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(false);
|
||||
});
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() =>
|
||||
verifyBlobKzgProof(
|
||||
blobBadLength,
|
||||
commitmentValidLength,
|
||||
proofValidLength,
|
||||
),
|
||||
).toThrowError("Expected blob to be 131072 bytes");
|
||||
expect(() =>
|
||||
verifyBlobKzgProof(
|
||||
blobValidLength,
|
||||
commitmentBadLength,
|
||||
proofValidLength,
|
||||
),
|
||||
).toThrowError("Expected commitmentBytes to be 48 bytes");
|
||||
expect(() =>
|
||||
verifyBlobKzgProof(
|
||||
blobValidLength,
|
||||
commitmentValidLength,
|
||||
proofBadLength,
|
||||
),
|
||||
).toThrowError("Expected proofBytes to be 48 bytes");
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases for verifyBlobKzgProofBatch", () => {
|
||||
it("should reject non-array args", () => {
|
||||
expect(() =>
|
||||
verifyBlobKzgProofBatch(
|
||||
2 as unknown as Uint8Array[],
|
||||
[commitmentValidLength, commitmentValidLength],
|
||||
[proofValidLength, proofValidLength],
|
||||
),
|
||||
).toThrowError("blobs, commitments, and proofs must all be arrays");
|
||||
});
|
||||
it("should reject non-bytearray blob", () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
verifyBlobKzgProofBatch(["foo", "bar"], [], []),
|
||||
).toThrowError("Expected blob to be a UInt8Array");
|
||||
verifyBlobKzgProofBatch(
|
||||
["foo", "bar"] as unknown as Uint8Array[],
|
||||
[commitmentValidLength, commitmentValidLength],
|
||||
[proofValidLength, proofValidLength],
|
||||
),
|
||||
).toThrowError("Expected blob to be a Uint8Array");
|
||||
});
|
||||
it("throws as expected when given an argument of invalid length", () => {
|
||||
expect(() =>
|
||||
verifyBlobKzgProofBatch(
|
||||
[blobBadLength, blobValidLength],
|
||||
[commitmentValidLength, commitmentValidLength],
|
||||
[proofValidLength, proofValidLength],
|
||||
),
|
||||
).toThrowError("Expected blob to be 131072 bytes");
|
||||
expect(() =>
|
||||
verifyBlobKzgProofBatch(
|
||||
[blobValidLength, blobValidLength],
|
||||
[commitmentBadLength, commitmentValidLength],
|
||||
[proofValidLength, proofValidLength],
|
||||
),
|
||||
).toThrowError("Expected commitmentBytes to be 48 bytes");
|
||||
expect(() =>
|
||||
verifyBlobKzgProofBatch(
|
||||
[blobValidLength, blobValidLength],
|
||||
[commitmentValidLength, commitmentValidLength],
|
||||
[proofValidLength, proofBadLength],
|
||||
),
|
||||
).toThrowError("Expected proofBytes to be 48 bytes");
|
||||
});
|
||||
|
||||
it("zero blobs/commitments/proofs should verify as true", () => {
|
||||
|
@ -305,7 +419,7 @@ describe("C-KZG", () => {
|
|||
let commitments = new Array(count);
|
||||
let proofs = new Array(count);
|
||||
|
||||
for (let [i, _] of blobs.entries()) {
|
||||
for (let [i] of blobs.entries()) {
|
||||
blobs[i] = generateRandomBlob();
|
||||
commitments[i] = blobToKzgCommitment(blobs[i]);
|
||||
proofs[i] = computeBlobKzgProof(blobs[i]);
|
||||
|
|
Loading…
Reference in New Issue