docs: add DIP-33 (DashPay payment addresses) - #188
Conversation
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>
📝 WalkthroughWalkthroughDIP-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. ChangesDashPay payment addresses
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
README.mddip-0033.mdproject-words.txt
…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>
|
Addressed the review in 0fdee78:
|
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
dip-0033.md
|
|
||
| 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. |
There was a problem hiding this comment.
📐 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
| | `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 | |
There was a problem hiding this comment.
🗄️ 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.
…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.
|
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 |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
dip-0033.md
| 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" |
There was a problem hiding this comment.
🗄️ 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' {} \; || trueRepository: 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 -1200Repository: 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]}")
PYRepository: 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
doneRepository: 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:
- 1: https://docs.dash.org/projects/platform/en/latest/docs/explanations/dashpay.html
- 2: https://docs.dash.org/projects/platform/en/stable/docs/explanations/dashpay.html
- 3: https://docs.dash.org/projects/platform/en/latest/docs/explanations/platform-protocol-data-contract.html
- 4: https://docs.dash.org/en/stable/docs/core/dips/dip-0015.html
- 5: https://docs.dash.org/projects/platform/en/stable/docs/reference/data-contracts.html
- 6: https://docs.dash.org/projects/platform/en/latest/docs/reference/data-contracts.html
- 7: https://github.com/dashevo/dashpay-contract
- 8: https://www.npmjs.com/package/@dashevo/dashpay-contract
- 9: https://github.com/dashpay/platform/blob/v3.1-dev/packages/dashpay-contract/schema/v1/dashpay.schema.json
- 10: https://github.com/dashpay/platform/tree/master/packages/dashpay-contract
🏁 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)
PYRepository: 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
| * 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. |
There was a problem hiding this comment.
🗄️ 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.mdRepository: 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.mdRepository: 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)))
PYRepository: 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
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:
PAYMENT_SCAN(7) andPAYMENT_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.33'derivation:m/9'/coin'/33'/account'/key_class'/index'(scan / spend / notification out-key classes).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
Documentation