Refactor wallet inheritance.
This commit is contained in:
parent
cf4a579bf5
commit
1966c2d6d4
@ -6,33 +6,60 @@ import {
|
|||||||
defineProperties, resolveProperties, assert, assertArgument
|
defineProperties, resolveProperties, assert, assertArgument
|
||||||
} from "../utils/index.js";
|
} from "../utils/index.js";
|
||||||
|
|
||||||
import {
|
|
||||||
encryptKeystoreJson, encryptKeystoreJsonSync,
|
|
||||||
} from "./json-keystore.js";
|
|
||||||
|
|
||||||
import type { SigningKey } from "../crypto/index.js";
|
import type { SigningKey } from "../crypto/index.js";
|
||||||
import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
|
import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
|
||||||
import type { Provider, TransactionRequest } from "../providers/index.js";
|
import type { Provider, TransactionRequest } from "../providers/index.js";
|
||||||
import type { TransactionLike } from "../transaction/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 {
|
export class BaseWallet extends AbstractSigner {
|
||||||
|
/**
|
||||||
|
* The wallet address.
|
||||||
|
*/
|
||||||
readonly address!: string;
|
readonly address!: string;
|
||||||
|
|
||||||
readonly #signingKey: SigningKey;
|
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) {
|
constructor(privateKey: SigningKey, provider?: null | Provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
|
|
||||||
|
assertArgument(privateKey && typeof(privateKey.sign) === "function", "invalid private key", "privateKey", "[ REDACTED ]");
|
||||||
|
|
||||||
this.#signingKey = privateKey;
|
this.#signingKey = privateKey;
|
||||||
|
|
||||||
const address = computeAddress(this.signingKey.publicKey);
|
const address = computeAddress(this.signingKey.publicKey);
|
||||||
defineProperties<BaseWallet>(this, { address });
|
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; }
|
get signingKey(): SigningKey { return this.#signingKey; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key for this wallet.
|
||||||
|
*/
|
||||||
get privateKey(): string { return this.signingKey.privateKey; }
|
get privateKey(): string { return this.signingKey.privateKey; }
|
||||||
|
|
||||||
async getAddress(): Promise<string> { return this.address; }
|
async getAddress(): Promise<string> { return this.address; }
|
||||||
@ -41,16 +68,6 @@ export class BaseWallet extends AbstractSigner {
|
|||||||
return new BaseWallet(this.#signingKey, provider);
|
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> {
|
async signTransaction(tx: TransactionRequest): Promise<string> {
|
||||||
|
|
||||||
// Replace any Addressable or ENS name with an address
|
// 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> {
|
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
|
// @TODO: Add a secialized signTx and signTyped sync that enforces
|
||||||
// all parameters are known?
|
// all parameters are known?
|
||||||
|
/**
|
||||||
|
* Returns the signature for %%message%% signed with this wallet.
|
||||||
|
*/
|
||||||
signMessageSync(message: string | Uint8Array): string {
|
signMessageSync(message: string | Uint8Array): string {
|
||||||
return this.signingKey.sign(hashMessage(message)).serialized;
|
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 { computeHmac, randomBytes, ripemd160, SigningKey, sha256 } from "../crypto/index.js";
|
||||||
import { VoidSigner } from "../providers/index.js";
|
import { VoidSigner } from "../providers/index.js";
|
||||||
import { computeAddress } from "../transaction/index.js";
|
import { computeAddress } from "../transaction/index.js";
|
||||||
import {
|
import {
|
||||||
concat, dataSlice, decodeBase58, defineProperties, encodeBase58,
|
concat, dataSlice, decodeBase58, defineProperties, encodeBase58,
|
||||||
getBytes, hexlify, isBytesLike,
|
getBytes, hexlify, isBytesLike,
|
||||||
getNumber, toBigInt, toHex,
|
getNumber, toArray, toBigInt, toHex,
|
||||||
assertPrivate, assert, assertArgument
|
assertPrivate, assert, assertArgument
|
||||||
} from "../utils/index.js";
|
} 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 { 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 { Provider } from "../providers/index.js";
|
||||||
|
import type { BytesLike, Numeric } from "../utils/index.js";
|
||||||
import type { Wordlist } from "../wordlists/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"
|
// "Bitcoin seed"
|
||||||
@ -114,6 +125,9 @@ export class HDNodeWallet extends BaseWallet {
|
|||||||
readonly index!: number;
|
readonly index!: number;
|
||||||
readonly depth!: 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) {
|
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);
|
super(signingKey, provider);
|
||||||
assertPrivate(guard, _guard, "HDNodeWallet");
|
assertPrivate(guard, _guard, "HDNodeWallet");
|
||||||
@ -134,6 +148,45 @@ export class HDNodeWallet extends BaseWallet {
|
|||||||
this.chainCode, this.path, this.index, this.depth, this.mnemonic, provider);
|
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 {
|
get extendedKey(): string {
|
||||||
// We only support the mainnet values for now, but if anyone needs
|
// We only support the mainnet values for now, but if anyone needs
|
||||||
// testnet values, let me know. I believe current sentiment is that
|
// 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 {
|
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,
|
assertArgument(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey,
|
||||||
"invalid extended key", "extendedKey", "[ REDACTED ]");
|
"invalid extended key", "extendedKey", "[ REDACTED ]");
|
||||||
@ -228,7 +281,7 @@ export class HDNodeWallet extends BaseWallet {
|
|||||||
static createRandom(password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
static createRandom(password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
||||||
if (password == null) { password = ""; }
|
if (password == null) { password = ""; }
|
||||||
if (path == null) { path = defaultPath; }
|
if (path == null) { path = defaultPath; }
|
||||||
if (wordlist == null) { wordlist = langEn; }
|
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||||
const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist)
|
const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist)
|
||||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
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 {
|
static fromPhrase(phrase: string, password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
||||||
if (password == null) { password = ""; }
|
if (password == null) { password = ""; }
|
||||||
if (path == null) { path = defaultPath; }
|
if (path == null) { path = defaultPath; }
|
||||||
if (wordlist == null) { wordlist = langEn; }
|
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||||
const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist)
|
const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist)
|
||||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||||
}
|
}
|
||||||
@ -263,6 +316,9 @@ export class HDNodeVoidWallet extends VoidSigner {
|
|||||||
readonly index!: number;
|
readonly index!: number;
|
||||||
readonly depth!: 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) {
|
constructor(guard: any, address: string, publicKey: string, parentFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, provider: null | Provider) {
|
||||||
super(address, provider);
|
super(address, provider);
|
||||||
assertPrivate(guard, _guard, "HDNodeVoidWallet");
|
assertPrivate(guard, _guard, "HDNodeVoidWallet");
|
||||||
@ -330,7 +386,10 @@ export class HDNodeVoidWallet extends VoidSigner {
|
|||||||
export class HDNodeWalletManager {
|
export class HDNodeWalletManager {
|
||||||
#root: HDNodeWallet;
|
#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);
|
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 { decryptCrowdsaleJson, isCrowdsaleJson } from "./json-crowdsale.js";
|
||||||
import {
|
import {
|
||||||
decryptKeystoreJson, decryptKeystoreJsonSync,
|
decryptKeystoreJson, decryptKeystoreJsonSync,
|
||||||
|
encryptKeystoreJson, encryptKeystoreJsonSync,
|
||||||
isKeystoreJson
|
isKeystoreJson
|
||||||
} from "./json-keystore.js";
|
} from "./json-keystore.js";
|
||||||
import { Mnemonic } from "./mnemonic.js";
|
import { Mnemonic } from "./mnemonic.js";
|
||||||
@ -21,6 +22,16 @@ function stall(duration: number): Promise<void> {
|
|||||||
return new Promise((resolve) => { setTimeout(() => { resolve(); }, duration); });
|
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 {
|
export class Wallet extends BaseWallet {
|
||||||
|
|
||||||
constructor(key: string | SigningKey, provider?: null | Provider) {
|
constructor(key: string | SigningKey, provider?: null | Provider) {
|
||||||
@ -32,6 +43,33 @@ export class Wallet extends BaseWallet {
|
|||||||
return new Wallet(this.signingKey, provider);
|
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 {
|
static #fromAccount(account: null | CrowdsaleAccount | KeystoreAccount): HDNodeWallet | Wallet {
|
||||||
assertArgument(account, "invalid JSON wallet", "json", "[ REDACTED ]");
|
assertArgument(account, "invalid JSON wallet", "json", "[ REDACTED ]");
|
||||||
|
|
||||||
@ -67,7 +105,7 @@ export class Wallet extends BaseWallet {
|
|||||||
return Wallet.#fromAccount(account);
|
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;
|
let account: null | CrowdsaleAccount | KeystoreAccount = null;
|
||||||
if (isKeystoreJson(json)) {
|
if (isKeystoreJson(json)) {
|
||||||
account = decryptKeystoreJsonSync(json, password);
|
account = decryptKeystoreJsonSync(json, password);
|
||||||
|
Loading…
Reference in New Issue
Block a user