bocRootHash function
Representation hash of the ROOT cell of a BoC — the bytes TON signs.
Implementation
Uint8List bocRootHash(Uint8List boc) {
if (boc.length < 10) throw _err('too short');
final magic = (boc[0] << 24) | (boc[1] << 16) | (boc[2] << 8) | boc[3];
if (magic != _bocMagic) throw _err('not a generic BoC');
final flags = boc[4];
final hasIdx = (flags >> 7) & 1;
final refSize = flags & 0x07;
final offSize = boc[5];
if (refSize == 0 || refSize > 4) throw _err('bad ref size');
if (offSize == 0 || offSize > 8) throw _err('bad offset size');
var pos = 6;
int readInt(int byteLen) {
var value = 0;
for (var i = 0; i < byteLen; i++) {
if (pos >= boc.length) throw _err('truncated header');
value = value * 256 + boc[pos++];
// Checked per step, not once at the end: the accumulator is monotonic
// so the rejection set is the same either way, and stopping here keeps
// a native 64-bit int from wrapping before the check can see it.
if (value > _maxSafeInteger) throw _err('header value out of range');
}
return value;
}
final cellCount = readInt(refSize);
final rootCount = readInt(refSize);
readInt(refSize); // absent count
readInt(offSize); // total cell data size
if (rootCount == 0) throw _err('no roots');
if (cellCount == 0 || cellCount > _maxCells) {
throw _err('cell count out of range');
}
final rootIndex = readInt(refSize);
for (var i = 1; i < rootCount; i++) {
readInt(refSize); // remaining root indices
}
if (rootIndex >= cellCount) throw _err('root index out of range');
if (hasIdx != 0) pos += cellCount * offSize; // skip the offsets index
final cells = <_Cell>[];
for (var i = 0; i < cellCount; i++) {
if (pos + 2 > boc.length) throw _err('truncated cell');
final d1 = boc[pos++];
final d2 = boc[pos++];
final refCount = d1 & 0x07;
if (refCount > _maxRefs) throw _err('too many references');
final dataByteLen = (d2 + 1) >> 1;
final incomplete = (d2 & 1) == 1;
if (dataByteLen > _maxCellDataBytes) throw _err('cell data too large');
if (pos + dataByteLen > boc.length) throw _err('truncated cell data');
final data = boc.sublist(pos, pos + dataByteLen);
pos += dataByteLen;
// Bit length from the completion tag (last set bit marks the end).
int dataBits;
if (incomplete && dataByteLen > 0) {
final last = data[dataByteLen - 1];
if (last == 0) {
dataBits = (dataByteLen - 1) * 8;
} else {
var trailingZeros = 0;
for (var b = 0; b < 8; b++) {
if (last & (1 << b) != 0) break;
trailingZeros++;
}
dataBits = dataByteLen * 8 - 1 - trailingZeros;
}
} else {
dataBits = dataByteLen * 8;
}
final refs = <int>[];
for (var r = 0; r < refCount; r++) {
final ref = readInt(refSize);
if (ref >= cellCount) throw _err('reference out of range');
if (ref <= i) throw _err('non-topological cell reference');
refs.add(ref);
}
cells.add(_Cell(dataBits: dataBits, data: data, refs: refs));
}
// Bottom-up (children first — guaranteed by the forward-only reference
// check).
for (var i = cellCount - 1; i >= 0; i--) {
final cell = cells[i];
var maxChildDepth = -1;
for (final r in cell.refs) {
if (cells[r].depth > maxChildDepth) maxChildDepth = cells[r].depth;
}
cell.depth = cell.refs.isEmpty ? 0 : maxChildDepth + 1;
final dataBytes = (cell.dataBits + 7) >> 3;
final incomplete = cell.dataBits % 8 != 0;
final repr = <int>[cell.refs.length, dataBytes * 2 - (incomplete ? 1 : 0)];
for (var b = 0; b < dataBytes; b++) {
repr.add(b < cell.data.length ? cell.data[b] : 0);
}
if (incomplete && dataBytes > 0) {
final shift = 7 - (cell.dataBits % 8);
final last = repr.length - 1;
repr[last] = (repr[last] | (1 << shift)) & (0xff << shift) & 0xff;
}
for (final r in cell.refs) {
repr
..add((cells[r].depth >> 8) & 0xff)
..add(cells[r].depth & 0xff);
}
var bytes = Uint8List.fromList(repr);
for (final r in cell.refs) {
bytes = concatBytes([bytes, cells[r].hash]);
}
cell.hash = sha256(bytes);
}
return cells[rootIndex].hash;
}