From 21b3139dc891061ba70484459023eb4acf22c891 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Sat, 18 Feb 2023 15:49:52 -0600 Subject: [PATCH] Update nodejs bindings --- bindings/node.js/kzg.cxx | 242 +++++++++++++++------------- bindings/node.js/kzg.ts | 70 ++++++--- bindings/node.js/test.ts | 332 +++++++++++++++++++++++++++++---------- 3 files changed, 431 insertions(+), 213 deletions(-) diff --git a/bindings/node.js/kzg.cxx b/bindings/node.js/kzg.cxx index 7207944..5e0d098 100644 --- a/bindings/node.js/kzg.cxx +++ b/bindings/node.js/kzg.cxx @@ -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(); - auto kzg_settings = info[1].As>().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().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(); @@ -228,80 +183,33 @@ 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(); - auto commitments_param = info[1].As(); - auto proof_param = info[2].As(); - auto kzg_settings = info[3].As>().Data(); + auto blob = extract_byte_array_from_param(info, 0, "blob"); + auto kzg_settings = info[1].As>().Data(); - auto proof_bytes = proof_param.As().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(); - } - - 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().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().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 +250,133 @@ 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>().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(); + + 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(); + auto commitments_param = info[1].As(); + auto proofs_param = info[2].As(); + auto kzg_settings = info[3].As>().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(); + 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(commitments_count, sizeof(Bytes48)); + if (commitments == NULL) { + free(blobs); + Napi::Error::New(env, "Error while allocating memory for commitments").ThrowAsJavaScriptException(); + return env.Null(); + }; + + auto proofs = (Bytes48*)calloc(proofs_count, sizeof(Bytes48)); + if (proofs == NULL) { + free(blobs); + free(commitments); + Napi::Error::New(env, "Error while allocating memory for proofs").ThrowAsJavaScriptException(); + return env.Null(); + }; + + 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().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().Data(); + memcpy(&commitments[index], commitment_bytes, BYTES_PER_COMMITMENT); + + // Extract proof from parameter + Napi::Value proof = proofs_param[index]; + auto proof_bytes = proof.As().Data(); + memcpy(&proofs[index], proof_bytes, BYTES_PER_PROOF); + } + + bool out; + C_KZG_RET 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(); + return env.Null(); + } + + return Napi::Boolean::New(env, out); +} + 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); diff --git a/bindings/node.js/kzg.ts b/bindings/node.js/kzg.ts index c9b4e43..f489eb1 100644 --- a/bindings/node.js/kzg.ts +++ b/bindings/node.js/kzg.ts @@ -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 = { @@ -113,6 +117,12 @@ function checkProof(proof: KZGProof) { } } +function checkProofs(proofs: KZGProof[]) { + for (let proof of proofs) { + checkProof(proof); + } +} + function checkFieldElement(field: Bytes32) { if (field.length != BYTES_PER_FIELD_ELEMENT) { throw new Error( @@ -173,9 +183,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 +207,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(), + ); +} diff --git a/bindings/node.js/test.ts b/bindings/node.js/test.ts index c80bf87..a67dd4f 100644 --- a/bindings/node.js/test.ts +++ b/bindings/node.js/test.ts @@ -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,88 +79,129 @@ 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", () => { + it("blobToKzgCommitment", () => { + 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("computeKzgProof", () => { + 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("computeBlobKzgProof", () => { + 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("verifyKzgProof", () => { + 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("verifyBlobKzgProof", () => { + 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("verifyBlobKzgProofBatch", () => { + 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("blobToKzgCommitment", () => { it("throws as expected when given an argument of invalid type", () => { // @ts-expect-error expect(() => blobToKzgCommitment("wrong type")).toThrowError( @@ -141,4 +215,96 @@ describe("C-KZG", () => { ).toThrowError("Expected blob to be UInt8Array of 131072 bytes"); }); }); + + describe("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); + }); + }); + + describe("computeBlobKzgProof", () => { + it("computes a proof from blob", () => { + let blob = generateRandomBlob(); + computeBlobKzgProof(blob); + }); + }); + + describe("verifyKzgProof", () => { + it("valid 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("invalid 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); + }); + }); + + describe("verifyBlobKzgProof", () => { + it("correct blob/commitment/proof", () => { + let blob = generateRandomBlob(); + let commitment = blobToKzgCommitment(blob); + let proof = computeBlobKzgProof(blob); + expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(true); + }); + + it("incorrect commitment", () => { + let blob = generateRandomBlob(); + let commitment = blobToKzgCommitment(generateRandomBlob()); + let proof = computeBlobKzgProof(blob); + expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(false); + }); + + it("incorrect proof", () => { + let blob = generateRandomBlob(); + let commitment = blobToKzgCommitment(blob); + let proof = computeBlobKzgProof(generateRandomBlob()); + expect(verifyBlobKzgProof(blob, commitment, proof)).toBe(false); + }); + }); + + describe("verifyBlobKzgProofBatch", () => { + it("zero blobs/commitments/proofs", () => { + expect(verifyBlobKzgProofBatch([], [], [])).toBe(true); + }); + + it("mismatching blobs/commitments/proofs count", () => { + 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"); + }); + }); });