使用本地私钥
原文:https://web 3py . readthedocs . io/en/stable/web 3 . eth . account . html
## 本地与托管节点
Local Node
A local node is started and controlled by you. It is as safe as you keep it. When you run geth
or parity
on your machine, you are running a local node.
Hosted Node
A hosted node is controlled by someone else. When you connect to Infura, you are connected to a hosted node.
本地密钥与托管密钥
Local Private Key
A key is 32 bytes
of data that you can use to sign transactions and messages, before sending them to your node. You must use send_raw_transaction()
when working with local keys, instead of send_transaction()
.
Hosted Private Key
This is a common way to use accounts with local nodes. Each account returned by w3.eth.accounts
has a hosted private key stored in your node. This allows you to use send_transaction()
.
警告
托管节点提供托管私钥是不可接受的。它让其他人完全控制你的帐户。安德烈亚斯·安东诺普洛斯的名言是“不是你的钥匙,不是你的以太”。
本地私钥的一些常见用法
使用本地私钥的一个非常常见的原因是与托管节点进行交互。
您可能想对本地私钥做的一些常见事情有:
使用私钥通常会以某种方式涉及到w3.eth.account
。请继续阅读,或者在 eth_account.Account
文档中查看您可以做的事情的完整列表。
## 从 geth 密钥文件中提取私钥
注意
可用 ram 的容量应该大于 1GB。
with open('~/.ethereum/keystore/UTC--...--5ce9454909639D2D17A3F753ce7d93fa0b9aB12E') as keyfile:
encrypted_key = keyfile.read()
private_key = w3.eth.account.decrypt(encrypted_key, 'correcthorsebatterystaple')
# tip: do not save the key or password anywhere, especially into a shared source file
签署一条消息
警告
没有一种单一的消息格式被广泛采用并得到社区的一致认可。留意几个选项,像 EIP-683 、 EIP-712 和 EIP-719 。考虑不推荐使用 w3.eth.sign()
方法。
对于这个例子,我们将使用由 w3.eth.sign()
提供的相同的消息散列机制。
>>> from web3.auto import w3
>>> from eth_account.messages import encode_defunct
>>> msg = "I♥SF"
>>> private_key = b"\xb2\\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d"
>>> message = encode_defunct(text=msg)
>>> signed_message = w3.eth.account.sign_message(message, private_key=private_key)
>>> signed_message
SignedMessage(messageHash=HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'),
r=104389933075820307925104709181714897380569894203213074526835978196648170704563,
s=28205917190874851400050446352651915501321657673772411533993420917949420456142,
v=28,
signature=HexBytes('0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c'))
核实消息
带有原始邮件文本和签名:
>>> message = encode_defunct(text="I♥SF")
>>> w3.eth.account.recover_message(message, signature=signed_message.signature)
'0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E'
从消息哈希中验证消息
有时,由于历史原因,您没有原始消息,您有的只是带前缀的散列消息。要验证它,请使用:
警告
不推荐使用这种方法,只有散列通常表明您正在使用某种旧的机制。预计这种方法将在下一个主要版本升级中消失。
>>> message_hash = '0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'
>>> signature = '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c'
>>> w3.eth.account.recoverHash(message_hash, signature=signature)
'0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E'
在 Solidity 中准备电子邮件恢复
假设您想要一个合约来验证一个签名的消息,就像如果您正在制作支付通道,并且您想要验证 Remix 或 web3.js 中的值。
您可能已经在本地生成了 signed_message,如在中签名一条消息。如果是这样,这将为它的坚固性做好准备:
>>> from web3 import Web3
# ecrecover in Solidity expects v as a native uint8, but r and s as left-padded bytes32
# Remix / web3.js expect r and s to be encoded to hex
# This convenience method will do the pad & hex for us:
>>> def to_32byte_hex(val):
... return Web3.toHex(Web3.toBytes(val).rjust(32, b'\0'))
>>> ec_recover_args = (msghash, v, r, s) = (
... Web3.toHex(signed_message.messageHash),
... signed_message.v,
... to_32byte_hex(signed_message.r),
... to_32byte_hex(signed_message.s),
... )
>>> ec_recover_args
('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750',
28,
'0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3',
'0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce')
相反,您可能会收到一条消息和一个十六进制编码的签名。这将为它的稳固做好准备:
>>> from web3 import Web3
>>> from eth_account.messages import encode_defunct, _hash_eip191_message
>>> hex_message = '0x49e299a55346'
>>> hex_signature = '0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb33e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce1c'
# ecrecover in Solidity expects an encoded version of the message
# - encode the message
>>> message = encode_defunct(hexstr=hex_message)
# - hash the message explicitly
>>> message_hash = _hash_eip191_message(message)
# Remix / web3.js expect the message hash to be encoded to a hex string
>>> hex_message_hash = Web3.toHex(message_hash)
# ecrecover in Solidity expects the signature to be split into v as a uint8,
# and r, s as a bytes32
# Remix / web3.js expect r and s to be encoded to hex
>>> sig = Web3.toBytes(hexstr=hex_signature)
>>> v, hex_r, hex_s = Web3.toInt(sig[-1]), Web3.toHex(sig[:32]), Web3.toHex(sig[32:64])
# ecrecover in Solidity takes the arguments in order = (msghash, v, r, s)
>>> ec_recover_args = (hex_message_hash, v, hex_r, hex_s)
>>> ec_recover_args
('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750',
28,
'0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3',
'0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce')
用 ecrecover in Solidity 验证消息
在 Remix 中创建一个简单的 ecrecover 合约:
pragma solidity ^0.4.19;
contract Recover {
function ecr (bytes32 msgh, uint8 v, bytes32 r, bytes32 s) public pure
returns (address sender) {
return ecrecover(msgh, v, r, s);
}
}
然后用这些来自的参数调用 ecr,为 ecrecover 准备可靠的消息在 Remix,"0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750", 28, "0xe6ca9bba58c88611fad66a6ce8f996908195593807c4b38bd528d2cff09d4eb3", "0x3e5bfbbf4d3e39b1a2fd816a7680c19ebebaf3a141b239934ad43cb33fcec8ce"
消息得到了验证,因为我们在响应中得到正确的消息发送者:0x5ce9454909639d2d17a3f753ce7d93fa0b9ab12e
。
## 签署一项交易
创建一个交易,在本地签名,然后发送到你的节点进行广播,用 send_raw_transaction()
。
>>> transaction = {
... 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55',
... 'value': 1000000000,
... 'gas': 2000000,
... 'maxFeePerGas': 2000000000,
... 'maxPriorityFeePerGas': 1000000000,
... 'nonce': 0,
... 'chainId': 1,
... 'type': '0x2', # the type is optional and, if omitted, will be interpreted based on the provided transaction parameters
... 'accessList': ( # accessList is optional for dynamic fee transactions
... {
... 'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
... 'storageKeys': (
... '0x0000000000000000000000000000000000000000000000000000000000000003',
... '0x0000000000000000000000000000000000000000000000000000000000000007',
... )
... },
... {
... 'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
... 'storageKeys': ()
... },
... )
... }
>>> key = '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318'
>>> signed = w3.eth.account.sign_transaction(transaction, key)
>>> signed.rawTransaction
HexBytes('0x02f8e20180843b9aca008477359400831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080f872f85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007d694bb9bc244d798123fde783fcc1c72d3bb8c189413c001a0b9ec671ccee417ff79e06e9e52bfa82b37cf1145affde486006072ca7a11cf8da0484a9beea46ff6a90ac76e7bbf3718db16a8b4b09cef477fb86cf4e123d98fde')
>>> signed.hash
HexBytes('0xe85ce7efa52c16cb5c469c7bde54fbd4911639fdfde08003f65525a85076d915')
>>> signed.r
84095564551732371065849105252408326384410939276686534847013731510862163857293
>>> signed.s
32698347985257114675470251181312399332782188326270244072370350491677872459742
>>> signed.v
1
# When you run send_raw_transaction, you get back the hash of the transaction:
>>> w3.eth.send_raw_transaction(signed.rawTransaction)
'0xe85ce7efa52c16cb5c469c7bde54fbd4911639fdfde08003f65525a85076d915'
签订合约交易
要在本地签署将调用智能合约的事务,请执行以下操作:
- 初始化你的
Contract
对象 - 构建交易
- 签署交易,用
w3.eth.account.sign_transaction()
- 用
send_raw_transaction()
播报事务
# When running locally, execute the statements found in the file linked below to load the EIP20_ABI variable.
# See: https://github.com/carver/ethtoken.py/blob/v0.0.1-alpha.4/ethtoken/abi.py
>>> from web3.auto import w3
>>> unicorns = w3.eth.contract(address="0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", abi=EIP20_ABI)
>>> nonce = w3.eth.get_transaction_count('0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E')
# Build a transaction that invokes this contract's function, called transfer
>>> unicorn_txn = unicorns.functions.transfer(
... '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359',
... 1,
... ).build_transaction({
... 'chainId': 1,
... 'gas': 70000,
... 'maxFeePerGas': w3.toWei('2', 'gwei'),
... 'maxPriorityFeePerGas': w3.toWei('1', 'gwei'),
... 'nonce': nonce,
... })
>>> unicorn_txn
{'value': 0,
'chainId': 1,
'gas': 70000,
'maxFeePerGas': 2000000000,
'maxPriorityFeePerGas': 1000000000,
'nonce': 0,
'to': '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359',
'data': '0xa9059cbb000000000000000000000000fb6916095ca1df60bb79ce92ce3ea74c37c5d3590000000000000000000000000000000000000000000000000000000000000001'}
>>> private_key = b"\xb2\\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d"
>>> signed_txn = w3.eth.account.sign_transaction(unicorn_txn, private_key=private_key)
>>> signed_txn.hash
HexBytes('0x748db062639a45e519dba934fce09c367c92043867409160c9989673439dc817')
>>> signed_txn.rawTransaction
HexBytes('0x02f8b00180843b9aca0084773594008301117094fb6916095ca1df60bb79ce92ce3ea74c37c5d35980b844a9059cbb000000000000000000000000fb6916095ca1df60bb79ce92ce3ea74c37c5d3590000000000000000000000000000000000000000000000000000000000000001c001a0cec4150e52898cf1295cc4020ac0316cbf186071e7cdc5ec44eeb7cdda05afa2a06b0b3a09c7fb0112123c0bef1fd6334853a9dcf3cb5bab3ccd1f5baae926d449')
>>> signed_txn.r
93522894155654168208483453926995743737629589441154283159505514235904280342434
>>> signed_txn.s
48417310681110102814014302147799665717176259465062324746227758019974374282313
>>> signed_txn.v
1
>>> w3.eth.send_raw_transaction(signed_txn.rawTransaction)
# When you run send_raw_transaction, you get the same result as the hash of the transaction:
>>> w3.toHex(w3.keccak(signed_txn.rawTransaction))
'0x748db062639a45e519dba934fce09c367c92043867409160c9989673439dc817'