Delegated Payment Protocol
Introduction
The Delegated Payment Protocol is a protocol that allows an entity and a cryptocurrency wallet to establish a trust (within limits) relationship, allowing the entity to delegate cryptocurrency operations to the wallet. Based on policies set up by the wallet owner (the "user"), the wallet may automatically execute delegated operations (typically payments), or may request user authorization. The entity requesting the operation has no control over whether the operation is handled automatically, or promoted to user authorization. Delegated operations can be simple payment, the funding and signing of more complex transactions, or identity operations. Entities can be anything that can execute a cryptographic signature, or whose identity is validated by other means (such as via https). Common entities are web sites and smart phone apps.
This service can be broadly compared to the automatic fiat payment systems available via ACH (and others) that are used to deduct monthly membership dues, etc. However, this service may be used both for "traditional" (monthly bill) style payments, and for emerging small payment applications that are enabled by Bitcoin Cash's payment efficiency.
And from a philosophical standpoint this service is very different than traditional automatic payment permission. In this protocol, user control over these trust relationships is centered within the wallet, and the wallet is an active agent in the execution of every payment. This means that this trust relationship is easily adjusted by the user. In some traditional systems, control over the trust relationship is delegated to the entity receiving the funds. At best, this creates problems -- a different and separate system must be learned by the user for each relationship, often involving slow and obsolete technology, for example a physical, stamped and mailed letter with numerous required and redundant details. At worst, the entity deliberately obfuscates or complexifies their system to retain payments, and/or charges the user to stop charging the user.
With problems like these, it seems as if many banking systems have essentially been co-opted by bank/business relationships to extract value from the hapless depositor (who must participate because of the significant problems with holding physical bills of currency whose high denominations have been retired and is losing value to inflation). By returning control to the wallet owner, this protocol is aligned with cryptocurrency philosophy that seeks to provide an alternative that protects depositors rather than fleecing them.
Related Protocols
Untrusted delegated payment protocols are available in Bitcoin Cash.
First, the BIP21 URI scheme provides a simple mechanism to communicate a payment to an address.
Second the BIP70 payment protocol and its web-service-optimized equivalent by Bitpay, allow a more complex interaction that ensures that the receiver's invoice is still "live", allows the receiver to verify transaction details before it is signed, and allows a refund address to be supplied.
Protocol Envelopes
This specification will use a URI format to describe the data contents, however, this protocol can and should be encapsulated by the envelope most appropriate for the application. In particular it can be carried via QR code, HTTPS, or an Android Intent.
Intents are a technique that allow an Android application to communicate (and access functionality) with each other. Intents can be mapped to a URI since they contain the same basic elements. In particular URI parameters can be added to an intent via the extended data key value pair dictionary.
Default Replies
TDPP does not use the normal reply that may be part of a protocol for two reasons. First of all, we do not want to be limited by protocol timeouts. Second, the reply may be directed to a different entity than the request originated from. This happens because there are multiple parties involved in TDPP -- your wallet, an application server, and a browser or other application. And finally, certain protocols do not naturally for replies, such as QR codes. Therefore, on a per-protocol basis, it may make sense to define a different reply.
QR and other one-way protocols
If a reply protocol is unspecified, the default is a HTTPS POST to the entity name and path specified in the request. To aid the requester in connecting requests to responses, the cookie field is placed in the URL as a parameter (and may also be in the POST body). For example replying to a tdpp://foo.com/tx?cookie=1 message should POST to https://foo.com/tx?cookie=1.
Android Intents
Unless a "reply" field directs your reply over a different protocol, take the reply as formulated in a POST message and make an Android Intent out of the URI. Next "putExtra" data into the Intent with key="body" and value="[the POST body]". "setResult" with this intent and a result of either Activity.RESULT_OK or Activity.RESULT_CANCELED based on whether the user oked or dismissed the request. Then "finish()" your activity to go back to the calling activity.
HTTP/HTTPS
Reply with a JSON object if the request can be immediately handled, unless a "reply" field directs your reply over a different protocol.
Entity Wallet Trust
This protocol sets up a user-authorized trust relationship between a wallet and an entity. However, the opposite problem also exists. An entity cannot trust that the wallet faithfully executed payments. If such trust existed, wallets would be created that simply respond that payments were made without actually doing so, and therefore get free service.
One solution to this problem is that the entity needs to independently verify the transaction at its desired level of assurance (i.e. unconfirmed, unconfirmed with no known doublespends, confirmed depth N), and that the transaction has the desired outputs. From an engineering perspective this is awkward because it means that the app needs to contain blockchain libraries, and for unconfirmed assurances, access the blockchain network.
SPV proofs can be furnished to prove confirmed transactions, but 10+ minute waits is not acceptable for many anticipated uses of this technology.
Assurances of unconfirmed transactions will always be probabilistic since a miner may be secretly mining a doublespend. However, many businesses find the risk reasonable. Web site entities can deploy and access a trusted full node to gain this information. At a lower level of assurance, apps can use the simpler electrum protocol to at least poll a random or trusted server or two to verify that the transaction has propagated, or propagate the transaction itself. Maintaining a trusted full node for app payment queries increases the maintenance overhead of the app, but it is a possibility. Accessing random servers is vulnerable to eclipse attacks, but mounting such an attack may not be economically viable for small payments.
But the emerging technology "Tailstorm" (Bobtail and Storm combined) should allow partial proof-of-work inclusion proofs. These can be used to provide a probabilistic and economic assurance that the transaction is actually being mined, without requiring that the entity independently communicate to the blockchain or to an external server.
Protocol Messages
Signature of Intents and URLs
All message signatures sign the URI form of the message without the "sig" parameter. Intents do not appear to specify an ordering to the extra data (the parameters, using "putExtra"). To create an unambiguous conversion, we define that the URI form MUST sort these parameters alphanumerically by key and MUST NOT repeat any keys. Additionally ALL parameter values MUST use the standard "url parameter encoding" format even though a lot of software is now tolerant of technically illegal characters in URLs.
This policy MUST be applied to other message formats, to keep the processing uniform across envelopes. This means that the parameters in received URLs may need to be reordered before the signature check, and the "sig" parameter needs to be removed, along with its linking "&". The other side is NOT REQUIRED to properly sort these parameters for you!
The signature is transmitted in the standard bitcoin signature encoding, which is a 64 character format beyond the scope of this document (see EncodeBase64() or use libbitcoincashkotlin.Codec.encode64).
Optional Common Fields
How to Reply
The following fields are available in every request and define how to reply to messages. This reply data is only meaningful if the request protocol does not have an inherent means to reply.
cookie: Data that the requester expects returned in the reply to match requests with replies. rproto: Reply protocol (e.g. https) rpath: Reply path. can contain either a fqdn and path or just a path (e.g. //foo.com/replyspot, or /replyspot)
Information
The following fields are available in every request.
reason: [optional, string] Text describing the purpose of this transaction, to be presented to the user during confirmation and stored in the transaction history.
Registration
The registration phase allows an entity to set up a trust relationship with a wallet that will allow that entity to direct limited automatic (no user interaction) payments.
Registration is the process where an entity sets up a relationship with a wallet, allowing automatic payments to occur. The user must accept or reject this relationship in the wallet. During this registration process, the wallet SHOULD allow the user to change the payment limits and other parameters suggested by the app. During operation, if a payment request exceeds these limits the wallet SHOULD fall back to a user interaction rather than rejecting the request automatically.
It is possible to use other DPP APIs without first registering. In that case, no automatic payments will occur, and the wallet MAY reject the request without notifying the user.
HTTP GET, and Intent protocols must include a pubkey and signature.
Intents MUST be issued with startActivityForResult
so that return data can be captured.
Additional undefined fields MUST be ignored (but included in any signature check).
Format
tdpp://entityName/reg?[topic=topic]&[uoa=uoa]&[addr=addr]&[maxper=amount]&[maxday=amount]&[maxweek=amount]&[maxmonth=amount]&[descper=desc]&[descday=desc]&[descweek=desc]&[descmonth=desc]&[sig=sig]
Fields
entityName: The name of the registering entity/service. This name will be shown in the wallet during payment authorizations or trust management. Since wallets will likely disallow 2 registrations using the same name and topic, make your name unique (e.g. use a full DNS name if a web site).
topic: [optional, string] A registering entity may set up multiple tdpp relationships for unrelated items. This string tells the user what this item is and differentiates them.
uoa: [optional, string] Unit of account. All recommended payment amounts are specified in the smallest unit of this currency. The unit of account is important since many cryptocurrencies are volatile. For example, if the uoa is "NEXA", the amount is specified in Satoshis. If the uoa is "USD" the amount is specified in pennies. The currency the payment actually uses is NOT defined by this field (it is specified when the entity actually requests a payment).
addr: [optional] If provided, this entity will sign requests with the public key associated with this address. This field MUST be provided if there is no implicit secure identity mechanism (such as https), or the wallet will reject with a NO_IDENTITY error.
maxper, maxday, maxweek,maxmonth: [optional, unsigned long integer] These fields specify the recommended automatic payment maximum in Satoshi (i.e. cryptocurrency’s finest unit).
descper, descday, descweek, descmonth: [optional, string] These fields specify short (3 lines or less) explanations of the chosen limits for optional user presentation by the wallet.
sig: [mandatory if pubkey] The signature of the stringified URI format (all fields are form URL encoded -- for example space is '+' not '%20') of this request, alphanumerically ordered by key, less the sig parameter (e.g, &sig=signature). This signature is encoded in bitcoin standard base64 and then as a URI (since base64 includes reserved URI characters, such as / and =).
An entity should either generate a single pubkey/privkey pair when the app is installed or a new pub/private key per relationship. It is insecure to hard-code a single private key into the app. In Android, the “Room” and “KeyStore” databases are private to the application [CHECK].
Example With Signature From a wallet with recovery key: quantum curve elephant soccer faculty cheese merge medal vault damage sniff purpose
tdpp://www.yoursite.com/reg?addr=nexa%3Aqqngstznrtpp3ac5t9vjpnk70v3kr7xkpsnhzpa6hu&descday=desc2%20space%20test&descper=desc1&descweek=week&maxday=10000&maxper=1000&maxweek=100000&topic=thisisatest&uoa=NEX&sig=IJonfE1Lq2i4st%2BueUo%2BC22SbT%2F0rCs1iNstXIQqX%2BaJEKPd26YCHXkjn7kgqwdXI0%2FVnxM%2BLqK6muy8t%2FmdyXY%3D
Registration Response
No response is necessary because how the user chooses to handle the server's TDPP requests should not be provided to the server.
Possible Future response Wallets should return data as part of the registration handling, indicating whether the registration was accepted. A wallet's "OK" response does not indicate whether the suggested policies were accepted, or whether any automatic payment was authorized by the user. The entity should never be provided with information pertaining to how much it can spend without "bothering" the user so that it cannot reliably quietly drain user's accounts. A wallet's "OK" response simply indicates a willingness to act as a payment gateway for the entity.
resultcode: [integer] 200= OK, 300 = user reject, 302 = pubkey required
supports: [integer bit map]:
1 = Pay Address supported,
2 = Pay Transaction supported,
4 = JSON payment protocol supported.
error: [optional] String response from the wallet with rejection details
Pay To Address Request
This request format provides a simple payment interface. Note that the fee is calculated by the wallet and is not included when calculating automatic payment limits. The addresses define the cryptocurrency, so MUST contain the appropriate prefix (e.g. nexa:, bitcoincash:, bitcoin: etc).
Additional undefined fields MUST be ignored, but included in any signature check.
tdpp://entityName/sendto?chain=blockchain[&amtN=amount][&addrN=address][&sig=sig][&topic=topic][&sig=sig][&commonFields]
entityName: Identifies the prior registration.
addrN: [at least one] (e.g. addr0, addr1, addr2) Specify the destination address. If amtN exists, addrN must exist, or the wallet will fail the request.
"N" is arbitrary, it just exists to distinguish multiple outputs. However, if addrN exists, addr0 to addrN MUST exist (In other words "N" must start at zero and increment). And if addrN exists, amtN MUST exist (and vice versa) or the wallet will fail the request. The requester MUST NOT assume that N specifies the transaction output index. Software that requires a particular transaction format should use the "tx" request to specify it explicitly.
amtN: [unsigned integer, at least one] (e.g. amt0, amt1, amt2, amt3) Specify the amount to send in Satoshis (or the finest unit of the currency). Its possible to send to multiple destinations, but the wallet must use the sum of all amounts to determine if automatic or user authorization is required.
The wallet will send amtN to addrN, e.g. amt1 to addr1.
chain: [Mandatory, string] The blockchain this transaction is for, as specified by the BIP21 URI prefix, for example: "bitcoincash" or "bitcoin".
topic: [optional, string] A registering entity may set up multiple tdpp relationships for unrelated items. This string tells the user what this item is and differentiates them.
info: [optional, string] Some information explaining why this payment is being requested
sig: [mandatory if pubkey] The signature of the stringified URI format of this request using the pubkey provided during registration of "entityName", alphanumerically ordered by key, less the sig key and value.
Example:
Donate 1 NEXA to Bitcoin Unlimited.
tdpp://www.myapp.com/sendto?amt0=100000000&addr0=nexa%3Aqqngstznrtpp3ac5t9vjpnk70v3kr7xkpsnhzpa6hu&sig=[TODO SIG FORMAT]
Pay To Address Response
Additional undefined fields MUST be ignored.
resultcode: [integer] 200 = OK, 300 = user reject, 301 = sig failed, 303 = unsupported request type, 304 = insufficient balance txid: [hex string, required on success] The transaction hash of the created transaction txidem: [hex string, required on success] The transaction hash of the created transaction tx: [hex string, required on success] The created transaction error: [optional] String response from the wallet with rejection details
Pay Transaction Request
This request format allows the app to handle bitcoin transaction details, yet delegate funding and/or signing to the wallet.
tdpp://appname/tx?chain=blockchain&tx=tx[&inamt=inputAmounts][&topic=topic][&flags=flags][&sig=sig][&commonFields]
chain: [Mandatory, string] The blockchain this transaction is for, as specified by the BIP21 URI prefix, for example: "bitcoincash" or "bitcoin".
topic: [optional, string] A registering entity may set up multiple tdpp relationships for unrelated items. This string tells the user what this item is and differentiates them.
tx: [Mandatory, hexstring] The transaction in network serialized hex string format. This transaction may be complete and signed, or it may be incomplete in many ways:
1. It may supply or be missing inputs. In other words, it may require additional inputs to meet the output quantity.
2. It may require an additional outputs, directed to this wallet, for change.
3. It may require signatures, but other inputs may be signed.
4. It may supply template constraints. These are incomplete output (constraint) scripts. The wallet should scan all outputs for TMPL_SCRIPT, TMPL_PUBKEYHASH, or TMPL_PUBKEY opcodes and replace the opcode with actual data paying to a unique address or constraint script controlled by this wallet.
The transaction may be unfinishable by the wallet. That is, inputs that this wallet cannot sign may be unsigned. In this case, the nopost flag is implied, and the more-complete transaction should be sent back to the requester for final signatures.
inamt: [Mandatory if size of provided tx inputs > 0, unless nofund flag set, integer]: The sum of all specified inputs in satoshis. This is provided so that the light wallet does not need to access all of unrelated inputs in order to determine how many satoshis it needs to provide as inputs or how many it can claim as an output.
Note that the requester could incorrectly report a too large amount in this field (accidentally or on purpose), which may result in a nonviable transaction or excessive transaction fees being paid. But any overpayment could only happen within the autopay constraints or by explicit acceptance by the user. If this is a problem, the light wallet may receive the inputs via a separate channel (e.g. electrum protocol). TODO: allow the requester to provide the input tx (this could be a lot of data)
Also note that the requester could report a too small amount in this field. This indicates to this wallet that some other entity will supply the missing amount. Generally this will only be done in conjunction with partial or unsigned transactions.
flags: [optional, unsigned int bitmap] An empty flags implies flags == 0 bit 0: nofund: do not add any inputs (but do sign any inputs that this wallet can sign) bit 1: nopost: do not post the transaction to the network (return the finished transaction to the entity) bit 2: noshuffle: do not change the order of inputs or outputs. Change outputs must be added to the end. bit 3: partial: After reworking the transaction as normal (adding any needed inputs/outputs), use sighash 0thru and sighash offset so that all inputs sign all outputs. If this functionality is non-existent, use sighash single. The result of this is that additional inputs and outputs can be added to this transaction without requiring that this wallet resign the transaction.
Pay Transaction Response
resultcode: [integer] 200 = txcompleted, 201 = tx missing sig, 202 = tx unmodified, 203 = tx not final, 204 = temporarily cannot post, 300 = user reject, 301 = sig failed, 303 = unsupported request type, 304 = insufficient balance
Result code 201 means that the transaction has been "filled out" by this wallet, but the wallet is not capable of completing the job -- there are inputs that still must be signed. This can legitimately occur with multisig inputs or interesting transaction forms.
Result code 203 means that the transaction creation is successful, but the transaction cannot be accepted on the blockchain at this time. This code may or may not be returned if "nopost" is specified.
Result code 204 means that the wallet's connection to the blockchain is interrupted so this transaction cannot be posted.
tx: The completed transaction in network serialized hex string format. txid: The completed transaction hash
error: [string, optional] detailed error message, if any
Pay JSON Payment Protocol Request
This request asks the wallet to complete a JSON payment protocol dialog.
tdpp://appname/jsonpay?uri=url
url: The payment protocol initiation URL, as specified in JSON payment protocol document
JSON Payment Protocol Response
resultcode: [integer] 200 = payment completed,
jppmemo: [string, optional] The "memo" field in the payment protocol response SHOULD be reported back to the calling entity, if it exists and is not empty.
Asset Information Request
To provide services, an app may need to know what assets are owned by the interacting wallet. For privacy reasons, wallet owners have the option of sending a subset of assets or no assets in response to this request, similar to how the wallet owner can choose to sign transactions.
tdpp://entityName/assets?chain=blockchain[&topic=topic][&af=scripttemplate][&afx=scripttemplateindex][&chaltx=challengeTransaction]
scripttemplate: [Optional, hexString] Asset filter script template. Assets that match this script template are requested. Multiple 'af' options are possible. scripttemplateindex: [Optional, integer] Asset filter script template index. Assets that match script template N (as installed during registration) are requested. Multiple 'afx' options are possible. challengeTransaction: [Optional, hexString] Request that the wallet prove ownership of this asset by signing an unspendable transaction. See Challenge Transaction. chain: [Mandatory, string] The blockchain this transaction is for, as specified by the BIP21 URI prefix, for example: “bitcoincash” or “bitcoin”.
Asset Information Reply
An asset information reply returns a json-formatted string. If the protocol is http, this is returned in the body of a POST message sent to /assets.
Nexa:
{
"prevout": "serialized hex UTXO data for this outpoint, including script, amount and type",
"amt": integer satoshis available in the prevout field but also included here for convenience,
"outpoint":"hex string",
"proof": "optional: hex string of solved challenge transaction, inputing outpoint"
}
Address Information Request
To provide services, an app may need a wallet address. This call should provide an address unique to this service. However, while it may enhance privacy for a single service to use new addresses for each operation, some service implementations are much easier if a single address is used. This will return either a new address or a single address per entityName/topic, depending on the unique flag. The most recently requested address will ALWAYS be the receiving address used when completing other TDPP requests (such as custom transactions) from this entity/topic.
tdpp://entityName/share?info=address&chain=blockchain[&topic=topic][&unique=true/false]
chain: [Mandatory, string] The blockchain this address is for, as specified by the BIP21 URI prefix, for example: “bitcoincash” or “bitcoin”. unique: [Optional, default to false] If unique is true, a new address is provided. If false, the SAME address must be provided (for this entityName/topic combination) every time.
Clipboard Request
To allow other information to be provided, the app can ask for the clipboard contents. This is a generally useful function. For security, this MUST not be made available over any local communications channel (those apps can just access the clipboard themselves), and the wallet MUST ask the user unless this action derived directly from a user action (e.g. scanning a QR code).
tdpp://entityName/share?info=clipboard[&topic=topic][&unique=true/false]
chain: [Mandatory, string] The blockchain this address is for, as specified by the BIP21 URI prefix, for example: “bitcoincash” or “bitcoin”. unique: [Optional, default to false] If unique is true, a new address is provided. If false, the SAME address must be provided (for this entityName/topic combination) every time.
Address Information Reply
An asset information reply returns a json-formatted string. If the protocol is http, this is returned in the body of a POST message sent to /address. The body type should be text/plain and it should be the address with blockchain prefix, for example "nexareg:nqtsq5g5fsq0nn23ahqeh8jr0ykfl5ewpulgnws780274muq".
Appendix
Document History
Jul 2023: Added Address Information Request. Andrew Stone, Bitcoin Unlimited. Jul 2022: Updated for Nexa. Andrew Stone, Bitcoin Unlimited. Oct 2020: Created by Andrew Stone, Bitcoin Unlimited.