From 817c80bf86bd89a377f7e62cf8fd5ae30756a5d0 Mon Sep 17 00:00:00 2001 From: stinkymonkeyph Date: Wed, 7 Aug 2024 01:49:03 +0800 Subject: [PATCH] feat: initial implementation of merkle trees --- blockchain/block.go | 16 +++++- blockchain/block_test.go | 4 +- blockchain/blockchain.go | 6 +- blockchain/merkle.go | 116 +++++++++++++++++++++++++++++++++++++++ main.go | 2 +- 5 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 blockchain/merkle.go diff --git a/blockchain/block.go b/blockchain/block.go index 6ca7178..8ef99dc 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" "time" "github.com/stinkymonkeyph/gopher-blocks/constants" @@ -14,14 +15,25 @@ type Block struct { Timestamp int64 `json:"timestamp"` Nonce int `json:"nonce"` Transactions []*Transaction `json:"transactions"` + MerkleRoot string `json:"merkle_root"` } -func NewBlock(prevHash string, nonce int) *Block { +func NewBlock(prevHash string, nonce int, txns []*Transaction) *Block { block := new(Block) block.PrevHash = prevHash block.Timestamp = time.Now().UnixMicro() block.Nonce = nonce - block.Transactions = []*Transaction{} + if txns != nil { + block.Transactions = txns + leaves := CreateLeafNodes(block.Transactions) + merkleTree := BuildMerkleTree(leaves) + merkleRoot := merkleTree.Hash + block.MerkleRoot = fmt.Sprintf("%x", merkleRoot) + } else { + block.Transactions = make([]*Transaction, 0) + block.MerkleRoot = "" + } + return block } diff --git a/blockchain/block_test.go b/blockchain/block_test.go index fb72e73..af3c6ae 100644 --- a/blockchain/block_test.go +++ b/blockchain/block_test.go @@ -5,7 +5,7 @@ import ( ) func TestNewBlock(t *testing.T) { - b := NewBlock("0x0", 0) + b := NewBlock("0x0", 0, nil) if b.PrevHash != "0x0" { t.Fatalf("New block return incorrect prev hash, expected 0x0 but received %q", b.PrevHash) @@ -13,7 +13,7 @@ func TestNewBlock(t *testing.T) { } func TestToJSON(t *testing.T) { - b := NewBlock("0x0", 0) + b := NewBlock("0x0", 0, nil) s := b.ToJson() if s == "" { t.Fatalf("ToJson returned an empty string") diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 119bbec..614a2e6 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -73,7 +73,6 @@ func (bc *Blockchain) AddBlock(b *Block) { } for index, txn := range b.Transactions { - txn.Status = constants.STATUS_SUCCESS m[txn.TransactioHash] = true balance := bc.WalletIndex.CalculateBalance(txn.From) log.Printf("\n\nsender balance -> %d \n\n", balance) @@ -110,6 +109,8 @@ func (bc *Blockchain) CopyTransactionPool() []*Transaction { if txn.From != constants.BLOCKCHAIN_AIRDROP_ADDRESS { if senderBalance < int(txn.Value) { txn.Status = constants.STATUS_FAILED + } else { + txn.Status = constants.STATUS_SUCCESS } } t = append(t, txn) @@ -139,8 +140,7 @@ func (bc *Blockchain) ProofOfWork() (int, []*Transaction) { func (bc *Blockchain) Mining() bool { nonce, txns := bc.ProofOfWork() previousHash := bc.LastBlock().Hash() - block := NewBlock(previousHash, nonce) - block.Transactions = txns + block := NewBlock(previousHash, nonce, txns) bc.AddBlock(block) return true } diff --git a/blockchain/merkle.go b/blockchain/merkle.go new file mode 100644 index 0000000..c71fd55 --- /dev/null +++ b/blockchain/merkle.go @@ -0,0 +1,116 @@ +package blockchain + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "errors" +) + +type Node struct { + Left *Node + Right *Node + Hash []byte +} + +func HashData(data []byte) []byte { + hash := sha256.Sum256(data) + return hash[:] +} + +func HashTransaction(tx *Transaction) []byte { + txBytes, _ := json.Marshal(tx) + return txBytes +} + +func CreateLeafNodes(transactions []*Transaction) []*Node { + var leaves []*Node + + for _, tx := range transactions { + hash := HashTransaction(tx) + leaves = append(leaves, &Node{Hash: hash}) + } + + return leaves +} + +func BuildMerkleTree(leaves []*Node) *Node { + if len(leaves) == 0 { + return nil + } + for len(leaves) > 1 { + var newLevel []*Node + for i := 0; i < len(leaves); i += 2 { + if i+1 < len(leaves) { + combinedHash := append(leaves[i].Hash, leaves[i+1].Hash...) + newNode := &Node{ + Left: leaves[i], + Right: leaves[i+1], + Hash: HashData(combinedHash), + } + newLevel = append(newLevel, newNode) + } else { + combinedHash := append(leaves[i].Hash, leaves[i].Hash...) + newNode := &Node{ + Left: leaves[i], + Right: leaves[i], + Hash: HashData(combinedHash), + } + newLevel = append(newLevel, newNode) + } + } + leaves = newLevel + } + return leaves[0] +} + +func GenerateMerkleProof(transactions []*Transaction, index int) ([][]byte, error) { + if index < 0 || index >= len(transactions) { + return nil, errors.New("invalid index") + } + + leaves := CreateLeafNodes(transactions) + proof := make([][]byte, 0) + + for len(leaves) > 1 { + var newLevel []*Node + for i := 0; i < len(leaves); i += 2 { + if i+1 < len(leaves) { + if i == index || i+1 == index { + siblingIndex := i ^ 1 + proof = append(proof, leaves[siblingIndex].Hash) + } + combinedHash := append(leaves[i].Hash, leaves[i+1].Hash...) + newNode := &Node{ + Left: leaves[i], + Right: leaves[i+1], + Hash: HashData(combinedHash), + } + newLevel = append(newLevel, newNode) + } else { + combinedHash := append(leaves[i].Hash, leaves[i].Hash...) + newNode := &Node{ + Left: leaves[i], + Right: leaves[i], + Hash: HashData(combinedHash), + } + newLevel = append(newLevel, newNode) + if i == index { + proof = append(proof, leaves[i].Hash) + } + } + } + leaves = newLevel + index /= 2 + } + return proof, nil +} + +func VerifyTransaction(rootHash []byte, tx *Transaction, proof [][]byte) bool { + currentHash := HashTransaction(tx) + for _, siblingHash := range proof { + combinedHash := append(currentHash, siblingHash...) + currentHash = HashData(combinedHash) + } + return bytes.Equal(currentHash, rootHash) +} diff --git a/main.go b/main.go index d820bdb..3999732 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ func init() { } func main() { - block := blockchain.NewBlock("0x0", 0) + block := blockchain.NewBlock("0x0", 0, nil) bc := blockchain.NewBlockchain(block) bc.Airdrop("0x1") bc.Mining()