kuatia-types (AccountId string form), kuatia
(inflight)ADR-0012 gave AccountId an IBAN-style string form: two leading mod-97 check
digits and a 26-character base-36 body, 28 characters in total. The body encoded
both i64 legs at full width (a 128-bit Feistel permutation, then 13 base-36
characters per 64-bit half). 28 characters is long to read, transcribe, or speak,
and it does not group evenly, so the presentation form was more awkward than an
IBAN or a card number.
The length is driven by encoding two full 64-bit values. Can the code be materially shorter while keeping the checksum, the obfuscation, and round-trip parsing?
ToBytes, serde, the SQL schema, and every content hash keep the two full
i64 legs, so there is no migration.Pack the base id (63 bits, since a snowflake never sets the sign bit) and the
subaccount (30 bits) into one 93-bit value, permute it, and base-36 encode it in
18 characters, then append two mod-97 check digits. 36^18 > 2^93, so 18
characters always fit. Total 20 characters, five groups of four.
Pros:
i64
legs), so there is no migration, exactly as in ADR-0012.Cons:
id
beyond 63 bits or a sub beyond 30 bits truncates rather than round-trips. All
real ids (snowflake id, masked inflight sub, small explicit buckets) are in
range.Leave the ADR-0012 form unchanged.
Pros:
(i64, i64) space and needs no change.Cons:
Encode only the id leg when sub == 0 (about 15 characters) and both legs
otherwise (about 21).
Pros:
Cons:
Chosen option: Option 1, a fixed 20-character code, because it is short, groups into a uniform five-by-four, keeps the checksum and the obfuscation, and stays presentation-only with no migration. The only real cost is capping the subaccount at 30 bits, which is ample for explicit buckets and acceptable for the hash-derived inflight subaccounts.
ID_BITS = 63 and SUB_BITS = 30 (93 bits packed) are public on
kuatia-types. pack places the low ID_BITS of id above the low
SUB_BITS of sub; unpack inverts it.2^93 - 1). Since
the domain is half of 2^94, this averages about two iterations and is a
bijection on exactly the packable values. The seed is still the global
set_id_seed key.FromStr strips spaces and dashes, upper-cases,
requires 20 characters, validates the checksum, rejects a decoded body outside
the 93-bit domain, then inverts the permutation and unpacks.AccountId { id: 5, sub: 7 } renders
FK9RA6QALU15JZ7DZM81 (grouped FK9R A6QA LU15 JZ7D ZM81).The per-destination inflight hold subaccount (ADR-0014) was a 63-bit truncation
of a trade hash. It is now masked to the low SUB_BITS so every hold has an
encodable code. It stays deterministic and trade-specific.
id / id.sub).i64.
Explicit buckets are unaffected; hash-derived inflight subaccounts collide
sooner: at 30 bits the birthday bound is roughly a 1% chance around 4,600
concurrent inflight trades to a single destination. Deployments with very high
concurrent inflight fan-in to one destination should account for this.