【Blockchain】minerがPoWした後のサーバ側の処理を考える

/chain
リクエストに対し、サーバが保持しているブロックチェーンを返すパス・変数

関数 chain_verify
マイニングを完了したマイナーから送られてきたブロックチェーンの中身をチェックして、問題なければブロックチェーンを追記する。
【処理内容】
1. first blockが改竄されていないか
2. Previous blockのハッシュ値を使用し、PoWによりハッシュ値が正しく計算されているか
3. 報酬用のトランザクションを作成する(報酬額はstaticで定義する、マイナーのアドレスが必要)
4. 生成したブロックを前のブロックに繋げて保存する

– 3.でマイナーへ報酬用のトランザクションを作成する際に、マイナーのアドレスと、大元のアドレスが必要になる。
– 本来であれば、上記1~4に加えて、merkle treeの検証も必要
– ブロックチェーンにつながっているブロックのすべてのハッシュ値を計算する場合は、計算処理が多くなりそう

【Rust】Rustでマークルツリーを作りたい

use sha2::{Digest, Sha256};

#[derive(Debug, Clone)]
enum Position {
    Right,
    Left,
}

#[derive(Debug, Clone)]
struct Node {
    left: String,
    right: String,
    parent: String,
    sibling: String,
    position: Position,
    data: String,
}

fn hash(data: String) -> String {
    let h = Sha256::digest(data.clone());
    hex::encode(h).to_string()
}

fn sha256hash(right_data: String, left_data: String) -> String {
        let s = format!("{}{}", right_data, left_data);
        let h = Sha256::digest(s);
        hex::encode(h).to_string()
}

#[derive(Debug, Clone)]
struct Tree {
    leaves: Vec<Node>,
    layer: Vec<Node>,
    root: String,
}

impl Tree {
    fn build_layer(&mut self) {
        let mut new_layer: Vec<Node> = Vec::new();

        if self.layer.len() % 2 == 1 {
            self.layer.push(self.layer[self.layer.len() - 1].clone());
        }

        for i in (0..self.layer.len()).step_by(2) {
            let parent_data = sha256hash(self.layer[i].data.clone(), self.layer[i+1].data.clone());
            let left = Node {left: "".to_string(), right: "".to_string(), parent: parent_data.clone(), sibling: self.layer[i+1].data.clone(), position: Position::Left, data: self.layer[i].data.clone()};
            let right = Node {left: "".to_string(), right: "".to_string(), parent: parent_data.clone(), sibling: self.layer[i].data.clone(), position: Position::Right, data: self.layer[i+1].data.clone()};
            let parent = Node {left: self.layer[i].data.clone(), right: self.layer[i+1].data.clone(), parent: "".to_string(), sibling: self.layer[i].data.clone(), position: Position::Left, data: parent_data.clone()};
            new_layer.push(parent.clone());
            self.leaves.push(left.clone());
            self.leaves.push(right.clone());
            self.leaves.push(parent.clone());
        }
        self.layer = new_layer;
    }

    fn build_tree(&mut self) {
        while self.layer.len() > 1 {
            let _ = self.build_layer();
        }
        self.root = self.layer[0].data.clone();
    }

    fn search(&mut self, data: String) -> Node {
        let hash_value = data.clone();
        let mut target = Node {left: "".to_string(), right: "".to_string(), parent: "".to_string(), sibling: "".to_string(), position: Position::Left, data: "".to_string()};
        for node in &self.leaves {
            if node.data == hash_value {
                let target: Node = node.clone();
            } 
        }
        return target
    }

    fn get_pass(&mut self, data: String) -> Vec<String>{
        let mut target = self.search(hash(data.clone()));
        let mut markle_pass: Vec<String> = Vec::new();
        markle_pass.push(target.data);
        while target.parent != "" {
            markle_pass.push(target.sibling);
            target = self.search(target.parent);
        }       
        return markle_pass
    }
}

fn merkle_root(v: Vec<&str>) -> String {
    let mut t = Tree {leaves: [].to_vec(), layer: [].to_vec(), root: "".to_string()};
    for n in v {
        let mut s =  Node {left: "".to_string(), right: "".to_string(), parent: "".to_string(), sibling: "".to_string(), position: Position::Left, data: hash(n.to_string())};
        t.layer.push(s.clone());
    }
    t.build_tree();
    t.root
}

fn main(){
    let v= vec!["aaa", "bbb", "ccc", "ddd", "eee", "fff", "gggg"];
    let m = merkle_root(v);
    println!("{}", m);
}

うーん….

【Rust】writeln!で後ろに改行が入った時に削除

writeln!でpemファイルを作成します。

env::set_var("RUST_BACKTRACE", "1");
    let secret_key = SigningKey::random(&mut OsRng);
    let secret_key_serialized = secret_key
        .to_pkcs8_pem(Default::default())
        .unwrap()
        .to_string();
    println!("Secret Key: \n{}", secret_key_serialized);

    let mut file = File::create("secret.pem").expect("file not found.");

    writeln!(file, "{}", secret_key_serialized).expect("can not write.");

すると、最後尾に2行改行が入ってしまいます。

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgO7s4/UmqJ5+UWIIU
oL4XIxslh+htWtvTY7wZPp+usEKhRANCAAS/iV6WinhhKw8M/tkGNNwf2W+Vt+cd
d0hLjWQ9iZhjP7NxlPLoUjWERctvft3zPOktCedW5rzLIhVtj7rX2F4j
-----END PRIVATE KEY-----


これをstd::fs::read_to_stringで読み取ってパースしても、改行が入っているためエラーになってしまいます。
改行を指定しての文字列削除や文字列の置き換え(s.replace)だとうまくいかないのですが、
trim()だと、いい具合に最後2行の改行を削除してくれます。

    let mut file = File::create("secret.pem").expect("file not found.");

    writeln!(file, "{}", secret_key_serialized).expect("can not write.");

    let contents = std::fs::read_to_string("secret.pem")
        .expect("something went wrong reading the file");
    let secret_pem = contents.trim();
    println!("{}", secret_pem);

Secret Key:
—–BEGIN PRIVATE KEY—–
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNMDcEubwpsVrs/TJ
YgiL/HFyb3hvOgF/QI3AwXWBFKmhRANCAARyuwFIbkPm5Q1zbd6DZbNMG35s7NmU
6QduJGRjofpwCrVaOorsjZASpG546WgoTof9eONpXYY92NY5hCvhPJrU
—–END PRIVATE KEY—–

—–BEGIN PRIVATE KEY—–
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNMDcEubwpsVrs/TJ
YgiL/HFyb3hvOgF/QI3AwXWBFKmhRANCAARyuwFIbkPm5Q1zbd6DZbNMG35s7NmU
6QduJGRjofpwCrVaOorsjZASpG546WgoTof9eONpXYY92NY5hCvhPJrU
—–END PRIVATE KEY—–

素晴らしいですね。これ解決するのに半日かかりました。

【Rust】p256(ecdsa)でpemファイルを扱う

use p256::{
    ecdsa::{
        signature::{Signer, Verifier},
        SigningKey, VerifyingKey,
    },
    pkcs8::EncodePrivateKey,
    PublicKey, SecretKey,
};
use rand_core::OsRng;
use std::fs::OpenOptions;
use std::io::Write;

fn main(){
    let secret_key = SigningKey::random(&mut OsRng);
    let secret_key_serialized = secret_key
        .to_pkcs8_pem(Default::default())
        .unwrap()
        .to_string();
    println!("Secret Key: \n{}", secret_key_serialized);
    let secret_key = secret_key_serialized.parse::<SecretKey>().unwrap();

    let public_key = secret_key.public_key();
    let public_key_serialized = public_key.to_string();
    println!("Public Key: \n{}", public_key_serialized);
    let public_key = public_key_serialized.parse::<PublicKey>().unwrap();

    let signing_key: SigningKey = secret_key.into();
    let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
    let signature = signing_key.sign(message);

    let verifying_key: VerifyingKey = public_key.into();
    assert!(verifying_key.verify(message, &signature).is_ok());
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.89s
Running `target/debug/sample`
Secret Key:
—–BEGIN PRIVATE KEY—–
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqmVTGsuHMDgaN3TG
Jvsem2P1dA3l/wnRsxPfN8PTnUqhRANCAASv58iralPg2mOjuf28sSC8UuxqR4kD
u9tATiYXDvaU6BfmuI0tl0JsrZ2Brf1BkXtzbwBbiM2+h6+I6J55TU2p
—–END PRIVATE KEY—–

Public Key:
—–BEGIN PUBLIC KEY—–
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEr+fIq2pT4Npjo7n9vLEgvFLsakeJ
A7vbQE4mFw72lOgX5riNLZdCbK2dga39QZF7c28AW4jNvoeviOieeU1NqQ==
—–END PUBLIC KEY—–

p256とk256は微妙に違うのね。。これをpemファイルとして保存したい。

【Shell】ファイルを生成

ジェネシスブロックを生成するシェルを作ります。

#!/bin/bash

echo "{\"time\":\"0000-00-00 00:00:00.000000000 UTC\",\"transactions\":[],\"hash\":\"genesisblockhash\",\"nonce\":\"0\"}" > data/blocks.txt

$ ./setup.sh

// blockを生成

data/blocks.txt

{"time":"0000-00-00 00:00:00.000000000 UTC","transactions":[],"hash":"genesisblockhash","nonce":"0"}
{"time":"2025-01-05 01:43:43.528815824 UTC","transactions":[{"time":"2024-12-25 22:53:36.824066840 UTC","sender":"5bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991123","receiver":"4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124","amount":10,"signature":"8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98"},{"time":"2024-12-25 22:53:36.824066840 UTC","sender":"4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991123","receiver":"4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124","amount":10,"signature":"8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98"}],"hash":"00006059ac7bd5ea2ece1428c90c402c602cf402b0483e7063dfa4f3e58c7ae4","nonce":"215835"}

ちゃんと改行されていますね。echoのテキストの最後に\nを入れなくても大丈夫でした。
genesis blockとblockの生成までできました。
続いて、verify chainの関数を作って、ブロックが正しく生成されているか確認した上で、minerへの報酬トランザクションを生成する関数を作りたい。

【Rust】PoWをしてBlockを生成しよう

fn main(){
    let previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01";
    let mut num = rand::thread_rng().gen_range(0..1000000);
    let mut hash_num = format!("{}{}", previous_hash, num.to_string());
    let mut header = Sha256::digest(hash_num);
    let mut target: String  = (&hex::encode(header)[..4]).to_string();
    
    let mut cnt = 1;
    println!("count: {} {:x}", cnt, header);

    while target != "0000" {
        println!("count: {} {:x}", cnt, header);
        num = rand::thread_rng().gen_range(0..1000000);
        hash_num = format!("{}{}", previous_hash, num.to_string());
        header = Sha256::digest(hash_num);
        target = (&hex::encode(header)[..4]).to_string();
        cnt += 1;
    }
    println!("count: {} {:x}", cnt, header);

    let t = vec!["sender".to_string(), "receiver".to_string(), "amount".to_string()];
    let utc_datetime: DateTime<Utc> = Utc::now();
    let b = Block{time:utc_datetime.to_string(), transactions: t, hash: hex::encode(header).to_string(), nonce:num.to_string()};
    println!("{:?}", b);
}

$ cargo run

Block { time: “2025-01-04 00:22:15.386703002 UTC”, transactions: [“sender”, “receiver”, “amount”], hash: “0000cef5d82d7d11f5e90aab7439a128e5877ee6e6b8da31301a6359d74b5503”, nonce: “494683” }

なるほど、こういう仕組みなのか!
うん、悪くないかも
これで、トランザクションの箇所を、Postされてきたデータの構造体に置き換えると…

#[derive(Debug, Serialize, Clone, Deserialize)]
struct Block {
    time: String,
    transactions: Vec<SignedTransaction>,
    hash: String,
    nonce: String,
}

$ cargo run

Block { time: “2025-01-04 00:31:53.015599503 UTC”, transactions: [SignedTransaction { time: “2024-12-25 22:53:36.824066840 UTC”, sender: “5bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991123”, receiver: “4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124”, amount: 10, signature: “8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98” }, SignedTransaction { time: “2024-12-25 22:53:36.824066840 UTC”, sender: “4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991123”, receiver: “4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124”, amount: 10, signature: “8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98” }], hash: “0000f5b116872033b276499b3e039b03fc4753aaa4e400f2cce856c358d2d51f”, nonce: “4961” }
blockを作成しました。

SignedTransactionがvectorとしてblockの中に入ります。
おおおおおおおおおおおおおお
基本的な形はできたやん
次の課題として、
– blockをjsonデータとして保存する
– genesis blockと生成したblockを繋げていく処理を検討する

【Rust】axumでformの送信とトランザクションのPostを実装したい

templates/withdrawal.html

<form action="/sent" method="post" class="">
            <input type="hidden" name="time" id="time" value="">
            <input type="hidden" name="sender" value="{{address}}">
            <div class="mb-3">
                <label for="receiver" class="form-label">送付先アドレス</label>
                <input type="text" class="form-control" id="receiver" name="receiver" placeholder="送付先のアドレスを入力してください">
              </div>
              <div class="mb-3">
                <label for="amount" class="form-label">送付コイン量</label>
                <input type="text" class="form-control" id="amount" name="amount" placeholder="数量を半角数字で入力してください。e.g. 1000">
              </div>
              <input type="submit" value="送信" class="btn btn-primary"/>
        </form>
#[derive(Serialize, Deserialize, Debug)]
struct UnsignedTransaction {
    time: String,
    sender: String,
    receiver: String,
    amount: i32,
}
// 
async fn handle_sent(axum::Form(unsignedtransaction): axum::Form<UnsignedTransaction>)
    -> axum::response::Html<String> {

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("time", &unsignedtransaction.time);
    context.insert("receiver", &unsignedtransaction.receiver);
    context.insert("amount", &unsignedtransaction.amount);

    let output = tera.render("sent.html", &context);
    axum::response::Html(output.unwrap())
}

formのPOSTはできました。
このデータを受け取ったタイミングで、外部のIP(node)に合わせてPOSTしたい。

【Rust】秘密鍵・公開鍵・アドレスの作成をaxumを使ってWebで表現

templates/account.html

   <body class="container">
        <h1 class="display-6 my-2">Crypt Wallet</h1>
        <hr>
        <nav aria-label="breadcrumb">
            <ol class="breadcrumb">
              <li class="breadcrumb-item"><a href="/">Home</a></li>
              <li class="breadcrumb-item"><a href="/account">アカウント情報</a></li>
            </ol>
          </nav>
        <div class="alert alert-primary">
            <p class="my-2">秘密鍵、公開鍵、アドレスを生成しました。</p>
        </div>
        <dl class="row">
            <dt class="col-sm-3">秘密鍵</dt>
            <dd class="col-sm-9">{{private_key}}</dd>
          
            <dt class="col-sm-3">公開鍵</dt>
            <dd class="col-sm-9">{{public_key}}</dd>
          
            <dt class="col-sm-3">アドレス</dt>
            <dd class="col-sm-9">{{address}}</dd>
          
          </dl>
        <br><br>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    </body>
async fn handle_account()-> axum::response::Html<String> {

    let signing_key = SigningKey::random(&mut OsRng);
    let private_key = hex::encode(signing_key.to_bytes());
    let verifying_key = signing_key.verifying_key();
    let public_key = hex::encode(verifying_key.to_sec1_bytes());
    let address = new_address(&verifying_key);    

    let tera = tera::Tera::new("templates/*").unwrap();
    let mut context = tera::Context::new();
    context.insert("private_key", &private_key);
    context.insert("public_key", &public_key);
    context.insert("address", &address);

    let output = tera.render("account.html", &context);
    axum::response::Html(output.unwrap())
}

リファクタリングが必要だけど、やりたいことは大体できている^^

【Rust】k256の公開鍵からアドレスを作成する

use k256::{ecdsa::{SigningKey, Signature, signature::Signer, signature::Verifier, VerifyingKey}};
use rand_core::OsRng;
use sha2::{Digest, Sha256};
use ripemd::{Ripemd160};

fn new_keys() {
    let signing_key = SigningKey::random(&mut OsRng);
    let private_key = hex::encode(signing_key.to_bytes());
    println!("private key: {:x?}", private_key);
    let verifying_key = signing_key.verifying_key();
    let public_key = hex::encode(verifying_key.to_sec1_bytes());
    println!("public key: {:x?}", public_key);

    new_address(&verifying_key);    
}   

fn new_address(verifying_key: &VerifyingKey) {

    let vk = verifying_key.to_sec1_bytes();

    let mut hasher = Sha256::new();
    hasher.update(vk);
    let hashed_sha256 = hasher.finalize();

    let mut hasher = Ripemd160::new();
    hasher.update(hashed_sha256);
    let account_id = hasher.finalize();

    let mut payload = account_id.to_vec();
    payload.insert(0, 0x00);

    let mut hasher = Sha256::new();
    hasher.update(&payload);
    let hash = hasher.finalize();

    let mut hasher = Sha256::new();
    hasher.update(hash);
    let checksum = hasher.finalize();

    payload.append(&mut checksum[0..4].to_vec());

    const ALPHABET: &str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    let address = base_x::encode(ALPHABET, &payload);

    println!("address: {:?}", address);
}


fn main() {
    new_keys();
}

$ cargo run

private key: “745aa1c916085b2e3423dc7a22792945bae57c38bd5bc1bb0426ba2156f8a39c”
public key: “029e1baf2992b44af147c306fb728f8b00e908aa7f09e25eaa0a2fed3f71ad4cf6”
address: “1E5b59jN4nyM9kpzqdXfW7MkLJ2CApAVjT”

これをWebフレームワークで表現したい。使うのはaxumかな。。。

【Blockchain】ウォレット機能の考察

### ウォレットとして必要な機能
– 秘密鍵、公開鍵の作成、保存
– ブロックチェーン残高の表示
– トランザクションの送信、受信
– ブロックチェーン価格のマーケットデータ表示
※ステーキング、スワッピング、dAppsなど応用機能もある

### 前準備
$ pip install flask
$ pip install web3
$ pip install requests

from aiohttp import request
from flask import Flask, jsonify, render_template, session
from web3 import Web3
import requests
import jsonify

app = Flask(__name__)

infura_url = 'https://mainnet.infura.io/v3/fuga'
app.config['SECRET_KEY'] = 'hoge'

@app.route('/new_account', methods=['GET'])
def new_account():
    account = web3.eth.account.create('hogehoge')
    session['account'] = {
        'privateKey': account.key.hex(),
        'address' : account.address
    }
    return jsonify(session['account'])
with open('erc20_abi.json') as f:
    erc20_abi = json.load(f)

@app.route('/balance/<contract_address>', methods=['GET'])
def get_balance(contract_address):
    address = session.get('account').get('address')
    checksum_address = Web3.to_checksum_address(address)
    print(checksum_address)
    contract = web3.eth.contract(address=contract_address, abi=erc20_abi)
    balance = contract.functions.balanceOf(checksum_address).call()
    return jsonify({'balance': balance})

@app.route('/send_transaction', methods=['POST'])
def send_transaction():
    data = request.get_json()
    nonce = web3.eth.getTransactionCount(session['account']['address'])
    txn_dict = {
        'to': data['to'],
        'value': web3.toWei(data['amount'], 'either'),
        'gas': 2000000,
        'gasPrice': web3.toWei('40', 'gwei'),
        'nonce': nonce,
        'chainId': 3
    }
    signed_txn = web3.eth.account.signTransaction(txn_dict, session['account']['privateKey'])
    txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction)
    return jsonify({'transaction_hash': txn_hash.hex()})

@app.route('/market_chart/<contract_address>/<days>', methods=['GET'])
def get_market_chart(contract_address, days):
    api_key = 'coingecho_api_key'
    response = requests.get(f'https://api.coingecko.com/api/v3/coins/ethereum/contract/{contract_address}/market_chart?vs_currency=usd&days={days}&api_key={api_key}')
    market_chart = response.json(f'https://api.coingecko.com/api/v3/coins/ethereum/contract/{contract_address}/market_chart?vs_currency=usd&days={days}&api_key={api_key}')
    market_chart = response.json()
    return jsonify(market_chart)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

なるほど、walletのエッセンスは理解できました。Pythonでは”web3″のライブラリでかなり抽象化されているのがわかります。
balanceのところはUTXOにするか、Account型にするかで変わってきますね。
マーケットデータは一旦スタック。
テストネット、メインネットの概念はもう少し深掘りする