用 Rust实现以太坊公私钥与地址的生成
本篇是 Rust 学习笔记的第二篇。在第一篇里,我们魔改出了一个 Encoder,现在我们继续延续我们的魔改之路,挑战一个难度+1的Repo:
Rust library for generating cryptocurrency wallets
魔改目标 0x1:
抽取 Repo 中以太坊私钥、公钥、地址生成的部分,打印到控制台中。
但在魔改之前,笔者首先要对上一篇文章稍作补充,总结一下上篇文章中所涉及的知识点。
首先要验证这个库符合我们的需求,所以按照 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 下
首先喵一眼目录结构:
.
├── 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<(), CLIError> {
let arguments = App::new("wagyu")
.version("v0.6.3")
.about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash")
.author("Aleo <hello@aleo.org>")
.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<R: Rng>(rng: &mut R) -> Result<Self, CLIError> {
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<EthereumPrivateKey, CreateError> {
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<'a >() -> Result<i32, &'a str> { // 这里必须要返回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<AddressError> for CreateError {
fn from(error: AddressError) -> Self {
CreateError::AddressError(error)
}
}
impl From<PrivateKeyError> for CreateError {
fn from(error: PrivateKeyError) -> Self {
CreateError::PrivateKeyError(error)
}
}
impl From<PublicKeyError> for CreateError {
fn from(error: PublicKeyError) -> Self {
CreateError::PublicKeyError(error)
}
}
实现成功:
lib.rs
的用法pretty_env_logger
的用法CreateError
为例本系列所有源码:
https://github.com/leeduckgo/RustStudy
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!