Skip to content

Commit

Permalink
feat: initial implementation of merkle trees
Browse files Browse the repository at this point in the history
  • Loading branch information
stinkymonkeyph committed Aug 6, 2024
1 parent 3cee8eb commit 817c80b
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 8 deletions.
16 changes: 14 additions & 2 deletions blockchain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"

"github.com/stinkymonkeyph/gopher-blocks/constants"
Expand All @@ -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
}

Expand Down
4 changes: 2 additions & 2 deletions blockchain/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ 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)
}
}

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")
Expand Down
6 changes: 3 additions & 3 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
116 changes: 116 additions & 0 deletions blockchain/merkle.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 817c80b

Please sign in to comment.