Skip to content

dig-node native Chia §21.9 signer + auth sync, dig-wallet send/sign, dig.getAnchoredRoot#1

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
claude/chia-signer-browser-native-8ewnno
Jun 25, 2026
Merged

dig-node native Chia §21.9 signer + auth sync, dig-wallet send/sign, dig.getAnchoredRoot#1
MichaelTaylor3d merged 3 commits into
mainfrom
claude/chia-signer-browser-native-8ewnno

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Summary

Three increments for the DIG Browser's native sidecars (all additive; no change to the digstore CLI command surface or the producer↔verifier contract):

1. dig-node — native Chia §21.9 identity signer + authenticated whole-store sync

On a local module-cache miss for a concrete (store, root), dig-node now pulls the whole .dig module from rpc.dig.net's auth-gated §21 GET /stores/{id}/module, stamping X-Dig-Identity/-Timestamp/-Nonce/-Auth exactly like the digstore CLI, caches it, and serves locally. The identity persistence + seed→BLS-key derivation + RequestIdentity construction live once in a shared digstore_remote::identity module used by both the CLI (its ops/identity.rs is now a thin wrapper) and dig-node — no duplicated identity code. Best-effort: with no identity key it falls back to the anonymous proxy. Upstream rpc base overridable via DIG_NODE_UPSTREAM.

2. dig-wallet — native XCH send/sign (gated broadcast)

digstore_chain::send builds + BLS-signs a standard XCH payment (AugScheme; lead-coin carries payment + change and reserves the fee, extra coins bound via ASSERT_CONCURRENT_SPEND; balances exactly). Pure build+sign — never broadcasts. POST /api/send is double-gated: a real push needs broadcast:true and DIG_WALLET_ALLOW_BROADCAST=1; otherwise it returns the signed bundle as a dry run, and an explicit-but-disabled broadcast is refused (403).

3. dig.getAnchoredRoot — trusted-root resolver (closes the fail-open dig:// verifier, Rust half)

dig-node now serves dig.getAnchoredRoot {store_id} → {root}, resolving a store's CHIP-0035 chain-anchored tip root from coinset.org via the already-tested digstore_chain::singleton::sync_datastorenever from the serving node. This is the trusted-root source the browser must pin rootless URNs against (instead of the rpc-served "latest" root). The remaining thin C++ wiring in the DIG Browser is captured in the superproject HANDOFF.md (it only builds on Windows).

Tests

  • dig-node 12/12 (cache LRU, response keying, §21.9 auth/sync incl. a real auth-required RemoteServer admitting the native identity / rejecting anonymous, anchored-root input validation)
  • digstore-chain send 6/6 (balance invariant, multi-coin path, no-change, zero/insufficient errors, deterministic signing, conditions-decode proving the recipient is paid the exact amount)
  • dig-wallet 2/2 (broadcast safety gate)
  • digstore_remote::identity 2/2
  • Local gates: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, full digstore-chain lib suite (77) all green.

Notes

  • This PR runs the full workspace ci.yml build. A workspace version bump to 0.5.29 + the v0.5.29 release tag are the follow-up after merge (to cut the release binary via release.yml).
  • SYSTEM.md / DEVELOPMENT_LOG.md / runbooks updated in-tree (superproject) to match.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HDr4m1MiYc5JPfjQ7Ed3fV


Generated by Claude Code

claude added 3 commits June 19, 2026 21:21
…re sync

The DIG Browser's dig-node sidecar proxied rpc.dig.net's dig.getContent
unauthenticated and could not populate its module cache from the §21
GET /stores/{id}/module endpoint (it 401s for anonymous clients). dig-node
now carries a native Chia identity signer (paper §21.9): on a local
module-cache miss for a concrete (store, root) it fetches the whole .dig
module over the authenticated §21 endpoint — stamping
X-Dig-Identity/-Timestamp/-Nonce/-Auth exactly like the digstore CLI — caches
it, and serves every subsequent resource in that store locally.

To authenticate "the same way the CLI does", the persistent identity key
(seed + seed→BLS-key derivation + RequestIdentity construction) is extracted
into a shared digstore_remote::identity module — the single source of truth.
The CLI's ops/identity.rs is now a thin CliError-mapping wrapper over it, and
dig-node loads the seed once and mints a fresh RequestIdentity per DigClient
via identity_from_seed. The signer is best-effort: with no identity key,
dig-node skips the authenticated sync and falls back to the per-resource proxy.

The synced module is not trusted at sync time — every response dig-node serves
from it carries its merkle proof, which the browser verifies against the
chain-anchored root.

TDD: dig-node is 8/8 (adds sync_eligible gating, verifiable-identity header
stamping against a mock §21 host, served-root cache keying, and the
no-identity skip path); digstore_remote::identity carries the seed/signer
tests ported from the CLI.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HDr4m1MiYc5JPfjQ7Ed3fV
Implements the wallet's send/sign increment. digstore_chain::send builds + BLS-
signs a standard XCH payment (AugScheme, drawing coins across the HD wallet):
one lead coin carries the payment + change and reserves the fee, every other
selected coin is spent under its own synthetic key bound to the lead via
ASSERT_CONCURRENT_SPEND, so the bundle is atomic and balances exactly
(inputs == amount + change + fee). Pure build + sign — it never broadcasts.

dig-wallet exposes POST /api/send. Because a broadcast spends REAL mainnet
funds, it is double-gated: it pushes only when the request sets broadcast:true
AND the process runs with DIG_WALLET_ALLOW_BROADCAST=1; otherwise it returns the
fully signed bundle as a dry run (status "signed") and pushes nothing. An
explicit broadcast:true while disabled is refused (403), never silently
downgraded — so the signing path is exercised unattended without spending.

TDD: digstore-chain send 6/6 (balance invariant, multi-coin path, single-coin
no-change, zero-amount + insufficient-funds errors, deterministic non-empty
signing, and a conditions-decode proving the lead CREATE_COINs the recipient the
exact amount); dig-wallet 2/2 on the broadcast safety gate. fmt + clippy
-D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HDr4m1MiYc5JPfjQ7Ed3fV
…/ pinning

Adds a JSON-RPC method that resolves a store's CHIP-0035 chain-anchored tip root
from coinset.org (via the already-tested digstore_chain::singleton::sync_datastore)
— NEVER from the serving node. This is the trusted-root source for fixing the
browser's fail-open dig:// verifier: a rootless dig:// URN must pin
proof.root == anchored_root instead of trusting the rpc-served "latest" root.

This is the verified Rust half of the fix; the thin C++ wiring in the DIG Browser
(call dig.getAnchoredRoot for rootless URNs; drop the "latest" bypass in
VerifyInclusion) is documented in the superproject HANDOFF.md and must be built +
verified on Windows.

TDD: dig-node 12/12 (adds store_id parsing + an invalid-params path that errors
before any chain read); the resolution itself reuses sync_datastore's existing
singleton tests. DIG_NODE_COINSET overrides the coinset base. fmt + clippy
-D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HDr4m1MiYc5JPfjQ7Ed3fV
@MichaelTaylor3d MichaelTaylor3d merged commit 6170f7f into main Jun 25, 2026
3 checks passed
MichaelTaylor3d added a commit that referenced this pull request Jun 27, 2026
Add `digstore deploy` + `digstore deploy-key export` and a composite GitHub
Action so every push can publish a new capsule to an existing DIG store from
CI — like Vercel's Git integration. CI advances the store (never mints: no
`init`).

Why a new command (not pure clone-glue): a fresh CI checkout has no `.dig`.
`commit` needs `config.toml`, `anchor.toml`, `signing_key.bin` (the per-store
BLS PUBLISHER key the DIGHub remote pinned at first push), and a `roots.log`
head at the on-chain tip. `clone` re-keys with a FRESH host key and writes no
`anchor.toml`, so a clone-then-push is rejected (wrong publisher key → 403).
The publisher key is random at `init` and unrecoverable from the wallet seed —
the one irreducible CI secret beyond the funded mnemonic.

- `store_ops::adopt_existing_store`: reconstruct the per-store `.dig` from the
  provided publisher seed + the current on-chain root (read via
  `onchain_tip_root`); seeds anchor.toml=Confirmed@tip and roots.log head=tip
  so the next commit fast-forwards. `read_signing_seed` exports the key.
- `digstore deploy`: reads a committable `dig.toml` (store-id, output-dir,
  build-command, remote, network, wait-timeout; flags/env override), optionally
  builds, adopts, stages output-dir, then runs the canonical `commit -m --push`.
- `digstore deploy-key export`: print/write the 64-hex publisher seed for a CI
  secret (no spend authority).
- Fix: `commit --push --json` now actually pushes. `--push` is built for CI
  (which runs `--json`), but the push only ran in the human branch — json+push
  silently published nothing. Shared `push::push_core` (no double-emitted JSON);
  json output gains `pushed`/`claimed`. Regression-tested.
- action.yml (composite) + examples/ (workflow + dig.toml) + README section.
  Security: v1 ships the funded mnemonic as a CI secret — documented to use a
  dedicated low-balance deploy wallet; scoped revocable tokens are the planned
  Wave-2 replacement.

TDD: 6 store_ops unit tests + 7 cli_deploy integration tests (incl. the core
end-to-end proving a fresh checkout advances the store and the remote accepts
the push with the original publisher key). Full suite green; guest wasm built,
binary reinstalled, fmt + clippy(-D warnings) clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01GjQgGAisBupqs7vpkmUhuN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants