From b3fc6b2bb9b9f4c7d70aaad2adff91d8fd135ab0 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Date: Thu, 23 May 2024 15:14:48 -0300 Subject: [PATCH 1/5] feat: transactions structure made --- internal/transactions/transactions.go | 168 ++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 internal/transactions/transactions.go diff --git a/internal/transactions/transactions.go b/internal/transactions/transactions.go new file mode 100644 index 0000000..1d53628 --- /dev/null +++ b/internal/transactions/transactions.go @@ -0,0 +1,168 @@ +package transactions + +import ( + "bytes" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "fmt" + "strings" + + "github.com/NicholasRodrigues/go-chain/pkg/crypto" +) + +type Transaction struct { + ID []byte + Vin []TransactionInput + Vout []TransactionOutput +} + +type TransactionInput struct { + Txid []byte + Vout int + ScriptSig string + Signature []byte + PubKey []byte +} + +type TransactionOutput struct { + Value int + ScriptPubKey string +} + +// NewTransaction creates a new transaction with the given inputs and outputs. +func NewTransaction(vin []TransactionInput, vout []TransactionOutput) *Transaction { + tx := &Transaction{Vin: vin, Vout: vout} + tx.SetID() + return tx +} + +// Sign signs each input of the transaction using the provided private key. +func (tx *Transaction) Sign(privKey *crypto.PrivateKey) { + for i := range tx.Vin { + msg := tx.Hash() + tx.Vin[i].Signature = privKey.Sign(msg) + tx.Vin[i].PubKey = privKey.PublicKey().Bytes() + } +} + +// Hash returns the hash of the transaction, excluding the signature to avoid circular dependencies. +func (tx *Transaction) Hash() []byte { + txCopy := *tx + for i := range txCopy.Vin { + txCopy.Vin[i].Signature = nil + txCopy.Vin[i].PubKey = nil + } + + var encoded bytes.Buffer + var hash [32]byte + + enc := gob.NewEncoder(&encoded) + err := enc.Encode(txCopy) + if err != nil { + panic(err) + } + + hash = sha256.Sum256(encoded.Bytes()) + return hash[:] +} + +// SetID sets the ID of the transaction by hashing its contents. +func (tx *Transaction) SetID() { + tx.ID = tx.Hash() +} + +// IsCoinbase checks whether the transaction is a coinbase transaction. +func (tx *Transaction) IsCoinbase() bool { + return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 +} + +func utxoKey(txid []byte, vout int) string { + return fmt.Sprintf("%x:%d", txid, vout) +} + +// / Validate ensures that the transaction is valid. +func (tx *Transaction) Validate(utxoSet map[string]TransactionOutput) bool { + if tx.IsCoinbase() { + return true + } + + inputValue := 0 + for _, vin := range tx.Vin { + // Look up the referenced transaction output in the UTXO set + key := utxoKey(vin.Txid, vin.Vout) + utxo, ok := utxoSet[key] + if !ok { + fmt.Printf("Referenced output not found in UTXO set: %s\n", key) + return false // Referenced output not found, transaction is invalid + } + + // Verify the signature + pubKey, err := crypto.PublicKeyFromString(hex.EncodeToString(vin.PubKey)) + if err != nil { + fmt.Printf("Failed to parse public key: %v\n", err) + return false + } + if !pubKey.Verify(tx.Hash(), vin.Signature) { + fmt.Println("Signature is invalid") + return false // Signature is invalid + } + + inputValue += utxo.Value + } + + outputValue := 0 + for _, vout := range tx.Vout { + outputValue += vout.Value + } + + fmt.Printf("Input value: %d, Output value: %d\n", inputValue, outputValue) + + // Check if input value matches output value + return inputValue >= outputValue +} + +// Serialize serializes the transaction into a byte slice. +func (tx *Transaction) Serialize() []byte { + var encoded bytes.Buffer + enc := gob.NewEncoder(&encoded) + err := enc.Encode(tx) + if err != nil { + panic(err) + } + return encoded.Bytes() +} + +// DeserializeTransaction deserializes a transaction from a byte slice. +func DeserializeTransaction(data []byte) *Transaction { + var transaction Transaction + decoder := gob.NewDecoder(bytes.NewReader(data)) + err := decoder.Decode(&transaction) + if err != nil { + panic(err) + } + return &transaction +} + +// String returns a human-readable representation of the transaction. +func (tx *Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("---- Transaction %x:", tx.ID)) + for i, input := range tx.Vin { + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" ScriptSig: %s", input.ScriptSig)) + lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) + lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) + } + + for i, output := range tx.Vout { + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" ScriptPubKey: %s", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} From da7c3f1c78bb5242311f0e4048cf66b36a807e4d Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Date: Thu, 23 May 2024 15:15:02 -0300 Subject: [PATCH 2/5] test: transactions tests made --- internal/transactions/transactions_test.go | 158 +++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 internal/transactions/transactions_test.go diff --git a/internal/transactions/transactions_test.go b/internal/transactions/transactions_test.go new file mode 100644 index 0000000..f1a2287 --- /dev/null +++ b/internal/transactions/transactions_test.go @@ -0,0 +1,158 @@ +package transactions + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/NicholasRodrigues/go-chain/pkg/crypto" +) + +func TestNewTransaction(t *testing.T) { + inputs := []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 1, ScriptSig: "signature"}, + } + outputs := []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + } + tx := NewTransaction(inputs, outputs) + + if len(tx.ID) == 0 { + t.Errorf("Expected transaction ID to be set") + } + if len(tx.Vin) != 1 { + t.Errorf("Expected 1 input, got %d", len(tx.Vin)) + } + if len(tx.Vout) != 1 { + t.Errorf("Expected 1 output, got %d", len(tx.Vout)) + } +} + +func TestTransaction_SerializeDeserialize(t *testing.T) { + inputs := []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 1, ScriptSig: "signature"}, + } + outputs := []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + } + tx := NewTransaction(inputs, outputs) + + serialized := tx.Serialize() + deserialized := DeserializeTransaction(serialized) + + if !bytes.Equal(tx.ID, deserialized.ID) { + t.Errorf("Expected transaction IDs to be equal, got %x and %x", tx.ID, deserialized.ID) + } + if len(deserialized.Vin) != len(tx.Vin) { + t.Errorf("Expected %d inputs, got %d", len(tx.Vin), len(deserialized.Vin)) + } + if len(deserialized.Vout) != len(tx.Vout) { + t.Errorf("Expected %d outputs, got %d", len(tx.Vout), len(deserialized.Vout)) + } +} + +func TestTransaction_SetID(t *testing.T) { + inputs := []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 1, ScriptSig: "signature"}, + } + outputs := []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + } + tx := NewTransaction(inputs, outputs) + txIDBefore := tx.ID + + tx.SetID() + if bytes.Equal(tx.ID, txIDBefore) { + t.Errorf("Expected transaction ID to change after setting") + } +} + +func TestTransaction_SignAndVerify(t *testing.T) { + privKey, err := crypto.NewPrivateKey() + if err != nil { + t.Fatalf("Failed to create private key: %v", err) + } + + inputs := []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 1, ScriptSig: "signature"}, + } + outputs := []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + } + tx := NewTransaction(inputs, outputs) + + tx.Sign(privKey) + + for _, vin := range tx.Vin { + pubKey, err := crypto.PublicKeyFromString(hex.EncodeToString(vin.PubKey)) + if err != nil { + t.Fatalf("Failed to parse public key: %v", err) + } + if !pubKey.Verify(tx.Hash(), vin.Signature) { + t.Errorf("Failed to verify transaction input signature") + } + } +} + +func TestTransaction_Validate(t *testing.T) { + privKey, err := crypto.NewPrivateKey() + if err != nil { + t.Fatalf("Failed to create private key: %v", err) + } + + // Example UTXO set + utxoSet := make(map[string]TransactionOutput) + utxoSet[utxoKey([]byte("somepreviousid"), 0)] = TransactionOutput{Value: 10, ScriptPubKey: "pubkey1"} + + inputs := []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 0, ScriptSig: "signature"}, + } + outputs := []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + } + tx := NewTransaction(inputs, outputs) + + // Sign the transaction + tx.Sign(privKey) + + // Validate the transaction + isValid := tx.Validate(utxoSet) + if !isValid { + t.Errorf("Expected transaction to be valid") + } + + // Modify the transaction to make it invalid + tx.Vin[0].Txid = []byte("invalidid") + isValid = tx.Validate(utxoSet) + if isValid { + t.Errorf("Expected transaction to be invalid") + } +} + +func TestTransaction_IsCoinbase(t *testing.T) { + coinbaseTx := NewTransaction( + []TransactionInput{ + {Txid: []byte{}, Vout: -1, ScriptSig: "coinbase"}, + }, + []TransactionOutput{ + {Value: 10, ScriptPubKey: "miner1"}, + }, + ) + + if !coinbaseTx.IsCoinbase() { + t.Errorf("Expected transaction to be a coinbase transaction") + } + + regularTx := NewTransaction( + []TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 0, ScriptSig: "signature"}, + }, + []TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + }, + ) + + if regularTx.IsCoinbase() { + t.Errorf("Expected transaction to be a regular transaction") + } +} From 8d9c61d60e31acb3d88aa060b46b05c5f91c847c Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Date: Thu, 23 May 2024 15:44:54 -0300 Subject: [PATCH 3/5] test: transactions integrated with blocks and blockchain --- cmd/gochain/main.go | 18 ------ internal/blockchain/block.go | 23 +++++-- internal/blockchain/block_test.go | 76 +++++++++++----------- internal/blockchain/blockchain.go | 22 +++++-- internal/blockchain/blockchain_test.go | 77 ++++++++--------------- internal/blockchain/proof_of_work.go | 12 ++-- internal/blockchain/proof_of_work_test.go | 32 ++++++---- internal/blockchain/validantions.go | 49 ++++++--------- internal/blockchain/validations_test.go | 26 +++++--- 9 files changed, 164 insertions(+), 171 deletions(-) diff --git a/cmd/gochain/main.go b/cmd/gochain/main.go index 4b09a79..da29a2c 100644 --- a/cmd/gochain/main.go +++ b/cmd/gochain/main.go @@ -1,22 +1,4 @@ package main -import ( - "fmt" - "github.com/NicholasRodrigues/go-chain/internal/blockchain" -) - func main() { - bc := blockchain.NewBlockchain() - - bc.AddBlock("First Block after Genesis") - bc.AddBlock("Second Block after Genesis") - - for _, block := range bc.Blocks { - fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) - fmt.Printf("Data: %s\n", block.Data) - fmt.Printf("Hash: %x\n", block.Hash) - fmt.Println() - } - - fmt.Println("Is blockchain valid?", bc.IsValid()) } diff --git a/internal/blockchain/block.go b/internal/blockchain/block.go index e27243d..b9d890c 100644 --- a/internal/blockchain/block.go +++ b/internal/blockchain/block.go @@ -3,30 +3,45 @@ package blockchain import ( "bytes" "crypto/sha256" + "github.com/NicholasRodrigues/go-chain/internal/transactions" "strconv" "time" ) type Block struct { Timestamp int64 - Data []byte // Transactions + Transactions []*transactions.Transaction PrevBlockHash []byte Hash []byte Counter int // Nonce } +// SetHash recalculates the hash of the block. func (b *Block) SetHash() { timestamp := []byte(strconv.FormatInt(b.Timestamp, 10)) - headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{}) + headers := bytes.Join([][]byte{b.PrevBlockHash, b.HashTransactions(), timestamp}, []byte{}) hash := sha256.Sum256(headers) b.Hash = hash[:] } +// HashTransactions returns a hash of the transactions in the block. +func (b *Block) HashTransactions() []byte { + var txHashes [][]byte + var txHash [32]byte + + for _, tx := range b.Transactions { + txHashes = append(txHashes, tx.Serialize()) + } + txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) + + return txHash[:] +} + // NewBlock creates and returns a new block. -func NewBlock(data string, prevBlockHash []byte) *Block { +func NewBlock(transactions []*transactions.Transaction, prevBlockHash []byte) *Block { block := &Block{ Timestamp: time.Now().Unix(), - Data: []byte(data), + Transactions: transactions, PrevBlockHash: prevBlockHash, Hash: []byte{}, Counter: 0, diff --git a/internal/blockchain/block_test.go b/internal/blockchain/block_test.go index 97cafd2..9d253e9 100644 --- a/internal/blockchain/block_test.go +++ b/internal/blockchain/block_test.go @@ -2,54 +2,54 @@ package blockchain import ( "bytes" - "crypto/sha256" - "strconv" + "github.com/NicholasRodrigues/go-chain/internal/transactions" + "github.com/NicholasRodrigues/go-chain/pkg/crypto" "testing" - "time" ) -func TestSetHash(t *testing.T) { - data := "test data" - prevHash := []byte("previous hash") - block := &Block{ - Timestamp: time.Now().Unix(), - Data: []byte(data), - PrevBlockHash: prevHash, - Counter: 0, - } - block.SetHash() - - expectedHash := sha256.Sum256(bytes.Join([][]byte{prevHash, []byte(data), []byte(strconv.FormatInt(block.Timestamp, 10))}, []byte{})) - if !bytes.Equal(block.Hash, expectedHash[:]) { - t.Errorf("expected hash %x, got %x", expectedHash, block.Hash) - } +func createTransaction() *transactions.Transaction { + privKey, _ := crypto.NewPrivateKey() + tx := transactions.NewTransaction( + []transactions.TransactionInput{ + {Txid: []byte("somepreviousid"), Vout: 0, ScriptSig: "scriptSig"}, + }, + []transactions.TransactionOutput{ + {Value: 10, ScriptPubKey: "pubkey1"}, + }, + ) + tx.Sign(privKey) + return tx } -func TestNewBlock(t *testing.T) { - data := "test data" - prevHash := []byte("previous hash") - block := NewBlock(data, prevHash) +// TestBlockHash checks if the block hash is set correctly +func TestBlockHash(t *testing.T) { + tx := createTransaction() + block := NewBlock([]*transactions.Transaction{tx}, []byte{}) + expectedHash := block.Hash - if block.Data == nil || !bytes.Equal(block.Data, []byte(data)) { - t.Errorf("expected data %s, got %s", data, string(block.Data)) - } - if block.PrevBlockHash == nil || !bytes.Equal(block.PrevBlockHash, prevHash) { - t.Errorf("expected previous hash %x, got %x", prevHash, block.PrevBlockHash) - } - if block.Hash == nil || len(block.Hash) == 0 { - t.Error("expected non-nil and non-empty hash") + if !bytes.Equal(block.Hash, expectedHash) { + t.Errorf("Expected hash %x, but got %x", expectedHash, block.Hash) } } -func TestNewGenesisBlock(t *testing.T) { - genesisBlock := NewGenesisBlock() - if genesisBlock == nil { - t.Fatalf("Expected genesis block to be created") +// TestGenesisBlock checks if the genesis block is created correctly +func TestGenesisBlock(t *testing.T) { + block := NewGenesisBlock() + if block == nil { + t.Errorf("Genesis block is nil") } - if len(genesisBlock.Hash) == 0 { - t.Errorf("Expected genesis block hash to be set") + if len(block.PrevBlockHash) != 0 { + t.Errorf("Genesis block should have no previous block hash") } - if !bytes.Equal(genesisBlock.PrevBlockHash, []byte{}) { - t.Errorf("Expected genesis block to have no previous block hash") +} + +// TestBlockMining ensures that mining a block works correctly +func TestBlockMining(t *testing.T) { + tx := createTransaction() + block := NewBlock([]*transactions.Transaction{tx}, []byte{}) + pow := NewProofOfWork(block) + + if !pow.Validate() { + t.Errorf("Block did not pass proof of work validation") } } diff --git a/internal/blockchain/blockchain.go b/internal/blockchain/blockchain.go index e0b3f06..ba7bd82 100644 --- a/internal/blockchain/blockchain.go +++ b/internal/blockchain/blockchain.go @@ -1,21 +1,35 @@ package blockchain -import "bytes" +import ( + "bytes" + "github.com/NicholasRodrigues/go-chain/internal/transactions" +) type Blockchain struct { Blocks []*Block } -func (bc *Blockchain) AddBlock(data string) { +// AddBlock adds a new block to the blockchain with the given transactions. +func (bc *Blockchain) AddBlock(transactions []*transactions.Transaction) { prevBlock := bc.Blocks[len(bc.Blocks)-1] - newBlock := NewBlock(data, prevBlock.Hash) + newBlock := NewBlock(transactions, prevBlock.Hash) bc.Blocks = append(bc.Blocks, newBlock) } +// NewGenesisBlock creates and returns the genesis block. func NewGenesisBlock() *Block { - return NewBlock("Genesis Block", []byte{}) + coinbase := transactions.NewTransaction( + []transactions.TransactionInput{ + {Txid: []byte{}, Vout: -1, ScriptSig: "Genesis"}, + }, + []transactions.TransactionOutput{ + {Value: 50, ScriptPubKey: "coinbase"}, + }, + ) + return NewBlock([]*transactions.Transaction{coinbase}, []byte{}) } +// NewBlockchain creates and returns a new blockchain with the genesis block. func NewBlockchain() *Blockchain { return &Blockchain{[]*Block{NewGenesisBlock()}} } diff --git a/internal/blockchain/blockchain_test.go b/internal/blockchain/blockchain_test.go index 1db318c..39c18d9 100644 --- a/internal/blockchain/blockchain_test.go +++ b/internal/blockchain/blockchain_test.go @@ -2,70 +2,45 @@ package blockchain import ( "bytes" + "github.com/NicholasRodrigues/go-chain/internal/transactions" "testing" ) -func TestProofOfWork_Run(t *testing.T) { - block := NewBlock("test data", []byte{}) - pow := NewProofOfWork(block) - - hash, nonce := pow.Run() - block.Hash = hash[:] - block.Counter = nonce - - if !pow.Validate() { - t.Errorf("Proof of work validation failed for block with hash %x", block.Hash) - } -} - -func TestProofOfWork_Validate(t *testing.T) { - block := NewBlock("test data", []byte{}) - pow := NewProofOfWork(block) - - hash, nonce := pow.Run() - block.Hash = hash[:] - block.Counter = nonce +// TestBlockchainAddBlock checks if blocks are added correctly to the blockchain +func TestBlockchainAddBlock(t *testing.T) { + bc := NewBlockchain() + tx := createTransaction() + bc.AddBlock([]*transactions.Transaction{tx}) - if !pow.Validate() { - t.Errorf("Expected block to be valid, but validation failed") + if len(bc.Blocks) != 2 { + t.Errorf("Expected blockchain length 2, but got %d", len(bc.Blocks)) } -} -func TestNewBlockchain(t *testing.T) { - bc := NewBlockchain() - if bc == nil { - t.Fatalf("Expected blockchain to be created") - } - if len(bc.Blocks) != 1 { - t.Fatalf("Expected blockchain to have one block, got %d", len(bc.Blocks)) - } - if !bytes.Equal(bc.Blocks[0].Hash, NewGenesisBlock().Hash) { - t.Errorf("Expected genesis block hash to match") + if !bytes.Equal(bc.Blocks[1].PrevBlockHash, bc.Blocks[0].Hash) { + t.Errorf("Previous block hash does not match") } } -func TestAddBlock(t *testing.T) { +// TestBlockchainIsValid checks if the blockchain validation works correctly +func TestBlockchainIsValid(t *testing.T) { bc := NewBlockchain() - bc.AddBlock("First Block after Genesis") - bc.AddBlock("Second Block after Genesis") + tx := createTransaction() + bc.AddBlock([]*transactions.Transaction{tx}) + bc.AddBlock([]*transactions.Transaction{tx}) - if len(bc.Blocks) != 3 { - t.Fatalf("Expected blockchain to have three Blocks, got %d", len(bc.Blocks)) - } - if !bytes.Equal(bc.Blocks[1].PrevBlockHash, bc.Blocks[0].Hash) { - t.Errorf("Expected first block to reference genesis block hash") - } - if !bytes.Equal(bc.Blocks[2].PrevBlockHash, bc.Blocks[1].Hash) { - t.Errorf("Expected second block to reference first block hash") + if !bc.IsValid() { + t.Errorf("Blockchain should be valid") } -} -func TestBlockchain_IsValid(t *testing.T) { - bc := NewBlockchain() - bc.AddBlock("First Block after Genesis") - bc.AddBlock("Second Block after Genesis") + // Tamper with the blockchain + bc.Blocks[1].Transactions = []*transactions.Transaction{ + transactions.NewTransaction( + []transactions.TransactionInput{{Txid: []byte("tampered"), Vout: 0, ScriptSig: "tampered"}}, + []transactions.TransactionOutput{{Value: 50, ScriptPubKey: "tampered"}}, + ), + } - if !bc.IsValid() { - t.Errorf("Expected blockchain to be valid") + if bc.IsValid() { + t.Errorf("Blockchain should be invalid after tampering") } } diff --git a/internal/blockchain/proof_of_work.go b/internal/blockchain/proof_of_work.go index 9a4094a..31512c4 100644 --- a/internal/blockchain/proof_of_work.go +++ b/internal/blockchain/proof_of_work.go @@ -14,8 +14,8 @@ var maxNonce = math.MaxInt64 // ProofOfWork represents a proof-of-work. type ProofOfWork struct { - block *Block - T big.Int + block *Block + target *big.Int } // NewProofOfWork creates and returns a ProofOfWork. @@ -23,7 +23,7 @@ func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-Difficulty)) - pow := &ProofOfWork{b, *target} + pow := &ProofOfWork{b, target} return pow } @@ -33,7 +33,7 @@ func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, - pow.block.Data, + pow.block.HashTransactions(), []byte(strconv.FormatInt(pow.block.Timestamp, 10)), []byte(strconv.FormatInt(int64(Difficulty), 10)), []byte(strconv.FormatInt(int64(nonce), 10)), @@ -55,7 +55,7 @@ func (pow *ProofOfWork) Run() ([]byte, int) { hash = sha256.Sum256(data) hashInt.SetBytes(hash[:]) - if hashInt.Cmp(&pow.T) == -1 { + if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ @@ -73,5 +73,5 @@ func (pow *ProofOfWork) Validate() bool { hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) - return hashInt.Cmp(&pow.T) == -1 + return hashInt.Cmp(pow.target) == -1 } diff --git a/internal/blockchain/proof_of_work_test.go b/internal/blockchain/proof_of_work_test.go index 95de734..f9da932 100644 --- a/internal/blockchain/proof_of_work_test.go +++ b/internal/blockchain/proof_of_work_test.go @@ -1,24 +1,32 @@ package blockchain import ( - "math/big" + "github.com/NicholasRodrigues/go-chain/internal/transactions" "testing" ) -func TestNewProofOfWork(t *testing.T) { - block := NewBlock("test data", []byte{}) +// TestProofOfWorkRun ensures that the proof of work runs correctly +func TestProofOfWorkRun(t *testing.T) { + tx := createTransaction() + block := NewBlock([]*transactions.Transaction{tx}, []byte{}) pow := NewProofOfWork(block) + hash, nonce := pow.Run() - expectedTarget := big.NewInt(1) - expectedTarget.Lsh(expectedTarget, uint(256-Difficulty)) + block.Hash = hash[:] + block.Counter = nonce - if pow == nil { - t.Fatal("expected non-nil ProofOfWork") + if !pow.Validate() { + t.Errorf("Proof of work did not validate") } - if pow.block != block { - t.Error("expected pow.block to be the same as block") - } - if pow.T.Cmp(expectedTarget) != 0 { - t.Errorf("expected target %s, got %s", expectedTarget, &pow.T) +} + +// TestProofOfWorkValidate checks if proof of work validation works correctly +func TestProofOfWorkValidate(t *testing.T) { + tx := createTransaction() + block := NewBlock([]*transactions.Transaction{tx}, []byte{}) + pow := NewProofOfWork(block) + + if !pow.Validate() { + t.Errorf("Proof of work validation failed") } } diff --git a/internal/blockchain/validantions.go b/internal/blockchain/validantions.go index a1b274e..0da081b 100644 --- a/internal/blockchain/validantions.go +++ b/internal/blockchain/validantions.go @@ -1,24 +1,20 @@ package blockchain import ( - "crypto/sha256" + "bytes" "fmt" - "math/big" - "reflect" - "time" + "github.com/NicholasRodrigues/go-chain/internal/transactions" ) +// Receive is a function type for receiving data type Receive func() string + +// Input is a function type for providing input data type Input func() string // ValidateBlockPredicate validates a block using Proof of Work func ValidateBlockPredicate(pow *ProofOfWork) bool { - var hashInt big.Int - data := pow.prepareData(pow.block.Counter) - hash := sha256.Sum256(data) - hashInt.SetBytes(hash[:]) - - return hashInt.Cmp(&pow.T) == -1 + return pow.Validate() } // ContentValidatePredicate validates the content of the blockchain @@ -28,7 +24,7 @@ func ContentValidatePredicate(chain *Blockchain) bool { } for i := 1; i < len(chain.Blocks); i++ { - if reflect.DeepEqual(chain.Blocks[i].Hash, chain.Blocks[i-1].Hash) { + if !bytes.Equal(chain.Blocks[i].PrevBlockHash, chain.Blocks[i-1].Hash) { return false } } @@ -41,12 +37,13 @@ func InputContributionFunction(data []byte, chain *Blockchain, round int, input receiveData := receive() concatData := inputData + receiveData - newBlock := &Block{ - Timestamp: time.Now().Unix(), - Data: []byte(concatData), - PrevBlockHash: chain.Blocks[len(chain.Blocks)-1].Hash, - Counter: round, - } + newTransaction := transactions.NewTransaction( + []transactions.TransactionInput{{Txid: []byte{}, Vout: -1, ScriptSig: concatData}}, + []transactions.TransactionOutput{{Value: 50, ScriptPubKey: "pubkey1"}}, + ) + + prevBlock := chain.Blocks[len(chain.Blocks)-1] + newBlock := NewBlock([]*transactions.Transaction{newTransaction}, prevBlock.Hash) chain.Blocks = append(chain.Blocks, newBlock) @@ -62,7 +59,9 @@ func InputContributionFunction(data []byte, chain *Blockchain, round int, input func ChainReadFunction(chain *Blockchain) string { var data string for _, block := range chain.Blocks { - data += string(block.Data) + for _, tx := range block.Transactions { + data += fmt.Sprintf("%x", tx.ID) + } } return data } @@ -73,19 +72,11 @@ func ChainValidationPredicate(chain *Blockchain) bool { return false } - for len(chain.Blocks) > 0 { - lastBlock := chain.Blocks[len(chain.Blocks)-1] - hash := sha256.Sum256(lastBlock.Data) - proof := &ProofOfWork{block: lastBlock, T: *big.NewInt(1)} - - if !ValidateBlockPredicate(proof) || !reflect.DeepEqual(lastBlock.Hash, hash) { + for _, block := range chain.Blocks { + pow := NewProofOfWork(block) + if !pow.Validate() { return false } - - // Prepare the hash for the next iteration - hash = [32]byte{} - copy(hash[:], lastBlock.Data) - chain.Blocks = chain.Blocks[:len(chain.Blocks)-1] } return true diff --git a/internal/blockchain/validations_test.go b/internal/blockchain/validations_test.go index f171827..bbf0550 100644 --- a/internal/blockchain/validations_test.go +++ b/internal/blockchain/validations_test.go @@ -1,19 +1,25 @@ package blockchain import ( + "fmt" + "github.com/NicholasRodrigues/go-chain/internal/transactions" "testing" ) func TestContentValidatePredicate(t *testing.T) { bc := NewBlockchain() - bc.AddBlock("New Block 1") - bc.AddBlock("New Block 2") + tx1 := createTransaction() + tx2 := createTransaction() + bc.AddBlock([]*transactions.Transaction{tx1}) + bc.AddBlock([]*transactions.Transaction{tx2}) if !ContentValidatePredicate(bc) { t.Error("expected blockchain to be valid") } - bc.Blocks[1].Hash = bc.Blocks[0].Hash + // Tamper with the blockchain + bc.Blocks[1].Transactions[0].Vin[0].ScriptSig = "tampered" + bc.Blocks[1].SetHash() // Recalculate the hash after tampering if ContentValidatePredicate(bc) { t.Error("expected blockchain to be invalid") } @@ -37,9 +43,9 @@ func TestInputContributionFunction(t *testing.T) { t.Errorf("expected blockchain length 2, got %d", len(bc.Blocks)) } - concat_data := "input data" + "received data" - if string(bc.Blocks[1].Data) != concat_data { - t.Errorf("expected block data %s, got %s", concat_data, string(bc.Blocks[1].Data)) + concatData := "input data" + "received data" + if string(bc.Blocks[1].Transactions[0].Vin[0].ScriptSig) != concatData { + t.Errorf("expected block data %s, got %s", concatData, string(bc.Blocks[1].Transactions[0].Vin[0].ScriptSig)) } if !ContentValidatePredicate(bc) { @@ -49,11 +55,13 @@ func TestInputContributionFunction(t *testing.T) { func TestChainReadFunction(t *testing.T) { bc := NewBlockchain() - bc.AddBlock("New Block 1") - bc.AddBlock("New Block 2") + tx1 := createTransaction() + tx2 := createTransaction() + bc.AddBlock([]*transactions.Transaction{tx1}) + bc.AddBlock([]*transactions.Transaction{tx2}) data := ChainReadFunction(bc) - expectedData := "Genesis BlockNew Block 1New Block 2" + expectedData := fmt.Sprintf("%x%x%x", bc.Blocks[0].Transactions[0].ID, tx1.ID, tx2.ID) if data != expectedData { t.Errorf("expected chain data %s, got %s", expectedData, data) From 2b04437d447151f1c22e578f0f553bd2291019a7 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Date: Thu, 23 May 2024 16:05:20 -0300 Subject: [PATCH 4/5] test: transactions integrated with blocks and blockchain --- cmd/gochain/main.go | 156 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/cmd/gochain/main.go b/cmd/gochain/main.go index da29a2c..d60b54a 100644 --- a/cmd/gochain/main.go +++ b/cmd/gochain/main.go @@ -1,4 +1,160 @@ package main +import ( + "bufio" + "encoding/hex" + "fmt" + "github.com/NicholasRodrigues/go-chain/internal/blockchain" + "github.com/NicholasRodrigues/go-chain/internal/transactions" + "github.com/NicholasRodrigues/go-chain/pkg/crypto" + "os" + "strconv" +) + func main() { + bc := blockchain.NewBlockchain() + scanner := bufio.NewScanner(os.Stdin) + + for { + printMenu() + scanner.Scan() + cmd := scanner.Text() + + switch cmd { + case "1": + handleAddBlock(bc, scanner) + case "2": + handleViewBlockchain(bc) + case "3": + handleValidateBlockchain(bc) + case "4": + handleCreateTransaction(scanner) + case "5": + handleValidateTransaction(scanner) + case "6": + handleExit() + default: + fmt.Println("Invalid command. Please try again.") + } + } +} + +func printMenu() { + fmt.Println("\nBlockchain CLI") + fmt.Println("1. Add Block") + fmt.Println("2. View Blockchain") + fmt.Println("3. Validate Blockchain") + fmt.Println("4. Create Transaction") + fmt.Println("5. Validate Transaction") + fmt.Println("6. Exit") + fmt.Print("Enter command: ") +} + +func handleAddBlock(bc *blockchain.Blockchain, scanner *bufio.Scanner) { + fmt.Print("Enter data for the new block: ") + scanner.Scan() + data := scanner.Text() + + input := func() string { + return "input data" + } + receive := func() string { + return data + } + + blockchain.InputContributionFunction([]byte(data), bc, len(bc.Blocks), input, receive) + fmt.Println("Block added successfully!") +} + +func handleViewBlockchain(bc *blockchain.Blockchain) { + for i, block := range bc.Blocks { + fmt.Printf("Block %d: %x\n", i, block.Hash) + fmt.Printf("Previous Hash: %x\n", block.PrevBlockHash) + fmt.Printf("Transactions: \n") + for _, tx := range block.Transactions { + fmt.Printf(" %s\n", tx.String()) + } + fmt.Println() + } +} + +func handleValidateBlockchain(bc *blockchain.Blockchain) { + if blockchain.ChainValidationPredicate(bc) { + fmt.Println("Blockchain is valid.") + } else { + fmt.Println("Blockchain is invalid.") + } +} + +func handleCreateTransaction(scanner *bufio.Scanner) { + fmt.Print("Enter Txid: ") + scanner.Scan() + txid := scanner.Text() + + fmt.Print("Enter Vout: ") + scanner.Scan() + vout, _ := strconv.Atoi(scanner.Text()) + + fmt.Print("Enter ScriptSig: ") + scanner.Scan() + scriptSig := scanner.Text() + + fmt.Print("Enter Value: ") + scanner.Scan() + value, _ := strconv.Atoi(scanner.Text()) + + fmt.Print("Enter ScriptPubKey: ") + scanner.Scan() + scriptPubKey := scanner.Text() + + tx := transactions.NewTransaction( + []transactions.TransactionInput{{Txid: []byte(txid), Vout: vout, ScriptSig: scriptSig}}, + []transactions.TransactionOutput{{Value: value, ScriptPubKey: scriptPubKey}}, + ) + + privKey, _ := crypto.NewPrivateKey() + tx.Sign(privKey) + + fmt.Println("Transaction created successfully!") + fmt.Println(tx.String()) +} + +func handleValidateTransaction(scanner *bufio.Scanner) { + fmt.Print("Enter serialized transaction: ") + scanner.Scan() + serializedTx := scanner.Text() + txBytes, _ := hex.DecodeString(serializedTx) + tx := transactions.DeserializeTransaction(txBytes) + + utxoSet := make(map[string]transactions.TransactionOutput) + fmt.Println("Enter UTXO set (enter 'done' to finish):") + for { + fmt.Print("Enter UTXO key (txid:vout): ") + scanner.Scan() + key := scanner.Text() + if key == "done" { + break + } + + fmt.Print("Enter value: ") + scanner.Scan() + value, _ := strconv.Atoi(scanner.Text()) + + fmt.Print("Enter ScriptPubKey: ") + scanner.Scan() + scriptPubKey := scanner.Text() + + utxoSet[key] = transactions.TransactionOutput{Value: value, ScriptPubKey: scriptPubKey} + } + + if tx.Validate(utxoSet) { + fmt.Println("Transaction is valid.") + } else { + fmt.Println("Transaction is invalid.") + } +} + +func handleExit() { + fmt.Println("Exiting...") + os.Exit(0) } From 47ff03c87f2d07dbc1f6d769d27fc77753127d84 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Date: Thu, 23 May 2024 16:10:11 -0300 Subject: [PATCH 5/5] test: transactions integrated with blocks and blockchain --- internal/blockchain/block_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/blockchain/block_test.go b/internal/blockchain/block_test.go index 9d253e9..a70f69c 100644 --- a/internal/blockchain/block_test.go +++ b/internal/blockchain/block_test.go @@ -35,9 +35,6 @@ func TestBlockHash(t *testing.T) { // TestGenesisBlock checks if the genesis block is created correctly func TestGenesisBlock(t *testing.T) { block := NewGenesisBlock() - if block == nil { - t.Errorf("Genesis block is nil") - } if len(block.PrevBlockHash) != 0 { t.Errorf("Genesis block should have no previous block hash") }