///|
pub(all) struct V2FileHash {
root : Bytes
piece_layer : Bytes
}
///|
fn[D : @crypto.ByteSource] sha256(data : D) -> Bytes {
Bytes::from_array(@crypto.sha256(data)[:])
}
///|
fn hash_pair(left : Bytes, right : Bytes) -> Bytes {
let pair = left.to_array()
append_bytes(pair, right)
sha256(Bytes::from_array(pair))
}
///|
fn zero_piece_hash(piece_length : Int32) -> Bytes {
let mut width = 16384
let mut zero = Bytes::make(32, 0)
while width < piece_length {
zero = hash_pair(zero, zero)
width *= 2
}
zero
}
///|
fn merkle_root(hashes : Array[Bytes], zero : Bytes) -> Bytes raise CrateError {
if hashes.is_empty() {
reject("merkle", "cannot hash an empty tree")
}
let nodes = hashes.copy()
let mut capacity = 1
while capacity < nodes.length() {
capacity *= 2
}
while nodes.length() < capacity {
nodes.push(zero)
}
let mut width = capacity
while width > 1 {
for i in 0..<(width / 2) {
nodes[i] = hash_pair(nodes[i * 2], nodes[i * 2 + 1])
}
width /= 2
}
nodes[0]
}
///|
fn split_hashes(data : Bytes) -> Array[Bytes] raise CrateError {
if data.length() % 32 != 0 {
reject("merkle", "SHA-256 layer length must be divisible by 32")
}
Array::makei(data.length() / 32, fn(i) { data[i * 32:i * 32 + 32].to_owned() })
}
///|
// Hash a file-local piece. A short final piece in a multi-piece file still
// occupies a full piece's Merkle width; a one-piece file does not.
fn hash_v2_piece(
length : Int64,
piece_length : Int32,
offset : Int64,
read : (Int64, Int32) -> Bytes raise CrateError,
) -> Bytes raise CrateError {
let end = if length - offset < piece_length.to_int64() {
length
} else {
offset + piece_length.to_int64()
}
let blocks : Array[Bytes] = []
let mut cursor = offset
while cursor < end {
// Read at most 1 MiB without crossing this piece. Merkle leaves remain
// exactly 16 KiB (except the final short block), independent of I/O size.
let size = if end - cursor < 1048576L {
(end - cursor).to_int()
} else {
1048576
}
let data = read(cursor, size)
if data.length() != size {
reject("read", "short or oversized v2 chunk read")
}
let mut block = 0
while block < size {
let block_end = if size - block < 16384 { size } else { block + 16384 }
blocks.push(sha256(data[block:block_end]))
block = block_end
}
cursor += size.to_int64()
}
if length > piece_length.to_int64() {
while blocks.length() < piece_length / 16384 {
blocks.push(Bytes::make(32, 0))
}
}
merkle_root(blocks, Bytes::make(32, 0))
}
///|
/// Hash 16 KiB blocks with BEP 52 zero-hash padding, buffering one piece's hashes.
/// Empty files have neither pieces root nor piece layer.
pub fn hash_v2_file(
length : Int64,
piece_length : Int32,
read : (Int64, Int32) -> Bytes raise CrateError,
) -> V2FileHash raise CrateError {
check_piece_length(piece_length)
if length < 0L ||
length > 9007199254740991L ||
(length + piece_length.to_int64() - 1L) / piece_length.to_int64() > 400000L {
reject("limit", "invalid file length or too many pieces")
}
if length == 0L {
return { root: b"", piece_layer: b"", }
}
let pieces : Array[Bytes] = []
let mut offset = 0L
while offset < length {
pieces.push(hash_v2_piece(length, piece_length, offset, read))
offset += piece_length.to_int64()
}
if length <= piece_length.to_int64() {
{ root: pieces[0], piece_layer: b"", }
} else {
let layer : Array[Byte] = []
for hash in pieces {
append_bytes(layer, hash)
}
{
root: merkle_root(pieces, zero_piece_hash(piece_length)),
piece_layer: Bytes::from_array(layer),
}
}
}
///|
pub fn validate_piece_layer(
root : Bytes,
length : Int64,
piece_length : Int32,
layer : Bytes,
) -> Unit raise CrateError {
check_piece_length(piece_length)
if length <= piece_length.to_int64() ||
length > 9007199254740991L ||
root.length() != 32 {
reject(
"merkle", "piece layer requires a nonempty multi-piece file and 32-byte root",
)
}
let count = (length + piece_length.to_int64() - 1L) / piece_length.to_int64()
if count > 400000L || layer.length().to_int64() != count * 32L {
reject("merkle", "piece layer count mismatch")
}
if merkle_root(split_hashes(layer), zero_piece_hash(piece_length)) != root {
reject("merkle", "piece layer does not reconstruct file root")
}
}