From 8aa78231f23ac88ab7b62f1196af7f1766dc2131 Mon Sep 17 00:00:00 2001 From: Justin Traglia <95511699+jtraglia@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:11:10 -0600 Subject: [PATCH] Check UInt8Array lengths (#126) --- bindings/node.js/kzg.cxx | 5 +++- bindings/node.js/kzg.ts | 65 ++++++++++++++++++++++++++++++++++++++-- bindings/node.js/test.ts | 34 ++++++++++++--------- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/bindings/node.js/kzg.cxx b/bindings/node.js/kzg.cxx index 0d3e578..7207944 100644 --- a/bindings/node.js/kzg.cxx +++ b/bindings/node.js/kzg.cxx @@ -353,8 +353,11 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { exports["verifyAggregateKzgProof"] = Napi::Function::New(env, VerifyAggregateKzgProof); // Constants - exports["FIELD_ELEMENTS_PER_BLOB"] = Napi::Number::New(env, FIELD_ELEMENTS_PER_BLOB); + exports["BYTES_PER_BLOB"] = Napi::Number::New(env, BYTES_PER_BLOB); + exports["BYTES_PER_COMMITMENT"] = Napi::Number::New(env, BYTES_PER_COMMITMENT); exports["BYTES_PER_FIELD_ELEMENT"] = Napi::Number::New(env, BYTES_PER_FIELD_ELEMENT); + exports["BYTES_PER_PROOF"] = Napi::Number::New(env, BYTES_PER_PROOF); + exports["FIELD_ELEMENTS_PER_BLOB"] = Napi::Number::New(env, FIELD_ELEMENTS_PER_BLOB); return exports; } diff --git a/bindings/node.js/kzg.ts b/bindings/node.js/kzg.ts index 2607248..c9b4e43 100644 --- a/bindings/node.js/kzg.ts +++ b/bindings/node.js/kzg.ts @@ -15,8 +15,11 @@ type SetupHandle = Object; // The C++ native addon interface type KZG = { - FIELD_ELEMENTS_PER_BLOB: number; + BYTES_PER_BLOB: number; + BYTES_PER_COMMITMENT: number; BYTES_PER_FIELD_ELEMENT: number; + BYTES_PER_PROOF: number; + FIELD_ELEMENTS_PER_BLOB: number; loadTrustedSetup: (filePath: string) => SetupHandle; @@ -58,8 +61,11 @@ type TrustedSetupJSON = { roots_of_unity: string[]; }; -export const FIELD_ELEMENTS_PER_BLOB = kzg.FIELD_ELEMENTS_PER_BLOB; +export const BYTES_PER_BLOB = kzg.BYTES_PER_BLOB; +export const BYTES_PER_COMMITMENT = kzg.BYTES_PER_COMMITMENT; export const BYTES_PER_FIELD_ELEMENT = kzg.BYTES_PER_FIELD_ELEMENT; +export const BYTES_PER_PROOF = kzg.BYTES_PER_PROOF; +export const FIELD_ELEMENTS_PER_BLOB = kzg.FIELD_ELEMENTS_PER_BLOB; // Stored as internal state let setupHandle: SetupHandle | undefined; @@ -71,6 +77,50 @@ function requireSetupHandle(): SetupHandle { return setupHandle; } +function checkBlob(blob: Blob) { + if (blob.length != BYTES_PER_BLOB) { + throw new Error( + `Expected blob to be UInt8Array of ${BYTES_PER_BLOB} bytes.`, + ); + } +} + +function checkBlobs(blobs: Blob[]) { + for (let blob of blobs) { + checkBlob(blob); + } +} + +function checkCommitment(commitment: KZGCommitment) { + if (commitment.length != BYTES_PER_COMMITMENT) { + throw new Error( + `Expected commitment to be UInt8Array of ${BYTES_PER_COMMITMENT} bytes.`, + ); + } +} + +function checkCommitments(commitments: KZGCommitment[]) { + for (let commitment of commitments) { + checkCommitment(commitment); + } +} + +function checkProof(proof: KZGProof) { + if (proof.length != BYTES_PER_PROOF) { + throw new Error( + `Expected proof to be UInt8Array of ${BYTES_PER_PROOF} bytes.`, + ); + } +} + +function checkFieldElement(field: Bytes32) { + if (field.length != BYTES_PER_FIELD_ELEMENT) { + throw new Error( + `Expected field element to be UInt8Array of ${BYTES_PER_FIELD_ELEMENT} bytes.`, + ); + } +} + export async function transformTrustedSetupJSON( filePath: string, ): Promise { @@ -113,14 +163,18 @@ export function freeTrustedSetup(): void { } export function blobToKzgCommitment(blob: Blob): KZGCommitment { + checkBlob(blob); return kzg.blobToKzgCommitment(blob, requireSetupHandle()); } export function computeKzgProof(blob: Blob, zBytes: Bytes32): KZGProof { + checkBlob(blob); + checkFieldElement(zBytes); return kzg.computeKzgProof(blob, zBytes, requireSetupHandle()); } export function computeAggregateKzgProof(blobs: Blob[]): KZGProof { + checkBlobs(blobs); return kzg.computeAggregateKzgProof(blobs, requireSetupHandle()); } @@ -130,6 +184,10 @@ export function verifyKzgProof( yBytes: Bytes32, proofBytes: Bytes48, ): boolean { + checkCommitment(commitmentBytes); + checkFieldElement(zBytes); + checkFieldElement(yBytes); + checkProof(proofBytes); return kzg.verifyKzgProof( commitmentBytes, zBytes, @@ -144,6 +202,9 @@ export function verifyAggregateKzgProof( commitmentsBytes: Bytes48[], proofBytes: Bytes48, ): boolean { + checkBlobs(blobs); + checkCommitments(commitmentsBytes); + checkProof(proofBytes); return kzg.verifyAggregateKzgProof( blobs, commitmentsBytes, diff --git a/bindings/node.js/test.ts b/bindings/node.js/test.ts index bdae8f8..c80bf87 100644 --- a/bindings/node.js/test.ts +++ b/bindings/node.js/test.ts @@ -9,8 +9,10 @@ import { computeKzgProof, computeAggregateKzgProof, verifyAggregateKzgProof, + BYTES_PER_BLOB, + BYTES_PER_COMMITMENT, + BYTES_PER_PROOF, BYTES_PER_FIELD_ELEMENT, - FIELD_ELEMENTS_PER_BLOB, transformTrustedSetupJSON, } from "./kzg"; @@ -20,13 +22,11 @@ const SETUP_FILE_PATH = existsSync(setupFileName) ? setupFileName : `../../src/${setupFileName}`; -const BLOB_BYTE_COUNT = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT; - const MAX_TOP_BYTE = 114; const generateRandomBlob = () => { return new Uint8Array( - randomBytes(BLOB_BYTE_COUNT).map((x, i) => { + randomBytes(BYTES_PER_BLOB).map((x, i) => { // Set the top byte to be low enough that the field element doesn't overflow the BLS modulus if (x > MAX_TOP_BYTE && i % BYTES_PER_FIELD_ELEMENT == 31) { return Math.floor(Math.random() * MAX_TOP_BYTE); @@ -48,7 +48,7 @@ describe("C-KZG", () => { it("computes a proof from blob", () => { let blob = generateRandomBlob(); - const zBytes = new Uint8Array(32).fill(0); + const zBytes = new Uint8Array(BYTES_PER_FIELD_ELEMENT).fill(0); computeKzgProof(blob, zBytes); // No check, just make sure it doesn't crash. }); @@ -78,22 +78,22 @@ describe("C-KZG", () => { }); it("verifies a valid KZG proof", () => { - const commitment = new Uint8Array(48).fill(0); + const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0); commitment[0] = 0xc0; - const z = new Uint8Array(32).fill(0); - const y = new Uint8Array(32).fill(0); - const proof = new Uint8Array(48).fill(0); + 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(48).fill(0); + const commitment = new Uint8Array(BYTES_PER_COMMITMENT).fill(0); commitment[0] = 0xc0; - const z = new Uint8Array(32).fill(1); - const y = new Uint8Array(32).fill(1); - const proof = new Uint8Array(48).fill(0); + 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); @@ -131,8 +131,14 @@ describe("C-KZG", () => { it("throws as expected when given an argument of invalid type", () => { // @ts-expect-error expect(() => blobToKzgCommitment("wrong type")).toThrowError( - "Invalid argument type: blob. Expected UInt8Array", + "Expected blob to be UInt8Array of 131072 bytes", ); }); + + 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"); + }); }); });