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

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.
@@ -1,222 +0,0 @@
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://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;
10
-
11
- const font = {
12
- header: 24,
13
- subheader: 15,
14
- body: 12,
15
- };
16
-
17
- const color = {
18
- black: '#000000',
19
- darkgray: '#4c4c4c',
20
- gray: '#9b9b9b',
21
- red: '#e21e1e',
22
- };
23
-
24
- const margin = 30;
25
-
26
- export async function drawKeycard({ activationCode, questions, keyCardImage, qrData, walletLabel }: {
27
- activationCode?: string;
28
- keyCardImage?: HTMLImageElement;
29
- qrData: QrData;
30
- questions: FAQ[];
31
- walletLabel: string;
32
- }): Promise<jsPDF> {
33
- // document details
34
- const width = 8.5 * 72;
35
- let y = 0;
36
-
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
- // Create the PDF instance
42
-
43
- const doc = new jsPDF('portrait', 'pt', 'letter'); // jshint ignore:line
44
- doc.setFont('helvetica');
45
-
46
- // PDF Header Area - includes the logo and company name
47
- // This is data for the BitGo logo in the top left of the PDF
48
- moveDown(30);
49
-
50
- if (keyCardImage) {
51
- doc.addImage(keyCardImage, left(0), y, 303, 40);
52
- }
53
-
54
- // Activation Code
55
- if (activationCode) {
56
- moveDown(8);
57
- doc.setFontSize(font.body).setTextColor(color.gray);
58
- doc.text('Activation Code', left(460), y);
59
- }
60
- doc.setFontSize(font.header).setTextColor(color.black);
61
- moveDown(25);
62
- doc.text('KeyCard', left(325), y - 1);
63
- if (activationCode) {
64
- doc.setFontSize(font.header).setTextColor(color.gray);
65
- doc.text(activationCode, left(460), y);
66
- }
67
-
68
- // Subheader
69
- // titles
70
- const date = new Date().toDateString();
71
- moveDown(margin);
72
- doc.setFontSize(font.body).setTextColor(color.gray);
73
- doc.text('Created on ' + date + ' for wallet named:', left(0), y);
74
- // copy
75
- moveDown(25);
76
- doc.setFontSize(font.subheader).setTextColor(color.black);
77
- doc.text(walletLabel, left(0), y);
78
- // Red Bar
79
- moveDown(20);
80
- doc.setFillColor(255, 230, 230);
81
- doc.rect(left(0), y, width - 2 * margin, 32, 'F');
82
-
83
- // warning message
84
- moveDown(20);
85
- doc.setFontSize(font.body).setTextColor(color.red);
86
- doc.text('Print this document, or keep it securely offline. See below for FAQ.', left(75), y);
87
-
88
- // Generate the first page's data for the backup PDF
89
- moveDown(35);
90
- const qrSize = 130;
91
-
92
- const qrKeys = ['user', 'passcode', 'backup', 'bitgo'];
93
- for (let index = 0; index < qrKeys.length; index++) {
94
- const name = qrKeys[index];
95
- if (index === 2) {
96
- // Add 2nd Page
97
- doc.addPage();
98
-
99
- // 2nd page title
100
- y = 30;
101
- }
102
-
103
- const qr = qrData[name];
104
- let topY = y;
105
- const textLeft = left(qrSize + 15);
106
- let textHeight = 0;
107
-
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;
124
- }
125
-
126
- doc.setFontSize(font.subheader).setTextColor(color.black);
127
- moveDown(10);
128
- textHeight += 10;
129
- doc.text(qr.title, textLeft, y);
130
- textHeight += doc.getLineHeight();
131
- moveDown(15);
132
- textHeight += 15;
133
- doc.setFontSize(font.body).setTextColor(color.darkgray);
134
- doc.text(qr.description, textLeft, y);
135
- textHeight += doc.getLineHeight();
136
- doc.setFontSize(font.body - 2);
137
- if (qr?.data?.length > QRBinaryMaxLength) {
138
- moveDown(30);
139
- textHeight += 30;
140
- doc.text('Note: you will need to put Part 1 and Part 2 together for the full key', textLeft, y);
141
- }
142
- moveDown(30);
143
- textHeight += 30;
144
- doc.text('Data:', textLeft, y);
145
- textHeight += doc.getLineHeight();
146
- moveDown(15);
147
- textHeight += 15;
148
- const width = 72 * 8.5 - textLeft - 30;
149
- doc.setFont('courier').setFontSize(9).setTextColor(color.black);
150
- const lines = doc.splitTextToSize(qr.data, width);
151
- const buffer = 10;
152
- for (let line = 0; line < lines.length; line++) {
153
- // add new page if data does not fit on one page
154
- if (y + buffer >= doc.internal.pageSize.getHeight()) {
155
- doc.addPage();
156
- textHeight = 0;
157
- y = 30;
158
- topY = y;
159
- }
160
- doc.text(lines[line], textLeft, y);
161
- if (line !== lines.length - 1) {
162
- moveDown(buffer);
163
- textHeight += buffer;
164
- }
165
- }
166
-
167
- // Add public key if exists
168
- if (qr.publicMasterKey) {
169
- const text = 'Key Id: ' + qr.publicMasterKey;
170
-
171
- // Gray bar
172
- moveDown(20);
173
- textHeight += 20;
174
- doc.setFillColor(247, 249, 249); // Gray background
175
- doc.setDrawColor(0, 0, 0); // Border
176
-
177
- // Leave a bit of space for the side of the rectangle.
178
- const splitKeyId = doc.splitTextToSize(text, width - 10);
179
-
180
- // The height of the box must be at least 15 px (for single line case), or
181
- // a multiple of 13 for each line. This allows for proper padding.
182
- doc.rect(textLeft, y, width, Math.max(12 * splitKeyId.length, 15), 'FD');
183
- textHeight += splitKeyId.length * doc.getLineHeight();
184
- doc.text(splitKeyId, textLeft + 5, y + 10);
185
- }
186
-
187
- doc.setFont('helvetica');
188
- // Move down the size of the QR code minus accumulated height on the right side, plus margin
189
- // 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
- }
194
- const marginBottom = 15;
195
- moveDown(rowHeight - (y - topY) + marginBottom);
196
- }
197
-
198
- // Add next Page
199
- doc.addPage();
200
-
201
- // next pages title
202
- y = 0;
203
- moveDown(55);
204
- doc.setFontSize(font.header).setTextColor(color.black);
205
- doc.text('BitGo KeyCard FAQ', left(0), y);
206
-
207
- // Generate the second page's data for the backup PDF
208
- moveDown(30);
209
- questions.forEach(function (q) {
210
- doc.setFontSize(font.subheader).setTextColor(color.black);
211
- doc.text(q.question, left(0), y);
212
- moveDown(20);
213
- doc.setFontSize(font.body).setTextColor(color.darkgray);
214
- q.answer.forEach(function (line) {
215
- doc.text(line, left(0), y);
216
- moveDown(font.body + 3);
217
- });
218
- moveDown(22);
219
- });
220
-
221
- return doc;
222
- }
package/src/faq.ts DELETED
@@ -1,82 +0,0 @@
1
- export interface FAQ {
2
- question: string;
3
- // the answer to the question, already split into individual lines of text
4
- answer: string[];
5
- }
6
-
7
- export function generateFaq(coinName: string): FAQ[] {
8
- const sectionACoinSpecific =
9
- `The KeyCard contains important information which can be used to recover the ${coinName} `;
10
- const sectionBCoinSpecific =
11
- `If you have lost or damaged all copies of your KeyCard, your ${coinName} is still safe, but this `;
12
- const sectionCCoinSpecific =
13
- `source tools at https://github.com/bitgo to recover your ${coinName}. Note: You should never enter `;
14
- const sectionDCoinSpecific =
15
- `your KeyCard will be able to steal your ${coinName}. We recommend keeping your wallet password `;
16
-
17
- return [
18
- {
19
- question: 'What is the KeyCard?',
20
- answer: [
21
- sectionACoinSpecific,
22
- 'from your wallet in several situations. Each BitGo wallet has its own, unique KeyCard. ',
23
- 'If you have created multiple wallets, you should retain the KeyCard for each of them.',
24
- ],
25
- },
26
- {
27
- question: 'What should I do with it?',
28
- answer: [
29
- 'You should print the KeyCard and/or save the PDF to an offline storage device. The print-out ',
30
- "or USB stick should be kept in a safe place, such as a bank vault or home safe. It's a good idea ",
31
- 'to keep a second copy in a different location.',
32
- '',
33
- "Important: If you haven't provided an external backup key, then the original PDF should be ",
34
- 'deleted from any machine where the wallet will be regularly accessed to prevent malware from ',
35
- 'capturing both the KeyCard and your wallet passcode.',
36
- ],
37
- },
38
- {
39
- question: 'What should I do if I lose it?',
40
- answer: [
41
- sectionBCoinSpecific,
42
- 'Wallet should be considered at risk for loss. As soon as is convenient, you should use BitGo ',
43
- 'to empty the wallet into a new wallet and discontinue use of the old wallet.',
44
- ],
45
- },
46
- {
47
- question: 'What if someone sees my KeyCard?',
48
- answer: [
49
- "Don't panic! All sensitive information on the KeyCard is encrypted with your passcode, or with a",
50
- 'key which only BitGo has. But, in general, you should make best efforts to keep your ',
51
- 'KeyCard private. If your KeyCard does get exposed or copied in a way that makes you ',
52
- 'uncomfortable, the best course of action is to empty the corresponding wallet into another ',
53
- 'wallet and discontinue use of the old wallet.',
54
- ],
55
- },
56
- {
57
- question: 'What if I forget or lose my wallet password?',
58
- answer: [
59
- 'BitGo can use the information in QR Code D to help you recover access to your wallet. ',
60
- 'Without the KeyCard, BitGo is not able to recover funds from a wallet with a lost password.',
61
- ],
62
- },
63
- {
64
- question: 'What if BitGo becomes inaccessible for an extended period?',
65
- answer: [
66
- "Your KeyCard and wallet passcode can be used together with BitGo's published open ",
67
- sectionCCoinSpecific,
68
- 'information from your KeyCard into tools other than the tools BitGo has published, or your ',
69
- 'funds may be at risk for theft.',
70
- ],
71
- },
72
- {
73
- question: 'Should I write my wallet password on my KeyCard?',
74
- answer: [
75
- "No! BitGo's signature scheme approach to security depends on there not being a single point ",
76
- 'of attack. But if your wallet password is on your KeyCard, then anyone who gains access to ',
77
- sectionDCoinSpecific,
78
- 'safe in a secure password manager such as LastPass, 1Password or KeePass.',
79
- ],
80
- },
81
- ];
82
- }
@@ -1,187 +0,0 @@
1
- import { BaseCoin } from '@bitgo-beta/statics';
2
- import { Keychain } from '@bitgo-beta/sdk-core';
3
- import { encrypt } from '@bitgo-beta/sdk-api';
4
- import * as assert from 'assert';
5
-
6
- export interface GenerateQrDataParams {
7
- // The backup keychain as it is returned from the BitGo API upon creation
8
- backupKeychain: Keychain,
9
- // The name of the 3rd party provider of the backup key if neither the user nor BitGo stores it
10
- backupKeyProvider?: string;
11
- // The key id of the backup key, only used for cold keys
12
- backupMasterKey?: string;
13
- // The BitGo keychain as it is returned from the BitGo API upon creation
14
- bitgoKeychain: Keychain,
15
- // The coin of the wallet that was/ is about to be created
16
- coin: Readonly<BaseCoin>;
17
- // A code that can be used to encrypt the wallet password to.
18
- // If both the passphrase and passcodeEncryptionCode are passed, then this code encrypts the passphrase with the
19
- // passcodeEncryptionCode and puts the result into Box D. Allows recoveries of the wallet password.
20
- passcodeEncryptionCode?: string;
21
- // The wallet password
22
- // If both the passphrase and passcodeEncryptionCode are passed, then this code encrypts the passphrase with the
23
- // passcodeEncryptionCode and puts the result into Box D. Allows recoveries of the wallet password.
24
- passphrase?: string;
25
- // The user keychain as it is returned from the BitGo API upon creation
26
- userKeychain: Keychain;
27
- // The key id of the user key, only used for cold keys
28
- userMasterKey?: string;
29
- }
30
-
31
- interface QrDataEntry {
32
- data: string;
33
- description: string;
34
- title: string;
35
- publicMasterKey?: string;
36
- }
37
-
38
- export interface QrData {
39
- backup?: QrDataEntry;
40
- bitgo?: QrDataEntry;
41
- passcode?: QrDataEntry;
42
- user: QrDataEntry;
43
- }
44
-
45
- function getPubFromKey(key: Keychain): string | undefined {
46
- switch (key.type) {
47
- case 'blsdkg':
48
- return key.commonPub;
49
- case 'tss':
50
- return key.commonKeychain;
51
- case 'independent':
52
- return key.pub;
53
- }
54
- }
55
-
56
- function generateUserQrData(userKeychain: Keychain, userMasterKey?: string): QrDataEntry {
57
- if (userKeychain.encryptedPrv) {
58
- return {
59
- title: 'A: User Key',
60
- description: 'This is your private key, encrypted with your wallet password.',
61
- data: userKeychain.encryptedPrv,
62
- };
63
- }
64
-
65
- const pub = getPubFromKey(userKeychain);
66
- assert(pub);
67
-
68
- return {
69
- title: 'A: Provided User Key',
70
- description: 'This is the public key you provided for your wallet.',
71
- data: pub,
72
- publicMasterKey: userMasterKey,
73
- };
74
- }
75
-
76
- function generateBackupQrData(coin: Readonly<BaseCoin>, backupKeychain: Keychain, {
77
- backupKeyProvider,
78
- backupMasterKey,
79
- }: {
80
- backupKeyProvider?: string;
81
- backupMasterKey?: string;
82
- } = {}): QrDataEntry {
83
- const title = 'B: Backup Key';
84
- if (backupKeychain.encryptedPrv) {
85
- return {
86
- title,
87
- description: 'This is your backup private key, encrypted with your wallet password.',
88
- data: backupKeychain.encryptedPrv,
89
- };
90
- }
91
-
92
- if (backupKeyProvider === 'BitGo Trust' && backupKeychain.type === 'tss') {
93
- const userToBackupShare = backupKeychain.keyShares?.find((keyShare) => keyShare.from === 'user' && keyShare.to === 'backup');
94
- assert(userToBackupShare);
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),
100
- };
101
- }
102
-
103
- const pub = getPubFromKey(backupKeychain);
104
- assert(pub);
105
-
106
- if (backupKeyProvider) {
107
- return {
108
- title: 'B: Backup Key',
109
- description: `This is the public key held at ${backupKeyProvider}, an ${coin.name} recovery service. ` +
110
- `If you lose\r\nyour key, ${backupKeyProvider} will be able to sign transactions to recover funds.`,
111
- data: pub,
112
- };
113
- }
114
-
115
- return {
116
- title: 'B: Provided Backup Key',
117
- description: 'This is the public key you provided for your wallet.',
118
- data: pub,
119
- publicMasterKey: backupMasterKey,
120
- };
121
- }
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
-
141
- const bitgoData = getPubFromKey(bitgoKeychain);
142
- assert(bitgoData);
143
-
144
- return {
145
- title: 'C: BitGo Public Key',
146
- description:
147
- 'This is the public part of the key that BitGo will use to ' +
148
- 'co-sign transactions\r\nwith you on your wallet.',
149
- data: bitgoData,
150
- };
151
- }
152
-
153
- export function generateQrData({
154
- backupKeychain,
155
- backupKeyProvider,
156
- backupMasterKey,
157
- bitgoKeychain,
158
- coin,
159
- passcodeEncryptionCode,
160
- passphrase,
161
- userKeychain,
162
- userMasterKey,
163
- }: GenerateQrDataParams): QrData {
164
- const qrData: QrData = {
165
- user: generateUserQrData(userKeychain, userMasterKey),
166
- backup: generateBackupQrData(coin, backupKeychain, {
167
- backupKeyProvider,
168
- backupMasterKey,
169
- }),
170
- bitgo: generateBitGoQrData(bitgoKeychain, {
171
- backupKeychain,
172
- backupKeyProvider,
173
- }),
174
- };
175
-
176
- if (passphrase && passcodeEncryptionCode) {
177
- const encryptedWalletPasscode = encrypt(passcodeEncryptionCode, passphrase);
178
-
179
- qrData.passcode = {
180
- title: 'D: Encrypted wallet Password',
181
- description: 'This is the wallet password, encrypted client-side with a key held by BitGo.',
182
- data: encryptedWalletPasscode,
183
- };
184
- }
185
-
186
- return qrData;
187
- }
package/src/index.ts DELETED
@@ -1,22 +0,0 @@
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';
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
- }
package/src/utils.ts DELETED
@@ -1,15 +0,0 @@
1
- export function splitKeys(key: string, limit: number): string[] {
2
- if (key.length <= limit) {
3
- return [key];
4
- }
5
-
6
- const keys: string[] = [];
7
-
8
- let rightIndex = limit;
9
- for (let i = 0; i < key.length; i += limit) {
10
- keys.push(key.substring(i, rightIndex));
11
- rightIndex = Math.min(rightIndex + limit, key.length);
12
- }
13
-
14
- return keys;
15
- }
package/test/unit/faq.ts DELETED
@@ -1,14 +0,0 @@
1
- import { generateFaq } from '../../src/faq';
2
-
3
- describe('generateFaq', function () {
4
- it('generates faq with filled in coin name', function () {
5
- const coinName = 'fooCoin';
6
- const questions = generateFaq(coinName);
7
-
8
- questions.length.should.equal(7);
9
- questions[0].answer[0].should.match(new RegExp(coinName));
10
- questions[2].answer[0].should.match(new RegExp(coinName));
11
- questions[5].answer[1].should.match(new RegExp(coinName));
12
- questions[6].answer[2].should.match(new RegExp(coinName));
13
- });
14
- });