Core concepts · Explanation
Protocol primitives and canonical data
A Singularity transaction names accounts, declares authority and signs an ordered set of instructions. Execution turns those instructions into account changes and receipts. Consensus depends on exact binary representations at each boundary: JSON presentation, loaded account keys and local caches cannot silently become part of the signed or hashed identity.
For builders, the practical rule is to use a supported SDK's transaction codec and preserve its signed bytes. For runtime engineers, this chapter explains what must remain identical across implementations. To experience the signature boundary first, build a message and change one signed amount; return here for the account, wire and execution identities behind that result.
Accounts and authority
An account is keyed by a 32-byte Pubkey. Its value contains lamports: u64, arbitrary data bytes, a program-owner key, an executable flag and rent_epoch: u64. Base58 is the human-readable representation of the key, not a separate identity. These primitive types are shared independently of the runtime's transaction and verification code. Account model, primitive crate boundary.
Three permissions answer different questions:
| Property | Meaning |
|---|---|
| Transaction signer | The signed message authorizes an action using that key |
| Writable account declaration | The transaction grants access to change the named account |
| Account owner | The runtime program authorized to govern the account's data and applicable transitions |
A writable account is not automatically spendable by every called program. Cross-VM child calls receive a bounded subset of the parent's capabilities, and final account changes must satisfy the relevant owner and balance rules. See shared state and cross-VM composition.
Program-derived addresses (PDAs) avoid using an ordinary private key for program-controlled accounts. Derivation hashes the seeds, program ID and literal ProgramDerivedAddress; a digest that decompresses to an Ed25519 point is rejected. Seeds are bounded to 32 bytes each and 16 in the final derivation. Canonical bump search tries 255 down to 0, reserving one seed position for the bump. This is why an application must use the correct seeds and program ID rather than inventing an address that merely looks deterministic. PDA derivation.
An instruction becomes a message
An uncompiled instruction carries its program ID, ordered account metadata and program-specific bytes. A compiled instruction replaces the keys with one-byte indices into the message's key list. Each program still owns its payload schema: using Borsh for one program does not make another program's wire format Borsh.
The message contains three header counts, the ordered account keys, a 32-byte recent blockhash and the ordered compiled instructions. Static keys are grouped as:
writable signers → readonly signers → writable non-signers → readonly non-signersThe first key is the fee payer. Required signatures correspond to the first num_required_signatures keys. Moving a key without recompiling instruction indices changes which account the instruction addresses. Instruction forms, message and privilege model.
For a simple transfer, the wallet builds System-program instruction bytes, declares the sender as a writable signer and the recipient writable, selects a recent blockhash and signs the compiled message. The quickstart demonstrates construction and verification; the transaction lifecycle explains admission, fees and execution.
Signed wire bytes and execution-only keys
Explore the transaction boundaries.
The preferred public transport is canonical Solana legacy or v0 wire encoding:
compact-u16 signature_count
signature_count × 64-byte signatures
exact signed message bytesLegacy messages begin with the header. Version 0 prefixes it with 0x80 and can include address-lookup-table descriptors. The runtime retains the exact signed message. Re-serializing that message as a different format would invalidate the signature.
Strict decoding checks compact lengths, signature/header agreement, a writable signer/payer, header bounds, instruction indices, at most 256 addressable keys and supported versions. A v0 program ID must be among static keys. Empty lookup descriptors and malformed/trailing message data are rejected. Canonical decoder and sanitizer.
A v0 signature covers lookup descriptors, not the account keys later loaded from state. Resolution appends all loaded writable keys, then all loaded readonly keys. Newly appended table addresses are not visible during the slot in which the table was extended. Immediately before execution, the runtime rebuilds the transaction from its signed public bytes and reloads its tables from authoritative state. A failed reload leaves the canonical static-key form rather than retaining caller-supplied loaded keys. Lookup visibility and ordering, execution-boundary reconstruction.
Historical transport and transaction identity
Historical native transactions use Borsh and sign the Borsh message. Their envelope begins with a little-endian u32 signature count. The decoder attempts canonical Solana first, then Borsh; the fallback still applies header/index sanitation and exact signature-count checks. The retired ETOV0TX1 envelope is explicitly rejected, because it transported resolved keys outside the signed message. Dual decoder.
All consensus signature paths use the same ZIP-215 acceptance rule. The transaction's raw_borsh field is only a retained serialization cache, excluded from equality and serialized identity.
Keep these identifiers separate:
| Identifier | Construction and use |
|---|---|
| First transaction signature | 64 bytes; used for Solana-style lookup and displayed in base58 |
| Runtime transaction hash | SHA-256 of canonical signed public transaction bytes, or the native Borsh transaction |
| Raw EVM transaction hash | Keccak hash of the Ethereum transaction envelope; see EVM |
| Block digest | Hash of the consensus block representation |
Loaded lookup-table keys cannot change the runtime transaction hash. Signature and identity methods.
The public decoder ceiling is 4 MiB. Operational admission defaults to 1 MiB through SVM_MAX_TX_BYTES, clamped to that ceiling and resolved once per process. TCP, UDP and RPC use the shared cap. Replay decoding deliberately does not use a potentially lowered admission cap: an operator changing a limit must not make already-admitted historical bytes undecodable. Size policy.
Blocks commit to ordered input
SimplexBlock contains parent view, parent digest, height, ordered payload and recorded timestamp. Its SHA-256 digest covers the little-endian parent view, parent digest, height, a u64 payload length and payload. A nonzero timestamp is appended to the hash input. Zero means “unrecorded” and preserves historical digest behavior. Do not derive this digest from the network codec or JSON: those are different representations. Block digest.
The proposer records time once. Validators check it deterministically against the parent-derived floor: time cannot move backward and cannot advance more than 86,400 seconds in one block. They do not sample their own clocks while executing the same block. This supports shared deadline semantics, although the permitted window is not a promise of precise wall-clock accuracy. Timestamp policy.
Ordinary proposal payloads contain transaction bodies. Optional SVM_TX_REF_GOSSIP=1 uses ETOREFV2 framing with bodies or signature references. Reference resolution currently depends on each node's local verified transaction cache; missing-body repair is not implemented. Best-effort forwarding helps fill the cache but does not guarantee reconstruction. Therefore the smaller payload format has an availability tradeoff and remains off by default. Reference payload contract.
Execution outputs and version boundaries
State changes distinguish full account replacement, lamport-only replacement and explicit deletion. A deletion is a tombstone, not permission to fall through to an older snapshot. The compact Borsh forms use 41 bytes for a lamport-only change and 33 for deletion. Those are record sizes, not network-throughput measurements. State diffs.
Receipts are execution products. Their persistence codec keeps a stable bincode base and appends an identified E484 extension with version and length for blob-related metadata. Normal readers skip unknown extension versions and leave those fields at defaults; strict readers reject them. Malformed lengths are errors. Receipt metadata support alone does not implement Ethereum blob data availability. Receipt codec.
Protocol-version metadata identifies compatibility intent in attestations, release manifests and control-plane operations. It is currently not enforced as a Simplex block-validity field; a breaking execution change still requires coordinated validator deployment. Digest golden tests pin the binary calculation and version relationship, while receipt tests cover transport/persistence preservation. Those tests were inspected, not rerun here. Version boundary, digest vectors.
Follow durability and recovery to see when these execution products become recoverable local state.