7

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

 3 years ago
source link: https://learnblockchain.cn/article/2144
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用 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,采用源码的方式跑一遍。

# Download the source code
git clone https://github.com/AleoHQ/wagyu
cd wagyu

# Build in release mode
$ cargo build --release
./target/release/wagyu

成功:

JFfmEjF.jpg!mobile

在这个过程里,我们学习到了 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<(), CLIError> {
    let arguments = App::new("wagyu")
        .version("v0.6.3")
        .about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash")
        .author("Aleo <[email protected]>")
        .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()
        })
    }

很好,就拿这个改造了!

复制必要文件到新项目

  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<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 ,里面会囊括 AddressErrorPrivateKeyErrorPublicKeyError

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!

实现成功:

vIRzEne.jpg!mobile

本篇所涉及的知识点

lib.rs
pretty_env_logger
CreateError

本系列所有源码:

https://github.com/leeduckgo/RustStudy

RB3UJfu.jpg!mobile

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

成功:

JFfmEjF.jpg!mobile

在这个过程里,我们学习到了 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<(), CLIError> {
    let arguments = App::new("wagyu")
        .version("v0.6.3")
        .about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash")
        .author("Aleo <[email protected]>")
        .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()
        })
    }

很好,就拿这个改造了!

复制必要文件到新项目

  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<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 ,里面会囊括 AddressErrorPrivateKeyErrorPublicKeyError

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!

实现成功:

vIRzEne.jpg!mobile

本篇所涉及的知识点

lib.rs
pretty_env_logger
CreateError

本系列所有源码:

https://github.com/leeduckgo/RustStudy

RB3UJfu.jpg!mobile

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 5分钟前
  • 阅读 ( 3 )
  • 学分 ( 0 )
  • 分类:Polkadot

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK