Skip to content

refactor(sdk): shared wire-request decode and pure DPNS/DashPay document builders - #4389

Open
PastaPastaPasta wants to merge 4 commits into
refactor/dash-platform-queriesfrom
refactor/document-query-decode-builders
Open

refactor(sdk): shared wire-request decode and pure DPNS/DashPay document builders#4389
PastaPastaPasta wants to merge 4 commits into
refactor/dash-platform-queriesfrom
refactor/document-query-decode-builders

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Third slice of the feat/transport-free-embedder-core series (#4335; after #4344, #4345, and #4388): gives transport-free embedders the remaining pieces they need to construct and verify Platform interactions without reimplementing SDK logic — the drift-prone code Dash Core's Platform GUI (PastaPastaPasta/dash#67, dashpay/dash#7512) currently hand-builds in C++.

Stacked on #4388 (base branch refactor/dash-platform-queries); will be retargeted to v4.2-dev when that merges. Only the last four commits are new.

What was done?

  • Wire-request decode: DocumentQuery::try_from_request decodes a wire-format GetDocumentsRequest back into a rich DocumentQuery — the inverse of request encoding — by lifting drive-abci's server-side proto conversions into shared client code, so the bytes the server decodes and the client verifies go through the same code. drive-abci now consumes the shared conversions (deduplicated, −382 lines in its conversions.rs). Round-trip coverage in tests/document_query_wire_roundtrip.rs.
  • Request-driven proof verification: documents::verify_documents_response delegating to drive-proof-verifier's FromProof, keyed on the exact request bytes sent.
  • Pure document builders: build_dpns_preorder_and_domain_documents (salted-domain-hash preorder/domain pair) and dashpay::build_contact_request_document, extracted from rs-sdk's networked flows. Crypto material is supplied by the caller — ECDH/key custody stays out of this crate.
  • Consensus-aligned DPNS validation: split is_consensus_valid_label (matches the DPNS contract regex; gates the builders) from is_valid_username (stricter client-side policy, e.g. consecutive-hyphen rejection) so builders cannot reject labels the contract accepts.
  • rs-sdk re-exports everything at its old paths; no consumer changes imports.

How Has This Been Tested?

Breaking Changes

None. Moved items remain importable at their previous dash_sdk paths; drive-abci's request decoding behavior is unchanged (same conversions, now shared).

PastaPastaPasta and others added 4 commits August 12, 2026 19:02
…d client code

The proto-to-domain decoding for document queries existed only server-side
(rs-drive-abci's v1 conversions), so a client verifying a documents proof had
to reconstruct the query shape by hand and could silently drift from what the
server actually proves. Move the decode logic into
dash-platform-queries::documents::proto_conversions with a neutral error
type; drive-abci's conversions module becomes a thin mapping onto its
QueryError surface with identical error message strings.

On top of the shared decoder, DocumentQuery::try_from_request(request,
contract) reconstructs the rich query from the wire request (both request
versions), and verify_documents_response(...) /
verify_documents_response_with_provider_contract(...) give embedders a
request-driven verification entry point that delegates to the existing
FromProof machinery, resolving the contract explicitly or via
ContextProvider::get_data_contract.

Round-trip tests cover encode-decode equality for representative queries in
both wire versions plus malformed-clause rejection; drive-abci's
document_query unit tests pass unchanged (76 cases).
Move the document *content* assembly of rs-sdk's networked DPNS and DashPay flows into transport-free functions in dash-platform-queries, so offline/embedder consumers and rs-sdk share one implementation:

- build_dpns_preorder_and_domain_documents assembles the preorder and domain documents exactly as register_dpns_name did: both ids from the same entropy via generate_document_id_v0, saltedDomainHash = sha256d(salt || normalized_label + ".dash"), and the full domain property map (parentDomainName/normalizedParentDomainName, label, normalizedLabel, preorderSalt, records.identity, subdomainRules.allowSubdomains=false). It additionally rejects labels failing is_valid_username up front - previously only enforced by rs-sdk-ffi and platform consensus - so register_dpns_name now fails locally on an invalid label instead of after a network round-trip.

- build_contact_request_document assembles the DIP-15 contactRequest id and property map from already-derived crypto material (encrypted xpub/label bytes, key indices, entropy). ECDH, encryption, the 69-byte compact-xpub check, key purpose checks, and recipient fetching stay in rs-sdk; the ciphertext size validations (96-byte xpub, 48-80-byte label, 38-102-byte autoAcceptProof) moved into the builder, with validate_auto_accept_proof also called early in create_contact_request to keep the pre-fetch fail-fast.

- ensure_entropy_matches_document_id and prepare_document_for_transition moved from put_document.rs into dash_platform_queries::transition::put_document; rs-sdk re-exports and keeps calling them.

Entropy/salt generation and all networking remain in rs-sdk. Builder validation errors surface through the new dash_platform_queries::Error::InvalidInput variant, which rs-sdk maps back to Error::Generic with the exact pre-move messages. New unit tests in dash-platform-queries pin a DPNS known vector (document ids and property maps for fixed label/entropy/salt), mirror the entropy-derives-id relation for contact requests, and cover the negative validation paths.
…er seams

Review follow-ups on the transport-free series:

- The DPNS document builder validated labels with is_valid_username, whose
  consecutive-hyphen rejection is stricter than the DPNS contract's schema
  pattern - consensus accepts names like ab--cd. Split the check: new
  is_consensus_valid_label matches the contract pattern exactly and gates
  the builder (so dash-sdk's register_dpns_name no longer refuses
  consensus-valid labels), while is_valid_username keeps the stricter
  policy for its existing FFI/wasm gates and now documents the difference.
- verify_documents_response rejects aggregate projections (COUNT/SUM/AVG)
  up front with a pointer to the aggregate proof helpers, instead of
  surfacing an opaque low-level proof error; try_from_request documents
  that it mirrors the server's wire-shape decode, not validate_and_route
  business rules.
- The CI transport-leak guard also asserts wasm-sdk's wasm32 tree stays
  free of the native transport stack.
- The proof-vector corpus gains a README with an explicit coverage matrix:
  the four documents-family cases pin query shape and clean decode failure
  but stop before the BLS check (placeholder payloads in the fixture
  state); identity, contested, and quorum-sig families run the full
  pipeline. This corrects the corpus commit's broader claim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d9279a23-f8f0-42f6-a974-d16b37b720c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

⛔ Blockers found — Opus deferred (commit 2a6dbe3)
Canonical validated blockers: 1

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preliminary review — Codex only

The shared decoders and extracted builders largely preserve the existing behavior, but the new request-driven document verifier does not bind verification to every semantically relevant request field. A malicious transport can therefore substitute a valid proof for a different query, so this trust-boundary issue must be fixed before merging.
Source: reviewer backend model gpt-5.6-sol; final verifier backend model gpt-5.6-sol. Orchestration-only: openclaw-agent/cliproxy/gpt-5.6-sol (not reviewer evidence).

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/dash-platform-queries/src/documents/document_query.rs`:
- [BLOCKING] packages/dash-platform-queries/src/documents/document_query.rs:665-678: Reject request fields that are discarded before proof verification
  Validating only the `select` projection does not ensure that the proof corresponds to the wire request. The subsequent `TryFrom<&DocumentQuery> for DriveDocumentQuery` conversion discards `group_by` and `having`, even though the server rejects both for `SELECT DOCUMENTS`; consequently, an untrusted transport can pair a request such as `SELECT DOCUMENTS GROUP BY age` with a valid proof for the corresponding plain document query and this function will accept it. The conversion also narrows `request.limit` with `as u16` at lines 1127-1129, so a wire limit of 65537 becomes 1 even though the server rejects limits above `u16::MAX`. Plain-document `offset` and a false `prove` flag are additional request shapes that cannot produce this proved response from the real server but are not rejected here. GroveDB and Tenderdash proofs authenticate the state and resolved Drive query, not the discarded request envelope. Before delegating, reject every field incompatible with a proved plain-document request (`group_by`, `having`, `offset`, and `prove == false`) and use a checked `u16::try_from` conversion for the limit so no request information is silently changed.

Comment on lines +665 to +678
// This entry point verifies plain document fetches only. An aggregate
// projection (COUNT/SUM/AVG) is proved with a different proof shape;
// handing it to the Documents verifier would surface as an opaque
// low-level proof error, so reject it up front instead.
if query.select != drive::query::SelectProjection::documents() {
return Err(drive_proof_verifier::Error::RequestError {
error: format!(
"verify_documents_response only verifies plain document fetches; the request \
carries a {:?} projection — use the aggregate proof helpers instead",
query.select.function
),
});
}
<Documents as FromProof<DocumentQuery>>::maybe_from_proof_with_metadata(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Reject request fields that are discarded before proof verification

Validating only the select projection does not ensure that the proof corresponds to the wire request. The subsequent TryFrom<&DocumentQuery> for DriveDocumentQuery conversion discards group_by and having, even though the server rejects both for SELECT DOCUMENTS; consequently, an untrusted transport can pair a request such as SELECT DOCUMENTS GROUP BY age with a valid proof for the corresponding plain document query and this function will accept it. The conversion also narrows request.limit with as u16 at lines 1127-1129, so a wire limit of 65537 becomes 1 even though the server rejects limits above u16::MAX. Plain-document offset and a false prove flag are additional request shapes that cannot produce this proved response from the real server but are not rejected here. GroveDB and Tenderdash proofs authenticate the state and resolved Drive query, not the discarded request envelope. Before delegating, reject every field incompatible with a proved plain-document request (group_by, having, offset, and prove == false) and use a checked u16::try_from conversion for the limit so no request information is silently changed.

source: ['codex']

PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Aug 13, 2026
…-builders base

Move all seven dashpay/platform git dependencies from the old feat/transport-free-embedder-core pin (e8e1961fe54f) to rev 2a6dbe39065104981b7f9bb4fbee598aab869fe4, the head of refactor/document-query-decode-builders (PR dashpay/platform#4389) whose content is the rebased equivalent on the current v4.2-dev base. No FFI-visible API drift: the crate builds unchanged and all 31 rust/platform tests pass against the new revision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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