docs: added jsdocs to wallet
This commit is contained in:
parent
7dd9049993
commit
5581ec3f97
@ -1,3 +1,24 @@
|
||||
/**
|
||||
* 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 [wallets]
|
||||
*/
|
||||
|
||||
export { BaseWallet } from "./base-wallet.js";
|
||||
|
||||
export {
|
||||
defaultPath,
|
||||
|
||||
@ -13,7 +34,7 @@ export { isCrowdsaleJson, decryptCrowdsaleJson } from "./json-crowdsale.js";
|
||||
export {
|
||||
isKeystoreJson,
|
||||
decryptKeystoreJsonSync, decryptKeystoreJson,
|
||||
encryptKeystoreJson
|
||||
encryptKeystoreJson, encryptKeystoreJsonSync
|
||||
} from "./json-keystore.js";
|
||||
|
||||
export { Mnemonic } from "./mnemonic.js";
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @_subsection: api/wallet:JSON Wallets [json-wallets]
|
||||
*/
|
||||
|
||||
import { CBC, pkcs7Strip } from "aes-js";
|
||||
|
||||
import { getAddress } from "../address/index.js";
|
||||
@ -8,11 +12,18 @@ import { getBytes, assertArgument } from "../utils/index.js";
|
||||
import { getPassword, looseArrayify, spelunk } from "./utils.js";
|
||||
|
||||
|
||||
export interface CrowdsaleAccount {
|
||||
/**
|
||||
* 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);
|
||||
@ -22,6 +33,17 @@ export function isCrowdsaleJson(json: string): boolean {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
@ -1,13 +1,24 @@
|
||||
/**
|
||||
* 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, assert, assertArgument
|
||||
concat, getBytes, hexlify, uuidV4, assert, assertArgument
|
||||
} from "../utils/index.js";
|
||||
|
||||
import { getPassword, spelunk, uuidV4, zpad } from "./utils.js";
|
||||
import { getPassword, spelunk, zpad } from "./utils.js";
|
||||
|
||||
import type { ProgressCallback } from "../crypto/index.js";
|
||||
import type { BytesLike } from "../utils/index.js";
|
||||
@ -17,7 +28,9 @@ 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;
|
||||
@ -28,6 +41,9 @@ export type KeystoreAccount = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The parameters to use when encrypting a JSON Keystore Wallet.
|
||||
*/
|
||||
export type EncryptOptions = {
|
||||
progressCallback?: ProgressCallback;
|
||||
iv?: BytesLike;
|
||||
@ -42,6 +58,9 @@ export type EncryptOptions = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%json%% is a valid JSON Keystore Wallet.
|
||||
*/
|
||||
export function isKeystoreJson(json: string): boolean {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
@ -52,7 +71,7 @@ export function isKeystoreJson(json: string): boolean {
|
||||
}
|
||||
|
||||
function decrypt(data: any, key: Uint8Array, ciphertext: Uint8Array): string {
|
||||
const cipher = spelunk(data, "crypto.cipher: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);
|
||||
@ -69,7 +88,7 @@ function getAccount(data: any, _key: string): KeystoreAccount {
|
||||
const ciphertext = spelunk<Uint8Array>(data, "crypto.ciphertext:data!");
|
||||
|
||||
const computedMAC = hexlify(keccak256(concat([ key.slice(16, 32), ciphertext ]))).substring(2);
|
||||
assertArgument(computedMAC === spelunk(data, "crypto.mac:string!").toLowerCase(),
|
||||
assertArgument(computedMAC === spelunk<string>(data, "crypto.mac:string!").toLowerCase(),
|
||||
"incorrect password", "password", "[ REDACTED ]");
|
||||
|
||||
const privateKey = decrypt(data, key.slice(0, 16), ciphertext);
|
||||
@ -124,24 +143,18 @@ type KdfParams = ScryptParams | {
|
||||
function getDecryptKdfParams<T>(data: any): KdfParams {
|
||||
const kdf = spelunk(data, "crypto.kdf:string");
|
||||
if (kdf && typeof(kdf) === "string") {
|
||||
const throwError = function(name: string, value: any): never {
|
||||
assertArgument(false, "invalid key-derivation function parameters", name, value);
|
||||
}
|
||||
|
||||
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!");
|
||||
|
||||
// Check for all required parameters
|
||||
if (!N || !r || !p) { return throwError("kdf", kdf); }
|
||||
|
||||
// Make sure N is a power of 2
|
||||
if ((N & (N - 1)) !== 0) { return throwError("N", N); }
|
||||
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!");
|
||||
if (dkLen !== 32) { return throwError("dklen", dkLen); }
|
||||
assertArgument(dkLen === 32, "invalid kdf.dklen", "kdf.dflen", dkLen);
|
||||
|
||||
return { name: "scrypt", salt, N, r, p, dkLen: 64 };
|
||||
|
||||
@ -149,16 +162,14 @@ function getDecryptKdfParams<T>(data: any): KdfParams {
|
||||
|
||||
const salt = spelunk<Uint8Array>(data, "crypto.kdfparams.salt:data!");
|
||||
|
||||
const prf = spelunk(data, "crypto.kdfparams.prf:string!");
|
||||
const prf = spelunk<string>(data, "crypto.kdfparams.prf:string!");
|
||||
const algorithm = prf.split("-").pop();
|
||||
if (algorithm !== "sha256" && algorithm !== "sha512") {
|
||||
return throwError("prf", prf);
|
||||
}
|
||||
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!");
|
||||
if (dkLen !== 32) { throwError("dklen", dkLen); }
|
||||
assertArgument(dkLen === 32, "invalid kdf.dklen", "kdf.dklen", dkLen);
|
||||
|
||||
return { name: "pbkdf2", salt, count, dkLen, algorithm };
|
||||
}
|
||||
@ -168,6 +179,18 @@ function getDecryptKdfParams<T>(data: any): KdfParams {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@ -191,6 +214,17 @@ 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);
|
||||
|
||||
@ -229,24 +263,24 @@ function getEncryptKdfParams(options: EncryptOptions): ScryptParams {
|
||||
if (options.scrypt.r) { r = options.scrypt.r; }
|
||||
if (options.scrypt.p) { p = options.scrypt.p; }
|
||||
}
|
||||
assertArgument(typeof(N) === "number" && Number.isSafeInteger(N) && (BigInt(N) & BigInt(N - 1)) === BigInt(0), "invalid scrypt N parameter", "options.N", N);
|
||||
assertArgument(typeof(r) === "number" && Number.isSafeInteger(r), "invalid scrypt r parameter", "options.r", r);
|
||||
assertArgument(typeof(p) === "number" && Number.isSafeInteger(p), "invalid scrypt p parameter", "options.p", 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 };
|
||||
}
|
||||
|
||||
export function _encryptKeystore(key: Uint8Array, kdf: ScryptParams, account: KeystoreAccount, options: EncryptOptions): any {
|
||||
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", "options.iv", options.iv);
|
||||
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", "options.uuid", options.iv);
|
||||
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)
|
||||
@ -318,6 +352,14 @@ export function _encryptKeystore(key: Uint8Array, kdf: ScryptParams, account: Ke
|
||||
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 = { }; }
|
||||
|
||||
@ -327,6 +369,15 @@ export function encryptKeystoreJsonSync(account: KeystoreAccount, password: stri
|
||||
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 = { }; }
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
/**
|
||||
* @_ignore
|
||||
*/
|
||||
|
||||
import {
|
||||
getBytes, getBytesCopy, hexlify, assertArgument, toUtf8Bytes
|
||||
getBytesCopy, assertArgument, toUtf8Bytes
|
||||
} from "../utils/index.js";
|
||||
|
||||
import type { BytesLike } from "../utils/index.js";
|
||||
|
||||
|
||||
export function looseArrayify(hexString: string): Uint8Array {
|
||||
if (typeof(hexString) === 'string' && hexString.substring(0, 2) !== '0x') {
|
||||
hexString = '0x' + hexString;
|
||||
@ -25,7 +26,7 @@ export function getPassword(password: string | Uint8Array): Uint8Array {
|
||||
return getBytesCopy(password);
|
||||
}
|
||||
|
||||
export function spelunk<T = string>(object: any, _path: string): T {
|
||||
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);
|
||||
@ -120,6 +121,7 @@ export function followRequired(data: any, path: string): string {
|
||||
}
|
||||
*/
|
||||
// See: https://www.ietf.org/rfc/rfc4122.txt (Section 4.4)
|
||||
/*
|
||||
export function uuidV4(randomBytes: BytesLike): string {
|
||||
const bytes = getBytes(randomBytes, "randomBytes");
|
||||
|
||||
@ -142,4 +144,4 @@ export function uuidV4(randomBytes: BytesLike): string {
|
||||
value.substring(22, 34),
|
||||
].join("-");
|
||||
}
|
||||
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user