refactor: move brother_node development artifact to dev/test-nodes subdirectory
Development Artifact Cleanup: ✅ BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location - dev/test-nodes/brother_node/: Moved from root directory for better organization - Contains development configuration, test logs, and test chain data - No impact on production systems - purely development/testing artifact ✅ DEVELOPMENT ARTIFACTS IDENTIFIED: - Chain ID: aitbc-brother-chain (test/development chain) - Ports: 8010 (P2P) and 8011 (RPC) - different from production - Environment: .env file with test configuration - Logs: rpc.log and node.log from development testing session (March 15, 2026) ✅ ROOT DIRECTORY CLEANUP: Removed development clutter from production directory - brother_node/ moved to dev/test-nodes/brother_node/ - Root directory now contains only production-ready components - Development artifacts properly organized in dev/ subdirectory DIRECTORY STRUCTURE IMPROVEMENT: 📁 dev/test-nodes/: Development and testing node configurations 🏗️ Root Directory: Clean production structure with only essential components 🧪 Development Isolation: Test environments separated from production BENEFITS: ✅ Clean Production Directory: No development artifacts in root ✅ Better Organization: Development nodes grouped in dev/ subdirectory ✅ Clear Separation: Production vs development environments clearly distinguished ✅ Maintainability: Easier to identify and manage development components RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
160
dev/env/node_modules/ethers/src.ts/wallet/base-wallet.ts
generated
vendored
Executable file
160
dev/env/node_modules/ethers/src.ts/wallet/base-wallet.ts
generated
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
import { getAddress, resolveAddress } from "../address/index.js";
|
||||
import {
|
||||
hashAuthorization, hashMessage, TypedDataEncoder
|
||||
} from "../hash/index.js";
|
||||
import { AbstractSigner, copyRequest } from "../providers/index.js";
|
||||
import { computeAddress, Transaction } from "../transaction/index.js";
|
||||
import {
|
||||
defineProperties, getBigInt, resolveProperties, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
|
||||
import type { SigningKey } from "../crypto/index.js";
|
||||
import type {
|
||||
AuthorizationRequest, TypedDataDomain, TypedDataField
|
||||
} from "../hash/index.js";
|
||||
import type { Provider, TransactionRequest } from "../providers/index.js";
|
||||
import type { Authorization, TransactionLike } from "../transaction/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 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; }
|
||||
|
||||
connect(provider: null | Provider): BaseWallet {
|
||||
return new BaseWallet(this.#signingKey, provider);
|
||||
}
|
||||
|
||||
async signTransaction(tx: TransactionRequest): Promise<string> {
|
||||
tx = copyRequest(tx);
|
||||
|
||||
// Replace any Addressable or ENS name with an address
|
||||
const { to, from } = await resolveProperties({
|
||||
to: (tx.to ? resolveAddress(tx.to, this): undefined),
|
||||
from: (tx.from ? resolveAddress(tx.from, this): undefined)
|
||||
});
|
||||
|
||||
if (to != null) { tx.to = to; }
|
||||
if (from != null) { tx.from = from; }
|
||||
|
||||
if (tx.from != null) {
|
||||
assertArgument(getAddress(<string>(tx.from)) === this.address,
|
||||
"transaction from address mismatch", "tx.from", tx.from);
|
||||
delete tx.from;
|
||||
}
|
||||
|
||||
// Build the transaction
|
||||
const btx = Transaction.from(<TransactionLike<string>>tx);
|
||||
btx.signature = this.signingKey.sign(btx.unsignedHash);
|
||||
|
||||
return btx.serialized;
|
||||
}
|
||||
|
||||
async signMessage(message: string | Uint8Array): Promise<string> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Authorization for %%auth%%.
|
||||
*/
|
||||
authorizeSync(auth: AuthorizationRequest): Authorization {
|
||||
assertArgument(typeof(auth.address) === "string",
|
||||
"invalid address for authorizeSync", "auth.address", auth);
|
||||
|
||||
const signature = this.signingKey.sign(hashAuthorization(auth));
|
||||
return Object.assign({ }, {
|
||||
address: getAddress(auth.address),
|
||||
nonce: getBigInt(auth.nonce || 0),
|
||||
chainId: getBigInt(auth.chainId || 0),
|
||||
}, { signature });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to the Authorization for %%auth%%.
|
||||
*/
|
||||
async authorize(auth: AuthorizationRequest): Promise<Authorization> {
|
||||
auth = Object.assign({ }, auth, {
|
||||
address: await resolveAddress(auth.address, this)
|
||||
});
|
||||
return this.authorizeSync(await this.populateAuthorization(auth));
|
||||
}
|
||||
|
||||
async signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
|
||||
|
||||
// Populate any ENS names
|
||||
const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => {
|
||||
// @TODO: this should use resolveName; addresses don't
|
||||
// need a provider
|
||||
|
||||
assert(this.provider != null, "cannot resolve ENS names without a provider", "UNSUPPORTED_OPERATION", {
|
||||
operation: "resolveName",
|
||||
info: { name }
|
||||
});
|
||||
|
||||
const address = await this.provider.resolveName(name);
|
||||
assert(address != null, "unconfigured ENS name", "UNCONFIGURED_NAME", {
|
||||
value: name
|
||||
});
|
||||
|
||||
return address;
|
||||
});
|
||||
|
||||
return this.signingKey.sign(TypedDataEncoder.hash(populated.domain, types, populated.value)).serialized;
|
||||
}
|
||||
}
|
||||
586
dev/env/node_modules/ethers/src.ts/wallet/hdwallet.ts
generated
vendored
Executable file
586
dev/env/node_modules/ethers/src.ts/wallet/hdwallet.ts
generated
vendored
Executable file
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* 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, toBeArray, toBigInt, toBeHex,
|
||||
assertPrivate, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
import { LangEn } from "../wordlists/lang-en.js";
|
||||
|
||||
import { BaseWallet } from "./base-wallet.js";
|
||||
import { Mnemonic } from "./mnemonic.js";
|
||||
import {
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync,
|
||||
} from "./json-keystore.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";
|
||||
|
||||
/**
|
||||
* The default derivation path for Ethereum HD Nodes. (i.e. ``"m/44'/60'/0'/0/0"``)
|
||||
*/
|
||||
export const defaultPath: string = "m/44'/60'/0'/0/0";
|
||||
|
||||
|
||||
// "Bitcoin seed"
|
||||
const MasterSecret = new Uint8Array([ 66, 105, 116, 99, 111, 105, 110, 32, 115, 101, 101, 100 ]);
|
||||
|
||||
const HardenedBit = 0x80000000;
|
||||
|
||||
const N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
|
||||
|
||||
const Nibbles = "0123456789abcdef";
|
||||
function zpad(value: number, length: number): string {
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Nibbles[value % 16] + result;
|
||||
value = Math.trunc(value / 16);
|
||||
}
|
||||
while (result.length < length * 2) { result = "0" + result; }
|
||||
return "0x" + result;
|
||||
}
|
||||
|
||||
function encodeBase58Check(_value: BytesLike): string {
|
||||
const value = getBytes(_value);
|
||||
const check = dataSlice(sha256(sha256(value)), 0, 4);
|
||||
const bytes = concat([ value, check ]);
|
||||
return encodeBase58(bytes);
|
||||
}
|
||||
|
||||
const _guard = { };
|
||||
|
||||
function ser_I(index: number, chainCode: string, publicKey: string, privateKey: null | string): { IL: Uint8Array, IR: Uint8Array } {
|
||||
const data = new Uint8Array(37);
|
||||
|
||||
if (index & HardenedBit) {
|
||||
assert(privateKey != null, "cannot derive child of neutered node", "UNSUPPORTED_OPERATION", {
|
||||
operation: "deriveChild"
|
||||
});
|
||||
|
||||
// Data = 0x00 || ser_256(k_par)
|
||||
data.set(getBytes(privateKey), 1);
|
||||
|
||||
} else {
|
||||
// Data = ser_p(point(k_par))
|
||||
data.set(getBytes(publicKey));
|
||||
}
|
||||
|
||||
// Data += ser_32(i)
|
||||
for (let i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff); }
|
||||
const I = getBytes(computeHmac("sha512", chainCode, data));
|
||||
|
||||
return { IL: I.slice(0, 32), IR: I.slice(32) };
|
||||
}
|
||||
|
||||
type HDNodeLike<T> = { depth: number, deriveChild: (i: number) => T };
|
||||
function derivePath<T extends HDNodeLike<T>>(node: T, path: string): T {
|
||||
const components = path.split("/");
|
||||
|
||||
assertArgument(components.length > 0, "invalid path", "path", path);
|
||||
|
||||
if (components[0] === "m") {
|
||||
assertArgument(node.depth === 0, `cannot derive root path (i.e. path starting with "m/") for a node at non-zero depth ${ node.depth }`, "path", path);
|
||||
components.shift();
|
||||
}
|
||||
|
||||
let result: T = node;
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
|
||||
if (component.match(/^[0-9]+'$/)) {
|
||||
const index = parseInt(component.substring(0, component.length - 1));
|
||||
assertArgument(index < HardenedBit, "invalid path index", `path[${ i }]`, component);
|
||||
result = result.deriveChild(HardenedBit + index);
|
||||
|
||||
} else if (component.match(/^[0-9]+$/)) {
|
||||
const index = parseInt(component);
|
||||
assertArgument(index < HardenedBit, "invalid path index", `path[${ i }]`, component);
|
||||
result = result.deriveChild(index);
|
||||
|
||||
} else {
|
||||
assertArgument(false, "invalid path component", `path[${ i }]`, component);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* An **HDNodeWallet** is a [[Signer]] backed by the private key derived
|
||||
* from an HD Node using the [[link-bip-32]] stantard.
|
||||
*
|
||||
* An HD Node forms a hierarchal structure with each HD Node having a
|
||||
* private key and the ability to derive child HD Nodes, defined by
|
||||
* a path indicating the index of each child.
|
||||
*/
|
||||
export class HDNodeWallet extends BaseWallet {
|
||||
/**
|
||||
* The compressed public key.
|
||||
*/
|
||||
readonly publicKey!: string;
|
||||
|
||||
/**
|
||||
* The fingerprint.
|
||||
*
|
||||
* A fingerprint allows quick qay to detect parent and child nodes,
|
||||
* but developers should be prepared to deal with collisions as it
|
||||
* is only 4 bytes.
|
||||
*/
|
||||
readonly fingerprint!: string;
|
||||
|
||||
/**
|
||||
* The parent fingerprint.
|
||||
*/
|
||||
readonly parentFingerprint!: string;
|
||||
|
||||
/**
|
||||
* The mnemonic used to create this HD Node, if available.
|
||||
*
|
||||
* Sources such as extended keys do not encode the mnemonic, in
|
||||
* which case this will be ``null``.
|
||||
*/
|
||||
readonly mnemonic!: null | Mnemonic;
|
||||
|
||||
/**
|
||||
* The chaincode, which is effectively a public key used
|
||||
* to derive children.
|
||||
*/
|
||||
readonly chainCode!: string;
|
||||
|
||||
/**
|
||||
* The derivation path of this wallet.
|
||||
*
|
||||
* Since extended keys do not provide full path details, this
|
||||
* may be ``null``, if instantiated from a source that does not
|
||||
* encode it.
|
||||
*/
|
||||
readonly path!: null | string;
|
||||
|
||||
/**
|
||||
* The child index of this wallet. Values over ``2 *\* 31`` indicate
|
||||
* the node is hardened.
|
||||
*/
|
||||
readonly index!: number;
|
||||
|
||||
/**
|
||||
* The depth of this wallet, which is the number of components
|
||||
* in its path.
|
||||
*/
|
||||
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");
|
||||
|
||||
defineProperties<HDNodeWallet>(this, { publicKey: signingKey.compressedPublicKey });
|
||||
|
||||
const fingerprint = dataSlice(ripemd160(sha256(this.publicKey)), 0, 4);
|
||||
defineProperties<HDNodeWallet>(this, {
|
||||
parentFingerprint, fingerprint,
|
||||
chainCode, path, index, depth
|
||||
});
|
||||
|
||||
defineProperties<HDNodeWallet>(this, { mnemonic });
|
||||
}
|
||||
|
||||
connect(provider: null | Provider): HDNodeWallet {
|
||||
return new HDNodeWallet(_guard, this.signingKey, this.parentFingerprint,
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The extended key.
|
||||
*
|
||||
* This key will begin with the prefix ``xpriv`` and can be used to
|
||||
* reconstruct this HD Node to derive its children.
|
||||
*/
|
||||
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
|
||||
// we should always use mainnet, and use BIP-44 to derive the network
|
||||
// - Mainnet: public=0x0488B21E, private=0x0488ADE4
|
||||
// - Testnet: public=0x043587CF, private=0x04358394
|
||||
|
||||
assert(this.depth < 256, "Depth too deep", "UNSUPPORTED_OPERATION", { operation: "extendedKey" });
|
||||
|
||||
return encodeBase58Check(concat([
|
||||
"0x0488ADE4", zpad(this.depth, 1), this.parentFingerprint,
|
||||
zpad(this.index, 4), this.chainCode,
|
||||
concat([ "0x00", this.privateKey ])
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this wallet has a path, providing a Type Guard
|
||||
* that the path is non-null.
|
||||
*/
|
||||
hasPath(): this is { path: string } { return (this.path != null); }
|
||||
|
||||
/**
|
||||
* Returns a neutered HD Node, which removes the private details
|
||||
* of an HD Node.
|
||||
*
|
||||
* A neutered node has no private key, but can be used to derive
|
||||
* child addresses and other public data about the HD Node.
|
||||
*/
|
||||
neuter(): HDNodeVoidWallet {
|
||||
return new HDNodeVoidWallet(_guard, this.address, this.publicKey,
|
||||
this.parentFingerprint, this.chainCode, this.path, this.index,
|
||||
this.depth, this.provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the child for %%index%%.
|
||||
*/
|
||||
deriveChild(_index: Numeric): HDNodeWallet {
|
||||
const index = getNumber(_index, "index");
|
||||
assertArgument(index <= 0xffffffff, "invalid index", "index", index);
|
||||
|
||||
// Base path
|
||||
let path = this.path;
|
||||
if (path) {
|
||||
path += "/" + (index & ~HardenedBit);
|
||||
if (index & HardenedBit) { path += "'"; }
|
||||
}
|
||||
|
||||
const { IR, IL } = ser_I(index, this.chainCode, this.publicKey, this.privateKey);
|
||||
const ki = new SigningKey(toBeHex((toBigInt(IL) + BigInt(this.privateKey)) % N, 32));
|
||||
|
||||
return new HDNodeWallet(_guard, ki, this.fingerprint, hexlify(IR),
|
||||
path, index, this.depth + 1, this.mnemonic, this.provider);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HDNode for %%path%% from this node.
|
||||
*/
|
||||
derivePath(path: string): HDNodeWallet {
|
||||
return derivePath<HDNodeWallet>(this, path);
|
||||
}
|
||||
|
||||
static #fromSeed(_seed: BytesLike, mnemonic: null | Mnemonic): HDNodeWallet {
|
||||
assertArgument(isBytesLike(_seed), "invalid seed", "seed", "[REDACTED]");
|
||||
|
||||
const seed = getBytes(_seed, "seed");
|
||||
assertArgument(seed.length >= 16 && seed.length <= 64 , "invalid seed", "seed", "[REDACTED]");
|
||||
|
||||
const I = getBytes(computeHmac("sha512", MasterSecret, seed));
|
||||
const signingKey = new SigningKey(hexlify(I.slice(0, 32)));
|
||||
|
||||
return new HDNodeWallet(_guard, signingKey, "0x00000000", hexlify(I.slice(32)),
|
||||
"m", 0, 0, mnemonic, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new HD Node from %%extendedKey%%.
|
||||
*
|
||||
* If the %%extendedKey%% will either have a prefix or ``xpub`` or
|
||||
* ``xpriv``, returning a neutered HD Node ([[HDNodeVoidWallet]])
|
||||
* or full HD Node ([[HDNodeWallet) respectively.
|
||||
*/
|
||||
static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet {
|
||||
const bytes = toBeArray(decodeBase58(extendedKey)); // @TODO: redact
|
||||
|
||||
assertArgument(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey,
|
||||
"invalid extended key", "extendedKey", "[ REDACTED ]");
|
||||
|
||||
const depth = bytes[4];
|
||||
const parentFingerprint = hexlify(bytes.slice(5, 9));
|
||||
const index = parseInt(hexlify(bytes.slice(9, 13)).substring(2), 16);
|
||||
const chainCode = hexlify(bytes.slice(13, 45));
|
||||
const key = bytes.slice(45, 78);
|
||||
|
||||
switch (hexlify(bytes.slice(0, 4))) {
|
||||
// Public Key
|
||||
case "0x0488b21e": case "0x043587cf": {
|
||||
const publicKey = hexlify(key);
|
||||
return new HDNodeVoidWallet(_guard, computeAddress(publicKey), publicKey,
|
||||
parentFingerprint, chainCode, null, index, depth, null);
|
||||
}
|
||||
|
||||
// Private Key
|
||||
case "0x0488ade4": case "0x04358394 ":
|
||||
if (key[0] !== 0) { break; }
|
||||
return new HDNodeWallet(_guard, new SigningKey(key.slice(1)),
|
||||
parentFingerprint, chainCode, null, index, depth, null, null);
|
||||
}
|
||||
|
||||
|
||||
assertArgument(false, "invalid extended key prefix", "extendedKey", "[ REDACTED ]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new random HDNode.
|
||||
*/
|
||||
static createRandom(password?: string, path?: string, wordlist?: Wordlist): HDNodeWallet {
|
||||
if (password == null) { password = ""; }
|
||||
if (path == null) { path = defaultPath; }
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
const mnemonic = Mnemonic.fromEntropy(randomBytes(16), password, wordlist)
|
||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HD Node from %%mnemonic%%.
|
||||
*/
|
||||
static fromMnemonic(mnemonic: Mnemonic, path?: string): HDNodeWallet {
|
||||
if (!path) { path = defaultPath; }
|
||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HD Node from a mnemonic %%phrase%%.
|
||||
*/
|
||||
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.wordlist(); }
|
||||
const mnemonic = Mnemonic.fromPhrase(phrase, password, wordlist)
|
||||
return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HD Node from a %%seed%%.
|
||||
*/
|
||||
static fromSeed(seed: BytesLike): HDNodeWallet {
|
||||
return HDNodeWallet.#fromSeed(seed, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A **HDNodeVoidWallet** cannot sign, but provides access to
|
||||
* the children nodes of a [[link-bip-32]] HD wallet addresses.
|
||||
*
|
||||
* The can be created by using an extended ``xpub`` key to
|
||||
* [[HDNodeWallet_fromExtendedKey]] or by
|
||||
* [nuetering](HDNodeWallet-neuter) a [[HDNodeWallet]].
|
||||
*/
|
||||
export class HDNodeVoidWallet extends VoidSigner {
|
||||
/**
|
||||
* The compressed public key.
|
||||
*/
|
||||
readonly publicKey!: string;
|
||||
|
||||
/**
|
||||
* The fingerprint.
|
||||
*
|
||||
* A fingerprint allows quick qay to detect parent and child nodes,
|
||||
* but developers should be prepared to deal with collisions as it
|
||||
* is only 4 bytes.
|
||||
*/
|
||||
readonly fingerprint!: string;
|
||||
|
||||
/**
|
||||
* The parent node fingerprint.
|
||||
*/
|
||||
readonly parentFingerprint!: string;
|
||||
|
||||
/**
|
||||
* The chaincode, which is effectively a public key used
|
||||
* to derive children.
|
||||
*/
|
||||
readonly chainCode!: string;
|
||||
|
||||
/**
|
||||
* The derivation path of this wallet.
|
||||
*
|
||||
* Since extended keys do not provider full path details, this
|
||||
* may be ``null``, if instantiated from a source that does not
|
||||
* enocde it.
|
||||
*/
|
||||
readonly path!: null | string;
|
||||
|
||||
/**
|
||||
* The child index of this wallet. Values over ``2 *\* 31`` indicate
|
||||
* the node is hardened.
|
||||
*/
|
||||
readonly index!: number;
|
||||
|
||||
/**
|
||||
* The depth of this wallet, which is the number of components
|
||||
* in its path.
|
||||
*/
|
||||
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");
|
||||
|
||||
defineProperties<HDNodeVoidWallet>(this, { publicKey });
|
||||
|
||||
const fingerprint = dataSlice(ripemd160(sha256(publicKey)), 0, 4);
|
||||
defineProperties<HDNodeVoidWallet>(this, {
|
||||
publicKey, fingerprint, parentFingerprint, chainCode, path, index, depth
|
||||
});
|
||||
}
|
||||
|
||||
connect(provider: null | Provider): HDNodeVoidWallet {
|
||||
return new HDNodeVoidWallet(_guard, this.address, this.publicKey,
|
||||
this.parentFingerprint, this.chainCode, this.path, this.index, this.depth, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* The extended key.
|
||||
*
|
||||
* This key will begin with the prefix ``xpub`` and can be used to
|
||||
* reconstruct this neutered key to derive its children addresses.
|
||||
*/
|
||||
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
|
||||
// we should always use mainnet, and use BIP-44 to derive the network
|
||||
// - Mainnet: public=0x0488B21E, private=0x0488ADE4
|
||||
// - Testnet: public=0x043587CF, private=0x04358394
|
||||
|
||||
assert(this.depth < 256, "Depth too deep", "UNSUPPORTED_OPERATION", { operation: "extendedKey" });
|
||||
|
||||
return encodeBase58Check(concat([
|
||||
"0x0488B21E",
|
||||
zpad(this.depth, 1),
|
||||
this.parentFingerprint,
|
||||
zpad(this.index, 4),
|
||||
this.chainCode,
|
||||
this.publicKey,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this wallet has a path, providing a Type Guard
|
||||
* that the path is non-null.
|
||||
*/
|
||||
hasPath(): this is { path: string } { return (this.path != null); }
|
||||
|
||||
/**
|
||||
* Return the child for %%index%%.
|
||||
*/
|
||||
deriveChild(_index: Numeric): HDNodeVoidWallet {
|
||||
const index = getNumber(_index, "index");
|
||||
assertArgument(index <= 0xffffffff, "invalid index", "index", index);
|
||||
|
||||
// Base path
|
||||
let path = this.path;
|
||||
if (path) {
|
||||
path += "/" + (index & ~HardenedBit);
|
||||
if (index & HardenedBit) { path += "'"; }
|
||||
}
|
||||
|
||||
const { IR, IL } = ser_I(index, this.chainCode, this.publicKey, null);
|
||||
const Ki = SigningKey.addPoints(IL, this.publicKey, true);
|
||||
|
||||
const address = computeAddress(Ki);
|
||||
|
||||
return new HDNodeVoidWallet(_guard, address, Ki, this.fingerprint, hexlify(IR),
|
||||
path, index, this.depth + 1, this.provider);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the signer for %%path%% from this node.
|
||||
*/
|
||||
derivePath(path: string): HDNodeVoidWallet {
|
||||
return derivePath<HDNodeVoidWallet>(this, path);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class HDNodeWalletManager {
|
||||
#root: HDNodeWallet;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
getSigner(index?: number): HDNodeWallet {
|
||||
return this.#root.deriveChild((index == null) ? 0: index);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the [[link-bip-32]] path for the account at %%index%%.
|
||||
*
|
||||
* This is the pattern used by wallets like Ledger.
|
||||
*
|
||||
* There is also an [alternate pattern](getIndexedAccountPath) used by
|
||||
* some software.
|
||||
*/
|
||||
export function getAccountPath(_index: Numeric): string {
|
||||
const index = getNumber(_index, "index");
|
||||
assertArgument(index >= 0 && index < HardenedBit, "invalid account index", "index", index);
|
||||
return `m/44'/60'/${ index }'/0/0`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path using an alternative pattern for deriving accounts,
|
||||
* at %%index%%.
|
||||
*
|
||||
* This derivation path uses the //index// component rather than the
|
||||
* //account// component to derive sequential accounts.
|
||||
*
|
||||
* This is the pattern used by wallets like MetaMask.
|
||||
*/
|
||||
export function getIndexedAccountPath(_index: Numeric): string {
|
||||
const index = getNumber(_index, "index");
|
||||
assertArgument(index >= 0 && index < HardenedBit, "invalid account index", "index", index);
|
||||
return `m/44'/60'/0'/0/${ index}`;
|
||||
}
|
||||
|
||||
47
dev/env/node_modules/ethers/src.ts/wallet/index.ts
generated
vendored
Executable file
47
dev/env/node_modules/ethers/src.ts/wallet/index.ts
generated
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* When interacting with Ethereum, it is necessary to use a private
|
||||
* key authenticate actions by signing a payload.
|
||||
*
|
||||
* Wallets are the simplest way to expose the concept of an
|
||||
* //Externally Owner Account// (EOA) as it wraps a private key
|
||||
* and supports high-level methods to sign common types of interaction
|
||||
* and send transactions.
|
||||
*
|
||||
* The class most developers will want to use is [[Wallet]], which
|
||||
* can load a private key directly or from any common wallet format.
|
||||
*
|
||||
* The [[HDNodeWallet]] can be used when it is necessary to access
|
||||
* low-level details of how an HD wallets are derived, exported
|
||||
* or imported.
|
||||
*
|
||||
* @_section: api/wallet:Wallets [about-wallets]
|
||||
*/
|
||||
|
||||
export { BaseWallet } from "./base-wallet.js";
|
||||
|
||||
export {
|
||||
defaultPath,
|
||||
|
||||
getAccountPath, getIndexedAccountPath,
|
||||
|
||||
HDNodeWallet,
|
||||
HDNodeVoidWallet,
|
||||
} from "./hdwallet.js";
|
||||
|
||||
export { isCrowdsaleJson, decryptCrowdsaleJson } from "./json-crowdsale.js";
|
||||
|
||||
export {
|
||||
isKeystoreJson,
|
||||
decryptKeystoreJsonSync, decryptKeystoreJson,
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync
|
||||
} from "./json-keystore.js";
|
||||
|
||||
export { Mnemonic } from "./mnemonic.js";
|
||||
|
||||
export { Wallet } from "./wallet.js";
|
||||
|
||||
|
||||
export type { CrowdsaleAccount } from "./json-crowdsale.js";
|
||||
export type {
|
||||
KeystoreAccount, EncryptOptions
|
||||
} from "./json-keystore.js"
|
||||
74
dev/env/node_modules/ethers/src.ts/wallet/json-crowdsale.ts
generated
vendored
Executable file
74
dev/env/node_modules/ethers/src.ts/wallet/json-crowdsale.ts
generated
vendored
Executable file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @_subsection: api/wallet:JSON Wallets [json-wallets]
|
||||
*/
|
||||
|
||||
import { CBC, pkcs7Strip } from "aes-js";
|
||||
|
||||
import { getAddress } from "../address/index.js";
|
||||
import { pbkdf2 } from "../crypto/index.js";
|
||||
import { id } from "../hash/index.js";
|
||||
import { getBytes, assertArgument } from "../utils/index.js";
|
||||
|
||||
import { getPassword, looseArrayify, spelunk } from "./utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* The data stored within a JSON Crowdsale wallet is fairly
|
||||
* minimal.
|
||||
*/
|
||||
export type CrowdsaleAccount = {
|
||||
privateKey: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%json%% is a valid JSON Crowdsale wallet.
|
||||
*/
|
||||
export function isCrowdsaleJson(json: string): boolean {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
if (data.encseed) { return true; }
|
||||
} catch (error) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
// See: https://github.com/ethereum/pyethsaletool
|
||||
|
||||
/**
|
||||
* Before Ethereum launched, it was necessary to create a wallet
|
||||
* format for backers to use, which would be used to receive ether
|
||||
* as a reward for contributing to the project.
|
||||
*
|
||||
* The [[link-crowdsale]] format is now obsolete, but it is still
|
||||
* useful to support and the additional code is fairly trivial as
|
||||
* all the primitives required are used through core portions of
|
||||
* the library.
|
||||
*/
|
||||
export function decryptCrowdsaleJson(json: string, _password: string | Uint8Array): CrowdsaleAccount {
|
||||
const data = JSON.parse(json);
|
||||
const password = getPassword(_password);
|
||||
|
||||
// Ethereum Address
|
||||
const address = getAddress(spelunk(data, "ethaddr:string!"));
|
||||
|
||||
// Encrypted Seed
|
||||
const encseed = looseArrayify(spelunk(data, "encseed:string!"));
|
||||
assertArgument(encseed && (encseed.length % 16) === 0, "invalid encseed", "json", json);
|
||||
|
||||
const key = getBytes(pbkdf2(password, password, 2000, 32, "sha256")).slice(0, 16);
|
||||
|
||||
const iv = encseed.slice(0, 16);
|
||||
const encryptedSeed = encseed.slice(16);
|
||||
|
||||
// Decrypt the seed
|
||||
const aesCbc = new CBC(key, iv);
|
||||
const seed = pkcs7Strip(getBytes(aesCbc.decrypt(encryptedSeed)));
|
||||
|
||||
// This wallet format is weird... Convert the binary encoded hex to a string.
|
||||
let seedHex = "";
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
seedHex += String.fromCharCode(seed[i]);
|
||||
}
|
||||
|
||||
return { address, privateKey: id(seedHex) };
|
||||
}
|
||||
389
dev/env/node_modules/ethers/src.ts/wallet/json-keystore.ts
generated
vendored
Executable file
389
dev/env/node_modules/ethers/src.ts/wallet/json-keystore.ts
generated
vendored
Executable file
@@ -0,0 +1,389 @@
|
||||
/**
|
||||
* The JSON Wallet formats allow a simple way to store the private
|
||||
* keys needed in Ethereum along with related information and allows
|
||||
* for extensible forms of encryption.
|
||||
*
|
||||
* These utilities facilitate decrypting and encrypting the most common
|
||||
* JSON Wallet formats.
|
||||
*
|
||||
* @_subsection: api/wallet:JSON Wallets [json-wallets]
|
||||
*/
|
||||
|
||||
import { CTR } from "aes-js";
|
||||
|
||||
import { getAddress } from "../address/index.js";
|
||||
import { keccak256, pbkdf2, randomBytes, scrypt, scryptSync } from "../crypto/index.js";
|
||||
import { computeAddress } from "../transaction/index.js";
|
||||
import {
|
||||
concat, getBytes, hexlify, uuidV4, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
|
||||
import { getPassword, spelunk, zpad } from "./utils.js";
|
||||
|
||||
import type { ProgressCallback } from "../crypto/index.js";
|
||||
import type { BytesLike } from "../utils/index.js";
|
||||
|
||||
import { version } from "../_version.js";
|
||||
|
||||
|
||||
const defaultPath = "m/44'/60'/0'/0/0";
|
||||
|
||||
/**
|
||||
* The contents of a JSON Keystore Wallet.
|
||||
*/
|
||||
export type KeystoreAccount = {
|
||||
address: string;
|
||||
privateKey: string;
|
||||
mnemonic?: {
|
||||
path?: string;
|
||||
locale?: string;
|
||||
entropy: string;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The parameters to use when encrypting a JSON Keystore Wallet.
|
||||
*/
|
||||
export type EncryptOptions = {
|
||||
progressCallback?: ProgressCallback;
|
||||
iv?: BytesLike;
|
||||
entropy?: BytesLike;
|
||||
client?: string;
|
||||
salt?: BytesLike;
|
||||
uuid?: string;
|
||||
scrypt?: {
|
||||
N?: number;
|
||||
r?: number;
|
||||
p?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%json%% is a valid JSON Keystore Wallet.
|
||||
*/
|
||||
export function isKeystoreJson(json: string): boolean {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
const version = ((data.version != null) ? parseInt(data.version): 0);
|
||||
if (version === 3) { return true; }
|
||||
} catch (error) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
function decrypt(data: any, key: Uint8Array, ciphertext: Uint8Array): string {
|
||||
const cipher = spelunk<string>(data, "crypto.cipher:string");
|
||||
if (cipher === "aes-128-ctr") {
|
||||
const iv = spelunk<Uint8Array>(data, "crypto.cipherparams.iv:data!")
|
||||
const aesCtr = new CTR(key, iv);
|
||||
return hexlify(aesCtr.decrypt(ciphertext));
|
||||
}
|
||||
|
||||
assert(false, "unsupported cipher", "UNSUPPORTED_OPERATION", {
|
||||
operation: "decrypt"
|
||||
});
|
||||
}
|
||||
|
||||
function getAccount(data: any, _key: string): KeystoreAccount {
|
||||
const key = getBytes(_key);
|
||||
const ciphertext = spelunk<Uint8Array>(data, "crypto.ciphertext:data!");
|
||||
|
||||
const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2);
|
||||
assertArgument(computedMAC === spelunk<string>(data, "crypto.mac:string!").toLowerCase(),
|
||||
"incorrect password", "password", "[ REDACTED ]");
|
||||
|
||||
const privateKey = decrypt(data, key.slice(0, 16), ciphertext);
|
||||
|
||||
const address = computeAddress(privateKey);
|
||||
if (data.address) {
|
||||
let check = data.address.toLowerCase();
|
||||
if (!check.startsWith("0x")) { check = "0x" + check; }
|
||||
|
||||
assertArgument(getAddress(check) === address, "keystore address/privateKey mismatch", "address", data.address);
|
||||
}
|
||||
|
||||
const account: KeystoreAccount = { address, privateKey };
|
||||
|
||||
// Version 0.1 x-ethers metadata must contain an encrypted mnemonic phrase
|
||||
const version = spelunk(data, "x-ethers.version:string");
|
||||
if (version === "0.1") {
|
||||
const mnemonicKey = key.slice(32, 64);
|
||||
|
||||
const mnemonicCiphertext = spelunk<Uint8Array>(data, "x-ethers.mnemonicCiphertext:data!");
|
||||
const mnemonicIv = spelunk<Uint8Array>(data, "x-ethers.mnemonicCounter:data!");
|
||||
|
||||
const mnemonicAesCtr = new CTR(mnemonicKey, mnemonicIv);
|
||||
|
||||
account.mnemonic = {
|
||||
path: (spelunk<null | string>(data, "x-ethers.path:string") || defaultPath),
|
||||
locale: (spelunk<null | string>(data, "x-ethers.locale:string") || "en"),
|
||||
entropy: hexlify(getBytes(mnemonicAesCtr.decrypt(mnemonicCiphertext)))
|
||||
};
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
type ScryptParams = {
|
||||
name: "scrypt";
|
||||
salt: Uint8Array;
|
||||
N: number;
|
||||
r: number;
|
||||
p: number;
|
||||
dkLen: number;
|
||||
};
|
||||
|
||||
type KdfParams = ScryptParams | {
|
||||
name: "pbkdf2";
|
||||
salt: Uint8Array;
|
||||
count: number;
|
||||
dkLen: number;
|
||||
algorithm: "sha256" | "sha512";
|
||||
};
|
||||
|
||||
function getDecryptKdfParams<T>(data: any): KdfParams {
|
||||
const kdf = spelunk(data, "crypto.kdf:string");
|
||||
if (kdf && typeof(kdf) === "string") {
|
||||
if (kdf.toLowerCase() === "scrypt") {
|
||||
const salt = spelunk<Uint8Array>(data, "crypto.kdfparams.salt:data!");
|
||||
const N = spelunk<number>(data, "crypto.kdfparams.n:int!");
|
||||
const r = spelunk<number>(data, "crypto.kdfparams.r:int!");
|
||||
const p = spelunk<number>(data, "crypto.kdfparams.p:int!");
|
||||
|
||||
// Make sure N is a power of 2
|
||||
assertArgument(N > 0 && (N & (N - 1)) === 0, "invalid kdf.N", "kdf.N", N);
|
||||
assertArgument(r > 0 && p > 0, "invalid kdf", "kdf", kdf);
|
||||
|
||||
const dkLen = spelunk<number>(data, "crypto.kdfparams.dklen:int!");
|
||||
assertArgument(dkLen === 32, "invalid kdf.dklen", "kdf.dflen", dkLen);
|
||||
|
||||
return { name: "scrypt", salt, N, r, p, dkLen: 64 };
|
||||
|
||||
} else if (kdf.toLowerCase() === "pbkdf2") {
|
||||
|
||||
const salt = spelunk<Uint8Array>(data, "crypto.kdfparams.salt:data!");
|
||||
|
||||
const prf = spelunk<string>(data, "crypto.kdfparams.prf:string!");
|
||||
const algorithm = prf.split("-").pop();
|
||||
assertArgument(algorithm === "sha256" || algorithm === "sha512", "invalid kdf.pdf", "kdf.pdf", prf);
|
||||
|
||||
const count = spelunk<number>(data, "crypto.kdfparams.c:int!");
|
||||
|
||||
const dkLen = spelunk<number>(data, "crypto.kdfparams.dklen:int!");
|
||||
assertArgument(dkLen === 32, "invalid kdf.dklen", "kdf.dklen", dkLen);
|
||||
|
||||
return { name: "pbkdf2", salt, count, dkLen, algorithm };
|
||||
}
|
||||
}
|
||||
|
||||
assertArgument(false, "unsupported key-derivation function", "kdf", kdf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the account details for the JSON Keystore Wallet %%json%%
|
||||
* using %%password%%.
|
||||
*
|
||||
* It is preferred to use the [async version](decryptKeystoreJson)
|
||||
* instead, which allows a [[ProgressCallback]] to keep the user informed
|
||||
* as to the decryption status.
|
||||
*
|
||||
* This method will block the event loop (freezing all UI) until decryption
|
||||
* is complete, which can take quite some time, depending on the wallet
|
||||
* paramters and platform.
|
||||
*/
|
||||
export function decryptKeystoreJsonSync(json: string, _password: string | Uint8Array): KeystoreAccount {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
const password = getPassword(_password);
|
||||
|
||||
const params = getDecryptKdfParams(data);
|
||||
if (params.name === "pbkdf2") {
|
||||
const { salt, count, dkLen, algorithm } = params;
|
||||
const key = pbkdf2(password, salt, count, dkLen, algorithm);
|
||||
return getAccount(data, key);
|
||||
}
|
||||
|
||||
assert(params.name === "scrypt", "cannot be reached", "UNKNOWN_ERROR", { params })
|
||||
|
||||
const { salt, N, r, p, dkLen } = params;
|
||||
const key = scryptSync(password, salt, N, r, p, dkLen);
|
||||
return getAccount(data, key);
|
||||
}
|
||||
|
||||
function stall(duration: number): Promise<void> {
|
||||
return new Promise((resolve) => { setTimeout(() => { resolve(); }, duration); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to the decrypted JSON Keystore Wallet %%json%% using the
|
||||
* %%password%%.
|
||||
*
|
||||
* If provided, %%progress%% will be called periodically during the
|
||||
* decrpytion to provide feedback, and if the function returns
|
||||
* ``false`` will halt decryption.
|
||||
*
|
||||
* The %%progressCallback%% will **always** receive ``0`` before
|
||||
* decryption begins and ``1`` when complete.
|
||||
*/
|
||||
export async function decryptKeystoreJson(json: string, _password: string | Uint8Array, progress?: ProgressCallback): Promise<KeystoreAccount> {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
const password = getPassword(_password);
|
||||
|
||||
const params = getDecryptKdfParams(data);
|
||||
if (params.name === "pbkdf2") {
|
||||
if (progress) {
|
||||
progress(0);
|
||||
await stall(0);
|
||||
}
|
||||
const { salt, count, dkLen, algorithm } = params;
|
||||
const key = pbkdf2(password, salt, count, dkLen, algorithm);
|
||||
if (progress) {
|
||||
progress(1);
|
||||
await stall(0);
|
||||
}
|
||||
return getAccount(data, key);
|
||||
}
|
||||
|
||||
assert(params.name === "scrypt", "cannot be reached", "UNKNOWN_ERROR", { params })
|
||||
|
||||
const { salt, N, r, p, dkLen } = params;
|
||||
const key = await scrypt(password, salt, N, r, p, dkLen, progress);
|
||||
return getAccount(data, key);
|
||||
}
|
||||
|
||||
function getEncryptKdfParams(options: EncryptOptions): ScryptParams {
|
||||
// Check/generate the salt
|
||||
const salt = (options.salt != null) ? getBytes(options.salt, "options.salt"): randomBytes(32);
|
||||
|
||||
// Override the scrypt password-based key derivation function parameters
|
||||
let N = (1 << 17), r = 8, p = 1;
|
||||
if (options.scrypt) {
|
||||
if (options.scrypt.N) { N = options.scrypt.N; }
|
||||
if (options.scrypt.r) { r = options.scrypt.r; }
|
||||
if (options.scrypt.p) { p = options.scrypt.p; }
|
||||
}
|
||||
assertArgument(typeof(N) === "number" && N > 0 && Number.isSafeInteger(N) && (BigInt(N) & BigInt(N - 1)) === BigInt(0), "invalid scrypt N parameter", "options.N", N);
|
||||
assertArgument(typeof(r) === "number" && r > 0 && Number.isSafeInteger(r), "invalid scrypt r parameter", "options.r", r);
|
||||
assertArgument(typeof(p) === "number" && p > 0 && Number.isSafeInteger(p), "invalid scrypt p parameter", "options.p", p);
|
||||
|
||||
return { name: "scrypt", dkLen: 32, salt, N, r, p };
|
||||
}
|
||||
|
||||
function _encryptKeystore(key: Uint8Array, kdf: ScryptParams, account: KeystoreAccount, options: EncryptOptions): any {
|
||||
|
||||
const privateKey = getBytes(account.privateKey, "privateKey");
|
||||
|
||||
// Override initialization vector
|
||||
const iv = (options.iv != null) ? getBytes(options.iv, "options.iv"): randomBytes(16);
|
||||
assertArgument(iv.length === 16, "invalid options.iv length", "options.iv", options.iv);
|
||||
|
||||
// Override the uuid
|
||||
const uuidRandom = (options.uuid != null) ? getBytes(options.uuid, "options.uuid"): randomBytes(16);
|
||||
assertArgument(uuidRandom.length === 16, "invalid options.uuid length", "options.uuid", options.iv);
|
||||
|
||||
// This will be used to encrypt the wallet (as per Web3 secret storage)
|
||||
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
|
||||
// - 32 bytes AES key to encrypt mnemonic with (required here to be Ethers Wallet)
|
||||
const derivedKey = key.slice(0, 16);
|
||||
const macPrefix = key.slice(16, 32);
|
||||
|
||||
// Encrypt the private key
|
||||
const aesCtr = new CTR(derivedKey, iv);
|
||||
const ciphertext = getBytes(aesCtr.encrypt(privateKey));
|
||||
|
||||
// Compute the message authentication code, used to check the password
|
||||
const mac = keccak256(concat([ macPrefix, ciphertext ]))
|
||||
|
||||
// See: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
const data: { [key: string]: any } = {
|
||||
address: account.address.substring(2).toLowerCase(),
|
||||
id: uuidV4(uuidRandom),
|
||||
version: 3,
|
||||
Crypto: {
|
||||
cipher: "aes-128-ctr",
|
||||
cipherparams: {
|
||||
iv: hexlify(iv).substring(2),
|
||||
},
|
||||
ciphertext: hexlify(ciphertext).substring(2),
|
||||
kdf: "scrypt",
|
||||
kdfparams: {
|
||||
salt: hexlify(kdf.salt).substring(2),
|
||||
n: kdf.N,
|
||||
dklen: 32,
|
||||
p: kdf.p,
|
||||
r: kdf.r
|
||||
},
|
||||
mac: mac.substring(2)
|
||||
}
|
||||
};
|
||||
|
||||
// If we have a mnemonic, encrypt it into the JSON wallet
|
||||
if (account.mnemonic) {
|
||||
const client = (options.client != null) ? options.client: `ethers/${ version }`;
|
||||
|
||||
const path = account.mnemonic.path || defaultPath;
|
||||
const locale = account.mnemonic.locale || "en";
|
||||
|
||||
const mnemonicKey = key.slice(32, 64);
|
||||
|
||||
const entropy = getBytes(account.mnemonic.entropy, "account.mnemonic.entropy");
|
||||
const mnemonicIv = randomBytes(16);
|
||||
const mnemonicAesCtr = new CTR(mnemonicKey, mnemonicIv);
|
||||
const mnemonicCiphertext = getBytes(mnemonicAesCtr.encrypt(entropy));
|
||||
|
||||
const now = new Date();
|
||||
const timestamp = (now.getUTCFullYear() + "-" +
|
||||
zpad(now.getUTCMonth() + 1, 2) + "-" +
|
||||
zpad(now.getUTCDate(), 2) + "T" +
|
||||
zpad(now.getUTCHours(), 2) + "-" +
|
||||
zpad(now.getUTCMinutes(), 2) + "-" +
|
||||
zpad(now.getUTCSeconds(), 2) + ".0Z");
|
||||
const gethFilename = ("UTC--" + timestamp + "--" + data.address);
|
||||
|
||||
data["x-ethers"] = {
|
||||
client, gethFilename, path, locale,
|
||||
mnemonicCounter: hexlify(mnemonicIv).substring(2),
|
||||
mnemonicCiphertext: hexlify(mnemonicCiphertext).substring(2),
|
||||
version: "0.1"
|
||||
};
|
||||
}
|
||||
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON Keystore Wallet for %%account%% encrypted with
|
||||
* %%password%%.
|
||||
*
|
||||
* The %%options%% can be used to tune the password-based key
|
||||
* derivation function parameters, explicitly set the random values
|
||||
* used. Any provided [[ProgressCallback]] is ignord.
|
||||
*/
|
||||
export function encryptKeystoreJsonSync(account: KeystoreAccount, password: string | Uint8Array, options?: EncryptOptions): string {
|
||||
if (options == null) { options = { }; }
|
||||
|
||||
const passwordBytes = getPassword(password);
|
||||
const kdf = getEncryptKdfParams(options);
|
||||
const key = scryptSync(passwordBytes, kdf.salt, kdf.N, kdf.r, kdf.p, 64);
|
||||
return _encryptKeystore(getBytes(key), kdf, account, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved to the JSON Keystore Wallet for %%account%% encrypted
|
||||
* with %%password%%.
|
||||
*
|
||||
* The %%options%% can be used to tune the password-based key
|
||||
* derivation function parameters, explicitly set the random values
|
||||
* used and provide a [[ProgressCallback]] to receive periodic updates
|
||||
* on the completion status..
|
||||
*/
|
||||
export async function encryptKeystoreJson(account: KeystoreAccount, password: string | Uint8Array, options?: EncryptOptions): Promise<string> {
|
||||
if (options == null) { options = { }; }
|
||||
|
||||
const passwordBytes = getPassword(password);
|
||||
const kdf = getEncryptKdfParams(options);
|
||||
const key = await scrypt(passwordBytes, kdf.salt, kdf.N, kdf.r, kdf.p, 64, options.progressCallback);
|
||||
return _encryptKeystore(getBytes(key), kdf, account, options);
|
||||
}
|
||||
|
||||
203
dev/env/node_modules/ethers/src.ts/wallet/mnemonic.ts
generated
vendored
Executable file
203
dev/env/node_modules/ethers/src.ts/wallet/mnemonic.ts
generated
vendored
Executable file
@@ -0,0 +1,203 @@
|
||||
import { pbkdf2, sha256 } from "../crypto/index.js";
|
||||
import {
|
||||
defineProperties, getBytes, hexlify, assertNormalize, assertPrivate, assertArgument, toUtf8Bytes
|
||||
} from "../utils/index.js";
|
||||
import { LangEn } from "../wordlists/lang-en.js";
|
||||
|
||||
import type { BytesLike } from "../utils/index.js";
|
||||
import type { Wordlist } from "../wordlists/index.js";
|
||||
|
||||
|
||||
// Returns a byte with the MSB bits set
|
||||
function getUpperMask(bits: number): number {
|
||||
return ((1 << bits) - 1) << (8 - bits) & 0xff;
|
||||
}
|
||||
|
||||
// Returns a byte with the LSB bits set
|
||||
function getLowerMask(bits: number): number {
|
||||
return ((1 << bits) - 1) & 0xff;
|
||||
}
|
||||
|
||||
|
||||
function mnemonicToEntropy(mnemonic: string, wordlist?: null | Wordlist): string {
|
||||
assertNormalize("NFKD");
|
||||
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
|
||||
const words = wordlist.split(mnemonic);
|
||||
assertArgument((words.length % 3) === 0 && words.length >= 12 && words.length <= 24,
|
||||
"invalid mnemonic length", "mnemonic", "[ REDACTED ]");
|
||||
|
||||
const entropy = new Uint8Array(Math.ceil(11 * words.length / 8));
|
||||
|
||||
let offset = 0;
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
let index = wordlist.getWordIndex(words[i].normalize("NFKD"));
|
||||
assertArgument(index >= 0, `invalid mnemonic word at index ${ i }`, "mnemonic", "[ REDACTED ]");
|
||||
|
||||
for (let bit = 0; bit < 11; bit++) {
|
||||
if (index & (1 << (10 - bit))) {
|
||||
entropy[offset >> 3] |= (1 << (7 - (offset % 8)));
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
const entropyBits = 32 * words.length / 3;
|
||||
|
||||
|
||||
const checksumBits = words.length / 3;
|
||||
const checksumMask = getUpperMask(checksumBits);
|
||||
|
||||
const checksum = getBytes(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask;
|
||||
|
||||
assertArgument(checksum === (entropy[entropy.length - 1] & checksumMask),
|
||||
"invalid mnemonic checksum", "mnemonic", "[ REDACTED ]");
|
||||
|
||||
return hexlify(entropy.slice(0, entropyBits / 8));
|
||||
}
|
||||
|
||||
function entropyToMnemonic(entropy: Uint8Array, wordlist?: null | Wordlist): string {
|
||||
|
||||
assertArgument((entropy.length % 4) === 0 && entropy.length >= 16 && entropy.length <= 32,
|
||||
"invalid entropy size", "entropy", "[ REDACTED ]");
|
||||
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
|
||||
const indices: Array<number> = [ 0 ];
|
||||
|
||||
let remainingBits = 11;
|
||||
for (let i = 0; i < entropy.length; i++) {
|
||||
|
||||
// Consume the whole byte (with still more to go)
|
||||
if (remainingBits > 8) {
|
||||
indices[indices.length - 1] <<= 8;
|
||||
indices[indices.length - 1] |= entropy[i];
|
||||
|
||||
remainingBits -= 8;
|
||||
|
||||
// This byte will complete an 11-bit index
|
||||
} else {
|
||||
indices[indices.length - 1] <<= remainingBits;
|
||||
indices[indices.length - 1] |= entropy[i] >> (8 - remainingBits);
|
||||
|
||||
// Start the next word
|
||||
indices.push(entropy[i] & getLowerMask(8 - remainingBits));
|
||||
|
||||
remainingBits += 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the checksum bits
|
||||
const checksumBits = entropy.length / 4;
|
||||
const checksum = parseInt(sha256(entropy).substring(2, 4), 16) & getUpperMask(checksumBits);
|
||||
|
||||
// Shift the checksum into the word indices
|
||||
indices[indices.length - 1] <<= checksumBits;
|
||||
indices[indices.length - 1] |= (checksum >> (8 - checksumBits));
|
||||
|
||||
return wordlist.join(indices.map((index) => (<Wordlist>wordlist).getWord(index)));
|
||||
}
|
||||
|
||||
const _guard = { };
|
||||
|
||||
/**
|
||||
* A **Mnemonic** wraps all properties required to compute [[link-bip-39]]
|
||||
* seeds and convert between phrases and entropy.
|
||||
*/
|
||||
export class Mnemonic {
|
||||
/**
|
||||
* The mnemonic phrase of 12, 15, 18, 21 or 24 words.
|
||||
*
|
||||
* Use the [[wordlist]] ``split`` method to get the individual words.
|
||||
*/
|
||||
readonly phrase!: string;
|
||||
|
||||
/**
|
||||
* The password used for this mnemonic. If no password is used this
|
||||
* is the empty string (i.e. ``""``) as per the specification.
|
||||
*/
|
||||
readonly password!: string;
|
||||
|
||||
/**
|
||||
* The wordlist for this mnemonic.
|
||||
*/
|
||||
readonly wordlist!: Wordlist;
|
||||
|
||||
/**
|
||||
* The underlying entropy which the mnemonic encodes.
|
||||
*/
|
||||
readonly entropy!: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, entropy: string, phrase: string, password?: null | string, wordlist?: null | Wordlist) {
|
||||
if (password == null) { password = ""; }
|
||||
if (wordlist == null) { wordlist = LangEn.wordlist(); }
|
||||
assertPrivate(guard, _guard, "Mnemonic");
|
||||
defineProperties<Mnemonic>(this, { phrase, password, wordlist, entropy });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the seed for the mnemonic.
|
||||
*/
|
||||
computeSeed(): string {
|
||||
const salt = toUtf8Bytes("mnemonic" + this.password, "NFKD");
|
||||
return pbkdf2(toUtf8Bytes(this.phrase, "NFKD"), salt, 2048, 64, "sha512");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Mnemonic for the %%phrase%%.
|
||||
*
|
||||
* The default %%password%% is the empty string and the default
|
||||
* wordlist is the [English wordlists](LangEn).
|
||||
*/
|
||||
static fromPhrase(phrase: string, password?: null | string, wordlist?: null | Wordlist): Mnemonic {
|
||||
// Normalize the case and space; throws if invalid
|
||||
const entropy = mnemonicToEntropy(phrase, wordlist);
|
||||
phrase = entropyToMnemonic(getBytes(entropy), wordlist);
|
||||
return new Mnemonic(_guard, entropy, phrase, password, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new **Mnemonic** from the %%entropy%%.
|
||||
*
|
||||
* The default %%password%% is the empty string and the default
|
||||
* wordlist is the [English wordlists](LangEn).
|
||||
*/
|
||||
static fromEntropy(_entropy: BytesLike, password?: null | string, wordlist?: null | Wordlist): Mnemonic {
|
||||
const entropy = getBytes(_entropy, "entropy");
|
||||
const phrase = entropyToMnemonic(entropy, wordlist);
|
||||
return new Mnemonic(_guard, hexlify(entropy), phrase, password, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the phrase for %%mnemonic%%.
|
||||
*/
|
||||
static entropyToPhrase(_entropy: BytesLike, wordlist?: null | Wordlist): string {
|
||||
const entropy = getBytes(_entropy, "entropy");
|
||||
return entropyToMnemonic(entropy, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entropy for %%phrase%%.
|
||||
*/
|
||||
static phraseToEntropy(phrase: string, wordlist?: null | Wordlist): string {
|
||||
return mnemonicToEntropy(phrase, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%phrase%% is a valid [[link-bip-39]] phrase.
|
||||
*
|
||||
* This checks all the provided words belong to the %%wordlist%%,
|
||||
* that the length is valid and the checksum is correct.
|
||||
*/
|
||||
static isValidMnemonic(phrase: string, wordlist?: null | Wordlist): boolean {
|
||||
try {
|
||||
mnemonicToEntropy(phrase, wordlist);
|
||||
return true;
|
||||
} catch (error) { }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
147
dev/env/node_modules/ethers/src.ts/wallet/utils.ts
generated
vendored
Executable file
147
dev/env/node_modules/ethers/src.ts/wallet/utils.ts
generated
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @_ignore
|
||||
*/
|
||||
|
||||
import {
|
||||
getBytesCopy, assertArgument, toUtf8Bytes
|
||||
} from "../utils/index.js";
|
||||
|
||||
export function looseArrayify(hexString: string): Uint8Array {
|
||||
if (typeof(hexString) === "string" && !hexString.startsWith("0x")) {
|
||||
hexString = "0x" + hexString;
|
||||
}
|
||||
return getBytesCopy(hexString);
|
||||
}
|
||||
|
||||
export function zpad(value: String | number, length: number): String {
|
||||
value = String(value);
|
||||
while (value.length < length) { value = '0' + value; }
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getPassword(password: string | Uint8Array): Uint8Array {
|
||||
if (typeof(password) === 'string') {
|
||||
return toUtf8Bytes(password, "NFKC");
|
||||
}
|
||||
return getBytesCopy(password);
|
||||
}
|
||||
|
||||
export function spelunk<T>(object: any, _path: string): T {
|
||||
|
||||
const match = _path.match(/^([a-z0-9$_.-]*)(:([a-z]+))?(!)?$/i);
|
||||
assertArgument(match != null, "invalid path", "path", _path);
|
||||
|
||||
const path = match[1];
|
||||
const type = match[3];
|
||||
const reqd = (match[4] === "!");
|
||||
|
||||
let cur = object;
|
||||
for (const comp of path.toLowerCase().split('.')) {
|
||||
|
||||
// Search for a child object with a case-insensitive matching key
|
||||
if (Array.isArray(cur)) {
|
||||
if (!comp.match(/^[0-9]+$/)) { break; }
|
||||
cur = cur[parseInt(comp)];
|
||||
|
||||
} else if (typeof(cur) === "object") {
|
||||
let found: any = null;
|
||||
for (const key in cur) {
|
||||
if (key.toLowerCase() === comp) {
|
||||
found = cur[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur = found;
|
||||
|
||||
} else {
|
||||
cur = null;
|
||||
}
|
||||
|
||||
if (cur == null) { break; }
|
||||
}
|
||||
|
||||
assertArgument(!reqd || cur != null, "missing required value", "path", path);
|
||||
|
||||
if (type && cur != null) {
|
||||
if (type === "int") {
|
||||
if (typeof(cur) === "string" && cur.match(/^-?[0-9]+$/)) {
|
||||
return <T><unknown>parseInt(cur);
|
||||
} else if (Number.isSafeInteger(cur)) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "number") {
|
||||
if (typeof(cur) === "string" && cur.match(/^-?[0-9.]*$/)) {
|
||||
return <T><unknown>parseFloat(cur);
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "data") {
|
||||
if (typeof(cur) === "string") { return <T><unknown>looseArrayify(cur); }
|
||||
}
|
||||
|
||||
if (type === "array" && Array.isArray(cur)) { return <T><unknown>cur; }
|
||||
if (type === typeof(cur)) { return cur; }
|
||||
|
||||
assertArgument(false, `wrong type found for ${ type } `, "path", path);
|
||||
}
|
||||
|
||||
return cur;
|
||||
}
|
||||
/*
|
||||
export function follow(object: any, path: string): null | string {
|
||||
let currentChild = object;
|
||||
|
||||
for (const comp of path.toLowerCase().split('/')) {
|
||||
|
||||
// Search for a child object with a case-insensitive matching key
|
||||
let matchingChild = null;
|
||||
for (const key in currentChild) {
|
||||
if (key.toLowerCase() === comp) {
|
||||
matchingChild = currentChild[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingChild === null) { return null; }
|
||||
|
||||
currentChild = matchingChild;
|
||||
}
|
||||
|
||||
return currentChild;
|
||||
}
|
||||
|
||||
// "path/to/something:type!"
|
||||
export function followRequired(data: any, path: string): string {
|
||||
const value = follow(data, path);
|
||||
if (value != null) { return value; }
|
||||
return logger.throwArgumentError("invalid value", `data:${ path }`,
|
||||
JSON.stringify(data));
|
||||
}
|
||||
*/
|
||||
// See: https://www.ietf.org/rfc/rfc4122.txt (Section 4.4)
|
||||
/*
|
||||
export function uuidV4(randomBytes: BytesLike): string {
|
||||
const bytes = getBytes(randomBytes, "randomBytes");
|
||||
|
||||
// Section: 4.1.3:
|
||||
// - time_hi_and_version[12:16] = 0b0100
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
|
||||
// Section 4.4
|
||||
// - clock_seq_hi_and_reserved[6] = 0b0
|
||||
// - clock_seq_hi_and_reserved[7] = 0b1
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
|
||||
const value = hexlify(bytes);
|
||||
|
||||
return [
|
||||
value.substring(2, 10),
|
||||
value.substring(10, 14),
|
||||
value.substring(14, 18),
|
||||
value.substring(18, 22),
|
||||
value.substring(22, 34),
|
||||
].join("-");
|
||||
}
|
||||
*/
|
||||
163
dev/env/node_modules/ethers/src.ts/wallet/wallet.ts
generated
vendored
Executable file
163
dev/env/node_modules/ethers/src.ts/wallet/wallet.ts
generated
vendored
Executable file
@@ -0,0 +1,163 @@
|
||||
import { SigningKey } from "../crypto/index.js";
|
||||
import { assertArgument } from "../utils/index.js";
|
||||
|
||||
import { BaseWallet } from "./base-wallet.js";
|
||||
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";
|
||||
|
||||
import type { ProgressCallback } from "../crypto/index.js";
|
||||
import type { Provider } from "../providers/index.js";
|
||||
|
||||
import type { CrowdsaleAccount } from "./json-crowdsale.js";
|
||||
import type { KeystoreAccount } from "./json-keystore.js";
|
||||
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Create a new wallet for the private %%key%%, optionally connected
|
||||
* to %%provider%%.
|
||||
*/
|
||||
constructor(key: string | SigningKey, provider?: null | Provider) {
|
||||
if (typeof(key) === "string" && !key.startsWith("0x")) {
|
||||
key = "0x" + key;
|
||||
}
|
||||
|
||||
let signingKey = (typeof(key) === "string") ? new SigningKey(key): key;
|
||||
super(signingKey, provider);
|
||||
}
|
||||
|
||||
connect(provider: null | Provider): Wallet {
|
||||
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 ]");
|
||||
|
||||
if ("mnemonic" in account && account.mnemonic && account.mnemonic.locale === "en") {
|
||||
const mnemonic = Mnemonic.fromEntropy(account.mnemonic.entropy);
|
||||
const wallet = HDNodeWallet.fromMnemonic(mnemonic, account.mnemonic.path);
|
||||
if (wallet.address === account.address && wallet.privateKey === account.privateKey) {
|
||||
return wallet;
|
||||
}
|
||||
console.log("WARNING: JSON mismatch address/privateKey != mnemonic; fallback onto private key");
|
||||
}
|
||||
|
||||
const wallet = new Wallet(account.privateKey);
|
||||
|
||||
assertArgument(wallet.address === account.address,
|
||||
"address/privateKey mismatch", "json", "[ REDACTED ]");
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (asynchronously) a **Wallet** by decrypting the %%json%%
|
||||
* with %%password%%.
|
||||
*
|
||||
* If %%progress%% is provided, it is called periodically during
|
||||
* decryption so that any UI can be updated.
|
||||
*/
|
||||
static async fromEncryptedJson(json: string, password: Uint8Array | string, progress?: ProgressCallback): Promise<HDNodeWallet | Wallet> {
|
||||
let account: null | CrowdsaleAccount | KeystoreAccount = null;
|
||||
if (isKeystoreJson(json)) {
|
||||
account = await decryptKeystoreJson(json, password, progress);
|
||||
|
||||
} else if (isCrowdsaleJson(json)) {
|
||||
if (progress) { progress(0); await stall(0); }
|
||||
account = decryptCrowdsaleJson(json, password);
|
||||
if (progress) { progress(1); await stall(0); }
|
||||
|
||||
}
|
||||
|
||||
return Wallet.#fromAccount(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a **Wallet** by decrypting the %%json%% with %%password%%.
|
||||
*
|
||||
* The [[fromEncryptedJson]] method is preferred, as this method
|
||||
* will lock up and freeze the UI during decryption, which may take
|
||||
* some time.
|
||||
*/
|
||||
static fromEncryptedJsonSync(json: string, password: Uint8Array | string): HDNodeWallet | Wallet {
|
||||
let account: null | CrowdsaleAccount | KeystoreAccount = null;
|
||||
if (isKeystoreJson(json)) {
|
||||
account = decryptKeystoreJsonSync(json, password);
|
||||
} else if (isCrowdsaleJson(json)) {
|
||||
account = decryptCrowdsaleJson(json, password);
|
||||
} else {
|
||||
assertArgument(false, "invalid JSON wallet", "json", "[ REDACTED ]");
|
||||
}
|
||||
|
||||
return Wallet.#fromAccount(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new random [[HDNodeWallet]] using the available
|
||||
* [cryptographic random source](randomBytes).
|
||||
*
|
||||
* If there is no crytographic random source, this will throw.
|
||||
*/
|
||||
static createRandom(provider?: null | Provider): HDNodeWallet {
|
||||
const wallet = HDNodeWallet.createRandom();
|
||||
if (provider) { return wallet.connect(provider); }
|
||||
return wallet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [[HDNodeWallet]] for %%phrase%%.
|
||||
*/
|
||||
static fromPhrase(phrase: string, provider?: Provider): HDNodeWallet {
|
||||
const wallet = HDNodeWallet.fromPhrase(phrase);
|
||||
if (provider) { return wallet.connect(provider); }
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user