区块链编程golang(五)—钱包

 block/ block.go

package blockchain

import (
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "log"
)

type Block struct {
    Hash         []byte
    Transactions []*Transaction
    PrevHash     []byte
    Nonce        int
}

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[:]
}

func CreateBlock(txs []*Transaction, prevHash []byte) *Block {
    block := &Block{[]byte{}, txs, prevHash, 0}
    pow := NewProof(block)
    nonce, hash := pow.Run()

    block.Hash = hash[:]
    block.Nonce = nonce

    return block
}

func Genesis(coinbase *Transaction) *Block {
    return CreateBlock([]*Transaction{coinbase}, []byte{})
}

func (b *Block) Serialize() []byte {
    var res bytes.Buffer
    encoder := gob.NewEncoder(&res)

    err := encoder.Encode(b)

    Handle(err)

    return res.Bytes()
}

func Deserialize(data []byte) *Block {
    var block Block

    decoder := gob.NewDecoder(bytes.NewReader(data))

    err := decoder.Decode(&block)

    Handle(err)

    return &block
}

func Handle(err error) {
    if err != nil {
        log.Panic(err)
    }
}

 

block/ blockchain.go

package blockchain

import (
    "encoding/hex"
    "fmt"
    "os"
    "runtime"

    "github.com/dgraph-io/badger"
)

const (
    dbPath      = "./tmp/blocks"
    dbFile      = "./tmp/blocks/MANIFEST"
    genesisData = "First Transaction from Genesis"
)

type BlockChain struct {
    LastHash []byte
    Database *badger.DB
}

type BlockChainIterator struct {
    CurrentHash []byte
    Database    *badger.DB
}

func DBexists() bool {
    if _, err := os.Stat(dbFile); os.IsNotExist(err) {
        return false
    }

    return true
}

func ContinueBlockChain(address string) *BlockChain {
    if DBexists() == false {
        fmt.Println("No existing blockchain found, create one!")
        runtime.Goexit()
    }

    var lastHash []byte

    opts := badger.DefaultOptions
    opts.Dir = dbPath
    opts.ValueDir = dbPath

    db, err := badger.Open(opts)
    Handle(err)

    err = db.Update(func(txn *badger.Txn) error {
        item, err := txn.Get([]byte("lh"))
        Handle(err)
        lastHash, err = item.Value()

        return err
    })
    Handle(err)

    chain := BlockChain{lastHash, db}

    return &chain
}

func InitBlockChain(address string) *BlockChain {
    var lastHash []byte

    if DBexists() {
        fmt.Println("Blockchain already exists")
        runtime.Goexit()
    }

    opts := badger.DefaultOptions
    opts.Dir = dbPath
    opts.ValueDir = dbPath

    db, err := badger.Open(opts)
    Handle(err)

    err = db.Update(func(txn *badger.Txn) error {
        cbtx := CoinbaseTx(address, genesisData)
        genesis := Genesis(cbtx)
        fmt.Println("Genesis created")
        err = txn.Set(genesis.Hash, genesis.Serialize())
        Handle(err)
        err = txn.Set([]byte("lh"), genesis.Hash)

        lastHash = genesis.Hash

        return err

    })

    Handle(err)

    blockchain := BlockChain{lastHash, db}
    return &blockchain
}

func (chain *BlockChain) AddBlock(transactions []*Transaction) {
    var lastHash []byte

    err := chain.Database.View(func(txn *badger.Txn) error {
        item, err := txn.Get([]byte("lh"))
        Handle(err)
        lastHash, err = item.Value()

        return err
    })
    Handle(err)

    newBlock := CreateBlock(transactions, lastHash)

    err = chain.Database.Update(func(txn *badger.Txn) error {
        err := txn.Set(newBlock.Hash, newBlock.Serialize())
        Handle(err)
        err = txn.Set([]byte("lh"), newBlock.Hash)

        chain.LastHash = newBlock.Hash

        return err
    })
    Handle(err)
}

func (chain *BlockChain) Iterator() *BlockChainIterator {
    iter := &BlockChainIterator{chain.LastHash, chain.Database}

    return iter
}

func (iter *BlockChainIterator) Next() *Block {
    var block *Block

    err := iter.Database.View(func(txn *badger.Txn) error {
        item, err := txn.Get(iter.CurrentHash)
        Handle(err)
        encodedBlock, err := item.Value()
        block = Deserialize(encodedBlock)

        return err
    })
    Handle(err)

    iter.CurrentHash = block.PrevHash

    return block
}

func (chain *BlockChain) FindUnspentTransactions(address string) []Transaction {
    var unspentTxs []Transaction

    spentTXOs := make(map[string][]int)

    iter := chain.Iterator()

    for {
        block := iter.Next()

        for _, tx := range block.Transactions {
            txID := hex.EncodeToString(tx.ID)

        Outputs:
            for outIdx, out := range tx.Outputs {
                if spentTXOs[txID] != nil {
                    for _, spentOut := range spentTXOs[txID] {
                        if spentOut == outIdx {
                            continue Outputs
                        }
                    }
                }
                if out.CanBeUnlocked(address) {
                    unspentTxs = append(unspentTxs, *tx)
                }
            }
            if tx.IsCoinbase() == false {
                for _, in := range tx.Inputs {
                    if in.CanUnlock(address) {
                        inTxID := hex.EncodeToString(in.ID)
                        spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Out)
                    }
                }
            }
        }

        if len(block.PrevHash) == 0 {
            break
        }
    }
    return unspentTxs
}

func (chain *BlockChain) FindUTXO(address string) []TxOutput {
    var UTXOs []TxOutput
    unspentTransactions := chain.FindUnspentTransactions(address)

    for _, tx := range unspentTransactions {
        for _, out := range tx.Outputs {
            if out.CanBeUnlocked(address) {
                UTXOs = append(UTXOs, out)
            }
        }
    }
    return UTXOs
}

func (chain *BlockChain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
    unspentOuts := make(map[string][]int)
    unspentTxs := chain.FindUnspentTransactions(address)
    accumulated := 0

Work:
    for _, tx := range unspentTxs {
        txID := hex.EncodeToString(tx.ID)

        for outIdx, out := range tx.Outputs {
            if out.CanBeUnlocked(address) && accumulated < amount {
                accumulated += out.Value
                unspentOuts[txID] = append(unspentOuts[txID], outIdx)

                if accumulated >= amount {
                    break Work
                }
            }
        }
    }

    return accumulated, unspentOuts
}

 

block/  transcation.go

package blockchain

import (
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "encoding/hex"
    "fmt"
    "log"
)

type Transaction struct {
    ID      []byte
    Inputs  []TxInput
    Outputs []TxOutput
}

func (tx *Transaction) SetID() {
    var encoded bytes.Buffer
    var hash [32]byte

    encode := gob.NewEncoder(&encoded)
    err := encode.Encode(tx)
    Handle(err)

    hash = sha256.Sum256(encoded.Bytes())
    tx.ID = hash[:]
}

func CoinbaseTx(to, data string) *Transaction {
    if data == "" {
        data = fmt.Sprintf("Coins to %s", to)
    }

    txin := TxInput{[]byte{}, -1, data}
    txout := TxOutput{100, to}

    tx := Transaction{nil, []TxInput{txin}, []TxOutput{txout}}
    tx.SetID()

    return &tx
}

func NewTransaction(from, to string, amount int, chain *BlockChain) *Transaction {
    var inputs []TxInput
    var outputs []TxOutput

    acc, validOutputs := chain.FindSpendableOutputs(from, amount)

    if acc < amount {
        log.Panic("Error: not enough funds")
    }

    for txid, outs := range validOutputs {
        txID, err := hex.DecodeString(txid)
        Handle(err)

        for _, out := range outs {
            input := TxInput{txID, out, from}
            inputs = append(inputs, input)
        }
    }

    outputs = append(outputs, TxOutput{amount, to})

    if acc > amount {
        outputs = append(outputs, TxOutput{acc - amount, from})
    }

    tx := Transaction{nil, inputs, outputs}
    tx.SetID()

    return &tx
}

func (tx *Transaction) IsCoinbase() bool {
    return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].Out == -1
}

 

block./ tx.go

package blockchain

type TxOutput struct {
    Value  int
    PubKey string
}

type TxInput struct {
    ID  []byte
    Out int
    Sig string
}

func (in *TxInput) CanUnlock(data string) bool {
    return in.Sig == data
}

func (out *TxOutput) CanBeUnlocked(data string) bool {
    return out.PubKey == data
}

 

wallet / utils.go

package wallet

import (
    "log"

    "github.com/mr-tron/base58"
)

func Base58Encode(input []byte) []byte {
    encode := base58.Encode(input)

    return []byte(encode)
}

func Base58Decode(input []byte) []byte {
    decode, err := base58.Decode(string(input[:]))
    if err != nil {
        log.Panic(err)
    }

    return decode
}

 

wallet / walllet.go

package wallet

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "log"

    "golang.org/x/crypto/ripemd160"
)

const (
    checksumLength = 4
    version        = byte(0x00)
)

type Wallet struct {
    PrivateKey ecdsa.PrivateKey
    PublicKey  []byte
}

func (w Wallet) Address() []byte {
    pubHash := PublicKeyHash(w.PublicKey)

    versionedHash := append([]byte{version}, pubHash...)
    checksum := Checksum(versionedHash)

    fullHash := append(versionedHash, checksum...)
    address := Base58Encode(fullHash)

    return address
}

func NewKeyPair() (ecdsa.PrivateKey, []byte) {
    curve := elliptic.P256()

    private, err := ecdsa.GenerateKey(curve, rand.Reader)
    if err != nil {
        log.Panic(err)
    }

    pub := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
    return *private, pub
}

func MakeWallet() *Wallet {
    private, public := NewKeyPair()
    wallet := Wallet{private, public}

    return &wallet
}

func PublicKeyHash(pubKey []byte) []byte {
    pubHash := sha256.Sum256(pubKey)

    hasher := ripemd160.New()
    _, err := hasher.Write(pubHash[:])
    if err != nil {
        log.Panic(err)
    }

    publicRipMD := hasher.Sum(nil)

    return publicRipMD
}

func Checksum(payload []byte) []byte {
    firstHash := sha256.Sum256(payload)
    secondHash := sha256.Sum256(firstHash[:])

    return secondHash[:checksumLength]
}

 

wallet / wallets.go

package wallet

import (
    "bytes"
    "crypto/elliptic"
    "encoding/gob"
    "fmt"
    "io/ioutil"
    "log"
    "os"
)

const walletFile = "./tmp/wallets.data"

type Wallets struct {
    Wallets map[string]*Wallet
}

func CreateWallets() (*Wallets, error) {
    wallets := Wallets{}
    wallets.Wallets = make(map[string]*Wallet)

    err := wallets.LoadFile()

    return &wallets, err
}

func (ws *Wallets) AddWallet() string {
    wallet := MakeWallet()
    address := fmt.Sprintf("%s", wallet.Address())

    ws.Wallets[address] = wallet

    return address
}

func (ws *Wallets) GetAllAddresses() []string {
    var addresses []string

    for address := range ws.Wallets {
        addresses = append(addresses, address)
    }

    return addresses
}

func (ws Wallets) GetWallet(address string) Wallet {
    return *ws.Wallets[address]
}

func (ws *Wallets) LoadFile() error {
    if _, err := os.Stat(walletFile); os.IsNotExist(err) {
        return err
    }

    var wallets Wallets

    fileContent, err := ioutil.ReadFile(walletFile)
    if err != nil {
        return err
    }

    gob.Register(elliptic.P256())
    decoder := gob.NewDecoder(bytes.NewReader(fileContent))
    err = decoder.Decode(&wallets)
    if err != nil {
        return err
    }

    ws.Wallets = wallets.Wallets

    return nil
}

func (ws *Wallets) SaveFile() {
    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)
    }
}

 

main.go

package main

import (
    "os"

    "github.com/tensor-programming/golang-blockchain/cli"
)

func main() {
    defer os.Exit(0)
    cmd := cli.CommandLine{}
    cmd.Run()
}

 

go.mod 和 go.sum 文件一样

 

posted @ 2024-08-17 14:32  apeNote  阅读(1)  评论(0编辑  收藏  举报