Merge pull request #147 from jtraglia/update-nodejs-bindings

Update nodejs bindings
This commit is contained in:
George Kadianakis 2023-02-22 18:12:47 +02:00 committed by GitHub
commit d41a3b0060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 479 additions and 223 deletions

View File

@ -152,51 +152,6 @@ Napi::Value BlobToKzgCommitment(const Napi::CallbackInfo& info) {
return napi_typed_array_from_bytes((uint8_t *)(&commitment), BYTES_PER_COMMITMENT, env);
}
// computeAggregateKzgProof: (blobs: Blob[], setupHandle: SetupHandle) => KZGProof;
Napi::Value ComputeAggregateKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env();
size_t argument_count = info.Length();
size_t expected_argument_count = 2;
if (argument_count != expected_argument_count) {
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
}
auto blobs_param = info[0].As<Napi::Array>();
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
auto blobs_count = blobs_param.Length();
auto blobs = (Blob*)calloc(blobs_count, sizeof(Blob));
if (blobs == NULL) {
Napi::Error::New(env, "Error while allocating memory for blobs").ThrowAsJavaScriptException();
return env.Null();
};
for (uint32_t blob_index = 0; blob_index < blobs_count; blob_index++) {
Napi::Value blob = blobs_param[blob_index];
auto blob_bytes = blob.As<Napi::Uint8Array>().Data();
memcpy(blobs[blob_index].bytes, blob_bytes, BYTES_PER_BLOB);
}
KZGProof proof;
C_KZG_RET ret = compute_aggregate_kzg_proof(
&proof,
blobs,
blobs_count,
kzg_settings
);
free(blobs);
if (ret != C_KZG_OK) {
Napi::Error::New(env, "Failed to compute aggregated proof")
.ThrowAsJavaScriptException();
return env.Undefined();
};
return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
}
// computeKzgProof: (blob: Blob, zBytes: Bytes32, setupHandle: SetupHandle) => KZGProof;
Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env();
@ -211,6 +166,10 @@ Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) {
auto z_bytes = extract_byte_array_from_param(info, 1, "zBytes");
auto kzg_settings = info[2].As<Napi::External<KZGSettings>>().Data();
if (env.IsExceptionPending()) {
return env.Null();
}
KZGProof proof;
C_KZG_RET ret = compute_kzg_proof(
&proof,
@ -228,80 +187,37 @@ Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) {
return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
}
// verifyAggregateKzgProof: (blobs: Blob[], commitmentsBytes: Bytes48[], aggregatedProofBytes: Bytes48, setupHandle: SetupHandle) => boolean;
Napi::Value VerifyAggregateKzgProof(const Napi::CallbackInfo& info) {
// computeBlobKzgProof: (blob: Blob, setupHandle: SetupHandle) => KZGProof;
Napi::Value ComputeBlobKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env();
size_t argument_count = info.Length();
size_t expected_argument_count = 4;
size_t expected_argument_count = 2;
if (argument_count != expected_argument_count) {
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
}
auto blobs_param = info[0].As<Napi::Array>();
auto commitments_param = info[1].As<Napi::Array>();
auto proof_param = info[2].As<Napi::TypedArray>();
auto kzg_settings = info[3].As<Napi::External<KZGSettings>>().Data();
auto blob = extract_byte_array_from_param(info, 0, "blob");
auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
auto proof_bytes = proof_param.As<Napi::Uint8Array>().Data();
auto blobs_count = blobs_param.Length();
auto commitments_count = commitments_param.Length();
if (blobs_count != commitments_count) {
Napi::Error::New(env, "verifyAggregateKzgProof requires blobs count to match expectedKzgCommitments count")
.ThrowAsJavaScriptException();
return env.Undefined();
if (env.IsExceptionPending()) {
return env.Null();
}
auto blobs = (Blob*)calloc(blobs_count, sizeof(Blob));
if (blobs == NULL) {
Napi::Error::New(env, "Error while allocating memory for blobs").ThrowAsJavaScriptException();
return env.Null();
};
auto commitments = (Bytes48*)calloc(blobs_count, sizeof(Bytes48));
if (commitments == NULL) {
free(blobs);
Napi::Error::New(env, "Error while allocating memory for commitments").ThrowAsJavaScriptException();
return env.Null();
};
C_KZG_RET ret;
for (uint32_t blob_index = 0; blob_index < blobs_count; blob_index++) {
// Extract blob bytes from parameter
Napi::Value blob = blobs_param[blob_index];
auto blob_bytes = blob.As<Napi::Uint8Array>().Data();
memcpy(blobs[blob_index].bytes, blob_bytes, BYTES_PER_BLOB);
// Extract commitment from parameter
Napi::Value commitment = commitments_param[blob_index];
auto commitment_bytes = commitment.As<Napi::Uint8Array>().Data();
memcpy(&commitments[blob_index], commitment_bytes, BYTES_PER_COMMITMENT);
}
bool verification_result;
ret = verify_aggregate_kzg_proof(
&verification_result,
blobs,
commitments,
blobs_count,
(Bytes48 *)proof_bytes,
KZGProof proof;
C_KZG_RET ret = compute_blob_kzg_proof(
&proof,
(Blob *)blob,
kzg_settings
);
free(commitments);
free(blobs);
if (ret != C_KZG_OK) {
Napi::Error::New(
env,
"verify_aggregate_kzg_proof failed with error code: " + std::to_string(ret)
).ThrowAsJavaScriptException();
return env.Null();
}
Napi::Error::New(env, "Error in computeBlobKzgProof")
.ThrowAsJavaScriptException();
return env.Undefined();
};
return Napi::Boolean::New(env, verification_result);
return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
}
// verifyKzgProof: (commitmentBytes: Bytes48, zBytes: Bytes32, yBytes: Bytes32, proofBytes: Bytes48, setupHandle: SetupHandle) => boolean;
@ -342,15 +258,148 @@ Napi::Value VerifyKzgProof(const Napi::CallbackInfo& info) {
return Napi::Boolean::New(env, out);
}
// verifyBlobKzgProof: (blob: Blob, commitmentBytes: Bytes48, proofBytes: Bytes48, setupHandle: SetupHandle) => boolean;
Napi::Value VerifyBlobKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env();
size_t argument_count = info.Length();
size_t expected_argument_count = 4;
if (argument_count != expected_argument_count) {
return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
}
auto 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()) {
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
);
if (ret != C_KZG_OK) {
Napi::TypeError::New(env, "Error in verifyBlobKzgProof").ThrowAsJavaScriptException();
return env.Null();
}
return Napi::Boolean::New(env, out);
}
// verifyBlobKzgProofBatch: (blobs: Blob[], commitmentsBytes: Bytes48[], proofsBytes: Bytes48[], setupHandle: SetupHandle) => boolean;
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();
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();
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);
}
bool out;
ret = verify_blob_kzg_proof_batch(
&out,
blobs,
commitments,
proofs,
blobs_count,
kzg_settings
);
if (ret != C_KZG_OK) {
Napi::TypeError::New(env, "Error in verifyBlobKzgProofBatch").ThrowAsJavaScriptException();
result = env.Null();
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["computeAggregateKzgProof"] = Napi::Function::New(env, ComputeAggregateKzgProof);
exports["verifyAggregateKzgProof"] = Napi::Function::New(env, VerifyAggregateKzgProof);
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);

View File

@ -33,17 +33,7 @@ type KZG = {
setupHandle: SetupHandle,
) => KZGProof;
computeAggregateKzgProof: (
blobs: Blob[],
setupHandle: SetupHandle,
) => KZGProof;
verifyAggregateKzgProof: (
blobs: Blob[],
commitmentsBytes: Bytes48[],
aggregatedProofBytes: Bytes48,
setupHandle: SetupHandle,
) => boolean;
computeBlobKzgProof: (blob: Blob, setupHandle: SetupHandle) => KZGProof;
verifyKzgProof: (
commitmentBytes: Bytes48,
@ -52,6 +42,20 @@ type KZG = {
proofBytes: Bytes48,
setupHandle: SetupHandle,
) => boolean;
verifyBlobKzgProof: (
blob: Blob,
commitmentBytes: Bytes48,
proofBytes: Bytes48,
setupHandle: SetupHandle,
) => boolean;
verifyBlobKzgProofBatch: (
blobs: Blob[],
commitmentsBytes: Bytes48[],
proofsBytes: Bytes48[],
setupHandle: SetupHandle,
) => boolean;
};
type TrustedSetupJSON = {
@ -78,10 +82,11 @@ function requireSetupHandle(): 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 UInt8Array of ${BYTES_PER_BLOB} bytes.`,
);
throw new Error(`Expected blob to be ${BYTES_PER_BLOB} bytes.`);
}
}
@ -92,10 +97,11 @@ function checkBlobs(blobs: 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 UInt8Array of ${BYTES_PER_COMMITMENT} bytes.`,
);
throw new Error(`Expected commitment to be ${BYTES_PER_COMMITMENT} bytes.`);
}
}
@ -106,17 +112,27 @@ function checkCommitments(commitments: KZGCommitment[]) {
}
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 UInt8Array of ${BYTES_PER_PROOF} bytes.`,
);
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 UInt8Array of ${BYTES_PER_FIELD_ELEMENT} bytes.`,
`Expected field element to be ${BYTES_PER_FIELD_ELEMENT} bytes.`,
);
}
}
@ -173,9 +189,9 @@ export function computeKzgProof(blob: Blob, zBytes: Bytes32): KZGProof {
return kzg.computeKzgProof(blob, zBytes, requireSetupHandle());
}
export function computeAggregateKzgProof(blobs: Blob[]): KZGProof {
checkBlobs(blobs);
return kzg.computeAggregateKzgProof(blobs, requireSetupHandle());
export function computeBlobKzgProof(blob: Blob): KZGProof {
checkBlob(blob);
return kzg.computeBlobKzgProof(blob, requireSetupHandle());
}
export function verifyKzgProof(
@ -197,18 +213,34 @@ export function verifyKzgProof(
);
}
export function verifyAggregateKzgProof(
blobs: Blob[],
commitmentsBytes: Bytes48[],
export function verifyBlobKzgProof(
blob: Blob,
commitmentBytes: Bytes48,
proofBytes: Bytes48,
): boolean {
checkBlobs(blobs);
checkCommitments(commitmentsBytes);
checkBlob(blob);
checkCommitment(commitmentBytes);
checkProof(proofBytes);
return kzg.verifyAggregateKzgProof(
blobs,
commitmentsBytes,
return kzg.verifyBlobKzgProof(
blob,
commitmentBytes,
proofBytes,
requireSetupHandle(),
);
}
export function verifyBlobKzgProofBatch(
blobs: Blob[],
commitmentsBytes: Bytes48[],
proofsBytes: Bytes48[],
): boolean {
checkBlobs(blobs);
checkCommitments(commitmentsBytes);
checkProofs(proofsBytes);
return kzg.verifyBlobKzgProofBatch(
blobs,
commitmentsBytes,
proofsBytes,
requireSetupHandle(),
);
}

View File

@ -1,14 +1,17 @@
import { randomBytes } from "crypto";
import { existsSync } from "fs";
import path = require("path");
import fs = require("fs");
import {
loadTrustedSetup,
freeTrustedSetup,
blobToKzgCommitment,
verifyKzgProof,
computeKzgProof,
computeAggregateKzgProof,
verifyAggregateKzgProof,
computeBlobKzgProof,
verifyKzgProof,
verifyBlobKzgProof,
verifyBlobKzgProofBatch,
BYTES_PER_BLOB,
BYTES_PER_COMMITMENT,
BYTES_PER_PROOF,
@ -24,6 +27,36 @@ const SETUP_FILE_PATH = existsSync(setupFileName)
const MAX_TOP_BYTE = 114;
const TEST_DIR = "../../tests";
const BLOB_TO_KZG_COMMITMENT_TESTS = path.join(
TEST_DIR,
"blob_to_kzg_commitment",
);
const COMPUTE_KZG_PROOF_TESTS = path.join(TEST_DIR, "compute_kzg_proof");
const COMPUTE_BLOB_KZG_PROOF_TESTS = path.join(
TEST_DIR,
"compute_blob_kzg_proof",
);
const VERIFY_KZG_PROOF_TESTS = path.join(TEST_DIR, "verify_kzg_proof");
const VERIFY_BLOB_KZG_PROOF_TESTS = path.join(
TEST_DIR,
"verify_blob_kzg_proof",
);
const VERIFY_BLOB_KZG_PROOF_BATCH_TESTS = path.join(
TEST_DIR,
"verify_blob_kzg_proof_batch",
);
function getBytes(file: String): Uint8Array {
const data = require("fs").readFileSync(file, "ascii");
return Buffer.from(data, "hex");
}
function getBoolean(file: String): boolean {
const data = require("fs").readFileSync(file, "ascii");
return data.includes("true");
}
const generateRandomBlob = () => {
return new Uint8Array(
randomBytes(BYTES_PER_BLOB).map((x, i) => {
@ -46,99 +79,241 @@ describe("C-KZG", () => {
freeTrustedSetup();
});
it("computes a proof from blob", () => {
let blob = generateRandomBlob();
const zBytes = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
computeKzgProof(blob, zBytes);
// No check, just make sure it doesn't crash.
describe("reference tests should pass", () => {
it("reference tests for blobToKzgCommitment should pass", () => {
let tests = fs.readdirSync(BLOB_TO_KZG_COMMITMENT_TESTS);
tests.forEach((test) => {
let testPath = path.join(BLOB_TO_KZG_COMMITMENT_TESTS, test);
let blob = getBytes(path.join(testPath, "blob.txt"));
try {
let commitment = blobToKzgCommitment(blob);
let expectedCommitment = getBytes(
path.join(testPath, "commitment.txt"),
);
expect(commitment.buffer).toEqual(expectedCommitment.buffer);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "commitment.txt"))).toBe(
false,
);
}
});
});
it("reference tests for computeKzgProof should pass", () => {
let tests = fs.readdirSync(COMPUTE_KZG_PROOF_TESTS);
tests.forEach((test) => {
let testPath = path.join(COMPUTE_KZG_PROOF_TESTS, test);
let blob = getBytes(path.join(testPath, "blob.txt"));
let inputPoint = getBytes(path.join(testPath, "input_point.txt"));
try {
let proof = computeKzgProof(blob, inputPoint);
let expectedProof = getBytes(path.join(testPath, "proof.txt"));
expect(proof.buffer).toEqual(expectedProof.buffer);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "proof.txt"))).toBe(false);
}
});
});
it("reference tests for computeBlobKzgProof should pass", () => {
let tests = fs.readdirSync(COMPUTE_BLOB_KZG_PROOF_TESTS);
tests.forEach((test) => {
let testPath = path.join(COMPUTE_BLOB_KZG_PROOF_TESTS, test);
let blob = getBytes(path.join(testPath, "blob.txt"));
try {
let proof = computeBlobKzgProof(blob);
let expectedProof = getBytes(path.join(testPath, "proof.txt"));
expect(proof.buffer).toEqual(expectedProof.buffer);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "proof.txt"))).toBe(false);
}
});
});
it("reference tests for verifyKzgProof should pass", () => {
let tests = fs.readdirSync(VERIFY_KZG_PROOF_TESTS);
tests.forEach((test) => {
let testPath = path.join(VERIFY_KZG_PROOF_TESTS, test);
let commitment = getBytes(path.join(testPath, "commitment.txt"));
let inputPoint = getBytes(path.join(testPath, "input_point.txt"));
let claimedValue = getBytes(path.join(testPath, "claimed_value.txt"));
let proof = getBytes(path.join(testPath, "proof.txt"));
try {
let ok = verifyKzgProof(commitment, inputPoint, claimedValue, proof);
let expectedOk = getBoolean(path.join(testPath, "ok.txt"));
expect(ok).toEqual(expectedOk);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "ok.txt"))).toBe(false);
}
});
});
it("reference tests for verifyBlobKzgProof should pass", () => {
let tests = fs.readdirSync(VERIFY_BLOB_KZG_PROOF_TESTS);
tests.forEach((test) => {
let testPath = path.join(VERIFY_BLOB_KZG_PROOF_TESTS, test);
let blob = getBytes(path.join(testPath, "blob.txt"));
let commitment = getBytes(path.join(testPath, "commitment.txt"));
let proof = getBytes(path.join(testPath, "proof.txt"));
try {
let ok = verifyBlobKzgProof(blob, commitment, proof);
let expectedOk = getBoolean(path.join(testPath, "ok.txt"));
expect(ok).toEqual(expectedOk);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "ok.txt"))).toBe(false);
}
});
});
it("reference tests for verifyBlobKzgProofBatch should pass", () => {
let tests = fs.readdirSync(VERIFY_BLOB_KZG_PROOF_BATCH_TESTS);
tests.forEach((test) => {
let testPath = path.join(VERIFY_BLOB_KZG_PROOF_BATCH_TESTS, test);
let blobs = fs
.readdirSync(path.join(testPath, "blobs"))
.sort()
.map((filename) => {
return path.join(testPath, "blobs", filename);
})
.map(getBytes);
let commitments = fs
.readdirSync(path.join(testPath, "commitments"))
.sort()
.map((filename) => {
return path.join(testPath, "commitments", filename);
})
.map(getBytes);
let proofs = fs
.readdirSync(path.join(testPath, "proofs"))
.sort()
.map((filename) => {
return path.join(testPath, "proofs", filename);
})
.map(getBytes);
try {
let ok = verifyBlobKzgProofBatch(blobs, commitments, proofs);
let expectedOk = getBoolean(path.join(testPath, "ok.txt"));
expect(ok).toEqual(expectedOk);
} catch (err) {
expect(fs.existsSync(path.join(testPath, "ok.txt"))).toBe(false);
}
});
});
});
it("computes the correct commitments and aggregate proof from blobs", () => {
let blobs = new Array(2).fill(0).map(generateRandomBlob);
let commitments = blobs.map(blobToKzgCommitment);
let proof = computeAggregateKzgProof(blobs);
expect(verifyAggregateKzgProof(blobs, commitments, proof)).toBe(true);
});
it("returns the identity (aka zero, aka neutral) element when blobs is an empty array", () => {
const aggregateProofOfNothing = computeAggregateKzgProof([]);
expect(aggregateProofOfNothing.toString()).toEqual(
[
192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
].toString(),
);
});
it("verifies the aggregate proof of empty blobs and commitments", () => {
expect(verifyAggregateKzgProof([], [], computeAggregateKzgProof([]))).toBe(
true,
);
});
it("verifies a valid KZG proof", () => {
const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0);
commitment[0] = 0xc0;
const z = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
const y = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
const proof = new Uint8Array(BYTES_PER_PROOF).fill(0);
proof[0] = 0xc0;
expect(verifyKzgProof(commitment, z, y, proof)).toBe(true);
});
it("verifies an invalid valid KZG proof", () => {
const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0);
commitment[0] = 0xc0;
const z = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(1);
const y = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(1);
const proof = new Uint8Array(BYTES_PER_PROOF).fill(0);
proof[0] = 0xc0;
expect(verifyKzgProof(commitment, z, y, proof)).toBe(false);
});
it("computes the aggregate proof when for a single blob", () => {
let blobs = new Array(1).fill(0).map(generateRandomBlob);
let commitments = blobs.map(blobToKzgCommitment);
let proof = computeAggregateKzgProof(blobs);
expect(verifyAggregateKzgProof(blobs, commitments, proof)).toBe(true);
});
it("fails when given incorrect commitments", () => {
const blobs = new Array(2).fill(0).map(generateRandomBlob);
const commitments = blobs.map(blobToKzgCommitment);
commitments[0][0] = commitments[0][0] === 0 ? 1 : 0; // Mutate the commitment
const proof = computeAggregateKzgProof(blobs);
expect(() =>
verifyAggregateKzgProof(blobs, commitments, proof),
).toThrowError("verify_aggregate_kzg_proof failed with error code: 1");
});
it("throws the expected error when given fewer commitments than blobs", () => {
let blobs = new Array(1).fill(0).map(generateRandomBlob);
let commitments = [] as Uint8Array[];
let proof = computeAggregateKzgProof(blobs);
expect(() =>
verifyAggregateKzgProof(blobs, commitments, proof),
).toThrowError(
"verifyAggregateKzgProof requires blobs count to match expectedKzgCommitments count",
);
});
describe("computing commitment from blobs", () => {
describe("edge cases for blobToKzgCommitment", () => {
it("throws as expected when given an argument of invalid type", () => {
// @ts-expect-error
expect(() => blobToKzgCommitment("wrong type")).toThrowError(
"Expected blob to be UInt8Array of 131072 bytes",
"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 UInt8Array of 131072 bytes");
).toThrowError("Expected blob to be 131072 bytes");
});
});
// TODO: add more tests for this function.
describe("edge cases for computeKzgProof", () => {
it("computes a proof from blob/field element", () => {
let blob = generateRandomBlob();
const zBytes = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
computeKzgProof(blob, zBytes);
});
});
// TODO: add more tests for this function.
describe("edge cases for computeBlobKzgProof", () => {
it("computes a proof from blob", () => {
let blob = generateRandomBlob();
computeBlobKzgProof(blob);
});
});
describe("edge cases for verifyKzgProof", () => {
it("valid proof should result in true", () => {
const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0);
commitment[0] = 0xc0;
const z = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
const y = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0);
const proof = new Uint8Array(BYTES_PER_PROOF).fill(0);
proof[0] = 0xc0;
expect(verifyKzgProof(commitment, z, y, proof)).toBe(true);
});
it("invalid proof should result in false", () => {
const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0);
commitment[0] = 0xc0;
const z = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(1);
const y = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(1);
const proof = new Uint8Array(BYTES_PER_PROOF).fill(0);
proof[0] = 0xc0;
expect(verifyKzgProof(commitment, z, y, proof)).toBe(false);
});
});
describe("edge cases for verifyBlobKzgProof", () => {
it("correct blob/commitment/proof should verify as true", () => {
let blob = generateRandomBlob();
let commitment = blobToKzgCommitment(blob);
let proof = computeBlobKzgProof(blob);
expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(true);
});
it("incorrect commitment should verify as false", () => {
let blob = generateRandomBlob();
let commitment = blobToKzgCommitment(generateRandomBlob());
let proof = computeBlobKzgProof(blob);
expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(false);
});
it("incorrect proof should verify as false", () => {
let blob = generateRandomBlob();
let commitment = blobToKzgCommitment(blob);
let proof = computeBlobKzgProof(generateRandomBlob());
expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(false);
});
});
describe("edge cases for verifyBlobKzgProofBatch", () => {
it("should reject non-bytearray blob", () => {
expect(() =>
// @ts-expect-error
verifyBlobKzgProofBatch(["foo", "bar"], [], []),
).toThrowError("Expected blob to be a UInt8Array");
});
it("zero blobs/commitments/proofs should verify as true", () => {
expect(verifyBlobKzgProofBatch([], [], [])).toBe(true);
});
it("mismatching blobs/commitments/proofs should throw error", () => {
let count = 3;
let blobs = new Array(count);
let commitments = new Array(count);
let proofs = new Array(count);
for (let [i, _] of blobs.entries()) {
blobs[i] = generateRandomBlob();
commitments[i] = blobToKzgCommitment(blobs[i]);
proofs[i] = computeBlobKzgProof(blobs[i]);
}
expect(verifyBlobKzgProofBatch(blobs, commitments, proofs)).toBe(true);
expect(() =>
verifyBlobKzgProofBatch(blobs.slice(0, 1), commitments, proofs),
).toThrowError("requires equal number of blobs/commitments/proofs");
expect(() =>
verifyBlobKzgProofBatch(blobs, commitments.slice(0, 1), proofs),
).toThrowError("requires equal number of blobs/commitments/proofs");
expect(() =>
verifyBlobKzgProofBatch(blobs, commitments, proofs.slice(0, 1)),
).toThrowError("requires equal number of blobs/commitments/proofs");
});
});
});