Added sync version of wallet decryption.
This commit is contained in:
parent
2ac8c0276d
commit
0ad94cdf81
@ -5,7 +5,7 @@ import { ExternallyOwnedAccount } from "@ethersproject/abstract-signer";
|
||||
|
||||
import { decrypt as decryptCrowdsale } from "./crowdsale";
|
||||
import { getJsonWalletAddress, isCrowdsaleWallet, isKeystoreWallet } from "./inspect";
|
||||
import { decrypt as decryptKeystore, encrypt as encryptKeystore, EncryptOptions, ProgressCallback } from "./keystore";
|
||||
import { decrypt as decryptKeystore, decryptSync as decryptKeystoreSync, encrypt as encryptKeystore, EncryptOptions, ProgressCallback } from "./keystore";
|
||||
|
||||
function decryptJsonWallet(json: string, password: Bytes | string, progressCallback?: ProgressCallback): Promise<ExternallyOwnedAccount> {
|
||||
if (isCrowdsaleWallet(json)) {
|
||||
@ -22,10 +22,23 @@ function decryptJsonWallet(json: string, password: Bytes | string, progressCallb
|
||||
return Promise.reject(new Error("invalid JSON wallet"));
|
||||
}
|
||||
|
||||
function decryptJsonWalletSync(json: string, password: Bytes | string): ExternallyOwnedAccount {
|
||||
if (isCrowdsaleWallet(json)) {
|
||||
return decryptCrowdsale(json, password)
|
||||
}
|
||||
|
||||
if (isKeystoreWallet(json)) {
|
||||
return decryptKeystoreSync(json, password);
|
||||
}
|
||||
|
||||
throw new Error("invalid JSON wallet");
|
||||
}
|
||||
|
||||
export {
|
||||
decryptCrowdsale,
|
||||
|
||||
decryptKeystore,
|
||||
decryptKeystoreSync,
|
||||
encryptKeystore,
|
||||
|
||||
isCrowdsaleWallet,
|
||||
@ -33,6 +46,7 @@ export {
|
||||
getJsonWalletAddress,
|
||||
|
||||
decryptJsonWallet,
|
||||
decryptJsonWalletSync,
|
||||
|
||||
ProgressCallback,
|
||||
EncryptOptions,
|
||||
|
@ -9,7 +9,7 @@ import { getAddress } from "@ethersproject/address";
|
||||
import { arrayify, Bytes, BytesLike, concat, hexlify } from "@ethersproject/bytes";
|
||||
import { defaultPath, entropyToMnemonic, HDNode, Mnemonic, mnemonicToEntropy } from "@ethersproject/hdnode";
|
||||
import { keccak256 } from "@ethersproject/keccak256";
|
||||
import { pbkdf2 } from "@ethersproject/pbkdf2";
|
||||
import { pbkdf2 as _pbkdf2 } from "@ethersproject/pbkdf2";
|
||||
import { randomBytes } from "@ethersproject/random";
|
||||
import { Description } from "@ethersproject/properties";
|
||||
import { computeAddress } from "@ethersproject/transactions";
|
||||
@ -61,12 +61,7 @@ export type EncryptOptions = {
|
||||
}
|
||||
}
|
||||
|
||||
export async function decrypt(json: string, password: Bytes | string, progressCallback?: ProgressCallback): Promise<KeystoreAccount> {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
const passwordBytes = getPassword(password);
|
||||
|
||||
const decrypt = function(key: Uint8Array, ciphertext: Uint8Array): Uint8Array {
|
||||
function _decrypt(data: any, key: Uint8Array, ciphertext: Uint8Array): Uint8Array {
|
||||
const cipher = searchPath(data, "crypto/cipher");
|
||||
if (cipher === "aes-128-ctr") {
|
||||
const iv = looseArrayify(searchPath(data, "crypto/cipherparams/iv"))
|
||||
@ -78,22 +73,17 @@ export async function decrypt(json: string, password: Bytes | string, progressCa
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const computeMAC = function(derivedHalf: Uint8Array, ciphertext: Uint8Array) {
|
||||
return keccak256(concat([ derivedHalf, ciphertext ]));
|
||||
}
|
||||
|
||||
const getAccount = async function(key: Uint8Array): Promise<KeystoreAccount> {
|
||||
function _getAccount(data: any, key: Uint8Array): KeystoreAccount {
|
||||
const ciphertext = looseArrayify(searchPath(data, "crypto/ciphertext"));
|
||||
|
||||
const computedMAC = hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2);
|
||||
const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2);
|
||||
if (computedMAC !== searchPath(data, "crypto/mac").toLowerCase()) {
|
||||
throw new Error("invalid password");
|
||||
}
|
||||
|
||||
const privateKey = decrypt(key.slice(0, 16), ciphertext);
|
||||
const mnemonicKey = key.slice(32, 64);
|
||||
const privateKey = _decrypt(data, key.slice(0, 16), ciphertext);
|
||||
|
||||
if (!privateKey) {
|
||||
logger.throwError("unsupported cipher", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||
@ -101,6 +91,8 @@ export async function decrypt(json: string, password: Bytes | string, progressCa
|
||||
});
|
||||
}
|
||||
|
||||
const mnemonicKey = key.slice(32, 64);
|
||||
|
||||
const address = computeAddress(privateKey);
|
||||
if (data.address) {
|
||||
let check = data.address.toLowerCase();
|
||||
@ -153,8 +145,22 @@ export async function decrypt(json: string, password: Bytes | string, progressCa
|
||||
return new KeystoreAccount(account);
|
||||
}
|
||||
|
||||
type ScryptFunc<T> = (pw: Uint8Array, salt: Uint8Array, n: number, r: number, p: number, dkLen: number, callback?: ProgressCallback) => T;
|
||||
type Pbkdf2Func<T> = (pw: Uint8Array, salt: Uint8Array, c: number, dkLen: number, prfFunc: string) => T;
|
||||
|
||||
function pbkdf2Sync(passwordBytes: Uint8Array, salt: Uint8Array, count: number, dkLen: number, prfFunc: string): Uint8Array {
|
||||
return arrayify(_pbkdf2(passwordBytes, salt, count, dkLen, prfFunc));
|
||||
}
|
||||
|
||||
function pbkdf2(passwordBytes: Uint8Array, salt: Uint8Array, count: number, dkLen: number, prfFunc: string): Promise<Uint8Array> {
|
||||
return Promise.resolve(pbkdf2Sync(passwordBytes, salt, count, dkLen, prfFunc));
|
||||
}
|
||||
|
||||
function _computeKdfKey<T>(data: any, password: Bytes | string, pbkdf2Func: Pbkdf2Func<T>, scryptFunc: ScryptFunc<T>, progressCallback?: ProgressCallback): T {
|
||||
const passwordBytes = getPassword(password);
|
||||
|
||||
const kdf = searchPath(data, "crypto/kdf");
|
||||
|
||||
if (kdf && typeof(kdf) === "string") {
|
||||
const throwError = function(name: string, value: any): never {
|
||||
return logger.throwArgumentError("invalid key-derivation function parameters", name, value);
|
||||
@ -175,10 +181,7 @@ export async function decrypt(json: string, password: Bytes | string, progressCa
|
||||
const dkLen = parseInt(searchPath(data, "crypto/kdfparams/dklen"));
|
||||
if (dkLen !== 32) { throwError("dklen", dkLen); }
|
||||
|
||||
const key = await scrypt.scrypt(passwordBytes, salt, N, r, p, 64, progressCallback);
|
||||
//key = arrayify(key);
|
||||
|
||||
return getAccount(key);
|
||||
return scryptFunc(passwordBytes, salt, N, r, p, 64, progressCallback);
|
||||
|
||||
} else if (kdf.toLowerCase() === "pbkdf2") {
|
||||
|
||||
@ -194,20 +197,34 @@ export async function decrypt(json: string, password: Bytes | string, progressCa
|
||||
throwError("prf", prf);
|
||||
}
|
||||
|
||||
const c = parseInt(searchPath(data, "crypto/kdfparams/c"));
|
||||
const count = parseInt(searchPath(data, "crypto/kdfparams/c"));
|
||||
|
||||
const dkLen = parseInt(searchPath(data, "crypto/kdfparams/dklen"));
|
||||
if (dkLen !== 32) { throwError("dklen", dkLen); }
|
||||
|
||||
const key = arrayify(pbkdf2(passwordBytes, salt, c, dkLen, prfFunc));
|
||||
|
||||
return getAccount(key);
|
||||
return pbkdf2Func(passwordBytes, salt, count, dkLen, prfFunc);
|
||||
}
|
||||
}
|
||||
|
||||
return logger.throwArgumentError("unsupported key-derivation function", "kdf", kdf);
|
||||
}
|
||||
|
||||
|
||||
export function decryptSync(json: string, password: Bytes | string): KeystoreAccount {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
const key = _computeKdfKey(data, password, pbkdf2Sync, scrypt.scryptSync);
|
||||
return _getAccount(data, key);
|
||||
}
|
||||
|
||||
export async function decrypt(json: string, password: Bytes | string, progressCallback?: ProgressCallback): Promise<KeystoreAccount> {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
const key = await _computeKdfKey(data, password, pbkdf2, scrypt.scrypt, progressCallback);
|
||||
return _getAccount(data, key);
|
||||
}
|
||||
|
||||
|
||||
export function encrypt(account: ExternallyOwnedAccount, password: Bytes | string, options?: EncryptOptions, progressCallback?: ProgressCallback): Promise<string> {
|
||||
|
||||
try {
|
||||
|
@ -10,7 +10,7 @@ import { keccak256 } from "@ethersproject/keccak256";
|
||||
import { defineReadOnly, resolveProperties } from "@ethersproject/properties";
|
||||
import { randomBytes } from "@ethersproject/random";
|
||||
import { SigningKey } from "@ethersproject/signing-key";
|
||||
import { decryptJsonWallet, encryptKeystore, ProgressCallback } from "@ethersproject/json-wallets";
|
||||
import { decryptJsonWallet, decryptJsonWalletSync, encryptKeystore, ProgressCallback } from "@ethersproject/json-wallets";
|
||||
import { computeAddress, recoverAddress, serialize, UnsignedTransaction } from "@ethersproject/transactions";
|
||||
import { Wordlist } from "@ethersproject/wordlists";
|
||||
|
||||
@ -159,6 +159,10 @@ export class Wallet extends Signer implements ExternallyOwnedAccount {
|
||||
});
|
||||
}
|
||||
|
||||
static fromEncryptedJsonSync(json: string, password: Bytes | string): Wallet {
|
||||
return new Wallet(decryptJsonWalletSync(json, password));
|
||||
}
|
||||
|
||||
static fromMnemonic(mnemonic: string, path?: string, wordlist?: Wordlist): Wallet {
|
||||
if (!path) { path = defaultPath; }
|
||||
return new Wallet(HDNode.fromMnemonic(mnemonic, null, wordlist).derivePath(path));
|
||||
|
Loading…
x
Reference in New Issue
Block a user