diff --git a/blockchain/block_test.go b/blockchain/block_test.go index b420394..d11437f 100644 --- a/blockchain/block_test.go +++ b/blockchain/block_test.go @@ -17,3 +17,11 @@ func TestEncodeDecodeBlock(t *testing.T) { t.Fail() } } + +func TestContainsTransaction(t *testing.T) { + b := newBlock() + + if exists, _ := b.ContainsTransaction(b.Transactions[0]); !exists { + t.Fail() + } +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 126aaa4..e0835f7 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1,7 +1,6 @@ package blockchain import ( - "crypto/ecdsa" "encoding/gob" "io" ) @@ -42,77 +41,6 @@ func DecodeBlockChain(r io.Reader) *BlockChain { return &bc } -// ValidTransaction checks whether a transaction is valid, assuming the -// blockchain is valid. -func (bc *BlockChain) ValidTransaction(t *Transaction) bool { - - // Find the transaction input in the chain (by hash) - var input *Transaction - inputBlock := bc.Blocks[t.Input.BlockNumber] - for _, transaction := range inputBlock.Transactions { - if t.Input.Hash == HashSum(transaction) { - input = transaction - } - } - if input == nil { - return false - } - - // Check that output to sender in input is equal to outputs in t - var inAmount uint64 - for _, output := range input.Outputs { - if output.Recipient == t.Sender { - inAmount += output.Amount - } - } - var outAmount uint64 - for _, output := range t.Outputs { - outAmount += output.Amount - } - if inAmount != outAmount { - return false - } - - // Verify signature of t - hash := HashSum(t.TxBody) - if !ecdsa.Verify(t.Sender.Key(), hash.Marshal(), t.Sig.R, t.Sig.S) { - return false - } - - // Test if identical transaction already exists in chain. - endChain := uint32(len(bc.Blocks)) - for i := t.Input.BlockNumber; i < endChain; i++ { - if exists, _ := bc.Blocks[i].ContainsTransaction(t); exists { - return false - } - } - - return true -} - -// ValidBlock checks whether a block is valid -func (bc *BlockChain) ValidBlock(b *Block) bool { - // Check that block number is one greater than last block - lastBlock := bc.Blocks[b.BlockNumber-1] - if lastBlock.BlockNumber != b.BlockNumber-1 { - return false - } - - // Verify every Transaction in the block. - for _, t := range b.Transactions { - if !bc.ValidTransaction(t) { - return false - } - } - - // Check that hash of last block is correct - if HashSum(lastBlock) != b.LastBlock { - return false - } - - return true -} - // AppendBlock adds a block to the end of the block chain. func (bc *BlockChain) AppendBlock(b *Block, miner Address) { b.BlockNumber = uint32(len(bc.Blocks)) @@ -120,3 +48,16 @@ func (bc *BlockChain) AppendBlock(b *Block, miner Address) { b.Miner = miner bc.Blocks = append(bc.Blocks, b) } + +// GetInputTransaction returns the input Transaction to t. If the input does +// not exist, then GetInputTransaction returns nil. +func (bc *BlockChain) GetInputTransaction(t *Transaction) *Transaction { + if t.Input.BlockNumber > uint32(len(bc.Blocks)) { + return nil + } + b := bc.Blocks[t.Input.BlockNumber] + if t.Input.Index > uint32(len(b.Transactions)) { + return nil + } + return b.Transactions[t.Input.Index] +} diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 584e3fe..6e704a4 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -2,7 +2,6 @@ package blockchain import ( "bytes" - crand "crypto/rand" "testing" log "github.com/Sirupsen/logrus" @@ -12,100 +11,6 @@ func TestMain(t *testing.T) { log.SetLevel(log.DebugLevel) } -func TestValidTransactionNotInBlock(t *testing.T) { - tr, _ := newTransactionValue(newWallet(), newWallet(), 1) - bc, _ := newValidBlockChainFixture() - - if bc.ValidTransaction(tr) { - t.Fail() - } -} - -func TestValidTransactionInputsFail(t *testing.T) { - // 2 + 2 = 5 ? - bc, _ := newValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[0] - tr.Outputs[0].Amount = 5 - - if bc.ValidTransaction(tr) { - t.Fail() - } -} - -func TestValidTransactionSignatureFail(t *testing.T) { - bc, _ := newValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[0] - - fakeSender := newWallet() - tr, _ = tr.TxBody.Sign(fakeSender, crand.Reader) - bc.Blocks[1].Transactions[0] = tr - - if bc.ValidTransaction(tr) { - t.Fail() - } -} - -func TestValidTransactionPass(t *testing.T) { - bc, b := newValidChainAndBlock() - tr := b.Transactions[0] - - if !bc.ValidTransaction(tr) { - t.Fail() - } -} - -func TestTransactionRespend(t *testing.T) { - bc, _ := newValidBlockChainFixture() - trC := bc.Blocks[1].Transactions[0] - b := newOutputBlock([]*Transaction{trC}, bc.Blocks[1]) - bc.AppendBlock(b, newWallet().Public()) - - if bc.ValidTransaction(trC) { - t.Fail() - } -} - -func TestValidBlockTransactionInvalid(t *testing.T) { - bc, _ := newValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[0] - tr.Outputs[0].Amount = 5 - - if bc.ValidBlock(bc.Blocks[1]) { - t.Fail() - } -} - -func TestValidBlockNumberWrong(t *testing.T) { - bc, _ := newValidBlockChainFixture() - bc.Blocks[1].BlockNumber = 2 - - if bc.ValidBlock(bc.Blocks[1]) { - t.Fail() - } -} - -func TestValidBlockHashWrong(t *testing.T) { - bc, b := newValidChainAndBlock() - b.BlockHeader.LastBlock = newHash() - - if bc.ValidBlock(b) { - t.Fail() - } -} - -func TestValidBlock(t *testing.T) { - bc, b := newValidChainAndBlock() - - if !bc.ValidBlock(b) { - t.Fail() - } -} - -func TestBlockTwoInputs(t *testing.T) { - // block should fail to be valid if there exists two transactions - // referencing the same input, but output > input (double spend attack) -} - func TestEncodeDecodeBlockChain(t *testing.T) { b1 := newBlockChain() diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index c72dee5..25552e7 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -22,6 +22,7 @@ func newTxHashPointer() TxHashPointer { return TxHashPointer{ BlockNumber: mrand.Uint32(), Hash: newHash(), + Index: mrand.Uint32(), } } @@ -107,12 +108,13 @@ func newOutputBlock(t []*Transaction, input *Block) *Block { } } -func newTransactionValue(s, r Wallet, a uint64) (*Transaction, error) { +func newTransactionValue(s, r Wallet, a uint64, i uint32) (*Transaction, error) { tbody := TxBody{ Sender: s.Public(), Input: TxHashPointer{ BlockNumber: 0, Hash: newHash(), + Index: i, }, Outputs: make([]TxOutput, 1), } @@ -129,13 +131,13 @@ func newValidBlockChainFixture() (*BlockChain, Wallet) { sender := newWallet() recipient := newWallet() - trA, _ := newTransactionValue(original, sender, 2) + trA, _ := newTransactionValue(original, sender, 2, 1) trA.Outputs = append(trA.Outputs, TxOutput{ Amount: 2, Recipient: sender.Public(), }) - trB, _ := newTransactionValue(sender, recipient, 4) + trB, _ := newTransactionValue(sender, recipient, 4, 0) trB.Input.Hash = HashSum(trA) trB, _ = trB.TxBody.Sign(sender, crand.Reader) @@ -166,6 +168,7 @@ func newValidChainAndBlock() (*BlockChain, *Block) { Input: TxHashPointer{ BlockNumber: 1, Hash: HashSum(inputTransaction), + Index: 0, }, Outputs: make([]TxOutput, 1), } diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 3688072..a27ded7 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -16,6 +16,7 @@ const ( type TxHashPointer struct { BlockNumber uint32 Hash Hash + Index uint32 } // Marshal converts a TxHashPointer to a byte slice @@ -88,3 +89,23 @@ func (t *Transaction) Marshal() []byte { buf = append(buf, t.Sig.Marshal()...) return buf } + +// InputsEqualOutputs returns true if t.Inputs == other.Outputs, as well +// as the difference between the two (outputs - inputs). +func (t *Transaction) InputsEqualOutputs(other ...*Transaction) (bool, int) { + var inAmount uint64 + for _, otherTransaction := range other { + for _, output := range otherTransaction.Outputs { + inAmount += output.Amount + } + } + + var outAmount uint64 + for _, output := range t.Outputs { + outAmount += output.Amount + } + + diff := int(outAmount) - int(inAmount) + + return diff != 0, diff +} diff --git a/blockchain/validation.go b/blockchain/validation.go new file mode 100644 index 0000000..ee11c98 --- /dev/null +++ b/blockchain/validation.go @@ -0,0 +1,106 @@ +package blockchain + +// ValidTransaction checks whether a transaction is valid, assuming the +import "crypto/ecdsa" + +// TransactionCode is returned from ValidTransaction. +type TransactionCode uint32 + +// BlockCode is returned from ValidBlock. +type BlockCode uint32 + +const ( + // ValidTransaction is returned when transaction is valid. + ValidTransaction TransactionCode = 0 + // NoInputTransaction is returned when transaction has no valid input transaction. + NoInputTransaction TransactionCode = 1 + // Overspend is returned when transaction outputs exceed transaction inputs. + Overspend TransactionCode = 2 + // BadSig is returned when the signature verification fails. + BadSig TransactionCode = 3 + // Respend is returned when inputs have been spent elsewhere in the chain. + Respend TransactionCode = 4 +) + +const ( + // ValidBlock is returned when the block is valid. + ValidBlock BlockCode = 0 + // BadTransaction is returned when the block contains an invalid + // transaction. + BadTransaction BlockCode = 1 + // BadBlockNumber is returned when block number is not one greater than + // previous block. + BadBlockNumber BlockCode = 2 + // BadHash is returned when the block contains incorrect hash. + BadHash BlockCode = 3 + // DoubleSpend is returned when two transactions in the block share inputs, + // but outputs > inputs. + DoubleSpend BlockCode = 4 +) + +// ValidTransaction tests whether a transaction valid. +func (bc *BlockChain) ValidTransaction(t *Transaction) (bool, TransactionCode) { + + // Find the transaction input in the chain (by hash) + var input *Transaction + input = bc.GetInputTransaction(t) + if input == nil || HashSum(input) != t.Input.Hash { + return false, NoInputTransaction + } + + // Check that output to sender in input is equal to outputs in t + if _, diff := input.InputsEqualOutputs(t); diff != 0 { + return false, Overspend + } + + // Verify signature of t + hash := HashSum(t.TxBody) + if !ecdsa.Verify(t.Sender.Key(), hash.Marshal(), t.Sig.R, t.Sig.S) { + return false, BadSig + } + + // Test if identical transaction already exists in chain. + endChain := uint32(len(bc.Blocks)) + for i := t.Input.BlockNumber; i < endChain; i++ { + if exists, _ := bc.Blocks[i].ContainsTransaction(t); exists { + return false, Respend + } + } + + return true, ValidTransaction +} + +// ValidBlock checks whether a block is valid. +func (bc *BlockChain) ValidBlock(b *Block) (bool, BlockCode) { + // Check that block number is one greater than last block + lastBlock := bc.Blocks[b.BlockNumber-1] + if lastBlock.BlockNumber != b.BlockNumber-1 { + return false, BadBlockNumber + } + + // Verify every Transaction in the block. + for _, t := range b.Transactions { + if valid, _ := bc.ValidTransaction(t); !valid { + return false, BadTransaction + } + } + + // Check that hash of last block is correct + if HashSum(lastBlock) != b.LastBlock { + return false, BadHash + } + + // Check for multiple transactions referencing same input transaction. + for i, trA := range b.Transactions { + for j, trB := range b.Transactions { + if (i != j) && (trA.Input.Hash == trB.Input.Hash) { + inputTr := bc.GetInputTransaction(trA) + if _, diff := inputTr.InputsEqualOutputs(trA, trB); diff < 0 { + return false, DoubleSpend + } + } + } + } + + return true, ValidBlock +} diff --git a/blockchain/validation_test.go b/blockchain/validation_test.go new file mode 100644 index 0000000..0c9e5c3 --- /dev/null +++ b/blockchain/validation_test.go @@ -0,0 +1,157 @@ +package blockchain + +import ( + crand "crypto/rand" + "fmt" + "testing" +) + +func TestValidTransactionNoInputTransaction(t *testing.T) { + tr, _ := newTransactionValue(newWallet(), newWallet(), 1, 0) + bc, _ := newValidBlockChainFixture() + + valid, code := bc.ValidTransaction(tr) + + if valid { + t.Fail() + } + if code != NoInputTransaction { + t.Fail() + } +} + +func TestValidTransactionOverspend(t *testing.T) { + // 2 + 2 = 5 ? + bc, _ := newValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[0] + tr.Outputs[0].Amount = 5 + + valid, code := bc.ValidTransaction(tr) + + if valid { + t.Fail() + } + if code != Overspend { + fmt.Println(code) + t.Fail() + } +} + +func TestValidTransactionSignatureFail(t *testing.T) { + bc, _ := newValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[0] + + fakeSender := newWallet() + tr, _ = tr.TxBody.Sign(fakeSender, crand.Reader) + bc.Blocks[1].Transactions[0] = tr + + valid, code := bc.ValidTransaction(tr) + if valid { + t.Fail() + } + if code != BadSig { + t.Fail() + } +} + +func TestValidTransactionPass(t *testing.T) { + bc, b := newValidChainAndBlock() + tr := b.Transactions[0] + + valid, code := bc.ValidTransaction(tr) + + if !valid { + t.Fail() + } + if code != ValidTransaction { + t.Fail() + } +} + +func TestTransactionRespend(t *testing.T) { + bc, _ := newValidBlockChainFixture() + trC := bc.Blocks[1].Transactions[0] + b := newOutputBlock([]*Transaction{trC}, bc.Blocks[1]) + bc.AppendBlock(b, newWallet().Public()) + + valid, code := bc.ValidTransaction(trC) + + if valid { + t.Fail() + } + if code != Respend { + t.Fail() + } +} + +func TestValidBlockBadTransactoion(t *testing.T) { + bc, _ := newValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[0] + tr.Outputs[0].Amount = 5 + + valid, code := bc.ValidBlock(bc.Blocks[1]) + + if valid { + t.Fail() + } + if code != BadTransaction { + t.Fail() + } +} + +func TestValidBlocBadBlockNumber(t *testing.T) { + bc, _ := newValidBlockChainFixture() + bc.Blocks[1].BlockNumber = 2 + + valid, code := bc.ValidBlock(bc.Blocks[1]) + + if valid { + t.Fail() + } + if code != BadBlockNumber { + t.Fail() + } +} + +func TestValidBlockBadHash(t *testing.T) { + bc, b := newValidChainAndBlock() + b.BlockHeader.LastBlock = newHash() + + valid, code := bc.ValidBlock(b) + + if valid { + t.Fail() + } + if code != BadHash { + t.Fail() + } +} + +func TestValidBlock(t *testing.T) { + bc, b := newValidChainAndBlock() + + valid, code := bc.ValidBlock(b) + + if !valid { + t.Fail() + } + if code != ValidBlock { + t.Fail() + } +} + +func TestBlockDoubleSpend(t *testing.T) { + // block should fail to be valid if there exists two transactions + // referencing the same input, but output > input (double spend attack) + bc, b := newValidChainAndBlock() + b.Transactions = append(b.Transactions, b.Transactions[0]) + + valid, code := bc.ValidBlock(b) + + if valid { + t.Fail() + } + if code != DoubleSpend { + t.Fail() + } +}