-
Notifications
You must be signed in to change notification settings - Fork 66
/
crypto.service.ts
144 lines (119 loc) · 4.59 KB
/
crypto.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { Injectable } from '@angular/core';
import HDNode from 'hdkey';
import * as bip39 from 'bip39';
import HDKey from 'hdkey';
import {ec as EC} from 'elliptic';
import bs58check from 'bs58check';
import {CookieService} from 'ngx-cookie';
import {createHmac, createCipher, createDecipher, randomBytes} from 'crypto';
import {AccessLevel, Network} from '../types/identity';
@Injectable({
providedIn: 'root'
})
export class CryptoService {
constructor(private cookieService: CookieService) {
}
static PUBLIC_KEY_PREFIXES = {
mainnet: {
bitcoin: [0x00],
bitclout: [0xcd, 0x14, 0x0],
},
testnet: {
bitcoin: [0x6f],
bitclout: [0x11, 0xc2, 0x0],
}
};
// Safari only lets us store things in cookies
mustUseStorageAccess(): boolean {
return typeof document.hasStorageAccess === 'function';
}
// 32 bytes = 256 bits is plenty of entropy for encryption
newEncryptionKey(): string {
return randomBytes(32).toString('hex');
}
seedHexEncryptionStorageKey(hostname: string): string {
return `seed-hex-key-${hostname}`;
}
hasSeedHexEncryptionKey(hostname: string): boolean {
const storageKey = this.seedHexEncryptionStorageKey(hostname);
if (this.mustUseStorageAccess()) {
return !!this.cookieService.get(storageKey);
} else {
return !!localStorage.getItem(storageKey);
}
}
seedHexEncryptionKey(hostname: string): string {
const storageKey = this.seedHexEncryptionStorageKey(hostname);
let encryptionKey;
if (this.mustUseStorageAccess()) {
encryptionKey = this.cookieService.get(storageKey);
if (!encryptionKey) {
encryptionKey = this.newEncryptionKey();
this.cookieService.put(storageKey, encryptionKey, {
expires: new Date('2100/01/01 00:00:00'),
});
}
} else {
encryptionKey = localStorage.getItem(storageKey) || '';
if (!encryptionKey) {
encryptionKey = this.newEncryptionKey();
localStorage.setItem(storageKey, encryptionKey);
}
}
// If the encryption key is unset or malformed we need to stop
// everything to avoid returning unencrypted information.
if (!encryptionKey || encryptionKey.length !== 64) {
throw new Error('Failed to load or generate encryption key; this should never happen');
}
return encryptionKey;
}
encryptSeedHex(seedHex: string, hostname: string): string {
const encryptionKey = this.seedHexEncryptionKey(hostname);
const cipher = createCipher('aes-256-gcm', encryptionKey);
return cipher.update(seedHex).toString('hex');
}
decryptSeedHex(encryptedSeedHex: string, hostname: string): string {
const encryptionKey = this.seedHexEncryptionKey(hostname);
const decipher = createDecipher('aes-256-gcm', encryptionKey);
return decipher.update(Buffer.from(encryptedSeedHex, 'hex')).toString();
}
accessLevelHmac(accessLevel: AccessLevel, seedHex: string): string {
const hmac = createHmac('sha256', seedHex);
return hmac.update(accessLevel.toString()).digest().toString('hex');
}
validAccessLevelHmac(accessLevel: AccessLevel, seedHex: string, hmac: string): boolean {
if (!hmac || !seedHex) {
return false;
}
return hmac === this.accessLevelHmac(accessLevel, seedHex);
}
encryptedSeedHexToPrivateKey(encryptedSeedHex: string, domain: string): EC.KeyPair {
const seedHex = this.decryptSeedHex(encryptedSeedHex, domain);
return this.seedHexToPrivateKey(seedHex);
}
mnemonicToKeychain(mnemonic: string, extraText?: string, nonStandard?: boolean): HDNode {
const seed = bip39.mnemonicToSeedSync(mnemonic, extraText);
// @ts-ignore
return HDKey.fromMasterSeed(seed).derive('m/44\'/0\'/0\'/0/0', nonStandard);
}
keychainToSeedHex(keychain: HDNode): string {
return keychain.privateKey.toString('hex');
}
seedHexToPrivateKey(seedHex: string): EC.KeyPair {
const ec = new EC('secp256k1');
return ec.keyFromPrivate(seedHex);
}
privateKeyToBitcloutPublicKey(privateKey: EC.KeyPair, network: Network): string {
const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].bitclout;
const key = privateKey.getPublic().encode('array', true);
const prefixAndKey = Uint8Array.from([...prefix, ...key]);
return bs58check.encode(prefixAndKey);
}
keychainToBtcAddress(keychain: HDNode, network: Network): string {
const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].bitcoin;
// @ts-ignore TODO: add "identifier" to type definition
const identifier = keychain.identifier;
const prefixAndKey = Uint8Array.from([...prefix, ...identifier]);
return bs58check.encode(prefixAndKey);
}
}