Node binding argument and return type updates (#170)

This commit is contained in:
Matthew Keil 2023-03-06 19:28:30 -06:00 committed by GitHub
parent 7c0bd867d5
commit ecc668bbe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 484 additions and 247 deletions

View File

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

View File

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

View File

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