@bitgo-beta/key-card 0.0.1-beta.6 → 0.0.1-beta.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/package.json +10 -5
- package/src/drawKeycard.ts +240 -0
- package/src/generateQrData.ts +13 -10
- package/src/index.ts +21 -0
- package/test/unit/generateQrData.ts +52 -13
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.1](https://github.com/BitGo/BitGoJS/compare/@bitgo/key-card@0.1.0...@bitgo/key-card@0.1.1) (2023-02-17)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @bitgo/key-card
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 0.1.0 (2023-02-16)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **key-card:** fix errors in key-card drawing code ([092bc0a](https://github.com/BitGo/BitGoJS/commit/092bc0a4b851d6cf9d396e8eb5af476b4c52954d))
|
|
20
|
+
* **key-card:** reduce size of QR codes ([c8e08b8](https://github.com/BitGo/BitGoJS/commit/c8e08b8ca007bd45cb0628c5a06a80b81f5a308e))
|
|
21
|
+
* **root:** manually fix up incorrect versions ([3b1d28a](https://github.com/BitGo/BitGoJS/commit/3b1d28a8a4925e6dc1d89bb7482ea3b2f52b7b95))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* **key-card:** add basic code to generate key cards ([83b01fc](https://github.com/BitGo/BitGoJS/commit/83b01fc5c31801e4f8db1166b06f2b8e7de3183f))
|
|
27
|
+
* **key-card:** add code to select qrData ([8b0e814](https://github.com/BitGo/BitGoJS/commit/8b0e814e54d9e33c41e5459d4fc69dac7b7d7737))
|
|
28
|
+
* **key-card:** add function to generate key card faqs ([749798d](https://github.com/BitGo/BitGoJS/commit/749798d184153e8257a7926f62e6c72420c31bc8))
|
|
29
|
+
* **key-card:** add support for qrcodes ([d79b9f7](https://github.com/BitGo/BitGoJS/commit/d79b9f7192dcabe9a7a1d5c261b799b5cbedb6f7))
|
|
30
|
+
* **key-card:** add utils function to split keys ([3547b19](https://github.com/BitGo/BitGoJS/commit/3547b194b49a50da8901b8d76c5f9a96c2f9c994))
|
|
31
|
+
* **key-card:** change contents of Boxes B & C for SMHA wallets ([2c27cd0](https://github.com/BitGo/BitGoJS/commit/2c27cd055bcdeb1a1a444d204298a8adb1d7a1c8))
|
|
32
|
+
* **key-card:** support BitGo Trust holding TSS backup key ([6cb8f94](https://github.com/BitGo/BitGoJS/commit/6cb8f94a4916a211f73c774ee9d94f6fdf6e6650))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitgo-beta/key-card",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.60",
|
|
4
4
|
"description": "key card generator for BitGo wallets",
|
|
5
5
|
"main": "./dist/src/index.js",
|
|
6
6
|
"types": "./dist/src/index.d.ts",
|
|
@@ -33,9 +33,14 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bitgo-beta/sdk-api": "1.1.1-beta.
|
|
37
|
-
"@bitgo-beta/sdk-core": "1.1.1-beta.
|
|
38
|
-
"@bitgo-beta/statics": "7.0.1-beta.
|
|
36
|
+
"@bitgo-beta/sdk-api": "1.1.1-beta.185",
|
|
37
|
+
"@bitgo-beta/sdk-core": "1.1.1-beta.185",
|
|
38
|
+
"@bitgo-beta/statics": "7.0.1-beta.185",
|
|
39
|
+
"jspdf": "^2.5.1",
|
|
40
|
+
"qrcode": "^1.5.1"
|
|
39
41
|
},
|
|
40
|
-
"
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/qrcode": "1.5.0"
|
|
44
|
+
},
|
|
45
|
+
"gitHead": "fcd74261f04b23b14532a5031db203b5252bacad"
|
|
41
46
|
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { jsPDF } from 'jspdf';
|
|
2
|
+
import * as QRCode from 'qrcode';
|
|
3
|
+
import { FAQ } from './faq';
|
|
4
|
+
import { QrData } from './generateQrData';
|
|
5
|
+
import { splitKeys } from './utils';
|
|
6
|
+
|
|
7
|
+
// Max for Binary/Byte Data https://github.com/soldair/node-qrcode#qr-code-capacity
|
|
8
|
+
// the largest theoretically possible value is actually 2953 but the QR codes get so dense that scanning them with a
|
|
9
|
+
// phone (off of a printed page) doesn't work anymore
|
|
10
|
+
// this limitation was chosen by trial and error
|
|
11
|
+
export const QRBinaryMaxLength = 1500;
|
|
12
|
+
|
|
13
|
+
const font = {
|
|
14
|
+
header: 24,
|
|
15
|
+
subheader: 15,
|
|
16
|
+
body: 12,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const color = {
|
|
20
|
+
black: '#000000',
|
|
21
|
+
darkgray: '#4c4c4c',
|
|
22
|
+
gray: '#9b9b9b',
|
|
23
|
+
red: '#e21e1e',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const margin = 30;
|
|
27
|
+
|
|
28
|
+
// Helpers for data formatting / positioning on the paper
|
|
29
|
+
function left(x: number): number {
|
|
30
|
+
return margin + x;
|
|
31
|
+
}
|
|
32
|
+
function moveDown(y: number, ydelta: number): number {
|
|
33
|
+
return y + ydelta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function drawOnePageOfQrCodes(qrImages: HTMLCanvasElement[], doc: jsPDF, y: number, qrSize: number, startIndex): number {
|
|
37
|
+
doc.setFont('helvetica');
|
|
38
|
+
let qrIndex: number = startIndex;
|
|
39
|
+
for (; qrIndex < qrImages.length; qrIndex++) {
|
|
40
|
+
const image = qrImages[qrIndex];
|
|
41
|
+
const textBuffer = 15;
|
|
42
|
+
if (y + qrSize + textBuffer >= doc.internal.pageSize.getHeight()) {
|
|
43
|
+
return qrIndex;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
doc.addImage(image, left(0), y, qrSize, qrSize);
|
|
47
|
+
|
|
48
|
+
if (qrImages.length === 1) {
|
|
49
|
+
return qrIndex + 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
y = moveDown(y, qrSize + textBuffer);
|
|
53
|
+
doc.setFontSize(font.body).setTextColor(color.black);
|
|
54
|
+
doc.text('Part ' + (qrIndex + 1).toString(), left(0), y);
|
|
55
|
+
y = moveDown(y, 20);
|
|
56
|
+
}
|
|
57
|
+
return qrIndex + 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function drawKeycard({ activationCode, questions, keyCardImage, qrData, walletLabel }: {
|
|
61
|
+
activationCode?: string;
|
|
62
|
+
keyCardImage?: HTMLImageElement;
|
|
63
|
+
qrData: QrData;
|
|
64
|
+
questions: FAQ[];
|
|
65
|
+
walletLabel: string;
|
|
66
|
+
}): Promise<jsPDF> {
|
|
67
|
+
// document details
|
|
68
|
+
const width = 8.5 * 72;
|
|
69
|
+
let y = 0;
|
|
70
|
+
|
|
71
|
+
// Create the PDF instance
|
|
72
|
+
const doc = new jsPDF('portrait', 'pt', 'letter'); // jshint ignore:line
|
|
73
|
+
doc.setFont('helvetica');
|
|
74
|
+
|
|
75
|
+
// PDF Header Area - includes the logo and company name
|
|
76
|
+
// This is data for the BitGo logo in the top left of the PDF
|
|
77
|
+
y = moveDown(y, 30);
|
|
78
|
+
|
|
79
|
+
if (keyCardImage) {
|
|
80
|
+
doc.addImage(keyCardImage, left(0), y, 303, 40);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Activation Code
|
|
84
|
+
if (activationCode) {
|
|
85
|
+
y = moveDown(y, 8);
|
|
86
|
+
doc.setFontSize(font.body).setTextColor(color.gray);
|
|
87
|
+
doc.text('Activation Code', left(460), y);
|
|
88
|
+
}
|
|
89
|
+
doc.setFontSize(font.header).setTextColor(color.black);
|
|
90
|
+
y = moveDown(y, 25);
|
|
91
|
+
doc.text('KeyCard', left(325), y - 1);
|
|
92
|
+
if (activationCode) {
|
|
93
|
+
doc.setFontSize(font.header).setTextColor(color.gray);
|
|
94
|
+
doc.text(activationCode, left(460), y);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Subheader
|
|
98
|
+
// titles
|
|
99
|
+
const date = new Date().toDateString();
|
|
100
|
+
y = moveDown(y, margin);
|
|
101
|
+
doc.setFontSize(font.body).setTextColor(color.gray);
|
|
102
|
+
doc.text('Created on ' + date + ' for wallet named:', left(0), y);
|
|
103
|
+
// copy
|
|
104
|
+
y = moveDown(y, 25);
|
|
105
|
+
doc.setFontSize(font.subheader).setTextColor(color.black);
|
|
106
|
+
doc.text(walletLabel, left(0), y);
|
|
107
|
+
// Red Bar
|
|
108
|
+
y = moveDown(y, 20);
|
|
109
|
+
doc.setFillColor(255, 230, 230);
|
|
110
|
+
doc.rect(left(0), y, width - 2 * margin, 32, 'F');
|
|
111
|
+
|
|
112
|
+
// warning message
|
|
113
|
+
y = moveDown(y, 20);
|
|
114
|
+
doc.setFontSize(font.body).setTextColor(color.red);
|
|
115
|
+
doc.text('Print this document, or keep it securely offline. See below for FAQ.', left(75), y);
|
|
116
|
+
|
|
117
|
+
// Generate the first page's data for the backup PDF
|
|
118
|
+
y = moveDown(y, 35);
|
|
119
|
+
const qrSize = 130;
|
|
120
|
+
|
|
121
|
+
const qrKeys = ['user', 'passcode', 'backup', 'bitgo'];
|
|
122
|
+
for (let index = 0; index < qrKeys.length; index++) {
|
|
123
|
+
const name = qrKeys[index];
|
|
124
|
+
if (index === 2) {
|
|
125
|
+
// Add 2nd Page
|
|
126
|
+
doc.addPage();
|
|
127
|
+
|
|
128
|
+
// 2nd page title
|
|
129
|
+
y = 30;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const qr = qrData[name];
|
|
133
|
+
let topY = y;
|
|
134
|
+
const textLeft = left(qrSize + 15);
|
|
135
|
+
let textHeight = 0;
|
|
136
|
+
|
|
137
|
+
const qrImages: HTMLCanvasElement[] = [];
|
|
138
|
+
const keys = splitKeys(qr.data, QRBinaryMaxLength);
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
qrImages.push(await QRCode.toCanvas(key, { errorCorrectionLevel: 'L' }));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let nextQrIndex = drawOnePageOfQrCodes(qrImages, doc, y, qrSize, 0);
|
|
144
|
+
|
|
145
|
+
doc.setFontSize(font.subheader).setTextColor(color.black);
|
|
146
|
+
y = moveDown(y, 10);
|
|
147
|
+
textHeight += 10;
|
|
148
|
+
doc.text(qr.title, textLeft, y);
|
|
149
|
+
textHeight += doc.getLineHeight();
|
|
150
|
+
y = moveDown(y, 15);
|
|
151
|
+
textHeight += 15;
|
|
152
|
+
doc.setFontSize(font.body).setTextColor(color.darkgray);
|
|
153
|
+
doc.text(qr.description, textLeft, y);
|
|
154
|
+
textHeight += doc.getLineHeight();
|
|
155
|
+
doc.setFontSize(font.body - 2);
|
|
156
|
+
if (qr?.data?.length > QRBinaryMaxLength) {
|
|
157
|
+
y = moveDown(y, 30);
|
|
158
|
+
textHeight += 30;
|
|
159
|
+
doc.text('Note: you will need to put all Parts together for the full key', textLeft, y);
|
|
160
|
+
}
|
|
161
|
+
y = moveDown(y, 30);
|
|
162
|
+
textHeight += 30;
|
|
163
|
+
doc.text('Data:', textLeft, y);
|
|
164
|
+
textHeight += doc.getLineHeight();
|
|
165
|
+
y = moveDown(y, 15);
|
|
166
|
+
textHeight += 15;
|
|
167
|
+
const width = 72 * 8.5 - textLeft - 30;
|
|
168
|
+
doc.setFont('courier').setFontSize(9).setTextColor(color.black);
|
|
169
|
+
const lines = doc.splitTextToSize(qr.data, width);
|
|
170
|
+
const buffer = 10;
|
|
171
|
+
for (let line = 0; line < lines.length; line++) {
|
|
172
|
+
// add new page if data does not fit on one page
|
|
173
|
+
if (y + buffer >= doc.internal.pageSize.getHeight()) {
|
|
174
|
+
doc.addPage();
|
|
175
|
+
textHeight = 0;
|
|
176
|
+
y = 30;
|
|
177
|
+
topY = y;
|
|
178
|
+
nextQrIndex = drawOnePageOfQrCodes(qrImages, doc, y, qrSize, nextQrIndex);
|
|
179
|
+
doc.setFont('courier').setFontSize(9).setTextColor(color.black);
|
|
180
|
+
}
|
|
181
|
+
doc.text(lines[line], textLeft, y);
|
|
182
|
+
if (line !== lines.length - 1) {
|
|
183
|
+
y = moveDown(y, buffer);
|
|
184
|
+
textHeight += buffer;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add public key if exists
|
|
189
|
+
if (qr.publicMasterKey) {
|
|
190
|
+
const text = 'Key Id: ' + qr.publicMasterKey;
|
|
191
|
+
|
|
192
|
+
// Gray bar
|
|
193
|
+
y = moveDown(y, 20);
|
|
194
|
+
textHeight += 20;
|
|
195
|
+
doc.setFillColor(247, 249, 249); // Gray background
|
|
196
|
+
doc.setDrawColor(0, 0, 0); // Border
|
|
197
|
+
|
|
198
|
+
// Leave a bit of space for the side of the rectangle.
|
|
199
|
+
const splitKeyId = doc.splitTextToSize(text, width - 10);
|
|
200
|
+
|
|
201
|
+
// The height of the box must be at least 15 px (for single line case), or
|
|
202
|
+
// a multiple of 13 for each line. This allows for proper padding.
|
|
203
|
+
doc.rect(textLeft, y, width, Math.max(12 * splitKeyId.length, 15), 'FD');
|
|
204
|
+
textHeight += splitKeyId.length * doc.getLineHeight();
|
|
205
|
+
doc.text(splitKeyId, textLeft + 5, y + 10);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
doc.setFont('helvetica');
|
|
209
|
+
// Move down the size of the QR code minus accumulated height on the right side, plus margin
|
|
210
|
+
// if we have a key that spans multiple pages, then exclude QR code size
|
|
211
|
+
const rowHeight = Math.max(qr.data.length > QRBinaryMaxLength ? qrSize + 20 : qrSize, textHeight);
|
|
212
|
+
const marginBottom = 15;
|
|
213
|
+
y = moveDown(y, rowHeight - (y - topY) + marginBottom);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Add next Page
|
|
217
|
+
doc.addPage();
|
|
218
|
+
|
|
219
|
+
// next pages title
|
|
220
|
+
y = 0;
|
|
221
|
+
y = moveDown(y, 55);
|
|
222
|
+
doc.setFontSize(font.header).setTextColor(color.black);
|
|
223
|
+
doc.text('BitGo KeyCard FAQ', left(0), y);
|
|
224
|
+
|
|
225
|
+
// Generate the second page's data for the backup PDF
|
|
226
|
+
y = moveDown(y, 30);
|
|
227
|
+
questions.forEach(function (q) {
|
|
228
|
+
doc.setFontSize(font.subheader).setTextColor(color.black);
|
|
229
|
+
doc.text(q.question, left(0), y);
|
|
230
|
+
y = moveDown(y, 20);
|
|
231
|
+
doc.setFontSize(font.body).setTextColor(color.darkgray);
|
|
232
|
+
q.answer.forEach(function (line) {
|
|
233
|
+
doc.text(line, left(0), y);
|
|
234
|
+
y = moveDown(y, font.body + 3);
|
|
235
|
+
});
|
|
236
|
+
y = moveDown(y, 22);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return doc;
|
|
240
|
+
}
|
package/src/generateQrData.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as assert from 'assert';
|
|
|
6
6
|
export interface GenerateQrDataParams {
|
|
7
7
|
// The backup keychain as it is returned from the BitGo API upon creation
|
|
8
8
|
backupKeychain: Keychain,
|
|
9
|
-
// The 3rd party provider of the backup key if neither the user nor BitGo stores it
|
|
9
|
+
// The name of the 3rd party provider of the backup key if neither the user nor BitGo stores it
|
|
10
10
|
backupKeyProvider?: string;
|
|
11
11
|
// The key id of the backup key, only used for cold keys
|
|
12
12
|
backupMasterKey?: string;
|
|
@@ -31,7 +31,6 @@ export interface GenerateQrDataParams {
|
|
|
31
31
|
interface QrDataEntry {
|
|
32
32
|
data: string;
|
|
33
33
|
description: string;
|
|
34
|
-
image: string;
|
|
35
34
|
title: string;
|
|
36
35
|
publicMasterKey?: string;
|
|
37
36
|
}
|
|
@@ -58,7 +57,6 @@ function generateUserQrData(userKeychain: Keychain, userMasterKey?: string): QrD
|
|
|
58
57
|
if (userKeychain.encryptedPrv) {
|
|
59
58
|
return {
|
|
60
59
|
title: 'A: User Key',
|
|
61
|
-
image: '.qrEncryptedUserKey',
|
|
62
60
|
description: 'This is your private key, encrypted with your wallet password.',
|
|
63
61
|
data: userKeychain.encryptedPrv,
|
|
64
62
|
};
|
|
@@ -69,7 +67,6 @@ function generateUserQrData(userKeychain: Keychain, userMasterKey?: string): QrD
|
|
|
69
67
|
|
|
70
68
|
return {
|
|
71
69
|
title: 'A: Provided User Key',
|
|
72
|
-
image: '.qrPublicUserKey',
|
|
73
70
|
description: 'This is the public key you provided for your wallet.',
|
|
74
71
|
data: pub,
|
|
75
72
|
publicMasterKey: userMasterKey,
|
|
@@ -87,19 +84,28 @@ function generateBackupQrData(coin: Readonly<BaseCoin>, backupKeychain: Keychain
|
|
|
87
84
|
if (backupKeychain.encryptedPrv) {
|
|
88
85
|
return {
|
|
89
86
|
title,
|
|
90
|
-
image: '.qrEncryptedBackupKey',
|
|
91
87
|
description: 'This is your backup private key, encrypted with your wallet password.',
|
|
92
88
|
data: backupKeychain.encryptedPrv,
|
|
93
89
|
};
|
|
94
90
|
}
|
|
95
91
|
|
|
92
|
+
if (backupKeyProvider === 'BitGo Trust' && backupKeychain.type === 'tss') {
|
|
93
|
+
const keyShares = backupKeychain.keyShares?.filter((keyShare) => keyShare.to === 'backup');
|
|
94
|
+
assert(keyShares?.length === 2);
|
|
95
|
+
return {
|
|
96
|
+
title: 'B: Backup Key Shares',
|
|
97
|
+
description: `These are the key shares for ${backupKeyProvider}. If BitGo Inc. goes out of business,\r\n` +
|
|
98
|
+
`contact ${backupKeyProvider} and they will help you recover your funds.`,
|
|
99
|
+
data: JSON.stringify(keyShares),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
const pub = getPubFromKey(backupKeychain);
|
|
97
104
|
assert(pub);
|
|
98
105
|
|
|
99
106
|
if (backupKeyProvider) {
|
|
100
107
|
return {
|
|
101
108
|
title: 'B: Backup Key',
|
|
102
|
-
image: '.qrPublicBackupKey',
|
|
103
109
|
description: `This is the public key held at ${backupKeyProvider}, an ${coin.name} recovery service. ` +
|
|
104
110
|
`If you lose\r\nyour key, ${backupKeyProvider} will be able to sign transactions to recover funds.`,
|
|
105
111
|
data: pub,
|
|
@@ -108,7 +114,6 @@ function generateBackupQrData(coin: Readonly<BaseCoin>, backupKeychain: Keychain
|
|
|
108
114
|
|
|
109
115
|
return {
|
|
110
116
|
title: 'B: Provided Backup Key',
|
|
111
|
-
image: '.qrEncryptedUserProvidedXpub',
|
|
112
117
|
description: 'This is the public key you provided for your wallet.',
|
|
113
118
|
data: pub,
|
|
114
119
|
publicMasterKey: backupMasterKey,
|
|
@@ -121,7 +126,6 @@ function generateBitGoQrData(bitgoKeychain: Keychain): QrDataEntry {
|
|
|
121
126
|
|
|
122
127
|
return {
|
|
123
128
|
title: 'C: BitGo Public Key',
|
|
124
|
-
image: '.qrBitgoKey',
|
|
125
129
|
description:
|
|
126
130
|
'This is the public part of the key that BitGo will use to ' +
|
|
127
131
|
'co-sign transactions\r\nwith you on your wallet.',
|
|
@@ -154,8 +158,7 @@ export function generateQrData({
|
|
|
154
158
|
|
|
155
159
|
qrData.passcode = {
|
|
156
160
|
title: 'D: Encrypted wallet Password',
|
|
157
|
-
|
|
158
|
-
description: 'This is the wallet password, encrypted client-side with a key held by\r\nBitGo.',
|
|
161
|
+
description: 'This is the wallet password, encrypted client-side with a key held by BitGo.',
|
|
159
162
|
data: encryptedWalletPasscode,
|
|
160
163
|
};
|
|
161
164
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,22 @@
|
|
|
1
|
+
import { generateQrData, GenerateQrDataParams } from './generateQrData';
|
|
2
|
+
import { generateFaq } from './faq';
|
|
3
|
+
import { drawKeycard } from './drawKeycard';
|
|
4
|
+
|
|
5
|
+
export * from './drawKeycard';
|
|
6
|
+
export * from './faq';
|
|
7
|
+
export * from './generateQrData';
|
|
1
8
|
export * from './utils';
|
|
9
|
+
|
|
10
|
+
export interface GenerateKeycardParams extends GenerateQrDataParams {
|
|
11
|
+
activationCode?: string;
|
|
12
|
+
keyCardImage?: HTMLImageElement;
|
|
13
|
+
walletLabel: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function generateKeycard(params: GenerateKeycardParams): Promise<void> {
|
|
17
|
+
const questions = generateFaq(params.coin.fullName);
|
|
18
|
+
const qrData = generateQrData(params);
|
|
19
|
+
const keycard = await drawKeycard({ ...params, questions, qrData });
|
|
20
|
+
// Save the PDF on the user's browser
|
|
21
|
+
keycard.save(`BitGo Keycard for ${params.walletLabel}.pdf`);
|
|
22
|
+
}
|
|
@@ -2,19 +2,23 @@ import * as assert from 'assert';
|
|
|
2
2
|
import * as should from 'should';
|
|
3
3
|
import { generateQrData } from '../../src/generateQrData';
|
|
4
4
|
import { decrypt } from '@bitgo-beta/sdk-api';
|
|
5
|
-
import { Keychain, KeyType } from '@bitgo-beta/sdk-core';
|
|
5
|
+
import { ApiKeyShare, Keychain, KeyType } from '@bitgo-beta/sdk-core';
|
|
6
6
|
import { coins } from '@bitgo-beta/statics';
|
|
7
7
|
|
|
8
8
|
function createKeychain({
|
|
9
9
|
commonKeychain,
|
|
10
10
|
commonPub,
|
|
11
11
|
encryptedPrv,
|
|
12
|
+
keyShares,
|
|
13
|
+
provider,
|
|
12
14
|
pub,
|
|
13
15
|
type,
|
|
14
16
|
}: {
|
|
15
17
|
commonKeychain?: string;
|
|
16
18
|
commonPub?: string;
|
|
17
19
|
encryptedPrv?: string;
|
|
20
|
+
keyShares?: ApiKeyShare[];
|
|
21
|
+
provider?: string;
|
|
18
22
|
pub?: string;
|
|
19
23
|
type?: KeyType;
|
|
20
24
|
}): Keychain {
|
|
@@ -23,6 +27,8 @@ function createKeychain({
|
|
|
23
27
|
commonPub,
|
|
24
28
|
encryptedPrv,
|
|
25
29
|
id: 'id',
|
|
30
|
+
keyShares,
|
|
31
|
+
provider,
|
|
26
32
|
pub: pub ?? 'pub',
|
|
27
33
|
type: type ?? 'independent',
|
|
28
34
|
};
|
|
@@ -51,27 +57,23 @@ describe('generateQrData', function () {
|
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
qrData.user.title.should.equal('A: User Key');
|
|
54
|
-
qrData.user.image.should.equal('.qrEncryptedUserKey');
|
|
55
60
|
qrData.user.description.should.equal('This is your private key, encrypted with your wallet password.');
|
|
56
61
|
qrData.user.data.should.equal(userEncryptedPrv);
|
|
57
62
|
|
|
58
63
|
assert(qrData.backup);
|
|
59
64
|
qrData.backup.title.should.equal('B: Backup Key');
|
|
60
|
-
qrData.backup.image.should.equal('.qrEncryptedBackupKey');
|
|
61
65
|
qrData.backup.description.should.equal('This is your backup private key, encrypted with your wallet password.');
|
|
62
66
|
qrData.backup.data.should.equal(backupEncryptedPrv);
|
|
63
67
|
|
|
64
68
|
assert(qrData.bitgo);
|
|
65
69
|
qrData.bitgo.title.should.equal('C: BitGo Public Key');
|
|
66
|
-
qrData.bitgo.image.should.equal('.qrBitgoKey');
|
|
67
70
|
qrData.bitgo.description.should.equal('This is the public part of the key that BitGo will use to ' +
|
|
68
71
|
'co-sign transactions\r\nwith you on your wallet.');
|
|
69
72
|
qrData.bitgo.data.should.equal(bitgoPub);
|
|
70
73
|
|
|
71
74
|
assert(qrData.passcode);
|
|
72
75
|
qrData.passcode.title.should.equal('D: Encrypted wallet Password');
|
|
73
|
-
qrData.passcode.
|
|
74
|
-
qrData.passcode.description.should.equal('This is the wallet password, encrypted client-side with a key held by\r\nBitGo.');
|
|
76
|
+
qrData.passcode.description.should.equal('This is the wallet password, encrypted client-side with a key held by BitGo.');
|
|
75
77
|
const decryptedData = decrypt(passcodeEncryptionCode, qrData.passcode.data);
|
|
76
78
|
decryptedData.should.equal(passphrase);
|
|
77
79
|
});
|
|
@@ -114,14 +116,12 @@ describe('generateQrData', function () {
|
|
|
114
116
|
});
|
|
115
117
|
|
|
116
118
|
qrData.user.title.should.equal('A: Provided User Key');
|
|
117
|
-
qrData.user.image.should.equal('.qrPublicUserKey');
|
|
118
119
|
qrData.user.description.should.equal('This is the public key you provided for your wallet.');
|
|
119
120
|
qrData.user.data.should.equal(userPub);
|
|
120
121
|
should.equal(qrData.user.publicMasterKey, userMasterKey);
|
|
121
122
|
|
|
122
123
|
assert(qrData.backup);
|
|
123
124
|
qrData.backup.title.should.equal('B: Provided Backup Key');
|
|
124
|
-
qrData.backup.image.should.equal('.qrEncryptedUserProvidedXpub');
|
|
125
125
|
qrData.backup.description.should.equal('This is the public key you provided for your wallet.');
|
|
126
126
|
qrData.backup.data.should.equal(backupPub);
|
|
127
127
|
should.equal(qrData.backup?.publicMasterKey, backupMasterKey);
|
|
@@ -138,13 +138,14 @@ describe('generateQrData', function () {
|
|
|
138
138
|
const coin = coins.get('btc');
|
|
139
139
|
const userEncryptedPrv = 'prv123encrypted';
|
|
140
140
|
const backupPub = 'pub673backup';
|
|
141
|
-
const
|
|
141
|
+
const provider = '3rd Party Provider';
|
|
142
142
|
const bitgoPub = 'pub789bitgo';
|
|
143
143
|
const qrData = generateQrData({
|
|
144
144
|
backupKeychain: createKeychain({
|
|
145
145
|
pub: backupPub,
|
|
146
|
+
provider,
|
|
146
147
|
}),
|
|
147
|
-
backupKeyProvider,
|
|
148
|
+
backupKeyProvider: provider,
|
|
148
149
|
bitgoKeychain: createKeychain({
|
|
149
150
|
pub: bitgoPub,
|
|
150
151
|
}),
|
|
@@ -158,17 +159,55 @@ describe('generateQrData', function () {
|
|
|
158
159
|
|
|
159
160
|
assert(qrData.backup);
|
|
160
161
|
qrData.backup.title.should.equal('B: Backup Key');
|
|
161
|
-
qrData.backup.image.should.equal('.qrPublicBackupKey');
|
|
162
162
|
qrData.backup.description.should.equal('This is the public key held at ' +
|
|
163
|
-
|
|
163
|
+
provider +
|
|
164
164
|
', an ' +
|
|
165
165
|
coin.name +
|
|
166
166
|
' recovery service. If you lose\r\nyour key, ' +
|
|
167
|
-
|
|
167
|
+
provider +
|
|
168
168
|
' will be able to sign transactions to recover funds.');
|
|
169
169
|
qrData.backup.data.should.equal(backupPub);
|
|
170
170
|
|
|
171
171
|
assert(qrData.bitgo);
|
|
172
172
|
qrData.bitgo.data.should.equal(bitgoPub);
|
|
173
173
|
});
|
|
174
|
+
|
|
175
|
+
it('tss backup key held at BitGo Trust', function () {
|
|
176
|
+
const coin = coins.get('btc');
|
|
177
|
+
const userEncryptedPrv = 'prv123encrypted';
|
|
178
|
+
const provider = 'BitGoTrustAsKRS';
|
|
179
|
+
const backupKeyProvider = 'BitGo Trust';
|
|
180
|
+
const userToBackupKeyShare: ApiKeyShare = { from: 'user', to: 'backup', publicShare: 'userToBackupPublic', privateShare: 'userToBackupPrivate' };
|
|
181
|
+
const bitgoToBackupKeyShare: ApiKeyShare = { from: 'bitgo', to: 'backup', publicShare: 'bitgoToBackupPublic', privateShare: 'bitgoToBackupPrivate' };
|
|
182
|
+
const bitgoCommonKeychain = 'commonCommonBitGo';
|
|
183
|
+
const qrData = generateQrData({
|
|
184
|
+
backupKeychain: createKeychain({
|
|
185
|
+
provider,
|
|
186
|
+
keyShares: [userToBackupKeyShare, bitgoToBackupKeyShare],
|
|
187
|
+
type: 'tss',
|
|
188
|
+
}),
|
|
189
|
+
backupKeyProvider,
|
|
190
|
+
bitgoKeychain: createKeychain({
|
|
191
|
+
type: 'tss',
|
|
192
|
+
commonKeychain: bitgoCommonKeychain,
|
|
193
|
+
}),
|
|
194
|
+
coin,
|
|
195
|
+
userKeychain: createKeychain({
|
|
196
|
+
encryptedPrv: userEncryptedPrv,
|
|
197
|
+
type: 'tss',
|
|
198
|
+
}),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
qrData.user.data.should.equal(userEncryptedPrv);
|
|
202
|
+
|
|
203
|
+
assert(qrData.backup);
|
|
204
|
+
qrData.backup.title.should.equal('B: Backup Key Shares');
|
|
205
|
+
qrData.backup.description.should.equal(`These are the key shares for ${backupKeyProvider}. If BitGo Inc. goes out of business,\r\ncontact ${backupKeyProvider} and they will help you recover your funds.`);
|
|
206
|
+
qrData.backup.data.should.equal(JSON.stringify([userToBackupKeyShare, bitgoToBackupKeyShare]));
|
|
207
|
+
|
|
208
|
+
assert(qrData.bitgo);
|
|
209
|
+
qrData.bitgo.title.should.equal('C: BitGo Public Key');
|
|
210
|
+
qrData.bitgo.description.should.equal('This is the public part of the key that BitGo will use to co-sign transactions\r\nwith you on your wallet.');
|
|
211
|
+
qrData.bitgo.data.should.equal(bitgoCommonKeychain);
|
|
212
|
+
});
|
|
174
213
|
});
|