56 lines
1.8 KiB
JavaScript
56 lines
1.8 KiB
JavaScript
// For ESM compatibility
|
|
import pkg from 'js-sha3';
|
|
const {
|
|
keccak256
|
|
} = pkg;
|
|
import { BeeArgumentError } from "../utils/error.js";
|
|
import { keccak256Hash } from "../utils/hash.js";
|
|
const MAX_CHUNK_PAYLOAD_SIZE = 4096;
|
|
const SEGMENT_SIZE = 32;
|
|
const SEGMENT_PAIR_SIZE = 2 * SEGMENT_SIZE;
|
|
const HASH_SIZE = 32;
|
|
/**
|
|
* Calculate a Binary Merkle Tree hash for a chunk
|
|
*
|
|
* The BMT chunk address is the hash of the 8 byte span and the root
|
|
* hash of a binary Merkle tree (BMT) built on the 32-byte segments
|
|
* of the underlying data.
|
|
*
|
|
* If the chunk content is less than 4k, the hash is calculated as
|
|
* if the chunk was padded with all zeros up to 4096 bytes.
|
|
*
|
|
* @param chunkContent Chunk data including span and payload as well
|
|
*
|
|
* @returns the keccak256 hash in a byte array
|
|
*/
|
|
|
|
export function bmtHash(chunkContent) {
|
|
const span = chunkContent.slice(0, 8);
|
|
const payload = chunkContent.slice(8);
|
|
const rootHash = bmtRootHash(payload);
|
|
const chunkHashInput = new Uint8Array([...span, ...rootHash]);
|
|
const chunkHash = keccak256Hash(chunkHashInput);
|
|
return chunkHash;
|
|
}
|
|
|
|
function bmtRootHash(payload) {
|
|
if (payload.length > MAX_CHUNK_PAYLOAD_SIZE) {
|
|
throw new BeeArgumentError('invalid data length', payload);
|
|
} // create an input buffer padded with zeros
|
|
|
|
|
|
let input = new Uint8Array([...payload, ...new Uint8Array(MAX_CHUNK_PAYLOAD_SIZE - payload.length)]);
|
|
|
|
while (input.length !== HASH_SIZE) {
|
|
const output = new Uint8Array(input.length / 2); // in each round we hash the segment pairs together
|
|
|
|
for (let offset = 0; offset < input.length; offset += SEGMENT_PAIR_SIZE) {
|
|
const hashNumbers = keccak256.array(input.slice(offset, offset + SEGMENT_PAIR_SIZE));
|
|
output.set(hashNumbers, offset / 2);
|
|
}
|
|
|
|
input = output;
|
|
}
|
|
|
|
return input;
|
|
} |