1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//! The complete DIG opcode namespace — the `200..=226` band, in one place.
//!
//! DIG extends Chia's `ProtocolMessageTypes` (which stops at `RespondCostInfo = 107`) with a
//! band that starts at **200**, leaving a 100-value gap for future upstream additions. The band
//! has two halves:
//!
//! | Range | Half | Where it is defined |
//! |---|---|---|
//! | `200..=219` | **consensus** — the DIG L2 gossip opcodes | [`DigMessageType`], one variant each |
//! | `220..=` | **free** — directed / broadcast application protocols | the constants below |
//!
//! The consensus half is an enum because each opcode has a fixed body type and a gossip
//! strategy; the free half is plain constants because each opcode's body is owned by the
//! application protocol that defines it, not by this crate.
//!
//! These values are **canonical**: a second implementation must match them byte for byte, and
//! no assigned value ever moves (§5.1, additive only).
use crateDigMessageType;
/// First opcode of the DIG band. Everything below this belongs to Chia.
pub const DIG_BAND_START: u8 = 200;
/// First opcode of the **free** half of the DIG band — application protocols, not L2 consensus.
pub const FREE_BAND_START: u8 = 220;
/// Wire opcode for a directed **dig-message** envelope (WU6, epic #796).
///
/// Carries a `dig-message` directed envelope as OPAQUE bytes in [`DigMessage::data`]. The
/// transport (dig-gossip) never seals, opens, or parses it; end-to-end sealing to the
/// recipient's DID key is `dig-message`'s job.
///
/// [`DigMessage::data`]: crate::DigMessage::data
pub const DIG_MESSAGE: u8 = 220;
/// Wire opcode for a **store-melted** broadcast (epic #1316).
///
/// Announces that a dig-store's on-chain coin has been melted, so peers stop hosting its `.dig`
/// content. A public all-peers flood: signed and mTLS-authenticated, but NOT recipient-sealed —
/// store deletion is addressed to everyone (the §5.4 public-broadcast carve-out).
pub const STORE_MELTED: u8 = 221;
/// Wire opcode for a **holdings-announce** broadcast (#1428, spec #1394).
///
/// Announces a batch of signed holdings add/remove deltas so peers learn which content a
/// provider holds; this feeds dig-dht's holder set. Public flood, same carve-out as
/// [`STORE_MELTED`].
pub const HOLDINGS_ANNOUNCE: u8 = 222;
/// Wire opcode for a **profile-root announce** broadcast (epic #3008).
///
/// Body is exactly 64 bytes: `store_id ‖ root`, two 32-byte hashes with no framing, announcing
/// the sender's current profile-SMT root for that store. A public all-peers flood, same §5.4
/// carve-out as [`STORE_MELTED`].
///
/// **Deliberately unsigned.** The authority for a profile root is the on-chain root, not the
/// announcing peer, so the receiver compares any announced root against chain before trusting
/// it. A forged announce therefore costs an attacker one wasted [`PROFILE_BODY_REQUEST`] that
/// then fails that compare — signing would buy no additional guarantee while adding a signature
/// verification to every message of the highest-volume broadcast in the band.
pub const PROFILE_ROOT_ANNOUNCE: u8 = 223;
/// Wire opcode for a directed **profile-body request** (epic #3008).
///
/// Body is exactly 64 bytes: `store_id ‖ root`, asking one peer for the profile body behind a
/// root learned from a [`PROFILE_ROOT_ANNOUNCE`]. Answered with [`PROFILE_BODY`].
pub const PROFILE_BODY_REQUEST: u8 = 224;
/// Wire opcode for a directed **profile-body** response (epic #3008).
///
/// Body is `store_id ‖ root ‖ len:u32be ‖ body` — the two 32-byte hashes the request named,
/// then a big-endian length prefix and that many bytes of profile body. The receiver rehashes
/// the body and compares against `root`, which is why the announce that started the exchange
/// needs no signature.
pub const PROFILE_BODY: u8 = 225;
/// Wire opcode for a **distributor-announce** broadcast (DIG-Network/dig_ecosystem#3252).
///
/// A public all-peers flood carrying untrusted reward-distributor discovery hints: a
/// `store_id` plus the launcher ids of distributors the sender knows of for that store. Same
/// §5.4 public-broadcast carve-out as [`STORE_MELTED`] and [`HOLDINGS_ANNOUNCE`].
///
/// Body layout (encoded downstream in dig-gossip, not this crate): `store_id(32) ‖
/// launcher_id_count(u16 BE) ‖ launcher_ids(32 each, ≤ 32)`.
///
/// **Deliberately unsigned**, for the same reason [`PROFILE_ROOT_ANNOUNCE`] is: the authority
/// for a distributor is the on-chain coin, not the announcing peer, so a receiver re-derives
/// every property from chain before the launcher id becomes a candidate. Unlike
/// [`HOLDINGS_ANNOUNCE`], the frame carries no addresses, so there is no address-rewriting or
/// DHT-poisoning threat for a signature to close — a forged announce costs a receiver one
/// wasted chain lookup that then fails that compare.
///
/// **A frame asserts membership only, and never completeness.** The launcher ids present are
/// ids the sender claims to know of for that `store_id`. The absence of an id from a frame
/// asserts nothing at all — not that the sender doesn't know of it, not that it has been
/// evicted, nothing.
///
/// A receiver **MUST union** a received frame into whatever set it already holds for that
/// store; it **MUST NOT** replace its per-store set from a frame, and it **MUST NOT** diff two
/// frames against each other to infer a removal. This is what makes eviction
/// **unrepresentable** on this wire: because absence carries no meaning, `old_set \ new_set` is
/// never a signal, so a receiver that unions can never manufacture a false eviction
/// (dig_ecosystem §12.5 clause 7). Age out an entry locally on your own retention policy, never
/// on a peer's frame.
///
/// A sender who knows of more than `MAX_LAUNCHER_IDS_PER_ANNOUNCE` (32) distributors for one
/// store sends **any subset of at most 32** and MAY rotate which subset it sends across frames.
/// Because a frame is never a completeness claim, sending a partial subset is honest by
/// construction — it is not truncation, and it cannot forge an eviction the way silently
/// dropping ids from a claimed-complete snapshot would.
///
/// An **empty** launcher-id list is a distinct, positive statement in its own right: "my known
/// set for this store is empty" — not "I have nothing to say." It is how this wire says "I know
/// of none" instead of saying nothing, and a receiver MUST NOT read it as a request to clear
/// what it already holds (see the union rule above).
///
/// A received announce is a **hint only**: it must not admit an entry, rank a candidate, or be
/// a claim's authority. A peer that never hears one MUST still find and claim via §13.1 —
/// this opcode is a latency shortcut, never a dependency (dig_ecosystem §13.2 clause 2).
pub const DISTRIBUTOR_ANNOUNCE: u8 = 226;
/// Every opcode DIG has assigned, ascending — the 20 consensus opcodes plus the 7 free-band ones.
///
/// This is the list a peer link dispatches on and the list a conformance test checks against
/// Chia's namespace for collisions.
pub const ALL_DIG_OPCODES: = ;
/// Whether `opcode` belongs to the DIG band rather than Chia's namespace.
///
/// This is a *band* test, not an *assigned* test: an unassigned value such as `250` is still
/// DIG's to allocate, and a link must route it to DIG handling (where it is rejected as unknown)
/// rather than to a Chia decoder that would reject the whole connection.
pub const