What is DogeConnect?
DogeConnect is a payment protocol for transmitting a detail-rich Payment Request from a Vendor to a Client Wallet and receiving a Payment Response containing a signed transaction, which is validated and relayed to the network by a Payment Relay.
Note: this specification is ready to implement; please provide implementation feedback.
DogeConnect comprises:
- a Payment QR Code specification,
- a Payment Envelope JSON schema,
- an API specification for an internet Payment Relay.
Through DogeConnect, Vendors can issue detailed payment requests to customers with itemised goods and services as well as taxes and fees, which the Client Wallet can display to the user along with the vendor's name and logo.
In the DogeConnect model, the vendor or their nominated Payment Relay validates the transaction and determines their desired risk appetite, the number of blockchain confirmations they require, etc.
sequenceDiagram participant w as Wallet participant c as Client participant v as Vendor participant r as Relay c ->> v: Let me buy something v ->> r: Start payment (itemised payment info) r ->> v: QR Code URI, payment ID v ->> c: QR Code URI c ->> w: Scans QR Code / NFC w ->> r: Fetch Payment Envelope r ->> w: Payment Envelope w ->> w: Verify Hash (MITM) w ->> w: Display itemised payment w ->> w: Confirm/sign transaction w ->> r: Submit signed transaction r ->> r: Validate transaction r ->> r: Submit to dogecoin network r -->> v: Payment accepted r ->> r: Wait for confirmation r -->> v: Payment confirmed
Payment QR-Codes
The de-facto standard Dogecoin QR-Code encodes a URI like this:
dogecoin:DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL?amount=8.25
The URI scheme dogecoin:
is associated with wallet software on a customer's device,
so the wallet will open when the customer scans a QR Code with the device's camera.
The above QR-Code requests a payment of 8.25 dogecoin to a specified address. Traditional wallets simply pre-fill the amount and allow the user to broadcast their payment transaction directly to the dogecoin network.
This payment method has some problems:
- The vendor doesn't know the customer has initiated payment until the transaction is mined into a block, one or more minutes later.
- The customer can choose the amount to pay, or can pay with multiple transactions, which is difficult for the vendor to detect and reconcile.
- The customer doesn't see what they're paying for or who the vendor is, before signing the transaction.
DogeConnect QR-Codes
Due to the limited space available in a QR Code, DogeConnect addresses these problems by including the URL of an itemised Payment Request.
DogeConnect adds two parameters to the QR-Code:
dogecoin:DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL?amount=8.25&dc=example.com%2Fdc%2Fxyz123&h=p212MS4KXZBX5uDNXWmB
- dc is a
https://
URL the wallet can use to fetch the Payment Envelope - h is a hash of the Payment Relay public key, as a security check
The dc
URL always has the https://
prefix trimmed off to save space in the
QR code. This URL should be kept as short as possible.
Wallets that support DogeConnect should fetch the Payment Envelope from the dc
URL
rather than using the basic dogecoin address and amount; those fields provide backwards
compatibility with older wallets.
Please note that URI parameters are percent-encoded in line with RFC 3986;
wallets MUST decode the parameters according to section 2.1. We strongly recommend
using a URL/URI decoding library, which will handle all decoding requirements.
(Otherwise, make sure to use something like decodeURIComponent
or QueryUnescape
functions on each parameter, after splitting up the URI string.)
Wallets MUST use the h
hash to verify that the Payment Envelope came from the
vendor's nominated Payment Relay, as this protects against man-in-the-middle (MITM)
attacks and redirection of funds.
Wallet Implementation
- Receive the QR Code URI from the host system
- Use a URL/URI decoding library to break apart the URI into a scheme (
dogecoin:
), a path component (the dogecoin address) and a series of query parameters: theamount
,dc
andh
parameters. - Check if both
dc
andh
are present, non-empty parameters; this indicates a DogeConnect payment. Both parameters must be present for a secure interaction (for example, a missingh
parameter suggests a MITM attack.) If either are missing or empty, fall back on the dogecoin address and amount parameters, i.e. treat it as a non-DogeConnect QR Code. - Decode the Base64-encoded
h
parameter, using a library or Base64 decode routine. If this fails to decode, or doesn't yield 15 bytes of data, the request is invalid (this also suggests a MITM attack.) - Concatenate
https://
onto the start of thedc
parameter. It is always a https URL, but thehttps://
is trimmed off to keep the QR Codes from growing too large. - Fetch the DogeConnect Payment Envelope JSON payload from the URL created in step 4. If this request fails, make a few attempts before giving up, as sometimes there are spurious network errors (especially on mobile devices.)
- Decode the JSON Payment Envelope. If decoding fails, consider making another attempt (as part of the above retry process) as the response may be a spurious error from a network gateway; retries make the whole process more reliable.
- Take the
pubkey
field of the Payment Envelope, which is hex-encoded. Use a hex decode routine to decode to raw bytes, verify that you decoded 32 bytes, then use SHA-256 to hash those bytes. - Compare the first 15 bytes of the hash from step 8 with the 15 bytes of the
decoded
h
parameter from step 4. If they differ, the request is invalid and has likely been tampered with (this strongly suggests a MITM attack.) - Proceed with Payment Envelope decoding and signature verification described in the next section.
The goals of the above process are to recognise a DogeConnect QR Code, fetch the
Payment Envelope from the vendor's nominated Payment Relay, and verify that
the Payment Envelope actually came from the nominated Payment Relay, by
verifying the pubkey
hash included in the QR Code.
This protects against man-in-the-middle (MITM) attacks and redirection of funds to a potential attacker.
Payment Envelope Schema
Connect Envelope
{
"version": "1.0", // MUST be 1.0
"payload": "YTc2ZDc2MzEyZ..", // Base64-encoded JSON payload (e.g. Connect Payment)
"pubkey": "c8a6927d0a004..", // Relay Public Key, BIP-340 Schnorr X-only (32 bytes)
"sig": "202d831c6437c..", // Payload Signature, BIP-340 Schnorr (64 bytes)
}
Connect Payment
{
"type": "payment", // MUST be "payment"
"id": "PID-123", // Relay-unique Payment ID
"issued": "2006-01-02T15:04:05-07:00", // RFC 3339 Timestamp
"timeout": 60, // Timeout in seconds, do not pay after this time
"relay": "https://example.com/..", // Payment Relay to submit payment tx
"fee_per_kb": "0.01001386", // Minimum fee per 1000 bytes in payment tx, 8-DP string
"max_size": 10000, // Maximum size in bytes of payment tx
"vendor_icon": "https://example.com/..", // Vendor icon URL, JPG or PNG
"vendor_name": "Vendor Co", // Vendor display name
"vendor_address": "123 Example St", // Vendor business address (optional)
"total": "41.9395", // Total including fees and taxes, 8-DP string
"fees": "1.0", // Fees subtotal, 8-DP string
"taxes": "1.9495", // Taxes subtotal, 8-DP string
"fiat_total": "5.00", // Total in fiat currency, decimal string (optional)
"fiat_tax": "0.23", // Taxes in fiat currency, decimal string (optional)
"fiat_currency": "USD", // ISO 4217 currency code (required with fiat_total/fiat_tax)
"items": [], // List of line items to display (Connect Items)
"outputs": [], // List of outputs to pay (Connect Outputs)
}
Connect Item
{
"type": "item", // item, tax, fee, shipping, discount, donation
"id": "SK-101", // unique item ID or SKU
"icon": "https://example.com/itm/ic.png", // icon URL, JPG or PNG
"name": "Doge Plushie", // name to display
"desc": "One doge plushie in a soft bag", // item description to display
"count": 1, // number of units >= 1
"unit": "38.99", // unit price, 8-DP string
"total": "38.99", // count x unit, 8-DP string
"tax": "1.9495", // tax on this item, 8-DP string (optional)
}
Connect Output
{
"address": "DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL", // Dogecoin Address
"amount": "1.0", // Amount, 8-DP string
}
8-DP string
Fields marked 8-DP string
are DECIMAL numbers with up to 8 decimal places,
which represent a Koinu value – the smallest unit of Dogecoin.
They are strings to preserve accuracy; most JSON parsers treat numbers as floating point, which sacrifices some accuracy (e.g. a value of "0.1" cannot be stored accurately as a floating point number.)
These can be parsed using a Decimal library, or code like this.
Payment Envelope Verification
The DogeConnect Payment Envelope contains a Base64-encoded JSON payload, the Payment Relay's public key, and a digital signature of the payload signed by the Payment Relay's private key.
Use the following steps to decode and verify the payload.
- Ensure you verify the
pubkey
hash as described in the Payment QR-Code section. - Decode the Base64-encoded
payload
field, yielding bytes. - Decode the Hex-encoded
pubkey
andsig
fields, yielding bytes. - Compute the Double-SHA256 of the decoded payload bytes from step 2, i.e.
SHA-256(SHA-256(bytes))
- Apply BIP-340
lift_x
algorithm on thepubkey
bytes to recover the full Public Key. A BIP-340 library will supply this step (it's essentially parsing a compressed public key.) - Verify the BIP-340 Schnorr signature, using the Double-SHA256 hash as the
message
, and the full Public Key andsig
. A BIP-340 library will supply the signature verification algorithm. If this step fails, it suggests a MITM attack or faulty implementation. - Parse the JSON payload bytes using a standard JSON parser.
- Check the
timeout
field: do not submit a payment transaction after the timeissued
+timeout
. - Display the payment information and ask the user to confirm payment.
- Create and sign a payment transaction and submit it to the Payment Relay.
The goals of the above process are to verify that the Payment Envelope is cryptographically signed by the Payment Relay's private key, i.e. the envelope was created by the Payment Relay.
A reference implementation of these algorithms exist at github.com/dogeorg/dogeconnect-go which can be packaged for mobile using gomobile bind.
BIP-340 Implementations
Payment Relay
The Payment Relay base URL is found in the relay
field of Connect Payment.
The following Web API must be implemented by a Payment Relay:
URL | Method | Post Data | Response | Function |
---|---|---|---|---|
relay/pay | POST | Payment Response | Payment Reply | Submit payment tx |
relay/status | POST | Status Query | Status Reply | Query payment status |
Requests and responses are JSON payloads; JSON must be UTF-8 encoded.
All responses must include a Cache-Control: no-store
header, to avoid
leaking payment information into caches. Both endpoints additionally use
the POST method to avoid caching edge-cases (e.g. error responses.)
The Relay must implement the pay
endpoint with idempotence in
the following way: if the status of payment id
is already accepted
or
confirmed
, it responds to another pay
request with that status, ignoring
the supplied tx
. This is because wallets will retry their pay
request
if they did not receive or process the first reply.
Payment Response
This is the wallet's response to the original Payment Request.
{
"id": "PID-123", // Relay-unique Payment ID from Connect Payment
"tx": "489c47f8a3ba3293737..", // Hex-encoded signed dogecoin transaction
"refund": "DKY8dUTQthSX..", // Dogecoin address for refunds (RECOMMENDED)
}
Payment Reply
This is the Payment Relay's reply from the pay
URL.
{
"id": "PID-123", // Relay-unique Payment ID from Connect Payment
"status": "accepted", // One of: accepted | confirmed | declined
"reason": "", // Reason for decline (message, optional)
"required": 5, // Number of block confirmations required (risk analysis)
"confirmed": 0, // Current number of block confirmations on-chain
"due_sec": 300, // Estimated time in seconds until confirmed
}
The status will be accepted
if the Relay requires one or more block
confirmations on the blockchain, reflected in the required
field.
The status may be confirmed
if the Relay deems the payment low-risk.
If the transaction is malformed, or does not pay the requested amounts to the requested addresses, the POST will be rejected with a 400 Bad Request http response. This represents a programming error in the wallet. A bad request should not be retried.
Payments may also be rejected with a declined
status, in the case that
the Vendor or their nominated Relay believes the transaction is too
risky. This represents a customer-specific problem. A declined request
should not be retried.
Wallets should also be prepared to handle 500 and 503 http errors, and other spurious errors, which indicate a temporary Relay or network problem. Wallets should retry the request a few times (with a small random delay) to increase reliability.
The final three fields, required
, confirmed
and due_sec
are in common
with the Status Reply below.
Status Query
This allows the wallet to query the current status of a payment.
{
"id": "PID-123" // Relay-unique Payment ID from Connect Payment
}
Status Reply
This is the Payment Relay's reply from the status
URL.
{
"id": "PID-123", // Relay-unique Payment ID from Connect Payment
"status": "accepted", // unpaid | accepted | confirmed
"required": 5, // Number of block confirmations required
"confirmed": 4, // Current number of block confirmations on-chain
"due_sec": 30, // Estimated time in seconds until confirmed
}
Payments transition from unpaid
to accepted
after the signed transaction
is submitted to the pay
endpoint (provided payment was accepted.)
After the required number of block confirmations have been seen on-chain,
the payment status transitions to confirmed
.
When the payment status is confirmed
, the confirmed
field is always greater
or equal to required
, and the due_sec
field is alway zero.
Note: there are some edge-cases where the confirmed
count can reduce,
i.e. during a short-term blockchain fork.