https://github.com/ethereumjs/ethereumjs-tx
A simple module for creating, manipulating and signing ethereum transactions
安裝:
npm install ethereumjs-tx --save
example:
const EthereumTx = require('ethereumjs-tx') const privateKey = Buffer.from('e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex') const txParams = { nonce: '0x00', gasPrice: '0x09184e72a000', gasLimit: '0x2710', to: '0x0000000000000000000000000000000000000000', value: '0x00', data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', // EIP 155 chainId - mainnet: 1, ropsten: 3 chainId: 3 } const tx = new EthereumTx(txParams) tx.sign(privateKey) const serializedTx = tx.serialize()
Note: this package expects ECMAScript 6 (ES6) as a minimum environment. From browsers lacking ES6 support, please use a shim (like es6-shim) before including any of the builds from this repo.
支持ES6及以上,如果你的瀏覽器不支持ES6,則使用shim以兼容
BROWSER
For a browser build please see https://github.com/ethereumjs/browser-builds.
API
Transaction
Creates a new transaction object.
Parameters
data
Buffer or Array or Object a transaction can be initiailized with either a buffer containing the RLP serialized transaction or an array of buffers relating to each of the tx Properties, listed in order below in the exmple.Or lastly an Object containing the Properties of the transaction like in the Usage example.For Object and Arrays each of the elements can either be a Buffer, a hex-prefixed (0x) String , Number, or an object with a toBuffer method such as Bignumdata.chainId
Number EIP 155 chainId - mainnet: 1, ropsten: 3data.gasLimit
Buffer transaction gas limitdata.gasPrice
Buffer transaction gas pricedata.to
Buffer to the to addressdata.nonce
Buffer nonce numberdata.data
Buffer this will contain the data of the message or the init of a contractdata.v
Buffer EC recovery IDdata.r
Buffer EC signature parameterdata.s
Buffer EC signature parameterdata.value
Buffer the amount of ether sent
Properties
raw
Buffer The raw rlp encoded transaction
var rawTx = { nonce: '0x00', gasPrice: '0x09184e72a000', gasLimit: '0x2710', to: '0x0000000000000000000000000000000000000000', value: '0x00', data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', v: '0x1c', r: '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab', s: '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13' }; var tx = new Transaction(rawTx);
getBaseFee
the minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee)
Returns BN
該筆交易最少要花費的gas
getChainId
返回chain id,即說明連接的network
Returns Buffer
getDataFee
The amount of gas paid for the data in this tx為了傳遞交易中的data需要花費的gas
Returns BN
getSenderAddress
returns the sender's address返回交易發送者的address
Returns Buffer
getSenderPublicKey
returns the public key of the sender返回交易發送者的公鑰
Returns Buffer
getUpfrontCost
the up front amount that an account must have for this transaction to be valid為使該交易有效,帳戶必須擁有的預備金額
Returns BN
hash
Computes a sha3-256 hash of the serialized tx計算序列化交易的hash值
Parameters
includeSignature
[Boolean] whether or not to inculde the signature (optional, defaulttrue
)是否包含簽名,默認為true
Returns Buffer
sign
sign a transaction with a given a private key使用privateKey
去對交易進行簽名
Parameters
privateKey
Buffer
toCreationAddress
If the tx's to
is to the creation address如果交易的to的地址是0x00000000...,返回true
Returns Boolean
validate
validates the signature and checks to see if it has enough gas確認簽名並檢查是否有足夠的gas
Parameters
stringError
[Boolean] whether to return a string with a dscription of why the validation failed or return a Bloolean (optional, defaultfalse
)是返回一個帶有驗證失敗原因標記的字符串,還是返回一個布爾值
Returns Boolean or String
verifySignature
Determines if the signature is valid確認簽名是否有效
Returns Boolean
from
Properties
from
Buffer (read only) sender address of this transaction, mathematically derived from other parameters.屬性from即交易的發送者的address
serialize
Returns the rlp encoding of the transaction返回交易的rlp編碼格式
Returns Buffer
實現代碼:
'use strict' const ethUtil = require('ethereumjs-util') const fees = require('ethereum-common/params.json') const BN = ethUtil.BN // secp256k1n/2 const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16) /** * Creates a new transaction object. * * @example * var rawTx = { * nonce: '0x00', * gasPrice: '0x09184e72a000', * gasLimit: '0x2710', * to: '0x0000000000000000000000000000000000000000', * value: '0x00', * data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', * v: '0x1c', * r: '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab', * s: '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13' * }; * var tx = new Transaction(rawTx); * * @class * @param {Buffer | Array | Object} data a transaction can be initiailized with either a buffer containing the RLP serialized transaction or an array of buffers relating to each of the tx Properties, listed in order below in the exmple. * * Or lastly an Object containing the Properties of the transaction like in the Usage example. * * For Object and Arrays each of the elements can either be a Buffer, a hex-prefixed (0x) String , Number, or an object with a toBuffer method such as Bignum * * @property {Buffer} raw The raw rlp encoded transaction * @param {Buffer} data.nonce nonce number * @param {Buffer} data.gasLimit transaction gas limit * @param {Buffer} data.gasPrice transaction gas price * @param {Buffer} data.to to the to address * @param {Buffer} data.value the amount of ether sent * @param {Buffer} data.data this will contain the data of the message or the init of a contract * @param {Buffer} data.v EC recovery ID * @param {Buffer} data.r EC signature parameter * @param {Buffer} data.s EC signature parameter * @param {Number} data.chainId EIP 155 chainId - mainnet: 1, ropsten: 3 * */ class Transaction { constructor (data) { data = data || {} // Define Properties,定義生成的raw transaction的屬性 const fields = [{ name: 'nonce', length: 32, allowLess: true, default: new Buffer([]) }, { name: 'gasPrice', length: 32, allowLess: true, default: new Buffer([]) }, { name: 'gasLimit', alias: 'gas', length: 32, allowLess: true, default: new Buffer([]) }, { name: 'to', allowZero: true, length: 20, default: new Buffer([]) }, { name: 'value', length: 32, allowLess: true, default: new Buffer([]) }, { name: 'data', alias: 'input', allowZero: true, default: new Buffer([]) }, { name: 'v', allowZero: true, default: new Buffer([0x1c]) }, { name: 'r', length: 32, allowZero: true, allowLess: true, default: new Buffer([]) }, { name: 's', length: 32, allowZero: true, allowLess: true, default: new Buffer([]) }] /** * Returns the rlp encoding of the transaction * @method serialize * @return {Buffer} * @memberof Transaction * @name serialize */ // attached serialize ethUtil.defineProperties(this, fields, data) /** * @property {Buffer} from (read only) sender address of this transaction, mathematically derived from other parameters. * @name from * @memberof Transaction */ Object.defineProperty(this, 'from', { enumerable: true, configurable: true, get: this.getSenderAddress.bind(this) }) // calculate chainId from signature let sigV = ethUtil.bufferToInt(this.v) let chainId = Math.floor((sigV - 35) / 2) if (chainId < 0) chainId = 0 // set chainId this._chainId = chainId || data.chainId || 0 this._homestead = true //即該簽名的版本為homestead } /** * If the tx's `to` is to the creation address * @return {Boolean} */ toCreationAddress () { return this.to.toString('hex') === '' } /** * Computes a sha3-256 hash of the serialized tx * @param {Boolean} [includeSignature=true] whether or not to inculde the signature * @return {Buffer} */ hash (includeSignature) { if (includeSignature === undefined) includeSignature = true //是否包含簽名,默認為true // EIP155 spec: // when computing the hash of a transaction for purposes of signing or recovering, // instead of hashing only the first six elements (ie. nonce, gasprice, startgas, to, value, data), // hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0 let items if (includeSignature) {//包含簽名v,r,s items = this.raw } else { if (this._chainId > 0) {//如果chainId存在,則只是將r,s設為0 const raw = this.raw.slice() this.v = this._chainId this.r = 0 this.s = 0 items = this.raw this.raw = raw } else {//如果chainId不存在,則直接將v,r,s去掉 items = this.raw.slice(0, 6) } } // create hash return ethUtil.rlphash(items)//然后進行hash } /** * returns chain ID * @return {Buffer} */ getChainId () { return this._chainId } /** * returns the sender's address * @return {Buffer} */ getSenderAddress () { if (this._from) { return this._from } const pubkey = this.getSenderPublicKey() this._from = ethUtil.publicToAddress(pubkey) return this._from } /** * returns the public key of the sender * @return {Buffer} */ getSenderPublicKey () { if (!this._senderPubKey || !this._senderPubKey.length) { if (!this.verifySignature()) throw new Error('Invalid Signature') } return this._senderPubKey } /** * Determines if the signature is valid,確認簽名是否有效 * @return {Boolean} */ verifySignature () { const msgHash = this.hash(false) // All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid.簽名的s大於secp256k1n/2則無效 if (this._homestead && new BN(this.s).cmp(N_DIV_2) === 1) { return false } try { let v = ethUtil.bufferToInt(this.v) if (this._chainId > 0) { v -= this._chainId * 2 + 8 //將v值轉回正常的chain id值 } this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s) //根據簽名得到發送者的公鑰 } catch (e) { return false } return !!this._senderPubKey //有公鑰則返回true,否則為false } /** * sign a transaction with a given private key * @param {Buffer} privateKey */ sign (privateKey) { const msgHash = this.hash(false) const sig = ethUtil.ecsign(msgHash, privateKey) if (this._chainId > 0) { sig.v += this._chainId * 2 + 8 } Object.assign(this, sig) } /** * The amount of gas paid for the data in this tx * @return {BN} */ getDataFee () { const data = this.raw[5] const cost = new BN(0) for (let i = 0; i < data.length; i++) { data[i] === 0 ? cost.iaddn(fees.txDataZeroGas.v) : cost.iaddn(fees.txDataNonZeroGas.v)//根據data是否有值來決定怎么加 } return cost } /** * the minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee) * @return {BN} */ getBaseFee () { const fee = this.getDataFee().iaddn(fees.txGas.v) if (this._homestead && this.toCreationAddress()) { fee.iaddn(fees.txCreation.v) } return fee } /** * the up front amount that an account must have for this transaction to be valid * @return {BN} */ getUpfrontCost () {//為使該交易有效,帳戶必須擁有的預備金額 return new BN(this.gasLimit) .imul(new BN(this.gasPrice)) .iadd(new BN(this.value)) } /** * validates the signature and checks to see if it has enough gas * @param {Boolean} [stringError=false] whether to return a string with a description of why the validation failed or return a Boolean * @return {Boolean|String} */ validate (stringError) {//確認簽名並檢查是否有足夠的gas const errors = [] if (!this.verifySignature()) { errors.push('Invalid Signature') } if (this.getBaseFee().cmp(new BN(this.gasLimit)) > 0) { errors.push([`gas limit is too low. Need at least ${this.getBaseFee()}`]) } if (stringError === undefined || stringError === false) { return errors.length === 0 } else { return errors.join(' ') } } } module.exports = Transaction
實例:
// see full article here https://wanderer.github.io/ethereum/2014/06/14/creating-and-verifying-transaction-with-node/ var Transaction = require('ethereumjs-tx') // create a blank transaction var tx = new Transaction(null, 1) // mainnet Tx EIP155 // So now we have created a blank transaction but Its not quiet valid yet. We // need to add some things to it. Lets start: // notice we don't set the `to` field because we are creating a new contract. tx.nonce = 0 tx.gasPrice = 100 tx.gasLimit = 1000 tx.value = 0 tx.data = '0x7f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f60365960003356573360003557600035335700' var privateKey = new Buffer.from('e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex') tx.sign(privateKey) // We have a signed transaction, Now for it to be fully fundable the account that we signed // it with needs to have a certain amount of wei in to. To see how much this // account needs we can use the getUpfrontCost() method. var feeCost = tx.getUpfrontCost() tx.gas = feeCost console.log('Total Amount of wei needed:' + feeCost.toString()) // if your wondering how that is caculated it is // bytes(data length) * 5 // + 500 Default transaction fee // + gasAmount * gasPrice // lets serialize the transaction console.log('---Serialized TX----') console.log(tx.serialize().toString('hex')) console.log('--------------------') // Now that we have the serialized transaction we can get AlethZero to except by // selecting debug>inject transaction and pasting the transaction serialization and // it should show up in pending transaction. // Parsing & Validating transactions // If you have a transaction that you want to verify you can parse it. If you got // it directly from the network it will be rlp encoded. You can decode you the rlp // module. After that you should have something like var rawTx = [ '0x00', '0x09184e72a000', '0x2710', '0x0000000000000000000000000000000000000000', '0x00', '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', '0x1c', '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab', '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13' ] var tx2 = new Transaction(rawTx) // Note rlp.decode will actully produce an array of buffers `new Transaction` will // take either an array of buffers or an array of hex strings. // So assuming that you were able to parse the tranaction, we will now get the sender's // address console.log('Senders Address: ' + tx2.getSenderAddress().toString('hex')) // Cool now we know who sent the tx! Lets verfy the signature to make sure it was not // some poser. if (tx2.verifySignature()) { console.log('Signature Checks out!') } // And hopefully its verified. For the transaction to be totally valid we would // also need to check the account of the sender and see if they have at least // `TotalFee`.
返回:
userdeMacBook-Pro:test-hd-wallet user$ node transaction.js Total Amount of wei needed:100000 ---Serialized TX---- f8e48064830186a08080b8977f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f603659600033565733600035576000353357001ca0b8b9fedc076110cd002224a942e9d7099e4a626ebf66cd9301fc18e2c1181806a04e270be511d42189baf14599eb8d6eb5037ab105032dd3e0fa05b43dad4cb4c2 -------------------- Senders Address: 1f36f546477cda21bf2296c50976f2740247906f Signature Checks out!