Skip to content

Commit 262ef2c

Browse files
committed
Document the Protected Cards feature
1 parent fcd4627 commit 262ef2c

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

_protected-cards.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Protected Cards
2+
3+
The protected cards feature allows securing an Uphold card by associating a cryptographic key pair to it, so that a signature with the secret key of that pair will be required for performing transactions with it (except deposits into the card, which remain possible with unsigned requests).
4+
5+
The instructions below describe in detail how to make use of this feature.
6+
7+
## Generate a Key Pair
8+
9+
> Create an EdDSA cryptographic key pair, e.g. using the [tweetnacl](https://www.npmjs.com/package/tweetnacl) package:
10+
11+
```js
12+
const nacl = require('tweetnacl');
13+
const keyPair = nacl.sign.keyPair(); // Generate an Ed25519 key pair
14+
const publicKey = Buffer.from(keyPair.publicKey).toString('hex');
15+
const secretKey = Buffer.from(keyPair.secretKey).toString('hex');
16+
```
17+
18+
The public-private key pair used for signing transactions of protected cards must be generated by you, and it must be of the [EdDSA](https://en.wikipedia.org/wiki/EdDSA) type (specifically, `Ed25519`).
19+
20+
This step should be performed on your server-side, and the private key must be stored securely.
21+
Never expose or transmit the private key to Uphold, or any other third party outside your control.
22+
23+
## Create a Protected Card
24+
25+
> Construct the body of the request to create a card:
26+
27+
```js
28+
const data = {
29+
currency: 'USD',
30+
label: 'My Protected Card',
31+
publicKey: publicKey
32+
}
33+
```
34+
35+
> Create a SHA-256 (or SHA-512) hash of the request body, and store it as a base64-encoded digest:
36+
37+
```js
38+
const crypto = require('crypto');
39+
const digest = crypto.createHash('sha256').update(JSON.stringify(data)).digest('base64');
40+
```
41+
42+
> Create the signature, e.g. using the [http-request-signature](https://www.npmjs.com/package/http-request-signature) package:
43+
44+
```js
45+
const { sign } = require('http-request-signature');
46+
const signature = sign({
47+
headers: {
48+
digest: `SHA-256=${digest}` // This must match the `Digest` header
49+
}, // that will be sent in the request.
50+
keyId: 'primary', // We require the `keyId` to be "primary".
51+
secretKey
52+
}, { algorithm: 'ed25519' }); // We only support the `ed25519` algorithm.
53+
```
54+
55+
> Submit the request including the `Digest` and the `Signature` headers, as well as the data used to generate them:
56+
57+
```bash
58+
$ curl 'https://api.uphold.com/v0/me/cards' \
59+
-H 'Accept: application/json' \
60+
-H 'Authorization: Bearer <token>' \
61+
-H 'Digest: SHA-256=<digest>' \
62+
-H 'Signature: <signature>' \
63+
-H 'Content-Type: application/json' \
64+
-d '{ "currency": "USD", "label": "My Protected Card", "publicKey": "<publicKey>" }'
65+
```
66+
67+
> The response should be a [card object](https://uphold.com/en/developer/api/documentation/#card-object)
68+
> (sample output truncated for conciseness):
69+
70+
```json
71+
{
72+
"available": "0.00",
73+
"balance": "0.00",
74+
"currency": "USD",
75+
"id": "71064207-b557-4808-ac33-e4eb86d78a01",
76+
"label": "My Protected Card"
77+
}
78+
```
79+
80+
Once a key pair has been generated, a protected card can be created by adding the public key in the data of a request for [creating a card](https://uphold.com/en/developer/api/documentation/#create-card).
81+
82+
As additional safeguards, two headers must be included in this request: a `Digest` (consisting of a SHA-256 hash of the body of the request) to guard against transmission errors in the request data; and a `Signature` (consisting of the same digest payload, but encrypted with the private key, and formatted to be compliant with the draft Internet Standard "[Signing HTTP Messages](https://tools.ietf.org/html/draft-cavage-http-signatures-12)"), which validates that the public key included in the data does match the private key used for encryption.
83+
84+
<aside class="notice">
85+
Keep in mind that the data sent in the request body must be an <b>exact string match</b> to the input used to generate the <code>Digest</code> and the <code>Signature</code> headers.
86+
In the example shown here, it must equal the output of <code>JSON.stringify()</code>, which is fed into <code>crypto.createHash()</code>.
87+
</aside>
88+
89+
## Create Signed Transactions
90+
91+
> Construct the body of the request to create a transaction from a protected card:
92+
93+
```js
94+
const data = {
95+
denomination: {
96+
amount: '10',
97+
currency: USD'
98+
},
99+
destination: '<address>'
100+
}
101+
```
102+
103+
> Generate the digest and signature of the request data:
104+
105+
```js
106+
const crypto = require('crypto');
107+
const digest = crypto.createHash('256').update(JSON.stringify(data)).digest('base64');
108+
109+
const { sign } = require('http-request-signature');
110+
const signature = sign({
111+
headers: {
112+
digest: `SHA-256=${digest}`
113+
},
114+
keyId: 'primary',
115+
secretKey
116+
}, { algorithm: 'ed25519' });
117+
```
118+
119+
> Submit a request for creating a signed transaction, using the id of the protected card in the URL parameters:
120+
121+
```bash
122+
$ curl 'https://api.uphold.com/v0/me/cards/<id>/transactions?commit=true' \
123+
-H 'Accept: application/json' \
124+
-H 'Authorization: Bearer <token>' \
125+
-H 'Digest: SHA-256=<digest>' \
126+
-H 'Signature: <signature>' \
127+
-H 'Content-Type: application/json' \
128+
-d <data>
129+
```
130+
131+
> Uphold's server verifies that the transaction's signature is correct and proceeds with committing the transaction
132+
> (sample output truncated for conciseness):
133+
134+
```json
135+
{
136+
"createdAt": "2017-06-26T17:17:57.532Z",
137+
"denomination": {
138+
"pair": "USDUSD",
139+
"rate": "1.00",
140+
"amount": "1.00",
141+
"currency": "USD"
142+
},
143+
"id": "efc5aadf-87eb-4731-8697-eb0dd8d48b48",
144+
"status": "completed",
145+
"type": "transfer"
146+
}
147+
```
148+
149+
> Note that the actual response will contain several fields in addition to those shown in this simplified example.
150+
151+
In protected cards, the only operations that can be performed without a signature are deposits _into_ the card.
152+
In order to transact _from_ a protected card to any destination, we'll need to sign the request.
153+
154+
Creating signed transactions from a protected card can be done in much the same way as the process for creating the protected cards themselves — that is, via normal transaction creation requests that include the `Digest` and `Signature` headers.
155+
156+
In this case, since the public key is not transmitted in the request body, the signature serves as a cryptographically strong assurance that the originator of the transaction is authorized to move funds from this card.
157+
More concretely, it proves that they have access to the private part of the key pair that's linked to the protected card in Uphold's internal records.

0 commit comments

Comments
 (0)