ZIP: 324 Title: URI-Encapsulated Payments Owners: Jack Grigg <jack@electriccoin.co> Daira-Emma Hopwood <daira-emma@electriccoin.co> Kris Nuttycombe <kris@electriccoin.co> Original-Authors: Ian Miers Eran Tromer Jack Grigg Kevin Gorham Daira-Emma Hopwood Credits: Sean Bowe Deirdre Connolly Linda Naeun Lee George Tankersley Henry de Valence Status: Draft Category: Standards / Wallet Created: 2019-07-17 License: MIT
The key words "MUST", "MUST NOT", "SHOULD", and "MAY" in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.
Zcash protocol terms are as defined in the Zcash Protocol Specification. 3
By “applink” we mean a platform mechanism for triggering local applications using HTTPS links, such as App Links 5 on Android, Universal Links 6 on iOS, or App URI handlers 7 on Windows.
This proposal specifies a mechanism for sending Zcash payments via any unmodified (secure) messaging service, e.g., Signal or WhatsApp. The sender need not know the recipient's address and the recipient need not have a Zcash wallet already installed. Payments occur via an ordinary text/WhatsApp/Signal message containing a URI. The URI encodes the secret spending key of an ephemeral Zcash “wallet address”, to which the funds have been transferred. Anyone who learns the URI can accept this payment, by a “finalization” process which uses the key given in the URI to transfer the encapsulated funds to their own wallet. After the payment is finalized, via a suitable on-chain transaction by the recipient, it becomes irrevocable.
The proposal specifies the syntax and semantics of URI-Encapsulated Payments, the workflow of generating and handling them, and requisite infrastructure.
At its core, a URI encapsulated payment communicates the existence of a transaction (specifically a note committing to an amount of funds) to a receiving client. The URI encodes the amount of the payment and a key used to derive all necessary randomness for note construction including the address and secret key needed to spend it.
Alice wants to send a 1.23 ZEC payment to Bob. She doesn't have Bob's Zcash address — and Bob may not even have a wallet or have heard of Zcash! However, she does have some end-to-end encrypted channel to Bob on a secure messaging app, such as Signal or WhatsApp. She instructs her own Zcash wallet to send 1.23 ZEC on that channel. Her wallet then automatically generates a new ephemeral Zcash address and sends 1.23001 ZEC to this address. It then constructs a payment URI containing the secret key corresponding to the ephemeral address, and sends it to Bob via the secure messaging app.
Bob receives the message, which looks as follows:
This message contains a Zcash payment. Click the following link to view it and receive the funds using your Zcash wallet app:
https://pay.withzcash.com:65536/v1#amount=1.23&desc=Payment+for+foo&key=...
If you do not yet have a Zcash wallet app, see: https://z.cash/wallets
Bob clicks the link. His Zcash mobile wallet app (which is already installed and has configured itself as a handler for URLs of that form) shows up, and tells Bob that a payment of 1.23 ZEC awaits him. The wallet app confirms the existence of the pertinent transactions on the blockchain, and then offers to finalize the payment. Bob clicks the “Finalize” button, and his wallet app generates a transaction moving the money to his own address (using the extra 0.00001 ZEC he has received to pay the transaction fee). When this transaction is confirmed on-chain, Alice's and Bob's wallets both indicate that the payment is finalized, and thus Bob can send the funds to other parties.
This proposal enables sending of funds without exposing users to the notion of payment addresses and their secure distribution. Instead, funds can be sent using any pre-existing communication channel, by a single message sent unidirectionally from the sender to the recipient. This message conveys all the information needed to obtain control of the funds, compactly expressed as a textual URI.
Consequently, all functionality related to contact discovery and secure-channel establishment can be delegated to the message app(s) with which the user is already familiar, and in which the user has already established communication channels to many of their contacts.
Moreover, funds can be sent to users who have not yet installed wallet software and who do not yet have a payment address. The recipient can collect the funds at any later time by installing suitable software.
Additionally, the proposal greatly improves the scalability of Zcash. If a recipient only receives URI payments, they need not scan the blockchain or delegate their view keys to a third party to do so.
Finally, this avoids the fat-finger problem of sending funds to the wrong address. The user sends funds via a known contact on, e.g., WhatsApp, who they have a relationship with or at least can confirm the recipient's identity. Moreover, even once funds are sent via a URI, they can be recovered if the other party does not claim them.
The proposal is complementary to ZIP 321 4, which will standardize Payment Request URIs using which the payment recipient can convey their persistent payment address to the sender, for subsequent fund transfers (to be done using the normal on-chain mechanism rather than the encapsulated payments described in the current proposal).
This proposal's specification of URI-Encapsulated Payments, and the intended protocols for using them, is meant to fulfill the following requirements:
A Payment-Encapsulating URI represents the capability to claim the Zcash funds from specific on-chain transactions, as long as they're unspent. See Usage Story for an example.
A Payment-Encapsulating URI is a Universal Resource Locator (URL), as defined in RFC 3986 2, of the following form.
Scheme: https
Host: pay.withzcash.com
Port: 65536
(this is intentionally not a valid TCP/IP port number)
Path: payment/v1
Fragment parameters: these attribute-value pairs, in this order, separated by &
, and with all values percent-encoded where necessary:
amount=...
where the attribute is a decimal number representing the amount of ZEC included in the payment. MUST be present. If a decimal fraction is present then a period (.) MUST be used as the separating character to separate the whole number from the decimal fraction, and both the whole number and the decimal fraction MUST be nonempty. No other separators (such as commas for grouping or thousands) are permitted. Leading zeros in the whole number or trailing zeros in the decimal fraction are ignored. There MUST NOT be more than 8 digits in the decimal fraction.desc=...
where the attribute is a human-readable string associated with the payment. MAY be present. If present, it MUST be encoded as “textual data consisting of characters from the Universal Character Set” as specified in RFC 3986 section 2.5.key=
is a 256-bit random number encoded with Bech32 as specified in Section 5.6.9 of the Zcash Protocol Specification 3). MUST be present.The values of key
and amount
deterministically imply a unique payment note corresponding to this URI, which is a Zcash Sapling note that carries the given amount and is spendable by a Sapling spending key derived from key
. The derivation of this note is done by the following procedure:
DerivePaymentNote(key,amount):
TODO: Possible alternate way to derive pk_d and rseed from key:
Construct a shielded zcash transaction containing that note as an output.
The payment note SHOULD be unspent at the time it is intended to be received by the recipient.
Clients MAY generate and send the URI before the transaction is built, sent, or confirmed.
The amount
parameter MUST match the total amount of ZEC in the payment note plus the standard transaction fee for fully-shielded transactions (currently 0.00001 ZEC).
There MUST NOT exist any other notes on the blockchain, or broadcast to the node network, beyond the payment note derived from the Payment URI, that are addressed to any payment address derived from key
(with any diversifier). Such notes MAY be generated within an implementation (e.g., as speculative pre-generation with various note values) but MUST NOT be broadcast for mining.
Wallet software MUST NOT expose the ephemeral payment address corresponding to a payment URI (which helps to ensure the prior paragraph).
The desc
parameter MAY convey a human-readable description of the payment, entered manually by the user or generated by the application in any reasonable manner.
The encrypted memo fields in the output description containing the payment note commitment SHOULD be either empty (all-zero), or identical to the desc
parameter (padded with zeros).
The payment associated with an URI is not deemed “received” by the recipient until they execute a “finalization” process (see section Finalization).
When conveying payment to users, the sender's and recipient's wallet software MAY convey the description encoded in the desc
parameter.
The recipient's wallet software SHOULD convey to the user that the desc
value is merely a claim made by the party who sent the URI, and may be tentative, inaccurate or malicious.
In particular, the recipient's wallet software SHOULD convey to the user that the amount of ZEC they can successfully transfer to their wallet may be different than that given by the amount
parameter, and may change (possibly to zero), until the finalization process has been completed.
The owner of the withzcash.com
domain name MUST NOT create a DNS record for the pay.withzcash.com
domain name, nor a TLS certificate for it. All feasible means SHOULD be taken to ensure this, and to prevent unintended transfer of ownership or control over the withzcash.com
domain name. (See Rationale for URI Format and Security Considerations below for discussion.)
Applink mechanisms let domain name owners provide a whitelist, specifying which apps are authorized to handle URLs with that domain name. This is implemented by serving suitable files at well-known paths on the web server of that domain or, in the case of a subdomain, its parent domain. Thus, the owner of the withzcash.com
domain effectively controls the whitelist of apps that may be launched by users' platform to handle URI-Encapsulated Payments (see Security Considerations). This whitelist should protect users from installing rogue apps that intercept incoming payments. Thus, the domain owner MUST do the following:
They also SHOULD:
For testing purposes, all of the above specification is duplicated for the Zcash testnet network, substituting TAZ
(Zcash testnet coins) for ZEC
and testzcash.com
for withzcash.com
.
A separate “testnet whitelist” MUST be maintained by the owner of the testzcash.com
domain name, with a separate policy that SHOULD allow any legitimate third-party developer to add their work-in-progress wallet for testing purposes. Integrity and availability MAY be looser.
Wallets apps MAY support just one type of payments (ZEC or TAZ), and if they support both then they MUST keep separate accounting and must clearly distinguish the type when payments or balances are conveyed to users.
The lifecycle of a Payment-Encapsulating URI consists of several stages, which in the usual case culminate in the funds being irrevocably deposited into the recipient's personal wallet irrevocably:
The sender's Zcash wallet app creates an ephemeral spending key, sends ZEC funds to the payment addressed derived from that key, and creates a Payment-Encapsulating URI that contains this ephemeral spending key and the newly-generated note commitments.
The ephemeral keys within payment URIs are derived deterministically from the same seed as the main wallet. This ensures that if a wallet is recovered from backup, sent-but-unfinalized payments can be reclaimed.
The derivation mechanism is as follows:
m_Sapling/324'/coin_type'/payment_index'
The sender conveys the Payment-Encapsulating URI to the intended recipient, over some secure channel (e.g., an end-to-end encrypted messaging platform such as Signal, WhatsApp or Magic Wormhole; or a QR code scanned in person).
If transmitted via a human-readable medium, such as a messaging app, the Payment-Encapsulating URI MAY be accompanied by a contextual explanation that the URI encapsulates a payment, and a suggested action by the recipient to complete the process (see Usage Story above for an example).
When sent via a human-readable medium that consists of discrete messages, the message that contains the URI SHOULD NOT contain any payment-specific or manually-entered information outside the URI itself, since this information may not be visible to the recipient (see “Message Rendering” below).
From this point, and until finalization or cancellation (see below), from the sender's perspective the payment is “in progress”; it SHOULD be conveyed as such to the sender; and MUST NOT be conveyed as “finalized” or other phrasing that conveys successful completion.
The recipient's device renders the Payment-Encapsulating URI, or an indication of its arrival, along with the aforementioned contextual explanation (if any). The user has the option of “opening” the URI (i.e., by clicking it), which results in the device opening a Zcash wallet app, using the local platforms app link mechanism.
A messaging app MAY recognize URI-Encapsulated Payments, and render them in a way that conveys their nature more clearly than raw URI strings. If the messaging medium consists of discrete messages, and a message contains one or more URI-Encapsulated Payments, then the messaging app MAY assume that all other content in that message is automatically generated and contains no payment-specific or manually-generated information, and thus may be discarded during rendering.
The recipient's Zcash wallet app SHOULD present the payment amount and MAY present the description, as conveyed in the URI, along with an indication of the tentative nature of this information.
In parallel, the wallet app SHOULD retrieve the relevant transactions from the Zcash blockchain, by looking up the transaction given by the cmu
parameter (this MAY use an efficient index, perhaps assisted by a server), and check whether:
key
parameter.The wallet conveys to the user one of the following states:
Within the Pending state, the wallet app MAY also consider “0 confirmations” transactions (i.e., transactions that have been broadcast on the node network but are neither mined nor expired), and convey their existence to the user. These do not suffice for entering the Ready-to-finalize state (since unmined notes cannot be immediately spent.)
The aforementioned conditions may change over time (e.g., the transactions may be spent by someone else in the interim), so the status SHOULD be updated periodically.
When the recipient chooses to finalize the payment, the wallet app generates transactions that spends the aforementioned notes (using the ephemeral spending key) and send these Zcash funds to the user's own persistent payment address. These transactions carry the default expiry time (currently 100 blocks).
The recipient's wallet app SHOULD convey the payment status as “Finalizing…” starting at the time that the uses initiates the finalization process. It MAY in addition convey the specific action done or event waited.
The sender's wallet SHOULD convey the payment status as “Finalizing…” as soon as it detects that relevant transactions have been broadcast on the peer-to-peer network, or mined to the blockchain.
Once these transactions are confirmed (to an extent considered satisfactory by the local wallet app; currently 10 confirmations is common practice), their status SHOULD be conveyed as “Finalized”, by both the sender's wallet app and the recipient's wallet app. Both wallets MUST NOT convey the payment as “finalized”, or other phrasing that conveys irrevocability, until this point.
If these transactions expire unmined, or are otherwise rendered irrevocably invalidated (e.g., by a rollback), then both wallets' status SHOULD convey this, and the recipient's wallet SHOULD revert to the “Payment Rendering and Blockchain Lookup” stage above.
At any time prior to the payment being finalized, the sender is capable of cancelling the payment, by themselves finalizing the payment into their own wallet (thereby “clawing back” the funds). If the wallet has not yet sent, for inclusion in the blockchain, any of the transactions associated with the ephemeral spending key, then cancellation can also be done by discarding these transactions or aborting their generation. The sender's wallet app SHOULD offer this feature, and in this case, MUST appropriately handle the race condition where the recipient initiated finalization concurrently.
Cancellation requires the sender to know the ephemeral spending key. If the sender has lost this state, it can be recovered deterministically (see Recovery From Wallet Crash, below).
Wallet apps SHOULD let the user view the status of all payments they have generated, as well as all inbound payment (i.e., URI-Encapsulated Payments that have been sent to the app, e.g., by invocation from messaging apps). The status includes the available metadata, and the payment's current state. When pertinent, the wallet app SHOULD offer the ability to finalize any Pending inbound payment, and MAY offer the ability to cancel any outbound payment.
Wallet apps SHOULD actively alert the user (e.g., via status notifications) if a payment that they sent has not been finalized within a reasonable time period (e.g., 1 week), and offer to cancel the payment.
When recovering from a backed-up wallet phrase, wallet implementations already need to scan the entire chain (from the wallet's birthday) to find transactions that were received by or sent from the wallet. Simultaneously with this, the wallet may recover state about previously-created payment URIs, and regain access to non-finalized funds.
We define a “gap limit” N, similar to the “address gap limit” in BIP 44. If a wallet implementation observes N sequentially-derived payment URIs that have no corresponding on-chain note, they may safely expect that no payment URIs beyond that point have ever been derived.
Given that both the derivation of a payment URI and the action of “filling” it with a note are performed by the same entity (and in most cases sequentially), it is unlikely that there would be a significantly large gap in payment URI usage. As a balance between the cost of scanning multiple ivk
s, and the likelihood of missing on-chain funds due to out-of-order payment URI generation, we specify a standard gap limit of N = 3
.
The process for determining the position of this gap during wallet recovery is as follows:
ivk
s corresponding to these keys via the process defined in 3 Section 4.2.2 (setting sk = key
).ivk
s. If a note is detected:
ivk
from the set of current payment URI ivk
s.ivk
in line, and add it to the set.For this recovery process to succeed, wallet implementations MUST fund payment URIs with a Sapling spending key in the wallet. Alternatively, wallet implementations MAY include the set of payment URI ivk
s within the set of ivk
s they are using for normal chain scanning, but this will slow down the recovery process by a factor of 4 (for a gap limit of N = 3
, and a wallet with one Sapling account).
zcash:payment-address?amount=...
as specified in ZIP 321 4. Normally these serve different workflows, and work in opposing directions (send vs. receive of funds), and thus ought to not arise in ambiguous context. Wallet apps should take care to not create or send a Payment-Encapsulating URI (which is for sending funds) in a context where the user may be intending to receive funds.withzcash.com
domain effectively controls the whitelist of apps that may be launched by users' platform to handle URI-Encapsulated Payments using the applink mechanism. If the whitelist is too permissive and includes a malicious or vulnerable app, and a user installs that app (which itself may be subject to the platform vendor's app review mechanism), then the user is placed at risk of having their payments intercepted by an attacker. Conversely, if the whitelist is too restrictive, or altogether unavailable, then users would not be able to trigger desirable wallet apps by simply following links, and would need to instead ”share” the message containing the URI into their wallet app (note that, as discussed above, clipboard copy-and-paste is insecure).The URI format https://pay.withzcash.com:65536/v1#...
was chosen to allow automatic triggering of wallet software on mobile devices, using the platform's applink mechanism, while minimizing the risk of payment information being intercepted by third parties. The latter is prevented by a defense in depth, where any of the following suffices to prevent the payment information from being exposed over the network:
pay.withzcash.com
domain should not resolve.pay.withzcash.com
should not exist..65536
is not valid for the TCPv4, TCPv6 or UDP protocols. Empirically, the common behavior in browsers and messaging apps, when following HTTPS links with port number port number 65536, is to render an empty or about:blank
page rather than a DNS error; a network fetch is not triggered. (This may change if a network proxy protocol is used, but SOCKS5 also cannot represent port 65536.)The downside is that if the user follows the link prior to installing a suitable wallet app, they get a weird-looking DNS error or a blank page. Also, the URL looks weird due to the port number.
Several alternatives were considered, but found to have inferior usability and/or security ramifications:
https://pay.withzcash.com/v1#...
: similar to above, but without the port number, and backed by a DNS record, TLS certificate and web server for pay.withzcash.com
that serves an informative HTML page (e.g., “Please install a wallet to receive this payment”). This still allows handling by wallet apps using an applink mechanism, and provides a friendlier fallback in case the user follows the link prior to installing a suitable app. However, it creates a security risk. If the web server serving that web page is compromised, or impersonated using an DNS+TLS attack, then the attacker can capture they payment parameters and steal the funds. (Note that the sensitive information is in the fragment following the #
, which is not sent in an HTTP GET request; but the malicious server can serve JavaScript code which retrieves the fragment.)zcash-data:payment/v1?amount=1.23&desc=Payment+for+foo&key=...
: a custom URI scheme, such as zcash-data
. This still allows for triggering application action (e.g., using Mobile Deep Links). However, on most platorms, any app installed on the device is able to register to handle links from (almost) any custom URI scheme. If the request is received by a rogue party, then the funds could be stolen. Even if received by an honest operator, funds could be stolen if they are compromised. Also, custom URI schemes are not linkified when displayed in some messaging apps.
Note the use of the zcash-data
URI scheme, rather than the more elegant zcash
, because URIs of the form zcash:address?...
are already used to specify Zcash addresses and payment requests in ZIP 321 4, by analogy to the bitcoin
URIs of BIP 21. An alternative is to use zcash:v1/payment?...
; legacy software may parse this as a payment request to the address v1
, which is invalid. Another alternative is to use zcash-payment:v1?...
, which is appealing in terms of length and readability, but may be gratuitous pollution of the URI scheme namespace.
Another option, which can be added to any of the above, is to add a confirmation code outside the URI that needs to be manually entered by the user into the wallet app, so that merely intercepting the URI link would not suffice. This does not seem to significantly reduce risk in the scenarios considered, and so deemed to not justify the reduced usability.
The recipient's wallet needs to identify the notes related to the payment (and the on-chain transactions that contain them), in order to verify their validity and then (during the finalization process) spend them.
The following is out of date, and reflects an earlier design choice (“0”), while we have transitioned to a different choice (“4”). To be revised.
In the above description, we explicitly list the notes involved in the payment (which are easily mapped to the transactions containing them, using a suitable index). This results in long URIs when multiple notes are involved (e.g., when using the aforementioned “powers-of-2 amounts” technique).
Instead, we can have the nodes be implicitly identified by the spending key (or similar) included in the URI. This can make URI shorter, thus less scary and less likely to run into length limits (consider SMS). The following alternatives are feasible:
\(\hspace{0.9em}\) 0. Explicitly list the note commitments within the URI.
desc
parameter.The URI could be changed in several ways due to usability concerns:
amount
and desc
parameters from being human readable. This is to discourage people from just looking at the URI, seeing the numbers and text, and mistakenly thinking this is already a confirmation of successful receipt (without going through the finalization process).zcash-data:/payment/v1/password=
, as a cue that this string must be kept secret. (Note that technically nothing here is a password in the usual sense of the term.)zcash-data:payment/v1/password=witch+collapse+practice+feed+shame+open+despair+creek+road+again+ice+least
or
zcash-data:payment/v1/password=WitchCollapsePracticeFeedShameOpenDespairCreekRoadAgainIceLeast
This provides an additional cue that the URI contains a sensitive password (for users who are accustomed to BIP 39 style word lists; to others the Base 64 encoding may be more evocative of a password). Moreover, users may discover the fact that they can manually send these words to recipients, in writing or verbally, as a way to send money without a textual messaging service. Alternatively, the BIP 39 words can be used as an alternative syntax for the encapsulation, without the confusing-to-humans URI syntax (but generating this alternative syntax this may complicate the UI).
Ideally: a lightweight wallet can receive the funds with the assistance of a more powerful node, with minimal information leakage to that node (e.g., using simple lookups queries that can be implemented via Private Information Retrieval). The open question is how to do this given that most practical PIR are for retrieving an index out of an array, not a key from a key value standpoint.
Should senders delay admitting a generated transaction by a random amount to prevent traffic analysis (i.e., so the messaging service operator cannot correlate messages with on-chain transactions)?
Consider the behavior in case a chain reorgs invalidates a sent payment. Should we specify a Merkle root or block hash to help detect this reason for payment failure? Or have some servers that maintain a cache of payments that were invalidated by reorgs?
1 | Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words" |
---|
2 | Uniform Resource Identifier (URI): Generic Syntax |
---|
3 | Zcash Protocol Specification, Version 2023.4.0 or later |
---|
4 | ZIP 321: Payment Request URIs |
---|
5 | Android Developer Guides: Handling Android App Links |
---|
6 | Apple Xcode Documentation: Allowing apps and websites to link to your content |
---|
7 | Microsoft Universal Windows Platform Documentation: Enable apps for websites using app URI handlers |
---|
8 | FinCEN Guidance FIN-2019-G001. May 9, 2019. Application of FinCEN’s Regulations to Certain Business Models Involving Convertible Virtual Currencies |
---|