生成 ETH 公私钥与地址 | Rust & Blockchain

用 Rust实现以太坊公私钥与地址的生成

本篇是 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,采用源码的方式跑一遍。 ```bash # Download the source code git clone https://github.com/AleoHQ/wagyu cd wagyu # Build in release mode $ cargo build --release ./target/release/wagyu ``` 成功: ![image-20210213091719643](https://img.learnblockchain.cn/2021/02/22_/104527698.jpg) 在这个过程里,我们学习到了 cargo 的更多用法: ```bash $ 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`平级的子模块进行调用。 其代码如下: ```rust 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`目录下,发现里面有个简单的函数: ```rust 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() }) } ``` 很好,就拿这个改造了! ## 复制必要文件到新项目 1. 新建项目 ```bash $ cargo new hello-crypto-rust ``` 或者直接把上一个项目复制一份。 2. 把`wagyu`的`Cargo.toml`中的必要内容复制过来 ```rust [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 ``` 3. 把`ethereum`与`model`两个文件夹复制到`hello-crypto-rust`目录下 此时的文件目录是这个样子的: ```bash . ├── Cargo.lock ├── Cargo.toml ├── ethereum ├── model ├── src └── target ``` ## 补充代码 1. 补充`lib.rs`文件 在`src`目录下新建`lib.rs`文件,内容: ```rust 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 2. 编写`main.rs`文件。 首先引用必要的外部模块: ```rust 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; ``` 然后我们编写主函数: ```rust fn main(){ pretty_env_logger::init(); // 初始化 pretty_env_logger 模块 new(); //调用new函数 } ``` 写`new()`函数: ```rust 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 两个等价的函数,一个使用了`?`,一个没有: ```rust 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`。 ```rust 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) } } ``` ## Try It! 实现成功: ![image-20210213095659766](https://img.learnblockchain.cn/2021/02/22_/320988596.jpg) ## 本篇所涉及的知识点 - cargo 的更多用法 - `lib.rs`的用法 - 函数与函数返回值 - `pretty_env_logger`的用法 - 枚举类型,以`CreateError`为例 **本系列所有源码:** https://github.com/leeduckgo/RustStudy --- ![slogan.jpeg](https://img.learnblockchain.cn/attachments/2020/05/2MLYUj8y5ec34769dcf2f.jpeg)

本篇是 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

wagyumain.rs中,会对cli目录下的子模块进行调用,进而对和cli平级的子模块进行调用。

其代码如下:

fn main() -> Result&lt;(), CLIError> {
    let arguments = App::new("wagyu")
        .version("v0.6.3")
        .about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash")
        .author("Aleo &lt;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&lt;R: Rng>(rng: &mut R) -> Result&lt;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()
        })
    }

很好,就拿这个改造了!

复制必要文件到新项目

  1. 新建项目
$ cargo new hello-crypto-rust

或者直接把上一个项目复制一份。

  1. wagyuCargo.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
  1. ethereummodel两个文件夹复制到hello-crypto-rust目录下

此时的文件目录是这个样子的:

.
├── Cargo.lock
├── Cargo.toml
├── ethereum
├── model
├── src
└── target

补充代码

  1. 补充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

  1. 编写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&lt;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&lt;'a >() -> Result&lt;i32, &'a str> {              // 这里必须要返回Result
    let a = 10;
    let half = halves_if_even(a)?;                     // 因为?要求其所在的函数必须要返回Result
    assert_eq!(half, 5);
    Ok(half)                                                                   
}

然后,我们定义一下枚举类型CreateError,里面会囊括AddressErrorPrivateKeyErrorPublicKeyError

pub enum CreateError {
    AddressError(AddressError),
    PrivateKeyError(PrivateKeyError),
    PublicKeyError(PublicKeyError)
}

impl From&lt;AddressError> for CreateError {
    fn from(error: AddressError) -> Self {
        CreateError::AddressError(error)
    }
}

impl From&lt;PrivateKeyError> for CreateError {
    fn from(error: PrivateKeyError) -> Self {
        CreateError::PrivateKeyError(error)
    }
}

impl From&lt;PublicKeyError> for CreateError {
    fn from(error: PublicKeyError) -> Self {
        CreateError::PublicKeyError(error)
    }
}

Try It!

实现成功:

本篇所涉及的知识点

  • cargo 的更多用法
  • lib.rs的用法
  • 函数与函数返回值
  • pretty_env_logger的用法
  • 枚举类型,以CreateError为例

本系列所有源码:

https://github.com/leeduckgo/RustStudy

区块链技术网。

  • 发表于 2021-02-16 18:10
  • 阅读 ( 2175 )
  • 学分 ( 9 )
  • 分类:Polkadot

评论