生成 ETH 公私钥与地址 | Rust & Blockchain
本篇是 Rust 学习笔记的第二篇。在第一篇里,我们魔改出了一个 Encoder,现在我们继续延续我们的魔改之路,挑战一个难度+1的Repo:
Rust library for generating cryptocurrency wallets
https://github.com/AleoHQ/wagyu
魔改目标 0x1:
抽取 Repo 中以太坊私钥、公钥、地址生成的部分,打印到控制台中。
但在魔改之前,笔者首先要对上一篇文章稍作补充,总结一下上篇文章中所涉及的知识点。
上篇文章中所涉及的知识点
- 变量的赋值
- format!函数(连接字符串)
- 库的添加与使用,以wasm-logger为例
- trunk 与 yew 结合,让Rust程序 wasm 化,使其在浏览器中可访问
跑一遍 wagyu
首先要验证这个库符合我们的需求,所以按照 Repo 中的 Readme,采用源码的方式跑一遍。
# Download the source code git clone https://github.com/AleoHQ/wagyu cd wagyu # Build in release mode $ cargo build --release ./target/release/wagyu
成功:
在这个过程里,我们学习到了 cargo 的更多用法:
$ cargo run # 直接执行 $ cargo build # build 出 debug 版本,可执行文件在 ./target/debug 目录下 $ cargo build --release # build 出 正式版本(release version),可执行文件在 ./target/release 下
研究 wagyu 代码
首先喵一眼目录结构:
. ├── AUTHORS ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bitcoin ├── ethereum ├── model ├── monero ├── target ├── zcash └── wagyu ├── cli │ ├── bitcoin.rs │ ├── ethereum.rs │ ├── mod.rs │ ├── monero.rs │ ├── parameters │ └── zcash.rs ├── lib.rs └── main.rs
我们可以看到,主入口是 wagyu
。
在 wagyu
的 main.rs
中,会对 cli
目录下的子模块进行调用,进而对和 cli
平级的子模块进行调用。
其代码如下:
fn main() -> Result { let arguments = App::new("wagyu") .version("v0.6.3") .about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash") .author("Aleo ") .settings(&[ AppSettings::ColoredHelp, AppSettings::DisableHelpSubcommand, AppSettings::DisableVersion, AppSettings::SubcommandRequiredElseHelp, ]) .subcommands(vec![ BitcoinCLI::new(), EthereumCLI::new(), MoneroCLI::new(), ZcashCLI::new(), ]) .set_term_width(0) .get_matches(); match arguments.subcommand() { ("bitcoin", Some(arguments)) => BitcoinCLI::print(BitcoinCLI::parse(arguments)?), ("ethereum", Some(arguments)) => EthereumCLI::print(EthereumCLI::parse(arguments)?), ("monero", Some(arguments)) => MoneroCLI::print(MoneroCLI::parse(arguments)?), ("zcash", Some(arguments)) => ZcashCLI::print(ZcashCLI::parse(arguments)?), _ => unreachable!(), } }
我们再进入 wagyu > cli > ethereum.rs
目录下,发现里面有个简单的函数:
pub fn new(rng: &mut R) -> Result { let private_key = EthereumPrivateKey::new(rng)?; let public_key = private_key.to_public_key(); let address = public_key.to_address(&EthereumFormat::Standard)?; Ok(Self { private_key: Some(private_key.to_string()), public_key: Some(public_key.to_string()), address: Some(address.to_string()), ..Default::default() }) }
很好,就拿这个改造了!
复制必要文件到新项目
- 新建项目
$ cargo new hello-crypto-rust
或者直接把上一个项目复制一份。
- 把
wagyu
的Cargo.toml
中的必要内容复制过来
[dependencies] log = "0.4" pretty_env_logger = "0.3" wagyu-ethereum = { path = "./ethereum", version = "0.6.3" } wagyu-model = { path = "./model", version = "0.6.3" } arrayvec = { version = "0.5.1" } base58 = { version = "0.1" } clap = { version = "~2.33.1" } colored = { version = "1.9" } digest = { version = "0.9.0" } either = { version = "1.5.3" } failure = { version = "0.1.8" } hex = { version = "0.4.2" } lazy_static = { version = "1.4.0" } rand = { version = "0.7" } rand_core = { version = "0.5.1" } safemem = { version = "0.3.3" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } tiny-keccak = { version = "1.4" } [profile.release] opt-level = 3 lto = "thin" incremental = true [profile.bench] opt-level = 3 debug = false rpath = false lto = "thin" incremental = true debug-assertions = false [profile.dev] opt-level = 0 [profile.test] opt-level = 3 incremental = true debug-assertions = true debug = true
- 把
ethereum
与model
两个文件夹复制到hello-crypto-rust
目录下
此时的文件目录是这个样子的:
. ├── Cargo.lock ├── Cargo.toml ├── ethereum ├── model ├── src └── target
补充代码
- 补充
lib.rs
文件
在 src
目录下新建 lib.rs
文件,内容:
pub extern crate wagyu_ethereum as ethereum; pub extern crate wagyu_model as model; extern crate pretty_env_logger;
作用是加载外部 crate,更详细的说明可见:
https://wiki.jikexueyuan.com/project/rust-primer/module/module.html
- 编写
main.rs
文件。
首先引用必要的外部模块:
use rand::{rngs::StdRng}; use rand_core::SeedableRng; use hello_crypto_rust::ethereum::{EthereumPrivateKey, EthereumFormat}; use hello_crypto_rust::model::{PrivateKey, PrivateKeyError, AddressError, PublicKeyError, PublicKey}; #[macro_use] extern crate log;
然后我们编写主函数:
fn main(){ pretty_env_logger::init(); // 初始化 pretty_env_logger 模块 new(); //调用new函数 }
写 new()
函数:
pub fn new() -> Result { let rng = &mut StdRng::from_entropy(); let private_key = EthereumPrivateKey::new(rng)?; info!("priv: {}", private_key.to_string()); let public_key = private_key.to_public_key(); info!("pub: {}", public_key.to_string()); let address = public_key.to_address(&EthereumFormat::Standard)?; info!("addr: {}", address.to_string()); Ok(private_key) }
我们这里使用了相对于 println!
更高级的输出方式,通过log输出。
这里有个关键的语法糖—— ?
,用于错误处理。
把 result 用 match 连接起来会显得很难看;幸运的是, ?
运算符可以把这种逻辑变得 干净漂亮。 ?
运算符用在返回值为 Result
的表达式后面,它等同于这样一个匹配 表达式:其中 Err(err)
分支展开成提前返回的 return Err(err)
,而 Ok(ok)
分支展开成 ok
表达式。
—— https://rustwiki.org/zh-CN/rust-by-example/std/result/question_mark.html
两个等价的函数,一个使用了 ?
,一个没有:
fn not_use_question_mark() { let a = 10; // 把这里改成 9 就会报错. let half = halves_if_even(a); let half = match half { Ok(item) => item, Err(e) => panic!(e), }; assert_eq!(half, 5); } fn use_question_mark() -> Result { // 这里必须要返回Result let a = 10; let half = halves_if_even(a)?; // 因为?要求其所在的函数必须要返回Result assert_eq!(half, 5); Ok(half) }
然后,我们定义一下枚举类型 CreateError
,里面会囊括 AddressError
、 PrivateKeyError
与 PublicKeyError
。
pub enum CreateError { AddressError(AddressError), PrivateKeyError(PrivateKeyError), PublicKeyError(PublicKeyError) } impl From for CreateError { fn from(error: AddressError) -> Self { CreateError::AddressError(error) } } impl From for CreateError { fn from(error: PrivateKeyError) -> Self { CreateError::PrivateKeyError(error) } } impl From for CreateError { fn from(error: PublicKeyError) -> Self { CreateError::PublicKeyError(error) } }
Try It!
实现成功:
本篇所涉及的知识点
lib.rs pretty_env_logger CreateError
本系列所有源码:
https://github.com/leeduckgo/RustStudy