:: lib::std::bytes::buffer
:: lib::std::bytes::sequence
:: lib::std::containers::collections
:: lib::std::cryptography::sha2
:: lib::std::encoding::binary
:: lib::std::text::strings;
export const stringv FILE_HEADER = "FLECHA-LEDGER\n1\n";
const stringv HASH_DOMAIN = "FLECHA-LEDGER-BLOCK-V1\n";
const usize RECORD_HEADER_SIZE = 48;
const usize DIGEST_SIZE = 32;
const usize RECORD_OVERHEAD = RECORD_HEADER_SIZE + DIGEST_SIZE;
export (buffer::Buffer, [32]u8, error) encode_block(
usize payloadLength = payload.len().cast<usize>;
usize capacity = RECORD_OVERHEAD + payloadLength;
if capacity < payloadLength or previousHash.len() != DIGEST_SIZE {
empty, allocationError := buffer::Buffer::withCapacity(0);
if allocationError != none {
return (empty, none, allocationError);
return (empty, none, model::LedgerError.PayloadTooLarge);
output, allocationError := buffer::Buffer::withCapacity(capacity);
if allocationError != none {
return (output, none, allocationError);
headerWriter := binary::Writer::init(header[:]);
error writeError = headerWriter.writeBig<u64>(index);
writeError = headerWriter.writeBig<u64>(payloadLength.cast<u64>);
writeError = headerWriter.write(previousHash);
return (output, none, writeError);
digest, digestError := hash_block(header[:], payload);
return (output, none, digestError);
[32]u8 digestBytes = model::copy_digest(digest.bytes());
writeError = output.write(header[:]);
writeError = output.write(payload.cast<[]const u8>);
writeError = output.write(digestBytes[:]);
return (output, digestBytes, writeError);
(sha2::Sha256Digest, error) hash_block(
hasher := sha2::Sha256::init();
error digestError = hasher.update(HASH_DOMAIN.cast<[]const u8>);
digestError = hasher.update(header);
digestError = hasher.update(payload.cast<[]const u8>);
return (none, digestError);
return hasher.finalize();
export (collections::Array<model::Block>, error) parse(stringv contents) {
blocks := collections::Array<model::Block>::init();
if contents.len() < FILE_HEADER.len() or
contents.substr(0, FILE_HEADER.len()) != FILE_HEADER {
return (blocks, model::LedgerError.InvalidFormat);
source := contents.cast<[]const u8>;
reader := binary::Reader::init(source[FILE_HEADER.len().cast<usize>:]);
[32]u8 expectedPrevious = none;
if reader.remaining() < RECORD_OVERHEAD {
return (blocks, model::LedgerError.InvalidFormat);
index, indexError := reader.readBig<u64>();
payloadLength, lengthError := reader.readBig<u64>();
if indexError != none or lengthError != none {
return (blocks, model::LedgerError.InvalidFormat);
[32]u8 previousValue = none;
previous, previousError := reader.read(DIGEST_SIZE);
if previousError != none {
return (blocks, model::LedgerError.InvalidFormat);
previousValue = model::copy_digest(previous);
if payloadLength > (reader.remaining() - DIGEST_SIZE).cast<u64> {
return (blocks, model::LedgerError.InvalidFormat);
payloadBytes, payloadError := reader.read(
payloadLength.cast<usize>
if payloadError != none {
return (blocks, model::LedgerError.InvalidFormat);
payload, allocationError = strings::string_from_bytes(payloadBytes);
if allocationError != none {
return (blocks, allocationError);
[32]u8 digestValue = none;
storedDigest, storedError := reader.read(DIGEST_SIZE);
return (blocks, model::LedgerError.InvalidFormat);
digestValue = model::copy_digest(storedDigest);
if index != expectedIndex or
!sequence::constant_time_equal(
return (blocks, model::LedgerError.IntegrityFailure);
[48]u8 canonicalHeader = none;
canonicalWriter := binary::Writer::init(canonicalHeader[:]);
error canonicalError = canonicalWriter.writeBig<u64>(index);
if canonicalError == none {
canonicalError = canonicalWriter.writeBig<u64>(payloadLength);
if canonicalError == none {
canonicalError = canonicalWriter.write(previousValue[:]);
if canonicalError != none {
return (blocks, canonicalError);
calculated, digestError := hash_block(
return (blocks, digestError);
if !sequence::constant_time_equal(calculated.bytes(), digestValue[:]) {
return (blocks, model::LedgerError.IntegrityFailure);
pushError := blocks.push(model::Block::init(
return (blocks, pushError);
expectedPrevious = digestValue;
if expectedIndex == 0 and !reader.empty() {
return (blocks, model::LedgerError.InvalidFormat);