{"content":{"title":"Web3专题(一) 助记词和生成私钥、公钥、地址的基本原理","body":"为了学习钱包、助记词、私钥、公钥、地址的知识，我浏览了大量的网页、博客、社区，没发现一篇令我满意的文章，能够用最简洁的方式 + 实例代码把知识讲解清楚。\r\n\r\n于是乎，我决定自己写一个系列文章。记录我的学习研究成果。\r\n\r\n我认为，用代码一步步生成一个私钥、公钥、地址，是更好的学习方式，接下来就使用 Go 语言来做到这一点。\r\n\r\n### 必备知识\r\n\r\n写代码之前，先补充一些必备知识。\r\n\r\n**1. 椭圆曲线是什么？**\r\n\r\n以太坊和比特币使用完全相同的椭圆曲线：`secp256k1`，公钥就是这个椭圆曲线上的(x, y)坐标，x，y 数值通过私钥唯一确定。\r\n\r\nx、y 各 32 字节，故公钥为 64 字节。你可能会看到 65 字节表示的公钥，这是由 SECG 发布的行业标准的一种序列化编码方式，在最前面加一个字节的前缀，04 表示公钥为非压缩格式，即完整存储了 x 和 y 的坐标各 32 字节。\r\n\r\n**为什么要分为 压缩公钥 和 非压缩公钥 呢？**\r\n\r\n因为在`secp256k1`的椭圆曲线方程里，只要知道其中一个坐标值，另一个坐标值可以通过解方程得出，因此可以只存储其中一个坐标，这样就可以节约 32 个字节，从而引入了压缩格式的公钥，前缀为 02 或 03。\r\n\r\n故非压缩格式的公钥 65 字节（前缀 04 + 64 字节的实际公钥），压缩格式的公钥 33 字节（前缀 02 或 03 + 32 字节的一个坐标值）。\r\n\r\n> 推荐阅读：吴军《数学之美》第 31 章 椭圆曲线加密原理\r\n\r\n**2. 私钥的简单介绍**\r\n\r\n以太坊私钥只是一个随机生成的数字。这个数字是 256 位的，且必须是完全随机的，你可以投掷硬币 256 次，得到一个二进制数字作为私钥。只要不可预测且不可重复，具体怎么得到这个 256 位的随机数字并不重要。\r\n\r\n大多数情况下，我们都是写代码生成私钥，这时候最重要的就是找到密码学安全的随机源，千万不要自己生成随机数或使用编程语言自带的简单随机数，你使用的随机数一定要是密码学安全的，这一点至关重要。\r\n\r\n私钥的重要性不言而喻。需要强调的是，私钥绝对不可以丢失，和银行卡密码不一样，私钥如果丢失无法像找回密码那样还能恢复，这是去中心化的特点。\r\n\r\n> 在中心化世界里，我们用银行卡不担心密码丢失，因为可以找回，但是消费记录、数据隐私无法保证，好比在互联网上裸奔。  \r\n> \r\n> 在去中心化世界，数据倒是可以做到隐私（虽然目前来看，大家并不关心隐私），但是很不方便，比如保存私钥就是个很麻烦的问题。  \r\n> \r\n> 我不是想谈数据隐私、保存私钥的话题，因为目前没什么好的解决方案。而是想引出我一直以来的想法，就是任何事物都具有两面性。正所谓 \"祸兮福所倚，福兮祸所伏\"，当你学习有负面情绪，坚持不下去的时候，想一想负面情绪的好处。我认为正是因为有了负面情绪的存在，才挡住了很多比我们更聪明的人的脚步，让我们得以有机会超越他们。如果学习没有负面情绪，那所有人都坚持学习了，那大家只能看谁最聪明了，这样哪还有普通人的机会。从这个角度，我们需要感谢负面情绪。\r\n\r\n**3. 助记词**\r\n\r\n助记词涉及到钱包的行业标准了，我会在本系列第三篇进一步讲解。现在，简单了解一下，前面说了，私钥是随机生成的一个数字，那如果需要 10 个账户？100 个账户？甚至 1000000 个账户呢？就要随机生成 1000000 个私钥吗？这么多私钥怎么管理？怎么保存？基于此，助记词诞生了。\r\n\r\n助记词可以管理成千上万个私钥，这就需要了解以太坊的 2 种钱包了，从下一篇文章开始，我将连续介绍钱包及开发一个钱包。\r\n\r\n### 使用 Go 语言生成私钥、公钥、地址\r\n\r\n下图是生成私钥、公钥、地址的基本原理，现在根据这个原理图，写出相应的代码。\r\n\r\n\r\n![web3-01-私钥生成原理.png](https://img.learnblockchain.cn/attachments/2023/12/RvHqJNyI657688a3ee936.png)\r\n\r\n1. 需要引入的包\r\n\r\n```go\r\nimport (\r\n    \"crypto/elliptic\"\r\n    \"crypto/rand\"\r\n    \"encoding/hex\"\r\n\r\n    \"github.com/ethereum/go-ethereum/common\"\r\n    \"github.com/ethereum/go-ethereum/crypto\"\r\n    \"github.com/ethereum/go-ethereum/crypto/secp256k1\"\r\n)\r\n```\r\n\r\n2. 生成一个椭圆曲线。\r\n\r\n```go\r\ncurve := secp256k1.S256()\r\n```\r\n\r\n3. 生成私钥（32byte）\r\n\r\n```go\r\nb := make([]byte, curve.Params().N.BitLen()/8)\r\nio.ReadFull(rand.Reader, b)\r\nkey := new(big.Int).SetBytes(b) // key就是私钥\r\n\r\n// 打印私钥\r\nfmt.Println(\"key:\", len(key.Bytes()))\r\nfmt.Println(\"key:\", hex.EncodeToString(key.Bytes()))\r\n```\r\n\r\n4. 生成公钥：对私钥进行椭圆曲线加密，生成公钥（64byte）\r\n\r\n```go\r\nX, Y := curve.ScalarBaseMult(key.Bytes())\r\npubKey := elliptic.Marshal(curve, X, Y)\r\n\r\nfmt.Println(\"pubKey:\", pubKey)\r\n```\r\n\r\n5. 生成地址：去掉公钥第一个字节 04，再使用 keccak256 算法压缩公钥，最后的 20byte 就是地址\r\n\r\n```go\r\ncompressPubKey := crypto.Keccak256(pubKey[1:])\r\naddr := common.BytesToAddress(compressPubKey[12:])\r\nfmt.Println(\"addr:\", addr.String())\r\n```\r\n\r\n6. 验证私钥和地址匹配\r\n\r\n将第 3 步产生的私钥导入 metamask  \r\n查看 metamask 生成的地址，和第 5 步计算出的地址是否匹配\r\n\r\n### 总结\r\n\r\n记住上面的基本原理流程图，就算是学会了本文最重要的内容。"},"author":{"user":"https://learnblockchain.cn/people/2540","address":"0x2b624faC1616D08684Cf1d21793c2f39CC1895a0"},"history":"bafkreidm7sn7xfy53ufukysdudliddjq2ej674xdni2p5vr6d57f4do6he","timestamp":1702267549,"version":1}