JLP Protocol Specification
protocol_version = 4 · shipped in collider v1.5.4 · v3 (v1.5.x) workers still accepted in compatibility mode · canonical IDL: collision-protocol/protocol/jlp.yaml
Overview
JLP (JeanLucPons protocol) is a binary protocol carried over TCP, optionally wrapped in TLS. It connects Pollard's Kangaroo workers to a pool server and handles authentication, work assignment, distinguished point (DP) submission, statistics, and solution broadcast. v3 (collider v1.5.0) is the current wire format; v1.4.x clients running v1 / v2 are refused at AUTH.
v4 (v1.5.4): checkpoint-replay anti-cheat
v4 adds proof-of-walk. Each worker commits a Merkle root over the checkpoint distances of its kangaroo walk (one checkpoint every 65,536 jumps) and attaches it to every distinguished point (DP_SUBMIT_V3). The server may then issue a CHALLENGE for a random subset of segments; the worker reveals the endpoint distances plus their Merkle proofs (CHALLENGE_RSP) and the server replays the forward jumps to confirm the segment links up. Because the challenge is random and issued after commitment, a grinder cannot fabricate a consistent checkpoint chain without actually walking — removing the advantage of submitting cheap off-walk points.
Backwards compatible. The pool runs in compatibility mode by default: existing v3 (v1.5.x) workers keep mining unchanged, while v4 workers additionally submit checkpoint commitments. Operators switch to strict (v4 required) once every worker has upgraded. Pre-v1.5 (v1.4.x) clients are refused in both modes.
The challenge check ships disabled (challenge_mode: off) and is turned on only after a v4 client is in the field and the walk step function is validated, so honest workers are never falsely penalized during the rollout.
v3 vs v2 vs v1 -- what changed
| Version | Shipped in | Key changes |
|---|---|---|
| 3 | v1.5.0 (current) | WORK_ASN grew from 109 to 126 bytes -- adds kangaroo_type, start_offset_a, start_offset_b for asymmetric TAME-only / WILD-only assignment. SOLUTION is strictly server-to-client; client never computes the key. v1.4.x clients (flags=2) are refused at AUTH with protocol_version_mismatch. |
| 2 | v1.4.1 to v1.4.4 | Header flags byte carries protocol_version (B.5). AUTH grew to AuthPayloadV2 (120 B) with timestamp_ms + nonce for replay defence. DP submission moved to DP_BATCH_V2 (78 B per DP) with work_id attestation + monotonic sequence. |
| 1 | v1.2.x to v1.4.0 | Legacy. 96-byte AUTH, 66-byte DPs, no work_id binding, no sequence nonce. Not accepted by the v1.5 server; v1.5 client cannot speak it. |
Theft-resistance: TAME / WILD asymmetric assignment
In v1.4.x, a pool worker who happened to find the cross-collision computed the puzzle's private key locally and could sweep the funds before the pool ever saw the solution. v3 closes the window architecturally: each worker runs ONLY tame kangaroos OR ONLY wild kangaroos, the host-side cross-type hashtable is disabled in pool mode, and the pool server is the sole entity where TAME and WILD DPs aggregate.
Worker invariants
WORK_ASN.kangaroo_typeis1 (TAME_ONLY)or2 (WILD_ONLY). Value0 (BOTH)is reserved and illegal in pool mode; standalone solo mode keeps BOTH.- The worker host-side cross-type DP hashtable is disabled. DPs flow straight to the network.
- The
report_solutioncodepath is removed from the client entirely. There is no client-to-server SOLUTION message in v3. - A worker submitting a WILD DP under a TAME assignment (or vice versa) is unambiguous binary modification. The server bans the originating IP permanently on first occurrence, bypassing the rolling-window invalid-DP ladder.
Server flow on collision
1. Match incoming TAME DP against the WILD DP set (and vice versa). 2. Compute k = (d_tame - d_wild + HalfRange) mod n; verify k * G == Q. 3. Sign + broadcast the puzzle-funds sweep transaction via mempool provider PRIMARY. 4. Poll mempool provider FALLBACK until the sweep tx is observed (cross-provider attestation; hard timeout). 5. ONLY THEN broadcast SOLUTION to every connected worker.
Workers receive the recovered key inside a SOLUTION frame whose puzzle UTXOs are already moving (or already moved). They cannot win the race even by extracting the key from the SOLUTION payload.
Connection URLs
jlp://host:portPlain TCP (testing only)jlps://host:portTLS (TLS 1.2 / 1.3, SNI + hostname verification, default trust store) -- always use this in productionMessage header (8 bytes)
Every JLP message starts with this fixed 8-byte header. The flags byte carries the negotiated protocol version (the layout itself is unchanged across v1/v2/v3 so a v1 receiver can at least read the type+length and emit a clean MSG_ERROR rather than crash).
+--------+--------+--------+--------+--------+--------+--------+--------+
| 'K' | 'A' | 'N' | 'G' | TYPE | FLAGS | LENGTH (u16) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 0x4B | 0x41 | 0x4E | 0x47 | 0xNN | 0xVV | LO | HI |
+--------+--------+--------+--------+--------+--------+--------+--------+
|<-------- 4 bytes -------->|<- 1B ->|<- 1B ->|<------ 2 bytes -------->|
| MAGIC "KANG" | TYPE | flags | payload_size (LE) |
(= protocol_version)3 in v1.5.0. Receivers that read a different value MUST respond MSG_ERROR with code 0x10 (protocol_version_mismatch).Message types
| Name | Value | Direction | v3 use |
|---|---|---|---|
| AUTH | 0x01 | Client → Server | Carries AuthPayloadV2 (120 B) |
| AUTH_OK | 0x02 | Server → Client | Empty payload |
| AUTH_FAIL | 0x03 | Server → Client | Carries MSG_ERROR code (e.g. 0x10 UPGRADE_REQUIRED for v1.4.x clients) |
| WORK_REQ | 0x10 | Client → Server | Empty payload; ask for a chunk |
| WORK_ASN | 0x11 | Server → Client | Carries WorkAssignment (126 B, v3 wire size) |
| DP_SUBMIT | 0x20 | -- | v1 legacy, unused in v3 |
| DP_ACK | 0x21 | Server → Client | uint32 count of accepted DPs |
| DP_BATCH | 0x22 | -- | v1 legacy, unused in v3 |
| DP_SUBMIT_V2 | 0x23 | -- | Reserved single-DP variant; v3 clients use DP_BATCH_V2 only |
| DP_BATCH_V2 | 0x24 | Client → Server | uint32 count + N x DistinguishedPointV2 (78 B each) |
| STATS_REQ | 0x30 | Client → Server | Empty payload |
| STATS_RSP | 0x31 | Server → Client | PoolStats (36 B) |
| SOLUTION | 0x40 | Server → Client ONLY | Broadcast after sweep + attestation. v3 clients that EMIT this are server-banned. |
| PING | 0x50 | Both | Keepalive (20 s default cadence from client) |
| PONG | 0x51 | Both | Keepalive response |
| MSG_ERROR | 0xFF | Server → Client | uint8 error code (table below) |
Named MSG_ERROR (not ERROR) to dodge the Windows ERROR macro.
Payload structures
AuthPayloadV2 (carried by AUTH 0x01) -- 120 bytes
struct pack format: <64s32sQ16s
struct AuthPayloadV2 {
char worker_name[64]; // UTF-8 worker name / BTC payout address
char password[32]; // Optional pool password (null-padded)
uint64_t timestamp_ms; // Client wall-clock time in ms (little-endian)
uint8_t nonce[16]; // Per-AUTH random nonce
};timestamp_msmust be within+/- 30 sof the server clock (0x12 auth_clock_skewon violation).nonceuniqueness is enforced in a short server-side seen-set (0x13 auth_nonce_reuseon replay).- Header flags must be
3(v1.4.x clients send 2 and are refused with0x10 protocol_version_mismatch).
WorkAssignment (carried by WORK_ASN 0x11) -- 126 bytes (v3)
struct pack format: <33s32s32sIQBQQ
struct WorkAssignment {
uint8_t public_key[33]; // Compressed secp256k1 target pubkey
uint8_t range_start[32]; // Big-endian 32-byte chunk start
uint8_t range_end[32]; // Big-endian 32-byte chunk end
uint32_t dp_bits; // Distinguished-point bit threshold (LE)
uint64_t work_id; // Low 64 bits of the chunk identifier (LE)
// -- v3 additions --
uint8_t kangaroo_type; // 1=TAME_ONLY, 2=WILD_ONLY (0=BOTH reserved)
uint64_t start_offset_a; // Inclusive low 64 bits of worker's offset window
uint64_t start_offset_b; // Exclusive upper bound (b > a)
};kangaroo_typetracks the RCKangarooKANG_MODE_*enum (third_party/RCKangaroo/defs.h). Distinct fromDistinguishedPointV2.type, which is the per-DP tame/wild byte (0=tame, 1=wild).[start_offset_a, start_offset_b)is the worker's disjoint sub-window inside[range_start, range_end). Server guarantees no two same-type workers overlap.- Pre-existing fields (public_key … work_id) keep their identical byte offsets and little-endian encoding. v1.4.x decoders see a 17-byte-too-long payload and reject it as malformed; old and new editions cannot accidentally interoperate.
dp_bitsmust satisfy8 ≤ dp_bits ≤ 32. A client that receives a value outside this range MUST disconnect (anti-DoS check; a buggy or malicious dp_bits=255 would otherwise burn GPU cycles indefinitely).
DistinguishedPointV2 (carried by DP_BATCH_V2 0x24) -- 78 bytes per DP
struct pack format: <QI32s32sBB
struct DistinguishedPointV2 {
uint64_t work_id; // Chunk id from the active WORK_ASN
uint32_t sequence; // Per-(worker, work_id) monotonic counter
uint8_t x[32]; // Big-endian X coordinate
uint8_t d[32]; // Big-endian walked distance
uint8_t type; // 0=tame, 1=wild
uint8_t dp_bits; // Leading-zero bit count required of x
};
struct DPBatchV2 {
uint32_t count; // 1 .. MAX_BATCH_SIZE (10,000)
DistinguishedPointV2 dps[count];
};
// Wire size: 4 + (count * 78) bytes
// Server rejects any payload >= 1 MB (MAX_MESSAGE_SIZE).work_idattests which chunk this DP came from. The server rejects any DP whose claimed work_id mismatches the worker's current assignment (0x20 work_id_mismatch).sequenceis a per-(worker, work_id) monotonic counter starting at 0 on the first DP of each chunk. Replays of captured DP_BATCHes fail with0x21 dp_sequence_out_of_window.typemust match the worker's assignedkangaroo_type(TAME_ONLY workers emit type=0 only; WILD_ONLY workers emit type=1 only). A single mismatch triggers a permanent IP ban -- there is no warning ladder.
PoolStats (carried by STATS_RSP 0x31) -- 36 bytes
struct pack format: <QIIffQI
struct PoolStats {
uint64_t total_dps; // Cumulative pool DP count
uint32_t total_workers; // Distinct worker names ever seen
uint32_t active_workers; // Workers active in last 5 minutes
float dps_per_second; // Pool aggregate DP rate
float your_share; // Fraction of total_dps for this worker name
uint64_t your_dps; // DPs credited to this worker name
uint32_t uptime_seconds; // Server uptime
};DP_ACK (0x21) -- 4 bytes
struct DPAck {
uint32_t count; // Number of DPs accepted from the last DP_BATCH_V2
};Connection flow (v3)
Client Server | | | -------- TCP / TLS handshake ----------> | | | | -------- AUTH (AuthPayloadV2, flags=3) --> | | <-------- AUTH_OK --------------------- | | | | -------- WORK_REQ -------------------> | | <-------- WORK_ASN (WorkAssignment, v3) | | kangaroo_type = TAME_ONLY or | | WILD_ONLY (BOTH is illegal) | | | | ... compute DPs of assigned type ... | | | | -------- DP_BATCH_V2 (work_id + seq) --> | | <-------- DP_ACK ---------------------- | | | | -------- STATS_REQ -----------------> | | <-------- STATS_RSP (PoolStats) ------- | | | | -------- PING (every 20 s) ----------> | | <-------- PONG ----------------------- | | | | [ collision found server-side ] | | [ sweep tx broadcast, attested ] | | <-------- SOLUTION (server-only) ------ | | | | Client confirms puzzle solved; no | | local key recovery, no key handling. |
Error codes (MSG_ERROR payload)
| Code | Symbol | Meaning |
|---|---|---|
| 0x01 | internal | Server-side fault |
| 0x02 | malformed_header | Bad MAGIC or layout |
| 0x03 | malformed_payload | Payload could not be decoded |
| 0x04 | payload_too_large | Exceeds MAX_MESSAGE_SIZE (1 MB) |
| 0x05 | unsupported_message_type | TYPE byte unknown to server (or SOLUTION from client) |
| 0x10 | protocol_version_mismatch | flags != PROTOCOL_VERSION (v3 server sees v1/v2 client) |
| 0x11 | auth_version_unsupported | Legacy 96-byte AuthPayload received |
| 0x12 | auth_clock_skew | timestamp_ms outside +/- 30 s |
| 0x13 | auth_nonce_reuse | Replay of a recently-seen AUTH nonce |
| 0x14 | auth_bad_credentials | Pool password mismatch (when one is configured) |
| 0x20 | work_id_mismatch | DP claims a chunk not assigned to this worker |
| 0x21 | dp_sequence_out_of_window | Replay of an old DP_BATCH (sequence too low) |
| 0x22 | dp_replay | Exact-duplicate DP submitted twice |
| 0x30 | rate_limited | Per-IP DP-rate ceiling tripped |
Anti-cheat thresholds
Reference implementations
Authoritative IDL
All wire structs above are emitted from a single YAML source by a codegen tool. C++ packed structs (with compile-time size asserts) and Python dataclasses (with to_bytes / from_bytes) are generated from the same definition; hand-editing the generated files is rejected by CI.
Server (Python, asyncio)
github.com/hevnsnt/collision-protocolClient (C++, CUDA + Metal)
github.com/hevnsnt/colliderThird-party client implementations are welcome in any language with TCP socket support; the IDL is the single source of truth. Carry forward the v3 kangaroo_type / start_offset_* fields in WORK_ASN and the type-mismatch ban behavior or the pool server will mark your implementation as a modified binary on the first wrong-type DP.