【Rust】トランザクションにnftデータを追加する

UnsignedTransaction, SignedTransactionの構造体に、nft_dataと、nft_originを追加する。
nft_dataは、nftとして保存するデータ。
nft_originはnftを発行したトランザクションのhashデータ。
NFTを譲渡する場合は、nft_dataを空にして、nft_originに値を入れる。

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

nft_holderというhashmapを作成して、そこにnftを発行したトランザクションのhashデータと、最後にNTFを譲渡されたreceiverの連想配列を入れていく。
誰が何を持っているかは、トランザクションのhashデータをデコードして、nft_dataの値を取り出す。

#[tokio::main]
async fn main(){

    let mut transaction_pool: Vec<SignedTransaction> = Vec::new();
    let transaction1 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"hello world".to_string(),nft_origin:"".to_string()};
    let transaction2 = UnsignedTransaction {time:Utc::now().to_string(), sender:"DyH5SHvezz".to_string(), receiver:"655EFC80ss".to_string(), amount: 0, nft_data:"".to_string(),nft_origin:"eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyOTo0Ni42NzgzNDU3OTQgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9".to_string()};
    let transaction3 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"Milk Cafe".to_string(),nft_origin:"".to_string()};
    let transaction4 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 1000, nft_data:"".to_string(),nft_origin:"".to_string()};

    // println!("{}", BASE64_STANDARD.encode(serde_json::to_vec(&transaction1.clone()).unwrap()));
    // println!("{}", base64_decode(&str));

    transaction_pool.push(sign_transaction(&transaction1));
    transaction_pool.push(sign_transaction(&transaction2));
    transaction_pool.push(sign_transaction(&transaction3));
    transaction_pool.push(sign_transaction(&transaction4));

    let nft_holder:HashMap<String, String> = nft_calc(transaction_pool);
    for (k, v) in nft_holder {
        let transaction_str = base64_decode(&k);
        let transaction:UnsignedTransaction = serde_json::from_str(&transaction_str).unwrap();
        println!("保有者:{}, NFT:{}", v, transaction.nft_data);
    }
}

fn nft_calc(transaction_pool: Vec<SignedTransaction>) -> HashMap<String, String> {
    let mut nft_holder: HashMap<String, String> = HashMap::new();
    for transaction in transaction_pool {
        if transaction.amount == 0 {
            let transaction_hash: String = BASE64_STANDARD.encode(serde_json::to_vec(&transaction.clone()).unwrap());
            if transaction.nft_origin == "" && transaction.nft_data != "" && (nft_holder.get(&transaction_hash) == None) {
                nft_holder.insert(transaction_hash, transaction.receiver);
            } else if (nft_holder.get(&transaction_hash) == Some(&transaction.sender)) && transaction.nft_data == "" {
                nft_holder.insert(transaction.nft_origin, transaction.receiver);
            }
        }
    }
    return nft_holder
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.13s
Running `target/debug/sample`
保有者:DyH5SHvezz, NFT:hello world
保有者:DyH5SHvezz, NFT:Milk Cafe

これは中々凄いな…

【Rust】トランザクションをbase64でエンコード、デコード

hello worldで挙動を確認します。

let str:String = "hello world".to_string();
println!("{}", BASE64_STANDARD.encode(str.clone()));

let b:String = "aGVsbG8gd29ybGQ=".to_string();
println!("{}", base64_decode(&b));

pub fn base64_decode_bytes(b64str: &str) -> Vec {
let t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut table: [u8; 256] = [0; 256];
for (i, v) in t.as_bytes().iter().enumerate() {
table[*v as usize] = i as u8;
}
let b64 = String::from(b64str).replace("\r", "").replace("\n", "");
let b64bytes = b64.as_bytes();
let mut result: Vec = vec![];
let cnt = b64bytes.len() / 4;
for i in 0..cnt {
let i0 = b64bytes[i*4+0];
let i1 = b64bytes[i*4+1];
let i2 = b64bytes[i*4+2];
let i3 = b64bytes[i*4+3];
let c0 = table[i0 as usize] as usize;
let c1 = table[i1 as usize] as usize;
let c2 = table[i2 as usize] as usize;
let c3 = table[i3 as usize] as usize;
let b24 = (c0 << 18) | (c1 << 12) | (c2 << 6) | (c3 << 0); let b0 = ((b24 >> 16) & 0xFF) as u8;
let b1 = ((b24 >> 8) & 0xFF) as u8;
let b2 = ((b24 >> 0) & 0xFF) as u8;
result.push(b0);
if i2 as char != '=' { result.push(b1); }
if i3 as char != '=' { result.push(b2); }
}
result
}
pub fn base64_decode(b64str: &str) -> String {
String::from_utf8(base64_decode_bytes(b64str)).unwrap()
}
[/cpde]
aGVsbG8gd29ybGQ=
hello world

上手くいっているようです。
これをトランザクションでやります。

let transaction1 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"hello world".to_string(),nft_origin:"".to_string()};

println!("{}", BASE64_STANDARD.encode(serde_json::to_vec(&transaction1.clone()).unwrap()));

let str:String = "eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyNzo1MS4wNTA0MjYxMDMgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9".to_string();
println!("{}", base64_decode(&str));
[/cpde]

eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyOTo0Ni42NzgzNDU3OTQgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9
{"time":"2025-01-17 08:27:51.050426103 UTC","sender":"047683c00f6z","receiver":"DyH5SHvezz","amount":0,"nft_data":"hello world","nft_origin":""}
SignedTransaction { time: "2025-01-17 08:29:46.678345794 UTC", sender: "047683c00f6z", receiver: "DyH5SHvezz", amount: 0, nft_data: "hello world", nft_origin: "", signature: "568082531D765278ACF8999B94BAA7C621C538B00857DAC165208DB8C73FE4D4CB8FDBC742A4A47BC1D0B421A543134D869B2EC1AC8CEA0DDE902D39B034EA84" }

よし、O~K

【Rust】各ブロックチェーンの対BTCスワッピングレートを計算して表示

各コインのUSDレートをbitcoinのUSDレートで割れば、スワッピングレートが計算される。
f32のvectorはpartial_cmpで比較してソートする。

let mut coin_rate = get_price().await.unwrap();

    let objs: Vec<Rate> = serde_json::from_value(coin_rate).unwrap();
    let mut btcRate: f32 = 0.0;

    let mut crypt_objs: Vec<Rate> = Vec::new();
    for obj in objs {
        if obj.r#type == "crypto" {
            if obj.symbol == "BTC" {
                crypt_objs.insert(0, obj.clone());
                btcRate = obj.rateUsd.parse::<f32>().unwrap();
            } else {
                crypt_objs.push(obj);
            }   
        }
    }
    println!("{}", &btcRate);

    let mut data: Vec<RateSwap> = vec![];
    for crypt_obj in crypt_objs {
        let obj = RateSwap { id: crypt_obj.id, symbol: crypt_obj.symbol, rateUsd: crypt_obj.rateUsd.clone(), rateBtc: crypt_obj.rateUsd.parse::<f32>().unwrap() / &btcRate };
        data.push(obj);
    }
    data.sort_by(|a, b| a.rateBtc.partial_cmp(&b.rateBtc).unwrap().reverse());

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("data", &data);

おおお、割とやりたいことはできている気がする。
対btcではなく、コイン対コインでスワッピングする場合も同様のロジックで、交換する両方のコインのUSDレートで割ればスワッピングレートが計算できる。

Nice、次はaxumのログイン機能およびDB連携

PoS(Proof of Stake)と DPoS(Delegated Proof of Stake)、NPosの違いは

PoS
トークンの保有量に応じて承認権が与えられる仕組みです。暗号資産を多く保有する承認者に権利が集中しやすいという課題があります。

DPoS
PoSの発展系で、トークンの保有量に応じて投票権が割り当てられ、投票によって取引の承認を委任する仕組みです。保有量と委任された票の合計で承認者が選ばれるため、PoSと比べて民主主義的な仕組みといえます。また、取引承認に必要な承認数を減らすことができるため、高速なトランザクション処理を実現できます。

NPos
NPoSではValidatorとNominatorのStakeの合計が多い上位50〜1,000人がValidatorプールとして選ばれます。ValidatorとNominatorをValidator選別のスキームに取り入れることによって、ネットワーク全体のセキュリティを担保することが可能になります。この仕組みにより単体の大量DOT保持者へのシステムの依存を回避することができ、DOT保持者全員が参加できることにより悪意を持つユーザーがValidatorになることを困難にします(Nominatorに選ばれるには信頼を築く必要があるため)。

PoSだと、保有量が多い方が有利なアルゴリズムだが、Nominated(NPoS)の場合はランダムに選別するので、より公平性が保たれる仕組みですね。

【Rust】Proof of StakeをRustで書きたい

use std::collections::HashMap;
use chrono::{Utc, Local, DateTime, Date};
use sha2::Sha256;
use sha2::Digest;
use rand::Rng;
use std::io;
use std::io::Error;

#[derive(Debug)]
struct Block {
    index: u32,
    previous_hash: String,
    timestamp: DateTime<Utc>,
    data: Vec<String>,
    validator: String,
    hash: String,
}

#[derive(Debug)]
struct BlockChain {
    chain: Vec<Block>,
    unconfirmed_data: Vec<String>,
    validators: HashMap<String, i32>,
    staked_tokens: Vec<i32>,
}

impl BlockChain {

    const minimum_stake: i32 = 10;

    fn last_block(&self) -> &Block {
        return self.chain.last().unwrap();
    }

    fn add_data(&mut self, new_data:String) {
        self.unconfirmed_data.push(new_data);
    }

    fn add_validator(&mut self, validator: String, stake: i32) {
        if stake >= Self::minimum_stake {
            self.validators.insert(validator, stake);
            self.staked_tokens.push(stake);
        } else {
            println!("{} does not meet the minimum stake requirement.", validator);
        }
    }

    fn select_validator(self) -> String {
        let total_stake: i32 = self.staked_tokens.iter().sum();
        let mut selected_validator: String = "".to_string();
        let mut rnd = rand::thread_rng();
        while selected_validator == "".to_string() {
            let pick = rnd.gen_range(0..total_stake);
            let mut current: i32 = 0;
            for (validator, stake) in &self.validators {
                // println!("{} {}", validator, stake);
                current += stake;
                if current > pick{
                    selected_validator = validator.to_string();
                    break;
                }
            }
        }
        return selected_validator;
    }

    fn create_block(mut self, validator: String)-> Result<(), String> {
        if self.unconfirmed_data.is_empty() {
            return Err("No transaction".to_string());
        }
        let last_block = self.last_block(); 
        let new_block = Block {
            index: last_block.index + 1,
            previous_hash: last_block.hash.clone(),
            timestamp: Utc::now(),
            data : self.unconfirmed_data.clone(),
            validator: validator,
            hash: "".to_string() 
        };
        self.chain.push(new_block);
        self.unconfirmed_data.clear();
        Ok(())
    }

    fn display_chain(self) {
        println!("{:?}", self.chain);
    }

}

fn calculate_hash(data: String) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data);
    let hashed_sha256 = hasher.finalize();
    return hex::encode(hashed_sha256);
}

fn main() {
    let genesis_hash = calculate_hash("Genesis Block".to_string());
    let genesis_block = Block { index: 0, previous_hash: "".to_string(), timestamp: Utc::now(), data: vec!["Genesis Block".to_string()], validator: "".to_string(), hash: genesis_hash };

    let mut blockchain = BlockChain { chain: vec![genesis_block], unconfirmed_data: vec![], validators: HashMap::from([]), staked_tokens: vec![]};

    blockchain.add_validator("Alice".to_string(), 200);
    blockchain.add_validator("Bob".to_string(), 300);
    blockchain.add_validator("Chan".to_string(), 400);

    blockchain.add_data("James got 1000 token".to_string());
    blockchain.add_data("James sent 500 token to Jhon".to_string());

    let selected_validator = blockchain.select_validator();
    println!("Selected validator is {}", selected_validator);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/sample`
Selected validator is Bob

基本的にPythonで書くのとそこまで変わりませんね。

【Rust】APIでBTC, ETHのUSD価格を取得する

use reqwest::Client;
use serde::{Serialize, Deserialize};
use serde_json::{Value};

#[tokio::main]
async fn main() {

    let btc: String = "BTC".to_string();
    let btc_price: Value = get_price(btc).await.unwrap();
    println!("{}", btc_price);

    let eth: String = "ETH".to_string();
    let eth_price: Value = get_price(eth).await.unwrap();
    println!("{}", eth_price);
}

async fn get_price(code: String) -> Result<Value, Box<dyn std::error::Error>> {
    let mut url:String = "https://api.coinbase.com/v2/exchange-rates?currency=".to_string();
    url.push_str(&code);
    let contents = reqwest::get(url).await?.text().await?;
    let res: Value = serde_json::from_str(&contents).unwrap();
    println!("{:?}", res["data"]["rates"]["USD"]);
    Ok((res["data"]["rates"]["USD"]).clone())
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.97s
Running `target/debug/wallet`
String(“94359.295”)
“94359.295”
String(“3246.82”)
“3246.82”

処理速度が遅いが、取得はできる。
よし、さて、PoSでもやるか!

【Rust】p256でhexにencodeしたpublic keyをdecodeしてverify

hex::decode(&public_key_hex) でdecodeして、さらにfrom_sec1_bytesでpublic keyにします。

    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_hex = hex::encode(&public_key.to_sec1_bytes());
    println!("hex Public Key: \n{}", public_key_hex);

    let public_key_decode = hex::decode(&public_key_hex).unwrap();
    println!("decode Public Key: \n{:?}", &public_key_decode);

    let public_key = p256::PublicKey::from_sec1_bytes(&public_key_decode).expect("import error");

    println!("decode Public Key: \n{:?}", &public_key);

    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: Signature = signing_key.sign(message);

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

$ cargo run
// 省略
hex Public Key:
040b807b5e5fee8c91df858e09aa6dc085eae1b9b70783ce32ec68ad4994f9d16a991e277a1515a22e35d2d7935dec95946f31a2e3ea1e125a9067a177523063b0
decode Public Key:
[4, 11, 128, 123, 94, 95, 238, 140, 145, 223, 133, 142, 9, 170, 109, 192, 133, 234, 225, 185, 183, 7, 131, 206, 50, 236, 104, 173, 73, 148, 249, 209, 106, 153, 30, 39, 122, 21, 21, 162, 46, 53, 210, 215, 147, 93, 236, 149, 148, 111, 49, 162, 227, 234, 30, 18, 90, 144, 103, 161, 119, 82, 48, 99, 176]
decode Public Key:
PublicKey { point: AffinePoint { x: FieldElement(0x3B226D0B94EFABC8980328AB7FE8AE347D418871A770F1EFFE8A24FCAC9EE9DB), y: FieldElement(0x307BB7BAC4B807E9D59F65BEC20EC8AB54591BE07549140238927D01CA9152EF), infinity: 0 } }

おおお、このdecodeと署名の確認は本当に上手くいくのか再度トランザクションを実際に作ってテストする必要がありますね。

### signatureとpublic keyをPostされた側でのverify

    let signed_ut1 = SignedTransaction {time: unsignedtransaction.time, sender: unsignedtransaction.sender, receiver: unsignedtransaction.receiver, amount: unsignedtransaction.amount, signature: sig1.to_string()};
    println!("{:?}", signed_ut1);

    let public_key_decode = hex::decode(&signed_ut1.sender).unwrap();
    let public_key = p256::PublicKey::from_sec1_bytes(&public_key_decode).expect("import error");
    let verifying_key: VerifyingKey = public_key.into();

    let signature: Signature = signed_ut1.signature.parse::<Signature>().unwrap();

    let posted_transaction = UnsignedTransaction { time:signed_ut1.time, sender:signed_ut1.sender, receiver: signed_ut1.receiver, amount: signed_ut1.amount};
    let posted_serialized: String = serde_json::to_string(&posted_transaction).unwrap();

    assert!(verifying_key.verify(posted_serialized.as_bytes(), &signature).is_ok());

おおおおおおお、hexのpublic keyからちゃんと署名の検証ができてます。

【Rust】別のノードのブロックサイズが大きければ更新

自分が持っているblockのサイズと別のノードが持っているブロックサイズを比較して、相手の方が大きければ自分のブロックチェーンを更新する

    let path = "own_blocks.txt";
    let own_blocks = count_lines(path);
    println!("{}", own_blocks);

    let json_data = json();
    let deserialized = json!(**&json_data);
    let objs: Vec<Block> = serde_json::from_value(deserialized).unwrap();
    if objs.len() > own_blocks {
        let mut file = File::create("data/blocks.txt").expect("file not found.");
        for obj in &objs {
            let t = serde_json::to_string(&obj).unwrap();
            writeln!(file, "{}", t).expect("can not write.");
        }
    }
    println!("{}", objs.len());

OK、これを関数化して繋げる。

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

うーん….