From 70c04fa8ce91d9dcd9df446c9bd247e20f138bbe Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 14:18:12 +0700 Subject: [PATCH 01/33] Implement address generation and wallets --- address.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ base58.go | 35 +++++++++++++++++++++++++++++ cli.go | 15 +++++++++++++ utils.go | 7 ++++++ 4 files changed, 123 insertions(+) create mode 100644 address.go create mode 100644 base58.go diff --git a/address.go b/address.go new file mode 100644 index 0000000..8c26e3a --- /dev/null +++ b/address.go @@ -0,0 +1,66 @@ +package main + +import ( + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "log" + + "golang.org/x/crypto/ripemd160" +) + +const version = byte(0x00) + +// Wallet ... +type Wallet struct { + PrivateKey []byte + PublicKey []byte +} + +// GetAddress returns wallet address +func (w Wallet) GetAddress() []byte { + publicSHA256 := sha256.Sum256(w.PublicKey) + + RIPEMD160Hasher := ripemd160.New() + _, err := RIPEMD160Hasher.Write(publicSHA256[:]) + if err != nil { + log.Panic(err) + } + publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) + + versionedPayload := append([]byte{version}, publicRIPEMD160...) + checksum := checksum(versionedPayload) + + fullPayload := append(versionedPayload, checksum...) + address := Base58Encode(fullPayload) + + return address +} + +// NewWallet ... +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet +} + +func newKeyPair() ([]byte, []byte) { + curve := elliptic.P256() + private, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + + public := append(x.Bytes(), y.Bytes()...) + + return private, public +} + +// Checksum ... +func checksum(payload []byte) []byte { + firstSHA := sha256.Sum256(payload) + secondSHA := sha256.Sum256(firstSHA[:]) + + return secondSHA[:4] +} diff --git a/base58.go b/base58.go new file mode 100644 index 0000000..b9ccdce --- /dev/null +++ b/base58.go @@ -0,0 +1,35 @@ +package main + +import ( + "math/big" +) + +var alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + +// Base58Encode encodes a byte array to Base58 +func Base58Encode(input []byte) []byte { + var result []byte + + x := big.NewInt(0) + x.SetBytes(input) + + base := big.NewInt(int64(len(alphabet))) + zero := big.NewInt(0) + mod := &big.Int{} + + for x.Cmp(zero) != 0 { + x.DivMod(x, base, mod) + result = append(result, alphabet[mod.Int64()]) + } + + ReverseBytes(result) + for c := range input { + if c == 0x00 { + result = append([]byte{alphabet[0]}, result...) + } else { + break + } + } + + return result +} diff --git a/cli.go b/cli.go index 1270c6e..6476822 100644 --- a/cli.go +++ b/cli.go @@ -17,6 +17,11 @@ func (cli *CLI) createBlockchain(address string) { fmt.Println("Done!") } +func (cli *CLI) createWallet() { + wallet := NewWallet() + fmt.Printf("Your address: %s\n", wallet.GetAddress()) +} + func (cli *CLI) getBalance(address string) { bc := NewBlockchain(address) defer bc.db.Close() @@ -83,6 +88,7 @@ func (cli *CLI) Run() { getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) + createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) @@ -103,6 +109,11 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "createwallet": + err := createWalletCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -134,6 +145,10 @@ func (cli *CLI) Run() { cli.createBlockchain(*createBlockchainAddress) } + if createWalletCmd.Parsed() { + cli.createWallet() + } + if printChainCmd.Parsed() { cli.printChain() } diff --git a/utils.go b/utils.go index 7f5e727..aecf919 100644 --- a/utils.go +++ b/utils.go @@ -16,3 +16,10 @@ func IntToHex(num int64) []byte { return buff.Bytes() } + +// ReverseBytes reverses a byte array +func ReverseBytes(data []byte) { + for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + data[i], data[j] = data[j], data[i] + } +} From 24b19381d2ff9323917cf45373f7ef7ec1cf6fcd Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:33:17 +0700 Subject: [PATCH 02/33] Rename address.go to wallet.go --- address.go => wallet.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename address.go => wallet.go (91%) diff --git a/address.go b/wallet.go similarity index 91% rename from address.go rename to wallet.go index 8c26e3a..e9c3a1b 100644 --- a/address.go +++ b/wallet.go @@ -37,7 +37,12 @@ func (w Wallet) GetAddress() []byte { return address } -// NewWallet ... +// SaveToFile saves the wallet to a file +func (w Wallet) SaveToFile() { + +} + +// NewWallet creates and returns a Wallet func NewWallet() *Wallet { private, public := newKeyPair() wallet := Wallet{private, public} From 8d7f9452515556060be764ce4d99a66c54e0f85e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:42:38 +0700 Subject: [PATCH 03/33] Save wallet to a file --- .gitignore | 1 + cli.go | 1 + wallet.go | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/.gitignore b/.gitignore index 98e6ef6..570133a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.db +wallet.dat diff --git a/cli.go b/cli.go index 6476822..f32e343 100644 --- a/cli.go +++ b/cli.go @@ -20,6 +20,7 @@ func (cli *CLI) createBlockchain(address string) { func (cli *CLI) createWallet() { wallet := NewWallet() fmt.Printf("Your address: %s\n", wallet.GetAddress()) + wallet.SaveToFile() } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index e9c3a1b..6752f62 100644 --- a/wallet.go +++ b/wallet.go @@ -1,15 +1,19 @@ package main import ( + "bytes" "crypto/elliptic" "crypto/rand" "crypto/sha256" + "encoding/gob" + "io/ioutil" "log" "golang.org/x/crypto/ripemd160" ) const version = byte(0x00) +const walletFile = "wallet.dat" // Wallet ... type Wallet struct { @@ -39,7 +43,18 @@ func (w Wallet) GetAddress() []byte { // SaveToFile saves the wallet to a file func (w Wallet) SaveToFile() { + var content bytes.Buffer + encoder := gob.NewEncoder(&content) + err := encoder.Encode(w) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } } // NewWallet creates and returns a Wallet From 5a1e6f7e47eec498fefa66e9dcd8b3eab255c705 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 16:46:55 +0700 Subject: [PATCH 04/33] Don't create a wallet when wallet.dat already exists --- cli.go | 11 ++++++++--- wallet.go | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cli.go b/cli.go index f32e343..8688056 100644 --- a/cli.go +++ b/cli.go @@ -18,9 +18,14 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallet := NewWallet() - fmt.Printf("Your address: %s\n", wallet.GetAddress()) - wallet.SaveToFile() + wallet, err := NewWallet() + if err == nil { + fmt.Printf("Your address: %s\n", wallet.GetAddress()) + wallet.SaveToFile() + } else { + fmt.Println(err.Error()) + os.Exit(1) + } } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index 6752f62..0bb5ee5 100644 --- a/wallet.go +++ b/wallet.go @@ -6,8 +6,10 @@ import ( "crypto/rand" "crypto/sha256" "encoding/gob" + "errors" "io/ioutil" "log" + "os" "golang.org/x/crypto/ripemd160" ) @@ -58,11 +60,14 @@ func (w Wallet) SaveToFile() { } // NewWallet creates and returns a Wallet -func NewWallet() *Wallet { +func NewWallet() (*Wallet, error) { + if _, err := os.Stat(walletFile); !os.IsNotExist(err) { + return nil, errors.New("Wallet already exists") + } private, public := newKeyPair() wallet := Wallet{private, public} - return &wallet + return &wallet, nil } func newKeyPair() ([]byte, []byte) { From caf71744f585b31210737fa27b2cb67f159c9138 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 17:05:57 +0700 Subject: [PATCH 05/33] Use crypto/ecdsa to generate ECDSA key pair --- wallet.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/wallet.go b/wallet.go index 0bb5ee5..338edbf 100644 --- a/wallet.go +++ b/wallet.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -19,13 +20,14 @@ const walletFile = "wallet.dat" // Wallet ... type Wallet struct { - PrivateKey []byte - PublicKey []byte + PrivateKey ecdsa.PrivateKey + PublicKey ecdsa.PublicKey } // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - publicSHA256 := sha256.Sum256(w.PublicKey) + public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) + publicSHA256 := sha256.Sum256(public) RIPEMD160Hasher := ripemd160.New() _, err := RIPEMD160Hasher.Write(publicSHA256[:]) @@ -47,6 +49,7 @@ func (w Wallet) GetAddress() []byte { func (w Wallet) SaveToFile() { var content bytes.Buffer + gob.Register(w.PrivateKey.Curve) encoder := gob.NewEncoder(&content) err := encoder.Encode(w) if err != nil { @@ -70,16 +73,14 @@ func NewWallet() (*Wallet, error) { return &wallet, nil } -func newKeyPair() ([]byte, []byte) { +func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { curve := elliptic.P256() - private, x, y, err := elliptic.GenerateKey(curve, rand.Reader) + private, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { log.Panic(err) } - public := append(x.Bytes(), y.Bytes()...) - - return private, public + return *private, private.PublicKey } // Checksum ... From 4805ce1bdb27914159903ead330f812446b235b5 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Thu, 7 Sep 2017 20:47:16 +0700 Subject: [PATCH 06/33] Implement Base58Decode --- base58.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/base58.go b/base58.go index b9ccdce..bdeccc2 100644 --- a/base58.go +++ b/base58.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "math/big" ) @@ -33,3 +34,27 @@ func Base58Encode(input []byte) []byte { return result } + +// Base58Decode decodes Base58 data +func Base58Decode(input []byte) []byte { + result := big.NewInt(0) + zeroBytes := 0 + + for c := range input { + if c == 0x00 { + zeroBytes++ + } + } + + address := input[zeroBytes:] + for _, b := range address { + charIndex := bytes.IndexByte(alphabet, b) + result.Mul(result, big.NewInt(58)) + result.Add(result, big.NewInt(int64(charIndex))) + } + + raw := result.Bytes() + raw = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), raw...) + + return raw +} From 5b0e4ecc193ddd05ec98d1dd59c80c2195599e99 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:46:06 +0700 Subject: [PATCH 07/33] Allow to create multiple wallets --- cli.go | 13 +++---- wallet.go | 100 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/cli.go b/cli.go index 8688056..e3bb29e 100644 --- a/cli.go +++ b/cli.go @@ -18,14 +18,11 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallet, err := NewWallet() - if err == nil { - fmt.Printf("Your address: %s\n", wallet.GetAddress()) - wallet.SaveToFile() - } else { - fmt.Println(err.Error()) - os.Exit(1) - } + wallets := NewWallets() + address := wallets.CreateWallet() + wallets.SaveToFile() + + fmt.Printf("Your new address: %s\n", address) } func (cli *CLI) getBalance(address string) { diff --git a/wallet.go b/wallet.go index 338edbf..ace99fc 100644 --- a/wallet.go +++ b/wallet.go @@ -7,7 +7,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/gob" - "errors" + "fmt" "io/ioutil" "log" "os" @@ -18,12 +18,17 @@ import ( const version = byte(0x00) const walletFile = "wallet.dat" -// Wallet ... +// Wallet stores private and public keys type Wallet struct { PrivateKey ecdsa.PrivateKey PublicKey ecdsa.PublicKey } +// Wallets stores a collection of wallets +type Wallets struct { + Wallets map[string]*Wallet +} + // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) @@ -45,32 +50,12 @@ func (w Wallet) GetAddress() []byte { return address } -// SaveToFile saves the wallet to a file -func (w Wallet) SaveToFile() { - var content bytes.Buffer - - gob.Register(w.PrivateKey.Curve) - encoder := gob.NewEncoder(&content) - err := encoder.Encode(w) - if err != nil { - log.Panic(err) - } - - err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) - if err != nil { - log.Panic(err) - } -} - // NewWallet creates and returns a Wallet -func NewWallet() (*Wallet, error) { - if _, err := os.Stat(walletFile); !os.IsNotExist(err) { - return nil, errors.New("Wallet already exists") - } +func NewWallet() *Wallet { private, public := newKeyPair() wallet := Wallet{private, public} - return &wallet, nil + return &wallet } func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { @@ -83,6 +68,73 @@ func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { return *private, private.PublicKey } +// CreateWallet adds a Wallet to Wallets +func (ws *Wallets) CreateWallet() string { + wallet := NewWallet() + address := fmt.Sprintf("%s", wallet.GetAddress()) + + ws.Wallets[address] = wallet + + return address +} + +// SaveToFile saves wallets to a file +func (ws Wallets) SaveToFile() { + var content bytes.Buffer + + gob.Register(elliptic.P256()) + + encoder := gob.NewEncoder(&content) + err := encoder.Encode(ws) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } +} + +// LoadFromFile loads wallets from the file +func (ws *Wallets) LoadFromFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + log.Panic(err) + } + + var wallets Wallets + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) + if err != nil { + log.Panic(err) + } + + ws.Wallets = wallets.Wallets + + return nil +} + +// NewWallets ... +func NewWallets() *Wallets { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFromFile() + if err != nil { + fmt.Println("Wallets file doesn't exist") + // wallets.CreateWallet() + // wallets.SaveToFile() + } + + return &wallets +} + // Checksum ... func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) From deb7e2ce030fed640c7a504d338c36d0c235a6fc Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:51:44 +0700 Subject: [PATCH 08/33] Implement 'listaddresses' CLI command --- cli.go | 19 +++++++++++++++++++ wallet.go | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/cli.go b/cli.go index e3bb29e..5b6e24f 100644 --- a/cli.go +++ b/cli.go @@ -39,6 +39,15 @@ func (cli *CLI) getBalance(address string) { fmt.Printf("Balance of '%s': %d\n", address, balance) } +func (cli *CLI) listAddresses() { + wallets := NewWallets() + addresses := wallets.GetAddresses() + + for _, address := range addresses { + fmt.Println(address) + } +} + func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") @@ -92,6 +101,7 @@ func (cli *CLI) Run() { getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) + listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) @@ -117,6 +127,11 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "listaddresses": + err := listAddressesCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } case "printchain": err := printChainCmd.Parse(os.Args[2:]) if err != nil { @@ -152,6 +167,10 @@ func (cli *CLI) Run() { cli.createWallet() } + if listAddressesCmd.Parsed() { + cli.listAddresses() + } + if printChainCmd.Parsed() { cli.printChain() } diff --git a/wallet.go b/wallet.go index ace99fc..edadfbe 100644 --- a/wallet.go +++ b/wallet.go @@ -120,6 +120,17 @@ func (ws *Wallets) LoadFromFile() error { return nil } +// GetAddresses returns an array of addresses stored in the wallet file +func (ws *Wallets) GetAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + // NewWallets ... func NewWallets() *Wallets { wallets := Wallets{} From 75105982ae6d983755e700a2ea148b2fb7132b4a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:53:26 +0700 Subject: [PATCH 09/33] Update usage --- cli.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cli.go b/cli.go index 5b6e24f..44de517 100644 --- a/cli.go +++ b/cli.go @@ -48,23 +48,7 @@ func (cli *CLI) listAddresses() { } } -func (cli *CLI) printUsage() { - fmt.Println("Usage:") - fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") - fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") - fmt.Println(" printchain - Print all the blocks of the blockchain") - fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") -} - -func (cli *CLI) validateArgs() { - if len(os.Args) < 2 { - cli.printUsage() - os.Exit(1) - } -} - func (cli *CLI) printChain() { - // TODO: Fix this bc := NewBlockchain("") defer bc.db.Close() @@ -94,6 +78,23 @@ func (cli *CLI) send(from, to string, amount int) { fmt.Println("Success!") } +func (cli *CLI) printUsage() { + fmt.Println("Usage:") + fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") + fmt.Println(" createwallet - Generates a new key-pair and saves it into the wallet file") + fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") + fmt.Println(" listaddresses - Lists all addresses from the wallet file") + fmt.Println(" printchain - Print all the blocks of the blockchain") + fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") +} + +func (cli *CLI) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + os.Exit(1) + } +} + // Run parses command line arguments and processes commands func (cli *CLI) Run() { cli.validateArgs() From 2b0619e103469d8294a4e8d391cfb7cf0bfadc74 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 09:56:04 +0700 Subject: [PATCH 10/33] Improve NewWallets and fix comments --- cli.go | 7 +++++-- wallet.go | 13 ++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cli.go b/cli.go index 44de517..ca15065 100644 --- a/cli.go +++ b/cli.go @@ -18,7 +18,7 @@ func (cli *CLI) createBlockchain(address string) { } func (cli *CLI) createWallet() { - wallets := NewWallets() + wallets, _ := NewWallets() address := wallets.CreateWallet() wallets.SaveToFile() @@ -40,7 +40,10 @@ func (cli *CLI) getBalance(address string) { } func (cli *CLI) listAddresses() { - wallets := NewWallets() + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } addresses := wallets.GetAddresses() for _, address := range addresses { diff --git a/wallet.go b/wallet.go index edadfbe..1c2bd3e 100644 --- a/wallet.go +++ b/wallet.go @@ -131,22 +131,17 @@ func (ws *Wallets) GetAddresses() []string { return addresses } -// NewWallets ... -func NewWallets() *Wallets { +// NewWallets creates Wallets and fills it from a file if it exists +func NewWallets() (*Wallets, error) { wallets := Wallets{} wallets.Wallets = make(map[string]*Wallet) err := wallets.LoadFromFile() - if err != nil { - fmt.Println("Wallets file doesn't exist") - // wallets.CreateWallet() - // wallets.SaveToFile() - } - return &wallets + return &wallets, err } -// Checksum ... +// Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) From 6b400109e90662ca342f1b92bb206c332f656772 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:06:19 +0700 Subject: [PATCH 11/33] In the 'printchain' command, print transactions as well --- cli.go | 3 +++ transaction.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/cli.go b/cli.go index ca15065..1a0010d 100644 --- a/cli.go +++ b/cli.go @@ -64,6 +64,9 @@ func (cli *CLI) printChain() { fmt.Printf("Hash: %x\n", block.Hash) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } fmt.Println() if len(block.PrevBlockHash) == 0 { diff --git a/transaction.go b/transaction.go index fe82a7b..336a9e4 100644 --- a/transaction.go +++ b/transaction.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "fmt" "log" + "strings" ) const subsidy = 10 @@ -23,6 +24,28 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } +// String returns a human-readable representation of a 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(" Script: %s", input.ScriptSig)) + } + + 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(" Script: %s", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} + // SetID sets ID of a transaction func (tx Transaction) SetID() { var encoded bytes.Buffer From e6eed1105f778fde024ea00d8f508ac49b1f8a8b Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:09:04 +0700 Subject: [PATCH 12/33] Fix Transaction.SetID --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 336a9e4..bab3562 100644 --- a/transaction.go +++ b/transaction.go @@ -47,7 +47,7 @@ func (tx Transaction) String() string { } // SetID sets ID of a transaction -func (tx Transaction) SetID() { +func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte From 484d0bbae2a79fc977ad68d9c580294d976243d4 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:27:28 +0700 Subject: [PATCH 13/33] Extract public key hashing into a separate function --- wallet.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/wallet.go b/wallet.go index 1c2bd3e..27b5d71 100644 --- a/wallet.go +++ b/wallet.go @@ -31,17 +31,10 @@ type Wallets struct { // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - public := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) - publicSHA256 := sha256.Sum256(public) + pubKey := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) + pubKeyHash := HashPubKey(pubKey) - RIPEMD160Hasher := ripemd160.New() - _, err := RIPEMD160Hasher.Write(publicSHA256[:]) - if err != nil { - log.Panic(err) - } - publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) - - versionedPayload := append([]byte{version}, publicRIPEMD160...) + versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) fullPayload := append(versionedPayload, checksum...) @@ -141,6 +134,20 @@ func NewWallets() (*Wallets, error) { return &wallets, err } +// HashPubKey hashes public key +func HashPubKey(pubKey []byte) []byte { + publicSHA256 := sha256.Sum256(pubKey) + + RIPEMD160Hasher := ripemd160.New() + _, err := RIPEMD160Hasher.Write(publicSHA256[:]) + if err != nil { + log.Panic(err) + } + publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) + + return publicRIPEMD160 +} + // Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) From cb1776224ef97ebcd330c084bb8f530e0e139c2e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 10:41:03 +0700 Subject: [PATCH 14/33] Store public key as a byte array --- wallet.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wallet.go b/wallet.go index 27b5d71..ca28c4f 100644 --- a/wallet.go +++ b/wallet.go @@ -21,7 +21,7 @@ const walletFile = "wallet.dat" // Wallet stores private and public keys type Wallet struct { PrivateKey ecdsa.PrivateKey - PublicKey ecdsa.PublicKey + PublicKey []byte } // Wallets stores a collection of wallets @@ -31,8 +31,7 @@ type Wallets struct { // GetAddress returns wallet address func (w Wallet) GetAddress() []byte { - pubKey := append(w.PublicKey.X.Bytes(), w.PublicKey.Y.Bytes()...) - pubKeyHash := HashPubKey(pubKey) + pubKeyHash := HashPubKey(w.PublicKey) versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) @@ -51,14 +50,15 @@ func NewWallet() *Wallet { return &wallet } -func newKeyPair() (ecdsa.PrivateKey, ecdsa.PublicKey) { +func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { log.Panic(err) } + pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) - return *private, private.PublicKey + return *private, pubKey } // CreateWallet adds a Wallet to Wallets @@ -124,6 +124,11 @@ func (ws *Wallets) GetAddresses() []string { return addresses } +// GetWallet returns a Wallet by its address +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + // NewWallets creates Wallets and fills it from a file if it exists func NewWallets() (*Wallets, error) { wallets := Wallets{} From 92be537fcdfe4a2464209fdc2aa073658189ee22 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 11:31:34 +0700 Subject: [PATCH 15/33] Use public key in transactions --- blockchain.go | 18 ++++++++-------- cli.go | 4 +++- transaction.go | 58 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/blockchain.go b/blockchain.go index b9cfb42..d644fe3 100644 --- a/blockchain.go +++ b/blockchain.go @@ -61,7 +61,7 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { } // FindUnspentTransactions returns a list of transactions containing unspent outputs -func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { +func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() @@ -83,14 +83,14 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { } } - if out.CanBeUnlockedWith(address) { + if out.Unlock(pubKeyHash) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { - if in.CanUnlockOutputWith(address) { + if in.UnlocksOutputWith(pubKeyHash) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } @@ -107,13 +107,13 @@ func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { } // FindUTXO finds and returns all unspent transaction outputs -func (bc *Blockchain) FindUTXO(address string) []TXOutput { +func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { var UTXOs []TXOutput - unspentTransactions := bc.FindUnspentTransactions(address) + unspentTransactions := bc.FindUnspentTransactions(pubKeyHash) for _, tx := range unspentTransactions { for _, out := range tx.Vout { - if out.CanBeUnlockedWith(address) { + if out.Unlock(pubKeyHash) { UTXOs = append(UTXOs, out) } } @@ -123,9 +123,9 @@ func (bc *Blockchain) FindUTXO(address string) []TXOutput { } // FindSpendableOutputs finds and returns unspent outputs to reference in inputs -func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { +func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) - unspentTXs := bc.FindUnspentTransactions(address) + unspentTXs := bc.FindUnspentTransactions(pubKeyHash) accumulated := 0 Work: @@ -133,7 +133,7 @@ Work: txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { - if out.CanBeUnlockedWith(address) && accumulated < amount { + if out.Unlock(pubKeyHash) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) diff --git a/cli.go b/cli.go index 1a0010d..2b01163 100644 --- a/cli.go +++ b/cli.go @@ -30,7 +30,9 @@ func (cli *CLI) getBalance(address string) { defer bc.db.Close() balance := 0 - UTXOs := bc.FindUTXO(address) + pubKeyHash := Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := bc.FindUTXO(pubKeyHash) for _, out := range UTXOs { balance += out.Value diff --git a/transaction.go b/transaction.go index bab3562..9939358 100644 --- a/transaction.go +++ b/transaction.go @@ -3,6 +3,7 @@ package main import ( "bytes" "crypto/sha256" + "encoding/gob" "encoding/hex" "fmt" @@ -34,13 +35,13 @@ func (tx Transaction) String() string { 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(" Script: %s", input.ScriptSig)) + lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) } 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(" Script: %s", output.ScriptPubKey)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) } return strings.Join(lines, "\n") @@ -64,23 +65,40 @@ func (tx *Transaction) SetID() { type TXInput struct { Txid []byte Vout int - ScriptSig string + ScriptSig []byte } // TXOutput represents a transaction output type TXOutput struct { Value int - ScriptPubKey string + ScriptPubKey []byte } -// CanUnlockOutputWith checks whether the address initiated the transaction -func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { - return in.ScriptSig == unlockingData +// UnlocksOutputWith checks whether the address initiated the transaction +func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { + lockingHash := HashPubKey(in.ScriptSig) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 } -// CanBeUnlockedWith checks if the output can be unlocked with the provided data -func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { - return out.ScriptPubKey == unlockingData +// Lock signs the output +func (out *TXOutput) Lock(address []byte) { + pubKeyHash := Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.ScriptPubKey = pubKeyHash +} + +// Unlock checks if the output can be used by the owner of the pubkey +func (out *TXOutput) Unlock(pubKeyHash []byte) bool { + return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 +} + +// NewTXOutput create a new TXOutput +func NewTXOutput(value int, address string) *TXOutput { + txo := &TXOutput{value, nil} + txo.Lock([]byte(address)) + + return txo } // NewCoinbaseTX creates a new coinbase transaction @@ -89,9 +107,9 @@ func NewCoinbaseTX(to, data string) *Transaction { data = fmt.Sprintf("Reward to '%s'", to) } - txin := TXInput{[]byte{}, -1, data} - txout := TXOutput{subsidy, to} - tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} + txin := TXInput{[]byte{}, -1, []byte(data)} + txout := NewTXOutput(subsidy, to) + tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.SetID() return &tx @@ -102,7 +120,13 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio var inputs []TXInput var outputs []TXOutput - acc, validOutputs := bc.FindSpendableOutputs(from, amount) + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } + wallet := wallets.GetWallet(from) + pubKeyHash := HashPubKey(wallet.PublicKey) + acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount) if acc < amount { log.Panic("ERROR: Not enough funds") @@ -116,15 +140,15 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } for _, out := range outs { - input := TXInput{txID, out, from} + input := TXInput{txID, out, wallet.PublicKey} inputs = append(inputs, input) } } // Build a list of outputs - outputs = append(outputs, TXOutput{amount, to}) + outputs = append(outputs, *NewTXOutput(amount, to)) if acc > amount { - outputs = append(outputs, TXOutput{acc - amount, from}) // a change + outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change } tx := Transaction{nil, inputs, outputs} From 7e8c88867da8e2fb67c4ca21c77a7834d11f579f Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Fri, 8 Sep 2017 21:29:34 +0700 Subject: [PATCH 16/33] Implement Transaction.Sign and Transaction.Verify --- transaction.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/transaction.go b/transaction.go index 9939358..81bbf46 100644 --- a/transaction.go +++ b/transaction.go @@ -2,7 +2,11 @@ package main import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/sha256" + "math/big" "encoding/gob" "encoding/hex" @@ -61,6 +65,98 @@ func (tx *Transaction) SetID() { tx.ID = hash[:] } +// TrimmedCopy creates a trimmed copy of Transaction to be used in signing +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TXInput + var outputs []TXOutput + + for _, vin := range tx.Vin { + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + } + + for _, vout := range tx.Vout { + outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + +// Sign signs each input of a Transaction +func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { + if tx.IsCoinbase() { + return + } + + for _, vin := range tx.Vin { + if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + log.Panic("ERROR: Previous transaction is not correct") + } + } + + txCopy := tx.TrimmedCopy() + + for inID, vin := range txCopy.Vin { + txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.SetID() + txCopy.Vin[inID].ScriptSig = []byte{} + + r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) + if err != nil { + log.Panic(err) + } + signature := append(r.Bytes(), s.Bytes()...) + + tx.Vin[inID].ScriptSig = append(signature, tx.Vin[inID].ScriptSig...) + } +} + +// Verify verifies signatures of Transaction inputs +func (tx *Transaction) Verify(prevTx *Transaction) bool { + sigLen := 64 + + if tx.IsCoinbase() { + return true + } + + for _, vin := range tx.Vin { + if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + log.Panic("ERROR: Previous transaction is not correct") + } + } + + txCopy := tx.TrimmedCopy() + curve := elliptic.P256() + + for inID, vin := range tx.Vin { + txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.SetID() + txCopy.Vin[inID].ScriptSig = []byte{} + + signature := vin.ScriptSig[:sigLen] + pubKey := vin.ScriptSig[sigLen:] + + r := big.Int{} + s := big.Int{} + r.SetBytes(signature[:(sigLen / 2)]) + s.SetBytes(signature[(sigLen / 2):]) + + x := big.Int{} + y := big.Int{} + keyLen := len(pubKey) + x.SetBytes(pubKey[:(keyLen / 2)]) + y.SetBytes(pubKey[(keyLen / 2):]) + + rawPubKey := ecdsa.PublicKey{curve, &x, &y} + if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { + return false + } + } + + return true +} + // TXInput represents a transaction input type TXInput struct { Txid []byte From 2ce04f8f595ad4442a54cbeb7fcd026f657d2934 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 10:34:39 +0700 Subject: [PATCH 17/33] Implement transactions signing and verification --- blockchain.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ transaction.go | 12 ++++++--- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/blockchain.go b/blockchain.go index d644fe3..48709ae 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,7 +1,10 @@ package main import ( + "bytes" + "crypto/ecdsa" "encoding/hex" + "errors" "fmt" "log" "os" @@ -29,6 +32,12 @@ type BlockchainIterator struct { func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte + for _, tx := range transactions { + if bc.VerifyTransaction(tx) != true { + log.Panic("ERROR: Invalid transaction") + } + } + err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) @@ -60,6 +69,63 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { }) } +// FindTransaction finds a transaction by its ID +func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { + bci := bc.Iterator() + + for { + block := bci.Next() + + for _, tx := range block.Transactions { + if bytes.Compare(tx.ID, ID) == 0 { + return *tx, nil + } + } + + if len(block.PrevBlockHash) == 0 { + break + } + } + + return Transaction{}, errors.New("Transaction is not found") +} + +// SignTransaction signs a Transaction +func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + tx.Sign(privKey, prevTXs) +} + +// VerifyTransaction verifies transaction +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + for _, tx := range prevTXs { + fmt.Println(tx) + } + // fmt.Println() + // fmt.Println(tx) + + return tx.Verify(prevTXs) +} + // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction diff --git a/transaction.go b/transaction.go index 81bbf46..b7463f1 100644 --- a/transaction.go +++ b/transaction.go @@ -36,6 +36,7 @@ func (tx Transaction) String() 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)) @@ -84,13 +85,13 @@ func (tx *Transaction) TrimmedCopy() Transaction { } // Sign signs each input of a Transaction -func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { +func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { return } for _, vin := range tx.Vin { - if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") } } @@ -98,6 +99,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { txCopy := tx.TrimmedCopy() for inID, vin := range txCopy.Vin { + prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey txCopy.SetID() txCopy.Vin[inID].ScriptSig = []byte{} @@ -113,7 +115,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTx *Transaction) { } // Verify verifies signatures of Transaction inputs -func (tx *Transaction) Verify(prevTx *Transaction) bool { +func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { sigLen := 64 if tx.IsCoinbase() { @@ -121,7 +123,7 @@ func (tx *Transaction) Verify(prevTx *Transaction) bool { } for _, vin := range tx.Vin { - if bytes.Compare(vin.Txid, prevTx.ID) != 0 { + if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") } } @@ -130,6 +132,7 @@ func (tx *Transaction) Verify(prevTx *Transaction) bool { curve := elliptic.P256() for inID, vin := range tx.Vin { + prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey txCopy.SetID() txCopy.Vin[inID].ScriptSig = []byte{} @@ -249,6 +252,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio tx := Transaction{nil, inputs, outputs} tx.SetID() + bc.SignTransaction(&tx, wallet.PrivateKey) return &tx } From fc0c819c437cd369b3cae3564c90c2e253676407 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 10:54:58 +0700 Subject: [PATCH 18/33] Extract some structs into separate files --- block.go | 52 +++---- blockchain.go | 333 +++++++++++++++++++---------------------- blockchain_iterator.go | 34 +++++ transaction.go | 122 +++++---------- transaction_input.go | 17 +++ transaction_output.go | 29 ++++ wallet.go | 100 +------------ wallets.go | 94 ++++++++++++ 8 files changed, 400 insertions(+), 381 deletions(-) create mode 100644 blockchain_iterator.go create mode 100644 transaction_input.go create mode 100644 transaction_output.go create mode 100644 wallets.go diff --git a/block.go b/block.go index 04d96cc..3baaa46 100644 --- a/block.go +++ b/block.go @@ -17,32 +17,6 @@ type Block struct { Nonce int } -// Serialize serializes the block -func (b *Block) Serialize() []byte { - var result bytes.Buffer - encoder := gob.NewEncoder(&result) - - err := encoder.Encode(b) - if err != nil { - log.Panic(err) - } - - return result.Bytes() -} - -// 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.ID) - } - txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) - - return txHash[:] -} - // NewBlock creates and returns Block func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} @@ -72,3 +46,29 @@ func DeserializeBlock(d []byte) *Block { return &block } + +// 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.ID) + } + txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) + + return txHash[:] +} + +// Serialize serializes the block +func (b *Block) Serialize() []byte { + var result bytes.Buffer + encoder := gob.NewEncoder(&result) + + err := encoder.Encode(b) + if err != nil { + log.Panic(err) + } + + return result.Bytes() +} diff --git a/blockchain.go b/blockchain.go index 48709ae..ef4d221 100644 --- a/blockchain.go +++ b/blockchain.go @@ -22,25 +22,38 @@ type Blockchain struct { db *bolt.DB } -// BlockchainIterator is used to iterate over blockchain blocks -type BlockchainIterator struct { - currentHash []byte - db *bolt.DB -} - -// MineBlock mines a new block with the provided transactions -func (bc *Blockchain) MineBlock(transactions []*Transaction) { - var lastHash []byte - - for _, tx := range transactions { - if bc.VerifyTransaction(tx) != true { - log.Panic("ERROR: Invalid transaction") - } +// CreateBlockchain creates a new blockchain DB +func CreateBlockchain(address string) *Blockchain { + if dbExists() { + fmt.Println("Blockchain already exists.") + os.Exit(1) } - err := bc.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - lastHash = b.Get([]byte("l")) + var tip []byte + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) + genesis := NewGenesisBlock(cbtx) + + b, err := tx.CreateBucket([]byte(blocksBucket)) + if err != nil { + log.Panic(err) + } + + err = b.Put(genesis.Hash, genesis.Serialize()) + if err != nil { + log.Panic(err) + } + + err = b.Put([]byte("l"), genesis.Hash) + if err != nil { + log.Panic(err) + } + tip = genesis.Hash return nil }) @@ -49,24 +62,63 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { log.Panic(err) } - newBlock := NewBlock(transactions, lastHash) + bc := Blockchain{tip, db} - err = bc.db.Update(func(tx *bolt.Tx) error { + return &bc +} + +// NewBlockchain creates a new Blockchain with genesis Block +func NewBlockchain(address string) *Blockchain { + if dbExists() == false { + fmt.Println("No existing blockchain found. Create one first.") + os.Exit(1) + } + + var tip []byte + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) - err := b.Put(newBlock.Hash, newBlock.Serialize()) - if err != nil { - log.Panic(err) - } - - err = b.Put([]byte("l"), newBlock.Hash) - if err != nil { - log.Panic(err) - } - - bc.tip = newBlock.Hash + tip = b.Get([]byte("l")) return nil }) + + if err != nil { + log.Panic(err) + } + + bc := Blockchain{tip, db} + + return &bc +} + +// FindSpendableOutputs finds and returns unspent outputs to reference in inputs +func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { + unspentOutputs := make(map[string][]int) + unspentTXs := bc.FindUnspentTransactions(pubKeyHash) + accumulated := 0 + +Work: + for _, tx := range unspentTXs { + txID := hex.EncodeToString(tx.ID) + + for outIdx, out := range tx.Vout { + if out.Unlock(pubKeyHash) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) + + if accumulated >= amount { + break Work + } + } + } + } + + return accumulated, unspentOutputs } // FindTransaction finds a transaction by its ID @@ -90,42 +142,6 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { return Transaction{}, errors.New("Transaction is not found") } -// SignTransaction signs a Transaction -func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - tx.Sign(privKey, prevTXs) -} - -// VerifyTransaction verifies transaction -func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - for _, tx := range prevTXs { - fmt.Println(tx) - } - // fmt.Println() - // fmt.Println(tx) - - return tx.Verify(prevTXs) -} - // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction @@ -188,29 +204,81 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } -// FindSpendableOutputs finds and returns unspent outputs to reference in inputs -func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { - unspentOutputs := make(map[string][]int) - unspentTXs := bc.FindUnspentTransactions(pubKeyHash) - accumulated := 0 +// MineBlock mines a new block with the provided transactions +func (bc *Blockchain) MineBlock(transactions []*Transaction) { + var lastHash []byte -Work: - for _, tx := range unspentTXs { - txID := hex.EncodeToString(tx.ID) - - for outIdx, out := range tx.Vout { - if out.Unlock(pubKeyHash) && accumulated < amount { - accumulated += out.Value - unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) - - if accumulated >= amount { - break Work - } - } + for _, tx := range transactions { + if bc.VerifyTransaction(tx) != true { + log.Panic("ERROR: Invalid transaction") } } - return accumulated, unspentOutputs + err := bc.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + lastHash = b.Get([]byte("l")) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + newBlock := NewBlock(transactions, lastHash) + + err = bc.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + err := b.Put(newBlock.Hash, newBlock.Serialize()) + if err != nil { + log.Panic(err) + } + + err = b.Put([]byte("l"), newBlock.Hash) + if err != nil { + log.Panic(err) + } + + bc.tip = newBlock.Hash + + return nil + }) +} + +// SignTransaction signs a Transaction +func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + tx.Sign(privKey, prevTXs) +} + +// VerifyTransaction verifies transaction +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + for _, tx := range prevTXs { + fmt.Println(tx) + } + // fmt.Println() + // fmt.Println(tx) + + return tx.Verify(prevTXs) } // Iterator returns a BlockchainIterat @@ -220,27 +288,6 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { return bci } -// Next returns next block starting from the tip -func (i *BlockchainIterator) Next() *Block { - var block *Block - - err := i.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - encodedBlock := b.Get(i.currentHash) - block = DeserializeBlock(encodedBlock) - - return nil - }) - - if err != nil { - log.Panic(err) - } - - i.currentHash = block.PrevBlockHash - - return block -} - func dbExists() bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false @@ -248,77 +295,3 @@ func dbExists() bool { return true } - -// NewBlockchain creates a new Blockchain with genesis Block -func NewBlockchain(address string) *Blockchain { - if dbExists() == false { - fmt.Println("No existing blockchain found. Create one first.") - os.Exit(1) - } - - var tip []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - - err = db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - tip = b.Get([]byte("l")) - - return nil - }) - - if err != nil { - log.Panic(err) - } - - bc := Blockchain{tip, db} - - return &bc -} - -// CreateBlockchain creates a new blockchain DB -func CreateBlockchain(address string) *Blockchain { - if dbExists() { - fmt.Println("Blockchain already exists.") - os.Exit(1) - } - - var tip []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - - err = db.Update(func(tx *bolt.Tx) error { - cbtx := NewCoinbaseTX(address, genesisCoinbaseData) - genesis := NewGenesisBlock(cbtx) - - b, err := tx.CreateBucket([]byte(blocksBucket)) - if err != nil { - log.Panic(err) - } - - err = b.Put(genesis.Hash, genesis.Serialize()) - if err != nil { - log.Panic(err) - } - - err = b.Put([]byte("l"), genesis.Hash) - if err != nil { - log.Panic(err) - } - tip = genesis.Hash - - return nil - }) - - if err != nil { - log.Panic(err) - } - - bc := Blockchain{tip, db} - - return &bc -} diff --git a/blockchain_iterator.go b/blockchain_iterator.go new file mode 100644 index 0000000..305311d --- /dev/null +++ b/blockchain_iterator.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + + "github.com/boltdb/bolt" +) + +// BlockchainIterator is used to iterate over blockchain blocks +type BlockchainIterator struct { + currentHash []byte + db *bolt.DB +} + +// Next returns next block starting from the tip +func (i *BlockchainIterator) Next() *Block { + var block *Block + + err := i.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + encodedBlock := b.Get(i.currentHash) + block = DeserializeBlock(encodedBlock) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + i.currentHash = block.PrevBlockHash + + return block +} diff --git a/transaction.go b/transaction.go index b7463f1..e5176d6 100644 --- a/transaction.go +++ b/transaction.go @@ -29,29 +29,6 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } -// String returns a human-readable representation of a 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(" Script: %x", input.ScriptSig)) - } - - 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(" Script: %x", output.ScriptPubKey)) - } - - return strings.Join(lines, "\n") -} - // SetID sets ID of a transaction func (tx *Transaction) SetID() { var encoded bytes.Buffer @@ -66,24 +43,6 @@ func (tx *Transaction) SetID() { tx.ID = hash[:] } -// TrimmedCopy creates a trimmed copy of Transaction to be used in signing -func (tx *Transaction) TrimmedCopy() Transaction { - var inputs []TXInput - var outputs []TXOutput - - for _, vin := range tx.Vin { - inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) - } - - for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) - } - - txCopy := Transaction{tx.ID, inputs, outputs} - - return txCopy -} - // Sign signs each input of a Transaction func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { @@ -114,6 +73,47 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac } } +// String returns a human-readable representation of a 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(" Script: %x", input.ScriptSig)) + } + + 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(" Script: %x", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} + +// TrimmedCopy creates a trimmed copy of Transaction to be used in signing +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TXInput + var outputs []TXOutput + + for _, vin := range tx.Vin { + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + } + + for _, vout := range tx.Vout { + outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + // Verify verifies signatures of Transaction inputs func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { sigLen := 64 @@ -160,46 +160,6 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { return true } -// TXInput represents a transaction input -type TXInput struct { - Txid []byte - Vout int - ScriptSig []byte -} - -// TXOutput represents a transaction output -type TXOutput struct { - Value int - ScriptPubKey []byte -} - -// UnlocksOutputWith checks whether the address initiated the transaction -func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { - lockingHash := HashPubKey(in.ScriptSig) - - return bytes.Compare(lockingHash, pubKeyHash) == 0 -} - -// Lock signs the output -func (out *TXOutput) Lock(address []byte) { - pubKeyHash := Base58Decode(address) - pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - out.ScriptPubKey = pubKeyHash -} - -// Unlock checks if the output can be used by the owner of the pubkey -func (out *TXOutput) Unlock(pubKeyHash []byte) bool { - return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 -} - -// NewTXOutput create a new TXOutput -func NewTXOutput(value int, address string) *TXOutput { - txo := &TXOutput{value, nil} - txo.Lock([]byte(address)) - - return txo -} - // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { diff --git a/transaction_input.go b/transaction_input.go new file mode 100644 index 0000000..1c57c2b --- /dev/null +++ b/transaction_input.go @@ -0,0 +1,17 @@ +package main + +import "bytes" + +// TXInput represents a transaction input +type TXInput struct { + Txid []byte + Vout int + ScriptSig []byte +} + +// UnlocksOutputWith checks whether the address initiated the transaction +func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { + lockingHash := HashPubKey(in.ScriptSig) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 +} diff --git a/transaction_output.go b/transaction_output.go new file mode 100644 index 0000000..f509dd8 --- /dev/null +++ b/transaction_output.go @@ -0,0 +1,29 @@ +package main + +import "bytes" + +// TXOutput represents a transaction output +type TXOutput struct { + Value int + ScriptPubKey []byte +} + +// Lock signs the output +func (out *TXOutput) Lock(address []byte) { + pubKeyHash := Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.ScriptPubKey = pubKeyHash +} + +// Unlock checks if the output can be used by the owner of the pubkey +func (out *TXOutput) Unlock(pubKeyHash []byte) bool { + return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 +} + +// NewTXOutput create a new TXOutput +func NewTXOutput(value int, address string) *TXOutput { + txo := &TXOutput{value, nil} + txo.Lock([]byte(address)) + + return txo +} diff --git a/wallet.go b/wallet.go index ca28c4f..d527988 100644 --- a/wallet.go +++ b/wallet.go @@ -1,16 +1,11 @@ package main import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" - "encoding/gob" - "fmt" - "io/ioutil" "log" - "os" "golang.org/x/crypto/ripemd160" ) @@ -24,9 +19,12 @@ type Wallet struct { PublicKey []byte } -// Wallets stores a collection of wallets -type Wallets struct { - Wallets map[string]*Wallet +// NewWallet creates and returns a Wallet +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet } // GetAddress returns wallet address @@ -42,14 +40,6 @@ func (w Wallet) GetAddress() []byte { return address } -// NewWallet creates and returns a Wallet -func NewWallet() *Wallet { - private, public := newKeyPair() - wallet := Wallet{private, public} - - return &wallet -} - func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -61,84 +51,6 @@ func newKeyPair() (ecdsa.PrivateKey, []byte) { return *private, pubKey } -// CreateWallet adds a Wallet to Wallets -func (ws *Wallets) CreateWallet() string { - wallet := NewWallet() - address := fmt.Sprintf("%s", wallet.GetAddress()) - - ws.Wallets[address] = wallet - - return address -} - -// SaveToFile saves wallets to a file -func (ws Wallets) SaveToFile() { - var content bytes.Buffer - - gob.Register(elliptic.P256()) - - encoder := gob.NewEncoder(&content) - err := encoder.Encode(ws) - if err != nil { - log.Panic(err) - } - - err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) - if err != nil { - log.Panic(err) - } -} - -// LoadFromFile loads wallets from the file -func (ws *Wallets) LoadFromFile() error { - if _, err := os.Stat(walletFile); os.IsNotExist(err) { - return err - } - - fileContent, err := ioutil.ReadFile(walletFile) - if err != nil { - log.Panic(err) - } - - var wallets Wallets - gob.Register(elliptic.P256()) - decoder := gob.NewDecoder(bytes.NewReader(fileContent)) - err = decoder.Decode(&wallets) - if err != nil { - log.Panic(err) - } - - ws.Wallets = wallets.Wallets - - return nil -} - -// GetAddresses returns an array of addresses stored in the wallet file -func (ws *Wallets) GetAddresses() []string { - var addresses []string - - for address := range ws.Wallets { - addresses = append(addresses, address) - } - - return addresses -} - -// GetWallet returns a Wallet by its address -func (ws Wallets) GetWallet(address string) Wallet { - return *ws.Wallets[address] -} - -// NewWallets creates Wallets and fills it from a file if it exists -func NewWallets() (*Wallets, error) { - wallets := Wallets{} - wallets.Wallets = make(map[string]*Wallet) - - err := wallets.LoadFromFile() - - return &wallets, err -} - // HashPubKey hashes public key func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) diff --git a/wallets.go b/wallets.go new file mode 100644 index 0000000..e5b2134 --- /dev/null +++ b/wallets.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "crypto/elliptic" + "encoding/gob" + "fmt" + "io/ioutil" + "log" + "os" +) + +// Wallets stores a collection of wallets +type Wallets struct { + Wallets map[string]*Wallet +} + +// NewWallets creates Wallets and fills it from a file if it exists +func NewWallets() (*Wallets, error) { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFromFile() + + return &wallets, err +} + +// CreateWallet adds a Wallet to Wallets +func (ws *Wallets) CreateWallet() string { + wallet := NewWallet() + address := fmt.Sprintf("%s", wallet.GetAddress()) + + ws.Wallets[address] = wallet + + return address +} + +// GetAddresses returns an array of addresses stored in the wallet file +func (ws *Wallets) GetAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + +// GetWallet returns a Wallet by its address +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + +// LoadFromFile loads wallets from the file +func (ws *Wallets) LoadFromFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + log.Panic(err) + } + + var wallets Wallets + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) + if err != nil { + log.Panic(err) + } + + ws.Wallets = wallets.Wallets + + return nil +} + +// SaveToFile saves wallets to a file +func (ws Wallets) SaveToFile() { + var content bytes.Buffer + + gob.Register(elliptic.P256()) + + encoder := gob.NewEncoder(&content) + err := encoder.Encode(ws) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } +} From 843858dc3739e2067555b34f5839ee9788cfde5a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 11:06:12 +0700 Subject: [PATCH 19/33] Fix TXInput.UnlocksOutputWith --- transaction_input.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transaction_input.go b/transaction_input.go index 1c57c2b..e64e9e6 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -11,7 +11,9 @@ type TXInput struct { // UnlocksOutputWith checks whether the address initiated the transaction func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { - lockingHash := HashPubKey(in.ScriptSig) + sigLen := 64 + pubKey := in.ScriptSig[sigLen:] + lockingHash := HashPubKey(pubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } From 80e320a16f0af2e9d7184996d712423a841483d1 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 11:42:11 +0700 Subject: [PATCH 20/33] Clean up base58.go --- base58.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/base58.go b/base58.go index bdeccc2..589e00c 100644 --- a/base58.go +++ b/base58.go @@ -5,28 +5,27 @@ import ( "math/big" ) -var alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") +var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") // Base58Encode encodes a byte array to Base58 func Base58Encode(input []byte) []byte { var result []byte - x := big.NewInt(0) - x.SetBytes(input) + x := big.NewInt(0).SetBytes(input) - base := big.NewInt(int64(len(alphabet))) + base := big.NewInt(int64(len(b58Alphabet))) zero := big.NewInt(0) mod := &big.Int{} for x.Cmp(zero) != 0 { x.DivMod(x, base, mod) - result = append(result, alphabet[mod.Int64()]) + result = append(result, b58Alphabet[mod.Int64()]) } ReverseBytes(result) - for c := range input { - if c == 0x00 { - result = append([]byte{alphabet[0]}, result...) + for b := range input { + if b == 0x00 { + result = append([]byte{b58Alphabet[0]}, result...) } else { break } @@ -35,26 +34,26 @@ func Base58Encode(input []byte) []byte { return result } -// Base58Decode decodes Base58 data +// Base58Decode decodes Base58-encoded data func Base58Decode(input []byte) []byte { result := big.NewInt(0) zeroBytes := 0 - for c := range input { - if c == 0x00 { + for b := range input { + if b == 0x00 { zeroBytes++ } } - address := input[zeroBytes:] - for _, b := range address { - charIndex := bytes.IndexByte(alphabet, b) + payload := input[zeroBytes:] + for _, b := range payload { + charIndex := bytes.IndexByte(b58Alphabet, b) result.Mul(result, big.NewInt(58)) result.Add(result, big.NewInt(int64(charIndex))) } - raw := result.Bytes() - raw = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), raw...) + decoded := result.Bytes() + decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...) - return raw + return decoded } From 942120679bd8fd845df826314c592cb508396edf Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:02:46 +0700 Subject: [PATCH 21/33] Clean up block.go; rework transaction hashing --- block.go | 30 +++++++++++++++--------------- transaction.go | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/block.go b/block.go index 3baaa46..b98c79c 100644 --- a/block.go +++ b/block.go @@ -8,7 +8,7 @@ import ( "time" ) -// Block keeps block headers +// Block represents a block in the blockchain type Block struct { Timestamp int64 Transactions []*Transaction @@ -34,26 +34,13 @@ func NewGenesisBlock(coinbase *Transaction) *Block { return NewBlock([]*Transaction{coinbase}, []byte{}) } -// DeserializeBlock deserializes a block -func DeserializeBlock(d []byte) *Block { - var block Block - - decoder := gob.NewDecoder(bytes.NewReader(d)) - err := decoder.Decode(&block) - if err != nil { - log.Panic(err) - } - - return &block -} - // 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.ID) + txHashes = append(txHashes, tx.Hash()) } txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) @@ -72,3 +59,16 @@ func (b *Block) Serialize() []byte { return result.Bytes() } + +// DeserializeBlock deserializes a block +func DeserializeBlock(d []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(d)) + err := decoder.Decode(&block) + if err != nil { + log.Panic(err) + } + + return &block +} diff --git a/transaction.go b/transaction.go index e5176d6..99e4282 100644 --- a/transaction.go +++ b/transaction.go @@ -29,7 +29,33 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } +// Serialize returns a serialized Transaction +func (tx Transaction) Serialize() []byte { + var encoded bytes.Buffer + + enc := gob.NewEncoder(&encoded) + err := enc.Encode(tx) + if err != nil { + log.Panic(err) + } + + return encoded.Bytes() +} + +// Hash returns the hash of the Transaction +func (tx *Transaction) Hash() []byte { + var hash [32]byte + + txCopy := *tx + txCopy.ID = []byte{} + + hash = sha256.Sum256(txCopy.Serialize()) + + return hash[:] +} + // SetID sets ID of a transaction +// TODO: Remove this func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte From bb70b4924ba97dda7d31b5c85c2ab80b38600ddd Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:45:15 +0700 Subject: [PATCH 22/33] Clean up blockchain.go; improve TXInput and TXOutput --- blockchain.go | 49 +++++++++++++++++++------------------------ transaction_input.go | 4 ++-- transaction_output.go | 4 ++-- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/blockchain.go b/blockchain.go index ef4d221..e2579fe 100644 --- a/blockchain.go +++ b/blockchain.go @@ -30,15 +30,16 @@ func CreateBlockchain(address string) *Blockchain { } var tip []byte + + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) + genesis := NewGenesisBlock(cbtx) + db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } err = db.Update(func(tx *bolt.Tx) error { - cbtx := NewCoinbaseTX(address, genesisCoinbaseData) - genesis := NewGenesisBlock(cbtx) - b, err := tx.CreateBucket([]byte(blocksBucket)) if err != nil { log.Panic(err) @@ -57,7 +58,6 @@ func CreateBlockchain(address string) *Blockchain { return nil }) - if err != nil { log.Panic(err) } @@ -86,7 +86,6 @@ func NewBlockchain(address string) *Blockchain { return nil }) - if err != nil { log.Panic(err) } @@ -107,7 +106,7 @@ Work: txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { - if out.Unlock(pubKeyHash) && accumulated < amount { + if out.IsLockedWithKey(pubKeyHash) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) @@ -158,21 +157,21 @@ func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { - for _, spentOut := range spentTXOs[txID] { - if spentOut == outIdx { + for _, spentOutIdx := range spentTXOs[txID] { + if spentOutIdx == outIdx { continue Outputs } } } - if out.Unlock(pubKeyHash) { + if out.IsLockedWithKey(pubKeyHash) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { - if in.UnlocksOutputWith(pubKeyHash) { + if in.UsesKey(pubKeyHash) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } @@ -195,7 +194,7 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { for _, tx := range unspentTransactions { for _, out := range tx.Vout { - if out.Unlock(pubKeyHash) { + if out.IsLockedWithKey(pubKeyHash) { UTXOs = append(UTXOs, out) } } @@ -204,6 +203,13 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } +// Iterator returns a BlockchainIterat +func (bc *Blockchain) Iterator() *BlockchainIterator { + bci := &BlockchainIterator{bc.tip, bc.db} + + return bci +} + // MineBlock mines a new block with the provided transactions func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte @@ -220,7 +226,6 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { return nil }) - if err != nil { log.Panic(err) } @@ -243,9 +248,12 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { return nil }) + if err != nil { + log.Panic(err) + } } -// SignTransaction signs a Transaction +// SignTransaction signs inputs of a Transaction func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) @@ -260,7 +268,7 @@ func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) tx.Sign(privKey, prevTXs) } -// VerifyTransaction verifies transaction +// VerifyTransaction verifies transaction input signatures func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs := make(map[string]Transaction) @@ -272,22 +280,9 @@ func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } - for _, tx := range prevTXs { - fmt.Println(tx) - } - // fmt.Println() - // fmt.Println(tx) - return tx.Verify(prevTXs) } -// Iterator returns a BlockchainIterat -func (bc *Blockchain) Iterator() *BlockchainIterator { - bci := &BlockchainIterator{bc.tip, bc.db} - - return bci -} - func dbExists() bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false diff --git a/transaction_input.go b/transaction_input.go index e64e9e6..baeccd9 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -9,8 +9,8 @@ type TXInput struct { ScriptSig []byte } -// UnlocksOutputWith checks whether the address initiated the transaction -func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { +// UsesKey checks whether the address initiated the transaction +func (in *TXInput) UsesKey(pubKeyHash []byte) bool { sigLen := 64 pubKey := in.ScriptSig[sigLen:] lockingHash := HashPubKey(pubKey) diff --git a/transaction_output.go b/transaction_output.go index f509dd8..27d89b5 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -15,8 +15,8 @@ func (out *TXOutput) Lock(address []byte) { out.ScriptPubKey = pubKeyHash } -// Unlock checks if the output can be used by the owner of the pubkey -func (out *TXOutput) Unlock(pubKeyHash []byte) bool { +// IsLockedWithKey checks if the output can be used by the owner of the pubkey +func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 } From 5f9e6c0c91962143106c2b3db8092e85d1c49803 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 12:53:06 +0700 Subject: [PATCH 23/33] Extract CLI commands into separate files --- cli.go | 76 ----------------------------------------- cli_createblockchain.go | 9 +++++ cli_createwallet.go | 11 ++++++ cli_getbalance.go | 19 +++++++++++ cli_listaddress.go | 18 ++++++++++ cli_printchain.go | 30 ++++++++++++++++ cli_send.go | 12 +++++++ 7 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 cli_createblockchain.go create mode 100644 cli_createwallet.go create mode 100644 cli_getbalance.go create mode 100644 cli_listaddress.go create mode 100644 cli_printchain.go create mode 100644 cli_send.go diff --git a/cli.go b/cli.go index 2b01163..863ffd1 100644 --- a/cli.go +++ b/cli.go @@ -5,87 +5,11 @@ import ( "fmt" "log" "os" - "strconv" ) // CLI responsible for processing command line arguments type CLI struct{} -func (cli *CLI) createBlockchain(address string) { - bc := CreateBlockchain(address) - bc.db.Close() - fmt.Println("Done!") -} - -func (cli *CLI) createWallet() { - wallets, _ := NewWallets() - address := wallets.CreateWallet() - wallets.SaveToFile() - - fmt.Printf("Your new address: %s\n", address) -} - -func (cli *CLI) getBalance(address string) { - bc := NewBlockchain(address) - defer bc.db.Close() - - balance := 0 - pubKeyHash := Base58Decode([]byte(address)) - pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - UTXOs := bc.FindUTXO(pubKeyHash) - - for _, out := range UTXOs { - balance += out.Value - } - - fmt.Printf("Balance of '%s': %d\n", address, balance) -} - -func (cli *CLI) listAddresses() { - wallets, err := NewWallets() - if err != nil { - log.Panic(err) - } - addresses := wallets.GetAddresses() - - for _, address := range addresses { - fmt.Println(address) - } -} - -func (cli *CLI) printChain() { - bc := NewBlockchain("") - defer bc.db.Close() - - bci := bc.Iterator() - - for { - block := bci.Next() - - fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) - fmt.Printf("Hash: %x\n", block.Hash) - pow := NewProofOfWork(block) - fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) - for _, tx := range block.Transactions { - fmt.Println(tx) - } - fmt.Println() - - if len(block.PrevBlockHash) == 0 { - break - } - } -} - -func (cli *CLI) send(from, to string, amount int) { - bc := NewBlockchain(from) - defer bc.db.Close() - - tx := NewUTXOTransaction(from, to, amount, bc) - bc.MineBlock([]*Transaction{tx}) - fmt.Println("Success!") -} - func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") diff --git a/cli_createblockchain.go b/cli_createblockchain.go new file mode 100644 index 0000000..c2acb48 --- /dev/null +++ b/cli_createblockchain.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +func (cli *CLI) createBlockchain(address string) { + bc := CreateBlockchain(address) + bc.db.Close() + fmt.Println("Done!") +} diff --git a/cli_createwallet.go b/cli_createwallet.go new file mode 100644 index 0000000..b42a142 --- /dev/null +++ b/cli_createwallet.go @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func (cli *CLI) createWallet() { + wallets, _ := NewWallets() + address := wallets.CreateWallet() + wallets.SaveToFile() + + fmt.Printf("Your new address: %s\n", address) +} diff --git a/cli_getbalance.go b/cli_getbalance.go new file mode 100644 index 0000000..76ab0d0 --- /dev/null +++ b/cli_getbalance.go @@ -0,0 +1,19 @@ +package main + +import "fmt" + +func (cli *CLI) getBalance(address string) { + bc := NewBlockchain(address) + defer bc.db.Close() + + balance := 0 + pubKeyHash := Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := bc.FindUTXO(pubKeyHash) + + for _, out := range UTXOs { + balance += out.Value + } + + fmt.Printf("Balance of '%s': %d\n", address, balance) +} diff --git a/cli_listaddress.go b/cli_listaddress.go new file mode 100644 index 0000000..96fd282 --- /dev/null +++ b/cli_listaddress.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "log" +) + +func (cli *CLI) listAddresses() { + wallets, err := NewWallets() + if err != nil { + log.Panic(err) + } + addresses := wallets.GetAddresses() + + for _, address := range addresses { + fmt.Println(address) + } +} diff --git a/cli_printchain.go b/cli_printchain.go new file mode 100644 index 0000000..4c2e865 --- /dev/null +++ b/cli_printchain.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "strconv" +) + +func (cli *CLI) printChain() { + bc := NewBlockchain("") + defer bc.db.Close() + + bci := bc.Iterator() + + for { + block := bci.Next() + + fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) + fmt.Printf("Hash: %x\n", block.Hash) + pow := NewProofOfWork(block) + fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } + fmt.Println() + + if len(block.PrevBlockHash) == 0 { + break + } + } +} diff --git a/cli_send.go b/cli_send.go new file mode 100644 index 0000000..ac3e001 --- /dev/null +++ b/cli_send.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func (cli *CLI) send(from, to string, amount int) { + bc := NewBlockchain(from) + defer bc.db.Close() + + tx := NewUTXOTransaction(from, to, amount, bc) + bc.MineBlock([]*Transaction{tx}) + fmt.Println("Success!") +} From 7290aaac649c275e11f9ed3d466d2d473eb54192 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 13:34:47 +0700 Subject: [PATCH 24/33] Use Hash funcion to set transaction ID --- transaction.go | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/transaction.go b/transaction.go index 99e4282..132ce83 100644 --- a/transaction.go +++ b/transaction.go @@ -54,21 +54,6 @@ func (tx *Transaction) Hash() []byte { return hash[:] } -// SetID sets ID of a transaction -// TODO: Remove this -func (tx *Transaction) SetID() { - var encoded bytes.Buffer - var hash [32]byte - - enc := gob.NewEncoder(&encoded) - err := enc.Encode(tx) - if err != nil { - log.Panic(err) - } - hash = sha256.Sum256(encoded.Bytes()) - tx.ID = hash[:] -} - // Sign signs each input of a Transaction func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { @@ -86,7 +71,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey - txCopy.SetID() + txCopy.ID = txCopy.Hash() txCopy.Vin[inID].ScriptSig = []byte{} r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) @@ -160,7 +145,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey - txCopy.SetID() + txCopy.ID = txCopy.Hash() txCopy.Vin[inID].ScriptSig = []byte{} signature := vin.ScriptSig[:sigLen] @@ -195,7 +180,7 @@ func NewCoinbaseTX(to, data string) *Transaction { txin := TXInput{[]byte{}, -1, []byte(data)} txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} - tx.SetID() + tx.ID = tx.Hash() return &tx } @@ -237,7 +222,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } tx := Transaction{nil, inputs, outputs} - tx.SetID() + tx.ID = tx.Hash() bc.SignTransaction(&tx, wallet.PrivateKey) return &tx From a436da6c194174fa0f3b14a241ae5e7e7cddb80e Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 13:53:14 +0700 Subject: [PATCH 25/33] Implement ValidateAddress --- cli_createblockchain.go | 8 +++++++- cli_getbalance.go | 8 +++++++- cli_send.go | 12 +++++++++++- wallet.go | 37 +++++++++++++++++++++++++------------ 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/cli_createblockchain.go b/cli_createblockchain.go index c2acb48..06ff1b1 100644 --- a/cli_createblockchain.go +++ b/cli_createblockchain.go @@ -1,8 +1,14 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) createBlockchain(address string) { + if !ValidateAddress(address) { + log.Panic("ERROR: Address is not valid") + } bc := CreateBlockchain(address) bc.db.Close() fmt.Println("Done!") diff --git a/cli_getbalance.go b/cli_getbalance.go index 76ab0d0..27cb55e 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -1,8 +1,14 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) getBalance(address string) { + if !ValidateAddress(address) { + log.Panic("ERROR: Address is not valid") + } bc := NewBlockchain(address) defer bc.db.Close() diff --git a/cli_send.go b/cli_send.go index ac3e001..fb8117c 100644 --- a/cli_send.go +++ b/cli_send.go @@ -1,8 +1,18 @@ package main -import "fmt" +import ( + "fmt" + "log" +) func (cli *CLI) send(from, to string, amount int) { + if !ValidateAddress(from) { + log.Panic("ERROR: Sender address is not valid") + } + if !ValidateAddress(to) { + log.Panic("ERROR: Recipient address is not valid") + } + bc := NewBlockchain(from) defer bc.db.Close() diff --git a/wallet.go b/wallet.go index d527988..31a2258 100644 --- a/wallet.go +++ b/wallet.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -12,6 +13,7 @@ import ( const version = byte(0x00) const walletFile = "wallet.dat" +const addressChecksumLen = 4 // Wallet stores private and public keys type Wallet struct { @@ -40,17 +42,6 @@ func (w Wallet) GetAddress() []byte { return address } -func newKeyPair() (ecdsa.PrivateKey, []byte) { - curve := elliptic.P256() - private, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - log.Panic(err) - } - pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) - - return *private, pubKey -} - // HashPubKey hashes public key func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) @@ -65,10 +56,32 @@ func HashPubKey(pubKey []byte) []byte { return publicRIPEMD160 } +// ValidateAddress check if address if valid +func ValidateAddress(address string) bool { + pubKeyHash := Base58Decode([]byte(address)) + actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:] + version := pubKeyHash[0] + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen] + targetChecksum := checksum(append([]byte{version}, pubKeyHash...)) + + return bytes.Compare(actualChecksum, targetChecksum) == 0 +} + // Checksum generates a checksum for a public key func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) - return secondSHA[:4] + return secondSHA[:addressChecksumLen] +} + +func newKeyPair() (ecdsa.PrivateKey, []byte) { + curve := elliptic.P256() + private, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + log.Panic(err) + } + pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) + + return *private, pubKey } From bf8c5d22e19c7f460fe75fbe3a885271f70df17a Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:05:23 +0700 Subject: [PATCH 26/33] Store input sig and key in different fields; rename TXInput.ScriptPubKey to PubKeyHash --- transaction.go | 39 +++++++++++++++++++-------------------- transaction_input.go | 7 +++---- transaction_output.go | 8 ++++---- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/transaction.go b/transaction.go index 132ce83..4102a22 100644 --- a/transaction.go +++ b/transaction.go @@ -70,9 +70,10 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].ScriptSig = []byte{} + txCopy.Vin[inID].PubKey = []byte{} r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) if err != nil { @@ -80,7 +81,7 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac } signature := append(r.Bytes(), s.Bytes()...) - tx.Vin[inID].ScriptSig = append(signature, tx.Vin[inID].ScriptSig...) + tx.Vin[inID].Signature = signature } } @@ -93,15 +94,16 @@ func (tx Transaction) String() string { 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(" Script: %x", input.ScriptSig)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + 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(" Script: %x", output.ScriptPubKey)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } return strings.Join(lines, "\n") @@ -117,7 +119,7 @@ func (tx *Transaction) TrimmedCopy() Transaction { } for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} @@ -127,8 +129,6 @@ func (tx *Transaction) TrimmedCopy() Transaction { // Verify verifies signatures of Transaction inputs func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { - sigLen := 64 - if tx.IsCoinbase() { return true } @@ -144,23 +144,22 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].ScriptSig = prevTx.Vout[vin.Vout].ScriptPubKey + txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].ScriptSig = []byte{} - - signature := vin.ScriptSig[:sigLen] - pubKey := vin.ScriptSig[sigLen:] + txCopy.Vin[inID].PubKey = []byte{} r := big.Int{} s := big.Int{} - r.SetBytes(signature[:(sigLen / 2)]) - s.SetBytes(signature[(sigLen / 2):]) + sigLen := len(vin.Signature) + r.SetBytes(vin.Signature[:(sigLen / 2)]) + s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} - keyLen := len(pubKey) - x.SetBytes(pubKey[:(keyLen / 2)]) - y.SetBytes(pubKey[(keyLen / 2):]) + keyLen := len(vin.PubKey) + x.SetBytes(vin.PubKey[:(keyLen / 2)]) + y.SetBytes(vin.PubKey[(keyLen / 2):]) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { diff --git a/transaction_input.go b/transaction_input.go index baeccd9..23beeba 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -6,14 +6,13 @@ import "bytes" type TXInput struct { Txid []byte Vout int - ScriptSig []byte + Signature []byte + PubKey []byte } // UsesKey checks whether the address initiated the transaction func (in *TXInput) UsesKey(pubKeyHash []byte) bool { - sigLen := 64 - pubKey := in.ScriptSig[sigLen:] - lockingHash := HashPubKey(pubKey) + lockingHash := HashPubKey(in.PubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } diff --git a/transaction_output.go b/transaction_output.go index 27d89b5..4d70d74 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -4,20 +4,20 @@ import "bytes" // TXOutput represents a transaction output type TXOutput struct { - Value int - ScriptPubKey []byte + Value int + PubKeyHash []byte } // Lock signs the output func (out *TXOutput) Lock(address []byte) { pubKeyHash := Base58Decode(address) pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - out.ScriptPubKey = pubKeyHash + out.PubKeyHash = pubKeyHash } // IsLockedWithKey checks if the output can be used by the owner of the pubkey func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { - return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 + return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 } // NewTXOutput create a new TXOutput From 7b6d5695d38f9c676378b3c6d0ef7b7c50b2ead4 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:11:21 +0700 Subject: [PATCH 27/33] Fix some initializations --- transaction.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/transaction.go b/transaction.go index 4102a22..598c65f 100644 --- a/transaction.go +++ b/transaction.go @@ -70,10 +70,10 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = []byte{} + txCopy.Vin[inID].PubKey = nil r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) if err != nil { @@ -115,7 +115,7 @@ func (tx *Transaction) TrimmedCopy() Transaction { var outputs []TXOutput for _, vin := range tx.Vin { - inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { @@ -176,7 +176,7 @@ func NewCoinbaseTX(to, data string) *Transaction { data = fmt.Sprintf("Reward to '%s'", to) } - txin := TXInput{[]byte{}, -1, []byte(data)} + txin := TXInput{[]byte{}, -1, nil, []byte(data)} txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.ID = tx.Hash() @@ -209,7 +209,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio } for _, out := range outs { - input := TXInput{txID, out, wallet.PublicKey} + input := TXInput{txID, out, nil, wallet.PublicKey} inputs = append(inputs, input) } } From c0b4d6d107140fb62770f9ccdcc661fdfbc1c974 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 14:31:01 +0700 Subject: [PATCH 28/33] Improve the printchain command --- cli_printchain.go | 8 ++++---- transaction.go | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cli_printchain.go b/cli_printchain.go index 4c2e865..cd5c7a0 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -14,14 +14,14 @@ func (cli *CLI) printChain() { for { block := bci.Next() - fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) - fmt.Printf("Hash: %x\n", block.Hash) + fmt.Printf("============ Block %x ============\n", block.Hash) + fmt.Printf("Prev. block: %x\n", block.PrevBlockHash) pow := NewProofOfWork(block) - fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) for _, tx := range block.Transactions { fmt.Println(tx) } - fmt.Println() + fmt.Printf("\n\n") if len(block.PrevBlockHash) == 0 { break diff --git a/transaction.go b/transaction.go index 598c65f..7f035a8 100644 --- a/transaction.go +++ b/transaction.go @@ -89,21 +89,21 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac func (tx Transaction) String() string { var lines []string - lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) + 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(" Signature: %x", input.Signature)) - lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) + 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(" 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(" Script: %x", output.PubKeyHash)) + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } return strings.Join(lines, "\n") From 465b85d5f28b6264d23ae225adc2ab53ab34d045 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 20:57:19 +0700 Subject: [PATCH 29/33] Fix the 'checksum' function --- wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet.go b/wallet.go index 31a2258..262a366 100644 --- a/wallet.go +++ b/wallet.go @@ -72,7 +72,7 @@ func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) - return secondSHA[:addressChecksumLen] + return secondSHA[len(secondSHA)-addressChecksumLen:] } func newKeyPair() (ecdsa.PrivateKey, []byte) { From b6f7626a13f6b85dd192d66b9929c2f72683ccf7 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:09:13 +0700 Subject: [PATCH 30/33] Fix Signature resetting --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 7f035a8..794c66c 100644 --- a/transaction.go +++ b/transaction.go @@ -144,7 +144,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] - txCopy.Vin[inID].Signature = []byte{} + txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = []byte{} From a6394c7afae7a09a2b8c5fff40075f03c7963fe5 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:10:18 +0700 Subject: [PATCH 31/33] Fix PubKey resetting --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 794c66c..90950cc 100644 --- a/transaction.go +++ b/transaction.go @@ -147,7 +147,7 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() - txCopy.Vin[inID].PubKey = []byte{} + txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} From 402b298d4f908d14df5d7e51e7ae917c0347da47 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 12 Sep 2017 21:18:50 +0700 Subject: [PATCH 32/33] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c9b585..87fc9f4 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,4 @@ A blockchain implementation in Go, as described in these articles: 2. [Proof-of-Work](https://jeiwan.cc/posts/building-blockchain-in-go-part-2/) 2. [Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/) 3. [Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/) +3. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/) From 201e7a165214ca4d7287c83a23d65e1c05578e48 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Tue, 19 Sep 2017 14:54:41 +0700 Subject: [PATCH 33/33] Revert "Fix the 'checksum' function" This reverts commit 465b85d5f28b6264d23ae225adc2ab53ab34d045. --- wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet.go b/wallet.go index 262a366..31a2258 100644 --- a/wallet.go +++ b/wallet.go @@ -72,7 +72,7 @@ func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) - return secondSHA[len(secondSHA)-addressChecksumLen:] + return secondSHA[:addressChecksumLen] } func newKeyPair() (ecdsa.PrivateKey, []byte) {