UNPKG

@ckbfs/api

Version:

SDK for CKBFS protocol on CKB

177 lines (151 loc) 6.76 kB
import { adler32 } from 'hash-wasm'; import ADLER32 from 'adler-32'; /** * Utility functions for Adler32 checksum generation and verification */ /** * Calculates Adler32 checksum for the provided data * @param data The data to calculate checksum for * @returns Promise resolving to the calculated checksum as a number */ export async function calculateChecksum(data: Uint8Array): Promise<number> { const checksumString = await adler32(data); const checksumBuffer = Buffer.from(checksumString, 'hex'); return checksumBuffer.readUInt32BE(); } /** * Updates an existing checksum with new data using proper rolling Adler-32 calculation * @param previousChecksum The existing checksum to update * @param newData The new data to add to the checksum * @returns Promise resolving to the updated checksum as a number */ export async function updateChecksum(previousChecksum: number, newData: Uint8Array): Promise<number> { // Extract a and b values from the previous checksum // In Adler-32, the checksum is composed of two 16-bit integers: a and b // The final checksum is (b << 16) | a const a = previousChecksum & 0xFFFF; const b = (previousChecksum >>> 16) & 0xFFFF; // Use the adler-32 package to calculate a proper rolling checksum // The package doesn't have a "resume" function, so we need to work with the underlying algorithm // Initialize with existing a and b values let adlerA = a; let adlerB = b; const MOD_ADLER = 65521; // Adler-32 modulo value // Process each byte of the new data for (let i = 0; i < newData.length; i++) { adlerA = (adlerA + newData[i]) % MOD_ADLER; adlerB = (adlerB + adlerA) % MOD_ADLER; } // Combine a and b to get the final checksum using standard Adler32 format // In Adler32, checksum = (b << 16) | a const updatedChecksum = ((adlerB << 16) | adlerA) >>> 0; // Use unsigned right shift to ensure uint32 console.log(`Updated checksum from ${previousChecksum} to ${updatedChecksum} for appended content`); return updatedChecksum; } /** * Verifies if a given checksum matches the expected checksum for the data * @param data The data to verify * @param expectedChecksum The expected checksum * @returns Promise resolving to a boolean indicating whether the checksum is valid */ export async function verifyChecksum(data: Uint8Array, expectedChecksum: number): Promise<boolean> { const calculatedChecksum = await calculateChecksum(data); return calculatedChecksum === expectedChecksum; } /** * Verifies the checksum of a CKBFS witness * @param witness The witness bytes * @param expectedChecksum The expected checksum * @param backlinks Optional backlinks to use for checksum verification * @returns Promise resolving to a boolean indicating whether the checksum is valid */ export async function verifyWitnessChecksum( witness: Uint8Array, expectedChecksum: number, backlinks: { checksum: number }[] = [] ): Promise<boolean> { // Extract the content bytes from the witness (skip the CKBFS header and version) const contentBytes = witness.slice(6); // If backlinks are provided, use the last backlink's checksum if (backlinks.length > 0) { const lastBacklink = backlinks[backlinks.length - 1]; const updatedChecksum = await updateChecksum(lastBacklink.checksum, contentBytes); return updatedChecksum === expectedChecksum; } // Otherwise, calculate checksum from scratch return verifyChecksum(contentBytes, expectedChecksum); } /** * Verifies the checksum of a CKBFS v3 witness * @param witness The v3 witness bytes * @param expectedChecksum The expected checksum * @param previousChecksum Optional previous checksum for chained verification * @returns Promise resolving to a boolean indicating whether the checksum is valid */ export async function verifyV3WitnessChecksum( witness: Uint8Array, expectedChecksum: number, previousChecksum?: number ): Promise<boolean> { // Check if this is a head witness (contains CKBFS header) const isHeadWitness = witness.length >= 50 && new TextDecoder().decode(witness.slice(0, 5)) === 'CKBFS' && witness[5] === 0x03; let contentBytes: Uint8Array; let extractedPreviousChecksum: number | undefined = previousChecksum; if (isHeadWitness) { // Head witness: extract content after backlink structure // Format: CKBFS(5) + version(1) + prevTxHash(32) + prevWitnessIndex(4) + prevChecksum(4) + nextIndex(4) + content contentBytes = witness.slice(50); // Extract previous checksum from head witness if not provided externally if (previousChecksum === undefined) { const prevChecksumBytes = witness.slice(42, 46); extractedPreviousChecksum = new DataView(prevChecksumBytes.buffer).getUint32(0, true); // little-endian } } else { // Continuation witness: extract content after next index // Format: nextIndex(4) + content contentBytes = witness.slice(4); } // If previous checksum is available (either provided or extracted), use it for rolling calculation if (extractedPreviousChecksum !== undefined && extractedPreviousChecksum !== 0) { const updatedChecksum = await updateChecksum(extractedPreviousChecksum, contentBytes); return updatedChecksum === expectedChecksum; } // Otherwise, calculate checksum from scratch return verifyChecksum(contentBytes, expectedChecksum); } /** * Verifies the checksum chain for multiple v3 witnesses * @param witnesses Array of v3 witness bytes * @param finalExpectedChecksum The expected final checksum * @param initialChecksum Optional initial checksum (for append operations) * @returns Promise resolving to a boolean indicating whether the checksum chain is valid */ export async function verifyV3WitnessChain( witnesses: Uint8Array[], finalExpectedChecksum: number, initialChecksum: number = 1 // Adler32 starts with 1 ): Promise<boolean> { if (witnesses.length === 0) { return finalExpectedChecksum === initialChecksum; } let currentChecksum = initialChecksum; // Process each witness in the chain for (const witness of witnesses) { const isHeadWitness = witness.length >= 50 && new TextDecoder().decode(witness.slice(0, 5)) === 'CKBFS' && witness[5] === 0x03; let contentBytes: Uint8Array; if (isHeadWitness) { // Head witness: extract content after backlink structure contentBytes = witness.slice(50); } else { // Continuation witness: extract content after next index contentBytes = witness.slice(4); } // Update checksum with this witness's content currentChecksum = await updateChecksum(currentChecksum, contentBytes); } return currentChecksum === finalExpectedChecksum; }