はじめに
以下の記事を元に超シンプルなブロックチェーンを実装します。
https://blog.logrocket.com/how-to-build-a-blockchain-in-rust/
実装は元記事同様Rustですが、細かいところがわからなくても読めるように書いたので、最後まで読んでいただけると嬉しいです。
実装
まず、データが格納されるブロックとブロックをつなげたチェーンの2つの構造を定義します。
pub struct Blockchain {
pub chain: Vec<Block>,
}
#[derive(Debug, Clone)]
pub struct Block {
pub index: u64,
pub hash: String,
pub previous_hash: String,
pub timestamp: u128,
pub data: String,
}
BlockchainはBlockを単純につなげるベクトルとします。
Blockの各フィールドは以下のように利用します。
-
index
: Blockの番号。今回は0, 1, 2, ...と連番で利用 -
hash
: Block全体をsha256でハッシュした値 -
previous_hash
: 1つ前のBlock全体をsha256でハッシュした値 -
timestamp
: Blockを作成した時刻 -
data
: Blockに格納されているデータ
Block::new()メソッド
データ構造を定義したら、Blockを生成するためのメソッドを定義します。
impl Block {
pub fn new(index: u64, previous_hash: String, data: String) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("The time is something wrong")
.as_millis();
// ハッシュ値の計算
let hash = Self::calculate_hash(index, &previous_hash, &data, timestamp);
Block {
index,
hash,
previous_hash,
timestamp,
data,
}
}
fn calculate_hash(index: u64, previous_hash: &str, data: &str, timestamp: u128) -> String {
let input = format!("{index}{previous_hash}{data}{timestamp}");
let hash = format!("{:x}", Sha256::digest(input.as_bytes()));
hash
}
}
newメソッドの内容はシンプルで、現在のtimestampを取得し、{index}{previous_hash}{data}{timestamp}の4つをハッシュしているだけです。
Blockchain::new()メソッド
Blockchain側でも初回Blockを追加できるようにnewメソッドを作成します。
impl Blockchain {
pub fn new() -> Self {
let genesis_block = Block::new(0, String::new(), "Genesis Block".to_string());
Blockchain {
chain: vec![genesis_block],
}
}
}
Block::newメソッドを使ってVec型データの最初の値にしているだけです。最初のBlockであるため、previous_hashは空文字列とします。ちなみにブロックチェーンではよく最初のブロックをGenesis Blockと呼びます。ここでも最初のブロックのdata値にGenesis Blockと入れることにします。
Blockchain::add_block()メソッド
Blockchainに初回のBlockを作成するメソッド(new()メソッド)を作成したら、2つ目以降のBlockを追加できるようにメソッドを定義します。
impl Blockchain {
...
pub fn add_block(&mut self, data: String) {
let previous_block = self.chain.last().unwrap();
let new_block = Block::new(previous_block.index + 1, previous_block.hash.clone(), data);
if new_block.is_valid(previous_block) {
self.chain.push(new_block);
} else {
panic!("Failed to add block: Block is invalid.");
}
}
pub fn is_valid(&self, previous_block: &Block) -> bool {
self.previous_hash == previous_block.hash
&& self.hash
== Self::calculate_hash(self.index, &self.previous_hash, &self.data, self.timestamp)
}
}
新しいBlockの作成には、Block::new()メソッドを使用します。このメソッドの重要なポイントは、直前のBlockのハッシュ値をprevious_hashとして利用している点です。この仕組みにより、ブロック同士がチェーンのように繋がった構造をになり、データの改ざんを難しくします。
また、実際にBlockChainに追加する前に追加するBlockが正しいことを確認(Block::is_validメソッド)します。
Blockchain::is_chain_valid()メソッド
最後にBlockChain全体が正しいかどうかを確認するメソッドを作成します。
"Genesis Block"から順にハッシュ値を検証し、ブロックチェーンに問題がないことを確認します。
impl Blockchain {
...
pub fn is_chain_valid(&self) -> bool {
for i in 1..self.chain.len() {
let current_block = &self.chain[i];
let previous_block = &self.chain[i - 1];
if !current_block.is_valid(previous_block) {
return false;
}
}
true
}
}
以上で実装したいデータ構造、メソッドが完成しました。
main()関数
実際に作成したコードを利用してみます。
今まで実装してきたコードを整理すると以下のようになります。
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct Block {
pub index: u64,
pub hash: String,
pub previous_hash: String,
pub timestamp: u128,
pub data: String,
}
impl Block {
pub fn new(index: u64, previous_hash: String, data: String) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("The time is something wrong")
.as_millis();
let hash = Self::calculate_hash(index, &previous_hash, &data, timestamp);
Block {
index,
hash,
previous_hash,
timestamp,
data,
}
}
fn calculate_hash(index: u64, previous_hash: &str, data: &str, timestamp: u128) -> String {
let input = format!("{index}{previous_hash}{data}{timestamp}");
let hash = format!("{:x}", Sha256::digest(input.as_bytes()));
hash
}
pub fn is_valid(&self, previous_block: &Block) -> bool {
self.previous_hash == previous_block.hash
&& self.hash
== Self::calculate_hash(self.index, &self.previous_hash, &self.data, self.timestamp)
}
}
use crate::block::Block;
pub struct Blockchain {
pub chain: Vec<Block>,
}
impl Blockchain {
pub fn new() -> Self {
let genesis_block = Block::new(0, String::new(), "Genesis Block".to_string());
Blockchain {
chain: vec![genesis_block],
}
}
pub fn add_block(&mut self, data: String) {
let previous_block = self.chain.last().unwrap();
let new_block = Block::new(previous_block.index + 1, previous_block.hash.clone(), data);
if new_block.is_valid(previous_block) {
self.chain.push(new_block);
} else {
panic!("Failed to add block: Block is invalid.");
}
}
pub fn is_chain_valid(&self) -> bool {
for i in 1..self.chain.len() {
let current_block = &self.chain[i];
let previous_block = &self.chain[i - 1];
if !current_block.is_valid(previous_block) {
return false;
}
}
true
}
}
mod block;
mod blockchain;
use blockchain::Blockchain;
fn main() {
pretty_env_logger::init();
// create a new blockchain
let mut blockchain = Blockchain::new();
log::info!("{:?}", blockchain.chain[0]);
// Add blocks
blockchain.add_block("First block".to_string());
blockchain.add_block("Second block".to_string());
blockchain.add_block("Third block".to_string());
// print the blockchain
for block in &blockchain.chain {
println!("{:?}", block);
}
// Validate the blockchain
println!(
"Check if the blockchain is valid: {}",
blockchain.is_chain_valid()
);
}
実行結果
cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/rust_simple_blockchain`
Block { index: 0, hash: "ab793e5f08ef2d0c3200999a7a9e92789c131451152603552e5050f8b9df93d2", previous_hash: "", timestamp: 1732669772313, data: "Genesis Block" }
Block { index: 1, hash: "8266fcc02a8428e30fb98d5f127c1157088305650801df0b2e38349d32ce665b", previous_hash: "ab793e5f08ef2d0c3200999a7a9e92789c131451152603552e5050f8b9df93d2", timestamp: 1732669772315, data: "First block" }
Block { index: 2, hash: "f896bdbd7011d8147103f704979e6c201de7b37aa7987ee6e26779d8b9df125b", previous_hash: "8266fcc02a8428e30fb98d5f127c1157088305650801df0b2e38349d32ce665b", timestamp: 1732669772316, data: "Second block" }
Block { index: 3, hash: "043062a264ffec3a78d166de40525b2d2d6e0dd05d2b8dd3cb521953bc110b60", previous_hash: "f896bdbd7011d8147103f704979e6c201de7b37aa7987ee6e26779d8b9df125b", timestamp: 1732669772316, data: "Third block" }
Check if the blockchain is valid: true
さいごに
上記の実装は以下リポジトリに置いています。
https://github.com/hsmto25519/rust-simple-blockchain
最後までお読みいただきありがとうございました。