- Source
arkin0x/cyberspace/CYBERSPACE_V2.mdat commit8a5b13662640- Rendered
- byte for byte from that commit; nothing here is paraphrased. Raw markdown · manifest
- sha256
cab3c5c0eff62fc3b81ccea98aead341ac99a04316b6355e7576d0bcb8414c1c- Status
- Normative. RFC-style MUST/SHOULD/MAY; explanatory material is labeled non-normative.
Cyberspace v2: Protocol Specification
Date: February 10, 2026 Last updated: September 2, 2026 Status: Design complete (spec); reference implementation in progress
What This Document Is
This is the canonical specification for the Cyberspace Protocol, version 2.
Cyberspace is a 256-bit coordinate system navigated by cryptographic keypairs using structured mathematical computation. It is not a game, not a platform, and not a virtual reality experience. It is a protocol: a specification for how to encode, prove, and verify spatial relationships using irreversible computational work.
The goal is simple: impose locality, the fundamental property of physical space, on a digital system. In physical space, crossing distance costs energy, and no one is exempt from that cost. Cyberspace does the same thing, but with computation instead of physics. Movement requires mathematical work. The work scales with distance. There are no shortcuts, no teleportation, no administrator who can move you or delete you. The math is the same for everyone.
This document specifies:
- A 256-bit coordinate system with three spatial axes and a plane bit
- How cryptographic identity maps directly into the coordinate fabric (your public key is your location)
- Three movement primitives: spawn (identity placement), hop (Cantor pairing tree proof), and sidestep (Merkle hash tree proof for storage-infeasible boundaries)
- A temporal work axis that prevents proof caching and replay
- Location-based encryption and discovery derived from stable spatial regions
- Integration with Nostr as the transmission layer
- A deterministic mapping from GPS coordinates into the coordinate system
Normative sections use RFC-style language (MUST/SHOULD/MAY). Explanatory material is labeled non-normative.
Reference implementation: https://github.com/arkin0x/cyberspace-cli
For extended design rationale and philosophical discussion, see RATIONALE.md (non-normative).
Table of Contents
- 1. Overview: Why This Exists
- 2. The Coordinate System
- 3. Identity Is Location
- 4. Movement: Cantor Pairing Trees
- 4.1 Why structured work, not hash grinding
- 4.2 The Cantor pairing function
- 4.3 Per-axis trees
- 4.4 Lowest common ancestor (LCA) height
- 4.5 Aligned subtrees
- 4.6 Computing the axis root
- 4.7 Combining into 3D (region_n)
- 4.8 Why you can’t cheat distance (decomposition invariance)
- 4.9 Five properties unique to this design (non-normative)
- 5. The Temporal Axis: Every Hop Costs Fresh Work
- 5.1 The problem: cacheable spatial roots
- 5.2 Terrain-derived temporal height K (normative)
- 5.3 Temporal axis seed and root (normative)
- 5.4 The 4D hop preimage
- 5.5 Integer→bytes encoding (normative)
- 5.6 Movement proof hash (normative)
- 5.7 Worked example (non-normative)
- 5.8 Performance expectations (non-normative)
- 6. The Wall and the Sidestep
- 6.1 The storage bottleneck (non-normative)
- 6.2 How the sidestep action works
- 6.3 Sidestep geometry (normative)
- 6.4 Per-axis Merkle root (normative)
- 6.5 Streaming computation (normative)
- 6.6 Spatial region integer (region_m)
- 6.7 Temporal binding
- 6.8 Sidestep proof hash (normative)
- 6.9 Multi-axis sidestep
- 6.10 Openings (normative)
- 6.11 Verification levels
- 6.12 Entering ≠ claiming (non-normative)
- 6.13 Natural continents (non-normative)
- 6.14 Performance expectations (non-normative)
- 6.15 Version 2 of the sidestep construction (normative)
- 7. Location-Based Encryption and Discovery
- 8. Nostr Integration: The Movement Chain
- 9. Mapping to Physical Reality: GPS and Dataspace
- 9.1 Why a physical mapping exists
- 9.2 Dataspace cube size (Cantor Height 34 scale)
- 9.3 Scale rationale (non-normative)
- 9.4 Axis naming convention (ECEF → Cyberspace)
- 9.5 Canonical spec version and deterministic arithmetic
- 9.6 Altitude handling (normative)
- 9.7 Canonical mapping algorithm (normative)
- 9.8 Golden vectors (consensus locks)
- 9.9 Consumer benchmarks (non-normative)
- 9.10 Nation-state limits (non-normative)
- 9.11 Storage as the primary constraint (non-normative)
- 10. Sectors and Spatial Querying
- 11. Visualization Conventions
- 12. Limitations and Threat Model (non-normative)
- 13. Structured Proof-of-Work (non-normative)
- 14. Reference Implementation
1. Overview: Why This Exists
1.1 The problem
Every digital system that calls itself a “space” (virtual worlds, social platforms, VR environments, cloud services) shares a fundamental limitation: they are owned. Every one of them has an administrator who can move you, delete you, change the rules, or shut the whole thing down. Your “presence” in any of these systems exists only because someone permits it.
This isn’t a flaw in specific products. It’s a consequence of how they’re built. They are permissioned systems. And permissioned systems are not space. They are services.
Physical space works differently. It imposes costs that nobody can cheat. Not governments, not corporations, not anyone. The cost is thermodynamic: paid in energy, irreversibly, to the universe itself. This is the deepest kind of fairness: not fairness by agreement (which can be broken), but fairness by physics (which cannot).
1.2 The approach
Cyberspace imposes the same constraint, irreversible computational work, on a 256-bit coordinate system. Movement requires computing mathematical structures called Cantor pairing trees, whose cost scales with the distance crossed. The work is not arbitrary hash grinding; it is the computation of the actual mathematical fabric between coordinates. The proof of your movement is the mathematics of the region you crossed.
Keypairs traverse Cyberspace by publishing signed Nostr events that commit to their movement history. Each event includes a proof derived from the Cantor tree computation, creating an auditable chain of verified movement.
1.3 Key properties
- Public key = spawn coordinate: your cryptographic identity maps directly into the coordinate fabric. Where you start is determined by who you are.
- Three movement primitives: spawn (identity placement), hop (Cantor pairing tree proof), and sidestep (Merkle hash tree proof for storage-infeasible boundaries).
- Hash chain continuity: each movement event references the previous one by its cryptographic hash, forming a linear chain of signed proofs. This chain is the keypair’s verifiable movement history and ensures every hop costs fresh work that cannot be cached or replayed.
- Axis symmetry: equal distances cost equal work regardless of direction.
- Location-based encryption: keys derive from stable spatial region preimages, enabling content that can only be decrypted by those who do the work to be “there.”
- Compact and deterministic: proofs fit in Nostr events and verify efficiently.
This v2 design replaces earlier drift/quaternion/velocity approaches (deprecated).
2. The Coordinate System
2.1 A 256-bit universe
Cyberspace exists in a 256-bit integer space. This number was not chosen arbitrarily. 256-bit numbers are the standard unit of work in cryptographic systems like SHA-256, Nostr, and Bitcoin. Working in 256 bits means the coordinate system is natively compatible with the tools that power the rest of the protocol.
Those 256 bits are divided into:
- Three spatial axes: X, Y, and Z, each 85 bits wide (an unsigned integer from 0 to 2^85 - 1)
- One plane bit: the least significant bit, which selects between dataspace (0) and ideaspace (1)
That accounts for 85 × 3 + 1 = 256 bits exactly.
Core terms:
- Coordinate (coord256): A 256-bit integer encoding X/Y/Z plus the plane bit.
- Axes (u85): X, Y, Z are 85-bit unsigned integers.
- Plane: 1 bit.
0 = dataspace(physical mapping),1 = ideaspace(non-physical). - Gibson (G): The fundamental unit of distance, equal to one axis step in u85 space.
2.2 Bit layout (normative)
The three axes are interleaved into the 256-bit integer, not packed sequentially. This means the bits alternate: an X bit, then a Y bit, then a Z bit, repeating 85 times.
Specifically:
- Bit
0(LSB): plane bitP - Bits
3, 6, 9, ...(every 3rd bit starting at 3): X bits (85 bits) - Bits
2, 5, 8, ...(every 3rd bit starting at 2): Y bits (85 bits) - Bits
1, 4, 7, ...(every 3rd bit starting at 1): Z bits (85 bits)
The result looks like: XYZXYZXYZ...P
The interleaving has an important consequence: coordinates that are spatially close share similar bit prefixes. This is what makes the Cantor pairing tree work efficiently, because nearby coordinates fall into the same aligned subtrees.
2.3 Reference pseudocode
AXIS_BITS = 85
def xyz_to_coord(x: int, y: int, z: int, plane: int = 0) -> int:
coord = plane & 1
for i in range(AXIS_BITS):
coord |= ((z >> i) & 1) << (1 + i * 3)
coord |= ((y >> i) & 1) << (2 + i * 3)
coord |= ((x >> i) & 1) << (3 + i * 3)
return coord
def coord_to_xyz(coord: int) -> tuple[int,int,int,int]:
plane = coord & 1
x = y = z = 0
for i in range(AXIS_BITS):
z |= ((coord >> (1 + i * 3)) & 1) << i
y |= ((coord >> (2 + i * 3)) & 1) << i
x |= ((coord >> (3 + i * 3)) & 1) << i
return (x, y, z, plane)
2.4 Two planes: dataspace and ideaspace
The plane bit creates two overlapping coordinate spaces:
- Dataspace (plane = 0): Maps to physical reality via GPS coordinates (see §9). The same X/Y/Z values in dataspace correspond to specific physical locations on and around Earth.
- Ideaspace (plane = 1): Has no physical mapping. The same X/Y/Z values are purely abstract positions. Ideaspace is where things exist that have no physical counterpart.
Both planes share the same mathematical properties. Movement costs, encryption, and discovery all work identically. They differ only in whether the coordinates have a physical-world interpretation.
3. Identity Is Location
3.1 Your key is your coordinate
One of the most distinctive properties of Cyberspace is that your cryptographic identity determines your spawn location. Your Nostr public key, the 256-bit number that is your identity, is also your coordinate. When you first enter Cyberspace, you appear at the point defined by your key.
You don’t choose where to spawn. Your identity chooses for you.
This means identity and location are the same thing. Not metaphorically, but mathematically. Your key encodes a specific X, Y, Z position. If someone knows your public key, they know where you spawn. If you want a different spawn point, you need a different identity.
Implication: In every other digital system, identity and location are separate concerns managed by separate authorities. In Cyberspace, the mapping from identity to space is deterministic, public, and permanent. No authority assigns locations. No registry tracks who is where. The math does it.
3.2 The spawn event
A spawn event is a signed Nostr event that declares “I exist at this coordinate.” It is the first event in a keypair’s movement chain. The coordinate in a spawn event MUST equal the event’s public key (see §8.3 for the full event format).
After spawning, a keypair can begin moving through Cyberspace by publishing hop or sidestep events that extend the chain.
A keypair may also respawn at any time by simply publishing a new spawn event. Because the new spawn event has a newer timestamp, it invalidates all prior movement events in the old chain. The keypair returns to its original spawn coordinate and starts fresh. Prior movement history remains on relays but is no longer part of the active chain.
Derezz is the common name for this: an identity whose chain has ended and which is back at its spawn coordinate is said to have derezzed, whether it got there by respawning deliberately or by having its chain go invalid. The word is a description of a state, not a mechanism. No event, action or tag in this specification performs a derezz, nothing any other identity publishes can cause one, and §1.1’s promise that there is no administrator who can move you or delete you means there never can be at this layer. A game may define anything it likes about liveness inside its own box (decks/README.md, Game mechanics), and the protocol will not consult it.
4. Movement: Cantor Pairing Trees
This is the heart of the protocol. Everything else (encryption, discovery, territory, transit) builds on top of the movement system. So it’s worth understanding not just how it works, but why it works this way.
4.1 Why structured work, not hash grinding
Standard proof-of-work (like Bitcoin mining) works by grinding random numbers until you find a hash that meets a difficulty target. The work is real. You burn energy. But the work is arbitrary. You’re searching a hash space, not traversing a space. Finding nonce #4,821,337 doesn’t tell you anything about where you are or where you went.
Cyberspace needs work that is structural: work where the computation itself encodes spatial information. The proof of movement should not be “I burned energy” but “I computed the mathematical fabric between these two coordinates.”
Cantor pairing trees achieve this. They create actual mathematical structure: each root uniquely represents a specific region of coordinate space. Computing it means building a tree from leaves to root. The number you produce is mathematically meaningful, not arbitrary. This is the difference between “digging a hole” and “following a path.” Both cost energy, but only one has spatial semantics.
4.2 The Cantor pairing function
The Cantor pairing function takes two natural numbers and produces exactly one:
π(a, b) = (a + b) × (a + b + 1) / 2 + b
This function is a bijection: every input maps to exactly one output, and every output maps back to exactly one input. No two different pairs of numbers ever produce the same result, and every natural number is the result of some pair. Compare this to a hash function like SHA-256, where many different inputs can produce the same output and you cannot reverse it. The Cantor pairing function loses nothing. It is a perfectly reversible, lossless mathematical encoding. Crucially, the function can be reversed. Given any Cantor number, you can unpair it to recover the two numbers that produced it. Applied recursively, this means a single Cantor root encodes an entire tree and that tree can be fully reconstructed from the root alone.
When applied recursively (pairing leaves into parents, parents into grandparents, all the way up) it builds a binary tree whose root is a single number that uniquely encodes every leaf in the tree.
4.3 Per-axis trees
An earlier version of Cyberspace used the full interleaved 256-bit coordinate in a single Cantor tree. This was elegant in theory but failed in practice:
- Axis asymmetry: Due to bit interleaving, X movements cost ~4× more than Z movements for the same distance. The protocol would privilege certain directions.
- Impractical scaling: Moving 64 Gibsons took ~115 seconds. Sector traversal would take months.
- Memory explosion: Large movements produced multi-gigabyte Cantor numbers.
The v2 solution: each axis (X, Y, Z) gets its own independent 85-bit Cantor tree. Movement proofs are computed separately per axis, then combined. This gives:
- Axis symmetry: Equal distances cost equal work regardless of direction.
- Practical performance: 1,024 Gibsons in ~1ms instead of ~33 seconds.
- Bounded computation: 85-bit trees are manageable.
- Parallelizable: Three independent computations can run in parallel.
4.4 Lowest common ancestor (LCA) height
When you move from one position to another along a single axis, the cost of that movement depends on which binary boundary you cross. The Lowest Common Ancestor (LCA) height captures this: it is the level in a binary tree where the paths from the two positions first diverge.
For a 1D axis movement between v1 and v2:
def find_lca_height(v1: int, v2: int) -> int:
if v1 == v2:
return 0
return (v1 ^ v2).bit_length()
The LCA height determines how much work is required: the Cantor tree you need to compute has 2^h leaves, where h is the LCA height.
An important subtlety: the LCA height is not simply a measure of distance in Gibsons. It depends on which boundary you cross, not just how far you move. A move of 1,024 Gibsons within a single aligned region might have the same LCA height as a move of 1 Gibson that happens to cross a large power-of-two boundary.
Consider two single-Gibson moves that have very different costs:
- Moving from position 4 to position 5:
4 ^ 5 = 1, bit_length = 1, soh = 1. This is cheap. You are staying within a small aligned block. - Moving from position 7 to position 8:
7 ^ 8 = 15, bit_length = 4, soh = 4. This costs 16 times as many Cantor pairs, even though you only moved 1 Gibson. The reason is that position 8 sits on the boundary of a height-4 aligned subtree, and crossing that boundary requires computing the entire subtree.
At the extreme: moving from position 2^34 - 1 to position 2^34 is a single-Gibson step, but the LCA height is 35: the two positions differ in every one of their lowest 35 bits, so bit_length(v1 ^ v2) = 35. That one step requires computing a Cantor tree with over 34 billion leaves, because you are crossing the largest binary boundary in that region of the axis. (The step from 2^33 - 1 to 2^33 is the height-34 case, with over 17 billion leaves.)
This is not a quirk. It is the core mechanism by which Cyberspace imposes locality. Boundaries in the binary structure of the coordinate space act as natural walls, and crossing them costs real work regardless of how small the step is. This property is formalized as decomposition invariance in §4.8.
4.5 Aligned subtrees
Before defining aligned subtrees formally, it is worth understanding why they exist and what problem they solve.
When two coordinates are paired into a Cantor tree, the tree must cover a specific range of leaf values. If we allowed the tree to start at any arbitrary position, then two different movements through the same neighborhood could produce different trees with different roots. There would be no stable “regions.” Every pair of coordinates would generate its own unique tree, and no two people would agree on what a region looks like or what its identifier is.
Alignment solves this by snapping tree boundaries to power-of-two positions in the coordinate space. An aligned subtree of height h always starts at a position that is a multiple of 2^h and always covers exactly 2^h consecutive leaves. This means the boundaries are fixed and universal. Everyone agrees on where the blocks are, because the blocks are determined by the math, not by anyone’s specific movement. Two people standing in the same neighborhood will compute the same Cantor root without ever communicating, because they are both computing the root of the same aligned subtree.
This property is what makes location-based encryption work (§7). It is what makes spatial consensus happen automatically. Without alignment, there is no consensus about what a region is, and the entire discovery and encryption system falls apart.
Formal definition: An aligned subtree of height h is a binary subtree whose base is a multiple of 2^h. Think of it like a base address: the subtree “owns” a block of 2^h leaves, and the base tells you where that block starts.
For any value v and height h:
base = (v >> h) << h(the aligned base)- The subtree spans
2^hconsecutive leaves:[base, base+1, ..., base + 2^h - 1]
For movement between v1 and v2, the covering aligned subtree is the smallest aligned subtree that contains both endpoints:
h = find_lca_height(v1, v2)base = (v1 >> h) << h(equivalently(v2 >> h) << h)
Example: Moving from 0 to 5:
h = find_lca_height(0, 5) = 3(because0 ^ 5 = 5, bit_length = 3)base = (0 >> 3) << 3 = 0- Aligned subtree covers leaves
[0, 1, 2, 3, 4, 5, 6, 7](8 leaves =2^3)
Example: Moving from 4 to 7:
h = find_lca_height(4, 7) = 2(because4 ^ 7 = 3, bit_length = 2)base = (4 >> 2) << 2 = 4- Aligned subtree covers leaves
[4, 5, 6, 7](4 leaves =2^2)
The alignment property is what makes region-based discovery work: coordinates within the same subtree share the same root. Two people standing in the same region will compute the same Cantor number without ever communicating. The math determines the answer, and the math is the same for everyone.
4.6 Computing the axis root
Given a movement from v1 to v2 on one axis, the axis Cantor root is computed by building a Cantor pairing tree over the aligned subtree’s leaves, pairing adjacent nodes bottom-up until one value remains:
def cantor_pair(a: int, b: int) -> int:
s = a + b
return (s * (s + 1)) // 2 + b
def compute_subtree_cantor(base: int, height: int) -> int:
if height < 0:
raise ValueError("height must be >= 0")
if height == 0:
return base
values = list(range(base, base + (1 << height)))
for _ in range(height):
values = [cantor_pair(values[i], values[i + 1]) for i in range(0, len(values), 2)]
return values[0]
The axis root for movement (v1 → v2) is:
h = find_lca_height(v1, v2)base = (v1 >> h) << haxis_root(v1, v2) = compute_subtree_cantor(base, h)
(Implementations MAY compute this with any equivalent algorithm; the result MUST match this definition.)
A root names a region at a height. The same region always produces the same root, and at one height different regions always produce different roots, because every pairing step is a bijection. Across heights the root alone is not always enough: at height 0 there is nothing to pair, so a position’s root is the position itself, and a small region at a positive height can share its root with a single position. compute_subtree_cantor(2, 1) is π(2, 3) = 18, and so is compute_subtree_cantor(18, 0). For every height of 1 and above, roots are distinct across heights as well as within them, so the identifier of a region is the pair (root, height) in general, and the root alone identifies both the region and its height whenever the height is at least 1.
Why roots at heights 1 and above never collide (non-normative). Suppose two regions of different heights h1 < h2, both at least 1, had the same root. Every pairing step is a bijection, so peeling one level off both trees gives equal left-child roots and equal right-child roots, and peeling h1 levels gives that the 2^h1 consecutive positions of the lower region equal the roots of 2^h1 adjacent aligned regions of height d = h2 - h1, which is at least 1. Adjacent aligned regions at any height of 1 or more have roots that differ by at least 16 (compute_subtree_cantor(2, 1) - compute_subtree_cantor(0, 1) = 18 - 2 = 16, the smallest case), so two consecutive integers cannot both be such roots. The lower region therefore has a single position, which is height 0. The height-0 case never reaches a bag: a bag’s height is at least 1 (§7.6) and discovery scans heights from 1 (§7.4).
4.7 Combining into 3D (region_n)
Each axis computes its root independently:
cantor_x = axis_root(x1, x2)cantor_y = axis_root(y1, y2)cantor_z = axis_root(z1, z2)
Then they are combined using nested Cantor pairing:
region_n = π(π(cantor_x, cantor_y), cantor_z)
The resulting region_n is the stable spatial region integer, a single number that uniquely identifies the 3D region implied by the movement. This number is used for location-based encryption and discovery (§7).
Region uniqueness (non-normative): Each aligned subtree root corresponds to a unique region at its height, and for heights of 1 and above to a unique region across heights as well (§4.6). Many coordinate pairs inside the same aligned subtree share the same root; this is intentional. The Cantor root is a region identifier, not a unique coordinate-pair identifier.
Example (1D):
LCA(0, 3) => subtree [0..3] => root = 228
LCA(1, 2) => subtree [0..3] => root = 228
LCA(0, 2) => subtree [0..3] => root = 228
This means all these movements “see” the same region, which is exactly what enables location-based discovery.
Heights need not match across axes (non-normative). A movement’s three axis heights are computed independently (§4.4), and an axis along which the position did not change has height 0, so its root is the position itself. That is ordinary: a move along X alone has heights (h, 0, 0). A movement’s region_n is verified by recomputing it from the two coordinates, so it does not rely on a root identifying its height. A bag’s region is one aligned cube, one height for all three axes and at least 1 (§7.6), so its region_n, its key and its lookup_id (§7.2) are distinct from those of every other region at every height.
4.8 Why you can’t cheat distance (decomposition invariance)
A natural question: can you cheat the cost of a large movement by breaking it into many small steps? If moving from 0 to 1000 costs a lot of work, can you instead take 1000 one-Gibson steps and pay less?
No. This is provable.
Theorem (follows from the alignment properties of complete binary trees and the 2-adic valuation): For any two leaf positions v1 and v2 with v1 < v2:
find_lca_height(v1, v2) == max(find_lca_height(i, i+1) for i in range(v1, v2))
The LCA height of the direct pair is always equal to the maximum LCA height among all unit-step pairs in the sequence.
What this means in plain terms: If you decompose a movement (v1 → v2) into sequential adjacent steps, at least one step in the sequence will require the same covering subtree height as the direct movement. You can’t avoid the expensive boundary crossing, because any path from v1 to v2 must cross it.
Proof sketch: The LCA height h of (v1, v2) is determined by the highest bit position where v1 and v2 differ. Any sequential walk from v1 to v2 must cross the largest power-of-2 boundary between them. The step at which this crossing occurs produces an XOR with a bit set at the same highest position, yielding identical height h.
In simple terms: the computational cost of movement is the same whether you take small steps or large steps. You can’t avoid, decompose, or sidestep the computational burden of power-of-two boundaries along each axis. This is the digital equivalent of the triangle inequality: you can’t cheat geometry.
4.9 Five properties unique to this design (non-normative)
The Cantor pairing tree produces five properties that, to our knowledge, no other digital spatial system possesses:
1. Locality without trusted parties. The cost of computation scales with the height of the Cantor tree, which scales with distance. This is locality — the fundamental property of space — and it emerges from the math without any authority enforcing it. Nobody decides how much it costs to move. The mathematics decides.
2. Hierarchical spatial encryption. Because each region has a unique Cantor root, that root can serve as a cryptographic key. Content encrypted with a region’s root can only be decrypted by someone who computes that root (see §7). This is the digital equivalent of writing a message in chalk on a sidewalk: you can only read it by being there.
3. Work equivalence. In most digital systems, observing is free and doing costs. In Cyberspace, observation and action cost nearly the same. To discover what’s in a region, you must compute the region’s root, the same work a traveler would do to cross it. There is no free surveillance.
Where the equivalence is not exact, and it favors the observer (non-normative). Two differences, neither of which the earlier wording admitted. First, a traveler additionally pays the temporal axis, which §7.2 deliberately excludes from key derivation, so observing one region is strictly cheaper than crossing into it by that term. Second, and without bound at scale, a traveler’s work is sequential and an observer’s is not: each chain event commits to the previous one, so a thousand moves are a thousand moves in order, while a thousand regions to observe is an embarrassingly parallel job for a cluster. The equivalence is therefore a good description of one region and a poor one of a million. §7.2’s key derivation is where that gap would be closed if it is to be closed.
4. Deterministic regions. Any two people computing the Cantor root of the same aligned subtree will get the same answer. Regions don’t need to be assigned, registered, or coordinated. They exist as mathematical facts. Spatial consensus happens automatically, without communication.
5. Decomposition invariance. As proven in §4.8, sequential decomposition doesn’t reduce cost. There are no shortcuts through space.
Checking a root is cheap; producing one is not (non-normative). These are different operations and only the second is protected. The low bits of a Cantor pairing depend only on the low bits of its operands, because carries propagate upward, and the one right shift in (s(s+1))/2 pulls down exactly one bit per level. So carrying a window of k + h low bits through the same 2^h pairings reproduces the true root’s low k bits exactly, in a few machine words instead of the root’s full width. Measured against real roots, a 64-bit answer is exact at every height tested, and the saving is 116 times at h18 and grows with height.
This does not weaken anything above. The operation count is unchanged, so §4.8 is untouched: the window performs the same 2^h pairings in the same order and merely carries fewer bits through each. And the window never yields the root. At h34 it returns 64 bits of a number 1.46 trillion bits wide, so it cannot produce region_bytes, cannot derive a key under §7.2, and cannot forge a proof.
It detects error, not fraud, and the difference matters. The window is cheap for everyone symmetrically, so an adversary runs it too, learns the same low bits, and appends whatever they like above them. A prefix that matches is therefore evidence of an honest mistake not having happened, and no evidence at all against somebody trying. Use it to confirm that a transfer arrived intact, that a disclosed root is for the region you asked for rather than a neighbouring one, or that a long computation on your own hardware did not corrupt. Do not use it as a proof of anything.
The load-bearing consequence is the negative one: a root prefix is not evidence of possession. Anyone can compute one in seconds without holding the region, so no protocol, market or game may treat a prefix as attestation. Possession is demonstrated by using the root, which is to say by deriving a key that actually decrypts, and a wrong root simply fails to open anything.
These five properties together create something unprecedented: digital space with the structural integrity of physical space, enforced by mathematics rather than by any authority.
5. The Temporal Axis: Every Hop Costs Fresh Work
5.1 The problem: cacheable spatial roots
The per-axis Cantor roots described in §4 are region identifiers: the same region always produces the same root. This is desirable for discovery and encryption (you want the “address” of a region to be stable). But it creates a movement loophole: once a mover has computed the Cantor root for a region, they could reuse that cached result to generate arbitrarily many hop events through the same region at near-zero marginal cost.
They would only be able to return to places they had already been, but instant teleportation over previously-trod terrain breaks the proof-of-work continuity of Cyberspace as a thermodynamic system.
The solution: extend movement proofs into a fourth dimension, a temporal work axis derived from the Nostr movement chain. Each hop includes an additional Cantor tree computation whose inputs are derived from chain context. Because each Nostr event id commits to the previous event (including its proof), the temporal seed for hop N is not known until hop N-1 is complete. The work cannot be precomputed or amortized.
What this does NOT change: Stable spatial region identifiers (used for location-based encryption and discovery in §7) remain a pure function of coordinates, independent of time or identity. The temporal axis exists only to make advancing the movement chain cost work.
What this is NOT: This is not a continuous “heartbeat” cost. An avatar’s last hop event remains its state indefinitely. The temporal axis work is paid only when moving.
5.2 Terrain-derived temporal height K (normative)
The temporal axis has a height K that varies by destination: a deterministic “terrain” function that makes some regions of Cyberspace intrinsically easier or harder to traverse. Think of it as hills and valleys in the computational landscape.
Constants:
TERRAIN_DOMAIN_V2 = b"CYBERSPACE_TERRAIN_K_V2"(ASCII bytes)TERRAIN_CELL_BITS = [3, 7, 9, 11](exactly four integers)
If any part of this terrain function changes (constants, hashing preimage format, byte selection, etc.), the domain string MUST be bumped to a new value to avoid ambiguity.
Algorithm: Given the hop destination coordinate (x2, y2, z2, plane):
-
For each
bitsinTERRAIN_CELL_BITS(in order):- Align the destination to the cell of width
2^bitsalong each axis:bx = (x2 >> bits) << bitsby = (y2 >> bits) << bitsbz = (z2 >> bits) << bits
- Compute
cell_coord = xyz_to_coord(bx, by, bz, plane). - Encode
cell_coordas exactly 32 big-endian bytes:cell_coord_bytes. - Compute
digest = sha256(TERRAIN_DOMAIN_V2 || byte(bits) || cell_coord_bytes).byte(bits)is a single unsigned byte with valuebits.
- Record
nibble_i = digest[0] & 0x0F(the low 4 bits of the first byte of the digest).
- Align the destination to the cell of width
-
Concatenate the four nibbles into a 16-bit word:
word16 = (nibble_0 << 12) | (nibble_1 << 8) | (nibble_2 << 4) | nibble_3
-
Define
Kas the popcount ofword16(the number of 1 bits in its 16-bit binary representation).- Therefore
KMUST be an integer in[0, 16].
- Therefore
Why this works (non-normative): Because K is the popcount of 16 pseudorandom bits, it follows approximately a binomial distribution with mean 8. The worst-case temporal computation is 2^16 = 65,536 Cantor pairs (~100 ms on modern hardware). The aligned cells at different scales introduce spatial correlation, so nearby coordinates tend to share the same K value, creating “hills” in the temporal landscape.
5.3 Temporal axis seed and root (normative)
For hop events, let previous_event_id be the 32-byte NIP-01 event id referenced by the e tag with marker previous (see §8.4).
- Parse
previous_event_idfrom lowercase hex into 32 bytes. - Interpret it as a big-endian integer and reduce into the u85 axis:
prev_id_int = int.from_bytes(previous_event_id_bytes, "big")t = prev_id_int % (1 << 85)
- Compute an aligned temporal subtree and its Cantor root at the terrain-derived height
K:t_base = (t >> K) << Kcantor_t = compute_subtree_cantor(t_base, K)
Key insight (non-normative): The temporal axis is derived from chain context and destination coordinates, not wall-clock time. There is no continuous “alive” cost; you pay this work only when you publish a hop.
5.4 The 4D hop preimage
The hop preimage combines spatial and temporal work:
hop_n = π(region_n, cantor_t)
This is the single number that encodes both where you moved (spatial) and when in the chain you moved (temporal).
5.5 Integer→bytes encoding (normative)
Many operations in this protocol hash large integers. This spec defines the canonical encoding:
def int_to_bytes_be_min(n: int) -> bytes:
if n < 0:
raise ValueError("expected non-negative")
if n == 0:
return b"\x00"
# minimal big-endian bytes
return n.to_bytes((n.bit_length() + 7) // 8, "big")
5.6 Movement proof hash (normative)
The movement proof hash is derived from the 4D hop preimage hop_n. This intentionally differs from the stable spatial region identifiers used for location-based encryption and discovery (§7).
hop_bytes = int_to_bytes_be_min(hop_n)movement_proof_key = sha256(hop_bytes)(32 bytes)proof_hash = sha256(movement_proof_key)(32 bytes)
When used in Nostr tags, proof_hash MUST be encoded as lowercase hex in the proof tag.
5.7 Worked example (non-normative)
Movement: (0, 0, 0) → (4104, 0, 0)
Per-axis roots:
- X:
0 → 4104=> height 13 => root =compute_subtree_cantor(0, 13) - Y:
0 → 0=> height 0 => root0 - Z:
0 → 0=> height 0 => root0
3D combine (stable spatial region integer):
region_n = π(π(cantor_x, 0), 0)
Stable lookup id (used for location-based encryption/discovery, §7.2):
lookup_id = sha256(sha256(int_to_bytes_be_min(region_n)))8d2463eb22301d97a1f7e33b90e473ba2eec69079f418a72609c3e4d2981669b
Terrain-derived temporal height at destination (x2=4104, y2=0, z2=0, plane=0) using TERRAIN_CELL_BITS = [3, 7, 9, 11]:
For each cell size, align the destination coordinate and extract a nibble:
bits=3: cell =(4104, 0, 0)→ nibble =0b0000(0 ones)bits=7: cell =(4096, 0, 0)→ nibble =0b1111(4 ones)bits=9: cell =(4096, 0, 0)→ nibble =0b1111(4 ones)bits=11: cell =(4096, 0, 0)→ nibble =0b1101(3 ones)
Combine into 16-bit word and compute popcount:
word16 = (0 << 12) | (15 << 8) | (15 << 4) | 13 = 0b0000111111111101K = popcount(word16) = 0 + 4 + 4 + 3 = 11
Temporal axis example using previous_event_id = 64 hex zeros:
previous_event_id = "0000000000000000000000000000000000000000000000000000000000000000"t = 0t_base = 0cantor_t = compute_subtree_cantor(0, 11)
4D hop preimage and movement proof hash:
hop_n = π(region_n, cantor_t)proof_hash = sha256(sha256(int_to_bytes_be_min(hop_n)))ed9d09ca697b2da29c9d042207ac8ef7aab40f6dde550e6467452aa0e2e8cac6
Different previous_event_id values produce different proof_hash values, even for identical spatial moves. This is the temporal axis at work.
5.8 Performance expectations (non-normative)
Reference implementations observe that cost grows with the per-axis LCA height (because the aligned subtree contains 2^h leaves). In addition, each hop includes the temporal axis traversal at terrain-derived height K, imposing non-cacheable work per hop even when spatial region_n is reused. Because K depends on the destination coordinate, some regions of Cyberspace are intrinsically easier or harder to traverse.
Approximate per-axis expectations from early benchmarks (illustrative only):
| Distance | Height | Cantor size | Time (approx) |
|---|---|---|---|
| 1 G | 1 | 1 B | < 0.01 ms |
| 16 G | 5 | 90 B | < 0.01 ms |
| 256 G | 9 | 2.5 KB | ~ 0.1 ms |
| 1,024 G | 11 | 12 KB | ~ 1 ms |
| 4,096 G | 13 | 57 KB | ~ 10 ms |
| 65,536 G | 17 | 1.2 MB | ~ 1 sec |
Implementations should cap per-hop distance for UX and may rely on multiple hops for long travel.
6. The Wall and the Sidestep
The Cantor pairing tree gives Cyberspace its spatial fabric, but it also reveals a fascinating limitation that turns out to be a feature.
6.1 The storage bottleneck (non-normative)
As the Cantor tree gets taller (meaning, as you cross larger regions of space), the intermediate values computed during the pairing process grow exponentially in bit size. The Cantor pairing function π(a, b) = (a + b)(a + b + 1)/2 + b produces outputs roughly quadratic in the size of its inputs. After h levels of pairing, intermediate values can be millions or billions of bits long, and they must be stored during construction because parent nodes require both children.
| LCA Height | Cantor Intermediate Storage | Merkle Working Memory |
|---|---|---|
| h20 | ~11 MB | 640 bytes |
| h30 | ~11 GB | 960 bytes |
| h34 | ~182 GB | 1,088 bytes |
| h40 | ~11 TB | 1,280 bytes |
| h50 | ~11 PB | 1,600 bytes |
| h60 | ~12 EB (about 0.1% of installed world storage) | 1,920 bytes |
| h70 | ~12 ZB (roughly all installed storage today) | 2,240 bytes |
This is not a bug. This is the digital equivalent of a mountain range.
Storage figures in this document are 85 × 2^h bits per axis root in decimal units (1 GB is 10^9 bytes); the h34 root is 182.5 GB, which is 170 GiB.
The storage bottleneck creates natural barriers in Cyberspace: walls that cannot be crossed by direct Cantor computation regardless of how much time you have, because you simply can’t store the intermediate values. These walls aren’t designed by anyone. They emerge from the mathematics.
But walls are only interesting if there’s a way to get past them — expensively, deliberately, with real effort.
6.2 How the sidestep action works
A sidestep action traverses 1 Gibson in a direction using an alternative POW that makes otherwise impossible LCA barriers feasible to cross. Instead of Cantor pairing, a sidestep produces a tree with a Merkle hash tree over SHA-256 hashes of leaf coordinates. The critical insight: SHA-256 operations are fixed-size (256 bits in, 256 bits out) regardless of tree height. No storage bottleneck. The cost of a sidestep is purely time: how long it takes to hash every leaf coordinate.
At heights above roughly h16 a sidestep is cheaper in wall-clock time than the equivalent hop, and the gap widens with height (see §6.14). The hop remains the primitive that produces a region root; the sidestep is the primitive that crosses a boundary. An agent hops when it wants the root (a discovery key or a domain) and sidesteps when it wants to move.
The sidestep is a toll: its work is seeded by the mover’s chain position, so it is paid in full by every traveller and cannot be reduced, sold, or inherited from anyone else’s published proof (§6.4, §6.15). The hop is deliberately not a toll, because its work product is a canonical region root that a holder may choose to share (§6.12).
Core terms:
- Sidestep: A movement action that crosses an LCA boundary via a Merkle hash tree proof instead of a Cantor pairing tree proof. Crosses exactly 1 Gibson past the boundary, regardless of the amount of work it takes.
- Merkle root (sidestep): The root hash of a binary Merkle tree built over SHA-256 hashes of every leaf coordinate in an aligned subtree, seeded by the mover’s chain position. Domain-separated from other protocol hashes.
- Toll: The property that a sidestep’s spatial work is non-transferable. Every traveller crossing a given boundary pays the full price; no published proof reduces the cost for any other traveller.
- Openings: The inclusion paths published with a sidestep: the destination leaf’s path plus
SIDESTEP_SAMPLESpaths at pseudorandomly sampled positions (§6.10). - SIDESTEP_DOMAIN:
b"CYBERSPACE_SIDESTEP_V2", the domain separation prefix used for all sidestep leaf hashes. - SIDESTEP_SAMPLE_DOMAIN:
b"CYBERSPACE_SIDESTEP_SAMPLE_V1", the domain separation prefix used to derive sampled opening indices. - SIDESTEP_SAMPLES:
8, the number of sampled openings published per non-trivial axis.
6.3 Sidestep geometry (normative)
A sidestep is the smallest possible move across a wall. The wall is the boundary between the two children of the LCA subtree: the two aligned halves that an axis value must cross between when its high bit at height h - 1 flips. A sidestep starts on the leaf touching the wall on the source side, and ends on the leaf touching the wall on the destination side. The two leaves are neighbours: they differ by exactly 1 Gibson, and the wall runs between them. Everything else the avatar wants to do (reaching the wall, and continuing past it) is done with hops, which are the primitive that produces a region root (§4, §5); the sidestep only crosses.
Given source axis value v1 and destination axis value v2 on an axis where movement occurs:
- The LCA height
h = find_lca_height(v1, v2)identifies the wall.base = (v1 >> h) << his the first leaf of the LCA subtree, andhalf = 2^(h - 1)is the number of leaves in each of its two children. The wall lies between leafbase + half - 1(the last leaf of the lower child) and leafbase + half(the first leaf of the upper child). - The source MUST be the leaf touching the wall on its side. Crossing upward (
v2 > v1) the source isbase + half - 1: every bit belowh - 1is one. Crossing downward (v2 < v1) the source isbase + half: every bit belowh - 1is zero. - The destination MUST be the neighbouring leaf on the other side of the wall:
v2 = v1 + 1crossing upward,v2 = v1 - 1crossing downward. In both directions|v2 - v1| = 1, andv1andv2are in different children of the LCA subtree. - Non-crossing axes MUST have
v1 == v2. A sidestep that crosses walls on more than one axis at once is described in §6.9; each crossing axis obeys the rules above independently.
Worked examples on one axis (values written in binary, most significant bit first):
v1 = 0b0111andv2 = 0b1000. Hereh = 4,base = 0,half = 8. The wall is between leaves 7 and 8. The source 7 isbase + half - 1(all lower bits one) and the destination 8 isbase + half(all lower bits zero): a valid upward sidestep.v1 = 0b1000andv2 = 0b0111. The same wall crossed downward: the source 8 isbase + halfand the destination 7 isbase + half - 1: a valid downward sidestep.v1 = 0b0101andv2 = 0b1000. Stillh = 4, but the source 5 is not touching the wall (leaf 7 is). This is NOT a valid sidestep. The mover first hops from 5 to 7 (a hop withh = 2), then sidesteps from 7 to 8.v1 = 0b0111andv2 = 0b1011. Not a valid sidestep either: the destination 11 is 3 Gibsons past the wall. The mover sidesteps from 7 to 8, then hops from 8 to 11 (a hop withh = 2).
Why the source constraint exists: the Merkle root (§6.4) is computed over every leaf of the whole LCA subtree, 2^h leaves, whichever leaf the avatar starts from. If the source could be anywhere inside its half, a single sidestep event would carry the avatar up to half - 1 Gibsons to the wall and then across, with no hop and therefore no region root along the way. Pinning the source to the wall keeps a sidestep to its one job, crossing, and keeps every other Gibson of travel inside hops. It also removes an ambiguity: with the source at the wall, “the first leaf of the adjacent aligned subtree” and “exactly 1 Gibson past the boundary” describe the same leaf in both directions.
A verifier at Level 1 (§6.11) checks this geometry directly from the event’s coordinates before it looks at any hash: h from the two values, the source touching the wall on its side, the destination exactly one Gibson beyond it. An event that fails the geometry check is invalid regardless of its proof.
6.4 Per-axis Merkle root (normative)
Sidestep leaves are seeded. Let previous_event_id be the 32 raw bytes of the id referenced by this event’s e tag with marker previous, and let axis_byte be a single byte identifying the axis: 0x00 for X, 0x01 for Y, 0x02 for Z. Define the per-axis seed prefix:
seed_prefix = SIDESTEP_DOMAIN || previous_event_id || axis_byte || SEED_PAD
SIDESTEP_DOMAIN is 22 bytes and SEED_PAD is 9 zero bytes, so seed_prefix is exactly 64 bytes: one full SHA-256 block. This is deliberate (§6.5).
For each axis where movement occurs (v1 ≠ v2):
- Compute
h = find_lca_height(v1, v2). - Compute the aligned subtree base:
base = (v1 >> h) << h. - The aligned subtree contains
2^hleaves:[base, base+1, ..., base + 2^h - 1]. - For each leaf value
L_i(whereiranges from0to2^h - 1):- Encode as big-endian minimal bytes:
leaf_bytes = int_to_bytes_be_min(base + i) - Compute leaf hash:
H_i = SHA256(seed_prefix || leaf_bytes)
- Encode as big-endian minimal bytes:
- Build the Merkle tree bottom-up:
- For each pair
(H_{2j}, H_{2j+1}):parent = SHA256(H_{2j} || H_{2j+1}) - Continue until a single root
M_axisremains.
- For each pair
- The per-axis Merkle root is
M_axis(32 bytes).
Domain separation constant (normative):
SIDESTEP_DOMAIN = b"CYBERSPACE_SIDESTEP_V2" # 22 bytes
SEED_PAD = b"\x00" * 9 # brings seed_prefix to exactly 64 bytes
If any aspect of sidestep leaf hash computation changes in a future version (preimage format, domain string, encoding), the domain string MUST be bumped to a new value to avoid cross-version collisions.
Trivial axes (h = 0): When v1 == v2 on an axis, the Merkle root is the single leaf hash: M_axis = SHA256(seed_prefix || int_to_bytes_be_min(v1)). No tree construction is needed.
Why the seed (normative rationale): Without it, the leaf preimage contains no identity and no chain context, so the root for a given aligned subtree is the same 32 bytes for every traveller in the history of the protocol. Because the destination is deterministic (§6.3) and both the roots and the openings are published in the clear (§8.5), the first identity to cross any boundary would publish everything a later identity needs: a follower could copy mr and mp from a relay, compute only the temporal axis (at most 2^16 Cantor pairs, about 100 ms), and produce a proof that passes both verification levels, because the copied root is the correct root. Boundary crossings would be priced for the pioneer and free for everyone after. The seed makes each traveller’s tree unique to its chain position, so the price in §6.14 is what every traveller pays, every time.
Why the axis byte (normative rationale): Earth is centred at exactly 2^84 on every axis (§9.7) and its surface radius is about 2^55.6 Gibsons. Because 2^84 is a multiple of 2^h for every h ≤ 84, the centre is always a cell boundary, so above about h56 the aligned base on each axis takes one of only two values, and any two axes falling on the same side of the centre plane share it exactly. Without axis separation such a crossing would compute one tree and claim it as two or three, and §6.9’s “the total work is the sum of per-axis work” would be false by that factor at exactly the heights where the work is largest.
6.5 Streaming computation (normative)
The Merkle tree MUST be computable in streaming fashion without storing all leaf hashes simultaneously:
- Process leaves in ascending order (deterministic enumeration from
basetobase + 2^h - 1). - Maintain a stack of pending intermediate hashes, maximum depth
h. - Working memory:
h × 32bytes (under 3 KB even at h85). - Total hash operations:
2^(h+1) - 1(2^hleaf hashes +2^h - 1internal hashes).
def compute_merkle_root_streaming(seed_prefix: bytes, base: int, height: int) -> bytes:
"""Compute Merkle root over aligned subtree in O(h) memory."""
if height == 0:
return sha256(seed_prefix + int_to_bytes_be_min(base))
stack = [] # (hash_value, level)
for i in range(1 << height):
leaf_bytes = int_to_bytes_be_min(base + i)
current = sha256(seed_prefix + leaf_bytes)
level = 0
while stack and stack[-1][1] == level:
left = stack.pop()[0]
current = sha256(left + current)
level += 1
stack.append((current, level))
return stack[0][0]
(Implementations MAY use any equivalent algorithm; the result MUST match this definition.)
Midstate optimization (normative for performance, not for consensus): seed_prefix is exactly 64 bytes and constant for the whole tree, so it fills exactly one SHA-256 block. Implementations SHOULD precompute the compression-function midstate over that block once per axis and resume from it for every leaf. Because leaf_bytes is at most 11 bytes and SHA-256 padding needs 9, each leaf then costs exactly one further compression, which is the same per-leaf cost as an unseeded leaf. The figures in §6.14 are therefore unchanged by seeding.
Had seed_prefix not been padded to a block boundary, each leaf would have straddled two blocks and every sidestep in the protocol would have cost twice as much. Implementations that ignore the midstate optimization are still consensus-correct; they are simply half as fast.
6.6 Spatial region integer (region_m)
Combine per-axis Merkle roots into a single spatial proof integer:
mx = int.from_bytes(M_x, "big") # X-axis Merkle root as integer
my = int.from_bytes(M_y, "big") # Y-axis Merkle root as integer
mz = int.from_bytes(M_z, "big") # Z-axis Merkle root as integer
region_m = cantor_pair(cantor_pair(mx, my), mz)
This mirrors the hop proof’s region_n = π(π(cantor_x, cantor_y), cantor_z) but uses Merkle roots instead of Cantor roots.
All three axes always use Merkle roots in a sidestep. There is no mixed mode; the proof type (Cantor or Merkle) is determined by the action type (hop or sidestep), not per-axis.
6.7 Temporal binding
The temporal binding mechanism for sidesteps is identical to hop proofs (§5.2–§5.3):
- Compute terrain height
Kat the destination coordinate per §5.2. - Derive temporal seed
tfromprevious_event_idper §5.3. - Compute
cantor_t = compute_subtree_cantor(t_base, K).
This is always feasible because K ≤ 16 (maximum 65,536 Cantor pairs, ~100 ms).
Why temporal binding is retained: In v1 of the sidestep construction the temporal axis was the only thing binding a proof to a chain position, because the spatial Merkle root was deterministic and replayable. Since §6.4 now seeds the spatial leaves with previous_event_id, replay protection is already provided by the spatial component. The temporal axis is retained because it carries the terrain-derived height K (§5.2), which is a property of the destination and not of the mover, and because it keeps sidestep and hop proofs structurally parallel. It is no longer load-bearing for replay.
No precomputation (normative consequence): Because seed_prefix depends on previous_event_id, which is only known once the preceding event is published, the spatial Merkle root cannot be computed in advance of a crossing. An entity MUST perform the full spatial work between publishing its previous event and publishing the sidestep. This is the intended behaviour: it is what makes the cost in §6.14 a toll rather than a one-time preparation that can be amortized or scheduled.
Resumption (non-normative): A crossing at h47 or above is hours of GPU work that can no longer be staged ahead of time, so an interrupted crossing is expensive to lose. Implementations SHOULD persist completed subtree state keyed by (previous_event_id, axis_byte, base, h) so an interrupted sidestep resumes rather than restarting. The seeding of §6.4 makes this safe: cached state is only ever valid for the one chain position it was computed under, so it can never be replayed into a later crossing. This mirrors decks/DECK-0001-hyperspace.md §5.7.
6.8 Sidestep proof hash (normative)
Combine spatial and temporal components:
sidestep_n = cantor_pair(region_m, cantor_t)
Apply double SHA-256 (consistent with hop proofs, §5.6):
sidestep_bytes = int_to_bytes_be_min(sidestep_n)
proof_key = sha256(sidestep_bytes)
proof_hash = sha256(proof_key) # 32 bytes
When used in Nostr tags, proof_hash MUST be encoded as lowercase hex.
6.9 Multi-axis sidestep
A single sidestep event MAY cross boundaries on multiple axes simultaneously (if source and destination differ on more than one axis). Each crossing axis obeys the geometry of §6.3 on its own: its source touches its wall and its destination is 1 Gibson past it, so the event moves the avatar by exactly 1 Gibson on every crossing axis and by nothing on the others. The proof is constructed independently per axis:
- Each axis computes its own Merkle root (or trivial single-leaf hash for axes where
h = 0). - The three roots are combined into
region_mvia Cantor pairing (§6.6). - Temporal binding applies once to the combined proof, not per-axis.
- The total work is the sum of per-axis work.
6.10 Openings (normative)
In addition to the Merkle root, the prover MUST publish, for each axis where movement occurs, an inclusion proof for the destination leaf and SIDESTEP_SAMPLES inclusion proofs at pseudorandomly sampled positions.
Each inclusion proof is a sequence of sibling hashes from leaf to root:
axis_proof = H_sibling_0 || H_sibling_1 || ... || H_sibling_{h-1}
Where H_sibling_i is the 32-byte sibling hash at depth i (leaf = depth 0). The verifier determines left/right ordering at each level from the leaf’s position in the subtree (deterministic from the leaf value).
Sample indices. For an axis with root M_axis, height h, and axis_byte as in §6.4, the sampled positions are, for i in 0 .. SIDESTEP_SAMPLES - 1:
idx_i = int(SHA256(SIDESTEP_SAMPLE_DOMAIN || M_axis || axis_byte || be32(i))) mod 2^h
be32(i) is i as four big-endian bytes. Indices are positions within the aligned subtree, so the sampled leaf value is base + idx_i. Indices MAY collide; implementations MUST NOT deduplicate, so that the opening count is fixed and the encoding in §8.5 is fixed-width.
Constants (normative):
SIDESTEP_SAMPLE_DOMAIN = b"CYBERSPACE_SIDESTEP_SAMPLE_V1"
SIDESTEP_SAMPLES = 8
Ordering. The openings for an axis are ordered: the destination proof first, then the sampled proofs in ascending i. Each is exactly h sibling hashes, so an axis contributes exactly (SIDESTEP_SAMPLES + 1) × h × 32 bytes.
Trivial axes (h = 0): no openings are published. The Merkle root IS the single leaf hash and there is nothing to sample.
Why sampling is required (normative rationale): The destination inclusion proof alone proves only that the destination leaf sits in some tree with the claimed root. It does not prove the tree was built over the aligned subtree, and it never did. A prover can fabricate h arbitrary sibling hashes, hash upward from the genuine destination leaf, and publish the result: h hash operations instead of 2^h, passing the destination check exactly.
Under the v1 unseeded construction this gap was survivable, because every honest root for a boundary was the same 32 bytes, so any party holding that root detected the forgery by comparison, and no rational prover forged when copying was free and correct. Seeding removes the copy, and with it removes comparison auditing: after §6.4 there is no canonical root to compare a claim against, and only a full O(2^h) recomputation would catch a fabricated tree. Seeding without sampling would therefore be strictly worse than the design it replaces. The two changes MUST ship together.
6.11 Verification levels
Sidestep verification has two levels, reflecting a trade-off between verification cost and trust assumptions:
Level 1: Sampled opening verification, O(SIDESTEP_SAMPLES × h) per axis
A verifier checks that the claimed openings are consistent with the claimed root and that the sampled leaves are the correct seeded values:
- Validate coordinates: source and destination are valid 256-bit Cyberspace coordinates.
- Validate crossing geometry per §6.3: on every axis where movement occurs,
h = find_lca_height(v1, v2), the source touches the wall on its side (base + half - 1going up,base + halfgoing down), and the destination is exactly 1 Gibson past it; axes without movement havev1 == v2. - Reconstruct
seed_prefixfrom the event’se previoustag andaxis_byteper §6.4. - Recompute the destination leaf hash
H_dest = SHA256(seed_prefix || int_to_bytes_be_min(v_dest))and verify its path to the claimed rootM_axis. - Derive the sample indices from
M_axisper §6.10. For each, recompute the sampled leaf hashSHA256(seed_prefix || int_to_bytes_be_min(base + idx_i))from scratch and verify its path toM_axis. - Recompute
region_m, temporal axis, andproof_hash. Compare against claimed value.
Level 1 costs SIDESTEP_SAMPLES + 1 leaf hashes and the same number of paths, seconds at any height.
Level 2: Full root verification, O(2^h) per axis
To fully verify that the claimed Merkle root was computed over the correct aligned subtree, a verifier recomputes the entire Merkle tree from scratch. This costs the same order of work as the original proof.
Security model: The protocol does NOT require every verifier to perform Level 2. Security relies on deterministic fraud detectability: the Merkle root for an aligned subtree is a deterministic function of (previous_event_id, axis_byte, base, h), every one of which is public on the event itself, so a fraudulent root remains permanently and objectively detectable by any party willing to do the work. Seeding changes who bears the audit cost, not whether fraud is detectable. Under v1 an auditor could compute a boundary’s root once and check every crossing of it forever; under v2 each event must be audited on its own, which is why Level 1 was strengthened from a single destination path to sampled openings.
What sampling bounds (non-normative). A tree over 2^h leaves requires 2^h leaf hashes and 2^h - 1 internal hashes, so the internal tree is an irreducible half of the honest work and no prover can avoid it. A prover who honestly computes a fraction f of the leaves and fabricates the rest passes Level 1 with probability f^SIDESTEP_SAMPLES, because the sampled positions are spread pseudorandomly over the whole subtree and each sampled leaf is recomputed by the verifier from the public seed. Grinding is not an escape: the sample indices are derived from the root, so steering them requires rebuilding the tree, and each rebuild costs at least the 2^h - 1 internal hashes, which is half the honest total.
With SIDESTEP_SAMPLES = 8, passing half the time requires honestly computing about 92 percent of the leaves. Skipping the other 8 percent saves about 4 percent of the crossing’s total work, since the internal tree is untouched, and buys a one-in-two chance of publishing permanently detectable fraud that invalidates the chain from that event forward. A prover willing to accept a one-in-a-thousand pass rate still has to compute 42 percent of the leaves, saving 29 percent. The security margin here comes primarily from the rebuild cost, not from the sample count, which is why 8 suffices where decks/DECK-0001-hyperspace.md §5.5 needs 32: a hyperspace leaf is expensive and its tree is cheap, and a sidestep is the reverse.
In practice, Level 1 is for routine validation. Level 2 is for auditors, competitors, or automated fraud-detection services.
Small heights (non-normative). Level 1 costs about (SIDESTEP_SAMPLES + 1) × (h + 1) hash operations and Level 2 costs 2^(h+1) - 1, so the two cross over near h5. Below that, a verifier SHOULD simply perform Level 2, which is both cheaper and complete. Provers still publish the full opening set at every height, so that the mp encoding of §8.5 stays fixed-width.
6.12 Entering ≠ claiming (non-normative)
The sidestep Merkle tree is built over SHA-256 hashes of leaf coordinates. The Cantor pairing tree over those same leaves produces a completely different value. Computing the Merkle root reveals nothing about the Cantor root.
This preserves a critical separation: sidestepping into a region does not yield the region’s keys. The region’s Cantor root, and with it every location-based key inside the region (§7), still requires the full Cantor computation. A visitor who sidesteps through a wall has proven they spent the computational time to cross it, but they hold none of the keys to the space. You can walk into a building without having the keys.
This is desirable: it creates a natural asymmetry between those who hold a region (who have invested in Cantor computation and keep its keys, §7.8) and visitors (who have done the minimum work to cross the boundary). Holding is a capability, not a title: anyone who does the work holds the same keys, and no one is excluded. There are no domains in the base protocol; RATIONALE.md §6 says what holding does and does not buy, and docs/territory-conflict-game-layer.md records the design decision.
A crossing cannot be sold; a root can (non-normative). After §6.4 the two primitives are priced on opposite principles, deliberately.
A sidestep is a toll on an edge. Its proof is seeded to one chain position, so it is worth nothing to anyone else. There is no market in crossings, no pioneer subsidising later travellers, and no way to buy a wall crossing except by paying for it yourself.
A hop is a canonical price on a cell. Its work product is the region root of §4.6, whose hash is the region key of §7.2, and the one property that makes region keys work at all is canonical convergence (§4.9, property 4): two parties at the same place derive the same key without meeting. That property is what makes a key issuer-free, and it cannot survive seeding. A consequence is that an identity handed a region root can produce a hop across the corresponding boundary for the cost of the temporal axis alone, roughly 100 ms, at any height.
This is not an oversight and it is not a road in the sense §6.4 forbids. A region root is disclosed only when a holder chooses to disclose it, one recipient at a time, and that disclosure is exactly the act the protocol is built to support: handing someone the key to a place. The v1 sidestep published the equivalent to the whole world automatically, as a side effect of one traveller moving, with nobody choosing anything. Movement is priced; disclosure is a social act.
How much a handed root is worth, measured (non-normative). Less than it sounds, and the reason is worth stating because it decides what businesses are possible here (docs/what-can-be-sold.md).
A hop is four stages: build three axis trees, combine them into region_n, compute the temporal axis cantor_t, then bind and hash. A recipient skips only the first. What remains is not cheap, because a root at height h is a number of 85 × 2^h bits, and the combine multiplies numbers of that size: region_n is about four times an axis root and hop_n about eight times. The three pairings that remain are the three largest operations in the whole computation, and the millions of tiny pairings at the bottom of a tree, which are what the recipient was spared, are nearly free.
Measured, building a tree and then combining: a recipient handed all three axis roots saves about 19% of the work, stable across heights (19.7% at h12, 19.2% at h14, 19.0% at h16), and less than that with faster multiplication. The remainder is Θ(2^h) and includes cantor_t, which is derived from the recipient’s own previous_event_id (§5.3) and is therefore per traveller: it cannot be precomputed by a discloser, shared between recipients, or reused on a second hop.
So a handed root is a real saving on a real cost, not a bypass. A recipient still needs hardware of the discloser’s class to use one at all, since region_n at h47 is on the order of petabytes. This does not close the market in computation, only the market in reusable answers. Anyone may be paid to perform a traveller’s own work for them, and §6.4’s seeding is what keeps that market honest: because nothing can be resold, no operator can compute the world once and rent the archive forever.
6.13 Natural continents (non-normative)
The combination of Cantor hops and Merkle sidesteps creates emergent geography in the coordinate space:
- h ≤ ~20: Crossable by hop (Cantor) on consumer hardware in milliseconds to seconds. Above this the Merkle sidestep is faster at every height and is the practical movement primitive.
- h20–50: Crossable by sidestep on consumer hardware (seconds to days on a GPU).
- h50–58: Crossable by sidestep with a rented-GPU budget of roughly $20 to $3,000.
- h60+: Not crossable by consumer computation. Crossable by ASIC-scale hash work (a 1 EH/s farm crosses h78 in about a week; a Bitcoin-scale fleet crosses h85 in about a day). Hyperspace (DECK-0001) is the consumer route.
These prices are per traveller and per crossing. Because §6.4 seeds every sidestep tree to its mover’s chain position, a boundary that costs three GPU hours costs three GPU hours for the first identity to cross it and three GPU hours for the ten thousandth. The continents below are therefore a description of the space as every agent actually experiences it, rather than a description of what the first arrival paid.
Nobody designed these continents. They emerge from the interaction between the Cantor pairing function, SHA-256, and the physical limits of computation and storage. Different agents experience different continental boundaries depending on their hardware and patience. There is no single universal map of “passable” and “impassable” walls.
No arbitrary ceiling is designed. The boundary emerges from thermodynamics.
6.14 Performance expectations (non-normative)
Sidestep cost is dominated by SHA-256 leaf hashing and is fixed-size per leaf. Hop cost grows faster than 2× per height because the Cantor operands double in size as well as in count. Measured in the reference implementation (pure Python, same language for both): h14 hop 12 ms vs sidestep 27 ms; h16 100 ms vs 100 ms; h18 720 ms vs 270 ms; h20 6.6 s vs 1.1 s. Above roughly h16 the sidestep wins and its advantage widens by about 1.5× per height; a compiled bignum library is 5 to 10× faster for hops, and SHA-NI or a GPU is 50 to 10,000× faster for sidesteps, so the ordering only sharpens in optimized code.
| LCA Height | Hop (Cantor) | Sidestep (1 CPU core, 10⁸ H/s) | Sidestep (1 GPU, 2×10¹⁰ H/s) | Notes |
|---|---|---|---|---|
| h14 | ~10 ms | 0.3 ms | 1.5 μs | either |
| h20 | ~6 s | 21 ms | 0.1 ms | sidestep |
| h30 | ~days (11 GB root) | 21 s | 0.1 s | sidestep |
| h34 | ~a day (182 GB root) | 6 min | 1.6 s | sidestep; hop only when the region root itself is wanted |
| h40 | not feasible (12 TB) | 6 h | 100 s | sidestep |
| h50 | not feasible (12 PB) | 261 d | 28 h | sidestep |
| h55 | not feasible | 23 y | 38 d | sidestep, ~$700 of rented GPU |
| h60 | not feasible (12 EB) | 731 y | 3.3 y | ASIC-class hash work |
| h85 | not feasible | 2.5×10¹⁰ y | 1.1×10⁸ y | Bitcoin-scale ASIC fleet: about a day |
The practical consumer sidestep ceiling is about h55 per axis for a thousand dollars of rented GPU time. Sidestep work is plain SHA-256 over short preimages and is therefore subject to ASIC acceleration; the storage bound of §13.2 applies to Cantor roots, not to travel. Beyond consumer reach, hyperspace (DECK-0001) is the route.
Every figure in this table is now what each traveller pays on each crossing, and none of it can be prepared in advance (§6.7). The openings of §6.10 cost (SIDESTEP_SAMPLES + 1) × h × 32 bytes per non-trivial axis, which the mp tag carries as hex and so doubles: about 11 KB of tag text at h20, 23 KB at h40, and 26 KB at h47 for a single-axis crossing. A three-axis crossing at h47 approaches 80 KB and at h55 approaches 93 KB. Implementations SHOULD confirm their relays’ event size limits before attempting multi-axis crossings above h40.
6.15 Version 2 of the sidestep construction (normative)
The seeding of §6.4, the axis separation of §6.4, and the sampled openings of §6.10 are a breaking change to sidestep verification. They were adopted together because seeding without sampling is strictly worse than the construction it replaces (§6.10).
SIDESTEP_DOMAINis bumped fromCYBERSPACE_SIDESTEP_V1toCYBERSPACE_SIDESTEP_V2, so no v1 proof can be mistaken for a v2 proof or collide with one.- Verifiers implementing this revision MUST reject sidestep events built under the v1 construction. There is no grace period and no dual-acceptance mode: accepting v1 proofs would keep the pioneer-priced path open, which is the entire defect being repaired.
- Chains containing a v1 sidestep are invalidated from that event forward and must respawn. At adoption this affected three events published by two identities, all single-axis crossings at h19 and h20, which is why no migration path was specified.
Implementations of the base protocol prior to this revision computed leaves as SHA256(b"CYBERSPACE_SIDESTEP_V1" || leaf_bytes) with no seed and no axis separation, and published a single destination inclusion path. Those proofs are recognisable by their mp tag carrying exactly one path per axis rather than SIDESTEP_SAMPLES + 1.
Porting hazard: which leaf the path covers. v1 implementations diverged on this and the spec was not the tie-breaker it should have been. §6.10 and §8.7.2 have always specified the destination leaf, and that is what the two TypeScript ports and every sidestep so far published to a public relay actually use. The Python reference implementation named in §14 instead collected the path for leaf 0, the base of the aligned subtree, so its inclusion verifier rejects conforming events and accepts its own. Because the choice never entered region_m, it never affected proof_hash, which is why the divergence survived: the events verified at the proof level while their mp tags were mutually unreadable.
Implementations porting to v2 MUST use destination-leaf semantics for the first opening. The sampled openings make any remaining divergence self-correcting, since a verifier built on the wrong convention fails the sampled paths as well as the destination path, rather than silently ignoring both.
7. Location-Based Encryption and Discovery
7.1 The purpose: chalk on the sidewalk (non-normative)
The purpose of location-based encryption is not primarily secrecy. There are better cryptographic systems for secure communication. The purpose is to model traversable reality.
Consider a message written in chalk on a sidewalk:
- It is not “encrypted” in any technical sense
- Anyone who walks by can read it
- But you cannot read it without walking there
- Even if someone tells you about it, they had to walk there to know
This is location-gated access that requires no keys, no permissions, no infrastructure. Only presence.
Cyberspace implements this using region-derived keys. A ciphertext can be published publicly on Nostr, but deriving the decryption key requires computing the region preimage, which is the Cantor root for that spatial region. The work required is the same whether you traveled there via a movement chain or computed the region directly for an arbitrary coordinate. There is no free surveillance. Looking and walking cost the same.
7.2 Key derivation (normative)
Given a spatial region integer region_n (the 3D region identifier from §4.7 for some aligned region):
region_bytes = int_to_bytes_be_min(region_n)location_decryption_key = sha256(region_bytes)lookup_id = sha256(location_decryption_key)
Why two layers: Seeing lookup_id (which is published to help people find the content) does not allow deriving location_decryption_key without the region preimage. The lookup ID is safe to publish; the decryption key requires work.
Note (non-normative): the temporal axis used for hop proofs (§5) is intentionally not included here. Location-based identifiers and keys remain a stable function of spatial regions. They do not change when someone moves through.
Outputs are 32-byte digests. When used in Nostr tags, they MUST be lowercase hex.
7.3 Discovery radius (non-normative)
Cantor subtree roots represent aligned regions. Choosing a subtree height implicitly chooses a discovery radius: how large an area the encrypted content is visible from.
At the Cantor Height 34 scale (2 meters per height-34 subtree), approximate physical scales are:
| Height | Leaves (per axis) | Physical scale | Metaphor |
|---|---|---|---|
| 0 | 1 | ~10⁻¹⁰ m | Atomic precision |
| 10 | 1,024 | ~0.1 μm | Microscopic |
| 20 | ~10⁶ | ~0.1 mm | Grain of sand |
| 30 | ~10⁹ | ~0.1 m | Hand-span |
| 34 | ~1.7×10¹⁰ | 2 m | Human height (canonical) |
| 40 | ~10¹² | 128 m | City block |
| 50 | ~10¹⁵ | 131 km | City region |
A local secret at height 34 is discoverable within ~2 meters. At height 50, from anywhere in a city.
Important caveat: Discovery requires equivalent computation to secret creation. Typical scanning range is between height 0 and 16 for sub-second continuous scanning on average consumer hardware. Secrets at larger regions may be unattainable without some hint to help users scan up to their height. §7.7 defines hints: the hider MAY publish the aligned box a bag lies in, one height per axis, so a seeker sweeps the box instead of the space. As hardware improves, passive scanning range will grow and larger secret regions will become attainable.
The discovery radius grows exponentially with height, enabling a natural hierarchy of public, neighborhood, and intimate spatial messages.
7.4 Discovery scanning (recommended)
At a coordinate (x,y,z) you may scan nearby region keys by selecting heights h = 1..H and computing the aligned subtree base per axis:
bx = (x >> h) << hby = (y >> h) << hbz = (z >> h) << h
For each h, compute per-axis subtree Cantor roots:
rx = compute_subtree_cantor(bx, h)ry = compute_subtree_cantor(by, h)rz = compute_subtree_cantor(bz, h)
Combine to a 3D region integer:
region_n = π(π(rx, ry), rz)
Then derive lookup_id per §7.2.
Implementations SHOULD cap H for interactive use and may cache values; higher subtrees change less frequently.
7.5 Caching optimization (non-normative)
When moving, many higher subtrees do not change between positions. This provides a significant optimization for applications that scan multiple discovery radii.
Boundary crossing principle: An aligned subtree of height h changes only if you cross a boundary at that height.
Example (1D): Moving from position 7 to position 8:
Height Base (at pos 7) Base (at pos 8) Changed?
------- ---------------- ---------------- --------
0 7 8 YES (boundary at 8)
1 6 8 YES (boundary at 8)
2 4 8 YES (boundary at 8)
3 0 8 YES (boundary at 8)
4 0 0 NO (same [0..15])
5 0 0 NO (same [0..31])
... ... ... NO
Heights 4 and above are unchanged. Position 7 and 8 share the same base for h≥4.
Boundary detection:
def subtree_changes(v1: int, v2: int, h: int) -> bool:
return (v1 >> h) != (v2 >> h)
When (v1 >> h) == (v2 >> h), both positions lie in the same aligned subtree, so the cached region key remains valid.
Implementation strategy:
region_cache = {} # height -> (base, region_key)
def get_region_key(x, y, z, h):
bx = (x >> h) << h
by = (y >> h) << h
bz = (z >> h) << h
if h in region_cache:
cached_base, cached_key = region_cache[h]
if (bx, by, bz) == cached_base:
return cached_key # Cache hit
# Cache miss: compute and store
region_key = compute_region_key(bx, by, bz, h)
region_cache[h] = ((bx, by, bz), region_key)
return region_key
Performance implications:
- Small moves: most heights remain cached; only recompute low heights
- Large moves: more heights change, but high heights often remain the same
- Continuous scanning: avoid recomputing all 50+ region keys on every position update
Note: this caching optimization applies to spatial region computations for discovery. Hop proofs still require computing the temporal axis root per §5 on every hop.
7.6 The bag (normative)
Content hidden at a place is published as a bag. A bag is one Nostr event of kind 33330 (event format in §8.6) whose payload is encrypted with the key of one region at one height (§7.2), and it holds everything its author has hidden in that region at that height. Anyone can fetch a bag from a relay, because the ciphertext is public. Only someone who has computed the region’s key can open it, whether they computed it by moving into the region or by deriving it for the coordinate directly (§7.1). The bag is addressable by its lookup_id, so a relay keeps only the newest bag per author and region, and the author changes what is hidden there by publishing a newer bag.
Height (normative): the region’s height MUST be at least 1. A height-0 region is a single Gibson, and height 0 is the one case in which a root does not identify its height (§4.6); discovery scanning starts at height 1 (§7.4). A bag whose h tag (§8.6) is 0 MUST be rejected.
Cipher (normative):
- key: the
location_decryption_keyof §7.2 (32 bytes) - cipher: AES-256-GCM with a 12-byte nonce and a 16-byte tag, no additional authenticated data
- the nonce MUST be fresh for every encryption
payload = nonce || ciphertext || tag, encoded as base64 (standard alphabet, padded)
A reader without the region key cannot decrypt the payload. An attempt with the wrong key fails at the GCM tag check and reveals nothing about the plaintext. A failed decryption therefore means only that the reader does not hold this region’s key; it MUST NOT be treated as an error in the bag.
Plaintext (normative):
The plaintext is arbitrary bytes. The protocol places no requirement on it beyond the two shapes below, which tell a reader how to interpret what it has decrypted:
- A list of entries: a JSON array. Each element is either an item, a JSON object that is a nostr event carried inline, signed or unsigned, or a reference, a JSON array that is a tag naming an event published elsewhere. The JSON type tells them apart. Readers MUST try this shape first, because a list of entries is the shape clients render entry by entry. An element that is neither an event nor a reference is skipped.
- Opaque: anything that is not a list of entries, such as a text note or a file. Its interpretation is application-defined; a client may show it as text or offer it as a download.
Items (normative):
- An item is a nostr event. It MAY be signed. If it carries a
sig, itsidMUST be the canonical id (§8.2) and the signature MUST verify; a reader MUST drop an item that fails either check, and only that item, because one corrupt or forged item says nothing about the others. An item without asigis allowed, because some content is deliberately left unsigned; itspubkeyis then a claim, and readers MUST NOT present it as verified. - The bag’s
pubkeyis the key that placed the items in the region. Readers MUST attribute placement to the bag’s author, and MUST attribute authorship of an item’s content to the item’spubkeyonly when the item is signed. A signed item written by one key and hidden by another is therefore shown as that author’s content, placed here by the hider. - An item MAY carry a
Ctag:["C", "<coord_hex>"], its exact coordinate (§2), which lets a client render it at a point rather than somewhere in the region. If present, the coordinate MUST lie inside the region the bag is encrypted to: the same plane, and equal to the region’s base above heighth. Readers MAY drop an item whoseClies outside, because such an item claims a place its key does not cover. Without aCtag, an item is located no more precisely than the region. - A reader that does not understand an item’s
kindskips it and renders the rest.
References (normative):
A reference hides an event that is published on its own, so that the bag stays small and the referenced event has an id and an address of its own. It is one of two tags, in the shape NIP-01 gives them, with the entry’s exact coordinate as an optional fourth element:
["a", "<kind>:<pubkey>:<d>", "<relay hint>", "<coord_hex>"]names the event by its coordinate: its own kind, its author and itsdtag. A reader fetches the newest event at that coordinate, so the reference follows its author’s edits.["e", "<event_id>", "<relay hint>", "<coord_hex>"]names one event by its id. It is for events of a regular kind, which relays keep. It does not pin a version of an addressable event: NIP-01 lets a relay keep only the newest event at an address and discard the one it replaces, so anereference to an addressable event stops resolving once its author publishes over it. Name an addressable event by its coordinate.
The rules:
- The referenced event MAY be of any kind. Its kind is the kind in the
acoordinate, or the kind of the event theeid names; nothing in the bag restates it. - The relay hint MAY be the empty string.
<coord_hex>is the entry’s exact coordinate and follows the rules of theCtag above: it MUST lie inside the bag’s region, and a reader MAY drop a reference whose coordinate lies outside. It MAY be omitted, in which case the entry is located no more precisely than the region. - The referenced event is a partially encrypted event in the shape of Fanfares’ NIP FF-1: its
contentis a public preview for readers who cannot open it, and it carries exactly one["encrypted", "aes-256-gcm", "<payload>", "cyberspace:region"]tag. The payload is the event’s hidden content, encrypted with this bag’s region key under the cipher above. The fourth element,cyberspace:region, stands where FF-1 puts a key service URL and says that the key is derived from a place rather than issued: a reader MUST NOT make a network request for it. The event carries adtag, as FF-1 gives every partially encrypted event whatever its kind, which is what gives every referenced event a coordinate. - The referenced event MUST carry nothing that says where it is: no
C, noh, no hint and no sector tag. Those belong to the bag, which is what the region key protects. The event is a locked box in plain view; the bag is the note that says where the box stands. - Whoever opened the bag opens every event it references, with the key already in hand, and a reader that has not opened the bag can open none of them. What the decrypted payload means is defined by the referenced event’s kind; DECK-0003 §3.2 and §3.4 define it for objects.
- The referenced event is published, so it is signed like any other event, and a reader MUST verify it as usual. Placement is attributed to the bag’s author, and authorship of the referenced content to the referenced event’s
pubkey, which MAY differ from the bag’s: placing another author’s event is a placement. - A reference that cannot be fetched, or whose payload does not open with this bag’s key, is a missing entry. A reader drops it and SHOULD say that something was found but could not be retrieved, rather than that nothing was found.
Why a reference is a tag and not an item (non-normative). An item that only pointed elsewhere would repeat what the reference already says: its kind is in the coordinate, its author and time are the bag’s, and its content would be empty. What must survive is the pointer and the exact coordinate, and a tag carries both. Measured on an object reference, the item form is 304 bytes of JSON and the tag form 162, which matters because size is the reason references exist. NIP-51 lists already encrypt their private entries as a JSON array of tags, so the shape is familiar.
Note (non-normative): kinds in use. ONOSENDAI hides two kinds: 3330, a shard (geometry in content, the kind carried over from v1), and 1, a message (text in content). Both carry a C tag. New kinds need no change to this section: the container is the same, and a client that does not know a kind skips it. A large object is hidden by reference instead, as its own kind 33331 event (DECK-0003 §3.2).
Why one bag per region (non-normative): d is the lookup id, so there is exactly one bag per author, region and height, and it is addressable. A region accumulates content by rewriting its bag: the author decrypts the current bag, adds or removes items, and publishes the whole list again. This costs one event per change instead of one event per item, and it keeps the relay ignorant: a relay sees one ciphertext per region and never learns how many items the bag holds or what kinds they are.
Lifecycle (normative):
- To add or remove an entry, the author republishes the bag with the new list and a
created_atstrictly greater than the previous bag’s, because relays keep the newest addressable event and readers MUST do the same. - To remove the last entry, the author publishes a NIP-09 deletion (
kind = 5) with["e", "<bag_event_id>"]and["k", "33330"]. An empty bag would still occupy the region’s slot on the relay, so a bag holding a list of zero entries MUST NOT be published.
Note (non-normative): the reference CLI’s encrypt writes opaque plaintext (a text or a file); ONOSENDAI writes a list of entries. Both conform to this section, and each can open what the other publishes.
7.7 Hints (optional)
A bag is a cache of data encrypted by a region key at a height (§7.6) and it reveals nothing about where it is. Its lookup_id is a hash of a hash (§7.2), and discovery scanning (§7.4) reaches only a few heights around the scanner. Without more information, a bag is found only by intentionally deep scanning and/or wandering. With no additional information, any given bag is equally likely to be at any point in the full 2^256 coordinate space: an impossibly hardened secret.
A hint is the hider’s clue as to where the bag is. It is optional, public, and as coarse as the hider wants: the aligned box the bag’s region lies in, one height per axis, on one plane. The hint box is scalable along each axis; the bag is somewhere within the box. A seeker simply sweeps the box to find the secret, and the hider scales the hint to achieve the desired difficulty.
The hinted box (normative):
Hx,Hy,Hz: integers in[0, 85], the hint heightsbx = (x >> Hx) << Hx,by = (y >> Hy) << Hy,bz = (z >> Hz) << Hz: the aligned bases- the box is
[bx, bx + 2^Hx) × [by, by + 2^Hy) × [bz, bz + 2^Hz)on planeP
A height of 85 leaves an axis open: the base is 0, the box spans the whole axis, and the hint says nothing about that coordinate. Equal heights make a cube. Unequal heights make a slab or a column; an exact axis (height equal to the bag’s) with two coarse axes turns the search into two dimensions. Heights of 30 on all three axes name exactly one sector (§10).
The hint tag (normative):
["hint", "<coord_hex>", "<Hx>", "<Hy>", "<Hz>"]
coord_hex: the 256-bit coordinate of the box’s base(bx, by, bz, P), encoded per §2.2, 32-byte lowercase hex. It MUST be the aligned base: the lowHbits of each axis MUST be zero, and an axis withH = 85MUST be0. Requiring the base means every hider who hints the same box publishes the same tag, so readers can compare hints by equality.Hx,Hy,Hz: decimal strings, no sign, no leading zeros except"0"(the rules of §10).- A bag MUST carry at most one
hinttag.
What a hint claims (normative):
The bag’s region (the aligned cube of height h, where h is the h tag of §8.6) lies inside the box, on plane P. Because both the region and the box are aligned, containment is two checks per axis: H >= h, and the region’s base equals the box’s base after both are shifted right by H. When the bag carries an h tag, each hint height MUST therefore be at least h; a box smaller than the region could not contain it. Three heights equal to h name the region itself: the hint is then a destination the seeker can compute or walk to directly, not a search.
Sector tags (normative):
A hint fixes the sector on every axis with H <= 30, because the sector index is the axis value shifted right by 30 (§10) and the hint fixes every bit above H. For each such axis the bag MUST carry that axis’s sector tag (X, Y or Z), computed from the box’s base. When all three are fixed, the bag MUST carry S. An axis with H > 30 gets no sector tag, because its sector is not determined. A bag without a hint MUST NOT carry sector tags, because on a bag they are derived from the hint and would otherwise leak a location the hider did not choose to publish.
Malformed hints (normative):
A hint tag that breaks any rule above MUST be treated as absent, meaning the bag is read as if it carried no hint: wrong arity, bad hex, a base that is not aligned, a height outside [0, 85], a non-canonical integer, or a height below h. Sector tags that disagree with the hint MUST be ignored. A bad hint never invalidates the bag, because hints are advisory metadata about where to look; whether a bag is valid is decided by §7.2 and §7.6 alone.
Why the hint is a knob (non-normative):
A seeker who trusts a hint sweeps the box: for every candidate region of height h inside it, derive the region key (§7.2), compute its lookup_id, and check the relay for a bag with that d tag (one batched query can carry many lookup ids). The number of candidates is 2^((Hx - h) + (Hy - h) + (Hz - h)), the product of the choices on each axis. The seeker’s own position never enters this cost, because §7.1 makes looking and walking equivalent: a region key can be computed for any coordinate without traveling there. The gap between hint height and bag height, summed over the three axes, is therefore the price of the search, and the hider sets it when publishing the hint.
| Total gap | Candidates | Single core, bag at height 8 or below |
|---|---|---|
| 0 | 1 | a destination |
| 12 | 4,096 | seconds |
| 18 | 262,144 | minutes |
| 24 | 16.7 million | hours |
| 30 or more | a billion or more | days to never |
The times in the table assume the key cost measured on 2026-09-01 on one desktop core: about 0.05 ms per key at heights 0 to 4, 1.3 ms at height 8, 30 ms at height 12 and 816 ms at height 16. Because a key at a greater height costs more to derive, the same gap takes longer for a bag hidden at a greater height. A sector-only hint (H = 30 on every axis) on a bag at height 5 is a gap of 75, about 2^75 candidates, which no one will sweep; such a hint tells a seeker where to travel, not where to search.
Reading is not reaching (non-normative):
Anyone who does the work of deriving the key can read the bag from anywhere; reading has no distance term. Being inside the region is a separate fact: it costs the movement work of §4 to §6 to get there, and only a movement chain whose head lies inside the region proves it (§8). An application that rewards finding a bag has to say which of the two it rewards, reading or reaching. How a find is proven in public is left to applications and DECKs (§8.9).
A hint is a claim (non-normative):
Nothing in the protocol verifies a hint until the bag is found and its region is compared with the box. A false hint wastes the seeker’s work, and the protocol does not punish it. Applications should weigh a hint by the reputation of the key that published it, as they would any other unverified claim.
Riddles (non-normative):
The bag’s content field MAY hold a plaintext hint written for humans, a riddle (§8.6). A riddle can stand alone or accompany a geometric hint. The protocol does not read it; it is for the seeker to interpret.
Golden vectors:
Produced and checked by hint-reference.py. Points are 256-bit coordinates per §2.2 (32 bytes of hex). london is the §9.8 golden vector.
| Vector | Point (plane) | Bag h | Heights | hint coordinate | Sector tags | Candidates |
|---|---|---|---|---|---|---|
london_h5_box11 | london (0) | 5 | 11, 11, 11 | c492492492492492492492edf5bee7267451c787d95ba4d7840c76d000000000 | X 18014398541305938, Y 18014398549232983, Z 18014398509410999, S 18014398541305938-18014398549232983-18014398509410999 | 2^18 |
london_h5_x_exact | london (0) | 5 | 5, 14, 14 | c492492492492492492492edf5bee7267451c787d95ba4d7840c749041240000 | same as above | 2^18 |
ideaspace_h8_y_open | a4b64924924924924924924924924924924924924924924924924d84b60d9c8f (1) | 8 | 12, 40, 12 | a4b64924924924924924924924924924924924924924924924924d8000000001 | X 18014398509481984, Z 36028797018963967, no Y, no S | 2^40 |
The ideaspace point is x = 2^84 + 12345, y = 3 · 2^80 + 777, z = 2^85 - 1 - 4242 on plane 1. The second vector is a two-dimensional hunt: X is exact, so the seeker sweeps a 2^9 by 2^9 slab of height-5 regions. The third has an open axis: Hy = 40 leaves the Y sector undetermined, so the bag carries X and Z but neither Y nor S, and the sweep is 2^40 candidates, far beyond any search; that hint tells the seeker where to travel.
7.8 Holding a region (non-normative)
Computing a region’s Cantor root produces every intermediate node of the per-axis trees on the way up, and every intermediate node is itself the root of an aligned sub-region (§4.5). Whoever keeps those nodes on disk holds the region: the per-axis roots of every aligned sub-region inside it, at every height, are on hand, so any location-based key inside the holding costs one pairing step (§4.7, §7.2) instead of a tree. Holding is §7.5 carried to its limit.
What holding buys is latency, and only latency:
- reading content published anywhere in the region at any height without per-item work, where a passerby’s interactive scan reaches roughly h16 (§7.3);
- writing at any height in the region without rebuilding a tree;
- deriving a two-factor key,
KDF(location_decryption_key || owner_secret), for content that must be both found by presence and unlocked by the holder.
“Without rebuilding a tree” is not “at once”, and earlier drafts of this section said “at once” (non-normative). A holder skips the tree build and still pays the combine, which operates on numbers of 85 × 2^h bits and is measured at 0.76 to 1.03 times a full axis build. Holding therefore buys about 4.9 times, roughly two heights, not an instant answer: a held key at h34 is still on the order of hours on a desktop. That is a large and real advantage over not holding, and it is a latency advantage rather than a capability one, exactly as the rest of this section says. §6.12 works the same arithmetic from the other side, and docs/what-can-be-sold.md works through what both mean for anyone building on this.
What holding does not buy: exclusivity (anyone who does the work holds identical keys), any advantage in observing chains (they are public to everyone), or any effect on anyone else. It is a keyring, not a claim.
What holding costs is disk. Each level of a per-axis tree is about as large as its root (§9.9), so the full trees for three axes are about 3 × (h + 1) roots: roughly 19 TB at h34, 1.5 PB at h40, 107 PB at h46. Keeping only the nested cubes that contain one position costs about two roots per axis, roughly 1.1 TB at h34. Stop paying and the keys are a recomputation away again; nothing else happens.
There are no domains in the base protocol. Holding is the whole of what the protocol offers toward territory. Claims, exclusion and governance are left to applications and games; see RATIONALE.md §6 and docs/territory-conflict-game-layer.md.
8. Nostr Integration: The Movement Chain
Cyberspace uses Nostr as its transmission layer. Movement is represented as a per-pubkey, linear hash chain of signed Nostr events. This means Cyberspace does not require new network infrastructure. It composes on top of existing relays.
The state of Cyberspace is the sum of all cyberspace-related Nostr events. Because Nostr is decentralized and permissionless, knowledge of global state is not possible, just as in physical reality.
8.1 Event kind
- Movement events:
kind = 3333
8.2 Canonical event id (NIP-01)
The event id MUST be computed as NIP-01 canonical serialization:
- Serialize:
[0, pubkey, created_at, kind, tags, content] - Encode as UTF-8 JSON with no whitespace (equivalent to Python
json.dumps(..., separators=(",", ":"), ensure_ascii=False)) - Hash:
sha256(serialized_bytes)
Signature (sig): For published events, sig MUST be a valid Schnorr signature over the event id as required by NIP-01.
Note: some prototypes may leave sig blank for local-only chains and sign at publish-time; that is not a wire-format requirement.
8.3 Spawn event (first event)
The spawn event declares “I exist at this coordinate.” It is the first event in a keypair’s movement chain.
Required tags:
Atag:["A", "spawn"]Ctag:["C", "<coord_hex>"]coord_hexMUST be a 32-byte lowercase hex string (64 hex chars, no0xprefix)- For spawn events,
coord_hexMUST equal the eventpubkey(spawn coordinate)
- Sector tags:
X,Y,Z,S(per §10)
8.4 Hop event
A hop event extends the movement chain by one Cantor pairing tree proof.
Required tags:
Atag:["A", "hop"]egenesis:["e", "<spawn_event_id>", "", "genesis"]eprevious:["e", "<previous_event_id>", "", "previous"]ctag:["c", "<prev_coord_hex>"](32-byte lowercase hex string)Ctag:["C", "<coord_hex>"](32-byte lowercase hex string)prooftag:["proof", "<proof_hash_hex>"](32-byte lowercase hex string)- Sector tags:
X,Y,Z,S(per §10)
8.5 Sidestep event
A sidestep event extends the movement chain by one Merkle hash tree boundary crossing.
Required tags:
Atag:["A", "sidestep"]egenesis:["e", "<spawn_event_id>", "", "genesis"]eprevious:["e", "<previous_event_id>", "", "previous"]ctag:["c", "<prev_coord_hex>"](32-byte lowercase hex string)Ctag:["C", "<coord_hex>"](32-byte lowercase hex string)prooftag:["proof", "<proof_hash_hex>"](32-byte lowercase hex string)mrtag:["mr", "<M_x_hex>:<M_y_hex>:<M_z_hex>"](per-axis Merkle roots, colon-separated, each 64 hex chars)mptag:["mp", "<openings_x_hex>:<openings_y_hex>:<openings_z_hex>"](per-axis openings, colon-separated)hxtag:["hx", "<lca_height_x>"](LCA height on X axis, decimal string)hytag:["hy", "<lca_height_y>"](LCA height on Y axis, decimal string)hztag:["hz", "<lca_height_z>"](LCA height on Z axis, decimal string)- Sector tags:
X,Y,Z,S(per §10)
Openings encoding: Each per-axis segment in the mp tag is the concatenation of SIDESTEP_SAMPLES + 1 inclusion proofs in the order defined by §6.10 (destination first, then samples in ascending i). Each proof is h sibling hashes from leaf to root, hex-encoded, so an axis with LCA height h contributes exactly 64 × h × (SIDESTEP_SAMPLES + 1) hex characters. For trivial axes (h = 0), the segment is an empty string between colons.
Because the segment is fixed-width given h, a verifier reads the per-axis hx, hy, hz tags and splits the segment without ambiguity. A segment whose length is not an exact multiple of 64 × h is malformed and the event MUST be rejected; a segment of exactly 64 × h characters is a v1 proof and MUST be rejected per §6.15.
Height tags: The hx, hy, hz tags enable verifiers to determine expected proof lengths without re-deriving LCA heights from coordinates.
8.6 Encrypted content event (bag)
A bag publishes content encrypted to one region at one height (§7.6). Its cipher, plaintext, items and lifecycle are defined in §7.6; this section gives the event format, as §8.3 to §8.5 do for movement.
- Encrypted content events:
kind = 33330 kind 33330is addressable: relays keep the newest event per(pubkey, kind, d)
Required tags:
dtag:["d", "<lookup_id_hex>"](32-byte lowercase hex string, thelookup_idof §7.2)encryptedtag:["encrypted", "aes-256-gcm", "<payload_base64>"](the payload of §7.6)versiontag:["version", "2"]versionnames the rules of §7.6. A reader MUST ignore a bag whose version it does not know.
Optional tags:
htag:["h", "<height>"](decimal string): the height of the region whose key encrypts the content, which is the discovery radius of §7.3; at least 1 (§7.6)hinttag:["hint", "<coord_hex>", "<Hx>", "<Hy>", "<Hz>"]: the hider’s coarse statement of where the bag can be found (§7.7)- Sector tags
X,Y,Z,S: required on a bag that carries ahinttag, for each axis whose hint height is at most 30 (§7.7, §10); MUST NOT appear otherwise
Content: MAY carry plaintext meant for humans, a riddle (§7.7). The protocol does not interpret it.
8.7 Verification summary
8.7.1 Hop verification
To verify a hop:
- Parse previous and current coords; decode to
(x1,y1,z1,plane)and(x2,y2,z2,plane). - Plane changes are valid in v2; verifiers MUST support hops where
plane1 != plane2. - Compute the stable spatial region integer
region_nper §4.7. - Derive the terrain-based temporal height
Kfrom the destination coordinate(x2,y2,z2,plane2)per §5.2 (including the destination plane bit). - Compute the temporal axis root
cantor_tfrom the hop event’sprevious_event_id(etag with markerprevious) andKper §5.3. - Compute
hop_n = π(region_n, cantor_t)per §5.4. - Compute
proof_hashper §5.6. - Accept iff it matches the event’s
prooftag.
8.7.2 Sidestep verification (Level 1: sampled openings)
To verify a sidestep (Level 1, sampled openings check):
- Parse previous and current coords; decode to
(x1,y1,z1,plane)and(x2,y2,z2,plane). - Validate crossing geometry: for each axis, confirm the destination is exactly 1 Gibson past the LCA boundary (§6.3). Verify the
hx,hy,hztags match the computed LCA heights. - Parse per-axis Merkle roots from the
mrtag. Readprevious_event_idfrom theetag with markerprevious. - For each axis where movement occurs:
a. Build
seed_prefix = SIDESTEP_DOMAIN || previous_event_id || axis_byte || SEED_PADper §6.4, and the aligned basebase = (v1 >> h) << h. b. Split the axis segment of themptag intoSIDESTEP_SAMPLES + 1proofs ofhsiblings each; reject if the length does not match (§8.5). c. Compute the destination leaf hashH_dest = SHA256(seed_prefix || int_to_bytes_be_min(v_dest))and verify its path to the claimed rootM_axis. d. Derive the sample indices fromM_axisper §6.10. For eachidx_i, computeSHA256(seed_prefix || int_to_bytes_be_min(base + idx_i))and verify its path toM_axis. - Compute
region_m = π(π(mx, my), mz)from the claimed Merkle roots (§6.6). - Derive
Kandcantor_tfrom destination coordinate andprevious_event_id(§6.7, same as hop). - Compute
sidestep_n = π(region_m, cantor_t)andproof_hashper §6.8. - Accept iff it matches the event’s
prooftag.
A verifier that skips step 4d is performing a strictly weaker check than v1’s, not an equivalent one, because after seeding there is no canonical root to compare M_axis against (§6.10).
Level 2 (full root) verification is described in §6.11.
8.8 Core action types summary
The base Cyberspace v2 protocol defines three movement action types:
A tag value | Description | Proof type | Defined in |
|---|---|---|---|
spawn | Identity placement at pubkey-derived coordinate | None (identity proof) | §8.3 |
hop | Movement via Cantor pairing tree | Cantor root (§4.6) | §8.4 |
sidestep | Boundary crossing via Merkle hash tree | Merkle root (§6.4) | §8.5 |
All three use event kind = 3333.
8.9 Protocol extensions (DECKs)
This specification defines the base Cyberspace v2 protocol.
Optional extensions MAY introduce new event kinds, new movement action types (A tag values), and/or additional validation rules that are only applied when an extension is in use.
Extensions are specified as Design Extension and Compatibility Kits (DECKs) in the decks/ directory.
- Hyperspace extension (DECK-0001):
decks/DECK-0001-hyperspace.md
8.10 Avatar event
An avatar is the shape an identity is drawn as. It is the one thing in cyberspace that lands on other people’s screens whether they asked for it or not: everyone near its owner sees it, at every zoom. Its size and its detail are therefore paid for in work, on the event that publishes it, and a client draws nothing it cannot verify has paid.
- Avatar events:
kind = 11333 kind 11333is replaceable: relays keep the newest event per(pubkey, kind), so an identity has exactly one avatar and the newest replaces it
An avatar is one per identity, which is what a replaceable event is for. This was kind 33331, addressable, with a d tag fixed at "avatar": a constant d is emulating replaceable semantics with the wrong tool, and it asks every reader to trust a convention where the relay could enforce the rule. Everything in nostr that is one per person is replaceable, including metadata, contacts, relay lists and DM relay lists; addressable is for the other case, many per author told apart by d, as long-form articles and wiki pages and classified listings are.
The change is a clean break rather than a migration. At the time it was made, a query for kind 33331 across twelve relays, including the one this protocol uses, returned nothing, while the same query returned bags and movement events. An identity that had adopted an avatar without publishing it re-mines once, which is the work it paid the first time.
11333 was chosen by querying for it rather than by reading a registry. The registry of kinds and the NIPs index both list only what has been written down, and the replaceable range is full of numbers that are in daily use and appear in neither: 10333 carries Podcasting 2.0 favourites, 10086 a relay indexer’s lists across eighty pubkeys, 10088 something Amethyst writes. A kind is free when the relays say so.
Required tags:
noncetag:["nonce", "<nonce>", "<target>"]per NIP-13, wheretargetis the leading zero bits the publisher committed to before mining
Optional tags:
nametag:["name", "<name>"], the shape’s name for humans
Content: a shard payload as bags carry them, or empty. The work reads these fields of it: unit (a model unit is 2^unit gibsons), vertices (whole units per vertex), ticks (the fraction of a unit per vertex, in 120ths, packed so that -N stands for N zero triples) and faces. Empty content means the default avatar and owes no work.
No d tag: a replaceable kind has no second key, and writing one would only invite a reader to filter on it.
The work (normative):
AVATAR_FLOOR_BITS = 16 # every avatar
AVATAR_SIZE_BITS = 2 # per doubling of reach
AVATAR_DETAIL_BITS = 3 # per doubling of detail beyond the free thirty-two
AVATAR_DETAIL_FREE = 32
def avatar_work(payload) -> int:
# reach: the farthest any vertex lies from the build origin, on any axis,
# in gibsons at true scale; never below one gibson
reach = max(1.0, max(abs(v + t / 120) for every vertex coordinate) * 2 ** payload.unit)
detail = max(AVATAR_DETAIL_FREE, len(payload.vertices) + len(payload.faces))
return ceil(AVATAR_FLOOR_BITS + AVATAR_SIZE_BITS * log2(reach) + AVATAR_DETAIL_BITS * log2(detail / AVATAR_DETAIL_FREE))
An avatar event is paid when its nonce tag’s committed target is at least avatar_work(content) and its id carries at least target leading zero bits. Both conditions are required: committing the target before mining is what stops a lucky id from being claimed against a lower bar than it was mined for (NIP-13).
Verification: parse the content, compute avatar_work, read the nonce tag, count the id’s leading zero bits, apply the two conditions. One hash and a walk over the vertices, on any device.
Drawing (normative): a client MUST NOT draw an avatar event that is not paid, or that carries content it cannot read; it draws its default avatar for that identity instead. A client SHOULD draw a paid avatar at true scale, one model unit as 2^unit gibsons, wherever it would draw the default.
Why the price is shaped this way (non-normative). Reach is priced at two bits per doubling, so a shape twice as far across costs four times the work, and the ladder it makes is: one gibson 16 bits, two gibsons 18, four 20, sixteen 24, a thousand 36, and one the size of cyberspace about 190, which is to say never. Reach is the term that matters to other people, since a large avatar is the one that gets in everyone’s way, and the slope is set so that a modest shape of a few gibsons costs minutes on a phone while a sector-sized one is out of reach of any hash power. Detail is priced at three bits per doubling beyond thirty-two vertices and faces, because a busy small avatar troubles nobody much, and because bytes are charged already without a term: every nonce hashes the whole serialized event, so an avatar at the vertex and face caps runs about thirty times more slowly per try than a plain one. The sixteen-bit floor is seconds on a phone today and is set with a hundred-year horizon of growing hash power in mind; it is the one constant this section expects to be revisited. Reach is measured from the build origin, not from the shape’s own centre, so a shape is priced as its builder placed it against the reference avatar on the bench.
Reference implementations: avatar.ts in cyberspace-core and cyberspace_core/avatar.py in cyberspace-cli, pinned to one set of golden vectors.
9. Mapping to Physical Reality: GPS and Dataspace
9.1 Why a physical mapping exists
Dataspace (plane=0) maps WGS84 GPS coordinates (latitude/longitude/altitude) into the u85 axis space. This creates a cryptographic overlay on the physical world where Cyberspace coordinates correspond to actual locations on and around Earth.
This mapping is consensus-critical: if multiple clients are expected to agree on the same coord256 for a given GPS point, they must all use the exact same deterministic algorithm.
The mapping applies only to dataspace. Ideaspace (plane=1) has no GPS mapping.
9.2 Dataspace cube size (Cantor Height 34 scale)
The Cantor Height 34 scale defines the relationship between dataspace coordinates and physical distances:
- Full axis length: ~4.5 trillion kilometers (~0.48 light-years)
- Half axis length: ~2.25 trillion kilometers
- Gibson size: ~1.16×10⁻¹⁰ meters (approximately the diameter of a hydrogen atom)
- Cantor Height 34 = 2 meters (the canonical scale parameter)
This scale provides “atomic” granularity in dataspace while maintaining axis extents that vastly exceed the geosynchronous orbit requirement. The universe starts small and extends far.
9.3 Scale rationale (non-normative)
The Cantor Height 34 scale was chosen through rigorous testing to balance several concerns:
For consumers: At this scale, consumer hardware can traverse human-centric distances and derive useful location-based secrets with significant but achievable effort. Moderate cloud compute expenditure ($200–$1,000) extends range substantially.
Against nation-states: Cantor root cost scales with the side length of the aligned cube, per axis (85 × 2^h bits): a person (h34) is 182 GB, a 7 km city (h46) is 748 TB, a 262 km country (h51) is 24 PB, an Earth octant (h57) is 1.5 EB, and the GEO cube (h60) is 12 EB, against roughly 10 to 20 ZB of installed world storage. A country-scale root is within reach of a well-funded organization today and an Earth-scale root is within reach of a hyperscaler or a state. This is structural rather than a calibration choice: a country is only 2^17 times wider than a person, while the storage gap between a consumer and a state is about 2^20, so any scale that keeps human-scale hops feasible for consumers keeps country-scale roots feasible for states. The scale therefore does not deliver a century-long guarantee against large regions being held. Holding a region costs disk for as long as it is held (§7.8), which is the protocol’s whole maintenance economics; claims, exclusion and governance are not protocol matters and are left to applications and games.
The gap is fixed, and the calibration cannot widen it (non-normative). The argument above is right and can be made exact. A root is 85 × 2^h bits per axis, so the highest height a storage budget B reaches is h_max ≈ log2(B) − 6.018. The calibration term does not appear in that equation and cannot: the calibration maps heights to meters, the cost function maps heights to bytes, and the two compose without interacting. The distance between what an individual reaches and what a state reaches is therefore log2(B_state / B_consumer) whatever the calibration. Measured at 16.7 heights for a mainstream desktop, 13.0 for an enthusiast rig and 22.0 for a phone. Changing the calibration slides a window of fixed width; it never widens it, and which consumer you mean moves the answer by 9 heights, which is more than any calibration change ever proposed here.
The shelf life, with a number (non-normative). On central storage-growth assumptions a one billion dollar budget reaches the root of the whole Earth around 2045, or 2036 optimistic, or never if the 2025 to 2026 storage price reversal proves permanent. Every ceiling past about one day of patience is capacity-bound, so compute growth barely moves these dates and storage price is the only sensitive input. No calibration changes them meaningfully, because the window is rigid.
Changing the calibration is not a hard fork (non-normative). It is one constant in §9.7 step 9. No chain is invalidated, no proof breaks, nobody respawns; it is a smaller break than the sidestep v2 change already shipped. The reason not to change it is therefore not difficulty but consensus: two incompatible Earths is worse than either Earth, and that reason does not expire. Anyone proposing a change should be answered with the paragraph above rather than with the cost of making it.
Aesthetics:
- 2 meters is a metaphor for the human scale of the universe
- Cantor Height 34 / 85-bit axis = 34/85 = 0.4 = 2/5, a rational and memorable relationship
- Cantor Height 33 = 1 meter
- 1 Gibson is roughly the size of a hydrogen atom, the first atomic element
No difficulty adjustment. Unlike Bitcoin, Cyberspace has no difficulty adjustment mechanism. The scale is fixed by mathematical definition. A coordinate’s Cantor tree is deterministic. It cannot be made “harder” without changing the coordinate itself. As technology advances, all parties gain greater computation and storage, gradually increasing the scale of territorial claims over time. The base protocol remains stable; difficulty migrates upward through extension mechanisms (DECKs) over decades.
9.4 Axis naming convention (ECEF → Cyberspace)
Starting from standard Earth-Centered Earth-Fixed (ECEF):
+X_ecef: (lat=0°, lon=0°)+Y_ecef: (lat=0°, lon=+90°)+Z_ecef: north pole
Cyberspace dataspace axis naming is:
X_cs = X_ecefY_cs = Z_ecefZ_cs = Y_ecef
9.5 Canonical spec version and deterministic arithmetic
Spec version string (required):
CANONICAL_GPS_TO_DATASPACE_SPEC_VERSION = "2026-03-16-h34-corrected"
Canonical requirements:
- Use decimal arithmetic end-to-end (no platform
libmfor trig). - Decimal context:
- precision:
96 - rounding:
ROUND_HALF_EVEN
- precision:
- π constant: use this exact truncated decimal string:
PI_STR = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
- Deterministic trig:
- Termination epsilon:
TRIG_EPS = 1e-88 - Max iterations:
TRIG_MAX_ITER = 256
- Termination epsilon:
9.6 Altitude handling (normative)
The canonical mapping is defined for latitude/longitude plus an optional altitude in meters.
- If altitude is omitted, implementations MUST treat
altitude_m = 0. - Implementations MUST support “clamp to surface” behavior that forces
altitude_m = 0(this is what the golden vectors cover). - If non-zero altitude is supported,
altitude_mMUST be interpreted as meters above the WGS84 ellipsoid and processed using the same canonical decimal parsing rules.
9.7 Canonical mapping algorithm (normative)
-
Parse inputs as decimals.
-
Clamp latitude to
[-90, 90]. -
Wrap longitude to
[-180, 180). -
Convert degrees→radians using
PI_STR. -
Compute deterministic
sin/cosusing range reduction + Taylor series, terminating whenabs(term) < TRIG_EPS. -
Convert WGS84 geodetic to ECEF (meters) using decimals (using
altitude_mafter clamping, if applicable). -
Convert meters→kilometers.
-
Permute ECEF axes into Cyberspace axes per §9.4.
-
Convert kilometers-from-center into u85 axis values:
units_per_km = 1000 * 2^33(derived from Cantor Height 34 = 2 meters)u = km * units_per_km + 2^84- round using
ROUND_HALF_EVEN - clamp to
[0, 2^85 - 1]
Derivation: At Cantor Height 34 scale,
2^34Gibsons = 2 meters. Therefore 1 Gibson =2^-33meters, and 1 km =1000 * 2^33Gibsons. This formula maps GPS coordinates into a region centered at u85 value2^84(the half-axis point). -
Produce coord256 with
plane=0using the interleaving in §2.
Note (non-normative): the three h85 planes. Because the mapping centers Earth at exactly 2^84 on every axis, the equatorial plane (Y_cs = 2^84) and the planes X_cs = 2^84 and Z_cs = 2^84 (the meridians at 0°/180° and ±90° longitude) are h85 boundaries: a one-metre step across the equator costs the same as crossing the whole axis. They behave like oceans, uncrossable on foot and crossed routinely through hyperspace (DECK-0001), whose landfalls sit on both shores. Only points within one last mile of a plane (about 0.75% of the surface at current landfall density) ever notice them. The centering is kept for symmetry; an offset of 2^56 would lower the walls to h57 and remains available as a future revision if landfall density is ever reduced.
9.8 Golden vectors (consensus locks)
An implementation of §9.7 MUST reproduce every vector below exactly. The mapping is consensus-critical (§9.1): two clients that disagree on a vector disagree on where a place is. Implementations SHOULD include the vectors as tests, to catch accidental drift.
These are the required vectors for spec version 2026-03-16-h34-corrected (hex is 32 bytes, no 0x prefix).
Golden vectors assume altitude_m = 0 with clamp-to-surface behavior enabled:
origin_equator_primelat=0 lon=0e000000000000000000001200041040208048040000000000000000000000000
equator_east_90lat=0 lon=90e000000000000000000000480010410082012010000000000000000000000000
equator_west_90lat=0 lon=-90c492492492492492492492012482082410480490000000000000000000000000
north_polelat=90 lon=0e000000000000000000000900004924920020000820000920100824920800020
londonlat=51.5074 lon=-0.1278c492492492492492492492edf5bee7267451c787d95ba4d7840c76d1e33c9940
nyclat=40.7128 lon=-74.0060c4924924924924924924921f79235dae293ada913e78294253a235239a332854
9.9 Consumer benchmarks (non-normative)
Cantor root cost scales with the side length of the aligned cube, per axis, not with its volume: a region twice as wide costs twice as much storage and somewhat more than twice the time. The root of a height-h subtree over 85-bit leaves is exactly 85 × 2^h bits, which is checkable at any small height and holds without rounding: each level of pairing doubles the operand width and halves the count.
On peak memory, corrected (non-normative). An earlier draft said construction needs “roughly two levels live at once”, which would make the peak about twice a root. Measured, the peak is about 6.1 times a root. Two levels live is the floor a perfect implementation would reach, not what a real one does: producing a level allocates the new values before the old ones can be released, the multiply itself needs working space of the same order as its operands, and a garbage-collected runtime holds both longer still. Size a machine against 6.1 roots, not 2.
| Region | Aligned height | Root per axis | Consumer feasibility |
|---|---|---|---|
| 1 m cube | h33 | 92 GB | hours to a day on a desktop with a fast SSD |
| 2 m cube (canonical) | h34 | 182 GB | about a day |
| 4 m cube | h35 | 370 GB | days |
| 128 m block | h40 | 12 TB | external storage array, weeks |
Notes:
- A 50 m³ region is h35, twice the cost of h34, not fifty times. Volume-linear accounting overstates small claims and understates large ones.
- Computation is parallelizable across axes and levels; storage and I/O bandwidth are the binding constraints.
- Contiguous claims are significantly more efficient than discrete parcels due to Cantor subtree structure sharing.
9.10 Nation-state limits (non-normative)
| Root side | Height | Root per axis | Feasibility |
|---|---|---|---|
| City (7 km) | h46 | 756 TB | a well-funded organization |
| Country (262 km) | h51 | 24 PB | a state or a large company today (about a million dollars of disks, days of I/O) |
| Earth octant (16,777 km) | h57 | 1.5 EB | a hyperscaler or a state |
| GEO cube (134,000 km) | h60 | 12 EB | about 0.1% of installed world storage |
| h70 | h70 | 12.7 ZB | roughly all installed storage today |
The limiting factor on how high a party can go is storage capacity, and the protocol’s work equivalence property ensures the data cannot be optimized away. There is no ASIC advantage, because no chip removes the need to hold the intermediates. The limiting factor on how long it takes beneath that ceiling is arithmetic rather than I/O bandwidth: multiplying two n-bit numbers costs far more than one operation per byte, and the work is measured at about 28 times compute-bound. §13.2 says more. Sidestep travel (§6) is plain hash work and has no such protection; see §12.3.
9.11 Storage as the primary constraint (non-normative)
Cantor tree computation is memory-bound. At Cantor Height 34, a single subtree contains 2³⁴ ≈ 17 billion leaf nodes. The intermediate values cannot fit in RAM and must be streamed to disk.
This is intentional. Capacity is the equalizer (and see §13.2 on why the wait beneath the ceiling is arithmetic rather than bandwidth):
- Consumer SSDs provide enough I/O for small roots
- Nation-states have faster storage, but exponential growth limits scaling
- There is no “ASIC advantage” because the bottleneck is data movement, not hash rate
The storage constraint ensures that territorial roots remain bounded by physical infrastructure, not just financial resources.
10. Sectors and Spatial Querying
A sector is a cube of 2^30 Gibsons per axis. Sectors exist to divide Cyberspace into manageable pieces and, critically, to allow proximal querying of public Cyberspace objects on Nostr relays.
On the index’s width. An axis runs to 2^85 Gibsons and a sector is 2^30 of them, so a sector index is 2^55 values wide and needs 55 bits per axis, not 32. An earlier draft of this paragraph said sectors “fit into u32 systems”, which is wrong by 23 bits; a u32 would cover only the first 2^32 sectors of 2^55 on each axis. The tag values in §10 are decimal strings and are unaffected, but an implementation storing a sector index in a 32-bit integer will silently wrap. Use 64-bit integers, or the strings themselves.
On what a sector is useful for. At the calibration of §9 a sector is 2^30 Gibsons, which is about 12.5 cm. A single-sector query therefore asks what is inside a box the size of a shoebox, and a single-axis query asks about a 12.5 cm slab through the whole universe. Proximal querying at a scale a person perceives, a room or a street, is not expressible as a sector query and needs a tag ladder at several heights, which this document does not define.
Because Nostr cannot query “prefix ranges” on tag values, per-axis sector tags make it possible to query slices along a single axis.
Given x_u85, y_u85, z_u85:
sx = x_u85 >> 30sy = y_u85 >> 30sz = z_u85 >> 30
All events that claim a coordinate MUST include:
Xtag:["X", "<sx>"]Ytag:["Y", "<sy>"]Ztag:["Z", "<sz>"]Stag:["S", "<sx>-<sy>-<sz>"]
Tag formatting rules (normative):
sx,sy,szMUST be encoded as base-10 integers (strings), with no leading+and no leading zeros (except"0").SMUST be exactly"<sx>-<sy>-<sz>".
Encrypted content events (kind 33330): a bag without a hint claims no coordinate and carries no sector tags. A bag with a hint (§7.7) claims a box. It MUST carry the sector tag of each axis whose hint height is at most 30, computed from the box’s base, and S when all three are fixed. A relay query on #S, or on one axis tag, returns the hinted bags in a sector or a slice the same way it returns movement.
11. Visualization Conventions
This section defines canonical conventions for rendering Cyberspace coordinates in 3D visualizers. The goal is to ensure that different viewers agree on orientation (left/right, up/down, ahead/behind).
These conventions are about visualization only. They do not change coordinate encoding (§2) or movement proof verification (§4–§8).
11.1 Handedness and axis semantics
Implementations that render Cyberspace in 3D MUST preserve the Cyberspace axis semantics defined in §9.4 exactly.
Graphics-engine handedness and camera defaults are implementation details and MUST NOT change Cyberspace semantics.
When the viewer is oriented per §11.3:
+X_csis screen-right.+Y_csis up.+Z_csis forward (toward the black sun / east reference marker).
11.2 Black sun reference marker
The “black sun” is a reference to the hacker haven in Neal Stephenson’s Snow Crash, one of the foundational works that inspired the Cyberspace Protocol. In Cyberspace, the black sun serves a simple practical purpose: it is a subtle guidepost so you know which direction you are facing. It is rendered as a purple circle marking the +Z_cs boundary of the coordinate space.
If a visualizer renders the black sun, it MUST place it on the +Z_cs boundary of the Cyberspace cube.
The +Z_cs boundary is the Z axis maximum, 2^85 - 1 (§2.1). The marker is placed at the center of that face:
black_sun_u85 = (x=2^84, y=2^84, z=2^85 - 1)in u85 coordinates- In physical units:
black_sun = (x_km=0, y_km=0, z_km≈+2.25×10^12 km), one half-axis (approximately 0.24 light-years) from the cube center along+Z_cs
These two forms describe the same point. The physical frame of §9.7 is centered on the cube, so km=0 on each axis is u85 2^84, whereas u85 values are measured from the cube corner. Converting the km figures above with the §9.7 formula lands on the u85 coordinate above, up to the rounding in the km figure (the exact half-axis is 2251799813685.248 km).
The black sun is a directional guidepost for east (+Z_cs). Marker color SHOULD be purple. Marker shape (point/sphere/circle/disk) is implementation-defined.
The black sun marker MUST be visible in both planes. (The plane bit does not affect XYZ decoding; it only labels the plane.)
11.3 Camera convention (“facing the black sun”)
A visualizer MUST provide (either as its default view or as an explicit preset) a camera/view mode equivalent to:
- View direction: looking toward
+Z_cs. - Up direction:
+Y_cs. - Screen-right direction:
+X_cs.
This is the canonical interpretation used when describing a coordinate as “left/right”, “above/below”, or “ahead/behind” relative to the origin.
11.4 Engine adaptation
Different graphics engines have different defaults for camera forward direction and orbit-control behavior.
Implementations MUST use camera placement/orientation and/or a render-space transform so that the semantic rules in §11.1–§11.3 remain true, without mirroring or re-labeling Cyberspace axes.
For quick regression tests and cross-implementation debugging, see visualization_vectors.json in this spec repository.
12. Limitations and Threat Model (non-normative)
12.1 What the protocol provides
- Single-location constraint (per keypair): A valid, linear movement chain makes forking detectable.
- Hop freshness: Every hop includes non-cacheable temporal work derived from chain context.
- Work equivalence (for discovery): An entity must compute region preimages to derive discovery keys; there is no shortcut.
- Auditable movement history: The chain provides an ordered trail of hops.
- Locality imposition: Distance and regions become meaningful in a 256-bit address space.
12.2 What the protocol does NOT provide
- Physical location proof: Dataspace mapping is deterministic, but it does not prove a body is physically at that GPS point.
- Trusted identity / sybil resistance: One operator can control many keypairs.
- Privacy by default: Movement events are public if published.
- Traversal necessity for decryption: Region preimages can be computed directly without maintaining a movement chain.
12.3 Acknowledged attack vectors
- Coordinate scanning: An observer can compute region preimages for arbitrary coordinates and query for content. This is considered acceptable because the work required is the same as for a traveler.
- Chain abandonment: An entity may abandon a keypair and start fresh, or publish a new spawn event to restart their chain. Applications can require continuity/reputation at higher layers.
- ASIC-accelerated sidesteps: Sidestep work is plain SHA-256 over short preimages. A purpose-built ASIC fleet crosses boundaries that consumers cannot: a 1 EH/s farm crosses h78 in about a week and a Bitcoin-scale fleet crosses h85 in about a day. The storage bound protects Cantor roots (claims and keys), not travel.
- Key grinding: Spawning inside an aligned region of height h costs about 2^(3·(85−h)) key generations. Regions walled above roughly h75 can be spawn-camped by grinding; travel reaches about h50 and hyperspace reaches stop neighbourhoods; regions walled between h50 and h75 therefore have the strongest isolation the protocol offers. Applications should know which regime they are building in.
13. Structured Proof-of-Work (non-normative)
This section describes how the computational work in Cyberspace relates to physical reality and how it differs from traditional proof-of-work systems like Bitcoin.
13.1 A new class of proof-of-work
Bitcoin’s proof-of-work operates by grinding random nonces through SHA-256 until the output falls below a difficulty target. The work is real (it consumes electricity and generates heat) but the output is arbitrary. A valid Bitcoin hash proves that energy was spent. It does not encode any spatial or structural information. The hash is discarded after use.
Cantor pairing tree computation is a fundamentally different kind of work. The output is not arbitrary. When you compute a Cantor tree over a set of coordinates, the root you produce uniquely identifies that spatial region. It is a bijection. The root can be unpaired to reconstruct the entire tree. The proof of your movement is the mathematical fabric of the space itself.
Every Cantor root you compute becomes a stable region identifier that persists as useful infrastructure. It can be used for encrypting localized secrets, discovering nearby content, and holding a region (§7.8). The work product is meaningful, not disposable.
13.2 Capacity-bound, and compute-bound within it
Bitcoin’s proof-of-work is compute-bound. Faster chips produce more hashes per second, and specialized hardware (ASICs) can be built to optimize SHA-256 throughput. The bottleneck is hash rate.
Cantor work is storage-bound. The Cantor pairing function produces intermediate values that grow exponentially in bit size. At height 34, the intermediates require approximately 182 GB of storage. At height 40, approximately 11 TB. At height 50, approximately 11 PB. These intermediates must physically exist on disk during computation because parent nodes require both children during bottom-up tree construction.
This means the limiting resource on how high you can go is capacity: you cannot compute a root you cannot hold, and no ASIC optimizes around needing terabytes of intermediates. That part is right and it is the reason §13.3’s fixed difficulty behaves the way it does.
The limiting resource on how long it takes, at a height you can hold, is arithmetic rather than bandwidth, and an earlier draft of this section said otherwise. Multiplying two n-bit numbers costs far more than one operation per byte, so the work per byte read from disk is high and the computation is measured at about 28 times compute-bound rather than I/O-bound. Both statements matter and they are about different limits: capacity decides the ceiling, arithmetic decides the wait beneath it. §6.12 and §7.8 work the same arithmetic in the context of disclosure and holding, and docs/what-can-be-sold.md collects all of it.
13.3 Fixed difficulty
Bitcoin adjusts its difficulty every 2,016 blocks to maintain a 10-minute target block time. This adjustment requires protocol-level governance and creates a competitive arms race between miners.
Cantor tree difficulty is fixed by the mathematics. The cost of computing a region is a deterministic function of the coordinates involved. There is no adjustment mechanism, no governance, and no parameters to tune. As hardware improves over time, all parties gain access to greater computation and storage. The boundaries of what is computable expand gradually for everyone. The protocol requires no ongoing calibration.
13.4 Where the energy goes
The thermodynamic cost of Cantor work is real but mediated differently than in Bitcoin. In Bitcoin, the cost pipeline is direct: electricity flows into ASIC chips, which run SHA-256 repeatedly, generating heat. The conversion from watts to proofs is immediate and measurable.
In Cantor work, the energy costs arise from several sources. CPU cycles perform the pairing arithmetic, which generates heat but is relatively minor at scale. The dominant cost is disk I/O: writing and reading terabytes of intermediate values to and from storage generates heat and consumes significant electricity. The storage media itself must be physically allocated and occupied for the duration of the computation, representing both an energy cost and an opportunity cost.
There is also an interesting connection to Landauer’s principle, which states that erasing one bit of information costs a minimum of kT × ln(2) joules (where k is Boltzmann’s constant and T is temperature). When Cantor intermediates reach petabytes in size, the theoretical minimum energy cost of erasing them after computation may become non-trivial.
13.5 The sidestep as traditional proof-of-work
The sidestep (§6), which uses SHA-256 Merkle hash trees instead of Cantor pairing, has a more direct thermodynamic profile. SHA-256 hashing is pure compute with fixed-size inputs and outputs. The cost is straightforward: hash operations multiplied by time equals watts. The sidestep is, in this sense, closer to traditional proof-of-work than the Cantor hop.
This creates an interesting layering: Cantor hops are storage-bound structured work (fast but limited by storage), while Merkle sidesteps are compute-bound hash work (slow but unlimited by storage). The protocol naturally routes movement through whichever regime is feasible for the boundary being crossed.
The two also differ in what the work leaves behind, and the difference is deliberate (§6.12). A Cantor hop’s output is durable public infrastructure: a canonical region root that anyone may be handed and reuse. A Merkle sidestep’s output is consumed at the moment of use: seeded to one chain position, worth nothing to anyone else, and disposable in exactly the way §13.1 says a Bitcoin hash is disposable. The sidestep is the one place in Cyberspace where work is burned rather than accumulated, which is precisely what makes it a price on movement instead of an investment in a place.
14. Reference Implementation
The reference implementation for this spec is:
Implementers should treat that repo as the reference for:
- Integer→bytes canonicalization for hashing
- Movement proof computation
- Canonical GPS→dataspace mapping (
CANONICAL_GPS_TO_DATASPACE_SPEC_VERSIONand golden vectors)
This repository also carries stdlib-only reference scripts that are executable statements of specific sections, each self-checking when run:
sidestep-reference.py: the v2 sidestep construction (§6.4, §6.5, §6.10, §6.11), with golden vectors and a check that each property those sections claim actually holdshint-reference.py: §7.7 and the §10 rule for bags: canonical form, containment, sector tags, seeker work, malformed hints and plane preservation, locking the golden vectors of §7.7decks/landfall-reference.py: landfall derivation (DECK-0001 §1.2)