Skip to content

Commit

Permalink
Add support for EIP-7702 type=4 transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Sep 18, 2024
1 parent 17183a1 commit b88dcb9
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 126 deletions.
48 changes: 21 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,51 @@
/*! micro-eth-signer - MIT License (c) 2021 Paul Miller (paulmillr.com) */
import { secp256k1 } from '@noble/curves/secp256k1';
import { keccak_256 } from '@noble/hashes/sha3';
import { bytesToHex } from '@noble/hashes/utils';
import { bytesToHex, concatBytes } from '@noble/hashes/utils';
import { UnwrapCoder } from 'micro-packed';
import { addr } from './address.js';
// prettier-ignore
import {
TxType, TxVersions, TxCoder, RawTx,
decodeLegacyV, removeSig, sortRawData, validateFields,
AuthorizationItem, AuthorizationRequest, authorizationRequest
} from './tx.js';
import { RLP } from './rlp.js';
// prettier-ignore
import {
amounts, astr, add0x, ethHex, ethHexNoLeadingZero, strip0x, weieth, weigwei, cloneDeep,
amounts, astr, ethHex, ethHexNoLeadingZero, strip0x, weieth, weigwei, cloneDeep,
} from './utils.js';
export { addr, weigwei, weieth };

// The file exports Transaction, but actual (RLP) parsing logic is done in `./tx`

/**
* Basic message signing & verification. Matches ethers and etherscan behavior.
* TODO: research whether EIP-191 and EIP-712 are popular, add them.
* EIP-7702 Authorizations
*/
export const messenger = {
sign(msg: string, privateKey: string, extraEntropy = undefined) {
astr(msg);
export const authorization = {
_getHash(req: AuthorizationRequest) {
const msg = RLP.encode(authorizationRequest.decode(req));
return keccak_256(concatBytes(new Uint8Array([0x05]), msg));
},
sign(req: AuthorizationRequest, privateKey: string): AuthorizationItem {
astr(privateKey);
const hash = keccak_256(msg);
const sig = secp256k1.sign(hash, ethHex.decode(privateKey), { extraEntropy });
const end = sig.recovery === 0 ? '1b' : '1c';
return add0x(sig.toCompactHex() + end);
const sig = secp256k1.sign(this._getHash(req), ethHex.decode(privateKey));
return { ...req, r: sig.r, s: sig.s, yParity: sig.recovery };
},
verify(signature: string, msg: string, address: string) {
astr(signature);
astr(msg);
astr(address);
signature = strip0x(signature);
if (signature.length !== 65 * 2) throw new Error('invalid signature length');
const sigh = signature.slice(0, -2);
const end = signature.slice(-2);
if (!['1b', '1c'].includes(end)) throw new Error('invalid recovery bit');
const sig = secp256k1.Signature.fromCompact(sigh).addRecoveryBit(end === '1b' ? 0 : 1);
const hash = keccak_256(msg);
const pub = sig.recoverPublicKey(hash).toHex(false);
const recoveredAddr = addr.fromPublicKey(pub);
return recoveredAddr === address && secp256k1.verify(sig, hash, pub);
getAuthority(item: AuthorizationItem) {
const { r, s, yParity, ...req } = item;
const hash = this._getHash(req);
const sig = new secp256k1.Signature(r, s).addRecoveryBit(yParity);
const point = sig.recoverPublicKey(hash);
return addr.fromPublicKey(point.toHex(false));
},
};

// Transaction-related utils.

// 4 fields are required. Others are pre-filled with default values.
const DEFAULTS = {
accessList: [], // needs to be .slice()-d to create new reference
authorizationList: [],
chainId: 1n, // mainnet
data: '',
gasLimit: 21000n, // TODO: investigate if limit is smaller in eip4844 txs
Expand Down Expand Up @@ -128,7 +122,7 @@ export class Transaction<T extends TxType> {
for (const f in DEFAULTS) {
if (f !== 'type' && fields.has(f)) {
raw[f] = DEFAULTS[f as DefaultField];
if (f === 'accessList') raw[f] = cloneDeep(raw[f]);
if (['accessList', 'authorizationList'].includes(f)) raw[f] = cloneDeep(raw[f]);
}
}
// Copy all fields, so we can validate unexpected ones.
Expand Down
7 changes: 2 additions & 5 deletions src/net/archive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IWeb3Provider, Web3CallArgs, hexToNumber, amounts } from '../utils.js';
import { Transaction } from '../index.js';
import { TxVersions, legacySig } from '../tx.js';
import { TxVersions, legacySig, AccessList } from '../tx.js';
import { ContractInfo, createContract, events, ERC20, WETH } from '../abi/index.js';

/*
Expand Down Expand Up @@ -104,7 +104,7 @@ export type TxInfo = {
blockHash: string;
blockNumber: number;
hash: string;
accessList?: [string, string[]][];
accessList?: AccessList;
transactionIndex: number;
type: number;
nonce: bigint;
Expand Down Expand Up @@ -263,9 +263,6 @@ function fixTxInfo(info: TxInfo) {
] as const) {
if (info[i] !== undefined && info[i] !== null) info[i] = BigInt(info[i]!);
}
// Same API as Transaction, so we can re-create easily
if (info.accessList)
info.accessList = info.accessList.map((i: any) => [i.address, i.storageKeys]);
return info;
}

Expand Down
Loading

0 comments on commit b88dcb9

Please sign in to comment.