From 724881f34d428406488a1c9f9dbebe54b6edecda Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Fri, 9 Dec 2022 18:21:45 -0500 Subject: [PATCH] docs: added examples to jsdocs --- src.ts/_tests/test-providers-ccip.ts | 6 +- src.ts/_tests/test-providers-errors.ts | 4 +- src.ts/_tests/test-utils-maths.ts | 14 ++-- src.ts/abi/coders/abstract-coder.ts | 4 +- src.ts/abi/coders/address.ts | 4 +- src.ts/abi/interface.ts | 4 +- src.ts/address/address.ts | 25 ++++++++ src.ts/address/checks.ts | 61 ++++++++++++++++++ src.ts/address/contract-address.ts | 23 +++++++ src.ts/constants/addresses.ts | 2 + src.ts/constants/hashes.ts | 2 + src.ts/constants/numbers.ts | 10 +++ src.ts/constants/strings.ts | 7 ++ src.ts/crypto/hmac.ts | 13 ++++ src.ts/crypto/keccak.ts | 13 ++++ src.ts/crypto/pbkdf2.ts | 13 ++++ src.ts/crypto/random.ts | 4 ++ src.ts/crypto/ripemd160.ts | 11 ++++ src.ts/crypto/scrypt.ts | 26 ++++++++ src.ts/crypto/sha2.ts | 21 ++++++ src.ts/crypto/signature.ts | 48 ++++++++++++-- src.ts/crypto/signing-key.ts | 89 +++++++++++++++----------- src.ts/ethers.ts | 5 +- src.ts/hash/id.ts | 11 ++++ src.ts/hash/message.ts | 29 ++++++++- src.ts/hash/solidity.ts | 39 +++++++---- src.ts/hash/typed-data.ts | 8 +-- src.ts/index.ts | 2 +- src.ts/providers/abstract-provider.ts | 4 +- src.ts/providers/ens-resolver.ts | 4 +- src.ts/transaction/transaction.ts | 39 +++++------ src.ts/utils/errors.ts | 9 +-- src.ts/utils/fetch.ts | 6 ++ src.ts/utils/fixednumber.ts | 6 +- src.ts/utils/index.ts | 2 +- src.ts/utils/maths.ts | 27 +++++--- src.ts/wallet/hdwallet.ts | 6 +- 37 files changed, 471 insertions(+), 130 deletions(-) diff --git a/src.ts/_tests/test-providers-ccip.ts b/src.ts/_tests/test-providers-ccip.ts index 8407a62cc..1de80a37d 100644 --- a/src.ts/_tests/test-providers-ccip.ts +++ b/src.ts/_tests/test-providers-ccip.ts @@ -3,7 +3,7 @@ import assert from "assert"; import { concat, dataLength, keccak256, - toArray, + toBeArray, isCallException, isError } from "../index.js"; @@ -15,8 +15,8 @@ describe("Test CCIP execution", function() { // processed data from the endpoint const verify = function(sender: string, data: string, result: string): void { const check = concat([ - toArray(dataLength(sender)), sender, - toArray(dataLength(data)), data + toBeArray(dataLength(sender)), sender, + toBeArray(dataLength(data)), data ]); assert.equal(result, keccak256(check), "response is equal"); } diff --git a/src.ts/_tests/test-providers-errors.ts b/src.ts/_tests/test-providers-errors.ts index 5250ec1e7..1fe9a08a2 100644 --- a/src.ts/_tests/test-providers-errors.ts +++ b/src.ts/_tests/test-providers-errors.ts @@ -2,7 +2,7 @@ import assert from "assert"; import { - concat, dataSlice, id, toArray, zeroPadValue, + concat, dataSlice, id, toBeArray, zeroPadValue, isCallException } from "../index.js"; @@ -60,7 +60,7 @@ describe("Tests Provider Errors", function() { const data = concat([ dataSlice(id("testPanic(uint256)"), 0, 4), - zeroPadValue(toArray(code), 32) + zeroPadValue(toBeArray(code), 32) ]); const tx = { to: testAddr, data }; diff --git a/src.ts/_tests/test-utils-maths.ts b/src.ts/_tests/test-utils-maths.ts index 2bea0a01d..198fe4ee9 100644 --- a/src.ts/_tests/test-utils-maths.ts +++ b/src.ts/_tests/test-utils-maths.ts @@ -2,7 +2,7 @@ import assert from "assert"; import { isError, - getBigInt, getNumber, toArray, toHex, toQuantity, + getBigInt, getNumber, toBeArray, toBeHex, toQuantity, } from "../index.js"; describe("Tests Quantity Functions", function() { @@ -146,7 +146,7 @@ describe("Tests Bad Math Values", function() { { name: "negative value", value: -4, - error: "cannot toHex negative value" + error: "cannot toBeHex negative value" }, { name: "width too short", @@ -157,9 +157,9 @@ describe("Tests Bad Math Values", function() { ]; for (const { name, value, error, width } of badHex) { - it(`correctly fails on bad toHex values: ${ name }`, function() { + it(`correctly fails on bad toBeHex values: ${ name }`, function() { assert.throws(() => { - const result = toHex(value, width); + const result = toBeHex(value, width); console.log(result); }, (e: any) => { return (isError(e, "INVALID_ARGUMENT") && @@ -168,13 +168,13 @@ describe("Tests Bad Math Values", function() { }); } - it(`correctly fails on nad toArray values: negative value`, function() { + it(`correctly fails on nad toBeArray values: negative value`, function() { assert.throws(() => { - const result = toArray(-4); + const result = toBeArray(-4); console.log(result); }, (e: any) => { return (isError(e, "INVALID_ARGUMENT") && - e.message.startsWith("cannot toArray negative value")); + e.message.startsWith("cannot toBeArray negative value")); }); }); }); diff --git a/src.ts/abi/coders/abstract-coder.ts b/src.ts/abi/coders/abstract-coder.ts index 2a546915c..3eb1eae31 100644 --- a/src.ts/abi/coders/abstract-coder.ts +++ b/src.ts/abi/coders/abstract-coder.ts @@ -1,7 +1,7 @@ import { defineProperties, concat, getBytesCopy, getNumber, hexlify, - toArray, toBigInt, toNumber, + toBeArray, toBigInt, toNumber, assert, assertPrivate, assertArgument } from "../../utils/index.js"; @@ -188,7 +188,7 @@ export function checkResultErrors(result: Result): Array<{ path: Array): Promi * * If an ENS name is provided, but that name has not been correctly * configured a [[UnconfiguredNameError]] is thrown. + * + * @example: + * addr = "0x6B175474E89094C44Da98b954EedeAC495271d0F" + * + * // Addresses are return synchronously + * resolveAddress(addr, provider) + * //_result: + * + * // Address promises are resolved asynchronously + * resolveAddress(Promise.resolve(addr)) + * //_result: + * + * // ENS names are resolved asynchronously + * resolveAddress("dai.tokens.ethers.eth", provider) + * //_result: + * + * // Addressable objects are resolved asynchronously + * contract = new Contract(addr, [ ]) + * resolveAddress(contract, provider) + * //_result: + * + * // Unconfigured ENS names reject + * resolveAddress("nothing-here.ricmoo.eth", provider) + * //_error: + * + * // ENS names require a NameResolver object passed in + * // (notice the provider was omitted) + * resolveAddress("nothing-here.ricmoo.eth") + * //_error: */ export function resolveAddress(target: AddressLike, resolver?: null | NameResolver): string | Promise { diff --git a/src.ts/address/contract-address.ts b/src.ts/address/contract-address.ts index ec96d6ef9..6d8aebe27 100644 --- a/src.ts/address/contract-address.ts +++ b/src.ts/address/contract-address.ts @@ -20,6 +20,13 @@ import type { BigNumberish, BytesLike } from "../utils/index.js"; * This can also be used to compute the address a contract will be * deployed to by a contract, by using the contract's address as the * ``to`` and the contract's nonce. + * + * @example + * from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72"; + * nonce = 5; + * + * getCreateAddress({ from, nonce }); + * //_result: */ export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): string { const from = getAddress(tx.from); @@ -43,6 +50,22 @@ export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): str * * To compute the %%initCodeHash%% from a contract's init code, use * the [[keccak256]] function. + * + * For a quick overview and example of ``CREATE2``, see [[link-ricmoo-wisps]]. + * + * @example + * // The address of the contract + * from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72" + * + * // The salt + * salt = id("HelloWorld") + * + * // The hash of the initCode + * initCode = "0x6394198df16000526103ff60206004601c335afa6040516060f3"; + * initCodeHash = keccak256(initCode) + * + * getCreate2Address(from, salt, initCodeHash) + * //_result: */ export function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash: BytesLike): string { const from = getAddress(_from); diff --git a/src.ts/constants/addresses.ts b/src.ts/constants/addresses.ts index 935999b28..60aa9b25f 100644 --- a/src.ts/constants/addresses.ts +++ b/src.ts/constants/addresses.ts @@ -1,6 +1,8 @@ /** * A constant for the zero address. + * + * (**i.e.** ``"0x0000000000000000000000000000000000000000"``) */ export const ZeroAddress: string = "0x0000000000000000000000000000000000000000"; diff --git a/src.ts/constants/hashes.ts b/src.ts/constants/hashes.ts index 8eee4e25e..92c39c3f1 100644 --- a/src.ts/constants/hashes.ts +++ b/src.ts/constants/hashes.ts @@ -1,5 +1,7 @@ /** * A constant for the zero hash. + * + * (**i.e.** ``"0x0000000000000000000000000000000000000000000000000000000000000000"``) */ export const ZeroHash: string = "0x0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/src.ts/constants/numbers.ts b/src.ts/constants/numbers.ts index ca7712ac4..e94b69609 100644 --- a/src.ts/constants/numbers.ts +++ b/src.ts/constants/numbers.ts @@ -1,25 +1,35 @@ /** * A constant for the order N for the secp256k1 curve. + * + * (**i.e.** ``0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n``) */ export const N: bigint = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); /** * A constant for the number of wei in a single ether. + * + * (**i.e.** ``1000000000000000000n``) */ export const WeiPerEther: bigint = BigInt("1000000000000000000"); /** * A constant for the maximum value for a ``uint256``. + * + * (**i.e.** ``0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn``) */ export const MaxUint256: bigint = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); /** * A constant for the minimum value for an ``int256``. + * + * (**i.e.** ``-8000000000000000000000000000000000000000000000000000000000000000n``) */ export const MinInt256: bigint = BigInt("0x8000000000000000000000000000000000000000000000000000000000000000") * BigInt(-1); /** * A constant for the maximum value for an ``int256``. + * + * (**i.e.** ``0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn``) */ export const MaxInt256: bigint = BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); diff --git a/src.ts/constants/strings.ts b/src.ts/constants/strings.ts index 08aeb8826..3439ca7b1 100644 --- a/src.ts/constants/strings.ts +++ b/src.ts/constants/strings.ts @@ -2,8 +2,15 @@ /** * A constant for the ether symbol (normalized using NFKC). + * + * (**i.e.** ``"\\u039e"``) */ export const EtherSymbol: string = "\u039e"; // "\uD835\uDF63"; +/** + * A constant for the [[link-eip-191]] personal message prefix. + * + * (**i.e.** ``"\\x19Ethereum Signed Message:\\n"``) + */ export const MessagePrefix: string = "\x19Ethereum Signed Message:\n"; diff --git a/src.ts/crypto/hmac.ts b/src.ts/crypto/hmac.ts index 763bc5337..fa4e1ba8a 100644 --- a/src.ts/crypto/hmac.ts +++ b/src.ts/crypto/hmac.ts @@ -23,6 +23,19 @@ let __computeHmac = _computeHmac; /** * Return the HMAC for %%data%% using the %%key%% key with the underlying * %%algo%% used for compression. + * + * @example: + * key = id("some-secret") + * + * // Compute the HMAC + * computeHmac("sha256", key, "0x1337") + * //_result: + * + * // To compute the HMAC of UTF-8 data, the data must be + * // converted to UTF-8 bytes + * computeHmac("sha256", key, toUtf8Bytes("Hello World")) + * //_result: + * */ export function computeHmac(algorithm: "sha256" | "sha512", _key: BytesLike, _data: BytesLike): string { const key = getBytes(_key, "key"); diff --git a/src.ts/crypto/keccak.ts b/src.ts/crypto/keccak.ts index 171724814..1ff05e7cb 100644 --- a/src.ts/crypto/keccak.ts +++ b/src.ts/crypto/keccak.ts @@ -22,7 +22,20 @@ let __keccak256: (data: Uint8Array) => BytesLike = _keccak256; /** * Compute the cryptographic KECCAK256 hash of %%data%%. * + * The %%data%% **must** be a data representation, to compute the + * hash of UTF-8 data use the [[id]] function. + * * @returns DataHexstring + * @example: + * keccak256("0x") + * //_result: + * + * keccak256("0x1337") + * //_result: + * + * keccak256(new Uint8Array([ 0x13, 0x37 ])) + * //_result: + * */ export function keccak256(_data: BytesLike): string { const data = getBytes(_data, "data"); diff --git a/src.ts/crypto/pbkdf2.ts b/src.ts/crypto/pbkdf2.ts index 825d57451..b61882287 100644 --- a/src.ts/crypto/pbkdf2.ts +++ b/src.ts/crypto/pbkdf2.ts @@ -27,6 +27,19 @@ let __pbkdf2 = _pbkdf2; * * This PBKDF is outdated and should not be used in new projects, but is * required to decrypt older files. + * + * @example: + * // The password must be converted to bytes, and it is generally + * // best practices to ensure the string has been normalized. Many + * // formats explicitly indicate the normalization form to use. + * password = "hello" + * passwordBytes = toUtf8Bytes(password, "NFKC") + * + * salt = id("some-salt") + * + * // Compute the PBKDF2 + * pbkdf2(passwordBytes, salt, 1024, 16, "sha256") + * //_result: */ export function pbkdf2(_password: BytesLike, _salt: BytesLike, iterations: number, keylen: number, algo: "sha256" | "sha512"): string { const password = getBytes(_password, "password"); diff --git a/src.ts/crypto/random.ts b/src.ts/crypto/random.ts index 0dcd84f46..ddf5f2205 100644 --- a/src.ts/crypto/random.ts +++ b/src.ts/crypto/random.ts @@ -18,6 +18,10 @@ let __randomBytes = _randomBytes; /** * Return %%length%% bytes of cryptographically secure random data. + * + * @example: + * randomBytes(8) + * //_result: */ export function randomBytes(length: number): Uint8Array { return __randomBytes(length); diff --git a/src.ts/crypto/ripemd160.ts b/src.ts/crypto/ripemd160.ts index 5069c2d1e..7074be029 100644 --- a/src.ts/crypto/ripemd160.ts +++ b/src.ts/crypto/ripemd160.ts @@ -18,6 +18,17 @@ let __ripemd160: (data: Uint8Array) => BytesLike = _ripemd160; * * @_docloc: api/crypto:Hash Functions * @returns DataHexstring + * + * @example: + * ripemd160("0x") + * //_result: + * + * ripemd160("0x1337") + * //_result: + * + * ripemd160(new Uint8Array([ 0x13, 0x37 ])) + * //_result: + * */ export function ripemd160(_data: BytesLike): string { const data = getBytes(_data, "data"); diff --git a/src.ts/crypto/scrypt.ts b/src.ts/crypto/scrypt.ts index 0dad1b3ea..4c48ce598 100644 --- a/src.ts/crypto/scrypt.ts +++ b/src.ts/crypto/scrypt.ts @@ -49,6 +49,19 @@ let __scryptSync: (passwd: Uint8Array, salt: Uint8Array, N: number, r: number, p * can also help, assuring the user their waiting is for a good reason. * * @_docloc: api/crypto:Passwords + * + * @example: + * // The password must be converted to bytes, and it is generally + * // best practices to ensure the string has been normalized. Many + * // formats explicitly indicate the normalization form to use. + * password = "hello" + * passwordBytes = toUtf8Bytes(password, "NFKC") + * + * salt = id("some-salt") + * + * // Compute the scrypt + * scrypt(passwordBytes, salt, 1024, 8, 1, 16) + * //_result: */ export async function scrypt(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number, progress?: ProgressCallback): Promise { const passwd = getBytes(_passwd, "passwd"); @@ -71,6 +84,19 @@ Object.freeze(scrypt); * preferred to use the [async variant](scrypt). * * @_docloc: api/crypto:Passwords + * + * @example: + * // The password must be converted to bytes, and it is generally + * // best practices to ensure the string has been normalized. Many + * // formats explicitly indicate the normalization form to use. + * password = "hello" + * passwordBytes = toUtf8Bytes(password, "NFKC") + * + * salt = id("some-salt") + * + * // Compute the scrypt + * scryptSync(passwordBytes, salt, 1024, 8, 1, 16) + * //_result: */ export function scryptSync(_passwd: BytesLike, _salt: BytesLike, N: number, r: number, p: number, dkLen: number): string { const passwd = getBytes(_passwd, "passwd"); diff --git a/src.ts/crypto/sha2.ts b/src.ts/crypto/sha2.ts index 95c5dd032..2fc8f8ac1 100644 --- a/src.ts/crypto/sha2.ts +++ b/src.ts/crypto/sha2.ts @@ -24,6 +24,17 @@ let locked256 = false, locked512 = false; * * @_docloc: api/crypto:Hash Functions * @returns DataHexstring + * + * @example: + * sha256("0x") + * //_result: + * + * sha256("0x1337") + * //_result: + * + * sha256(new Uint8Array([ 0x13, 0x37 ])) + * //_result: + * */ export function sha256(_data: BytesLike): string { const data = getBytes(_data, "data"); @@ -43,6 +54,16 @@ Object.freeze(sha256); * * @_docloc: api/crypto:Hash Functions * @returns DataHexstring + * + * @example: + * sha512("0x") + * //_result: + * + * sha512("0x1337") + * //_result: + * + * sha512(new Uint8Array([ 0x13, 0x37 ])) + * //_result: */ export function sha512(_data: BytesLike): string { const data = getBytes(_data, "data"); diff --git a/src.ts/crypto/signature.ts b/src.ts/crypto/signature.ts index 653c09aaa..b6f94df4c 100644 --- a/src.ts/crypto/signature.ts +++ b/src.ts/crypto/signature.ts @@ -178,7 +178,14 @@ export class Signature { } /** - * Compute the chain ID from an EIP-155 ``v`` for legacy transactions. + * Compute the chain ID from the ``v`` in a legacy EIP-155 transactions. + * + * @example: + * Signature.getChainId(45) + * //_result: + * + * Signature.getChainId(46) + * //_result: */ static getChainId(v: BigNumberish): bigint { const bv = getBigInt(v, "v"); @@ -193,20 +200,51 @@ export class Signature { } /** - * Compute the EIP-155 ``v`` for a chain ID for legacy transactions. + * Compute the ``v`` for a chain ID for a legacy EIP-155 transactions. + * + * Legacy transactions which use [[link-eip-155]] hijack the ``v`` + * property to include the chain ID. + * + * @example: + * Signature.getChainIdV(5, 27) + * //_result: + * + * Signature.getChainIdV(5, 28) + * //_result: + * */ static getChainIdV(chainId: BigNumberish, v: 27 | 28): bigint { return (getBigInt(chainId) * BN_2) + BigInt(35 + v - 27); } /** - * Compute the normalized EIP-155 ``v`` for legacy transactions. + * Compute the normalized legacy transaction ``v`` from a ``yParirty``, + * a legacy transaction ``v`` or a legacy [[link-eip-155]] transaction. + * + * @example: + * // The values 0 and 1 imply v is actually yParity + * Signature.getNormalizedV(0) + * //_result: + * + * // Legacy non-EIP-1559 transaction (i.e. 27 or 28) + * Signature.getNormalizedV(27) + * //_result: + * + * // Legacy EIP-155 transaction (i.e. >= 35) + * Signature.getNormalizedV(46) + * //_result: + * + * // Invalid values throw + * Signature.getNormalizedV(5) + * //_error: */ static getNormalizedV(v: BigNumberish): 27 | 28 { const bv = getBigInt(v); - if (bv == BN_0) { return 27; } - if (bv == BN_1) { return 28; } + if (bv === BN_0 || bv === BN_27) { return 27; } + if (bv === BN_1 || bv === BN_28) { return 28; } + + assertArgument(bv >= BN_35, "invalid v", "v", v); // Otherwise, EIP-155 v means odd is 27 and even is 28 return (bv & BN_1) ? 27: 28; diff --git a/src.ts/crypto/signing-key.ts b/src.ts/crypto/signing-key.ts index a7f65d515..1f1f36224 100644 --- a/src.ts/crypto/signing-key.ts +++ b/src.ts/crypto/signing-key.ts @@ -2,7 +2,7 @@ import * as secp256k1 from "@noble/secp256k1"; import { - concat, dataLength, getBytes, getBytesCopy, hexlify, toHex, + concat, dataLength, getBytes, getBytesCopy, hexlify, toBeHex, assertArgument } from "../utils/index.js"; @@ -71,8 +71,8 @@ export class SigningKey { const sig = secp256k1.Signature.fromHex(sigDer); return Signature.from({ - r: toHex("0x" + sig.r.toString(16), 32), - s: toHex("0x" + sig.s.toString(16), 32), + r: toBeHex("0x" + sig.r.toString(16), 32), + s: toBeHex("0x" + sig.s.toString(16), 32), v: (recid ? 0x1c: 0x1b) }); } @@ -86,10 +86,23 @@ export class SigningKey { * * Best practice is usually to use a cryptographic hash on the * returned value before using it as a symetric secret. + * + * @example: + * sign1 = new SigningKey(id("some-secret-1")) + * sign2 = new SigningKey(id("some-secret-2")) + * + * // Notice that privA.computeSharedSecret(pubB)... + * sign1.computeSharedSecret(sign2.publicKey) + * //_result: + * + * // ...is equal to privB.computeSharedSecret(pubA). + * sign2.computeSharedSecret(sign1.publicKey) + * //_result: */ - computeShardSecret(other: BytesLike): string { + computeSharedSecret(other: BytesLike): string { const pubKey = SigningKey.computePublicKey(other); - return hexlify(secp256k1.getSharedSecret(getBytesCopy(this.#privateKey), pubKey)); + console.log(pubKey); + return hexlify(secp256k1.getSharedSecret(getBytesCopy(this.#privateKey), getBytes(pubKey))); } /** @@ -97,6 +110,25 @@ export class SigningKey { * * The %%key%% may be any type of key, a raw public key, a * compressed/uncompressed public key or private key. + * + * @example: + * sign = new SigningKey(id("some-secret")); + * + * // Compute the uncompressed public key for a private key + * SigningKey.computePublicKey(sign.privateKey) + * //_result: + * + * // Compute the compressed public key for a private key + * SigningKey.computePublicKey(sign.privateKey, true) + * //_result: + * + * // Compute the uncompressed public key + * SigningKey.computePublicKey(sign.publicKey, false); + * //_result: + * + * // Compute the Compressed a public key + * SigningKey.computePublicKey(sign.publicKey, true); + * //_result: */ static computePublicKey(key: BytesLike, compressed?: boolean): string { let bytes = getBytes(key, "key"); @@ -120,6 +152,20 @@ export class SigningKey { /** * Returns the public key for the private key which produced the * %%signature%% for the given %%digest%%. + * + * @example: + * key = new SigningKey(id("some-secret")) + * digest = id("hello world") + * sig = key.sign(digest) + * + * // Notice the signer public key... + * key.publicKey + * //_result: + * + * // ...is equal to the recovered public key + * SigningKey.recoverPublicKey(digest, sig) + * //_result: + * */ static recoverPublicKey(digest: BytesLike, signature: SignatureLike): string { assertArgument(dataLength(digest) === 32, "invalid digest length", "digest", digest); @@ -150,36 +196,3 @@ export class SigningKey { } } -/* -const key = new SigningKey("0x1234567890123456789012345678901234567890123456789012345678901234"); -console.log(key); -console.log(key.sign("0x1234567890123456789012345678901234567890123456789012345678901234")); -{ - const privKey = "0x1234567812345678123456781234567812345678123456781234567812345678"; - const signingKey = new SigningKey(privKey); - console.log("0", signingKey, signingKey.publicKey, signingKey.publicKeyCompressed); - - let pubKey = SigningKey.computePublicKey(privKey); - let pubKeyComp = SigningKey.computePublicKey(privKey, true); - let pubKeyRaw = "0x" + SigningKey.computePublicKey(privKey).substring(4); - console.log("A", pubKey, pubKeyComp); - - let a = SigningKey.computePublicKey(pubKey); - let b = SigningKey.computePublicKey(pubKey, true); - console.log("B", a, b); - - a = SigningKey.computePublicKey(pubKeyComp); - b = SigningKey.computePublicKey(pubKeyComp, true); - console.log("C", a, b); - - a = SigningKey.computePublicKey(pubKeyRaw); - b = SigningKey.computePublicKey(pubKeyRaw, true); - console.log("D", a, b); - - const digest = "0x1122334411223344112233441122334411223344112233441122334411223344"; - const sig = signingKey.sign(digest); - console.log("SS", sig, sig.r, sig.s, sig.yParity); - - console.log("R", SigningKey.recoverPublicKey(digest, sig)); -} -*/ diff --git a/src.ts/ethers.ts b/src.ts/ethers.ts index 5a62b3131..e7de9b50b 100644 --- a/src.ts/ethers.ts +++ b/src.ts/ethers.ts @@ -17,7 +17,8 @@ export { export { getAddress, getIcapAddress, - getCreateAddress, getCreate2Address + getCreateAddress, getCreate2Address, + isAddressable, isAddress, resolveAddress } from "./address/index.js"; export { @@ -92,7 +93,7 @@ export { isCallException, isError, FetchRequest, FetchResponse, FetchCancelSignal, FixedNumber, - getBigInt, getNumber, toArray, toBigInt, toHex, toNumber, toQuantity, + getBigInt, getNumber, getUint, toBeArray, toBigInt, toBeHex, toNumber, toQuantity, fromTwos, toTwos, mask, formatEther, parseEther, formatUnits, parseUnits, toUtf8Bytes, toUtf8CodePoints, toUtf8String, diff --git a/src.ts/hash/id.ts b/src.ts/hash/id.ts index 2c3eeb4ec..46cb5c17c 100644 --- a/src.ts/hash/id.ts +++ b/src.ts/hash/id.ts @@ -1,6 +1,17 @@ import { keccak256 } from "../crypto/index.js"; import { toUtf8Bytes } from "../utils/index.js"; +/** + * A simple hashing function which operates on UTF-8 strings to + * compute an 32-byte irentifier. + * + * This simply computes the [UTF-8 bytes](toUtf8Bytes) and computes + * the [[keccak256]]. + * + * @example: + * id("hello world") + * //_result: + */ export function id(value: string): string { return keccak256(toUtf8Bytes(value)); } diff --git a/src.ts/hash/message.ts b/src.ts/hash/message.ts index b97fbc9c9..654b878ee 100644 --- a/src.ts/hash/message.ts +++ b/src.ts/hash/message.ts @@ -2,7 +2,34 @@ import { keccak256 } from "../crypto/index.js"; import { MessagePrefix } from "../constants/index.js"; import { concat, toUtf8Bytes } from "../utils/index.js"; - +/** + * Computes the [[link-eip-191]] personal-sign message digest to sign. + * + * This prefixes the message with [[MessagePrefix]] and the decimal length + * of %%message%% and computes the [[keccak256]] digest. + * + * If %%message%% is a string, it is converted to its UTF-8 bytes + * first. To compute the digest of a [[DataHexString]], it must be converted + * to [bytes](getBytes). + * + * @example: + * hashMessage("Hello World") + * //_result: + * + * // Hashes the SIX (6) string characters, i.e. + * // [ "0", "x", "4", "2", "4", "3" ] + * hashMessage("0x4243") + * //_result: + * + * // Hashes the TWO (2) bytes [ 0x42, 0x43 ]... + * hashMessage(getBytes("0x4243")) + * //_result: + * + * // ...which is equal to using data + * hashMessage(new Uint8Array([ 0x42, 0x43 ])) + * //_result: + * + */ export function hashMessage(message: Uint8Array | string): string { if (typeof(message) === "string") { message = toUtf8Bytes(message); } return keccak256(concat([ diff --git a/src.ts/hash/solidity.ts b/src.ts/hash/solidity.ts index 2f68f0faa..6bab13195 100644 --- a/src.ts/hash/solidity.ts +++ b/src.ts/hash/solidity.ts @@ -1,8 +1,9 @@ +import { getAddress } from "../address/index.js"; import { keccak256 as _keccak256, sha256 as _sha256 } from "../crypto/index.js"; import { - concat, dataLength, getBytes, hexlify, toArray, toTwos, toUtf8Bytes, zeroPadBytes, zeroPadValue, + concat, dataLength, getBytes, hexlify, toBeArray, toTwos, toUtf8Bytes, zeroPadBytes, zeroPadValue, assertArgument } from "../utils/index.js"; @@ -16,7 +17,7 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { switch(type) { case "address": if (isArray) { return getBytes(zeroPadValue(value, 32)); } - return getBytes(value); + return getBytes(getAddress(value)); case "string": return toUtf8Bytes(value); case "bytes": @@ -38,7 +39,7 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { if (signed) { value = toTwos(value, size); } - return getBytes(zeroPadValue(toArray(value), size / 8)); + return getBytes(zeroPadValue(toBeArray(value), size / 8)); } match = type.match(regexBytes); @@ -70,6 +71,15 @@ function _pack(type: string, value: any, isArray?: boolean): Uint8Array { // @TODO: Array Enum +/** + * Computes the [[link-solc-packed]] representation of %%values%% + * respectively to their %%types%%. + * + * @example: + * addr = "0x8ba1f109551bd432803012645ac136ddd64dba72" + * solidityPacked([ "address", "uint" ], [ addr, 45 ]); + * //_result: + */ export function solidityPacked(types: ReadonlyArray, values: ReadonlyArray): string { assertArgument(types.length === values.length, "wrong number of values; expected ${ types.length }", "values", values); @@ -81,22 +91,27 @@ export function solidityPacked(types: ReadonlyArray, values: ReadonlyArr } /** - * Computes the non-standard packed (tightly packed) keccak256 hash of - * the values given the types. - * - * @param {Array} types - The Solidity types to interpret each value as [default: bar] - * @param {Array} values - The values to pack + * Computes the [[link-solc-packed]] [[keccak256]] hash of %%values%% + * respectively to their %%types%%. * * @example: - * solidityPackedKeccak256([ "address", "uint" ], [ "0x1234", 45 ]); - * / /_result: - * - * @see https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#non-standard-packed-mode + * addr = "0x8ba1f109551bd432803012645ac136ddd64dba72" + * solidityPackedKeccak256([ "address", "uint" ], [ addr, 45 ]); + * //_result: */ export function solidityPackedKeccak256(types: ReadonlyArray, values: ReadonlyArray): string { return _keccak256(solidityPacked(types, values)); } +/** + * Computes the [[link-solc-packed]] [[sha256]] hash of %%values%% + * respectively to their %%types%%. + * + * @example: + * addr = "0x8ba1f109551bd432803012645ac136ddd64dba72" + * solidityPackedSha256([ "address", "uint" ], [ addr, 45 ]); + * //_result: + */ export function solidityPackedSha256(types: ReadonlyArray, values: ReadonlyArray): string { return _sha256(solidityPacked(types, values)); } diff --git a/src.ts/hash/typed-data.ts b/src.ts/hash/typed-data.ts index f37732e9e..24ea0d3fe 100644 --- a/src.ts/hash/typed-data.ts +++ b/src.ts/hash/typed-data.ts @@ -2,7 +2,7 @@ import { getAddress } from "../address/index.js"; import { keccak256 } from "../crypto/index.js"; import { - concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toHex, toTwos, zeroPadValue, + concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toBeHex, toTwos, zeroPadValue, assertArgument } from "../utils/index.js"; @@ -41,8 +41,8 @@ function hexPadRight(value: BytesLike): string { return hexlify(bytes); } -const hexTrue = toHex(BN_1, 32); -const hexFalse = toHex(BN_0, 32); +const hexTrue = toBeHex(BN_1, 32); +const hexFalse = toBeHex(BN_0, 32); const domainFieldTypes: Record = { name: "string", @@ -100,7 +100,7 @@ function getBaseEncoder(type: string): null | ((value: any) => string) { assertArgument(value >= boundsLower && value <= boundsUpper, `value out-of-bounds for ${ type }`, "value", value); - return toHex(toTwos(value, 256), 32); + return toBeHex(toTwos(value, 256), 32); }; } } diff --git a/src.ts/index.ts b/src.ts/index.ts index f03672829..571fe8700 100644 --- a/src.ts/index.ts +++ b/src.ts/index.ts @@ -2,7 +2,7 @@ * The Application Programming Interface (API) is the collection of * functions, classes and types offered by the Ethers library. * - * @_section: api:API Specification + * @_section: api:API Specification [about-api] */ import * as ethers from "./ethers.js"; diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index e77df2ba1..01704f62d 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -19,7 +19,7 @@ import { getBigInt, getBytes, getNumber, isCallException, makeError, assert, assertArgument, FetchRequest, - toArray, toQuantity, + toBeArray, toQuantity, defineProperties, EventPayload, resolveProperties, toUtf8String } from "../utils/index.js"; @@ -1313,7 +1313,7 @@ function _parseBytes(result: string, start: number): null | string { } function numPad(value: number): Uint8Array { - const result = toArray(value); + const result = toBeArray(value); if (result.length > 32) { throw new Error("internal; should not happen"); } const padded = new Uint8Array(32); diff --git a/src.ts/providers/ens-resolver.ts b/src.ts/providers/ens-resolver.ts index acbcd3edd..a5cea3552 100644 --- a/src.ts/providers/ens-resolver.ts +++ b/src.ts/providers/ens-resolver.ts @@ -9,7 +9,7 @@ import { ZeroAddress, ZeroHash } from "../constants/index.js"; import { dnsEncode, namehash } from "../hash/index.js"; import { concat, dataSlice, getBytes, hexlify, zeroPadValue, - defineProperties, encodeBase58, getBigInt, toArray, + defineProperties, encodeBase58, getBigInt, toBeArray, toNumber, toUtf8Bytes, toUtf8String, assert, assertArgument, FetchRequest @@ -43,7 +43,7 @@ function parseString(result: string, start: number): null | string { } function numPad(value: BigNumberish): Uint8Array { - const result = toArray(value); + const result = toBeArray(value); if (result.length > 32) { throw new Error("internal; should not happen"); } const padded = new Uint8Array(32); diff --git a/src.ts/transaction/transaction.ts b/src.ts/transaction/transaction.ts index a874a881b..ff7ec0234 100644 --- a/src.ts/transaction/transaction.ts +++ b/src.ts/transaction/transaction.ts @@ -3,7 +3,7 @@ import { getAddress } from "../address/index.js"; import { keccak256, Signature, SigningKey } from "../crypto/index.js"; import { concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify, - assert, assertArgument, toArray, zeroPadValue + assert, assertArgument, toBeArray, zeroPadValue } from "../utils/index.js"; import { accessListify } from "./accesslist.js"; @@ -121,7 +121,7 @@ function handleUint(_value: string, param: string): bigint { function formatNumber(_value: BigNumberish, name: string): Uint8Array { const value = getBigInt(_value, "value"); - const result = toArray(value); + const result = toBeArray(value); assertArgument(result.length <= 32, `value too large`, `tx.${ name }`, value); return result; } @@ -210,7 +210,7 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string { if (!sig) { // We have an EIP-155 transaction (chainId was specified and non-zero) if (chainId !== BN_0) { - fields.push(toArray(chainId)); + fields.push(toBeArray(chainId)); fields.push("0x"); fields.push("0x"); } @@ -226,9 +226,9 @@ function _serializeLegacy(tx: Transaction, sig?: Signature): string { assertArgument(false, "tx.chainId/sig.v mismatch", "sig", sig); } - fields.push(toArray(v)); - fields.push(toArray(sig.r)); - fields.push(toArray(sig.s)); + fields.push(toBeArray(v)); + fields.push(toBeArray(sig.r)); + fields.push(toBeArray(sig.s)); return encodeRlp(fields); } @@ -296,8 +296,8 @@ function _serializeEip1559(tx: TransactionLike, sig?: Signature): string { if (sig) { fields.push(formatNumber(sig.yParity, "yParity")); - fields.push(toArray(sig.r)); - fields.push(toArray(sig.s)); + fields.push(toBeArray(sig.r)); + fields.push(toBeArray(sig.s)); } return concat([ "0x02", encodeRlp(fields)]); @@ -345,30 +345,25 @@ function _serializeEip2930(tx: TransactionLike, sig?: Signature): string { if (sig) { fields.push(formatNumber(sig.yParity, "recoveryParam")); - fields.push(toArray(sig.r)); - fields.push(toArray(sig.s)); + fields.push(toBeArray(sig.r)); + fields.push(toBeArray(sig.s)); } return concat([ "0x01", encodeRlp(fields)]); } -/** - * A transactions which has been signed. - */ - /* -export interface SignedTransaction extends Transaction { - type: number; - typeName: string; - from: string; - signature: Signature; -} -*/ - /** * A **Transaction** describes an operation to be executed on * Ethereum by an Externally Owned Account (EOA). It includes * who (the [[to]] address), what (the [[data]]) and how much (the * [[value]] in ether) the operation should entail. + * + * @example: + * tx = new Transaction() + * //_result: + * + * tx.data = "0x1234"; + * //_result: */ export class Transaction implements TransactionLike { #type: null | number; diff --git a/src.ts/utils/errors.ts b/src.ts/utils/errors.ts index 2e047d730..2bf010c2a 100644 --- a/src.ts/utils/errors.ts +++ b/src.ts/utils/errors.ts @@ -435,13 +435,14 @@ export type CodedEthersError = * * @See [ErrorCodes](api:ErrorCode) * @example - * try { - * / / code.... - * } catch (e) { + * try { + * // code.... + * } catch (e) { * if (isError(e, "CALL_EXCEPTION")) { + * // The Type Guard has validated this object * console.log(e.data); * } - * } + * } */ export function isError>(error: any, code: K): error is T { return (error && (error).code === code); diff --git a/src.ts/utils/fetch.ts b/src.ts/utils/fetch.ts index 783957033..5acb52343 100644 --- a/src.ts/utils/fetch.ts +++ b/src.ts/utils/fetch.ts @@ -153,6 +153,12 @@ function checkSignal(signal?: FetchCancelSignal): FetchCancelSignal { * and ``IPFS:``. * * Additional schemes can be added globally using [[registerGateway]]. + * + * @example: + * req = new FetchRequest("https://www.ricmoo.com") + * resp = await req.send() + * resp.body.length + * //_result: */ export class FetchRequest implements Iterable<[ key: string, value: string ]> { #allowInsecure: boolean; diff --git a/src.ts/utils/fixednumber.ts b/src.ts/utils/fixednumber.ts index a80806dbd..6d040ee2a 100644 --- a/src.ts/utils/fixednumber.ts +++ b/src.ts/utils/fixednumber.ts @@ -6,16 +6,12 @@ import { getBytes } from "./data.js"; import { assert, assertArgument, assertPrivate } from "./errors.js"; import { - getBigInt, fromTwos, mask, toBigInt, toHex, toTwos + getBigInt, fromTwos, mask, toBigInt } from "./maths.js"; import { defineProperties } from "./properties.js"; import type { BigNumberish, BytesLike } from "./index.js"; -if (0) { - console.log(getBytes, toBigInt, toHex, toTwos); -} - const BN_N1 = BigInt(-1); const BN_0 = BigInt(0); const BN_1 = BigInt(1); diff --git a/src.ts/utils/index.ts b/src.ts/utils/index.ts index bd65aa73d..7f4690ef1 100644 --- a/src.ts/utils/index.ts +++ b/src.ts/utils/index.ts @@ -30,7 +30,7 @@ export { FixedNumber } from "./fixednumber.js" export { fromTwos, toTwos, mask, - getBigInt, getNumber, toBigInt, toNumber, toHex, toArray, toQuantity + getBigInt, getNumber, getUint, toBigInt, toNumber, toBeHex, toBeArray, toQuantity } from "./maths.js"; export { resolveProperties, defineProperties} from "./properties.js"; diff --git a/src.ts/utils/maths.ts b/src.ts/utils/maths.ts index 1f38b1e2f..6c6653b0a 100644 --- a/src.ts/utils/maths.ts +++ b/src.ts/utils/maths.ts @@ -22,6 +22,9 @@ export type BigNumberish = string | Numeric; const BN_0 = BigInt(0); const BN_1 = BigInt(1); +//const BN_Max256 = (BN_1 << BigInt(256)) - BN_1; + + // IEEE 754 support 53-bits of mantissa const maxValue = 0x1fffffffffffff; @@ -32,10 +35,9 @@ const maxValue = 0x1fffffffffffff; * If the highest bit is ``1``, the result will be negative. */ export function fromTwos(_value: BigNumberish, _width: Numeric): bigint { - const value = getBigInt(_value, "value"); + const value = getUint(_value, "value"); const width = BigInt(getNumber(_width, "width")); - assertArgument(value >= BN_0, "invalid twos complement value", "value", value); assert((value >> width) === BN_0, "overflow", "NUMERIC_FAULT", { operation: "fromTwos", fault: "overflow", value: _value }); @@ -81,7 +83,7 @@ export function toTwos(_value: BigNumberish, _width: Numeric): bigint { * Mask %%value%% with a bitmask of %%bits%% ones. */ export function mask(_value: BigNumberish, _bits: Numeric): bigint { - const value = getBigInt(_value, "value"); + const value = getUint(_value, "value"); const bits = BigInt(getNumber(_bits, "bits")); return value & ((BN_1 << bits) - BN_1); } @@ -111,6 +113,13 @@ export function getBigInt(value: BigNumberish, name?: string): bigint { assertArgument(false, "invalid BigNumberish value", name || "value", value); } +export function getUint(value: BigNumberish, name?: string): bigint { + const result = getBigInt(value, name); + assert(result >= BN_0, "overflow", "NUMERIC_FAULT", { + fault: "overflow", operation: "getUint", value + }); + return result; +} const Nibbles = "0123456789abcdef"; @@ -168,9 +177,8 @@ export function toNumber(value: BigNumberish | Uint8Array): number { * Converts %%value%% to a Big Endian hexstring, optionally padded to * %%width%% bytes. */ -export function toHex(_value: BigNumberish, _width?: Numeric): string { - const value = getBigInt(_value, "value"); - assertArgument(value >= 0, "cannot toHex negative value", "value", _value); +export function toBeHex(_value: BigNumberish, _width?: Numeric): string { + const value = getUint(_value, "value"); let result = value.toString(16); @@ -192,9 +200,8 @@ export function toHex(_value: BigNumberish, _width?: Numeric): string { /** * Converts %%value%% to a Big Endian Uint8Array. */ -export function toArray(_value: BigNumberish): Uint8Array { - const value = getBigInt(_value, "value"); - assertArgument(value >= 0, "cannot toArray negative value", "value", _value); +export function toBeArray(_value: BigNumberish): Uint8Array { + const value = getUint(_value, "value"); if (value === BN_0) { return new Uint8Array([ ]); } @@ -218,7 +225,7 @@ export function toArray(_value: BigNumberish): Uint8Array { * numeric values. */ export function toQuantity(value: BytesLike | BigNumberish): string { - let result = hexlify(isBytesLike(value) ? value: toArray(value)).substring(2); + let result = hexlify(isBytesLike(value) ? value: toBeArray(value)).substring(2); while (result.substring(0, 1) === "0") { result = result.substring(1); } if (result === "") { result = "0"; } return "0x" + result; diff --git a/src.ts/wallet/hdwallet.ts b/src.ts/wallet/hdwallet.ts index 23d7f426e..5dc795ace 100644 --- a/src.ts/wallet/hdwallet.ts +++ b/src.ts/wallet/hdwallet.ts @@ -9,7 +9,7 @@ import { computeAddress } from "../transaction/index.js"; import { concat, dataSlice, decodeBase58, defineProperties, encodeBase58, getBytes, hexlify, isBytesLike, - getNumber, toArray, toBigInt, toHex, + getNumber, toBeArray, toBigInt, toBeHex, assertPrivate, assert, assertArgument } from "../utils/index.js"; import { LangEn } from "../wordlists/lang-en.js"; @@ -294,7 +294,7 @@ export class HDNodeWallet extends BaseWallet { } const { IR, IL } = ser_I(index, this.chainCode, this.publicKey, this.privateKey); - const ki = new SigningKey(toHex((toBigInt(IL) + BigInt(this.privateKey)) % N, 32)); + 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); @@ -329,7 +329,7 @@ export class HDNodeWallet extends BaseWallet { * or full HD Node ([[HDNodeWallet) respectively. */ static fromExtendedKey(extendedKey: string): HDNodeWallet | HDNodeVoidWallet { - const bytes = toArray(decodeBase58(extendedKey)); // @TODO: redact + const bytes = toBeArray(decodeBase58(extendedKey)); // @TODO: redact assertArgument(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey, "invalid extended key", "extendedKey", "[ REDACTED ]");