以太坊从公钥生成地址的具体过程

网上有大量的文章介绍了ECDSA(椭圆曲线加密)算法来生成以太坊公私钥对,进而生成一个唯一的以太坊地址。其中绝大部分都提到了非压缩公钥生成地址时先进行哈希运算,然后取后40位就是地址了。然而绝知此事要躬为。到底怎么哈希的,公钥和私钥的格式是什么,相信很多人和我一样不清楚!

笔者因为在研究一个东西,需要顺带弄明白以太坊公钥生成地址的细节流程。笔者首先百度了一下,参考了知乎上面的一篇文章《以太坊的私钥、公钥、地址、账户》 。然而摸索半天之后,得不到文章中列出的结果。最后笔者将文章中的私钥导入到MetaMask之后,让人大跌眼镜的是,最后得到的地址竟然不是文章中的示例地址。看来一定是哪出问题了,于是笔者决定弄清楚这个公钥到地址的清晰生成流程,不能再模模糊糊了。

知乎中这篇文章中有个流程图:
在这里插入图片描述
这个流程图很正确,只有简单三步,然而结果却不正确,最有可能出错的地方应该就是 从公钥经keccak-256算法到压缩公钥了。具体的Keccak-256函数是什么呢?不同的语言有不同的实现,这里笔者还是以最常用的Node.jsethers框架来进行这个问题的验证。

很幸运,ethers框架中有一个直接使用公钥计算地址的函数,下面是该函数的定义及说明:

ethers.utils.computeAddress( publicOrPrivateKey ) ⇒ string< Address >source
Returns the address for publicOrPrivateKey. A public key may be compressed or uncompressed, 
and a private key will be converted automatically to a public key for the derivation.

这是已经封装好的计算库,我们欲知道详细过程参考它的源码实现就能得到。这里具体的参考过程我不讲了,下面直接贴出验证成功的脚本(假定叫test.js):

//根据公钥生成地址实例详细流程
const eccrypto = require("eccrypto");
const sha3 = require("js-sha3");
const {ethers,utils} = require("ethers")

const private_key  = "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725";
const my_wallet = new ethers.Wallet(private_key)
const public_key = my_wallet.publicKey
printPublicKey(public_key)

//第一步: 移除公钥前两位04,如果包含0x就是移除四位了,再重新加上0x构造
let new_key = "0x" + public_key.substring(4)
//第二步:对上面的结果转化成bytesLike(不能漏)
let new_bytes = utils.arrayify(new_key)
//第三步,keccak_256,得到一个长度为64的哈希值
new_key = sha3.keccak_256(new_bytes)
//第四步,取上面结果的最后40位,就得到了全小写的地址。
let result = "0x" + new_key.substring(24)
//最后,将地址转换成检验后的地址
result = utils.getAddress(result)
console.log("")
console.log(result)
console.log(result === my_wallet.address)

function printPublicKey(public_key) {
    console.log(public_key.substring(2,4))
    let half = (public_key.length - 4)/2
    console.log(public_key.substring(4, 4+half))
    console.log(public_key.substring(4+half))
}

从上面的代码中可以看出,使用公钥生成地址有这么几步:

  1. 移除公钥的前缀04,这个知乎那篇文章中提到:

采用椭圆曲线数字签名算法ECDSA-secp256k1将私钥(32字节)映射成公钥(65字节)(前缀04+X公钥+Y公钥):

但是它却并没有提到计算Keccak-256时要移除前缀。

  1. 将上面移除04后的16进制字符串再次转化为字节数组
  2. 对上一步得到的字节数组进行keccak_256运算,得到一个长度为64的哈希值(压缩公钥,32字节)。
  3. 取上面结果的最后40位,就得到了全小写的地址。
  4. 可选项。将全小写地址转化为校验后的地址。

我们直接node test.js 会得到以下输出:

04
50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352
2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6

0x3E9003153d9A39D3f57B126b0c38513D5e289c3E
true

这样格式的输出是为了和原知乎文章相比较。

至于原文章为什么最后得到的地址是错的,这里笔者重现了一下,原因在于它直接对移除04后的公钥字符串进行了Keccak-256运算,而并未将其转化为字节数组。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
以太坊地址公钥是通过椭圆曲线加密算法生成的。在以太坊中,每个账户都有一个唯一的以太坊地址公钥以太坊公钥和私钥也是通过椭圆曲线加密算法生成的。私钥用于对数据进行加密,公钥用于对数据进行解密。 下面是一个简单的示例,展示如何使用以太坊公钥和私钥对数据进行加解密: 1. 生成公钥和私钥 在以太坊中,可以使用任何一个以太坊钱包生成公钥和私钥。例如,可以使用 MetaMask 钱包生成公钥和私钥。 2. 加密数据 要加密数据,可以使用以太坊公钥和私钥。以下是一个示例加密函数: ```solidity function encrypt(string memory message, address publicKey) public view returns (bytes32) { bytes32 encrypted = keccak256(abi.encodePacked(message, publicKey)); return encrypted; } ``` 在这个函数中,我们使用 keccak256 哈希函数对消息和公钥进行加密。 3. 解密数据 要解密数据,可以使用以太坊公钥和私钥。以下是一个示例解密函数: ```solidity function decrypt(bytes32 encrypted, address privateKey) public view returns (string memory) { bytes32 decrypted = keccak256(abi.encodePacked(encrypted, privateKey)); return string(decrypted); } ``` 在这个函数中,我们使用 keccak256 哈希函数对加密后的消息和私钥进行解密。 请注意,这只是一个简单的示例,实际上在以太坊中使用公钥和私钥进行加解密需要更多的安全措施和实现细节。建议在使用以太坊公钥和私钥进行加解密时,始终遵循最佳实践和安全标准。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AiMateZero

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值