15 - DashPay#
DIP: 0015 Title: DashPay Author(s): Samuel Westrich, Eric Britten Special-Thanks: Alex Werner, Andrei Baranouski, Andrew Podkovyrin, Andy Freer, Balazs Kiraly, Brian Foster, Dashameter, Evan Duffield, Ivan Shumkov, Pasta, Samuel Barbosa, Thephez, Tomasz Ludek Comments-Summary: No comments yet. Status: Proposed Type: Standard Created: 2020-12-02 License: MIT License
Table of Contents#
Abstract#
DashPay is an application built on Dash Platform that creates bidirectional direct settlement payment channels between Dash Identities. This document details the process of implementing DashPay inside a wallet.
Motivation#
At the time of writing there exists a large divide between what is expected from a modern user experience when making a payment and the current state of decentralized cryptocurrencies.
A spender generally wants three things:
Payments are easy to perform.
A history of payments is readily available.
Third parties aren’t knowledgeable about the details of the payment.
A merchant generally also wants three things:
Settlement is instantaneous.
Settlement is guaranteed.
Payments are easy to tally up for later accounting purposes.
While several of these have already been or are being solved in Dash, DashPay aims to tackle the ease of use aspect of payments as well as improving transaction history. For payments to be considered easy, users should not have to rely on side channels to exchange payment information (e.g. crypto addresses, payment links, SEPA). In order to achieve this, DashPay moves Contacts front and center. All users have a contact list and can easily pay their friends. From a user perspective, making a payment to a contact is much easier than asking them to provide an address and without verifying the address is exactly right. An added benefit is that the recipient knows and will always know the sender, hence contact history will always show who made a payment to the user, or who the user made a payment to. This better aligns with modern payment experiences.
Prior work#
We will refer to prior work whereby of particular note are:
Terminology#
In the rest of the document we will refer to multiple concepts that we will hereby explain:
Derivation Path: A derivation path is a path of successionally derived keys as defined in BIP32. For the purpose of this document, when referring to a derivation path we will generally mean the derivation path before the key space used for addresses even though the keys used for addresses each have their own derivation path.
Address space: The address space is the universe of addresses defined by the derivation path derived to termination paths, such as internal/external in classical derivation paths and indexes.
Extended Public Key: The extended public key is the public key of a derivation path along with some extra information defined in BIP32 needed to construct the address space. These addresses are not spendable with just the extended public key.
Extended Private Key: The extended private key is the private key of a derivation path. With it you can always figure out the extended public key.
Extended Key Pair: This is a generalization of the extended private key, since it always can generate the extended public key.
Identity (or Dash identity): In DashPay each user is identified by their Dash identity. An identity has a 256-bit unique identifier that should be displayed to users in Base58. DashPay uses this unique identifier as a foreign key in contact requests, profiles and contact info.
Username: This is the name defined in the Dash Platform Name Service (DPNS) domain document’s
label
property under the .dash
parent level domain. It is used in a client’s user interface to
uniquely identify a user (identity) in a human readable way.
Sender (Source): The identity that makes a contact request to another identity. It is also referred to as the source of the contact request.
Recipient (Destination): The identity that receives a contact request from another identity. It is also referred to as the destination of the contact request.
Contact Request: A platform document that defines a one way relationship between a sender and a recipient. It includes an encrypted extended public key which will allow the sender to pay the recipient using addresses that other users have no knowledge of. The sender creates and publishes this document. When two users have both sent contact requests to each other, then each is considered a fully established contact with the other.
Friendship: A two way relationship established by two users who have sent contact requests to each other. This is a technical term and should not be shown to end users.
Contact: For a user, a contact is the other side of an established friendship.
Direct Settlement Payment Channel (DSPC): Established contacts have address spaces to send and receive from each other. When these are present either in one way or bi-directional we will call this a direct settlement payment channel.
Profile: A document containing a set of public information for an identity that includes a display name, a public message (bio/status) and an avatar URL. The display name and avatar help complement the identity’s username from DPNS to better visually identify an identity in a user interface. An identity can only have a single DashPay profile.
Contact info: A document containing an identity’s set of private information related to other identities that are contacts.
Peer: A node that transmits and receives information to and from the client through any combination of Dash Core peer to peer or DAPI.
DashPay Features#
User Centric#
Traditional crypto wallets have long been designed with no user interactions. This was mostly due to the technological environment in the cryptocurrency space. Non-crypto wallets that have gained market share around the world all have a social aspect to them. DashPay is designed to bridge this gap and bring users front and center in a cryptocurrency wallet. Instead of sending to an address, a user sends directly to another user. Users will have a username, a display name, an avatar and a quick bio/information message. A user can also nickname other users to remember them better.
Making Payments Easy#
Once two users have exchanged contact requests, each can make payments to the other without manually sharing addresses via emails, texts or BIP21 QR codes. This is because every contact request contains the information required to send payments to the originator of the request. More precisely, an encrypted extended public key is sent in the contact request. When decrypted, this extended public key can be used by the recipient of the contact request to generate payment addresses for the originator of the contact request. The recipient must watch for transactions to those addresses (i.e. SPV clients need to add these addresses to the bloom filters).
Providing a Payment History#
When a contact is established, a user can easily track the payments they have sent to another user and the payments that they have received from that other user. A user will have an extended private key to track payments that are received from the other user and an extended public key to track payments that are sent to that other user.
Protecting Payment Participants#
Although contact requests are public in Dash Platform, the extended public keys are encrypted in such a way that only the two users involved in a contact’s two way relationship can decrypt those keys. This ensures that when any two users make payments in DashPay, only they know the sender and receiver while 3rd parties do not. This means that outside observers cannot link the identities involved in the transaction. Addresses in DashPay are intended to be single use and internal to the system only, as users will never see them. They should never be reused. PrivateSend funds can also be used in DashPay, though a PrivateSend mixing feature is not a requirement of a DashPay enabled wallet.
DashPay: Establishing Relationships between Dash Identities#
Dash based identities are defined in DIP11. In DashPay it is essential to understand that all relationships are between these identities. A Dash identity is referenced by its unique identifier. A friendship in DashPay is nothing more than two contact requests between Dash identities; one in each direction. DashPay clients should not use the term “friend” and instead use the term “contact”. While this document uses the term friendship for a completed relationship between two contacts, this will remain a technical term. It should not be presented to end users and should only be used in technical documents for clarity.
The DashPay Contract#
DashPay is one of the first applications of Dash Platform’s Data Contracts. Data Contracts are a set of structured document types. The DashPay contract has the following requirements:
A Hierarchical Deterministic capable wallet
Control over a registered Dash identity
What is in the DashPay Contract?#
The contract defines three document types: contactRequest
, profile
and contactInfo
.
ContactRequest documents are used to establish relationships and payment channels between Dash
identities. Profile documents are used to store public facing information about Dash identities.
Lastly contactInfo documents are used to store private information about other Dash identities.
A JSON Schema representation of the DashPay contract is associated with this document and can be found here. The three document types are described in further detail in the following sections.
The Contact Request#
A contactRequest
document defines a one-way relationship between the sender identity and the
recipient identity. The document type’s properties are as follows:
$ownerId (byte array) - The unique id of the sender.
toUserId (byte array) - The unique id of the recipient.
senderKeyIndex (integer) - The index of the sender’s identity public key. Used to derive the ECDH key.
recipientKeyIndex (integer) - The index of the recipient’s identity public key. Used to derive the ECDH key.
accountReference (integer) - A reference to the account from which the extended public key originated. This is encrypted for the sender. The recipient should disregard this field.
encryptedAccountLabel (byte array, optional) - An encrypted message to the recipient of the contact request that should be the label of the sender’s account.
encryptedPublicKey (byte array) - The sender’s extended public key that is encrypted with an ECDH shared key that will be defined below.
autoAcceptProof (byte array, optional) - An optional field intended to be used to provide proof to recipients to automatically accept the contact request.
$coreHeightCreatedAt (integer) - The last chain locked block height known by Dash Platform at the time of document creation.
$createdAt (integer) - The timestamp in milliseconds when this document was created.
Note: Properties prefixed with a $
sign are special system-wide predefined properties that inherit
from the base protocol definition.
Binary Property Validation Sizes#
The Contact Request has five binary properties. Their sizes are defined as follows:
Name |
Size |
---|---|
$ownerId |
32 bytes |
toUserId |
32 bytes |
encryptedAccountLabel |
48-80 bytes |
encryptedPublicKey |
96 bytes |
autoAcceptProof |
38-102 bytes |
Dash Identities ($ownerId and toUserId)#
The $ownerId
should be set to the identity unique id of the sender that will be making a contact
request. The recipient’s identity unique id is specified in toUserId
. Together $ownerId
,
toUserId
and accountReference
form the unique primary key of the contactRequest
. A client can
query contact requests based off of $ownerId
and $createdAt
to receive outgoing contact requests
and can query contact requests based off of toUserId
and $createdAt
to receive incoming contact
requests. While less common, a device can also query contact requests off of $ownerId
and
toUserId
to see if any contact requests exist.
The account reference (accountReference)#
BIP32 details the idea that a wallet can have multiple accounts. For example, a wallet can have a business account, a personal account, a savings account and many more. Identities are not account based. As described in DIP13 (Identities in Hierarchical Deterministic Wallets), identities represent a persona. DashPay uses contact requests to establish relationships between identities in the context of two accounts. Each contact request is set up to receive payments to an account.
Unlike the extended public key that is intended for the recipient of the request, the account reference is solely an indicator for the sender. A contact request from Bob to Carol assigned to account zero indicates to Bob that this relationship is associated with his wallet account at index zero. As an example, Bob’s account zero could be his personal account and account one his professional account. He could make a contact request to Carol on his personal account zero since Carol is his friend, then make another contact request to his colleague Marc who deals with company reimbursements on account one. When Marc sends him a payment, the funds will be added to his professional account.
The data format of the extended public key is an abbreviated version as described in Encrypted Extended Public Key.
The account reference is constructed the following way by the recipient:
Create the account secret key (ASK):
ASK = HMAC-SHA256(senderSecretKey, extendedPublicKey)
ASK28 = 28 most significant bits of ASK
ShortenedAccountBits = Account & 0x0FFFFFFF
VersionBits = Version << 28
AccountRef = VersionBits | (ASK28 xor ShortenedAccountBits)
While the 28 bits used in the ASK28
are not enough to guarantee uniqueness, uniqueness is not a
requirement of this system. The probability of two contact requests having the same ASK28
is
228. The birthday paradox might lead to some contact requests eventually having the same
ASK28
, however this does not pose an attack. The key can be considered a one time pad as it is
only used to encrypt one message per friendship; if the account would change so would the
extendedPublicKey
. Since the extendedPublicKey
is derived using the account as one source of
entropy, ASK28
can be considered the result of a pseudorandom function derived from the account.
The Version field should generally be set to zero. More advanced clients can optionally offer the
following scheme to their users: if receiving any number other than zero, and while also having a
contact request with the previous version, clients should notify the recipient user of the contact
request that the sender has updated their payment addresses. If accepted by the recipient, they
should update the accepted account to the AccountRef in their contactInfo
document for the sender.
If an identity (Bob) has already received a contact request from another identity (Carol), the client should either disregard all future contact requests from Carol, or preferably ask the user (Bob) to which destination account he wishes to send a payment.
The encrypted account label (encryptedAccountLabel)#
If desired, a sender can assign an encrypted account name in the contact request that can be decrypted by the recipient. This is useful when a user has multiple accounts on their device. We can imagine a scenario where a user would only wish to receive business-related reimbursements to one account, while using another account for personal expenses. When sending the second contact request, the user should explain its purpose so that the destination user will have readable options when sending a payment.
The encryptedAccountLabel property is a binary field that has the following format once it is deserialized to bytes:
Initialization Vector (16 bytes)
The account label with padding (32-64 bytes) that is encrypted by CBC-AES-256 using the shared ECDH key
Encrypted Extended Public Key (encryptedPublicKey)#
A contact request from Bob to Carol should contain an encrypted extended public key that Bob makes for Carol. Once Carol decrypts this extended public key, she can use it to derive keys representing the unique addresses where her payments to Bob should be sent.
The encryptedPublicKey
property is a binary field that has the following format once it is
deserialized to bytes:
Initialization Vector (16 bytes)
Encrypted extended public key with padding (80 bytes) that is encrypted by CBC-AES-256 using a shared ECDH key
The initialization vector used to encrypt the extended public key and the initialization vector used to encrypt the account label must be both different from each other and random.
The data format of the extended public key differs from what is defined in the Serialization Format of BIP32. This is because only the following fields are necessary when constructing derivations. The binary format used is as follows:
Parent fingerprint (4 bytes)
Chain code (32 bytes)
Public Key (33 bytes)
Deriving the extended public key in the wallet is described in its own section later in this document.
Core Height ($coreHeightCreatedAt)#
This field is meant to represent the height of the most recent ChainLocked block on the core Dash chain. Dash Platform includes this height with a slight delay as part of its state and updates it through platform chain block headers. This field is essential for receiving all payments between contacts.
When submitting a contact request, clients should first query this value from Dash Platform to be able to include it in the contact request. During validation, Platform will accept values that correspond to the last 5 ChainLocks known by the platform state.
Let us demonstrate the problem and why this field is important with the following example. User Bob is using DashPay on multiple devices: device A and device B. Both devices are synced to height n. Using Device A, Bob sends a contact request to Carol who immediately sends him a payment that is mined in a block on height n+1. Device B in the meantime has synced to height n+1 already and has not received the payment because it was not yet aware of the outgoing contact request. When it receives the outgoing contact request at height n+1, it updates its filter but will never receive the payment transaction as it is not in mempool and was already mined in the previously received block n+1.
Now let us demonstrate how this is fixed by including a core height in the request. Both devices are synced to height n. Using Device A, Bob sends a contact request with core height n to Carol. Carol once again immediately sends him a payment request that is mined on height n+1. As before, Device B has synced to height n+1 already and has not received the payment because it was not aware of the outgoing contact request. Still at height n+1, when it receives the outgoing contact, it sees that the contact request could have received payments in block n+1. Since n+1 has already been synced, it needs to resync blocks from height n+1 after updating its bloom filter. It will then receive the transaction that was mined at block n+1.
Created At Timestamp ($createdAt)#
The timestamp in milliseconds since the unix epoch time when the contact request was created. This
field is useful when fetching new contact requests. When a DashPay client starts up, it should query
contact requests, both incoming and outgoing, using this property. It should query contact requests
that were created at most 10 minutes before the most recent contact request that the wallet knows
about. Dash Platform allows documents with a $createdAt
property to be created within a 5 minute
window of the Platform state timestamp. We use 10 minutes because the window extends 5 minutes in
each direction.
DashPay Incoming Funds Derivation Path#
BIP32 introduced derivation paths for keys. BIP43 introduced a purpose field and DIP9 introduced a feature field that was coin specific.
We can therefore define the following root levels for identity features.
m / purpose' / coin_type' / feature' / account' /
The apostrophe signifies hardened paths as defined in BIP32.
Feature will be set to 15'
for all DashPay Incoming Funds derivation paths.
BIP32 and then BIP44 addresses are defined by either being internal (change) or external (receive) addresses. In DashPay there is no requirement for a change address on a relationship, as local BIP44 change addresses can and should be used instead. Therefore, the incoming fund derivation paths will only have one extra derivation on a friendship derivation path to produce addresses.
In order to create provably unique derivation paths per relationship, we must use 256-bit derivation paths, this is described in DIP14 (Extended Key Derivation using 256-Bit Unsigned Integers).
The derivation path therefore has the following paths:
m(userA)/9'/5'/15'/0'/(userA's unique id)/(userB's unique id)/index
Deconstructed that is:
9 hardened / See DIP9
5 hardened / Coin type
15 hardened / Feature
0 hardened / Account
userA’s unique id (not hardened)
userB’s unique id (not hardened)
index of payment address (not hardened)
Making the last three fields non-hardened allows for an extended public key that covers all address spaces of all our contacts for all our identities. This can be used for accounting and also for creating contact requests without having to use a private key.
Immutability of the Contact Request#
One essential aspect of Contact Requests is the fact that they are immutable and can never be deleted. This means they can never be updated or removed from the platform tree once accepted into the state. This is by design. If they were allowed to be mutable, users could change their extended public keys. This would overwrite previous extended public keys.
For recipients this would make the previous extended public keys’ associated derivation paths unretrievable. There would then be no way to know the addresses to add to the recipients bloom filter, causing them to fail to know to whom payments were sent. For senders it would not be obvious that there ever was a different previous extended public key. When resyncing they would no longer look for payments to addresses they previously might have received payments on. For end users this would appear to look like transactions were lost.
One drawback of this immutability is the fact that clients can not alter incorrect extended public keys. If a client has sent incorrect extended public keys, that client should send another contact request and update the version number sent to clients.
Creating a Contact Request#
When sending a contact request, a client should create a signed Documents Batch Transition containing a create document instruction and then broadcast (publish) it to Dash Platform. This is done in the following steps:
$ownerId
should be set to the sender’s Dash identity unique id which is the same Dash identity that will sign the transition.toUserId
should be set to be the recipient’s Dash identity unique id.senderKeyIndex
should be set to a valid public key index from the sender’s identity. This key must allow for a Diffie-Hellman key agreement protocol - currently only secp256k1 Elliptic Curve keys are supported.recipientKeyIndex
should be set to a valid public key index from the recipient’s identity. This key must be of the same type as the sender key and must allow for a Diffie-Hellman key agreement protocol.A shared key should be created using the private key associated with the
senderKeyIndex
and the public key associated withrecipientKeyIndex
.An extended public key should be derived from the wallet master seed which will be for the address space to which the sender will receive payments from the recipient.
The extended public key from step 6 should be encrypted to form the
encryptedPublicKey
.$coreHeightCreatedAt
should be set to the height of the most recent ChainLocked block known by Dash Platform.$createdAt
should be set to the current system time. Must be within 5 minutes of the most recently mined platform chain block.Once formed, the resulting transition must be signed using a key from the sender’s identity.
The transition should then be broadcast to the network. This is referred to as Broadcasting the transition.
If a Documents Batch Transition containing a contact request is signed, but not published to Dash Platform within 5 minutes for connectivity or other reasons, then the transition must be recreated and signed again before submission.
Fetching Contact Requests#
When fetching contact requests related to a particular Dash identity’s unique id, two queries can be made:
Query all sent contact requests where the
$ownerId
matches the unique id in questionQuery all received contact requests where the
toUserId
property matches the unique id in question
The first time these queries are made, all matching requests will be retrieved regardless of
creation date ($createdAt
). Subsequent queries should be made with $createdAt
set to 10 minutes
before the most recent date of the results from previous queries. This method saves bandwidth by
only retrieving the most recent contact requests. This assumes that the client is maintaining the
list of contacts in local storage.
Auto Accept Proof (autoAcceptProof)#
Certain situations merit that an identity would like to accept contact requests (i.e. responding with a contact request in the opposite direction) automatically. This could be the case when providing a QR code to scan for someone in close proximity or for a merchant that provides a QR code for their customers to send them payments.
This works by providing a key in the QR code that will later be used to sign the $ownerId
+
toUserId
+ accountReference
of the contact request and hence prove that the original owner gave
this key.
Keys should be provided as data in the following way:
Name |
Size |
Example (Using Base58 for 32 byte key) |
---|---|---|
key type |
1 byte |
0 |
timestamp (key index) |
4 bytes |
1605927033 |
key size |
1 byte |
32 |
key |
32-64 bytes |
5tG9PYhvPYzxyDb6yA1yQ4rTMUpLDgMF9q4z41pbKWES |
The derivation path for these keys based on DIP9 is:
m / purpose' / coin_type' / feature' / timestamp'
The apostrophe signifies hardened paths as defined in BIP 32. Feature will be set to 16'
for all
Auto Accept Proofs.
The derivation path therefore has the following paths:
m(userA)/9'/5'/16'/timestamp'
The timestamp used should not be the current timestamp but instead the timestamp at which the auto accept proof should expire. The auto accept proof will contain this timestamp as it is essential for the recipient of a contact request containing an auto-accept proof to later verify the proof.
URIs should encode the above information as referenced in BIP21 and BIP72 with the following modifications:
The Dash username should be referenced as du
The Auto accept proof key should be referenced as dapk
An example URI for a merchant (BobsPizza) using BIP70-72 could be the following:
dash:Xcu5iYBH3szP744sQ1RUp3JHTVFHrFVdYu?amount=0.11&du=bobspizza&dapk=13SuoA8Z5tG9PYhvPYzxyDb6yA1yQ4rTMUpLDgMF9q4z41pbKWES&r=https://merchant.com/pay.php?h%3D2a8628fc2fbe
An example for a URI for a contact request would be:
dash:?du=bobspizza&dapk=13SuoA8Z5tG9PYhvPYzxyDb6yA1yQ4rTMUpLDgMF9q4z41pbKWES
The auto accept proof should be formatted the following way:
Name |
Size |
---|---|
key type |
1 byte |
key index |
4 bytes |
signature size |
1 byte |
signature |
32-96 bytes |
The Profile#
The profile
document is an important notion for clients in DashPay. It holds all of the public
facing information about a DashPay user. In the system, a profile should have the following
attributes:
$ownerId (byte array) - The profile owner identity’s unique identifier.
avatarUrl (string, optional) - A URL to an image that will be the user’s avatar or photo.
avatarHash (byte array, optional) - The SHA-256 hash of the avatar image that is uploaded
avatarFingerprint (byte array, optional) - The perceptual hash of the avatar image using the difference hash DHash algorithm.
publicMessage (string, optional) - A public message or a bio that the user can provide to better identify themselves.
displayName (string, optional) - This display name can include any UTF-8 allowed characters. It is not unique and can be shared by many users. Apps should use this in addition to the username to identify users. Clients should always include the DPNS username that is being paid to in UI confirmation dialogs as it is unique in the system. This DPNS username is recommended to have a high prominence. Clients can also show the display name but it should be less prominent.
$createdAt (integer) - The timestamp in milliseconds when this document was created.
$updatedAt (integer) - The timestamp in milliseconds when this document was updated.
Note: Fields prefixed with a $
sign are special system-wide predefined properties that inherit
from the base protocol definition.
Field Validation Sizes#
The Profile Document, like all standard documents, has the byte array $ownerId
field. The Profile
Document also has three string fields along with optional avatarHash
and avatarFingerprint
byte
array fields.
Name |
Size |
---|---|
$ownerId |
32 bytes |
avatarUrl |
0-2048 characters |
publicMessage |
0-250 characters |
displayName |
0-25 characters |
avatarHash |
32 bytes |
avatarFingerprint |
8 bytes |
Avatar URL (avatarUrl)#
This URL points to an image stored at a publicly accessible location (e.g., personal server, image hosting site). Clients are advised to not retrieve this image directly but instead use a thumbnail server to display the image in the appropriate size and resolution for the device and screen. Clients are advised to cache these avatars. If the image is unavailable on the thumbnail server, it is recommended that the image should only be retrieved if under a reasonable size limit and if the user has expanded the profile section. The image should not be retrieved in this way when searching for users. Clients are advised to perform local URL sanitization and similar techniques to improve the security of the loaded content. Clients are also advised to have a backup system for determining what to display if no avatar can be retrieved.
Avatar Hash (avatarHash)#
This is the single SHA256 hash of the bytes of the original image referenced by avatar URL. It is an optional field. Clients are advised to use this field when uploading an image.
Avatar Perceptual Fingerprint (avatarFingerprint)#
This is the perceptual hash of the original image using the difference hash DHash algorithm. Various implementations of this hashing algorithm exist in most common languages. The difference hash uses variations in gradients to produce a fingerprint for an image. Clients should compare the Hamming distance between the document and the downloaded thumbnail in order to verify that the thumbnail accurately represents the original image.
Bio (publicMessage)#
This message is any text that the user wants to be publicly visible. It can contain UTF-8 encodable characters from any language as well as spaces, punctuation and symbols.
Display Name (displayName)#
The display name allows a user to specify a more friendly or personal name than the username. The username (defined in the DPNS domain document) must follow certain rules and is limited to Latin characters. However, displayName can include characters from any language as well as spaces, punctuation and symbols.
Timestamps ($createdAt, $updatedAt)#
The timestamp in milliseconds (unix epoch time) when the contact request was created or updated. The
protocol defined property, $updatedAt
, is useful when fetching updated profiles. When querying
DAPI in order to find updates to a profile, the query should use an $updatedAt
timestamp that is
greater than the profile’s currently known $updatedAt
timestamp.
Creating a Profile#
The normal procedure of creating and publishing a document create transition should be followed. At
least one field between the avatarUrl
, publicMessage
and displayName
must be set. $createdAt
should be set to the current system time which must be within 5 minutes of the most recently mined
platform block. $updatedAt
must be equal to $createdAt
.
Updating a Profile#
The normal procedure of creating and publishing a document replace transition should be followed.
All fields must be submitted when updating a profile. Fields that are not being updated should be
set to their current value. Additionally, one or more of the following fields must be updated:
avatarUrl
, publishMessage
and/or displayName
. $updatedAt
should be set to the current system
time which must be within 5 minutes of the most recently mined platform chain block.
Fetching a Profile#
Profiles should be fetched in three scenarios.
The first scenario is when a user is searching for a username during an Add Contact operation. In
such cases, the query should use the where clause to retrieve profiles for all identities returned
by the DPNS name query. This query is solely based on $ownerId(s) and should resemble $ownerId in ([OwnerIdArray])
.
The second scenario is when receiving new contact requests. Profiles should be fetched when new
contact requests are fetched if the profile for the identity is not yet known. (see Fetching
Contact Requests). This query is also solely based on $ownerId
and
can be batched as in the first scenario if receiving multiple contact requests simultaneously.
The third and final scenario is when checking for an updated profile. Clients are advised to only
submit this query when a user has entered the profile page of a contact. Clients can also submit
these queries routinely but should not do so more than once a day per contact. They should not be
batched. This query is based off of $ownerId
and $updatedAt
. The query should be made by
querying $updatedAt
superior to the last $updatedAt
known for the profile.
Contact Info#
These are the attributes of the contactInfo
document that allow a user to store private
information about a contact. A client should not transmit a contact info document for a user to the
network until that user has at least two established contacts. This is to prevent a trivial linking
to a corresponding contact request. The fields are as follows:
$ownerId (byte array) - The unique id of the user.
rootEncryptionKeyIndex (integer) - The index of the user’s key that is used to derive keys that will be used to encrypt the contact’s user id in encToUserId and the private data.
derivationEncryptionKeyIndex (integer) - The index at which to derive the root encryption key.
encToUserId (byte array) - The encrypted unique id bytes (32) of the contact. AES-256-ECB should be used.
privateData (byte array) - This is encrypted using AES-256-CBC. It has the following fields:
version (uInt32) - The version of this private data.
aliasName (String) - The nickname that this user has chosen for this contact.
note (String) - A note that this user has written about this contact.
displayHidden (uInt8) - Whether or not the user has chosen to hide or ignore this contact.
acceptedAccounts (array of uInt32) - This should be an array of all accepted accounts that are not on version 0.
$updatedAt (integer) - The timestamp in milliseconds when this document was updated.
$createdAt (integer) - The timestamp in milliseconds when this document was created.
Note: Fields prefixed with a $
sign are special system-wide predefined properties that inherit
from the base protocol definition.
Binary Field Validation Sizes#
The Contact Info has three binary fields using byte arrays. Validation sizes are as follows:
Name |
Size |
---|---|
$ownerId |
32 bytes |
privateData |
48-2048 bytes |
encToUserId |
32 bytes |
Deriving the Encryption Keys#
Two fields in our document need to be encrypted on creation: encToUserId
and privateData
.
Because the destination contact (toUserId
) could potentially be guessed based on user or client
actions and also because the pool of users in DashPay might start out small, it is imperative that
the encryption key used be hardened and never reused. Hence the keys encrypting encToUserId
and
privateData
must be different and unique.
In order to create these keys, a client must start with a key registered in Dash Platform. The index
of this key is set in the encryptionKeyIndex
. Two private keys should then be derived from this
root key using the CKDpriv function described in BIP32. Hardened derivation should be used in both
cases. The index used in the derivation should be used sequentially and should be unique to the
$ownerId
.
The path used for the key encrypting encToUserId should be:
rootEncryptionKey/(2^16)'/index'
The path used for the key encrypting privateData should be:
rootEncryptionKey/(2^16 + 1)'/index'
216 is used as an offset to discount other potential derivations of this key in other applications.
Encrypting the contact unique identifier (encToUserId)#
AES-256-ECB encryption should be used here. Electronic Code Book (ECB) mode is generally not secure if keys are reused or if encrypted data is not random. In our case however the contact unique identifier we are encrypting is made with the SHA256 hash function. It can therefore be assumed that the contact unique identifier appears random in nature. Since the key will also not be reused for other purposes, ECB mode can be used securely here.
The advantage of using ECB mode is that there is no need to supply an initialization vector.
Versioning of Private Data#
The fields are defined in this document for version zero. Future updates to this proposal or new improvement proposals might introduce additional versions. Version should be set to zero if no other new proposals have been made.
This version field should be decomposed into two subfields, major and minor with version = major << 16 | minor
. Major version changes are incompatible and if a client does not understand them the
whole contact info should be discarded. Minor version changes that a client does not understand most
likely reflect additional fields. If a client receives a contact info document and has not been
updated to support the minor version number, it should still be able to parse the first fields of
the private Data. It should ignore data past the final field known in the version.
Alias Name#
The Alias Name is a nickname that a user can give one of their contacts. This can be essential to recognize contacts that have both their DPNS usernames and their display names set to strings that don’t clearly identify them.
Note#
The note is a field to add specific notes to a contact. For example a note could be where the two users met, or any information that the user wants to remember about the contact.
Accepted Accounts#
The DashPay system allows for multiple contact requests between two identities. An obvious attack is to send a very high number of contact requests to an identity that had previously sent a contact request in the opposite direction. Without mitigation, these contact requests would pollute and expand the client’s bloom filter to its max capacity. In order to fix this, a client should only add to its bloom filter the first contact request from a contact. If additional contact requests have been made, they must be accepted by the user. If accepted by the user, the account reference of the newly accepted contact request should be added to the array of accepted accounts.
This array should be serialized by prepending the varInt length of the array, followed by all uInt32s in the array.
Encrypting Private Data#
AES-256-CBC encryption using the derived encrypted key should be used to encrypt the private data. In order to do so, the private data should be serialized in the same way as done for Dash message data (example for strings).
Creating Contact Info#
The normal procedure of creating and publishing a document create transition should be followed.
$createdAt
should be set to the current system time which must be within 5 minutes of the most
recently mined platform block. $updatedAt
must be equal to $createdAt
.
Updating Contact Info#
The normal procedure of creating and publishing a document update transition should be followed.
$updatedAt
should be set to the current system time which must be within 5 minutes of the most
recently mined platform block. $updatedAt
must be superior to the previous $updatedAt
.
Fetching Contact Info#
Contact Info documents should be fetched prior to fetching contact requests and profiles. This is so
Contacts do not pop in and out of the UI based on the Display Hidden property. This query is based
off of $ownerId
and $updatedAt
. The query should be made by querying $updatedAt
superior to 10
minutes before the last known $updatedAt
. $updatedAt
should be set to zero if querying for the
first time.
Example Steps to Establish a Contact#
Bob installs wallet software that supports DashPay.
Bob registers a username through DPNS which first requires the registration of an identity.
Bob finds Carol by her username on the network. Behind the scenes this search returns the unique identifier for Carol’s identity. For more information see the Dash Platform Name Service (DPNS) DIP.
Bob sends a contact request to Carol. This establishes a one way relationship from Bob to Carol.
Carol accepts the request by sending a contact request back to Bob. This establishes a one way relationship from Carol to Bob.
Bob and Carol are now contacts of one another and can make payments to each other. Since both have established one way relationships with each other, they now have a two way relationship. If Bob gets a new phone, he can use his recovery phrase from step one and restore his wallet, contacts (including Carol) and payments to and from his contacts.
Order of Synchronization#
One slightly trickier aspect of DashPay is synchronization that combines both layer 1 and layer 2. Clients should implement DIP16 (Simple Payment Verification Wallet Headers First Synchronization). In doing so, Dash Platform data requests will happen after the deterministic masternode lists and quorums have been loaded. This is done by retrieving Dash identities sequentially from wallet-derived keys as described in DIP13 (Identities in Hierarchical Deterministic Wallets). During the Sync Blocks Retrieval Phase and the Synced Phase, and as transactions and blocks come in on layer 1, clients should look for credit funding transactions that correspond to keys in the wallet used for registration (derivation path explained in DIP13). If one is found either in a block or from the mempool, and the Dash identity unique id corresponding to the credit funding transaction is not yet known by the wallet, the sync on layer 1 should be paused while the client requests information from layer 2.
A client should first retrieve information about the Dash identity. At that point it can get both the profile associated with the Dash identity, and incoming and outgoing contact requests. For each contact request, Dash identity information of the other parties should be requested along with their profiles.
Derivation paths should be constructed for all accepted contact requests. There are two ways for a contact request to be deemed as accepted: Either it is the first incoming contact request from a contact to whom the user had already sent a contact request, or it can be marked as accepted in the contact info document for that contact. All outgoing contact requests are considered accepted by default in the context of the sender.
For outgoing contact requests, derivation paths should be constructed by deriving the master contacts derivation path extended public key as described above. For incoming derivation paths, the derivation path should be constructed without full knowledge of the starting path but with the extended public key retrieved by the contact request after decryption. This extended public key can be derived to give an address space for sending to the contact in question.
Once all the derivation paths are known, address spaces can be constructed. We recommend a gap limit of 10 at this stage, which means to load 10 addresses past the last used address. For initial sync this will be 10 since at this point no addresses will have yet been seen to be used. These addresses should then be inserted into rebuilt peer bloom filters as defined in BIP37. Sync on layer 1 can then be resumed with the complete bloom filters. It is advised to also change peers in this process so nodes cannot easily associate payments to the identities of the sender and recipient.
The wallet should therefore contain the following address spaces that should be added to bloom filters on connection with peers:
BIP44 (external, change)
An address space for each outgoing contact request from each Dash identity. This address space should be derived from the wallet mnemonic. These addresses will receive payments.
An address space for each incoming contact request for each Dash identity. If the sender is not a Dash identity derived from the wallet (a rare case where both the sender and recipient are in the same wallet), then this address space should be derived from the contact request extended public key after decryption.
It is essential to also re-request blocks at the minimum of all $coreHeightCreatedAt
values of any
new incoming or outgoing contact requests. It is advised to re-request a little more than this
minimum block to preserve the probabilistic nature of false positives in the bloom filter. If the
first block of a client always returned a transaction, then nodes would learn that this transaction
is most likely not a false positive. We should do this unless we are within 10 blocks of the chain
tip. In that case, re-requesting more than from the minimum block would serve little purpose as any
incoming transactions close to the chain tip are highly probable to not be false positives. We
recommend requesting from the last millennial block (mod 1000 = 0).
Copyright#
Copyright (c) 2020 Dash Core Group, Inc. Licensed under the MIT License