1 import type { PrivateKeyReference, PublicKeyReference } from '@proton/crypto/lib';
2 import { CryptoProxy, VERIFICATION_STATUS } from '@proton/crypto/lib';
6 generateKey as generateAesGcmKey,
7 importKey as importAesGcmKey,
8 } from '@proton/crypto/lib/subtle/aesGcm';
9 import { signData as computeHmacSignature, importKey as importHmacKey } from '@proton/crypto/lib/subtle/hmac';
10 import { stringToUtf8Array } from '@proton/crypto/lib/utils';
11 import { base64StringToUint8Array, uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
12 import type { DecryptedKey } from '@proton/shared/lib/interfaces';
14 export enum WalletSignatureContextEnum {
15 WALLET_KEY = 'wallet.key',
16 BITCOIN_ADDRESS = 'wallet.bitcoin-address',
19 export type WalletSignatureContext = `${WalletSignatureContextEnum}`;
21 const encoder = new TextEncoder();
22 const decoder = new TextDecoder();
24 export type EncryptedWalletPart = Partial<
25 { mnemonic: string; publicKey: undefined } | { mnemonic: undefined; publicKey: string }
28 export const decryptTextData = async (armoredMessage: string, keys: PrivateKeyReference[]) => {
29 const { data } = await CryptoProxy.decryptMessage({
32 verificationKeys: keys,
38 type DecryptReturnType<T extends 'binary' | 'utf8'> = T extends 'binary' ? Uint8Array : string;
40 export const decryptPgp = async <T extends 'binary' | 'utf8'>(
41 armoredMessage: string,
43 keys: PrivateKeyReference[]
44 ): Promise<DecryptReturnType<T>> => {
45 const { data } = await CryptoProxy.decryptMessage({
48 verificationKeys: keys,
52 return data as DecryptReturnType<T>;
55 const isString = (data: string | Uint8Array): data is string => typeof data === 'string';
57 export const encryptPgp = async <T extends string | Uint8Array>(
59 keys: PublicKeyReference[],
60 signingKeys?: PrivateKeyReference[]
70 message = await CryptoProxy.encryptMessage({
73 }).then(({ message }) => message);
75 message = await CryptoProxy.encryptMessage({
76 binaryData: data as Uint8Array,
78 }).then(({ message }) => message);
84 export const signData = async <T extends string | Uint8Array>(
86 context: WalletSignatureContext,
87 keys: PrivateKeyReference[]
93 context: { critical: true, value: context },
98 signature = await CryptoProxy.signMessage({
103 signature = await CryptoProxy.signMessage({
104 binaryData: data as Uint8Array,
112 export const verifySignedData = async <T extends string | Uint8Array>(
115 context: WalletSignatureContext,
116 keys: (PrivateKeyReference | PublicKeyReference)[]
119 armoredSignature: signature,
120 verificationKeys: keys,
121 context: { required: true, value: context },
124 if (isString(data)) {
125 return CryptoProxy.verifyMessage({
128 }).then(({ verified }) => verified === VERIFICATION_STATUS.SIGNED_AND_VALID);
130 return CryptoProxy.verifyMessage({
131 binaryData: data as Uint8Array,
133 }).then(({ verified }) => verified === VERIFICATION_STATUS.SIGNED_AND_VALID);
137 const decryptArmoredWalletKey = async (walletKey: string, walletKeySignature: string, keys: DecryptedKey[]) => {
138 const decryptedEntropy = await decryptPgp(
141 keys.map((k) => k.privateKey)
144 const isKeyVerified = await verifySignedData(
148 keys.map((k) => k.publicKey)
151 if (!isKeyVerified) {
152 throw new Error('Key could not be verified');
155 return decryptedEntropy;
158 export const decryptWalletKey = async (walletKey: string, walletKeySignature: string, keys: DecryptedKey[]) => {
159 const decryptedEntropy = await decryptArmoredWalletKey(walletKey, walletKeySignature, keys);
160 return importAesGcmKey(decryptedEntropy);
163 export const decryptWalletKeyForHmac = async (walletKey: string, walletKeySignature: string, keys: DecryptedKey[]) => {
164 const decryptedEntropy = await decryptArmoredWalletKey(walletKey, walletKeySignature, keys);
165 return importHmacKey(decryptedEntropy);
168 export const hmac = async (hmacKey: CryptoKey, data: string) => {
169 return computeHmacSignature(hmacKey, stringToUtf8Array(data));
173 * Encrypts a wallet's data with provided key
175 * @param dataToEncrypt an array containing the data to encrypt with the provided wallet key
176 * @param key key to use to encrypt wallet data
177 * @returns an array containing the data encrypted
179 export const encryptWalletDataWithWalletKey = async (dataToEncrypt: string[], key: CryptoKey): Promise<string[]> => {
180 const encryptedData = await Promise.all(
181 dataToEncrypt.map(async (data) => {
182 const binaryData = encoder.encode(data);
184 const encryptedMnemonic = await encryptData(key, binaryData);
185 return uint8ArrayToBase64String(encryptedMnemonic);
189 return encryptedData;
193 * Encrypts a wallet's data
195 * @param dataToEncrypt an array containing the data to encrypt with the generated wallet key
196 * @param userKey user key to use to encrypt generated wallet key
197 * @returns a tupple containing encrypted data and a nested tupple with the encrypted wallet key, its signature, the decrypted wallet key and the id of the user key used to encrypt the wallet key
199 export const encryptWalletData = async (
200 dataToEncrypt: string[],
201 userKey: DecryptedKey
202 ): Promise<[string[], [string, string, CryptoKey, string]]> => {
203 const secretEntropy = generateAesGcmKey();
204 const key = await importAesGcmKey(secretEntropy);
206 const encryptedData = await encryptWalletDataWithWalletKey(dataToEncrypt, key);
208 const entropySignature = await signData(secretEntropy, 'wallet.key', [userKey.privateKey]);
209 const encryptedEntropy = await encryptPgp(secretEntropy, [userKey.publicKey]);
211 return [encryptedData, [encryptedEntropy, entropySignature, key, userKey.ID]];
215 * Decrypts an array of data using provided wallet key
217 * @param dataToDecrypt an array containing wallet's data to decrypt with provided walletKey
218 * @param walletKey (a.k.a encrypted entropy) used to encrypt wallet's data
219 * @returns an array containing wallet's decrypted wallet
221 export const decryptWalletData = async (dataToDecrypt: (string | null)[], walletKey: CryptoKey) => {
222 const decryptedData = await Promise.all(
223 dataToDecrypt.map(async (data) => {
229 const decodedEncryptedMnemonic = base64StringToUint8Array(data);
231 const decryptedBinaryMnemonic = await decryptData(walletKey, decodedEncryptedMnemonic);
232 return decoder.decode(decryptedBinaryMnemonic);
239 return decryptedData;
243 * Decrypts a mnemonic encrypted with user key
245 * @param encryptedMnemonic encrypted mnemonic with user key
246 * @param keys used to encrypt mnemonic
247 * @returns wallet's mnemonic encrypted with walletKey (a.k.a encrypted entropy)
249 export const decryptMnemonicWithUserKey = async (encryptedMnemonic: string | null, keys: DecryptedKey[]) => {
250 if (encryptedMnemonic === null) {
253 const { data: decryptedBinaryMnemonic } = await CryptoProxy.decryptMessage({
254 binaryMessage: base64StringToUint8Array(encryptedMnemonic),
255 decryptionKeys: keys.map((k) => k.privateKey),
259 return uint8ArrayToBase64String(decryptedBinaryMnemonic);