Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 64 additions & 63 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
[workspace]
resolver = "2"
# The canonical dig-node repo is a small workspace of the node ENGINE + its two
# DIG-Browser host shells:
# * dig-node-core — the NODE engine library (crate `dig_node_core`): RPC dispatch,
# serve/fetch/redirect, chain-watch, subscriptions, gap-fill,
# cache, P2P. The single node implementation shared by BOTH host
# shells below. (Renamed from `dig-node` so the engine library and
# the produced `dig-node` binary no longer share a name, #216.)
# * dig-node-service — the OS-service binary (`dig-node`): axum transport + control
# plane + CLI + service install. Depends on the engine library.
# * dig-runtime — the DIG Browser's in-process node: a cdylib (`dig_runtime.dll`)
# exposing the `dig_rpc`/`dig_wallet_rpc` C-ABI the browser links.
# * dig-wallet — the DIG Browser's built-in Chia wallet host (loopback UI + BLS
# signing), brought up by dig-runtime beside the node.
# For the `.dig` STORE FORMAT the node depends on digstore's store-format LIBRARY crates
# (digstore-core/-crypto/-chain/-host/-remote/-stage) as GIT dependencies — dig-node-core
# -> store-lib, never the reverse. digstore is only ever an RPC client of a node.
members = [
"crates/dig-node-core",
"crates/dig-node-service",
"crates/dig-runtime",
"crates/dig-wallet",
]

[workspace.package]
edition = "2021"
# The RELEASE version of the repo's shipped artifact — the `dig-node` binary
# (`dig-node-service`, which inherits this via `version.workspace = true`). This is
# the version the nightly-release.yml stable channel + version-increment CI reads from
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.73.0"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
# it, so silent wrapping in release would turn a length bug into a memory/logic hazard.
[profile.release]
overflow-checks = true

# -- dig-gossip vendored-fork patches (L7 peer network) -----------------------------------------------
#
# The node's P2P stack (dig-nat/dig-gossip/dig-dht/dig-pex/dig-download/dig-peer-selector) builds against
# ADDITIVE forks of `chia-protocol` + `chia-sdk-client` that dig-gossip vendors: the DIG introducer
# opcodes `RegisterPeer=218`/`RegisterAck=219` on `ProtocolMessageTypes`, plus `Peer::send_protocol_message`
# + `RateLimits.dig_wire`. Those forks are the SAME upstream versions (`chia-protocol` 0.26,
# `chia-sdk-client` 0.28) with additive DIG extensions, so patching the whole workspace to them is safe
# for the store-format crates too (they simply gain unused enum variants).
#
# dig-gossip's own `[patch.crates-io]` does NOT apply transitively when it is a git DEPENDENCY (cargo
# applies patches only from the ROOT manifest being built), so this workspace re-declares them, resolving
# the vendored packages from the SAME pinned dig-gossip git rev — CI-safe when the repo is checked out
# standalone. `native-tls` is NOT patched: dig-gossip is pulled with
# `default-features = false, features = ["rustls", "relay"]`, so the OpenSSL/native-tls path is off.
[patch.crates-io]
chia-protocol = { git = "https://github.com/DIG-Network/dig-gossip", rev = "6d458974522cdada1f9b09469d7709e08036a800" }
chia-sdk-client = { git = "https://github.com/DIG-Network/dig-gossip", rev = "6d458974522cdada1f9b09469d7709e08036a800" }

# The dig-nat unification patch is RETIRED (#1280 crates.io cascade): dig-nat 0.7 is on crates.io and
# the ENTIRE peer stack (dig-gossip 0.7.1, dig-dht 0.2.2, dig-download 0.2.1, dig-peer-selector 0.2.1,
# dig-node-core) now depends on dig-nat "0.7" from crates.io, so cargo already resolves ONE dig-nat 0.7
# instance without any git redirect. dig-constants is likewise a plain crates.io dep everywhere now.
[workspace]
resolver = "2"
# The canonical dig-node repo is a small workspace of the node ENGINE + its two
# DIG-Browser host shells:
# * dig-node-core — the NODE engine library (crate `dig_node_core`): RPC dispatch,
# serve/fetch/redirect, chain-watch, subscriptions, gap-fill,
# cache, P2P. The single node implementation shared by BOTH host
# shells below. (Renamed from `dig-node` so the engine library and
# the produced `dig-node` binary no longer share a name, #216.)
# * dig-node-service — the OS-service binary (`dig-node`): axum transport + control
# plane + CLI + service install. Depends on the engine library.
# * dig-runtime — the DIG Browser's in-process node: a cdylib (`dig_runtime.dll`)
# exposing the `dig_rpc`/`dig_wallet_rpc` C-ABI the browser links.
# * dig-wallet — the DIG Browser's built-in Chia wallet host (loopback UI + BLS
# signing), brought up by dig-runtime beside the node.
# For the `.dig` STORE FORMAT the node depends on digstore's store-format LIBRARY crates
# (digstore-core/-crypto/-chain/-host/-remote/-stage) as GIT dependencies — dig-node-core
# -> store-lib, never the reverse. digstore is only ever an RPC client of a node.
members = [
"crates/dig-node-core",
"crates/dig-chat-protocol",
"crates/dig-node-service",
"crates/dig-runtime",
"crates/dig-wallet",
]

[workspace.package]
edition = "2021"
# The RELEASE version of the repo's shipped artifact — the `dig-node` binary
# (`dig-node-service`, which inherits this via `version.workspace = true`). This is
# the version the nightly-release.yml stable channel + version-increment CI reads from
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.74.0"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
# it, so silent wrapping in release would turn a length bug into a memory/logic hazard.
[profile.release]
overflow-checks = true

# -- dig-gossip vendored-fork patches (L7 peer network) -----------------------------------------------
#
# The node's P2P stack (dig-nat/dig-gossip/dig-dht/dig-pex/dig-download/dig-peer-selector) builds against
# ADDITIVE forks of `chia-protocol` + `chia-sdk-client` that dig-gossip vendors: the DIG introducer
# opcodes `RegisterPeer=218`/`RegisterAck=219` on `ProtocolMessageTypes`, plus `Peer::send_protocol_message`
# + `RateLimits.dig_wire`. Those forks are the SAME upstream versions (`chia-protocol` 0.26,
# `chia-sdk-client` 0.28) with additive DIG extensions, so patching the whole workspace to them is safe
# for the store-format crates too (they simply gain unused enum variants).
#
# dig-gossip's own `[patch.crates-io]` does NOT apply transitively when it is a git DEPENDENCY (cargo
# applies patches only from the ROOT manifest being built), so this workspace re-declares them, resolving
# the vendored packages from the SAME pinned dig-gossip git rev — CI-safe when the repo is checked out
# standalone. `native-tls` is NOT patched: dig-gossip is pulled with
# `default-features = false, features = ["rustls", "relay"]`, so the OpenSSL/native-tls path is off.
[patch.crates-io]
chia-protocol = { git = "https://github.com/DIG-Network/dig-gossip", rev = "6d458974522cdada1f9b09469d7709e08036a800" }
chia-sdk-client = { git = "https://github.com/DIG-Network/dig-gossip", rev = "6d458974522cdada1f9b09469d7709e08036a800" }

# The dig-nat unification patch is RETIRED (#1280 crates.io cascade): dig-nat 0.7 is on crates.io and
# the ENTIRE peer stack (dig-gossip 0.7.1, dig-dht 0.2.2, dig-download 0.2.1, dig-peer-selector 0.2.1,
# dig-node-core) now depends on dig-nat "0.7" from crates.io, so cargo already resolves ONE dig-nat 0.7
# instance without any git redirect. dig-constants is likewise a plain crates.io dep everywhere now.
51 changes: 49 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,8 +844,9 @@ For the current node library (§2.2) the catalogue is:
- **local**: `dig.getContent`, `dig.getAnchoredRoot`, `dig.getManifest`, `dig.stage`,
`dig.getCollection`, `dig.listCollectionItems`, the L7 peer surface (`dig.getNetworkInfo`,
`dig.getPeers`, `dig.announce`, `dig.getAvailability`, `dig.listInventory`, `dig.fetchRange`),
and all `cache.*` (`cache.getConfig`, `cache.setCapBytes`, `cache.clear`, `cache.listCached`,
`cache.removeCached`, `cache.fetchAndCache`).
all `cache.*` (`cache.getConfig`, `cache.setCapBytes`, `cache.clear`, `cache.listCached`,
`cache.removeCached`, `cache.fetchAndCache`), and the chat subsystem `chat.send` / `chat.poll`
(§5.5.2).
- **passthrough**: `dig.getCapsule` (an alias the node does NOT resolve — local-first callers use
`dig.getContent`), `dig.getProof`, `dig.listCapsules`.
- **shell**: `rpc.discover`.
Expand Down Expand Up @@ -881,6 +882,52 @@ version per path) as of a given capsule's commit. PUBLIC, unencrypted data; no `
from the "held but no manifest" case above.
- Malformed `store_id`/`root` (not 64-hex) → `-32602` before any filesystem access.

#### 5.5.2. Chat subsystem — `chat.send` / `chat.poll` (epic #793)

The node is the directed-message **TRANSPORT** for dig-chat: an application seals its own opaque
`DIGCHAT1` message body and the node wraps that blob in an e2e-sealed `dig-message` envelope
addressed to the recipient's `0x0010` BLS identity key, then dig-gossip directed-sends it over
opcode 220 (`DIG_MESSAGE`). The node NEVER parses the `DIGCHAT1` body — it is carried verbatim in
`dig_chat_protocol::ChatMessage::envelope` (message type id `0x0000_0200`, dig-message's dig-chat
band).

**Double seal (NC-1, content-blindness).** Two independent seals stack: the inner `DIGCHAT1` seal
the app applies, and the outer `dig-message` seal to the recipient's BLS key. A relay or on-path
peer sees only the outer ciphertext; a peer that terminates the outer seal still faces the inner
one. The node cannot expose chat plaintext even in principle. A conformance test asserts neither
the plaintext body nor the plaintext message id appears in the on-wire sealed bytes.

- **`chat.send`** — seal + directed-send. Params `{ recipient_did (64-hex), recipient_pub (base64,
the recipient's 48-byte BLS G1 sealing key), peer_id (64-hex, the gossip directed-send target),
envelope (base64, the opaque `DIGCHAT1` bytes) }`. Result `{ message_id }` (64-hex, a
node-minted `SHA-256(sender_did ‖ counter ‖ envelope)`). Each send stamps a strictly-monotonic
per-node anti-replay counter and a 5-minute expiry (dig-message §5.6/§5.6b). Errors: `-32050`
(no node identity key), `-32602` (missing/malformed param), `-32051` (no peer network), `-32052`
(seal or directed-send failed). The node seals as the node identity DID
`SHA-256(node BLS G1 public key)`.
- **`chat.poll`** — drain the inbound inbox. No params. Result `{ messages: [{ sender_did (64-hex,
the verified envelope sender), message_id (64-hex), envelope (base64, the opaque `DIGCHAT1`
body) }] }`, in arrival order, leaving the inbox empty. The inbox is bounded (oldest evicted at
capacity) so a paired peer cannot grow node memory without bound.

**Inbound path.** A received opcode-220 frame is opened (`dig-message` unseal → BLS-G2 signature
verify → anti-replay → expiry), routed through the chat `MessageRegistry`, and the decoded
`ChatMessage` is queued into the inbox. A sender the node cannot resolve to a BLS key is rejected,
never queued (fail-closed). This describes the inbound handler (`process_inbound_frame`), which is
implemented and unit-tested; the **live peer-network feed that invokes it is not yet wired** into
`run_peer_network` (see Deferred), so in the shipped build `chat.poll` returns empty until that
loop lands.

**Deferred (epic #793):** the **live inbound feed** — the `run_peer_network` loop that drains
`GossipHandle::inbound_receiver()` into `process_inbound_frame` — is not yet wired (the handler is
implemented + unit-tested but nothing in production calls it, so `chat.poll` is always empty until
this lands; it is gated on the sender-key resolver below). The sealing-key directory
(`resolveSealingKey`) that maps a recipient DID to its attested `0x0010` BLS key + gossip `PeerId`,
and the inbound sender-key resolver, are NOT in this MVP — the app supplies `recipient_pub` +
`peer_id` on `chat.send`, and the inbound resolver is caller-supplied. Group chat, onion routing, and receipt UX are out of scope; the five
chat message types (message, delivery/read receipts, typing, presence) are defined in
`dig-chat-protocol` but only `ChatMessage` surfaces to `chat.poll`.

### 5.6. OpenRPC drift guard (conformance test)

`tests/openrpc_drift_guard.rs` pins the catalogue to reality and MUST be kept passing:
Expand Down
37 changes: 37 additions & 0 deletions crates/dig-chat-protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# dig-chat-protocol — the DIG Network chat message-TYPE layer.
#
# A PURE message-type layer riding the published `dig-message` base protocol: it defines the five
# chat payload types in dig-message's reserved dig-chat band (0x0000_0200..=0x0000_02FF) and registers
# them into a `MessageRegistry`. It contains NO cryptography — dig-message provides all seal / sign /
# replay / streaming, and the dig-chat application provides the content seal (`DIGCHAT1`, carried here
# as OPAQUE bytes and never parsed). See SPEC.md.
[package]
name = "dig-chat-protocol"
version = "0.1.0"
edition = "2021"
rust-version = "1.75.0"
license = "Apache-2.0 OR MIT"
description = "The DIG Network chat message-type layer: the five chat payload types (message, delivery/read receipts, typing, presence) defined in dig-message's reserved dig-chat band and registered into its type registry. A crypto-free, content-blind layer — the DIGCHAT1 content seal is carried as opaque bytes; dig-message provides all seal/sign/replay/streaming."
repository = "https://github.com/DIG-Network/dig-node"
readme = "README.md"
keywords = ["dig", "chat", "messaging", "protocol", "streamable"]
categories = ["network-programming", "encoding"]

[dependencies]
# The base message protocol this crate rides: the envelope, the type registry (`MessageKind` /
# `MessageRegistry` / `MessageBand`), the reserved dig-chat band, and the error taxonomy. All seal /
# sign / replay / streaming crypto lives HERE, never in this crate. Pinned to the SAME dig-message the
# dig-node engine seals with (0.5), so the type ids + envelope agree byte-for-byte across the workspace.
dig-message = "0.5"
# The byte-deterministic Chia Streamable wire types + the `Streamable` derive + the to_bytes/from_bytes
# trait — the SAME source + versions dig-message uses, so the payload bytes agree byte-for-byte.
chia-protocol = "0.26"
chia-traits = "0.26"
chia_streamable_macro = "0.26"
# The `Streamable` derive expands to `chia_sha2`-based `hash()` helpers; it must be a direct dep so the
# generated code resolves it (the same requirement dig-message carries).
chia-sha2 = "0.26"

[dev-dependencies]
# Deterministic KAT material derived from a hashed seed — never a hard-coded literal (CodeQL).
sha2 = "0.10"
9 changes: 9 additions & 0 deletions crates/dig-chat-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# dig-chat-protocol

The DIG Network chat message-TYPE layer: the five chat payload types — `ChatMessage`,
`DeliveryReceipt`, `ReadReceipt`, `TypingIndicator`, `Presence` — defined in `dig-message`'s reserved
dig-chat band (`0x0000_0200..=0x0000_02FF`) and registered into its `MessageRegistry`.

It is a crypto-free, content-blind layer: `ChatMessage::envelope` carries the opaque `DIGCHAT1`
content seal verbatim and is never parsed here; `dig-message` provides all seal / sign / replay /
streaming. See `SPEC.md`.
Loading
Loading