/// /// import * as Web3Core from "web3-core"; import { SignedTransaction } from "web3-core"; import * as Web3Eth from "web3-eth"; import { TransactionConfig, TransactionReceipt } from "web3-eth"; import { MessageTypes, TypedMessage } from "@metamask/eth-sig-util"; import * as Eulith from "./index"; export declare module Signing { /** * The R/S/V signature used throughout Ethereum. * * This is roughly analogous to the web3js interfaces/classes: SignedTransaction, RLPEncodedTransaction, SignatureObject * This is roughly analogous to the ethereum-js-tx interfaces/classes: BaseTransaction * * This class makes it easy to construct the signature from various representations, and to see either the independent R/S/V, or * the combined RSV (used in some APIs). * * SEE https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm (R/S) * SEE https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages#ecdsa-public-key-recovery-from-signature (V) */ class ECDSASignature { /** * Validate and convert arguments to a valid ECDSASignature object (with valid hex strings as r/s/v values) */ constructor({ r, s, v, rsv }: { r?: string | Buffer; s?: string | Buffer; v?: string | number; rsv?: string; }); r: string; s: string; v: string; /** * Compute the concatenation of the R/S/V values, in a standardized format, that can be compared with string equality. */ get rsv(): string; } /** * The most basic signing interface. Can only sign a hash, no other higher level signing APIs (like transactions or typed data) */ interface ICryptographicSigner { /** * signer address guaranteed a valid checksum address (len=66, hex characters, etc) - 0x3341.... */ readonly address: string; /** * Eulith.Signing.ISigner.prototype.signHash * * Take a hash, and sign it, returning the ECDSASignature (r,s,v) * * Note: this handles CRYPTOGRAPHIC signing, and doesn't know about the chainId, so the caller needs * to handle any adjustments to the 'v' value. * * Note: Unlike web3.eth.accounts.sign - this takes the digest/hash, as argument and just signs that. * The caller will typically want to first compute a message digest (aka hash), and hand that to this method. */ signHash(hash: Buffer): Promise; } /** * Signer for transaction configs */ interface ITransactionSigner { /** * signer address guaranteed a valid checksum address (len=66, hex characters, etc) - 0x3341.... */ readonly address: string; signTransaction(transactionConfig: TransactionConfig): Promise; } /** * Combines signing and sending for compatibility with some browser based wallets */ interface ITransactionSigningSender { /** * signer address guaranteed a valid checksum address (len=66, hex characters, etc) - 0x3341.... */ readonly address: string; /** * This has a builtin provider and sends the signed transaction through that provider */ sendTransaction(transactionConfig: TransactionConfig): Promise; } /** * EIP-712 Typed Data */ type TypedData = TypedMessage; /** * Supports EIP-712: Typed structured data hashing and signing - https://eips.ethereum.org/EIPS/eip-712 */ interface ITypedDataSigner { /** * signer address guaranteed a valid checksum address (len=66, hex characters, etc) - 0x3341.... */ readonly address: string; signTypedData(typedDataMessage: TypedData): Promise; } /** * Eulith.Signing.SigningService is a high level wrapper on all sorts of Ethereum signing - transaction signing, typed data, hashes, etc. * NOT ALL facilities will be available for ALL SigningService objects. * * There are several kinds of signing in the Ethereum world. At the core of all of them are two basic processes: * > message digest (creating a canonical standardized summary of a thing to be signed) * > cryptographic signing * there are more subtleties here, but this is the gist. * * Eulith provides abstract APIs for each kind of signing: * > ICryptographicSigner * > ITransactionSigner * > ITypedDataSigner * * Some services support one or more of these. For example, Metamask (generally) supports ITransactionSigner * and ITypedDataSigner (so ALL but ICryptographicSigner). * * But different 'wallets' might support any subset of them. * * SigningService is an abstraction that allows you to easily peform whatever KIND of signing you might want to * do, given whatever kind of building block signing functionality you might start with. SOMETIMES, there may * be a kind of signing you might want to do with it a SigningService instance configured to not support it. * You can check properties to see if the kind of signing you want to do will work, or just try to sign, and see * if you get an exception. * * Generally, if you start with a ICryptographicSigner (and a provider) - you can do any kind of signing. If you start * with a user-interface wallet service, it will depend on that service what sorts of signing it handles. * * * Example Usage: * With a LocalSigner, and a provider, you can do any sort of signing * * const signingService = new SigningService ({cryptographicSigner: new LocalSigner({privateKey: "0x...."}), provider}) * signingService.address; // no problem - ALL signing service objects support this * signingService.signTransaction(tx); // again - no problem * signingService.signTypedData(tx); // '' * * Example Usage: * // From react/WAGMI * const signingService = new SigningService ({typedDataSigner_: {address: "0x333....", signTypedData: signTypedDataFuncFromWagmi}}); * signingService.address; // no problem - ALL signing service objects support this * signingService.signTypedData(tx); // no problem - calls your signTypedDataFuncFromWagmi * signingService.signTransaction(tx); // WHOOPS! - NOT gona work - raises exception - because you only passed in a signTypedData handler) * * Example Usage: * // From react/WAGMI * const signingService = new SigningService ({transactionSigner_: {address: "0x333....", signTransaction: signTransactionFromWAGMI}}); * signingService.address; // no problem - ALL signing service objects support this * signingService.signTransaction(tx); // Works fine, using argument transaction signer * signingService.signTypedData(tx); // fails cuz provided transactionSigner * * Example Usage: * const signingService = new SigningService ({cryptographicSigner: kmsSigner, provider}) * signingService.signTransaction(tx); // no problem * signingService.signTypedData(tx); // anything goes - all the APIs will work * * Example Usage: * const signingService = new SigningService ({cryptographicSigner: kmsSigner, }) * signingService.signTypedData(tx); // no problem * signingService.signTransaction(tx); // WHOOPSIE - wont work cuz you need a provider to canonicalize your transaction * * Historical Note: * * The Eulith API used to have a ISigner interface, that amounts to the same thing as the new ICryptographicSigner * and then we implemented the various sorts of signing in Eulith.Provider and Eulith.Signing.UnsignedTransaction * using this. * * However, for reasons I don't completley follow, direct use of signHash (not really called that in web3js) is frowned * upon, and instead, APIs like wagmi, metamask etc, provide the derivitive APIs like signTypedData, * signTransaction and so on. * * We wish to be able to levarge metamask signing from the Eulith library, in some cases. But also, wish to be able * to easily access these other kinds of signing from APIs that only provide cryptogrpahic signing. * * SigningService bridges that gap. It provides an API which gives access to these various high level signing services * in a way that can be used through the Eulith client library framework. It carries with it, the low level peices its * composed of, and can easily compose the higher levle signing services from the crytpograhic service. * * So serves as a lingua-franca within the Eulith client library framework for signing services (thus the name). */ class SigningService { private readonly address_; private transactionSigner_?; private transactionSigningSender_?; private readonly cryptographicSigner_?; private typedDataSigner_?; private readonly provider_; /** * Thin wrapper around constructor, for backwards compatibility. */ static assure(signer: ICryptographicSigner, provider?: Eulith.Provider | Eulith.Web3): SigningService; /** * At least one of transactionSigner, cryptographicSigner, and typedDataSigner MUST be provided. * More MAY be provided, but if so, they must agree on the 'address' property. * * Provider argument is optional. But - if provided, along with cryptographicSigner, and missing other signers, it can be used * to implement transactionSigner functionality. */ constructor({ transactionSigner, transactionSigningSender, cryptographicSigner, typedDataSigner, provider }: { transactionSigner?: ITransactionSigner; transactionSigningSender?: ITransactionSigningSender; cryptographicSigner?: ICryptographicSigner; typedDataSigner?: ITypedDataSigner; provider?: Eulith.Provider | Eulith.Web3; }); get address(): string; get transactionSigner(): ITransactionSigner; get transactionSigningSender(): ITransactionSigningSender; get cryptographicSigner(): ICryptographicSigner; get typedDataSigner(): ITypedDataSigner; signHash(hash: Buffer): Promise; signTransaction(transactionConfig: TransactionConfig): Promise; /** * Takes the argument transaction, and signs it (which may involve canonicalization and filling in defaults). * and then sends the message on the associated provider with this signer */ sendTransaction(transactionConfig: TransactionConfig): Promise; /** * Signs and sends the transaction, then waits for confirmation and returns a receipt */ sendTransactionAndWait(transactionConfig: TransactionConfig, timeoutInMS?: number): Promise; /** * Signs EIP-712 TypedData */ signTypedData(typedDataMessage: TypedData): Promise; } /** * Uses plaintext private key to perform signatures locally. * * Note: this is a terrible idea in production. */ class LocalSigner implements ICryptographicSigner { constructor({ privateKey }: { privateKey?: string; }); /** * Returns signer's address */ get address(): string; /** * Signs hash of data */ signHash(hash: Buffer): Promise; private privateKey_; private account_; } /** * Eulith.Signing.hashMessage * * Take any string (interpreted as a BLOB made up of UTF-8 bytes) or Buffer (BLOB) as argument, and produce a hash, suitable for use with Ethereum signing. * * Roughly this consists of prepending 'Ethereum Signed Message', and running the keccak256 digest algorithm on the result. */ function hashMessage(message: string | Buffer): Buffer; /** * Recovers the signer's address of the provided raw transaction */ function recoverTransactionSigner(rawTransaction: string): string; /** * Recovers the signer's address of the provided message and signature string */ function recoverSignerAddress(message: string, rsvSignature: string): string; /** * Recovers the signer's address of the provided EIP-712 TypedData and signature string */ function recoverTypedDataSignature(typedDataMessage: TypedData, rsvSignature: string): string; class UnsignedTransaction implements Web3Eth.TransactionConfig { from?: string | number; to?: string; value?: number | string; gas?: number | string; gasPrice?: number | string; maxPriorityFeePerGas?: number | string; maxFeePerGas?: number | string; data?: string; nonce?: number; chainId?: number; common?: Web3Eth.Common; chain?: string; hardfork?: string; constructor(from: Partial); /** * Shorthand for Eulith.Signing.SigningService.assure(signer, provider).sendTransactionAndWait(this, timeoutInMS); */ signAndSendAndWait(signer: Eulith.Signing.SigningService | Eulith.Signing.ICryptographicSigner, provider: Eulith.Provider, timeoutInMS?: number): Promise; /** * Takes this UnsignedTransaction object, a signer, and provider (to canonicalize), and produces a new, signed transaction object */ signTransaction({ signer, provider }: { signer: Eulith.Signing.ICryptographicSigner; provider: Eulith.Provider; }): Promise; /** * Take this unsigned, and potentially incomplete transaction object, and fill in missing details * as needed (e.g. gas). This validates, and can throw if there are critical details missing, or that * cannot be computed (again, such as gas). * * This requires a signer as argument, only to provide certain defaults. * * This requires a provider as argument, because it (typically) will make a few RPC calls to gather the * data needed. */ canonicalize(signerAddress: string, provider: Eulith.Provider): Promise; /** * Hashes and rlp encodes transaction */ serialize(): Buffer; private get asEthereumJSTxTransaction_(); /** * Take this (possibly incomplete but valid) unsigned message, and compute the hash of the unsigned message (to be signed) */ private computeUnsignedHash; } }