【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型にするかで変わってきますね。
マーケットデータは一旦スタック。
テストネット、メインネットの概念はもう少し深掘りする

【Rust】RustでbitcoinのProof of work(PoW)を実装したい

まず、previous hashとnonceを足して、sha256で暗号化します。

use sha2::{Digest, Sha256};
use rand::Rng;

fn main() {
    let previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01";
    let num = rand::thread_rng().gen_range(0..1000000);
    let hash_num = format!("{}{}", previous_hash, num.to_string());
    let header = Sha256::digest(hash_num);
    println!("{:x}", header);
}

$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/crypt`
07b9391fce0c6299097b36a06392a6a6b6245ee59ca816de22aac6dbaf7419af

うむ、やりたいことはできている。ここから、hash値先頭が0000… のheaderになるようなnonceの値を探す計算処理を行う

use sha2::{Digest, Sha256};
use rand::Rng;

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);
}

$ cargo run

count: 37455 4b1d6582d1fed66a34041346d0f43cc7b6c2a803588da8c5d515a813c2dcff7a
count: 37456 34b478ac26947b93e63a747be64a06b904fe98953a8db9d4d3f773fdadf5abba
count: 37457 0000bdebe741af3994f4a2160b4480a23ca137aaf0ac51b10fe574f04afc7be4

凄い簡単なコードなんだけど、これ作るのに結構時間かかった…

あとは計算時間を計測してDifficultyの調整機能を作りたい

use sha2::{Digest, Sha256};
use rand::Rng;
use std::{thread, time};

fn main() {
    let now = time::Instant::now();
    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);
    println!("{:?}", now.elapsed());
}

$ cargo run

count: 70736 000074213e839089c9bd8e446dd5835d537cd7037cdf193bf9881df44d2a55b4
1.818378391s

これをmainに取り込む。powはminingとして別ファイルにして、ブロック作成時に読み込むようにする。

mod mining;

fn make_block (){
    mining::proof_of_work();
    println!("blockを作成しました。");
    Pool.lock().unwrap().clear();
}

$ cargo run

count: 16990 0000ea27e22db290e4f2163f968bfaf3ff7d58ccf1cd4ab43b3fbc4326c0eb4a
428.967757ms
blockを作成しました。
8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98

### 課題
– トランザクションプールが出来た後のblock作成のロジックを詰める必要がある。(merkletree, serialize等)
– genesis block, minerへのコイン分配なども考える必要あり。
– トランザクションスピードを上げるために並列処理を導入する場合、どこを並列処理にするのか考える必要あり。

【Python】pythonでProof of work

from random import randint
from hashlib import sha256

previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01"

cnt = 1

nonce = str(randint(0, 1000000))

header = sha256(f'{previous_hash}{nonce}'.encode()).hexdigest()

print(header)

while header[:4] != "0000":
    text = 'loop:{}, header:{}, header[:4]:{}, nonce:{}\n'
    print(text.format(cnt, header, header[:4], nonce))

    nonce = str(randint(0, 1000000))
    header = sha256(f'{previous_hash}{nonce}'.encode()).hexdigest()
    cnt += 1

text = 'loop:{}, header:{}, header[:4]:{}, nonce:{}\n'
print(text.format(cnt, header, header[:4], nonce))

$ python3 test.py

loop:17576, header:0000ea27e22db290e4f2163f968bfaf3ff7d58ccf1cd4ab43b3fbc4326c0eb4a, header[:4]:0000, nonce:8978

ほう、これをRustで書きたい & 処理時間に応じたdifficulty(0の個数)の調整機能も追加したい。

【Rust】sha256を使用する

[dependencies]
sha2 = "0.10.8"
use sha2::{Digest, Sha256};

fn main() {
    let data = b"hello world";
    let hash = Sha256::digest(data);
    println!("{:x}", hash);

    let data2 = b"hello world";
    let hash2 = Sha256::digest(data2);
    println!("{:x}", hash2);
}

Running `target/debug/crypt`
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

うーむ、そのまんまな感じがしますね…

【Rust】Rustでトランザクションを送信する

use serde::{Serialize, Deserialize};
use std::io::prelude::*;
use hex_literal::hex;
use k256::{ecdsa::{SigningKey, Signature, signature::Signer, signature::Verifier, VerifyingKey}};
use chrono::{Utc, Local, DateTime, Date};

#[derive(Serialize, Deserialize, Debug)]
struct UnsignedTransaction {
    time: String,
    sender: String,
    receiver: String,
    amount: i32,
}

#[derive(Serialize, Deserialize, Debug)]
struct SignedTransaction {
    time: String,
    sender: String,
    receiver: String,
    amount: i32,
    signature: String,
}

fn hex(bytes: &[u8]) -> String {
    bytes.iter().fold("".to_owned(), |s, b| format!("{}{:x}", s, b))
}

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {

    let private_key: SigningKey = SigningKey::from_bytes(&hex!(
        "DCDFF4B7CA287CC7BD30ECAEF0622265DB4E14054E12954225457C3A6B84F135"
    ).into()).unwrap();
    let public_key: &VerifyingKey = private_key.verifying_key();
    let public_key_str = hex(&public_key.to_encoded_point(false).to_bytes());
    let public_key_b_str = "4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124";

    let utc_datetime: DateTime<Utc> = Utc::now();
    let ut1 = UnsignedTransaction {time: utc_datetime.to_string(), sender: public_key_str.to_string(), receiver: public_key_b_str.to_string(), amount: 10};
    println!("{:?}", ut1);
    let serialized: String = serde_json::to_string(&ut1).unwrap();
    let sig1: Signature = private_key.sign(serialized.as_bytes());
    let signed_ut1 = SignedTransaction {time: utc_datetime.to_string(), sender: public_key_str.to_string(), receiver: public_key_b_str.to_string(), amount: 10, signature: sig1.to_string()};
 
    let uri = "https://httpbin.org/post";
    let mut res = surf::post(uri).body_json(&signed_ut1)?.await?;
    let body = res.body_string().await?;
    println!("{}", body);
    Ok(())
}

“files”: {},
“form”: {},
“headers”: {
“Accept”: “*/*”,
“Accept-Encoding”: “deflate, gzip”,
“Content-Length”: “471”,
“Content-Type”: “application/json”,
“Host”: “httpbin.org”,
“User-Agent”: “curl/8.11.0-DEV isahc/0.7.6”,
“X-Amzn-Trace-Id”: “Root=1-676c8cf1-39979cc169871725084b307e”
},
“json”: {
“amount”: 10,
“receiver”: “4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991124”,
“sender”: “4bac6cb0f4ad6397752c3d73b88c5c86e3d88ac695118494a1732e2abd16c76acad3d6586c37c8db7e69c2f812f99275198936957d72c38d71981991123”,
“signature”: “8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98”,
“time”: “2024-12-25 22:53:36.824066840 UTC”
},
“origin”: “hoge”,
“url”: “https://httpbin.org/post”
}

なるほど、送信側は相手のURL(IP)がわかっていれば、POSTするだけなので問題なく出来ますね。

ubuntuにcore lightningをインストール

$ cat /etc/os-release
PRETTY_NAME=”Ubuntu 24.04 LTS”
NAME=”Ubuntu”
VERSION_ID=”24.04″
//
$ sudo apt-get update
$ sudo apt install snapd
$ sudo snap install hello-world
$ hello-world
Hello World!
$ sudo snap install bitcoin-core
bitcoin-core 27.1 from Bitcoin Core installed

core-lightningに必要なライブラリをインストールする
$ sudo apt-get install –no-install-recommends –allow-unauthenticated python3 git make automake autoconf libtool build-essential libprotobuf-c-dev libsodium-dev libsqlite3-dev libgmp-dev libsqlite3-dev git net-tools valgrind curl ca-certificates jq
$ sudo apt install python3-pip

ソースコードをDL
$ git clone https://github.com/ElementsProject/lightning.git lightningd
$ cd lightningd && ls
CHANGELOG.md bitcoin configure flake.nix poetry.lock
Cargo.lock ccan conftest.py gossipd pyproject.toml
Cargo.toml ccan_compat.h connectd hsmd tests
Dockerfile channeld contrib lightningd tools
LICENSE cli db mkdocs.yml wallet
Makefile cln-grpc devtools nix wire
README.md cln-rpc doc onchaind
SECURITY.md closingd external openingd
action.yml common flake.lock plugins
$ ./configure
$ make

>> makoがないとエラーになるので、makoをインストールする
$ sudo apt-get install python3-mako
$ make

>> /bin/sh: 2: xgettext: not found
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install gettext libgettextpo-dev
$ make

>> No module named ‘grpc_tools’
$ sudo apt-get install python3-grpc-tools
$ make

>> Missing value for flag: –experimental_allow_proto3_optional
うーん、 解決できん…

というか、core lightningってcliベースでfundを開いたり、paymentを実行したりするのね…

Scrypt proof of workのScriptとは?

https://cryptobook.nakov.com/mac-and-key-derivation/scrypt

Scrypt (RFC 7914) is a strong cryptographic key-derivation function (KDF). It is memory-intensive, designed to prevent GPU, ASIC and FPGA attacks (highly efficient password cracking hardware).

key = Scrypt(password, salt, N, r, p, derived-key-len)

### Script Parameters
N – iterations count (affects memory and CPU usage), e.g. 16384 or 2048
r – block size (affects memory and CPU usage), e.g. 8
p – parallelism factor (threads to run in parallel – affects the memory, CPU usage), usually 1
password– the input password (8-10 chars minimal length is recommended)
salt – securely-generated random bytes (64 bits minimum, 128 bits recommended)
derived-key-length – how many bytes to generate as output, e.g. 32 bytes (256 bits)

The memory in Scrypt is accessed in strongly dependent order at each step, so the memory access speed is the algorithm’s bottleneck. The memory required to compute Scrypt key derivation is calculated as follows:

Memory required = 128 * N * r * p bytes

Choosing parameters depends on how much you want to wait and what level of security (password cracking resistance) do you want to achieve:

Script hash generator
https://8gwifi.org/scrypt.jsp

$ sudo apt-get install python-dev-is-python3
$ pip3 install scrypt

import pyscript

salt = b'aa1f2d3f4d23ac44e9c5a6c3d8f9ee8c'
passwd = b'p@$Sw0rD~7'
key = pyscript.hash(passwd, salt, 2048, 8, 1, 32)
print("Derived key:", key.hex())

Litecoin, Dogecoinとは

### Litecoin
2011年にリリースされた通貨
scryptをproof of workのアルゴリズムとして使用している

– ブロック生成時間 2.5分
– 通貨総発行量: 2140年までに8400万litecoin
– コンセンサスアルゴリズム: Scrypt proof of work
– 開発者はチャーリーリー
https://github.com/litecoin-project/litecoin

Scrypt proof of workは基本的なハッシュ関数としてscryptを使用しているHashcash証明の証明

### Dogecoin
2013年12月にリリースされたもので、Litecoinのフォークに基づくもの。
支払いやチップの利用を促すもので、通貨発行のスピードを速くしている
– ブロック生成時間 60分
– 通貨総発行量: 2015年までに1000億doge
– コンセンサスアルゴリズム: Scrypt proof of work
– Billy MarkusとJackson Palmerが開発者
https://github.com/dogecoin/dogecoin