Chapter 12 · Platform
Identities, content multimaps, destinations and signatures — with the wire formats, version constants and flag tables you need to read an identity byte by byte.
FIGURE 12.1 · Identity and the objects it is made of.
Principal is what an identity shares with any multi-signature authority: a version, a
flags word, a list of primary addresses, and a minimum signature count. Its serialiser is short
enough to memorise, and it is the template every Identity read follows:
toBuffer(): if containsVersion() writeUInt32(version) // 4 bytes LE if containsFlags() writeUInt32(flags) // 4 bytes LE if containsPrimaryAddresses() writeVector(addresses) // compactSize count, then var-slice each if containsMinSigs() writeUInt32(minSigs) // 4 bytes LE
The containsX() hooks are protected methods that subclasses override. That is the whole
inheritance trick: PartialIdentity turns some of them off and serialises a
contains bitmask instead of assuming every field is present.
| Constant | Value | Meaning |
|---|---|---|
VERSION_VERUSID | 1 | The original VerusID body |
VERSION_VAULT | 2 | Vault form |
VERSION_PBAAS | 3 | Current — system-aware identity |
Flags are hex constants on the identity body itself:
| Flag | Hex | Effect |
|---|---|---|
IDENTITY_FLAG_REVOKED | 0x8000 | Identity is revoked |
IDENTITY_FLAG_LOCKED | 0x2 | Identity is locked against updates |
IDENTITY_FLAG_ACTIVECURRENCY | 0x1 | Identity is in use as a currency name |
IDENTITY_FLAG_TOKENIZED_CONTROL | 0x4 | Revocation/recovery can be performed by whoever controls the token |
The rest of Identity is nine fields, in serialisation order: parent,
systemId, name, contentMap, contentMultiMap,
revocationAuthority, recoveryAuthority,
privateAddresses, unlockAfter. Note the change in this revision: snake_case
property names now throw with a rename hint rather than silently serialising zeros.
This is where identity data actually lives, and its layout is simple enough to debug by hand:
compactSize(keyCount)
repeat keyCount:
20 bytes key hash160 (i-address payload)
compactSize(valueCount)
repeat valueCount:
varSlice either raw bytes, or a VdxfUniValue blob with its own type tag
Two rules sit on top of that layout. KvMap stores keys by their serialised form, so a
TYPE_FQN key and a TYPE_I_ADDRESS key that resolve to the same i-address are considered a collision
and set() throws. And PartialIdentity upgrades any plain
ContentMultiMap to FqnContentMultiMap on construction, so FQN keys survive a
binary round-trip instead of collapsing to addresses.
Instead of "here is a whole identity", a partial identity says which fields participate:
| Bit | Constant | Field carried |
|---|---|---|
| 1 | PARTIAL_ID_CONTAINS_PARENT | parent |
| 2 | ...CONTENT_MULTIMAP | content multimap |
| 4 | ...PRIMARY_ADDRS | primary addresses |
| 8 | ...REVOCATION | revocation authority |
| 16 | ...RECOVERY | recovery authority |
| 32 | ...UNLOCK_AFTER | unlock height |
| 64 | ...SYSTEM_ID | system |
| 128 | ...PRIV_ADDRS | private addresses |
| 256 | ...CONTENT_MAP | legacy content map hashes |
| 512 | ...MINSIGS | minimum signatures |
| 1024 | ...FLAGS | identity flags |
| 2048 | ...VERSION | version |
This is why an identity update can change one field without restating the others — and why a mismatched bitmask is the classic cause of an update that "applies" but changes nothing.
Types 0–11: DEST_INVALID, PK, PKH, SH,
ID, FULLID, REGISTERCURRENCY, QUANTUM,
NESTEDTRANSFER, ETH, ETHNFT, RAW. Above
LAST_VALID_TYPE_NO_FLAGS the same byte also carries FLAG_RESERVED1 (16),
FLAG_RESERVED2 (32), FLAG_DEST_AUX (64) and FLAG_DEST_GATEWAY
(128); typeNoFlags() masks them off.
writeUInt8(type) // type | flags writeVarSlice(destinationBytes) if gateway: gatewayID(20) · gatewayCode(20) · fees(int64) if aux: compactSize(n) · varSlice(each aux dest)
getAddressString() only supports PKH, ID and ETH — anything else throws, which is a
useful signal that you are holding a destination that needs a gateway rather than an address.
SignatureData is the DER container with its own context: version, systemID,
hashType, signatureHash, identityID, sigType,
optional vdxfKeys, vdxfKeyNames, boundHashes, and
signatureAsVch. It is what VerifiableSignatureData wraps — read it when a
signature parses but fails verification.
DataDescriptor describes stored data with an eight-bit presence mask:
1 encrypted data · 2 salt · 4 encryption public key · 8 incoming viewing key 0x10 symmetric key · 0x20 label · 0x40 mime type · 0x80 vdxf key
Content multimap values can be any of: raw buffer, string, CurrencyValueMap,
Rating, TransferDestination, ContentMultiMapRemove,
CrossChainDataRef, SignatureData, DataDescriptor,
MMRDescriptor, URLRef, IdentityMultimapRef,
Credential. VdxfUniValue is the union that can hold any of them, with JSON
escape hatches (serializedhex, serializedbase64, message) for
payloads it does not model.
MMR.ts holds MerkleMountainRange, MMRLayer,
MMRNode, MMRBranch, MMRProof and
MerkleMountainView. You need them when verifying that an identity's data at some height
matches a commitment — the proof structure, not the payload.
src/pbaas/ ├── Principal.ts # version, flags, addresses, minSigs — the serialisation template ├── Identity.ts # nine more fields + identity flags ├── PartialIdentity.ts # bitmask of participating fields ├── IdentityID.ts / KeyID.ts ├── ContentMultiMap.ts # count · 20-byte keys · arrays of var slices ├── VdxfUniValue.ts # the 13-type union + JSON escape hatches ├── TransferDestination.ts # 12 types, 4 high-bit flags, gateway extension ├── NoDestination.ts / SaplingPaymentAddress.ts ├── SignatureData.ts # DER + context ├── DataDescriptor.ts # 8-bit presence mask ├── MMR.ts / MMRDescriptor.ts / CrossChainProof.ts └── transaction/*.ts