Update nodejs bindings

This commit is contained in:
Justin Traglia 2023-02-18 15:49:52 -06:00
parent 9a764de619
commit 21b3139dc8
3 changed files with 431 additions and 213 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); 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; // computeKzgProof: (blob: Blob, zBytes: Bytes32, setupHandle: SetupHandle) => KZGProof;
Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) { Napi::Value ComputeKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env(); 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); return napi_typed_array_from_bytes((uint8_t *)(&proof), BYTES_PER_PROOF, env);
} }
// verifyAggregateKzgProof: (blobs: Blob[], commitmentsBytes: Bytes48[], aggregatedProofBytes: Bytes48, setupHandle: SetupHandle) => boolean; // computeBlobKzgProof: (blob: Blob, setupHandle: SetupHandle) => KZGProof;
Napi::Value VerifyAggregateKzgProof(const Napi::CallbackInfo& info) { Napi::Value ComputeBlobKzgProof(const Napi::CallbackInfo& info) {
auto env = info.Env(); auto env = info.Env();
size_t argument_count = info.Length(); size_t argument_count = info.Length();
size_t expected_argument_count = 4; size_t expected_argument_count = 2;
if (argument_count != expected_argument_count) { if (argument_count != expected_argument_count) {
return throw_invalid_arguments_count(expected_argument_count, argument_count, env); return throw_invalid_arguments_count(expected_argument_count, argument_count, env);
} }
auto blobs_param = info[0].As<Napi::Array>(); auto blob = extract_byte_array_from_param(info, 0, "blob");
auto commitments_param = info[1].As<Napi::Array>(); auto kzg_settings = info[1].As<Napi::External<KZGSettings>>().Data();
auto proof_param = info[2].As<Napi::TypedArray>();
auto kzg_settings = info[3].As<Napi::External<KZGSettings>>().Data();
auto proof_bytes = proof_param.As<Napi::Uint8Array>().Data(); KZGProof proof;
auto blobs_count = blobs_param.Length(); C_KZG_RET ret = compute_blob_kzg_proof(
auto commitments_count = commitments_param.Length(); &proof,
(Blob *)blob,
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<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,
kzg_settings kzg_settings
); );
free(commitments);
free(blobs);
if (ret != C_KZG_OK) { if (ret != C_KZG_OK) {
Napi::Error::New( Napi::Error::New(env, "Error in computeBlobKzgProof")
env, .ThrowAsJavaScriptException();
"verify_aggregate_kzg_proof failed with error code: " + std::to_string(ret) return env.Undefined();
).ThrowAsJavaScriptException(); };
return env.Null();
}
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; // 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); 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();
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();
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<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;
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) { Napi::Object Init(Napi::Env env, Napi::Object exports) {
// Functions // Functions
exports["loadTrustedSetup"] = Napi::Function::New(env, LoadTrustedSetup); exports["loadTrustedSetup"] = Napi::Function::New(env, LoadTrustedSetup);
exports["freeTrustedSetup"] = Napi::Function::New(env, FreeTrustedSetup); exports["freeTrustedSetup"] = Napi::Function::New(env, FreeTrustedSetup);
exports["blobToKzgCommitment"] = Napi::Function::New(env, BlobToKzgCommitment); exports["blobToKzgCommitment"] = Napi::Function::New(env, BlobToKzgCommitment);
exports["computeKzgProof"] = Napi::Function::New(env, ComputeKzgProof); exports["computeKzgProof"] = Napi::Function::New(env, ComputeKzgProof);
exports["computeBlobKzgProof"] = Napi::Function::New(env, ComputeBlobKzgProof);
exports["verifyKzgProof"] = Napi::Function::New(env, VerifyKzgProof); exports["verifyKzgProof"] = Napi::Function::New(env, VerifyKzgProof);
exports["computeAggregateKzgProof"] = Napi::Function::New(env, ComputeAggregateKzgProof); exports["verifyBlobKzgProof"] = Napi::Function::New(env, VerifyBlobKzgProof);
exports["verifyAggregateKzgProof"] = Napi::Function::New(env, VerifyAggregateKzgProof); exports["verifyBlobKzgProofBatch"] = Napi::Function::New(env, VerifyBlobKzgProofBatch);
// Constants // Constants
exports["BYTES_PER_BLOB"] = Napi::Number::New(env, BYTES_PER_BLOB); exports["BYTES_PER_BLOB"] = Napi::Number::New(env, BYTES_PER_BLOB);

View File

@ -33,17 +33,7 @@ type KZG = {
setupHandle: SetupHandle, setupHandle: SetupHandle,
) => KZGProof; ) => KZGProof;
computeAggregateKzgProof: ( computeBlobKzgProof: (blob: Blob, setupHandle: SetupHandle) => KZGProof;
blobs: Blob[],
setupHandle: SetupHandle,
) => KZGProof;
verifyAggregateKzgProof: (
blobs: Blob[],
commitmentsBytes: Bytes48[],
aggregatedProofBytes: Bytes48,
setupHandle: SetupHandle,
) => boolean;
verifyKzgProof: ( verifyKzgProof: (
commitmentBytes: Bytes48, commitmentBytes: Bytes48,
@ -52,6 +42,20 @@ type KZG = {
proofBytes: Bytes48, proofBytes: Bytes48,
setupHandle: SetupHandle, setupHandle: SetupHandle,
) => boolean; ) => boolean;
verifyBlobKzgProof: (
blob: Blob,
commitmentBytes: Bytes48,
proofBytes: Bytes48,
setupHandle: SetupHandle,
) => boolean;
verifyBlobKzgProofBatch: (
blobs: Blob[],
commitmentsBytes: Bytes48[],
proofsBytes: Bytes48[],
setupHandle: SetupHandle,
) => boolean;
}; };
type TrustedSetupJSON = { 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) { function checkFieldElement(field: Bytes32) {
if (field.length != BYTES_PER_FIELD_ELEMENT) { if (field.length != BYTES_PER_FIELD_ELEMENT) {
throw new Error( throw new Error(
@ -173,9 +183,9 @@ export function computeKzgProof(blob: Blob, zBytes: Bytes32): KZGProof {
return kzg.computeKzgProof(blob, zBytes, requireSetupHandle()); return kzg.computeKzgProof(blob, zBytes, requireSetupHandle());
} }
export function computeAggregateKzgProof(blobs: Blob[]): KZGProof { export function computeBlobKzgProof(blob: Blob): KZGProof {
checkBlobs(blobs); checkBlob(blob);
return kzg.computeAggregateKzgProof(blobs, requireSetupHandle()); return kzg.computeBlobKzgProof(blob, requireSetupHandle());
} }
export function verifyKzgProof( export function verifyKzgProof(
@ -197,18 +207,34 @@ export function verifyKzgProof(
); );
} }
export function verifyAggregateKzgProof( export function verifyBlobKzgProof(
blobs: Blob[], blob: Blob,
commitmentsBytes: Bytes48[], commitmentBytes: Bytes48,
proofBytes: Bytes48, proofBytes: Bytes48,
): boolean { ): boolean {
checkBlobs(blobs); checkBlob(blob);
checkCommitments(commitmentsBytes); checkCommitment(commitmentBytes);
checkProof(proofBytes); checkProof(proofBytes);
return kzg.verifyAggregateKzgProof( return kzg.verifyBlobKzgProof(
blobs, blob,
commitmentsBytes, commitmentBytes,
proofBytes, proofBytes,
requireSetupHandle(), 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 { randomBytes } from "crypto";
import { existsSync } from "fs"; import { existsSync } from "fs";
import path = require("path");
import fs = require("fs");
import { import {
loadTrustedSetup, loadTrustedSetup,
freeTrustedSetup, freeTrustedSetup,
blobToKzgCommitment, blobToKzgCommitment,
verifyKzgProof,
computeKzgProof, computeKzgProof,
computeAggregateKzgProof, computeBlobKzgProof,
verifyAggregateKzgProof, verifyKzgProof,
verifyBlobKzgProof,
verifyBlobKzgProofBatch,
BYTES_PER_BLOB, BYTES_PER_BLOB,
BYTES_PER_COMMITMENT, BYTES_PER_COMMITMENT,
BYTES_PER_PROOF, BYTES_PER_PROOF,
@ -24,6 +27,36 @@ const SETUP_FILE_PATH = existsSync(setupFileName)
const MAX_TOP_BYTE = 114; 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 = () => { const generateRandomBlob = () => {
return new Uint8Array( return new Uint8Array(
randomBytes(BYTES_PER_BLOB).map((x, i) => { randomBytes(BYTES_PER_BLOB).map((x, i) => {
@ -46,88 +79,129 @@ describe("C-KZG", () => {
freeTrustedSetup(); freeTrustedSetup();
}); });
it("computes a proof from blob", () => { describe("reference tests", () => {
let blob = generateRandomBlob(); it("blobToKzgCommitment", () => {
const zBytes = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0); let tests = fs.readdirSync(BLOB_TO_KZG_COMMITMENT_TESTS);
computeKzgProof(blob, zBytes); tests.forEach((test) => {
// No check, just make sure it doesn't crash. 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", () => { describe("blobToKzgCommitment", () => {
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", () => {
it("throws as expected when given an argument of invalid type", () => { it("throws as expected when given an argument of invalid type", () => {
// @ts-expect-error // @ts-expect-error
expect(() => blobToKzgCommitment("wrong type")).toThrowError( expect(() => blobToKzgCommitment("wrong type")).toThrowError(
@ -141,4 +215,96 @@ describe("C-KZG", () => {
).toThrowError("Expected blob to be UInt8Array of 131072 bytes"); ).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");
});
});
}); });