Refactor wallet inheritance.
This commit is contained in:
parent
cf4a579bf5
commit
1966c2d6d4
@ -6,33 +6,60 @@ import {
|
||||
defineProperties, resolveProperties, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
|
||||
import {
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync,
|
||||
} from "./json-keystore.js";
|
||||
|
||||
import type { SigningKey } from "../crypto/index.js";
|
||||
import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
|
||||
import type { Provider, TransactionRequest } from "../providers/index.js";
|
||||
import type { TransactionLike } from "../transaction/index.js";
|
||||
|
||||
import type { ProgressCallback } from "../crypto/index.js";
|
||||
|
||||
|
||||
/**
|
||||
* The **BaseWallet** is a stream-lined implementation of a
|
||||
* [[Signer]] that operates with a private key.
|
||||
*
|
||||
* It is preferred to use the [[Wallet]] class, as it offers
|
||||
* additional functionality and simplifies loading a variety
|
||||
* of JSON formats, Mnemonic Phrases, etc.
|
||||
*
|
||||
* This class may be of use for those attempting to implement
|
||||
* a minimal Signer.
|
||||
*/
|
||||
export class BaseWallet extends AbstractSigner {
|
||||
/**
|
||||
* The wallet address.
|
||||
*/
|
||||
readonly address!: string;
|
||||
|
||||
readonly #signingKey: SigningKey;
|
||||
|
||||
/**
|
||||
* Creates a new BaseWallet for %%privateKey%%, optionally
|
||||
* connected to %%provider%%.
|
||||
*
|
||||
* If %%provider%% is not specified, only offline methods can
|
||||
* be used.
|
||||
*/
|
||||
constructor(privateKey: SigningKey, provider?: null | Provider) {
|
||||
super(provider);
|
||||
|
||||
assertArgument(privateKey && typeof(privateKey.sign) === "function", "invalid private key", "privateKey", "[ REDACTED ]");
|
||||
|
||||
this.#signingKey = privateKey;
|
||||
|
||||
const address = computeAddress(this.signingKey.publicKey);
|
||||
defineProperties<BaseWallet>(this, { address });
|
||||
}
|
||||
|
||||
// Store these in getters to reduce visibility in console.log
|
||||
// Store private values behind getters to reduce visibility
|
||||
// in console.log
|
||||
|
||||
/**
|
||||
* The [[SigningKey]] used for signing payloads.
|
||||
*/
|
||||
get signingKey(): SigningKey { return this.#signingKey; }
|
||||
|
||||
/**
|
||||
* The private key for this wallet.
|
||||
*/
|
||||
get privateKey(): string { return this.signingKey.privateKey; }
|
||||
|
||||
async getAddress(): Promise<string> { return this.address; }
|
||||
@ -41,16 +68,6 @@ export class BaseWallet extends AbstractSigner {
|
||||
return new BaseWallet(this.#signingKey, provider);
|
||||
}
|
||||
|
||||
async encrypt(password: Uint8Array | string, progressCallback?: ProgressCallback): Promise<string> {
|
||||
const account = { address: this.address, privateKey: this.privateKey };
|
||||
return await encryptKeystoreJson(account, password, { progressCallback });
|
||||
}
|
||||
|
||||
encryptSync(password: Uint8Array | string): string {
|
||||
const account = { address: this.address, privateKey: this.privateKey };
|
||||
return encryptKeystoreJsonSync(account, password);
|
||||
}
|
||||
|
||||
async signTransaction(tx: TransactionRequest): Promise<string> {
|
||||
|
||||
// Replace any Addressable or ENS name with an address
|
||||
@ -76,11 +93,14 @@ export class BaseWallet extends AbstractSigner {
|
||||
}
|
||||
|
||||
async signMessage(message: string | Uint8Array): Promise<string> {
|
||||
return this.signingKey.sign(hashMessage(message)).serialized;
|
||||
return this.signMessageSync(message);
|
||||
}
|
||||
|
||||
// @TODO: Add a secialized signTx and signTyped sync that enforces
|
||||
// all parameters are known?
|
||||
/**
|
||||
* Returns the signature for %%message%% signed with this wallet.
|
||||
*/
|
||||
signMessageSync(message: string | Uint8Array): string {
|
||||
return this.signingKey.sign(hashMessage(message)).serialized;
|
||||
}
|
||||
|
@ -1,23 +1,34 @@
|
||||
/**
|
||||
* Explain HD Wallets..
|
||||
*
|
||||
* @_subsection: api/wallet:HD Wallets [hd-wallets]
|
||||
*/
|
||||
import { computeHmac, randomBytes, ripemd160, SigningKey, sha256 } from "../crypto/index.js";
|
||||
import { VoidSigner } from "../providers/index.js";
|
||||
import { computeAddress } from "../transaction/index.js";
|
||||
import {
|
||||
concat, dataSlice, decodeBase58, defineProperties, encodeBase58,
|
||||
getBytes, hexlify, isBytesLike,
|
||||
getNumber, toBigInt, toHex,
|
||||
getNumber, toArray, toBigInt, toHex,
|
||||
assertPrivate, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
import { langEn } from "../wordlists/lang-en.js";
|
||||
import { LangEn } from "../wordlists/lang-en.js";
|
||||
|
||||
import { Mnemonic } from "./mnemonic.js";
|
||||
import { BaseWallet } from "./base-wallet.js";
|
||||
import { Mnemonic } from "./mnemonic.js";
|
||||
import {
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync,
|
||||
} from "./json-keystore.js";
|
||||
|
||||
import type { BytesLike, Numeric } from "../utils/index.js";
|
||||
import type { ProgressCallback } from "../crypto/index.js";
|
||||
import type { Provider } from "../providers/index.js";
|
||||
import type { BytesLike, Numeric } from "../utils/index.js";
|
||||
import type { Wordlist } from "../wordlists/index.js";
|
||||
|
||||
import type { KeystoreAccount } from "./json-keystore.js";
|
||||
|
||||
export const defaultPath = "m/44'/60'/0'/0/0";
|
||||
|
||||
export const defaultPath: string = "m/44'/60'/0'/0/0";
|
||||
|
||||
|
||||
// "Bitcoin seed"
|
||||
@ -114,6 +125,9 @@ export class HDNodeWallet extends BaseWallet {
|
||||
readonly index!: number;
|
||||
readonly depth!: number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, signingKey: SigningKey, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, mnemonic: null | Mnemonic, provider: null | Provider) {
|
||||
super(signingKey, provider);
|
||||
assertPrivate(guard, _guard, "HDNodeWallet");
|
||||
@ -134,6 +148,45 @@ export class HDNodeWallet extends BaseWallet {
|
||||
this.chainCode, this.path, this.index, this.depth, this.mnemonic, provider);
|
||||
}
|
||||
|
||||
#account(): KeystoreAccount {
|
||||
const account: KeystoreAccount = { address: this.address, privateKey: this.privateKey };
|
||||
const m = this.mnemonic;
|
||||
if (this.path && m && m.wordlist.locale === "en" && m.password === "") {
|
||||
account.mnemonic = {
|
||||
path: this.path,
|
||||
locale: "en",
|
||||
entropy: m.entropy
|
||||
};
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to a [JSON Keystore Wallet](json-wallets) encrypted with
|
||||
* %%password%%.
|
||||
*
|
||||
* If %%progressCallback%% is specified, it will receive periodic
|
||||
* updates as the encryption process progreses.
|
||||
*/
|
||||
async encrypt(password: Uint8Array | string, progressCallback?: ProgressCallback): Promise<string> {
|
||||
return await encryptKeystoreJson(this.#account(), password, { progressCallback });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [JSON Keystore Wallet](json-wallets) encryped with
|
||||
* %%password%%.
|
||||
*
|
||||
* It is preferred to use the [async version](encrypt) instead,
|
||||
* which allows a [[ProgressCallback]] to keep the user informed.
|
||||
*
|
||||
* This method will block the event loop (freezing all UI) until
|
||||
* it is complete, which may be a non-trivial duration.
|
||||
*/
|
||||
encryptSync(password: Uint8Array | string): string {
|
||||
return encryptKeystoreJsonSync(this.#account(), password);
|
||||
}
|
||||
|
||||
get extendedKey(): string {
|
||||
// We only support the mainnet values for now, but if anyone needs
|
||||
// testnet values, let me know. I believe current sentiment is that
|
||||
@ -195,7 +248,7 @@ export class HDNodeWallet extends BaseWallet {
|
||||
}
|
||||
|
||||
static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet {
|
||||
const bytes = getBytes(decodeBase58(extendedKey)); // @TODO: redact
|
||||
const bytes = toArray(decodeBase58(extendedKey)); // @TODO: redact
|
||||
|
||||
assertArgument(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey,
|
||||
"invalid extended key", "extendedKey", "[ REDACTED ]");
|
||||
@ -228,7 +281,7 @@ export class HDNodeWallet extends BaseWallet {
|
||||
static createRandom(password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
||||
if (password == null) { password = ""; }
|
||||
if (path == null) { path = defaultPath; }
|
||||
if (wordlist == null) { wordlist = langEn; }
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist)
|
||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||
}
|
||||
@ -241,7 +294,7 @@ export class HDNodeWallet extends BaseWallet {
|
||||
static fromPhrase(phrase: string, password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
||||
if (password == null) { password = ""; }
|
||||
if (path == null) { path = defaultPath; }
|
||||
if (wordlist == null) { wordlist = langEn; }
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist)
|
||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||
}
|
||||
@ -263,6 +316,9 @@ export class HDNodeVoidWallet extends VoidSigner {
|
||||
readonly index!: number;
|
||||
readonly depth!: number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, address: string, publicKey: string, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, provider: null | Provider) {
|
||||
super(address, provider);
|
||||
assertPrivate(guard, _guard, "HDNodeVoidWallet");
|
||||
@ -330,7 +386,10 @@ export class HDNodeVoidWallet extends VoidSigner {
|
||||
export class HDNodeWalletManager {
|
||||
#root: HDNodeWallet;
|
||||
|
||||
constructor(phrase: string, password: string = "", path: string = "m/44'/60'/0'/0", locale: Wordlist = langEn) {
|
||||
constructor(phrase: string, password?: null | string, path?: null | string, locale?: null | Wordlist) {
|
||||
if (password == null) { password = ""; }
|
||||
if (path == null) { path = "m/44'/60'/0'/0"; }
|
||||
if (locale == null) { locale = LangEn.wordlist(); }
|
||||
this.#root = HDNodeWallet.fromPhrase(phrase, password, path, locale);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { HDNodeWallet } from "./hdwallet.js";
|
||||
import { decryptCrowdsaleJson, isCrowdsaleJson } from "./json-crowdsale.js";
|
||||
import {
|
||||
decryptKeystoreJson, decryptKeystoreJsonSync,
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync,
|
||||
isKeystoreJson
|
||||
} from "./json-keystore.js";
|
||||
import { Mnemonic } from "./mnemonic.js";
|
||||
@ -21,6 +22,16 @@ function stall(duration: number): Promise<void> {
|
||||
return new Promise((resolve) => { setTimeout(() => { resolve(); }, duration); });
|
||||
}
|
||||
|
||||
/**
|
||||
* A **Wallet** manages a single private key which is used to sign
|
||||
* transactions, messages and other common payloads.
|
||||
*
|
||||
* This class is generally the main entry point for developers
|
||||
* that wish to use a private key directly, as it can create
|
||||
* instances from a large variety of common sources, including
|
||||
* raw private key, [[link-bip-39]] mnemonics and encrypte JSON
|
||||
* wallets.
|
||||
*/
|
||||
export class Wallet extends BaseWallet {
|
||||
|
||||
constructor(key: string | SigningKey, provider?: null | Provider) {
|
||||
@ -32,6 +43,33 @@ export class Wallet extends BaseWallet {
|
||||
return new Wallet(this.signingKey, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to a [JSON Keystore Wallet](json-wallets) encrypted with
|
||||
* %%password%%.
|
||||
*
|
||||
* If %%progressCallback%% is specified, it will receive periodic
|
||||
* updates as the encryption process progreses.
|
||||
*/
|
||||
async encrypt(password: Uint8Array | string, progressCallback?: ProgressCallback): Promise<string> {
|
||||
const account = { address: this.address, privateKey: this.privateKey };
|
||||
return await encryptKeystoreJson(account, password, { progressCallback });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [JSON Keystore Wallet](json-wallets) encryped with
|
||||
* %%password%%.
|
||||
*
|
||||
* It is preferred to use the [async version](encrypt) instead,
|
||||
* which allows a [[ProgressCallback]] to keep the user informed.
|
||||
*
|
||||
* This method will block the event loop (freezing all UI) until
|
||||
* it is complete, which may be a non-trivial duration.
|
||||
*/
|
||||
encryptSync(password: Uint8Array | string): string {
|
||||
const account = { address: this.address, privateKey: this.privateKey };
|
||||
return encryptKeystoreJsonSync(account, password);
|
||||
}
|
||||
|
||||
static #fromAccount(account: null | CrowdsaleAccount | KeystoreAccount): HDNodeWallet | Wallet {
|
||||
assertArgument(account, "invalid JSON wallet", "json", "[ REDACTED ]");
|
||||
|
||||
@ -67,7 +105,7 @@ export class Wallet extends BaseWallet {
|
||||
return Wallet.#fromAccount(account);
|
||||
}
|
||||
|
||||
static fromEncryptedJsonSync(json: string, password: Uint8Array | string): Wallet {
|
||||
static fromEncryptedJsonSync(json: string, password: Uint8Array | string): HDNodeWallet | Wallet {
|
||||
let account: null | CrowdsaleAccount | KeystoreAccount = null;
|
||||
if (isKeystoreJson(json)) {
|
||||
account = decryptKeystoreJsonSync(json, password);
|
||||
|
Loading…
Reference in New Issue
Block a user