Chapter 13 · Platform
CompactSize versus varint, the name-derivation formula, and the reader/writer methods every class is built on. The bottom of the stack, and the place to look first when a parse goes wrong.
FIGURE 13.1 · Four layers, one direction.
Every fromBuffer in the library is the same six lines: construct a reader at an offset,
call the parent's fromBuffer, read fields in order, return reader.offset.
The methods you will actually use:
| Writer | Reader | Wire form |
|---|---|---|
writeUInt8 / 32 / 64 | readUInt8 / 32 / 64 | Little-endian fixed width |
writeCompactSize(n) | readCompactSize() | Bitcoin compactSize (see below) |
writeVarSlice(buf) | readVarSlice() | compactSize length, then bytes |
writeSlice(buf) | readSlice(n) | Raw bytes, no length prefix |
writeVector(bufs) | readVector() | compactSize count, then var slice each |
writeInt64 | readInt64() | Two's-complement 64-bit LE (used for fees) |
varuint.encode is Bitcoin's compactSize: one byte below 0xfd, then
0xfd + uint16, 0xfe + uint32, 0xff + uint64. Lengths and counts
use this.
0 → 00 252 → fc 253 → fd fd 00 (2-byte form, little-endian) 65535→ fd ff ff 65536→ fe 00 00 01 00
varint.encode is a different beast: a signed Bitcoin varint (7 bits per byte, with the
high-bit continuation and the −1 bias). It is used for values that can be negative, not for lengths.
Feeding a varint where a compactSize is expected shifts every subsequent offset, and the parse
succeeds with plausible garbage — the single most common way to "deserialise" nonsense.
A Verus name has no stored id; the id is derived. For a root name:
idHash = hash(name.toLowerCase()) // sha256d address = toBase58Check(hash160(idHash), I_ADDR_VERSION) for a child name: idHash = hash(name.toLowerCase()) idHash = hash(parentHash, idHash) // note the order: parent first address = toBase58Check(hash160(idHash), I_ADDR_VERSION)
fqnToAddress extends this to dotted FQNs and applies three normalisation rules:
components are lower-cased in the C locale, the root system name is appended when the FQN does not
already end with it, and an @ suffix is allowed only as the last segment. Because the
id depends on the parent, the same name under two systems produces two different addresses — that is
the intended collision resistance, not a bug.
| Function | Behaviour worth knowing |
|---|---|
fromBase58Check | Returns { version, hash }; throws on bad checksum or length |
decodeDestination | Validates not just the version but round-trips the re-encoded address, so case/format variants fail |
decodeEthDestination | Accepts optional 0x, requires exactly 40 hex characters |
getDataKey | Resolves namespace::name by building a parent id from :: under the namespace, then hashing the name under it |
toIAddress / toXAddress | Same derivation, different version byte (i- vs x-address) |
Bech32 with a deliberately large length limit (1000) because extended keys are long. The prefixes
matter: secret-extended-key-main / -test are checked explicitly, and
decodeSaplingAddress splits the payload into d and pk_d.
Round-tripping through encodeSaplingAddress is the sanity check.
script.compile(chunks) turns a chunk array into script bytes; decompile
turns bytes back into chunks; asMinimalOP and isPushOnly implement the
minimal-encoding rules. pushdata.ts owns the OP_PUSHDATA1/2/4 length encoding, and
ops.ts is the opcode table. VerusScript is a thin wrapper over these three.
hash.ts — hash(...) is sha256d over the concatenated buffers;
hash160 is RIPEMD160(sha256(x)). Name derivation uses both, in that order.KvMap.ts — keys stored by serialised form, with a logical collision check on
toIAddress(); sorting-free iteration order, so never rely on insertion order on the wire.mmr.ts — GetMMRProofIndex(pos, mmvSize, extraHashes) returns the sibling
indices for a proof; it is the arithmetic behind MMRProof.numberConversion.ts — bnToDecimal / decimalToBn exist so
amounts never pass through a JS float.IdentityData.ts — getFriendlyNameForVdxfKey plus the
IdentityVdxfidMap, for rendering a key as a name in UIs.evals.ts / cccustom.ts — evaluation codes and the well-known
cross-chain addresses (stake guard, PBaaS definition, notarization).types/ — SerializableEntityBase is the contract every class implements;
BigNumber is BN's interface, used everywhere instead of number.src/utils/ ├── bufferutils.ts # BufferReader / BufferWriter — the API table above ├── varuint.ts # compactSize: fc / fd fd / fe / ff ├── varint.ts # signed Bitcoin varint — not for lengths ├── address.ts # name → i-address derivation, decodeDestination, getDataKey ├── hash.ts # sha256d + hash160 ├── sapling.ts # bech32, extended keys ├── script.ts · pushdata.ts · ops.ts · reverseops.ts ├── KvMap.ts · mmr.ts · numberConversion.ts · IdentityData.ts ├── string.ts · tolower.ts └── types/ # SerializableEntityBase, BigNumber, ScriptChunk