Skip to content

docs: add DIP-33 (DashPay payment addresses) - #188

Open
QuantumExplorer wants to merge 5 commits into
masterfrom
dip-payment-addresses-and-notifications
Open

docs: add DIP-33 (DashPay payment addresses)#188
QuantumExplorer wants to merge 5 commits into
masterfrom
dip-payment-addresses-and-notifications

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

Adds DIP 0033: DashPay Payment Addresses and Payment Notifications — two-tier payment addressing for DashPay across the Core chain and Platform payment address (DIP-17/DIP-18) rails.

Public tier — optional static address fields on the DashPay profile document:

  • corePaymentAddress (Base58Check string, position 5)
  • platformPaymentAddress (21-byte DIP-18 storage form, position 6)

Payments to these are publicly linkable to the profile by design (the "tips jar"). Extended public keys are deliberately rejected: a world-readable derivation source is exactly as linkable as one static address while adding gap-limit and index-coordination costs.

Notified tier — private payments without a prior contact relationship:

  • Two new identity key purposes, PAYMENT_SCAN (7) and PAYMENT_SPEND (8): non-signing, no contract bounds, at most one active each. Registry placement puts key changes behind master-key authorization and makes any identity payable without a DashPay profile.
  • DIP-9 feature 33' derivation: m/9'/coin'/33'/account'/key_class'/index' (scan / spend / notification out-key classes).
  • Stealth one-time address derivation shared by both rails, rail-domain-separated in the tweak hash; P2PKH-only.
  • Encrypted payment notification documents in an append-only private store, discovered by trial decryption: fixed 586-byte entries, five rail codes, inner recoverable signature for sender attribution, an anonymous-sender variant, and an OVK-style out-ciphertext so the payer can also recover payments from seed.
  • A normative ordering rule: for one-time address payments the notification must reach Platform finality before settlement broadcast — stealth outputs are unrecoverable from the recipient's seed alone, so the store is the recovery record.

Includes rationale for rejecting BIP-47-style notification transactions and BIP-352-style scanning (input public keys are absent from compact block filters, and the sought output script is uncomputable without per-transaction ECDH — so scanning degenerates to downloading every transaction forever), self-verified secp256k1 test vectors, and reservations for a shielded-pool tier and a future tweak-index scanning mode.

Also in this PR: README table row, a few dictionary words, and a fix for the one pre-existing markdownlint error on the README ([here] link text) so CI passes on changed files.

Note: DIP number 32 is intentionally skipped to avoid confusion with BIP-32, since both concern key derivation.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added DIP-0033 documentation for DashPay public and shielded payment addresses.
    • Documented profile and contact address fields, encrypted shielded addresses, payment-reference memos, validation, restoration, and privacy requirements.
  • Documentation

    • Updated the BIP repository reference and proposal summary.
    • Clarified that earlier payment-notification and detection specifications are deferred and withdrawn.
    • Added payment-related terminology to the spell-check dictionary.

Two-tier payment addressing for DashPay across the Core chain and
Platform payment address (DIP-17/18) rails:

* Public tier: optional corePaymentAddress / platformPaymentAddress
  fields on the profile document (static, deliberately linkable).
* Notified tier: PAYMENT_SCAN / PAYMENT_SPEND identity key purposes,
  DIP-9 feature 33' derivation, stealth one-time address derivation
  shared by both rails, and encrypted payment notification documents
  with sender recovery and an anonymous-sender variant.

Reserves the shielded-pool tier and a silent-payments-style tweak
index as future extensions. Includes derivation test vectors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

DIP-0033 specifies public and shielded DashPay payment addresses, profile and contact fields, payment-reference memos, client handling, security requirements, and deferred stealth notifications. Repository indexing and spell-check terms are also updated.

Changes

DashPay payment addresses

Layer / File(s) Summary
Payment-address contracts
dip-0033.md
Defines public Core and Platform addresses, shielded profile addresses, encrypted contact addresses, validation, rotation, and attribution.
Payment detection and client behavior
dip-0033.md
Adds PaymentRef and reserved memo kinds. It specifies detection, attribution, restoration, unshielding, watch-only behavior, compatibility, security, and privacy requirements.
Deferred protocol and repository registration
dip-0033.md, README.md, project-words.txt
Documents deferred transparent-rail notifications, reserves future extensions, adds DIP-0033 to the proposal table, updates the BIP link, and adds spell-check terms.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

  • dashpay/dash#7512 — The issue tracks DashPay contact and payment implementation, which directly overlaps with this specification’s contact and payment-address behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding DIP-33 for DashPay payment addresses.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dip-payment-addresses-and-notifications

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dip-0033.md`:
- Around line 194-201: Bind notifications to the exact settled amount and
transition: require the Core output amount to equal the signed amount, and add a
Platform transition locator plus proof binding the credited amount rather than
relying on aggregate balance/nonce state. Update notification deduplication and
restore logic to use this locator, including the corresponding flows near the
Platform verification and restore sections.
- Around line 127-139: Update the DIP-11 identity public-key schema and related
validation to accept purpose values 7 and 8 for PAYMENT_SCAN and PAYMENT_SPEND,
while preserving their required non-signing, no-contract-bounds, and
single-active-key constraints. Activate the updated schema and DIP-0033 under
the same protocol-version rule so validators accept these purposes only when
that version is enabled.
- Around line 288-297: Revise the Anonymous variant and Funding modes text in
dip-0033.md to distinguish inner-payload anonymity from envelope-level creator
attribution: identity-funded notifications must still be rendered as revealing
their document creator, while pool-funded anonymous notifications can omit payer
identity entirely. Update the client-rendering, abstract, and privacy statements
accordingly, and remove or qualify the claim that attribution always comes from
the inner signature.
- Around line 183-186: Update the scalar derivation and serialization rules
around t_n, esk, r, and ovk to make them normative: interpret each hash digest
as a big-endian integer, encode k_out as a fixed 32-byte scalar before
concatenation, and require regenerating eskSeed whenever esk == 0 or r == 0.
Apply the same clarification to the corresponding derivations in the referenced
sections so all implementations produce identical values.
- Around line 362-375: Update pay_notified to reject rail values other than 0
and 2 before deriving the stealth destination, so public profile rails 1 and 3
cannot enter this flow. Preserve the existing stealth derivation for supported
rails and require a separate public-payment path for public rails.
- Around line 139-162: The periodic notification scan must retain disabled
historical PAYMENT_SCAN keys and rescan each one once, not only the currently
active registered scan key. Update the periodic scan and key-rotation handling
to include disabled scan-key versions alongside the active key, while preserving
the existing restore behavior that replays the notification store from position
zero for every derived key.
- Around line 249-256: Update the signature verification requirements around
ECDSA_sign_recoverable so recipients resolve senderKeyIndex against the sender’s
authentication-capable key state at notification publication or finality, retain
proof of that historical key, and reject or anonymize notifications lacking
valid historical verification. Extend the signed preimage to include
senderIdentityId and senderKeyIndex alongside the existing fields, and require
both values during verification.
- Around line 207-211: Extend the paymentNotification document-type section to
define bounded read and admission semantics: specify how recipients are
partitioned or how private range reads are scoped so clients do not scan all
recipients, and define quotas or rate limits for immutable entries to bound
light-client trial-decryption work and append-only storage growth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 945aef72-cdea-4a28-b096-92c1c2ffed18

📥 Commits

Reviewing files that changed from the base of the PR and between a4d46dd and d3ed319.

📒 Files selected for processing (3)
  • README.md
  • dip-0033.md
  • project-words.txt

Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
Comment thread dip-0033.md Outdated
…g, rotation scanning

* Define int_be / ser_scalar and zero-scalar regeneration normatively
* Bind sig preimage to senderIdentityId + senderKeyIndex; verify against
  key state at notification creation (disabled-after counts as valid)
* Settled value is authoritative; signed amount must match for verified
  attribution; rail 2 gains an optional stHash settlement locator
* Anonymous variant must be pool-funded; clarify inner vs envelope
  attribution
* Scan-key rotation: retain disabled keys, grace-period dual scanning
* Document store admission economics and deliberate non-partitioning
* Guard pay_notified pseudo-code to notified rails only
* Couple the DIP-11 purpose registry update to the same activation rule

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Addressed the review in 0fdee78:

  • Scalar encodingint_be/ser_scalar defined normatively; zero-scalar seeds are regenerated; pseudo-code updated to match.
  • Signature binding — preimage now includes senderIdentityId and senderKeyIndex; verification resolves the key at notification creation time (a key disabled after creation still verifies — identity keys are disabled, never removed, so current state suffices).
  • Amount/transition binding — the settled value (Core output value / proven address balance) is authoritative for crediting; the signed amount must equal it for verified attributed display; rail 2 gains an optional stHash settlement locator (a one-time platform address receives exactly one payment by construction, which is why the derived address remains the primary locator).
  • Inner vs envelope attribution — the anonymous variant is now MUST-pool-funded, and the funding-modes text distinguishes verified attribution (inner sig only) from envelope metadata.
  • Scan-key rotation — new client rule: retain disabled detection keys, dual-scan with the previous scan key for a grace period, include all historical keys on restore.
  • pay_notified guard — restricted to rails 0/2.
  • DIP-11 registry — coupled textually in Backwards Compatibility (registry + validators extend with purposes 7/8 under the same protocol-version rule). Deliberately not editing dip-0011/identityPublicKey.json here: that schema is already stale against the implemented protocol (purposes stop at 3 and describe 3 as encryption+decryption, while the implementation defines 3–6 as TRANSFER/SYSTEM/VOTING/OWNER) — refreshing it is separate DIP-11 maintenance.
  • Bounded reads/admission — documented as designed: admission is fee-priced (no free write path), and the store is deliberately not recipient-partitioned because recipient-scoped reads would leak recipient activity to serving nodes; scan cost scales with global throughput, mitigated by fixed-size entries, chunked proofs, and scan-key delegation. Platform-side quotas are out of scope for this DIP.

The character-class pattern could not prove the checksum or network byte,
so clients had to fully validate the address regardless. Enforcing it on
chain only bought a partial check while making every optional-field
document generator produce schema-invalid profiles.

Consensus now constrains the length only; full validation is explicitly a
client responsibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dip-0033.md`:
- Line 118: Update the Markdown wording in the corePaymentAddress description to
use the hyphenated “on-chain” form instead of “on chain,” without changing the
surrounding validation requirements.
- Line 261: Update the notification verification requirements around
senderIdentityId, senderKeyIndex, and sig so any payload with claimed sender
fields whose signature fails verification is discarded, rather than downgraded
to anonymous. Preserve the anonymous variant only when all three sender fields
are zero, and retain the existing authentication-capability and key-validity
checks for attributed notifications.
- Around line 247-248: Update the rail-2 deduplication rule to treat an all-zero
stHash as absent rather than as a matching identifier. Deduplicate
locator-bearing notifications using the derived address and outputCounter, and
use non-zero stHash only as an additional identifier; preserve separate entries
for unrelated or multiple outputs that lack a usable locator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b990fb3-c6d5-440b-ae3a-d3112cae4cda

📥 Commits

Reviewing files that changed from the base of the PR and between d3ed319 and 1b04d3b.

📒 Files selected for processing (1)
  • dip-0033.md

Comment thread dip-0033.md Outdated

Normative requirements:

* `corePaymentAddress` MUST be a valid Base58Check Core chain address for the network the contract is deployed on. Consensus constrains only the string length: a Base58 character-class pattern would still not prove the checksum or network byte, so validation of the address proper is a client responsibility and is not duplicated on chain. Clients MUST validate the checksum and network byte before paying, and MUST treat an undecodable value as no address rather than as an error in the profile.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use on-chain in the Markdown text.

Replace on chain with on-chain to resolve the reported documentation lint warning.

🧰 Tools
🪛 LanguageTool

[grammar] ~118-~118: Use a hyphen to join words.
Context: ... responsibility and is not duplicated on chain. Clients MUST validate the checksu...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dip-0033.md` at line 118, Update the Markdown wording in the
corePaymentAddress description to use the hyphenated “on-chain” form instead of
“on chain,” without changing the surrounding validation requirements.

Source: Linters/SAST tools

Comment thread dip-0033.md Outdated
Comment on lines +247 to +248
| `2` | Platform, one-time address | `R[33] \|\| outputCounter[4] \|\| amount[8] \|\| fundingKind[1] \|\| stHash[32]` — amount in credits; `fundingKind`: `0` = address funds transfer, `1` = unshielding. The derived address is the primary locator; `stHash` (all-zeros when absent) SHOULD name the crediting state transition to bind the exact settlement |
| `3` | Platform, public profile address | `platformAddress[21] \|\| amount[8] \|\| stHash[32]` — amount in credits; `stHash` identifies the crediting state transition |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Exclude an absent stHash from deduplication.

Rail 2 uses an all-zero stHash when no locator exists. The deduplication rule at Line 318 treats equal stHash values as duplicates. That can merge unrelated rail-2 notifications without locators. It can also merge multiple outputs from one transition. Treat zero as absent and deduplicate by derived address plus outputCounter; use stHash only when non-zero.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dip-0033.md` around lines 247 - 248, Update the rail-2 deduplication rule to
treat an all-zero stHash as absent rather than as a matching identifier.
Deduplicate locator-bearing notifications using the derived address and
outputCounter, and use non-zero stHash only as an additional identifier;
preserve separate entries for unrelated or multiple outputs that lack a usable
locator.

Comment thread dip-0033.md Outdated
…formPaymentAddress

The Base58Check string form was rejected: consensus could not prove the
checksum or network byte anyway, the byte form is exact-size-validated
and smaller, and omitting the network byte makes a network mismatch
unrepresentable (the network follows the Platform chain the document
lives on).
The notified stealth-address tier is deferred and recorded in an
informational appendix. Any payment-time Platform artifact (the
notification that stealth discovery depended on) is timing-correlatable
with its settlement transaction — fatally for identity-funded
notifications, and still marking settlements as private payments when
pool-funded. The correlation-free alternative (a BIP-352-style tweak
index) is real infrastructure, and the shielded pool already exceeds
the tier's goals: it hides amounts, needs no discovery artifact, and
recovers from seed plus chain alone.

The new shielded tier standardizes the profile shieldedAddress field,
per-contact diversified addresses in contact requests, and the
PaymentRef memo kind. Detection keys, stealth derivation, notification
format, and vectors remain in project history for a future revival
paired with a tweak index; the DIP-9 feature index 33' reservation is
withdrawn.
@QuantumExplorer QuantumExplorer changed the title docs: add DIP-33 (DashPay payment addresses and payment notifications) docs: add DIP-33 (DashPay payment addresses) Aug 12, 2026
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Rescoped (fec74e3): the DIP now specifies two tiers — the public tips tier (transparent profile addresses, as merged in dashpay/platform#4380) and a shielded tier (profile shieldedAddress, per-contact diversified addresses via contact requests, PaymentRef memo kind). The notified stealth-address tier is deferred: its design and the deferral rationale (payment-time Platform artifacts are timing-correlatable with settlements) are recorded in an informational appendix, and its implementation is parked in dashpay/platform#4272. Title updated accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dip-0033.md`:
- Around line 145-154: Update the versioned contactRequest contract schema to
declare the optional encryptedShieldedAddress array at position 7 with byteArray
enabled and 64–80 item bounds, while preserving additionalProperties: false.
Also update the associated schema-validation trigger so documents containing
this field are validated against the revised schema.
- Around line 160-164: Define a deterministic selection rule for multiple
immutable contact requests, specifying which decrypted encryptedShieldedAddress
is used for new payments. Require clients to continue monitoring all superseded
contact-request addresses for incoming payments and during wallet restoration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d0373841-9847-4b86-90dc-9c0749ed651d

📥 Commits

Reviewing files that changed from the base of the PR and between 18e0756 and fec74e3.

📒 Files selected for processing (1)
  • dip-0033.md

Comment thread dip-0033.md
Comment on lines +145 to +154
The `contactRequest` document type gains one OPTIONAL field. Contact requests are immutable; the field is set at creation:

```json
"encryptedShieldedAddress": {
"type": "array",
"byteArray": true,
"minItems": 64,
"maxItems": 80,
"position": 7,
"description": "Sender's diversified shielded payment address (43 bytes), encrypted to the contact with the DIP-15 shared key"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Contact-request declarations =="
rg -n -C 8 \
  'autoAcceptProof|coreHeightCreatedAt|encryptedPublicKey|encryptedShieldedAddress|position' \
  dip-0015.md dip-0033.md || true

echo "== DashPay contract schemas =="
fd -a -i 'dashpay.*schema.*json|schema.*dashpay.*json' . \
  -x rg -n -C 4 \
  'contactRequest|autoAcceptProof|coreHeightCreatedAt|encryptedShieldedAddress|additionalProperties' {} \; || true

Repository: dashpay/dips

Length of output: 22701


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Repository files related to DashPay contracts =="
git ls-files | rg -i 'dashpay|contract|schema|contact|dip-0015|dip-0033' | head -200

echo "== Contact request schema and field-order references =="
rg -n -C 12 \
  'contactRequest|contact request|autoAcceptProof|coreHeightCreatedAt|encryptedPublicKey|position|additionalProperties|version' \
  --glob '*.md' --glob '*.json' --glob '*.yaml' --glob '*.yml' . | head -1200

Repository: dashpay/dips

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

for name in ("dip-0015.md", "dip-0033.md"):
    p = Path(name)
    print(f"== {name} ==")
    lines = p.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if re.search(r'contactRequest|autoAcceptProof|coreHeightCreatedAt|encryptedPublicKey|position', line, re.I):
            start, end = max(1, i - 5), min(len(lines), i + 8)
            print(f"-- lines {start}-{end} --")
            for n in range(start, end + 1):
                print(f"{n}:{lines[n-1]}")
PY

Repository: dashpay/dips

Length of output: 25398


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for url in \
  "https://raw.githubusercontent.com/dashevo/dashpay-contract/master/schema/dashpay.schema.json" \
  "https://raw.githubusercontent.com/dashevo/dashpay-contract/develop/schema/dashpay.schema.json" \
  "https://raw.githubusercontent.com/dashpay/dashpay-contract/master/schema/dashpay.schema.json"
do
  echo "== $url =="
  if curl -fsSL --max-time 20 "$url" | python3 -c '
import json, sys
d=json.load(sys.stdin)
def walk(x, path=""):
    if isinstance(x, dict):
        if "properties" in x and isinstance(x["properties"], dict):
            for n, v in x["properties"].items():
                if n in {"$ownerId","toUserId","senderKeyIndex","recipientKeyIndex","accountReference","encryptedAccountLabel","encryptedPublicKey","autoAcceptProof","$coreHeightCreatedAt","$createdAt","encryptedShieldedAddress"} or "position" in v:
                    print(path + "/" + n, "position=", v.get("position"), "required=", n in x.get("required", []))
        for k,v in x.items():
            walk(v, path + "/" + k)
    elif isinstance(x, list):
        for i,v in enumerate(x):
            walk(v, path + f"/{i}")
walk(d)
' ; then
    break
  fi
done

Repository: dashpay/dips

Length of output: 689


🌐 Web query:

dashevo dashpay-contract dashpay.schema.json contactRequest position autoAcceptProof

💡 Result:

In the DashPay data contract, the contactRequest document type includes an autoAcceptProof property with a defined position of 6 [1][2][3]. The autoAcceptProof field is an optional byte array (38–102 bytes) intended to provide proof that allows a recipient to automatically accept a contact request [1][4][2]. The position value is a mandatory attribute in Dash Platform data contracts, used to ensure consistent ordering for serialization and deserialization processes, with numbering starting at zero [5][6]. In the context of the contactRequest schema, the properties and their respective positions are as follows: - toUserId: position 0 [1][2][3] - encryptedPublicKey: position 1 [1][2][3] - senderKeyIndex: position 2 [1][2][3] - recipientKeyIndex: position 3 [1][2][3] - accountReference: position 4 [1][2][3] - encryptedAccountLabel: position 5 [1][2][3] - autoAcceptProof: position 6 [1][2][3] The original dashevo/dashpay-contract repository has been deprecated and moved to the Dash Platform repository [7][8]. The current authoritative source for this schema is located within the dashpay-contract package in the dashpay/platform repository [9][10][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

url="https://raw.githubusercontent.com/dashpay/platform/v3.1-dev/packages/dashpay-contract/schema/v1/dashpay.schema.json"
curl -fsSL --max-time 20 "$url" | python3 - <<'PY'
import json, sys
d = json.load(sys.stdin)

def walk(x, path=""):
    if isinstance(x, dict):
        props = x.get("properties")
        if isinstance(props, dict):
            for name, value in props.items():
                if name in {
                    "$ownerId", "toUserId", "senderKeyIndex", "recipientKeyIndex",
                    "accountReference", "encryptedAccountLabel", "encryptedPublicKey",
                    "autoAcceptProof", "$coreHeightCreatedAt", "$createdAt",
                    "encryptedShieldedAddress"
                }:
                    print(path + "/" + name, "position=", value.get("position"),
                          "required=", name in x.get("required", []))
            print(path, "additionalProperties=", x.get("additionalProperties"))
        for key, value in x.items():
            walk(value, path + "/" + key)
    elif isinstance(x, list):
        for i, value in enumerate(x):
            walk(value, path + f"/{i}")

walk(d)
PY

Repository: dashpay/dips

Length of output: 828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

curl -fsSL --max-time 20 \
  "https://raw.githubusercontent.com/dashpay/platform/v3.1-dev/packages/dashpay-contract/schema/v1/dashpay.schema.json" |
python3 -c '
import json, sys
d = json.load(sys.stdin)

def walk(x, path=""):
    if isinstance(x, dict):
        props = x.get("properties")
        if isinstance(props, dict):
            for name, value in props.items():
                if name in {
                    "$ownerId", "toUserId", "senderKeyIndex", "recipientKeyIndex",
                    "accountReference", "encryptedAccountLabel", "encryptedPublicKey",
                    "autoAcceptProof", "$coreHeightCreatedAt", "$createdAt",
                    "encryptedShieldedAddress"
                }:
                    print(path + "/" + name,
                          "position=", value.get("position"),
                          "required=", name in x.get("required", []))
            if "additionalProperties" in x:
                print(path, "additionalProperties=", x["additionalProperties"])
        for key, value in x.items():
            walk(value, path + "/" + key)
    elif isinstance(x, list):
        for i, value in enumerate(x):
            walk(value, path + f"/{i}")

walk(d)
'

Repository: dashpay/dips

Length of output: 691


Add encryptedShieldedAddress to the versioned contract schema.

Position 7 is unused; the existing contactRequest fields occupy positions 0–6. However, the current schema sets additionalProperties to false and does not declare encryptedShieldedAddress. Update the schema and validation trigger together.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dip-0033.md` around lines 145 - 154, Update the versioned contactRequest
contract schema to declare the optional encryptedShieldedAddress array at
position 7 with byteArray enabled and 64–80 item bounds, while preserving
additionalProperties: false. Also update the associated schema-validation
trigger so documents containing this field are validated against the revised
schema.

Source: MCP tools

Comment thread dip-0033.md
Comment on lines +160 to +164
* The plaintext is the sender's 43-byte raw shielded payment address at a **fresh diversifier index unique to this contact**.
* Encryption MUST use the same ECDH shared secret derivation as the contact request's `encryptedPublicKey` field ([DIP-15](dip-0015.md)), with AES-256-CBC and a random 16-byte IV prepended to the ciphertext (16 + 48 = 64 bytes). Values longer than 64 bytes are reserved for future encryption modes; clients MUST ignore an `encryptedShieldedAddress` they cannot decrypt or decode.
* The recipient of the contact request uses this address for all shielded payments to the sender, in preference to the sender's public profile `shieldedAddress`.

Per-contact diversified addresses provide attribution for free: because the wallet records which contact each diversifier index was minted for, an incoming note to that address identifies the paying contact without any memo — while remaining unlinkable, on chain and to third parties, to the profile address and to every other contact's address.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- DIP-0033 relevant sections ---'
sed -n '130,180p' dip-0033.md
printf '%s\n' '--- DIP-0015 account/version and replacement rules ---'
rg -n -i -C 8 'account|version|replace|immutable|active|selection|supersed|request' dip-0015.md | head -n 240
printf '%s\n' '--- DIP-0033 references to request selection and encryptedShieldedAddress ---'
rg -n -i -C 5 'encryptedShieldedAddress|contact request|account|version|supersed|previous address|restoration' dip-0033.md

Repository: dashpay/dips

Length of output: 31527


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- All DIP-0015 version/replacement references ---'
rg -n -i -C 6 'version|replace|replac|supersed|immutable|latest|newer|accountReference|account reference' dip-0015.md
printf '%s\n' '--- Contact-request schemas and position 7 ---'
rg -n -i -C 8 'contactRequest|encryptedPublicKey|accountReference|position.?7|position: 7|position": 7' --glob '*.json' --glob '*.md' --glob '*.yaml' --glob '*.yml' .
printf '%s\n' '--- DIP-0015 contact-request section ---'
sed -n '205,380p' dip-0015.md

Repository: dashpay/dips

Length of output: 41275


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

dip15 = Path("dip-0015.md").read_text()
dip33 = Path("dip-0033.md").read_text()

section = dip15.split("### The account reference (accountReference)", 1)[1].split("### The encrypted account label", 1)[0]
print("DIP-15 account-reference section facts:")
print("  accountReference is part of the contactRequest primary key:",
      "Together `$ownerId`,\n`toUserId` and `accountReference` form the unique primary key" in dip15)
print("  later-version guidance is optional:",
      "can optionally offer" in section and "If accepted by the recipient" in section)
print("  multiple-request guidance asks the user or disregards future requests:",
      "either disregard all future contact requests" in section and
      "ask the user" in section)
print("  mandatory single active-request rule present:",
      bool(re.search(r"\b(MUST|must|required|shall)\b[^\n]*(active|select|latest|newest)", section, re.I)))

contact_section = dip33.split("### Per-Contact Shielded Addresses", 1)[1].split("### Shielded Memo Kinds", 1)[0]
print("DIP-33 per-contact section facts:")
print("  says contact requests are immutable:", "Contact requests are immutable" in contact_section)
print("  states one address is used for all shielded payments:",
      "uses this address for all shielded payments" in contact_section)
print("  defines multiple-request address selection:",
      bool(re.search(r"multiple contact requests|superseded|latest|newest|active .*address|address.*active", contact_section, re.I)))
PY

Repository: dashpay/dips

Length of output: 623


Define selection for multiple contact requests.

DIP-15 allows multiple immutable contact requests but provides no mandatory rule for selecting one. Define which encryptedShieldedAddress clients use for new payments, and require monitoring superseded addresses for incoming payments and wallet restoration.

🧰 Tools
🪛 LanguageTool

[style] ~162-~162: ‘in preference to’ might be wordy. Consider a shorter alternative.
Context: ...or all shielded payments to the sender, in preference to the sender's public profile `shieldedAd...

(EN_WORDINESS_PREMIUM_IN_PREFERENCE_TO)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dip-0033.md` around lines 160 - 164, Define a deterministic selection rule
for multiple immutable contact requests, specifying which decrypted
encryptedShieldedAddress is used for new payments. Require clients to continue
monitoring all superseded contact-request addresses for incoming payments and
during wallet restoration.

Source: MCP tools

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.

1 participant