@bitgo-beta/key-card 0.0.1-beta.19 → 0.0.1-beta.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitgo-beta/key-card",
3
- "version": "0.0.1-beta.19",
3
+ "version": "0.0.1-beta.20",
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,14 +33,14 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@bitgo-beta/sdk-api": "1.1.1-beta.144",
37
- "@bitgo-beta/sdk-core": "1.1.1-beta.144",
38
- "@bitgo-beta/statics": "7.0.1-beta.144",
36
+ "@bitgo-beta/sdk-api": "1.1.1-beta.145",
37
+ "@bitgo-beta/sdk-core": "1.1.1-beta.145",
38
+ "@bitgo-beta/statics": "7.0.1-beta.145",
39
39
  "jspdf": "^2.5.1",
40
40
  "qrcode": "^1.5.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/qrcode": "1.5.0"
44
44
  },
45
- "gitHead": "76b60387c8b3094508738276126156e4626f9898"
45
+ "gitHead": "66b7845703411ad8d39b044d9b6048af0499398c"
46
46
  }
@@ -4,9 +4,11 @@ import { FAQ } from './faq';
4
4
  import { QrData } from './generateQrData';
5
5
  import { splitKeys } from './utils';
6
6
 
7
- // Max for Binary/Byte Data https://en.wikipedia.org/wiki/QR_code#Standards
8
- // keys for TSS wallets aren't alphanumeric, so qrcode.react falls back to binary/byte encoding
9
- export const QRBinaryMaxLength = 2953;
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;
10
12
 
11
13
  const font = {
12
14
  header: 24,
@@ -23,6 +25,38 @@ const color = {
23
25
 
24
26
  const margin = 30;
25
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
+
26
60
  export async function drawKeycard({ activationCode, questions, keyCardImage, qrData, walletLabel }: {
27
61
  activationCode?: string;
28
62
  keyCardImage?: HTMLImageElement;
@@ -34,18 +68,13 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
34
68
  const width = 8.5 * 72;
35
69
  let y = 0;
36
70
 
37
- // Helpers for data formatting / positioning on the paper
38
- const left = (x: number) => margin + x;
39
- const moveDown = (ydelta: number) => (y += ydelta);
40
-
41
71
  // Create the PDF instance
42
-
43
72
  const doc = new jsPDF('portrait', 'pt', 'letter'); // jshint ignore:line
44
73
  doc.setFont('helvetica');
45
74
 
46
75
  // PDF Header Area - includes the logo and company name
47
76
  // This is data for the BitGo logo in the top left of the PDF
48
- moveDown(30);
77
+ y = moveDown(y, 30);
49
78
 
50
79
  if (keyCardImage) {
51
80
  doc.addImage(keyCardImage, left(0), y, 303, 40);
@@ -53,12 +82,12 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
53
82
 
54
83
  // Activation Code
55
84
  if (activationCode) {
56
- moveDown(8);
85
+ y = moveDown(y, 8);
57
86
  doc.setFontSize(font.body).setTextColor(color.gray);
58
87
  doc.text('Activation Code', left(460), y);
59
88
  }
60
89
  doc.setFontSize(font.header).setTextColor(color.black);
61
- moveDown(25);
90
+ y = moveDown(y, 25);
62
91
  doc.text('KeyCard', left(325), y - 1);
63
92
  if (activationCode) {
64
93
  doc.setFontSize(font.header).setTextColor(color.gray);
@@ -68,25 +97,25 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
68
97
  // Subheader
69
98
  // titles
70
99
  const date = new Date().toDateString();
71
- moveDown(margin);
100
+ y = moveDown(y, margin);
72
101
  doc.setFontSize(font.body).setTextColor(color.gray);
73
102
  doc.text('Created on ' + date + ' for wallet named:', left(0), y);
74
103
  // copy
75
- moveDown(25);
104
+ y = moveDown(y, 25);
76
105
  doc.setFontSize(font.subheader).setTextColor(color.black);
77
106
  doc.text(walletLabel, left(0), y);
78
107
  // Red Bar
79
- moveDown(20);
108
+ y = moveDown(y, 20);
80
109
  doc.setFillColor(255, 230, 230);
81
110
  doc.rect(left(0), y, width - 2 * margin, 32, 'F');
82
111
 
83
112
  // warning message
84
- moveDown(20);
113
+ y = moveDown(y, 20);
85
114
  doc.setFontSize(font.body).setTextColor(color.red);
86
115
  doc.text('Print this document, or keep it securely offline. See below for FAQ.', left(75), y);
87
116
 
88
117
  // Generate the first page's data for the backup PDF
89
- moveDown(35);
118
+ y = moveDown(y, 35);
90
119
  const qrSize = 130;
91
120
 
92
121
  const qrKeys = ['user', 'passcode', 'backup', 'bitgo'];
@@ -105,45 +134,35 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
105
134
  const textLeft = left(qrSize + 15);
106
135
  let textHeight = 0;
107
136
 
108
- if (qr.data.length <= QRBinaryMaxLength) {
109
- const image = await QRCode.toCanvas(qr.data, { errorCorrectionLevel: 'L' });
110
- doc.addImage(image, left(0), y, qrSize, qrSize);
111
- } else {
112
- // key is too long for one QR code
113
- const keys = splitKeys(qr.data, QRBinaryMaxLength);
114
- for (let keyIndex = 0; keyIndex < keys.length; keyIndex++) {
115
- const image = await QRCode.toCanvas(keys[keyIndex], { errorCorrectionLevel: 'L' });
116
- doc.addImage(image, left(0), y, qrSize, qrSize);
117
- const textBuffer = 15;
118
- moveDown(qrSize + textBuffer);
119
- doc.setFontSize(font.body).setTextColor(color.black);
120
- doc.text('Part ' + (keyIndex + 1).toString(), left(0), y);
121
- moveDown(20);
122
- }
123
- y = topY;
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' }));
124
141
  }
125
142
 
143
+ let nextQrIndex = drawOnePageOfQrCodes(qrImages, doc, y, qrSize, 0);
144
+
126
145
  doc.setFontSize(font.subheader).setTextColor(color.black);
127
- moveDown(10);
146
+ y = moveDown(y, 10);
128
147
  textHeight += 10;
129
148
  doc.text(qr.title, textLeft, y);
130
149
  textHeight += doc.getLineHeight();
131
- moveDown(15);
150
+ y = moveDown(y, 15);
132
151
  textHeight += 15;
133
152
  doc.setFontSize(font.body).setTextColor(color.darkgray);
134
153
  doc.text(qr.description, textLeft, y);
135
154
  textHeight += doc.getLineHeight();
136
155
  doc.setFontSize(font.body - 2);
137
156
  if (qr?.data?.length > QRBinaryMaxLength) {
138
- moveDown(30);
157
+ y = moveDown(y, 30);
139
158
  textHeight += 30;
140
- doc.text('Note: you will need to put Part 1 and Part 2 together for the full key', textLeft, y);
159
+ doc.text('Note: you will need to put all Parts together for the full key', textLeft, y);
141
160
  }
142
- moveDown(30);
161
+ y = moveDown(y, 30);
143
162
  textHeight += 30;
144
163
  doc.text('Data:', textLeft, y);
145
164
  textHeight += doc.getLineHeight();
146
- moveDown(15);
165
+ y = moveDown(y, 15);
147
166
  textHeight += 15;
148
167
  const width = 72 * 8.5 - textLeft - 30;
149
168
  doc.setFont('courier').setFontSize(9).setTextColor(color.black);
@@ -156,10 +175,12 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
156
175
  textHeight = 0;
157
176
  y = 30;
158
177
  topY = y;
178
+ nextQrIndex = drawOnePageOfQrCodes(qrImages, doc, y, qrSize, nextQrIndex);
179
+ doc.setFont('courier').setFontSize(9).setTextColor(color.black);
159
180
  }
160
181
  doc.text(lines[line], textLeft, y);
161
182
  if (line !== lines.length - 1) {
162
- moveDown(buffer);
183
+ y = moveDown(y, buffer);
163
184
  textHeight += buffer;
164
185
  }
165
186
  }
@@ -169,7 +190,7 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
169
190
  const text = 'Key Id: ' + qr.publicMasterKey;
170
191
 
171
192
  // Gray bar
172
- moveDown(20);
193
+ y = moveDown(y, 20);
173
194
  textHeight += 20;
174
195
  doc.setFillColor(247, 249, 249); // Gray background
175
196
  doc.setDrawColor(0, 0, 0); // Border
@@ -187,12 +208,9 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
187
208
  doc.setFont('helvetica');
188
209
  // Move down the size of the QR code minus accumulated height on the right side, plus margin
189
210
  // if we have a key that spans multiple pages, then exclude QR code size
190
- let rowHeight = Math.max(qrSize, textHeight);
191
- if (qr?.data?.length > QRBinaryMaxLength) {
192
- rowHeight = textHeight;
193
- }
211
+ const rowHeight = Math.max(qr.data.length > QRBinaryMaxLength ? qrSize + 20 : qrSize, textHeight);
194
212
  const marginBottom = 15;
195
- moveDown(rowHeight - (y - topY) + marginBottom);
213
+ y = moveDown(y, rowHeight - (y - topY) + marginBottom);
196
214
  }
197
215
 
198
216
  // Add next Page
@@ -200,22 +218,22 @@ export async function drawKeycard({ activationCode, questions, keyCardImage, qrD
200
218
 
201
219
  // next pages title
202
220
  y = 0;
203
- moveDown(55);
221
+ y = moveDown(y, 55);
204
222
  doc.setFontSize(font.header).setTextColor(color.black);
205
223
  doc.text('BitGo KeyCard FAQ', left(0), y);
206
224
 
207
225
  // Generate the second page's data for the backup PDF
208
- moveDown(30);
226
+ y = moveDown(y, 30);
209
227
  questions.forEach(function (q) {
210
228
  doc.setFontSize(font.subheader).setTextColor(color.black);
211
229
  doc.text(q.question, left(0), y);
212
- moveDown(20);
230
+ y = moveDown(y, 20);
213
231
  doc.setFontSize(font.body).setTextColor(color.darkgray);
214
232
  q.answer.forEach(function (line) {
215
233
  doc.text(line, left(0), y);
216
- moveDown(font.body + 3);
234
+ y = moveDown(y, font.body + 3);
217
235
  });
218
- moveDown(22);
236
+ y = moveDown(y, 22);
219
237
  });
220
238
 
221
239
  return doc;
@@ -90,13 +90,13 @@ function generateBackupQrData(coin: Readonly<BaseCoin>, backupKeychain: Keychain
90
90
  }
91
91
 
92
92
  if (backupKeyProvider === 'BitGo Trust' && backupKeychain.type === 'tss') {
93
- const userToBackupShare = backupKeychain.keyShares?.find((keyShare) => keyShare.from === 'user' && keyShare.to === 'backup');
94
- assert(userToBackupShare);
93
+ const keyShares = backupKeychain.keyShares?.filter((keyShare) => keyShare.to === 'backup');
94
+ assert(keyShares?.length === 2);
95
95
  return {
96
- title: 'B: User To Backup Key Share',
97
- description: `This is the key share from you for ${backupKeyProvider}. If BitGo Inc goes out of\r\nbusiness,` +
98
- ` contact ${backupKeyProvider} and they will help you recover your funds.`,
99
- data: JSON.stringify(userToBackupShare),
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
100
  };
101
101
  }
102
102
 
@@ -120,24 +120,7 @@ function generateBackupQrData(coin: Readonly<BaseCoin>, backupKeychain: Keychain
120
120
  };
121
121
  }
122
122
 
123
- function generateBitGoQrData(bitgoKeychain: Keychain, {
124
- backupKeychain,
125
- backupKeyProvider,
126
- }: {
127
- backupKeychain?: Keychain,
128
- backupKeyProvider?: string;
129
- }): QrDataEntry {
130
- if (backupKeyProvider === 'BitGo Trust' && backupKeychain?.type === 'tss') {
131
- const bitgoToBackupShare = backupKeychain.keyShares?.find((keyShare) => keyShare.from === 'bitgo' && keyShare.to === 'backup');
132
- assert(bitgoToBackupShare);
133
- return {
134
- title: 'C: BitGo To Backup Key Share',
135
- description: `This is the key share from BitGo Inc for ${backupKeyProvider}. If BitGo Inc goes out of\r\nbusiness,` +
136
- ` contact ${backupKeyProvider} and they will help you recover your funds.`,
137
- data: JSON.stringify(bitgoToBackupShare),
138
- };
139
- }
140
-
123
+ function generateBitGoQrData(bitgoKeychain: Keychain): QrDataEntry {
141
124
  const bitgoData = getPubFromKey(bitgoKeychain);
142
125
  assert(bitgoData);
143
126
 
@@ -167,10 +150,7 @@ export function generateQrData({
167
150
  backupKeyProvider,
168
151
  backupMasterKey,
169
152
  }),
170
- bitgo: generateBitGoQrData(bitgoKeychain, {
171
- backupKeychain,
172
- backupKeyProvider,
173
- }),
153
+ bitgo: generateBitGoQrData(bitgoKeychain),
174
154
  };
175
155
 
176
156
  if (passphrase && passcodeEncryptionCode) {
@@ -179,6 +179,7 @@ describe('generateQrData', function () {
179
179
  const backupKeyProvider = 'BitGo Trust';
180
180
  const userToBackupKeyShare: ApiKeyShare = { from: 'user', to: 'backup', publicShare: 'userToBackupPublic', privateShare: 'userToBackupPrivate' };
181
181
  const bitgoToBackupKeyShare: ApiKeyShare = { from: 'bitgo', to: 'backup', publicShare: 'bitgoToBackupPublic', privateShare: 'bitgoToBackupPrivate' };
182
+ const bitgoCommonKeychain = 'commonCommonBitGo';
182
183
  const qrData = generateQrData({
183
184
  backupKeychain: createKeychain({
184
185
  provider,
@@ -188,6 +189,7 @@ describe('generateQrData', function () {
188
189
  backupKeyProvider,
189
190
  bitgoKeychain: createKeychain({
190
191
  type: 'tss',
192
+ commonKeychain: bitgoCommonKeychain,
191
193
  }),
192
194
  coin,
193
195
  userKeychain: createKeychain({
@@ -199,13 +201,13 @@ describe('generateQrData', function () {
199
201
  qrData.user.data.should.equal(userEncryptedPrv);
200
202
 
201
203
  assert(qrData.backup);
202
- qrData.backup.title.should.equal('B: User To Backup Key Share');
203
- qrData.backup.description.should.equal(`This is the key share from you for ${backupKeyProvider}. If BitGo Inc goes out of\r\nbusiness, contact ${backupKeyProvider} and they will help you recover your funds.`);
204
- qrData.backup.data.should.equal(JSON.stringify(userToBackupKeyShare));
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]));
205
207
 
206
208
  assert(qrData.bitgo);
207
- qrData.bitgo.title.should.equal('C: BitGo To Backup Key Share');
208
- qrData.bitgo.description.should.equal(`This is the key share from BitGo Inc for ${backupKeyProvider}. If BitGo Inc goes out of\r\nbusiness, contact ${backupKeyProvider} and they will help you recover your funds.`);
209
- qrData.bitgo.data.should.equal(JSON.stringify(bitgoToBackupKeyShare));
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);
210
212
  });
211
213
  });