Appendix B: Baseline Canonical Encoding (BCE) Specification
B.1 Type Encoding Table
| Type | Encoding | Byte Size | |
|---|---|---|---|
bool |
0x00 (false) or 0x01 (true) |
1 | |
u8 |
unsigned 8-bit integer | 1 | |
u16 |
unsigned 16-bit little-endian | 2 | |
u32 |
unsigned 32-bit little-endian | 4 | |
u64 |
unsigned 64-bit little-endian | 8 | |
u128 |
unsigned 128-bit little-endian | 16 | |
u256 |
unsigned 256-bit little-endian | 32 | |
bytes20 |
fixed 20-byte array (EVM address) | 20 | |
bytes32 |
fixed 32-byte array (Solana address) | 32 | |
string |
u32(length) + UTF-8 bytes |
4 + length | |
option<T> |
0x00 (None) \ |
0x01 + T (Some) |
1 + sizeof(T) |
array<T> |
u32(length) + T[] (elements) |
4 + length * sizeof(T) | |
map<K,V> |
u32(length) + sorted (K,V)[] pairs |
4 + length * (sizeof(K) + sizeof(V)) | |
fixedpoint64 |
signed 64-bit little-endian, implicit scale factor | 8 | |
null |
0xFF |
1 |
B.2 Fixed-Point Representation
BCE does not support IEEE 754 floating-point types. All real-valued quantities MUST be represented as fixedpoint64: a signed 64-bit integer with an implicit scale factor defined per field.
Why: IEEE 754 float64 introduces cross-implementation non-determinism — NaN bit patterns, rounding mode differences, denormalized number handling, and operation ordering all produce divergent byte representations for mathematically equivalent computations. Since voId = Keccak-256(BCE_ENCODE(...)), even a single bit difference causes replay failure.
Scale factor convention:
| Domain | Scale Factor | Precision | Range |
|---|---|---|---|
| SOL amounts | 10^9 (lamports) | 9 decimals | ±9.2 × 10^9 SOL |
| Token counts / integers | 10^0 (identity) | exact | ±9.2 × 10^18 |
| Ratios and percentages | 10^8 | 8 decimals | ±92,233,720,368 |
| Time (seconds) | 10^0 (identity) | exact | ±9.2 × 10^18 s |
| Rates (per second) | 10^12 | 12 decimals | ±9,223,372 |
| Standard deviation / scores | 10^8 | 8 decimals | ±92,233,720,368 |
Arithmetic rules for deterministic computation:
- All intermediate values MUST be computed in integer arithmetic (i64 or i128 for intermediates that may overflow i64).
- Division MUST use truncating integer division (round toward zero), not rounding.
std_devMUST be computed as:isqrt(sum((x_i - mean)^2 * SCALE^2) / n)whereisqrtis integer square root (floor).- If a denominator is zero, the result MUST be
0(not NaN or Infinity). - Scale factors are not stored in the encoding — they are defined by this specification per feature field.
Encoding: fixedpoint64 is encoded identically to a signed i64 little-endian (8 bytes). Decoders MUST apply the field's defined scale factor to recover the real value.
B.3 Object Encoding
Objects are encoded as sorted maps. Field keys are sorted lexicographically by UTF-8 byte order.
Example: ACCOUNT_BALANCE canonical form
Fields sorted: ["account", "balance", "mint", "slot"]
Encoding: map(4)
+ string("account") + bytes32(...)
+ string("balance") + u64(...)
+ string("mint") + bytes32(...)
+ string("slot") + u64(...)
B.4 Hash Computation
evidenceId = Keccak-256(canonicalForm)
voId = Keccak-256(BCE_ENCODE(VerificationObject_content_fields))