Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
970b63a
feat(platform)!: DashPay contact requests declare their checks (PV14)
QuantumExplorer Sep 23, 2026
64fb59c
test(drive-abci): re-pin the not-mutable replace fees for the fixed-s…
QuantumExplorer Sep 23, 2026
11c7ebc
Merge v5.1-dev, superseding this branch's protocol version 14 edits
QuantumExplorer Oct 31, 2026
fdcd160
feat(platform)!: dashpay v3 declares the checks of its data triggers …
QuantumExplorer Oct 31, 2026
8b16360
fix(platform): drop the needless struct update in the protocol versio…
QuantumExplorer Oct 31, 2026
ecf47d0
docs(platform): name the table that keeps DashPay v2 for protocol ver…
QuantumExplorer Oct 31, 2026
3489faf
feat(platform)!: keep the DashPay v3 profile address descriptions in …
QuantumExplorer Nov 1, 2026
9a20088
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 1, 2026
29e2ad6
test(platform): re-pin the latest-version fees and roots after the me…
QuantumExplorer Nov 1, 2026
0252872
docs(platform): say the DashPay v3 ciphertext shape check is new, and…
QuantumExplorer Nov 1, 2026
7abd036
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 1, 2026
3f03611
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 1, 2026
4a18fe5
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 3, 2026
b34cadd
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 3, 2026
b9c02b9
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 3, 2026
19daebd
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 3, 2026
8565a56
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 4, 2026
30a1839
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 4, 2026
2dd2027
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 4, 2026
9c44544
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 4, 2026
e8f90ec
test(drive-abci): check DashPay v3's declarations on the upgraded con…
QuantumExplorer Nov 4, 2026
2ad7f26
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 5, 2026
dd279c5
Merge v5.1-dev into the DashPay v3 branch
QuantumExplorer Nov 6, 2026
f974f3f
test(drive-abci): accept a DashPay profile without payment addresses
QuantumExplorer Nov 6, 2026
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: 17 additions & 0 deletions book/src/evo-sdk/dashpay-contact-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ identity, account, and address index. The sender then encrypts that xpub with an
ECDH shared secret from the sender's identity encryption private key and the
recipient's identity decryption public key.

## What consensus checks

From protocol version 15 the DashPay contract (v3) states these checks in its
schema, and a contact request that fails one is refused (and the fee charged):

| Rule | Declared as | Error |
| --- | --- | --- |
| `toUserId` is not the sender | `distinctFrom: "$ownerId"` on `toUserId` | `DocumentPropertyNotDistinctError` (10419) |
| The recipient identity exists | `refersTo` of type `identity` on `toUserId` | `ReferencedEntityNotFoundError` (40120) |
| `encryptedPublicKey` and `encryptedAccountLabel` are a 16-byte IV plus whole 16-byte blocks | `encryptedFor` with scheme `ecdh-secp256k1-aes256-cbc` | `InvalidEncryptedPropertyShapeError` (10420) |

Up to protocol version 14 a data trigger made only the first two checks and
reported both as a `DataTriggerConditionError` (40500). Neither version checks
that the two key indexes name keys the identities hold, the purpose of those
keys, or that the bytes decrypt. Wallets and SDKs can read the encryption
recipe from the contract (`encrypted_properties()` in Rust).

## Derive the contact payment xpub

```typescript
Expand Down
301 changes: 301 additions & 0 deletions packages/dashpay-contract/schema/v3/dashpay.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
{
"profile": {
"type": "object",
"indices": [
{
"name": "ownerId",
"properties": [
{
"$ownerId": "asc"
}
],
"unique": true
},
{
"name": "ownerIdAndUpdatedAt",
"properties": [
{
"$ownerId": "asc"
},
{
"$updatedAt": "asc"
}
]
}
],
"properties": {
"avatarUrl": {
"type": "string",
"format": "uri",
"minLength": 1,
"maxLength": 2048,
"position": 0
},
"avatarHash": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"description": "SHA256 hash of the bytes of the image specified by avatarUrl",
"position": 1
},
"avatarFingerprint": {
"type": "array",
"byteArray": true,
"minItems": 8,
"maxItems": 8,
"description": "dHash the image specified by avatarUrl",
"position": 2
},
"publicMessage": {
"type": "string",
"minLength": 1,
"maxLength": 140,
"position": 3
},
"displayName": {
"type": "string",
"minLength": 1,
"maxLength": 25,
"position": 4
},
"corePaymentAddress": {
"type": "array",
"byteArray": true,
"minItems": 21,
"maxItems": 21,
"description": "Core chain address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The corePaymentAddressType rule holds the type byte; clients render the address as Base58Check for the network they are on. Payments to it are publicly linkable to this profile.",
"position": 5
},
"platformPaymentAddress": {
"type": "array",
"byteArray": true,
"minItems": 21,
"maxItems": 21,
"description": "Platform address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The platformPaymentAddressType rule holds the type byte.",
"position": 6
},
"shieldedAddress": {
"type": "array",
"byteArray": true,
"minItems": 43,
"maxItems": 43,
"description": "Raw Orchard receiving address: 11-byte diversifier followed by 32-byte diversified transmission key. Clients validate before payment; wallets should use a dedicated tip account.",
"position": 7
Comment thread
QuantumExplorer marked this conversation as resolved.
Resolved
}
},
"minProperties": 1,
"dependentRequired": {
"avatarUrl": ["avatarHash", "avatarFingerprint"],
"avatarHash": ["avatarUrl", "avatarFingerprint"],
"avatarFingerprint": ["avatarUrl", "avatarHash"]
},
"propertyConstraints": {
"corePaymentAddressType": { "in": [{ "byteAt": ["corePaymentAddress", 0] }, [0, 1]] },
"platformPaymentAddressType": { "in": [{ "byteAt": ["platformPaymentAddress", 0] }, [0, 1]] }
},
"required": [
"$createdAt",
"$updatedAt"
],
"additionalProperties": false
},
"contactInfo": {
"type": "object",
"indices": [
{
"name": "ownerIdAndKeys",
"properties": [
{
"$ownerId": "asc"
},
{
"rootEncryptionKeyIndex": "asc"
},
{
"derivationEncryptionKeyIndex": "asc"
}
],
"unique": true
},
{
"name": "ownerIdAndUpdatedAt",
"properties": [
{
"$ownerId": "asc"
},
{
"$updatedAt": "asc"
}
]
}
],
"properties": {
"encToUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"position": 0
},
"rootEncryptionKeyIndex": {
"type": "integer",
"minimum": 0,
"position": 1
},
"derivationEncryptionKeyIndex": {
"type": "integer",
"minimum": 0,
"position": 2
},
"privateData": {
"type": "array",
"byteArray": true,
"minItems": 48,
"maxItems": 2048,
"position": 3,
"description": "This is the encrypted values of aliasName + note + displayHidden encoded as an array in cbor"
}
},
"required": [
"$createdAt",
"$updatedAt",
"encToUserId",
"privateData",
"rootEncryptionKeyIndex",
"derivationEncryptionKeyIndex"
],
"additionalProperties": false
},
"contactRequest": {
"documentsMutable": false,
"canBeDeleted": false,
"requiresIdentityEncryptionBoundedKey": 2,
"requiresIdentityDecryptionBoundedKey": 2,
"type": "object",
"indices": [
{
"name": "ownerIdUserIdAndAccountRef",
"properties": [
{
"$ownerId": "asc"
},
{
"toUserId": "asc"
},
{
"accountReference": "asc"
}
],
"unique": true
},
{
"name": "ownerIdUserId",
"properties": [
{
"$ownerId": "asc"
},
{
"toUserId": "asc"
}
]
},
{
"name": "userIdCreatedAt",
"properties": [
{
"toUserId": "asc"
},
{
"$createdAt": "asc"
}
]
},
{
"name": "ownerIdCreatedAt",
"properties": [
{
"$ownerId": "asc"
},
{
"$createdAt": "asc"
}
]
}
],
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"position": 0,
"contentMediaType": "application/x.dash.dpp.identifier",
"refersTo": {
"type": "identity"
},
"distinctFrom": "$ownerId"
},
"encryptedPublicKey": {
"type": "array",
"byteArray": true,
"minItems": 96,
"maxItems": 96,
"encryptedFor": {
"recipient": "toUserId",
"recipientKey": "recipientKeyIndex",
"senderKey": "senderKeyIndex",
"scheme": "ecdh-secp256k1-aes256-cbc"
},
"position": 1
},
"senderKeyIndex": {
"type": "integer",
"minimum": 0,
"maximum": 4294967295,
"position": 2
},
"recipientKeyIndex": {
"type": "integer",
"minimum": 0,
"maximum": 4294967295,
"position": 3
},
"accountReference": {
"type": "integer",
"minimum": 0,
"position": 4
},
"encryptedAccountLabel": {
"type": "array",
"byteArray": true,
"minItems": 48,
"maxItems": 80,
"encryptedFor": {
"recipient": "toUserId",
"recipientKey": "recipientKeyIndex",
"senderKey": "senderKeyIndex",
"scheme": "ecdh-secp256k1-aes256-cbc"
},
"position": 5
},
"autoAcceptProof": {
"type": "array",
"byteArray": true,
"minItems": 38,
"maxItems": 102,
"position": 6
}
},
"required": [
"$createdAt",
"$createdAtCoreBlockHeight",
"toUserId",
"encryptedPublicKey",
"senderKeyIndex",
"recipientKeyIndex",
"accountReference"
],
"additionalProperties": false
}
}
8 changes: 5 additions & 3 deletions packages/dashpay-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod error;
pub mod v1;
pub mod v2;
pub mod v3;

pub use crate::error::Error;
use platform_value::{Identifier, IdentifierBytes32};
Expand All @@ -19,10 +20,10 @@ pub const OWNER_ID: Identifier = Identifier(IdentifierBytes32(OWNER_ID_BYTES));

pub fn load_definitions(platform_version: &PlatformVersion) -> Result<Option<Value>, Error> {
match platform_version.system_data_contracts.dashpay {
1 | 2 => Ok(None),
1..=3 => Ok(None),
version => Err(Error::UnknownVersionMismatch {
method: "dashpay_contract::load_definitions".to_string(),
known_versions: vec![1, 2],
known_versions: vec![1, 2, 3],
received: version,
}),
}
Expand All @@ -31,9 +32,10 @@ pub fn load_documents_schemas(platform_version: &PlatformVersion) -> Result<Valu
match platform_version.system_data_contracts.dashpay {
1 => v1::load_documents_schemas(),
2 => v2::load_documents_schemas(),
3 => v3::load_documents_schemas(),
version => Err(Error::UnknownVersionMismatch {
method: "dashpay_contract::load_documents_schemas".to_string(),
known_versions: vec![1, 2],
known_versions: vec![1, 2, 3],
received: version,
}),
}
Expand Down
25 changes: 25 additions & 0 deletions packages/dashpay-contract/src/v3/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::error::Error;
use serde_json::Value;

// Document-type name and property constants live in `crate::v1::document_types`;
// v3 keeps every name and property v2 declares, stored as v2 stores it, and adds
// declarations only:
//
// - `contactRequest.toUserId` refers to an identity and must differ from the
// writer (`refersTo` identity, `distinctFrom` `$ownerId`), the two checks the
// contact request data trigger made up to protocol version 14.
// - `contactRequest.encryptedPublicKey` and `encryptedAccountLabel` name their
// encryption (`encryptedFor`, ECDH between the sender's `senderKeyIndex` key and
// the recipient's `recipientKeyIndex` key, then AES-256-CBC with the IV first),
// so each must be an IV plus whole AES blocks. Both key indexes gain the u32
// `maximum` that `encryptedFor` requires of a key id; DashPay keeps sized integer
// types off, so they are still stored as i64.
// - `profile`'s `corePaymentAddressType` and `platformPaymentAddressType` rules
// hold the first byte of each DIP-33 payment address to the address type, the
// check the profile data trigger made up to protocol version 14. Their descriptions say
// so in place of v2's mention of the data trigger.

pub fn load_documents_schemas() -> Result<Value, Error> {
serde_json::from_str(include_str!("../../schema/v3/dashpay.schema.json"))
.map_err(Error::InvalidSchemaJson)
}
Loading
Loading