Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

micro-eth-signer

Package Overview
Dependencies
Maintainers
1
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

micro-eth-signer - npm Package Compare versions

Comparing version 0.1.7 to 0.2.0

41

index.d.ts

@@ -9,9 +9,35 @@ /*! micro-eth-signer - MIT License (c) Paul Miller (paulmillr.com) */

};
export declare const TRANSACTION_TYPES: {
legacy: number;
eip2930: number;
eip1559: number;
};
export declare function add0x(hex: string): string;
export declare function strip0x(hex: string): string;
declare type Chain = keyof typeof CHAIN_TYPES;
declare type Type = keyof typeof TRANSACTION_TYPES;
declare const FIELDS: readonly ["nonce", "gasPrice", "gasLimit", "to", "value", "data", "v", "r", "s"];
export declare type Field = typeof FIELDS[number];
export declare type RawTx = [string, string, string, string, string, string, string, string, string];
export declare type RawTxMap = Record<Field, string>;
declare const FIELDS2930: readonly ["chainId", "nonce", "gasPrice", "gasLimit", "to", "value", "data", "accessList", "yParity", "r", "s"];
declare const FIELDS1559: readonly ["chainId", "nonce", "maxPriorityFeePerGas", "maxFeePerGas", "gasLimit", "to", "value", "data", "accessList", "yParity", "r", "s"];
export declare type Field = typeof FIELDS[number] | typeof FIELDS2930[number] | typeof FIELDS1559[number] | 'address' | 'storageKey';
export declare type RawTxLegacy = [string, string, string, string, string, string, string, string, string];
export declare type RawTx2930 = [string, string, string, string, string, string, [string, string[]][], string, string, string];
export declare type RawTx1559 = [string, string, string, string, string, string, string, [string, string[]][], string, string, string];
export declare type RawTx = RawTxLegacy | RawTx2930 | RawTx1559;
export declare type RawTxMap = {
chainId?: string;
nonce: string;
gasPrice?: string;
maxPriorityFeePerGas?: string;
maxFeePerGas?: string;
gasLimit: string;
to: string;
value: string;
data: string;
accessList?: [string, string[]][];
yParity?: string;
v?: string;
r: string;
s: string;
};
export declare const Address: {

@@ -24,12 +50,14 @@ fromPrivateKey(key: string | Uint8Array): string;

export declare class Transaction {
readonly chain: Chain;
readonly hardfork: string;
static DEFAULT_HARDFORK: string;
static DEFAULT_CHAIN: Chain;
static DEFAULT_TYPE: Type;
readonly hex: string;
readonly raw: RawTxMap;
readonly isSigned: boolean;
constructor(data: string | Uint8Array | RawTx | RawTxMap, chain?: Chain, hardfork?: string);
readonly type: Type;
constructor(data: string | Uint8Array | RawTx | RawTxMap, chain?: Chain, hardfork?: string, type?: Type);
get bytes(): Uint8Array;
equals(other: Transaction): boolean;
get chain(): Chain | undefined;
get sender(): string;

@@ -41,5 +69,4 @@ get amount(): bigint;

get nonce(): number;
private prepare;
private supportsReplayProtection;
getMessageToSign(): string;
getMessageToSign(signed?: boolean): string;
get hash(): string;

@@ -46,0 +73,0 @@ sign(privateKey: string | Uint8Array): Promise<Transaction>;

298

index.js
"use strict";
/*! micro-eth-signer - MIT License (c) Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transaction = exports.Address = exports.strip0x = exports.add0x = exports.CHAIN_TYPES = void 0;
exports.Transaction = exports.Address = exports.strip0x = exports.add0x = exports.TRANSACTION_TYPES = exports.CHAIN_TYPES = void 0;
const js_sha3_1 = require("js-sha3");

@@ -9,2 +9,3 @@ const rlp = require("micro-rlp");

exports.CHAIN_TYPES = { mainnet: 1, ropsten: 3, rinkeby: 4, goerli: 5, kovan: 42 };
exports.TRANSACTION_TYPES = { legacy: 0, eip2930: 1, eip1559: 2 };
function add0x(hex) {

@@ -18,2 +19,18 @@ return /^0x/i.test(hex) ? hex : `0x${hex}`;

exports.strip0x = strip0x;
function cloneDeep(obj) {
if (Array.isArray(obj)) {
return obj.map((i) => cloneDeep(i));
}
else if (typeof obj === 'bigint') {
return BigInt(obj);
}
else if (typeof obj === 'object') {
let res = {};
for (let key in obj)
res[key] = cloneDeep(obj[key]);
return res;
}
else
return obj;
}
function bytesToHex(uint8a) {

@@ -26,6 +43,5 @@ let hex = '';

}
const padHex = (hex) => (hex.length & 1 ? `0${hex}` : hex);
function hexToBytes(hex) {
hex = strip0x(hex);
if (hex.length & 1)
hex = `0${hex}`;
hex = padHex(strip0x(hex));
const array = new Uint8Array(hex.length / 2);

@@ -38,5 +54,2 @@ for (let i = 0; i < array.length; i++) {

}
function hexToBytesUnpadded(num) {
return num === '0x' || BigInt(num) === 0n ? new Uint8Array() : hexToBytes(num);
}
function numberToHex(num, padToBytes = 0) {

@@ -54,34 +67,154 @@ const hex = num.toString(16);

const FIELDS = ['nonce', 'gasPrice', 'gasLimit', 'to', 'value', 'data', 'v', 'r', 's'];
function mapToArray(input) {
return FIELDS.map((key) => input[key]);
}
const FIELDS2930 = ['chainId', 'nonce', 'gasPrice', 'gasLimit', 'to', 'value', 'data', 'accessList',
'yParity', 'r', 's'];
const FIELDS1559 = ['chainId', 'nonce', 'maxPriorityFeePerGas', 'maxFeePerGas', 'gasLimit', 'to', 'value',
'data', 'accessList', 'yParity', 'r', 's'];
const TypeToFields = {
legacy: FIELDS,
eip2930: FIELDS2930,
eip1559: FIELDS1559,
};
const FIELD_NUMBER = new Set(['chainId', 'nonce', 'gasPrice', 'maxPriorityFeePerGas', 'maxFeePerGas',
'gasLimit', 'value', 'v', 'yParity', 'r', 's']);
const FIELD_DATA = new Set(['data', 'to', 'storageKey', 'address']);
function normalizeField(field, value) {
if (field === 'gasLimit' && !value) {
value = '0x5208';
if (FIELD_NUMBER.has(field)) {
if (value instanceof Uint8Array)
value = add0x(bytesToHex(value));
if (field === 'yParity' && typeof value === 'boolean')
value = value ? '0x1' : '0x0';
if (typeof value === 'string')
value = BigInt(value === '0x' ? '0x0' : value);
if (typeof value === 'number' || typeof value === 'bigint')
value = add0x(padHex(value.toString(16)));
if (field === 'gasLimit' && (!value || BigInt(value) === 0n))
value = '0x5208';
if (typeof value !== 'string')
throw new TypeError(`Invalid type for field ${field}`);
if (field === 'gasPrice' && BigInt(value) === 0n)
throw new TypeError('The gasPrice must have non-zero value');
return BigInt(value) === 0n ? '' : value;
}
if (['nonce', 'gasPrice', 'gasLimit', 'value'].includes(field)) {
if (typeof value === 'number' || typeof value === 'bigint') {
value = value.toString(16);
if (FIELD_DATA.has(field)) {
if (!value)
value = '';
if (value instanceof Uint8Array)
value = bytesToHex(value);
if (typeof value !== 'string')
throw new TypeError(`Invalid type for field ${field}`);
value = add0x(value);
return value === '0x' ? '' : value;
}
if (field === 'accessList') {
if (!value)
return [];
let res = {};
if (Array.isArray(value)) {
for (let access of value) {
if (Array.isArray(access)) {
if (access.length !== 2 || !Array.isArray(access[1]))
throw new TypeError(`Invalid type for field ${field}`);
const key = normalizeField('address', access[0]);
if (!res[key])
res[key] = new Set();
for (let i of access[1])
res[key].add(normalizeField('storageKey', i));
}
else {
if (typeof access !== 'object' ||
access === null ||
!access.address ||
!Array.isArray(access.storageKeys))
throw new TypeError(`Invalid type for field ${field}`);
const key = normalizeField('address', access.address);
if (!res[key])
res[key] = new Set();
for (let i of access.storageKeys)
res[key].add(normalizeField('storageKey', i));
}
}
}
if (typeof value === 'string') {
if (['0', '00', '0x', '0x00'].includes(value))
value = '';
}
else {
throw new TypeError(`Invalid type for field ${field}`);
if (typeof value !== 'object' || value === null || value instanceof Uint8Array)
throw new TypeError(`Invalid type for field ${field}`);
for (let k in value) {
const key = normalizeField('address', k);
if (!value[k])
continue;
if (!Array.isArray(value[k]))
throw new TypeError(`Invalid type for field ${field}`);
res[key] = new Set(value[k].map((i) => normalizeField('storageKey', i)));
}
}
return Object.keys(res).map((i) => [i, Array.from(res[i])]);
}
if (['gasPrice'].includes(field) && !value) {
throw new TypeError('The field must have non-zero value');
throw new TypeError(`Invalid type for field ${field}`);
}
function possibleTypes(input) {
let types = new Set(Object.keys(exports.TRANSACTION_TYPES));
const keys = new Set(Object.keys(input));
if (keys.has('maxPriorityFeePerGas') || keys.has('maxFeePerGas')) {
types.delete('legacy');
types.delete('eip2930');
}
if (['v', 'r', 's', 'data'].includes(field) && !value)
return '';
if (typeof value !== 'string')
throw new TypeError(`Invalid type for field ${field}`);
return value;
if (keys.has('accessList') || keys.has('yParity'))
types.delete('legacy');
if (keys.has('gasPrice'))
types.delete('eip1559');
return types;
}
function rawToSerialized(input) {
const initial = Array.isArray(input) ? input : mapToArray(input);
const normalized = initial.map((value, i) => add0x(normalizeField(FIELDS[i], value)));
return add0x(bytesToHex(rlp.encode(normalized)));
const RawTxLength = { 9: 'legacy', 11: 'eip2930', 12: 'eip1559' };
const RawTxLengthRev = { legacy: 9, eip2930: 11, eip1559: 12 };
function rawToSerialized(input, chain, type) {
let chainId;
if (chain)
chainId = exports.CHAIN_TYPES[chain];
if (Array.isArray(input)) {
if (!type)
type = RawTxLength[input.length];
if (!type || RawTxLengthRev[type] !== input.length)
throw new Error(`Invalid fields length for ${type}`);
}
else {
const types = possibleTypes(input);
if (type && !types.has(type)) {
throw new Error(`Invalid type=${type}. Possible types with current fields: ${Array.from(types)}`);
}
if (!type) {
if (types.has('legacy'))
type = 'legacy';
else if (!types.size)
throw new Error('Impossible fields set');
else
type = Array.from(types)[0];
}
if (input.chainId) {
if (chain) {
const fromChain = normalizeField('chainId', exports.CHAIN_TYPES[chain]);
const fromInput = normalizeField('chainId', input.chainId);
if (fromChain !== fromInput) {
throw new Error(`Both chain=${chain}(${fromChain}) and chainId=${input.chainId}(${fromInput}) specified at same time`);
}
}
chainId = input.chainId;
}
else
input.chainId = chainId;
input = TypeToFields[type].map((key) => input[key]);
}
if (input) {
const sign = input.slice(-3);
if (!sign[0] || !sign[1] || !sign[2]) {
input = input.slice(0, -3);
if (type === 'legacy' && chainId)
input.push(normalizeField('chainId', chainId), '', '');
}
}
let normalized = input.map((value, i) => normalizeField(TypeToFields[type][i], value));
if (chainId)
chainId = normalizeField('chainId', chainId);
if (type !== 'legacy' && chainId && normalized[0] !== chainId)
throw new Error(`ChainId=${normalized[0]} incompatible with Chain=${chainId}`);
const tNum = exports.TRANSACTION_TYPES[type];
return (tNum ? `0x0${tNum}` : '0x') + bytesToHex(rlp.encode(normalized));
}

@@ -138,4 +271,3 @@ exports.Address = {

class Transaction {
constructor(data, chain = Transaction.DEFAULT_CHAIN, hardfork = Transaction.DEFAULT_HARDFORK) {
this.chain = chain;
constructor(data, chain, hardfork = Transaction.DEFAULT_HARDFORK, type) {
this.hardfork = hardfork;

@@ -150,3 +282,3 @@ let norm;

else if (Array.isArray(data) || (typeof data === 'object' && data != null)) {
norm = rawToSerialized(data);
norm = rawToSerialized(data, chain, type);
}

@@ -158,10 +290,31 @@ else {

throw new Error('Invalid tx length');
this.hex = norm;
const ui8a = rlp.decode(add0x(norm));
const arr = ui8a.map(bytesToHex).map((i) => (i ? add0x(i) : i));
this.raw = arr.reduce((res, value, i) => {
const name = FIELDS[i];
res[name] = value;
this.hex = add0x(norm);
let txData;
const prevType = type;
if (this.hex.startsWith('0x01'))
[txData, type] = [add0x(this.hex.slice(4)), 'eip2930'];
else if (this.hex.startsWith('0x02'))
[txData, type] = [add0x(this.hex.slice(4)), 'eip1559'];
else
[txData, type] = [this.hex, 'legacy'];
if (prevType && prevType !== type)
throw new Error('Invalid transaction type');
this.type = type;
const ui8a = rlp.decode(txData);
this.raw = ui8a.reduce((res, value, i) => {
const name = TypeToFields[type][i];
if (!name)
return res;
res[name] = normalizeField(name, value);
return res;
}, {});
if (!this.raw.chainId) {
if (type === 'legacy' && !this.raw.r && !this.raw.s) {
this.raw.chainId = this.raw.v;
this.raw.v = '';
}
}
if (!this.raw.chainId) {
this.raw.chainId = normalizeField('chainId', exports.CHAIN_TYPES[chain || Transaction.DEFAULT_CHAIN]);
}
this.isSigned = !!(this.raw.r && this.raw.r !== '0x');

@@ -175,2 +328,7 @@ }

}
get chain() {
for (let k in exports.CHAIN_TYPES)
if (exports.CHAIN_TYPES[k] === Number(this.raw.chainId))
return k;
}
get sender() {

@@ -186,2 +344,4 @@ const sender = this.recoverSenderPublicKey();

get fee() {
if (this.type === 'eip1559')
return BigInt(this.raw.maxFeePerGas) * BigInt(this.raw.gasLimit);
return BigInt(this.raw.gasPrice) * BigInt(this.raw.gasLimit);

@@ -198,15 +358,2 @@ }

}
prepare() {
return [
hexToBytesUnpadded(this.raw.nonce),
hexToBytesUnpadded(this.raw.gasPrice),
hexToBytesUnpadded(this.raw.gasLimit),
hexToBytes(this.raw.to),
hexToBytesUnpadded(this.raw.value),
hexToBytesUnpadded(this.raw.data),
hexToBytesUnpadded(this.raw.v),
hexToBytesUnpadded(this.raw.r),
hexToBytesUnpadded(this.raw.s),
];
}
supportsReplayProtection() {

@@ -217,14 +364,17 @@ const properBlock = !['chainstart', 'homestead', 'dao', 'tangerineWhistle'].includes(this.hardfork);

const v = Number(hexToNumber(this.raw.v));
const chainId = exports.CHAIN_TYPES[this.chain];
const chainId = Number(this.raw.chainId);
const meetsConditions = v === chainId * 2 + 35 || v === chainId * 2 + 36;
return properBlock && meetsConditions;
}
getMessageToSign() {
const values = this.prepare().slice(0, 6);
if (this.supportsReplayProtection()) {
values.push(hexToBytes(numberToHex(exports.CHAIN_TYPES[this.chain])));
values.push(new Uint8Array());
values.push(new Uint8Array());
getMessageToSign(signed = false) {
let values = TypeToFields[this.type].map((i) => this.raw[i]);
if (!signed) {
values = values.slice(0, -3);
if (this.type === 'legacy' && this.supportsReplayProtection())
values.push(this.raw.chainId, '', '');
}
return js_sha3_1.keccak256(rlp.encode(values));
let encoded = rlp.encode(values);
if (this.type !== 'legacy')
encoded = new Uint8Array([exports.TRANSACTION_TYPES[this.type], ...Array.from(encoded)]);
return js_sha3_1.keccak256(encoded);
}

@@ -234,3 +384,3 @@ get hash() {

throw new Error('Expected signed transaction');
return js_sha3_1.keccak256(rlp.encode(this.prepare()));
return this.getMessageToSign(true);
}

@@ -247,13 +397,14 @@ async sign(privateKey) {

const signature = secp256k1.Signature.fromHex(hex);
const chainId = exports.CHAIN_TYPES[this.chain];
const vv = chainId ? recovery + (chainId * 2 + 35) : recovery + 27;
const chainId = Number(this.raw.chainId);
const vv = this.type === 'legacy' ? (chainId ? recovery + (chainId * 2 + 35) : recovery + 27) : recovery;
const [v, r, s] = [vv, signature.r, signature.s].map((n) => add0x(numberToHex(n)));
const signedRaw = Object.assign({}, this.raw, { v, r, s });
return new Transaction(signedRaw, this.chain, this.hardfork);
const signedRaw = this.type === 'legacy'
? { ...this.raw, v, r, s }
: { ...cloneDeep(this.raw), yParity: v, r, s };
return new Transaction(signedRaw, this.chain, this.hardfork, this.type);
}
recoverSenderPublicKey() {
if (!this.isSigned) {
if (!this.isSigned)
throw new Error('Expected signed transaction: cannot recover sender of unsigned tx');
}
const [vv, r, s] = [this.raw.v, this.raw.r, this.raw.s].map((n) => hexToNumber(n));
const [r, s] = [this.raw.r, this.raw.s].map((n) => hexToNumber(n));
if (this.hardfork !== 'chainstart' && s && s > secp256k1.CURVE.n / 2n) {

@@ -263,5 +414,5 @@ throw new Error('Invalid signature: s is invalid');

const signature = new secp256k1.Signature(r, s).toHex();
const chainId = exports.CHAIN_TYPES[this.chain];
const v = Number(vv);
const recovery = chainId ? v - (chainId * 2 + 35) : v - 27;
const v = Number(hexToNumber(this.type === 'legacy' ? this.raw.v : this.raw.yParity));
const chainId = Number(this.raw.chainId);
const recovery = this.type === 'legacy' ? (chainId ? v - (chainId * 2 + 35) : v - 27) : v;
return secp256k1.recoverPublicKey(this.getMessageToSign(), signature, recovery);

@@ -271,3 +422,4 @@ }

exports.Transaction = Transaction;
Transaction.DEFAULT_HARDFORK = 'berlin';
Transaction.DEFAULT_HARDFORK = 'london';
Transaction.DEFAULT_CHAIN = 'mainnet';
Transaction.DEFAULT_TYPE = 'legacy';
{
"name": "micro-eth-signer",
"version": "0.1.7",
"description": "Create, sign and validate Ethereum transactions & addresses with minimum deps",
"version": "0.2.0",
"description": "Create, sign and validate Ethereum transactions & addresses with minimum deps. Supports London & Berlin txs",
"main": "index.js",

@@ -45,3 +45,3 @@ "files": [

"micro-rlp": "2.2.9",
"noble-secp256k1": "^1.2.4"
"noble-secp256k1": "^1.2.7"
},

@@ -48,0 +48,0 @@ "devDependencies": {

@@ -7,2 +7,4 @@ # micro-eth-signer

Validated with over 3 MB of ethers.js test vectors!
## Usage

@@ -35,7 +37,7 @@

console.log('Need wei', tx.upfrontCost); // also, tx.fee, tx.amount, tx.sender, etc
// Address manipulation
const addr = Address.fromPrivateKey(privateKey);
const pubKey = signedTx.recoverSenderPublicKey();
console.log('Verified', Address.verifyChecksum(addr));
console.log('Verified', Address.verifyChecksum(addr));
console.log('addr is correct', signedTx.sender, signedTx.sender == addr);

@@ -42,0 +44,0 @@ console.log(signedTx);

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc