From cc73a0835e466c976fdca686dbf6a6ad8f2dd0a8 Mon Sep 17 00:00:00 2001 From: server Date: Fri, 17 Jul 2020 18:42:50 -0400 Subject: [PATCH] update --- .gitignore | 1 + _example/_blockchain/blockchain/blockchain.go | 692 ++++++++++++++++++ _example/_blockchain/main.go | 39 + _example/_network/main.go | 52 ++ _example/_network/network/network.go | 118 +++ _example/_result/addr.json | 4 + _example/_result/blockchain/blockchain.go | 692 ++++++++++++++++++ _example/_result/client.go | 244 ++++++ _example/_result/network/network.go | 118 +++ _example/_result/node.go | 370 ++++++++++ blockchain/block.go | 16 +- blockchain/chain.go | 83 +-- blockchain/crypto.go | 4 +- blockchain/settings.go | 4 +- blockchain/transaction.go | 10 - client.go | 10 +- node.go | 1 - 17 files changed, 2354 insertions(+), 104 deletions(-) create mode 100644 _example/_blockchain/blockchain/blockchain.go create mode 100644 _example/_blockchain/main.go create mode 100644 _example/_network/main.go create mode 100644 _example/_network/network/network.go create mode 100644 _example/_result/addr.json create mode 100644 _example/_result/blockchain/blockchain.go create mode 100644 _example/_result/client.go create mode 100644 _example/_result/network/network.go create mode 100644 _example/_result/node.go diff --git a/.gitignore b/.gitignore index ac4fcf8..15d327b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +main node client node1.key diff --git a/_example/_blockchain/blockchain/blockchain.go b/_example/_blockchain/blockchain/blockchain.go new file mode 100644 index 0000000..b424731 --- /dev/null +++ b/_example/_blockchain/blockchain/blockchain.go @@ -0,0 +1,692 @@ +package blockchain + +import ( + "time" + "errors" + "bytes" + "sort" + "database/sql" + _ "github.com/mattn/go-sqlite3" + "os" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "math" + "math/big" + mrand "math/rand" +) + +type BlockChain struct { + index uint64 + DB *sql.DB +} + +type Block struct { + Nonce uint64 + Difficulty uint8 + CurrHash []byte + PrevHash []byte + Transactions []Transaction + Mapping map[string]uint64 + Miner string + Signature []byte + TimeStamp string +} + +type Transaction struct { + RandBytes []byte + PrevBlock []byte + Sender string + Receiver string + Value uint64 + ToStorage uint64 + CurrHash []byte + Signature []byte +} + +const ( + CREATE_TABLE = ` +CREATE TABLE BlockChain ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Hash VARCHAR(44) UNIQUE, + Block TEXT +); +` +) + +const ( + GENESIS_BLOCK = "GENESIS-BLOCK" + STORAGE_VALUE = 100 + GENESIS_REWARD = 100 + STORAGE_CHAIN = "STORAGE-CHAIN" +) + +const ( + DIFFICULTY = 20 +) + +const ( + RAND_BYTES = 32 + START_PERCENT = 10 + STORAGE_REWARD = 1 +) + +const ( + TXS_LIMIT = 2 +) + +const ( + DEBUG = true +) + +const ( + KEY_SIZE = 512 +) + +func NewChain(filename, receiver string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + file.Close() + db, err := sql.Open("sqlite3", filename) + if err != nil { + return err + } + defer db.Close() + _, err = db.Exec(CREATE_TABLE) + chain := &BlockChain{ + DB: db, + } + genesis := &Block{ + CurrHash: []byte(GENESIS_BLOCK), + Mapping: make(map[string]uint64), + Miner: receiver, + TimeStamp: time.Now().Format(time.RFC3339), + } + genesis.Mapping[STORAGE_CHAIN] = STORAGE_VALUE + genesis.Mapping[receiver] = GENESIS_REWARD + chain.AddBlock(genesis) + return nil +} + +func (chain *BlockChain) AddBlock(block *Block) { + chain.index += 1 + chain.DB.Exec("INSERT INTO BlockChain (Hash, Block) VALUES ($1, $2)", + Base64Encode(block.CurrHash), + SerializeBlock(block), + ) +} + +func SerializeBlock(block *Block) string { + jsonData, err := json.MarshalIndent(*block, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func LoadChain(filename string) *BlockChain { + db, err := sql.Open("sqlite3", filename) + if err != nil { + return nil + } + chain := &BlockChain{ + DB: db, + } + chain.index = chain.Size() + return chain +} + +func (chain *BlockChain) Size() uint64 { + var index uint64 + row := chain.DB.QueryRow("SELECT Id FROM BlockChain ORDER BY Id DESC") + row.Scan(&index) + return index +} + +func NewBlock(miner string, prevHash []byte) *Block { + return &Block{ + Difficulty: DIFFICULTY, + PrevHash: prevHash, + Miner: miner, + Mapping: make(map[string]uint64), + } +} + +func NewTransaction(user *User, lasthash []byte, to string, value uint64) *Transaction { + tx := &Transaction{ + RandBytes: GenerateRandomBytes(RAND_BYTES), + PrevBlock: lasthash, + Sender: user.Address(), + Receiver: to, + Value: value, + } + if value > START_PERCENT { + tx.ToStorage = STORAGE_REWARD + } + tx.CurrHash = tx.hash() + tx.Signature = tx.sign(user.Private()) + return tx +} + +type User struct { + PrivateKey *rsa.PrivateKey +} + +func GenerateRandomBytes(max uint) []byte { + var slice []byte = make([]byte, max) + _, err := rand.Read(slice) + if err != nil { + return nil + } + return slice +} + +func (user *User) Address() string { + return StringPublic(user.Public()) +} + +func (user *User) Private() *rsa.PrivateKey { + return user.PrivateKey +} + +func (tx *Transaction) hash() []byte { + return HashSum(bytes.Join( + [][]byte{ + tx.RandBytes, + tx.PrevBlock, + []byte(tx.Sender), + []byte(tx.Receiver), + ToBytes(tx.Value), + ToBytes(tx.ToStorage), + }, + []byte{}, + )) +} + +func (tx *Transaction) sign(priv *rsa.PrivateKey) []byte { + return Sign(priv, tx.CurrHash) +} + +func StringPublic(pub *rsa.PublicKey) string { + return Base64Encode(x509.MarshalPKCS1PublicKey(pub)) +} + +func (user *User) Public() *rsa.PublicKey { + return &(user.PrivateKey).PublicKey +} + +func HashSum(data []byte) []byte { + hash := sha256.Sum256(data) + return hash[:] +} + +func ToBytes(num uint64) []byte { + var data = new(bytes.Buffer) + err := binary.Write(data, binary.BigEndian, num) + if err != nil { + return nil + } + return data.Bytes() +} + +func Sign(priv *rsa.PrivateKey, data []byte) []byte { + signature, err := rsa.SignPSS(rand.Reader, priv, crypto.SHA256, data, nil) + if err != nil { + return nil + } + return signature +} + +func Base64Encode(data []byte) string { + return base64.StdEncoding.EncodeToString(data) +} + +func (block *Block) AddTransaction(chain *BlockChain, tx *Transaction) error { + if tx == nil { + return errors.New("tx is null") + } + if tx.Value == 0 { + return errors.New("tx value = 0") + } + if len(block.Transactions) == TXS_LIMIT && tx.Sender != STORAGE_CHAIN { + return errors.New("len tx = limit") + } + var balanceInChain uint64 + balanceInTX := tx.Value + tx.ToStorage + if value, ok := block.Mapping[tx.Sender]; ok { + balanceInChain = value + } else { + balanceInChain = chain.Balance(tx.Sender) + } + if tx.Value > START_PERCENT && tx.ToStorage != STORAGE_REWARD { + return errors.New("storage reward pass") + } + if balanceInTX > balanceInChain { + return errors.New("insufficient funds") + } + block.Mapping[tx.Sender] = balanceInChain - balanceInTX + block.addBalance(chain, tx.Receiver, tx.Value) + block.addBalance(chain, STORAGE_CHAIN, tx.ToStorage) + block.Transactions = append(block.Transactions, *tx) + return nil +} + +func (chain *BlockChain) Balance(address string) uint64 { + var ( + sblock string + block *Block + balance uint64 + ) + rows, err := chain.DB.Query("SELECT Block FROM BlockChain WHERE Id <= $1 ORDER BY Id DESC", + chain.index) + if err != nil { + return balance + } + defer rows.Close() + for rows.Next() { + rows.Scan(&sblock) + block = DeserializeBlock(sblock) + if value, ok := block.Mapping[address]; ok { + balance = value + break + } + } + return balance +} + +func (block *Block) addBalance(chain *BlockChain, receiver string, value uint64) { + var balanceInChain uint64 + if v, ok := block.Mapping[receiver]; ok { + balanceInChain = v + } else { + balanceInChain = chain.Balance(receiver) + } + block.Mapping[receiver] = balanceInChain + value +} + +func DeserializeBlock(data string) *Block { + var block Block + err := json.Unmarshal([]byte(data), &block) + if err != nil { + return nil + } + return &block +} + +func (block *Block) Accept(chain *BlockChain, user *User, ch chan bool) error { + if !block.transactionsIsValid(chain) { + return errors.New("transactions is not valid") + } + block.AddTransaction(chain, &Transaction{ + RandBytes: GenerateRandomBytes(RAND_BYTES), + Sender: STORAGE_CHAIN, + Receiver: user.Address(), + Value: STORAGE_REWARD, + }) + block.TimeStamp = time.Now().Format(time.RFC3339) + block.CurrHash = block.hash() + block.Signature = block.sign(user.Private()) + block.Nonce = block.proof(ch) + return nil +} + +func (block *Block) transactionsIsValid(chain *BlockChain) bool { + lentxs := len(block.Transactions) + plusStorage := 0 + for i := 0; i < lentxs; i++ { + if block.Transactions[i].Sender == STORAGE_CHAIN { + plusStorage = 1 + break + } + } + if lentxs == 0 || lentxs > TXS_LIMIT+plusStorage { + return false + } + for i := 0; i < lentxs-1; i++ { + for j := i + 1; j < lentxs; j++ { + if bytes.Equal(block.Transactions[i].RandBytes, block.Transactions[j].RandBytes) { + return false + } + if block.Transactions[i].Sender == STORAGE_CHAIN && + block.Transactions[j].Sender == STORAGE_CHAIN { + return false + } + } + } + for i := 0; i < lentxs; i++ { + tx := block.Transactions[i] + if tx.Sender == STORAGE_CHAIN { + if tx.Receiver != block.Miner || tx.Value != STORAGE_REWARD { + return false + } + } else { + if !tx.hashIsValid() { + return false + } + if !tx.signIsValid() { + return false + } + } + if !block.balanceIsValid(chain, tx.Sender) { + return false + } + if !block.balanceIsValid(chain, tx.Receiver) { + return false + } + } + return true +} + +func (block *Block) hash() []byte { + var tempHash []byte + for _, tx := range block.Transactions { + tempHash = HashSum(bytes.Join( + [][]byte{ + tempHash, + tx.CurrHash, + }, + []byte{}, + )) + } + var list []string + for hash := range block.Mapping { + list = append(list, hash) + } + sort.Strings(list) + for _, hash := range list { + tempHash = HashSum(bytes.Join( + [][]byte{ + tempHash, + []byte(hash), + ToBytes(block.Mapping[hash]), + }, + []byte{}, + )) + } + return HashSum(bytes.Join( + [][]byte{ + tempHash, + ToBytes(uint64(block.Difficulty)), + block.PrevHash, + []byte(block.Miner), + []byte(block.TimeStamp), + }, + []byte{}, + )) +} + +func (block *Block) sign(priv *rsa.PrivateKey) []byte { + return Sign(priv, block.CurrHash) +} + +func (block *Block) proof(ch chan bool) uint64 { + return ProofOfWork(block.CurrHash, block.Difficulty, ch) +} + +func (tx *Transaction) hashIsValid() bool { + return bytes.Equal(tx.hash(), tx.CurrHash) +} + +func (tx *Transaction) signIsValid() bool { + return Verify(ParsePublic(tx.Sender), tx.CurrHash, tx.Signature) == nil +} + +func (block *Block) balanceIsValid(chain *BlockChain, address string) bool { + if _, ok := block.Mapping[address]; !ok { + return false + } + lentxs := len(block.Transactions) + balanceInChain := chain.Balance(address) + balanceSubBlock := uint64(0) + balanceAddBlock := uint64(0) + for j := 0; j < lentxs; j++ { + tx := block.Transactions[j] + if tx.Sender == address { + balanceSubBlock += tx.Value + tx.ToStorage + } + if tx.Receiver == address { + balanceAddBlock += tx.Value + } + if STORAGE_CHAIN == address { + balanceAddBlock += tx.ToStorage + } + } + if (balanceInChain + balanceAddBlock - balanceSubBlock) != block.Mapping[address] { + return false + } + return true +} + +func ProofOfWork(blockHash []byte, difficulty uint8, ch chan bool) uint64 { + var ( + Target = big.NewInt(1) + intHash = big.NewInt(1) + nonce = uint64(mrand.Intn(math.MaxUint32)) + hash []byte + ) + Target.Lsh(Target, 256 - uint(difficulty)) + for nonce < math.MaxUint64 { + select { + case <-ch: + if DEBUG { + fmt.Println() + } + return nonce + default: + hash = HashSum(bytes.Join( + [][]byte{ + blockHash, + ToBytes(nonce), + }, + []byte{}, + )) + if DEBUG { + fmt.Printf("\rMining: %s", Base64Encode(hash)) + } + intHash.SetBytes(hash) + if intHash.Cmp(Target) == -1 { + if DEBUG { + fmt.Println() + } + return nonce + } + nonce++ + } + } + return nonce +} + +func Verify(pub *rsa.PublicKey, data, sign []byte) error { + return rsa.VerifyPSS(pub, crypto.SHA256, data, sign, nil) +} + +func ParsePublic(pubData string) *rsa.PublicKey { + pub, err := x509.ParsePKCS1PublicKey(Base64Decode(pubData)) + if err != nil { + return nil + } + return pub +} + +func init() { + mrand.Seed(time.Now().UnixNano()) +} + +func Base64Decode(data string) []byte { + result, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil + } + return result +} + +func GeneratePrivate(bits uint) *rsa.PrivateKey { + priv, err := rsa.GenerateKey(rand.Reader, int(bits)) + if err != nil { + return nil + } + return priv +} + +func StringPrivate(priv *rsa.PrivateKey) string { + return Base64Encode(x509.MarshalPKCS1PrivateKey(priv)) +} + +func ParsePrivate(privData string) *rsa.PrivateKey { + pub, err := x509.ParsePKCS1PrivateKey(Base64Decode(privData)) + if err != nil { + return nil + } + return pub +} + +func NewUser() *User { + return &User{ + PrivateKey: GeneratePrivate(KEY_SIZE), + } +} + +func LoadUser(purse string) *User { + priv := ParsePrivate(purse) + if priv == nil { + return nil + } + return &User{ + PrivateKey: priv, + } +} + +func (user *User) Purse() string { + return StringPrivate(user.Private()) +} + +func (chain *BlockChain) LastHash() []byte { + var hash string + row := chain.DB.QueryRow("SELECT Hash FROM BlockChain ORDER BY Id DESC") + row.Scan(&hash) + return Base64Decode(hash) +} + +func (block *Block) IsValid(chain *BlockChain) bool { + switch { + case block == nil: + return false + case block.Difficulty != DIFFICULTY: + return false + case !block.hashIsValid(chain, chain.Size()): + return false + case !block.signIsValid(): + return false + case !block.proofIsValid(): + return false + case !block.mappingIsValid(): + return false + case !block.timeIsValid(chain, chain.Size()): + return false + case !block.transactionsIsValid(chain): + return false + } + return true +} + +func SerializeTX(tx *Transaction) string { + jsonData, err := json.MarshalIndent(*tx, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func DeserializeTX(data string) *Transaction { + var tx Transaction + err := json.Unmarshal([]byte(data), &tx) + if err != nil { + return nil + } + return &tx +} + +func (block *Block) hashIsValid(chain *BlockChain, index uint64) bool { + if !bytes.Equal(block.hash(), block.CurrHash) { + return false + } + var id uint64 + row := chain.DB.QueryRow("SELECT Id FROM BlockChain WHERE Hash=$1", + Base64Encode(block.PrevHash)) + row.Scan(&id) + return id == index +} + +func (block *Block) signIsValid() bool { + return Verify(ParsePublic(block.Miner), block.CurrHash, block.Signature) == nil +} + +func (block *Block) proofIsValid() bool { + intHash := big.NewInt(1) + Target := big.NewInt(1) + hash := HashSum(bytes.Join( + [][]byte{ + block.CurrHash, + ToBytes(block.Nonce), + }, + []byte{}, + )) + intHash.SetBytes(hash) + Target.Lsh(Target, 256 - uint(block.Difficulty)) + if intHash.Cmp(Target) == -1 { + return true + } + return false +} + +func (block *Block) mappingIsValid() bool { + for hash := range block.Mapping { + if hash == STORAGE_CHAIN { + continue + } + flag := false + for _, tx := range block.Transactions { + if tx.Sender == hash || tx.Receiver == hash { + flag = true + break + } + } + if !flag { + return false + } + } + return true +} + +func (block *Block) timeIsValid(chain *BlockChain, index uint64) bool { + btime, err := time.Parse(time.RFC3339, block.TimeStamp) + if err != nil { + return false + } + diff := time.Now().Sub(btime) + if diff < 0 { + return false + } + var sblock string + row := chain.DB.QueryRow("SELECT Block FROM BlockChain WHERE Hash=$1", + Base64Encode(block.PrevHash)) + row.Scan(&sblock) + lblock := DeserializeBlock(sblock) + if lblock == nil { + return false + } + ltime, err := time.Parse(time.RFC3339, lblock.TimeStamp) + if err != nil { + return false + } + result := btime.Sub(ltime) + return result > 0 +} diff --git a/_example/_blockchain/main.go b/_example/_blockchain/main.go new file mode 100644 index 0000000..e62a1df --- /dev/null +++ b/_example/_blockchain/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + bc "./blockchain" +) + +const ( + DBNAME = "blockchain.db" +) + +func main() { + miner := bc.NewUser() + bc.NewChain(DBNAME, miner.Address()) + + chain := bc.LoadChain(DBNAME) + + for i := 0; i < 3; i++ { + block := bc.NewBlock(miner.Address(), chain.LastHash()) + + block.AddTransaction(chain, + bc.NewTransaction(miner, chain.LastHash(), "aaa", 5)) + block.AddTransaction(chain, + bc.NewTransaction(miner, chain.LastHash(), "bbb", 3)) + + block.Accept(chain, miner, make(chan bool)) + chain.AddBlock(block) + } + + var sblock string + rows, err := chain.DB.Query("SELECT Block FROM BlockChain") + if err != nil { + panic("error: query to db") + } + for rows.Next() { + rows.Scan(&sblock) + fmt.Println(sblock) + } +} diff --git a/_example/_network/main.go b/_example/_network/main.go new file mode 100644 index 0000000..4edc62b --- /dev/null +++ b/_example/_network/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "time" + "strings" + nt "./network" +) + +const ( + TO_UPPER = iota + 1 + TO_LOWER +) + +const ( + ADDRESS = ":8080" +) + +func main() { + var ( + res = new(nt.Package) + msg = "Hello, World!" + ) + + go nt.Listen(ADDRESS, handleServer) + time.Sleep(500 * time.Millisecond) + + res = nt.Send(ADDRESS, &nt.Package{ + Option: TO_UPPER, + Data: msg, + }) + fmt.Println(res.Data) + + res = nt.Send(ADDRESS, &nt.Package{ + Option: TO_LOWER, + Data: res.Data, + }) + fmt.Println(res.Data) +} + +func handleServer(conn nt.Conn, pack *nt.Package) { + nt.Handle(TO_UPPER, conn, pack, handleToUpper) + nt.Handle(TO_LOWER, conn, pack, handleToLower) +} + +func handleToUpper(pack *nt.Package) string { + return strings.ToUpper(pack.Data) +} + +func handleToLower(pack *nt.Package) string { + return strings.ToLower(pack.Data) +} diff --git a/_example/_network/network/network.go b/_example/_network/network/network.go new file mode 100644 index 0000000..cc28756 --- /dev/null +++ b/_example/_network/network/network.go @@ -0,0 +1,118 @@ +package network + +import ( + "net" + "strings" + "encoding/json" +) + +const ( + DMAXSIZE = (2 << 20) // (2^20)*2 = 2MiB + BUFFSIZE = (4 << 10) // (2^10)*4 = 4KiB +) + +const ( + ENDBYTES = "\000\005\007\001\001\007\005\000" +) + +type Listener net.Listener +type Conn net.Conn + +type Package struct { + Option int + Data string +} + +func Send(address string, pack *Package) *Package { + conn, err := net.Dial("tcp", address) + if err != nil { + return nil + } + conn.Write([]byte(SerializePackage(pack) + ENDBYTES)) + return readPackage(conn) +} + +func SerializePackage(pack *Package) string { + jsonData, err := json.MarshalIndent(*pack, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func Handle(option int, conn Conn, pack *Package, handle func(*Package) string) bool { + if pack.Option != option { + return false + } + conn.Write([]byte(SerializePackage(&Package{ + Option: option, + Data: handle(pack), + }) + ENDBYTES)) + return true +} + +func readPackage(conn net.Conn) *Package { + var ( + data string + size = uint64(0) + buffer = make([]byte, BUFFSIZE) + ) + for { + length, err := conn.Read(buffer) + if err != nil { + return nil + } + size += uint64(length) + if size > DMAXSIZE { + return nil + } + data += string(buffer[:length]) + if strings.Contains(data, ENDBYTES) { + data = strings.Split(data, ENDBYTES)[0] + break + } + } + return DeserializePackage(data) +} + +func DeserializePackage(data string) *Package { + var pack Package + err := json.Unmarshal([]byte(data), &pack) + if err != nil { + return nil + } + return &pack +} + +func Listen(address string, handle func(Conn, *Package)) Listener { + splited := strings.Split(address, ":") + if len(splited) != 2 { + return nil + } + listener, err := net.Listen("tcp", "0.0.0.0:"+splited[1]) + if err != nil { + return nil + } + go serve(listener, handle) + return Listener(listener) +} + +func serve(listener net.Listener, handle func(Conn, *Package)) { + defer listener.Close() + for { + conn, err := listener.Accept() + if err != nil { + break + } + go handleConn(conn, handle) + } +} +func handleConn(conn net.Conn, handle func(Conn, *Package)) { + defer conn.Close() + pack := readPackage(conn) + if pack == nil { + return + } + handle(Conn(conn), pack) +} + diff --git a/_example/_result/addr.json b/_example/_result/addr.json new file mode 100644 index 0000000..3cb1ce6 --- /dev/null +++ b/_example/_result/addr.json @@ -0,0 +1,4 @@ +[ + ":8080", + ":9090" +] diff --git a/_example/_result/blockchain/blockchain.go b/_example/_result/blockchain/blockchain.go new file mode 100644 index 0000000..b424731 --- /dev/null +++ b/_example/_result/blockchain/blockchain.go @@ -0,0 +1,692 @@ +package blockchain + +import ( + "time" + "errors" + "bytes" + "sort" + "database/sql" + _ "github.com/mattn/go-sqlite3" + "os" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "math" + "math/big" + mrand "math/rand" +) + +type BlockChain struct { + index uint64 + DB *sql.DB +} + +type Block struct { + Nonce uint64 + Difficulty uint8 + CurrHash []byte + PrevHash []byte + Transactions []Transaction + Mapping map[string]uint64 + Miner string + Signature []byte + TimeStamp string +} + +type Transaction struct { + RandBytes []byte + PrevBlock []byte + Sender string + Receiver string + Value uint64 + ToStorage uint64 + CurrHash []byte + Signature []byte +} + +const ( + CREATE_TABLE = ` +CREATE TABLE BlockChain ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Hash VARCHAR(44) UNIQUE, + Block TEXT +); +` +) + +const ( + GENESIS_BLOCK = "GENESIS-BLOCK" + STORAGE_VALUE = 100 + GENESIS_REWARD = 100 + STORAGE_CHAIN = "STORAGE-CHAIN" +) + +const ( + DIFFICULTY = 20 +) + +const ( + RAND_BYTES = 32 + START_PERCENT = 10 + STORAGE_REWARD = 1 +) + +const ( + TXS_LIMIT = 2 +) + +const ( + DEBUG = true +) + +const ( + KEY_SIZE = 512 +) + +func NewChain(filename, receiver string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + file.Close() + db, err := sql.Open("sqlite3", filename) + if err != nil { + return err + } + defer db.Close() + _, err = db.Exec(CREATE_TABLE) + chain := &BlockChain{ + DB: db, + } + genesis := &Block{ + CurrHash: []byte(GENESIS_BLOCK), + Mapping: make(map[string]uint64), + Miner: receiver, + TimeStamp: time.Now().Format(time.RFC3339), + } + genesis.Mapping[STORAGE_CHAIN] = STORAGE_VALUE + genesis.Mapping[receiver] = GENESIS_REWARD + chain.AddBlock(genesis) + return nil +} + +func (chain *BlockChain) AddBlock(block *Block) { + chain.index += 1 + chain.DB.Exec("INSERT INTO BlockChain (Hash, Block) VALUES ($1, $2)", + Base64Encode(block.CurrHash), + SerializeBlock(block), + ) +} + +func SerializeBlock(block *Block) string { + jsonData, err := json.MarshalIndent(*block, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func LoadChain(filename string) *BlockChain { + db, err := sql.Open("sqlite3", filename) + if err != nil { + return nil + } + chain := &BlockChain{ + DB: db, + } + chain.index = chain.Size() + return chain +} + +func (chain *BlockChain) Size() uint64 { + var index uint64 + row := chain.DB.QueryRow("SELECT Id FROM BlockChain ORDER BY Id DESC") + row.Scan(&index) + return index +} + +func NewBlock(miner string, prevHash []byte) *Block { + return &Block{ + Difficulty: DIFFICULTY, + PrevHash: prevHash, + Miner: miner, + Mapping: make(map[string]uint64), + } +} + +func NewTransaction(user *User, lasthash []byte, to string, value uint64) *Transaction { + tx := &Transaction{ + RandBytes: GenerateRandomBytes(RAND_BYTES), + PrevBlock: lasthash, + Sender: user.Address(), + Receiver: to, + Value: value, + } + if value > START_PERCENT { + tx.ToStorage = STORAGE_REWARD + } + tx.CurrHash = tx.hash() + tx.Signature = tx.sign(user.Private()) + return tx +} + +type User struct { + PrivateKey *rsa.PrivateKey +} + +func GenerateRandomBytes(max uint) []byte { + var slice []byte = make([]byte, max) + _, err := rand.Read(slice) + if err != nil { + return nil + } + return slice +} + +func (user *User) Address() string { + return StringPublic(user.Public()) +} + +func (user *User) Private() *rsa.PrivateKey { + return user.PrivateKey +} + +func (tx *Transaction) hash() []byte { + return HashSum(bytes.Join( + [][]byte{ + tx.RandBytes, + tx.PrevBlock, + []byte(tx.Sender), + []byte(tx.Receiver), + ToBytes(tx.Value), + ToBytes(tx.ToStorage), + }, + []byte{}, + )) +} + +func (tx *Transaction) sign(priv *rsa.PrivateKey) []byte { + return Sign(priv, tx.CurrHash) +} + +func StringPublic(pub *rsa.PublicKey) string { + return Base64Encode(x509.MarshalPKCS1PublicKey(pub)) +} + +func (user *User) Public() *rsa.PublicKey { + return &(user.PrivateKey).PublicKey +} + +func HashSum(data []byte) []byte { + hash := sha256.Sum256(data) + return hash[:] +} + +func ToBytes(num uint64) []byte { + var data = new(bytes.Buffer) + err := binary.Write(data, binary.BigEndian, num) + if err != nil { + return nil + } + return data.Bytes() +} + +func Sign(priv *rsa.PrivateKey, data []byte) []byte { + signature, err := rsa.SignPSS(rand.Reader, priv, crypto.SHA256, data, nil) + if err != nil { + return nil + } + return signature +} + +func Base64Encode(data []byte) string { + return base64.StdEncoding.EncodeToString(data) +} + +func (block *Block) AddTransaction(chain *BlockChain, tx *Transaction) error { + if tx == nil { + return errors.New("tx is null") + } + if tx.Value == 0 { + return errors.New("tx value = 0") + } + if len(block.Transactions) == TXS_LIMIT && tx.Sender != STORAGE_CHAIN { + return errors.New("len tx = limit") + } + var balanceInChain uint64 + balanceInTX := tx.Value + tx.ToStorage + if value, ok := block.Mapping[tx.Sender]; ok { + balanceInChain = value + } else { + balanceInChain = chain.Balance(tx.Sender) + } + if tx.Value > START_PERCENT && tx.ToStorage != STORAGE_REWARD { + return errors.New("storage reward pass") + } + if balanceInTX > balanceInChain { + return errors.New("insufficient funds") + } + block.Mapping[tx.Sender] = balanceInChain - balanceInTX + block.addBalance(chain, tx.Receiver, tx.Value) + block.addBalance(chain, STORAGE_CHAIN, tx.ToStorage) + block.Transactions = append(block.Transactions, *tx) + return nil +} + +func (chain *BlockChain) Balance(address string) uint64 { + var ( + sblock string + block *Block + balance uint64 + ) + rows, err := chain.DB.Query("SELECT Block FROM BlockChain WHERE Id <= $1 ORDER BY Id DESC", + chain.index) + if err != nil { + return balance + } + defer rows.Close() + for rows.Next() { + rows.Scan(&sblock) + block = DeserializeBlock(sblock) + if value, ok := block.Mapping[address]; ok { + balance = value + break + } + } + return balance +} + +func (block *Block) addBalance(chain *BlockChain, receiver string, value uint64) { + var balanceInChain uint64 + if v, ok := block.Mapping[receiver]; ok { + balanceInChain = v + } else { + balanceInChain = chain.Balance(receiver) + } + block.Mapping[receiver] = balanceInChain + value +} + +func DeserializeBlock(data string) *Block { + var block Block + err := json.Unmarshal([]byte(data), &block) + if err != nil { + return nil + } + return &block +} + +func (block *Block) Accept(chain *BlockChain, user *User, ch chan bool) error { + if !block.transactionsIsValid(chain) { + return errors.New("transactions is not valid") + } + block.AddTransaction(chain, &Transaction{ + RandBytes: GenerateRandomBytes(RAND_BYTES), + Sender: STORAGE_CHAIN, + Receiver: user.Address(), + Value: STORAGE_REWARD, + }) + block.TimeStamp = time.Now().Format(time.RFC3339) + block.CurrHash = block.hash() + block.Signature = block.sign(user.Private()) + block.Nonce = block.proof(ch) + return nil +} + +func (block *Block) transactionsIsValid(chain *BlockChain) bool { + lentxs := len(block.Transactions) + plusStorage := 0 + for i := 0; i < lentxs; i++ { + if block.Transactions[i].Sender == STORAGE_CHAIN { + plusStorage = 1 + break + } + } + if lentxs == 0 || lentxs > TXS_LIMIT+plusStorage { + return false + } + for i := 0; i < lentxs-1; i++ { + for j := i + 1; j < lentxs; j++ { + if bytes.Equal(block.Transactions[i].RandBytes, block.Transactions[j].RandBytes) { + return false + } + if block.Transactions[i].Sender == STORAGE_CHAIN && + block.Transactions[j].Sender == STORAGE_CHAIN { + return false + } + } + } + for i := 0; i < lentxs; i++ { + tx := block.Transactions[i] + if tx.Sender == STORAGE_CHAIN { + if tx.Receiver != block.Miner || tx.Value != STORAGE_REWARD { + return false + } + } else { + if !tx.hashIsValid() { + return false + } + if !tx.signIsValid() { + return false + } + } + if !block.balanceIsValid(chain, tx.Sender) { + return false + } + if !block.balanceIsValid(chain, tx.Receiver) { + return false + } + } + return true +} + +func (block *Block) hash() []byte { + var tempHash []byte + for _, tx := range block.Transactions { + tempHash = HashSum(bytes.Join( + [][]byte{ + tempHash, + tx.CurrHash, + }, + []byte{}, + )) + } + var list []string + for hash := range block.Mapping { + list = append(list, hash) + } + sort.Strings(list) + for _, hash := range list { + tempHash = HashSum(bytes.Join( + [][]byte{ + tempHash, + []byte(hash), + ToBytes(block.Mapping[hash]), + }, + []byte{}, + )) + } + return HashSum(bytes.Join( + [][]byte{ + tempHash, + ToBytes(uint64(block.Difficulty)), + block.PrevHash, + []byte(block.Miner), + []byte(block.TimeStamp), + }, + []byte{}, + )) +} + +func (block *Block) sign(priv *rsa.PrivateKey) []byte { + return Sign(priv, block.CurrHash) +} + +func (block *Block) proof(ch chan bool) uint64 { + return ProofOfWork(block.CurrHash, block.Difficulty, ch) +} + +func (tx *Transaction) hashIsValid() bool { + return bytes.Equal(tx.hash(), tx.CurrHash) +} + +func (tx *Transaction) signIsValid() bool { + return Verify(ParsePublic(tx.Sender), tx.CurrHash, tx.Signature) == nil +} + +func (block *Block) balanceIsValid(chain *BlockChain, address string) bool { + if _, ok := block.Mapping[address]; !ok { + return false + } + lentxs := len(block.Transactions) + balanceInChain := chain.Balance(address) + balanceSubBlock := uint64(0) + balanceAddBlock := uint64(0) + for j := 0; j < lentxs; j++ { + tx := block.Transactions[j] + if tx.Sender == address { + balanceSubBlock += tx.Value + tx.ToStorage + } + if tx.Receiver == address { + balanceAddBlock += tx.Value + } + if STORAGE_CHAIN == address { + balanceAddBlock += tx.ToStorage + } + } + if (balanceInChain + balanceAddBlock - balanceSubBlock) != block.Mapping[address] { + return false + } + return true +} + +func ProofOfWork(blockHash []byte, difficulty uint8, ch chan bool) uint64 { + var ( + Target = big.NewInt(1) + intHash = big.NewInt(1) + nonce = uint64(mrand.Intn(math.MaxUint32)) + hash []byte + ) + Target.Lsh(Target, 256 - uint(difficulty)) + for nonce < math.MaxUint64 { + select { + case <-ch: + if DEBUG { + fmt.Println() + } + return nonce + default: + hash = HashSum(bytes.Join( + [][]byte{ + blockHash, + ToBytes(nonce), + }, + []byte{}, + )) + if DEBUG { + fmt.Printf("\rMining: %s", Base64Encode(hash)) + } + intHash.SetBytes(hash) + if intHash.Cmp(Target) == -1 { + if DEBUG { + fmt.Println() + } + return nonce + } + nonce++ + } + } + return nonce +} + +func Verify(pub *rsa.PublicKey, data, sign []byte) error { + return rsa.VerifyPSS(pub, crypto.SHA256, data, sign, nil) +} + +func ParsePublic(pubData string) *rsa.PublicKey { + pub, err := x509.ParsePKCS1PublicKey(Base64Decode(pubData)) + if err != nil { + return nil + } + return pub +} + +func init() { + mrand.Seed(time.Now().UnixNano()) +} + +func Base64Decode(data string) []byte { + result, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil + } + return result +} + +func GeneratePrivate(bits uint) *rsa.PrivateKey { + priv, err := rsa.GenerateKey(rand.Reader, int(bits)) + if err != nil { + return nil + } + return priv +} + +func StringPrivate(priv *rsa.PrivateKey) string { + return Base64Encode(x509.MarshalPKCS1PrivateKey(priv)) +} + +func ParsePrivate(privData string) *rsa.PrivateKey { + pub, err := x509.ParsePKCS1PrivateKey(Base64Decode(privData)) + if err != nil { + return nil + } + return pub +} + +func NewUser() *User { + return &User{ + PrivateKey: GeneratePrivate(KEY_SIZE), + } +} + +func LoadUser(purse string) *User { + priv := ParsePrivate(purse) + if priv == nil { + return nil + } + return &User{ + PrivateKey: priv, + } +} + +func (user *User) Purse() string { + return StringPrivate(user.Private()) +} + +func (chain *BlockChain) LastHash() []byte { + var hash string + row := chain.DB.QueryRow("SELECT Hash FROM BlockChain ORDER BY Id DESC") + row.Scan(&hash) + return Base64Decode(hash) +} + +func (block *Block) IsValid(chain *BlockChain) bool { + switch { + case block == nil: + return false + case block.Difficulty != DIFFICULTY: + return false + case !block.hashIsValid(chain, chain.Size()): + return false + case !block.signIsValid(): + return false + case !block.proofIsValid(): + return false + case !block.mappingIsValid(): + return false + case !block.timeIsValid(chain, chain.Size()): + return false + case !block.transactionsIsValid(chain): + return false + } + return true +} + +func SerializeTX(tx *Transaction) string { + jsonData, err := json.MarshalIndent(*tx, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func DeserializeTX(data string) *Transaction { + var tx Transaction + err := json.Unmarshal([]byte(data), &tx) + if err != nil { + return nil + } + return &tx +} + +func (block *Block) hashIsValid(chain *BlockChain, index uint64) bool { + if !bytes.Equal(block.hash(), block.CurrHash) { + return false + } + var id uint64 + row := chain.DB.QueryRow("SELECT Id FROM BlockChain WHERE Hash=$1", + Base64Encode(block.PrevHash)) + row.Scan(&id) + return id == index +} + +func (block *Block) signIsValid() bool { + return Verify(ParsePublic(block.Miner), block.CurrHash, block.Signature) == nil +} + +func (block *Block) proofIsValid() bool { + intHash := big.NewInt(1) + Target := big.NewInt(1) + hash := HashSum(bytes.Join( + [][]byte{ + block.CurrHash, + ToBytes(block.Nonce), + }, + []byte{}, + )) + intHash.SetBytes(hash) + Target.Lsh(Target, 256 - uint(block.Difficulty)) + if intHash.Cmp(Target) == -1 { + return true + } + return false +} + +func (block *Block) mappingIsValid() bool { + for hash := range block.Mapping { + if hash == STORAGE_CHAIN { + continue + } + flag := false + for _, tx := range block.Transactions { + if tx.Sender == hash || tx.Receiver == hash { + flag = true + break + } + } + if !flag { + return false + } + } + return true +} + +func (block *Block) timeIsValid(chain *BlockChain, index uint64) bool { + btime, err := time.Parse(time.RFC3339, block.TimeStamp) + if err != nil { + return false + } + diff := time.Now().Sub(btime) + if diff < 0 { + return false + } + var sblock string + row := chain.DB.QueryRow("SELECT Block FROM BlockChain WHERE Hash=$1", + Base64Encode(block.PrevHash)) + row.Scan(&sblock) + lblock := DeserializeBlock(sblock) + if lblock == nil { + return false + } + ltime, err := time.Parse(time.RFC3339, lblock.TimeStamp) + if err != nil { + return false + } + result := btime.Sub(ltime) + return result > 0 +} diff --git a/_example/_result/client.go b/_example/_result/client.go new file mode 100644 index 0000000..ee87139 --- /dev/null +++ b/_example/_result/client.go @@ -0,0 +1,244 @@ +package main + +import ( + bc "./blockchain" + nt "./network" + "bufio" + "encoding/json" + "io/ioutil" + "fmt" + "os" + "strconv" + "strings" +) + +var ( + Addresses []string + User *bc.User +) + +const ( + ADD_BLOCK = iota + 1 + ADD_TRNSX + GET_BLOCK + GET_LHASH + GET_BLNCE +) + +func init() { + if len(os.Args) < 2 { + panic("failed: len(os.Args) < 2") + } + var ( + addrStr = "" + userNewStr = "" + userLoadStr = "" + ) + var ( + addrExist = false + userNewExist = false + userLoadExist = false + ) + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + switch { + case strings.HasPrefix(arg, "-loadaddr:"): + addrStr = strings.Replace(arg, "-loadaddr:", "", 1) + addrExist = true + case strings.HasPrefix(arg, "-newuser:"): + userNewStr = strings.Replace(arg, "-newuser:", "", 1) + userNewExist = true + case strings.HasPrefix(arg, "-loaduser:"): + userLoadStr = strings.Replace(arg, "-loaduser:", "", 1) + userLoadExist = true + } + } + if !(userNewExist || userLoadExist) || !addrExist { + panic("failed: !(userNewExist || userLoadExist) || !addrExist") + } + err := json.Unmarshal([]byte(readFile(addrStr)), &Addresses) + if err != nil { + panic("failed: load addresses") + } + if len(Addresses) == 0 { + panic("failed: len(Addresses) == 0") + } + if userNewExist { + User = userNew(userNewStr) + } + if userLoadExist { + User = userLoad(userLoadStr) + } + if User == nil { + panic("failed: load user") + } +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} + +func userNew(filename string) *bc.User { + user := bc.NewUser() + if user == nil { + return nil + } + err := writeFile(filename, user.Purse()) + if err != nil { + return nil + } + return user +} + +func userLoad(filename string) *bc.User { + priv := readFile(filename) + if priv == "" { + return nil + } + user := bc.LoadUser(priv) + if user == nil { + return nil + } + return user +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} + +func handleClient() { + var ( + message string + splited []string + ) + for { + message = inputString("> ") + splited = strings.Split(message, " ") + switch splited[0] { + case "/exit": + os.Exit(0) + case "/user": + if len(splited) < 2 { + fmt.Println("failed: len(user) < 2\n") + continue + } + switch splited[1] { + case "address": + userAddress() + case "purse": + userPurse() + case "balance": + userBalance() + } + case "/chain": + if len(splited) < 2 { + fmt.Println("failed: len(chain) < 2\n") + continue + } + switch splited[1] { + case "print": + chainPrint() + case "tx": + chainTX(splited[1:]) + case "balance": + chainBalance(splited[1:]) + } + default: + fmt.Println("command undefined\n") + } + } +} + +func inputString(begin string) string { + fmt.Print(begin) + msg, _ := bufio.NewReader(os.Stdin).ReadString('\n') + return strings.Replace(msg, "\n", "", 1) +} + +func userAddress() { + fmt.Println("Address:", User.Address(), "\n") +} + +func userPurse() { + fmt.Println("Purse:", User.Purse(), "\n") +} + +func userBalance() { + printBalance(User.Address()) +} + +func chainPrint() { + for i := 0; ; i++ { + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLOCK, + Data: fmt.Sprintf("%d", i), + }) + if res.Data == "" { + break + } + fmt.Printf("[%d] => %s\n", i+1, res.Data) + } + fmt.Println() +} + +func chainTX(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + num, err := strconv.Atoi(splited[2]) + if err != nil { + fmt.Println("failed: strconv.Atoi(num)\n") + return + } + for _, addr := range Addresses { + res := nt.Send(addr, &nt.Package{ + Option: GET_LHASH, + }) + if res == nil { + continue + } + tx := bc.NewTransaction(User, bc.Base64Decode(res.Data), splited[1], uint64(num)) + res = nt.Send(addr, &nt.Package{ + Option: ADD_TRNSX, + Data: bc.SerializeTX(tx), + }) + if res == nil { + continue + } + if res.Data == "ok" { + fmt.Printf("ok: (%s)\n", addr) + } else { + fmt.Printf("fail: (%s)\n", addr) + } + } + fmt.Println() +} + +func chainBalance(splited []string) { + if len(splited) != 2 { + fmt.Println("fail: len(splited) != 2\n") + return + } + printBalance(splited[1]) +} + +func printBalance(useraddr string) { + for _, addr := range Addresses { + res := nt.Send(addr, &nt.Package{ + Option: GET_BLNCE, + Data: useraddr, + }) + fmt.Printf("Balance (%s): %s coins\n", addr, res.Data) + } + fmt.Println() +} + +func main() { + handleClient() +} + diff --git a/_example/_result/network/network.go b/_example/_result/network/network.go new file mode 100644 index 0000000..cc28756 --- /dev/null +++ b/_example/_result/network/network.go @@ -0,0 +1,118 @@ +package network + +import ( + "net" + "strings" + "encoding/json" +) + +const ( + DMAXSIZE = (2 << 20) // (2^20)*2 = 2MiB + BUFFSIZE = (4 << 10) // (2^10)*4 = 4KiB +) + +const ( + ENDBYTES = "\000\005\007\001\001\007\005\000" +) + +type Listener net.Listener +type Conn net.Conn + +type Package struct { + Option int + Data string +} + +func Send(address string, pack *Package) *Package { + conn, err := net.Dial("tcp", address) + if err != nil { + return nil + } + conn.Write([]byte(SerializePackage(pack) + ENDBYTES)) + return readPackage(conn) +} + +func SerializePackage(pack *Package) string { + jsonData, err := json.MarshalIndent(*pack, "", "\t") + if err != nil { + return "" + } + return string(jsonData) +} + +func Handle(option int, conn Conn, pack *Package, handle func(*Package) string) bool { + if pack.Option != option { + return false + } + conn.Write([]byte(SerializePackage(&Package{ + Option: option, + Data: handle(pack), + }) + ENDBYTES)) + return true +} + +func readPackage(conn net.Conn) *Package { + var ( + data string + size = uint64(0) + buffer = make([]byte, BUFFSIZE) + ) + for { + length, err := conn.Read(buffer) + if err != nil { + return nil + } + size += uint64(length) + if size > DMAXSIZE { + return nil + } + data += string(buffer[:length]) + if strings.Contains(data, ENDBYTES) { + data = strings.Split(data, ENDBYTES)[0] + break + } + } + return DeserializePackage(data) +} + +func DeserializePackage(data string) *Package { + var pack Package + err := json.Unmarshal([]byte(data), &pack) + if err != nil { + return nil + } + return &pack +} + +func Listen(address string, handle func(Conn, *Package)) Listener { + splited := strings.Split(address, ":") + if len(splited) != 2 { + return nil + } + listener, err := net.Listen("tcp", "0.0.0.0:"+splited[1]) + if err != nil { + return nil + } + go serve(listener, handle) + return Listener(listener) +} + +func serve(listener net.Listener, handle func(Conn, *Package)) { + defer listener.Close() + for { + conn, err := listener.Accept() + if err != nil { + break + } + go handleConn(conn, handle) + } +} +func handleConn(conn net.Conn, handle func(Conn, *Package)) { + defer conn.Close() + pack := readPackage(conn) + if pack == nil { + return + } + handle(Conn(conn), pack) +} + diff --git a/_example/_result/node.go b/_example/_result/node.go new file mode 100644 index 0000000..b70a7da --- /dev/null +++ b/_example/_result/node.go @@ -0,0 +1,370 @@ +package main + +import ( + bc "./blockchain" + nt "./network" + "encoding/json" + "fmt" + "os" + "strings" + "io/ioutil" + "bytes" + "database/sql" + "encoding/hex" + _ "github.com/mattn/go-sqlite3" + "io" + "strconv" +) + +var ( + Addresses []string + User *bc.User +) + +const ( + ADD_BLOCK = iota + 1 + ADD_TRNSX + GET_BLOCK + GET_LHASH + GET_BLNCE +) + +var ( + Filename string + Serve string + Chain *bc.BlockChain + Block *bc.Block +) + +var ( + IsMining bool + BreakMining = make(chan bool) +) + +const ( + SEPARATOR = "_SEPARATOR_" +) + +func init() { + if len(os.Args) < 2 { + panic("failed: len(os.Args) < 2") + } + var ( + serveStr = "" + addrStr = "" + userNewStr = "" + userLoadStr = "" + chainNewStr = "" + chainLoadStr = "" + ) + var ( + serveExist = false + addrExist = false + userNewExist = false + userLoadExist = false + chainNewExist = false + chainLoadExist = false + ) + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + switch { + case strings.HasPrefix(arg, "-serve:"): + serveStr = strings.Replace(arg, "-serve:", "", 1) + serveExist = true + case strings.HasPrefix(arg, "-loadaddr:"): + addrStr = strings.Replace(arg, "-loadaddr:", "", 1) + addrExist = true + case strings.HasPrefix(arg, "-newuser:"): + userNewStr = strings.Replace(arg, "-newuser:", "", 1) + userNewExist = true + case strings.HasPrefix(arg, "-loaduser:"): + userLoadStr = strings.Replace(arg, "-loaduser:", "", 1) + userLoadExist = true + case strings.HasPrefix(arg, "-newchain:"): + chainNewStr = strings.Replace(arg, "-newchain:", "", 1) + chainNewExist = true + case strings.HasPrefix(arg, "-loadchain:"): + chainLoadStr = strings.Replace(arg, "-loadchain:", "", 1) + chainLoadExist = true + } + } + if !(userNewExist || userLoadExist) || !(chainNewExist || chainLoadExist) || + !serveExist || !addrExist { + panic("failed: !(userNewExist || userLoadExist)"+ + "|| !(chainNewExist || chainLoadExist) || !serveExist || !addrExist") + } + + Serve = serveStr + var addresses []string + err := json.Unmarshal([]byte(readFile(addrStr)), &addresses) + if err != nil { + panic("failed: load addresses") + } + var mapaddr = make(map[string]bool) + for _, addr := range addresses { + if addr == Serve { + continue + } + if _, ok := mapaddr[addr]; ok { + continue + } + mapaddr[addr] = true + Addresses = append(Addresses, addr) + } + if userNewExist { + User = userNew(userNewStr) + } + if userLoadExist { + User = userLoad(userLoadStr) + } + if User == nil { + panic("failed: load user") + } + if chainNewExist { + Filename = chainNewStr + Chain = chainNew(chainNewStr) + } + if chainLoadExist { + Filename = chainLoadStr + Chain = chainLoad(chainLoadStr) + } + if Chain == nil { + panic("failed: load chain") + } + Block = bc.NewBlock(User.Address(), Chain.LastHash()) +} + +func chainNew(filename string) *bc.BlockChain { + err := bc.NewChain(filename, User.Address()) + if err != nil { + return nil + } + return bc.LoadChain(filename) +} + +func chainLoad(filename string) *bc.BlockChain { + chain := bc.LoadChain(filename) + if chain == nil { + return nil + } + return chain +} + +func main() { + nt.Listen(Serve, handleServer) + for { + fmt.Scanln() + } +} + +func handleServer(conn nt.Conn, pack *nt.Package) { + nt.Handle(ADD_BLOCK, conn, pack, addBlock) + nt.Handle(ADD_TRNSX, conn, pack, addTransaction) + nt.Handle(GET_BLOCK, conn, pack, getBlock) + nt.Handle(GET_LHASH, conn, pack, getLastHash) + nt.Handle(GET_BLNCE, conn, pack, getBalance) +} + +func addBlock(pack *nt.Package) string { + splited := strings.Split(pack.Data, SEPARATOR) + if len(splited) != 3 { + return "fail" + } + block := bc.DeserializeBlock(splited[2]) + if !block.IsValid(Chain) { + currSize := Chain.Size() + num, err := strconv.Atoi(splited[1]) + if err != nil { + return "fail" + } + if currSize < uint64(num) { + return compareChains(splited[0], uint64(num)) + } + return "fail" + } + Chain.AddBlock(block) + Block = bc.NewBlock(User.Address(), Chain.LastHash()) + if IsMining { + BreakMining <- true + IsMining = false + } + return "ok" +} + +func addTransaction(pack *nt.Package) string { + var tx = bc.DeserializeTX(pack.Data) + if tx == nil || len(Block.Transactions) == bc.TXS_LIMIT { + return "fail" + } + if Block.AddTransaction(Chain, tx) != nil { + return "fail" + } + if len(Block.Transactions) == bc.TXS_LIMIT { + go func() { + block := *Block + IsMining = true + res := (&block).Accept(Chain, User, BreakMining) + IsMining = false + if res == nil && bytes.Equal(block.PrevHash, Block.PrevHash) { + Chain.AddBlock(&block) + pushBlockToNet(&block) + } + Block = bc.NewBlock(User.Address(), Chain.LastHash()) + }() + } + return "ok" +} + +func getBlock(pack *nt.Package) string { + num, err := strconv.Atoi(pack.Data) + if err != nil { + return "" + } + size := Chain.Size() + if uint64(num) < size { + return selectBlock(Chain, num) + } + return "" +} + +func getLastHash(pack *nt.Package) string { + return bc.Base64Encode(Chain.LastHash()) +} + +func getBalance(pack *nt.Package) string { + return fmt.Sprintf("%d", Chain.Balance(pack.Data)) +} + +func compareChains(address string, num uint64) string { + filename := "temp_" + hex.EncodeToString(bc.GenerateRandomBytes(8)) + file, err := os.Create(filename) + if err != nil { + return "fail" + } + file.Close() + defer func() { + os.Remove(filename) + }() + res := nt.Send(address, &nt.Package{ + Option: GET_BLOCK, + Data: fmt.Sprintf("%d", 0), + }) + if res == nil { + return "fail" + } + genesis := bc.DeserializeBlock(res.Data) + if genesis == nil { + return "fail" + } + db, err := sql.Open("sqlite3", filename) + if err != nil { + return "fail" + } + defer db.Close() + _, err = db.Exec(bc.CREATE_TABLE) + chain := &bc.BlockChain{ + DB: db, + } + chain.AddBlock(genesis) + defer func() { + chain.DB.Close() + }() + for i := uint64(1); i < num; i++ { + res := nt.Send(address, &nt.Package{ + Option: GET_BLOCK, + Data: fmt.Sprintf("%d", i), + }) + if res == nil { + return "fail" + } + block := bc.DeserializeBlock(res.Data) + if block == nil { + return "fail" + } + if !block.IsValid(chain) { + return "fail" + } + chain.AddBlock(block) + } + Chain.DB.Close() + os.Remove(Filename) + copyFile(filename, Filename) + Chain = bc.LoadChain(Filename) + Block = bc.NewBlock(User.Address(), Chain.LastHash()) + if IsMining { + BreakMining <- true + IsMining = false + } + return "ok" +} + +func pushBlockToNet(block *bc.Block) { + var sblock = bc.SerializeBlock(block) + for _, addr := range Addresses { + nt.Send(addr, &nt.Package{ + Option: ADD_BLOCK, + Data: Serve + SEPARATOR + fmt.Sprintf("%d", Chain.Size()) + SEPARATOR + sblock, + }) + } +} + +func selectBlock(chain *bc.BlockChain, i int) string { + var block string + row := chain.DB.QueryRow("SELECT Block FROM BlockChain WHERE Id=$1", i+1) + row.Scan(&block) + return block +} + +func copyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} + +func userNew(filename string) *bc.User { + user := bc.NewUser() + if user == nil { + return nil + } + err := writeFile(filename, user.Purse()) + if err != nil { + return nil + } + return user +} + +func userLoad(filename string) *bc.User { + priv := readFile(filename) + if priv == "" { + return nil + } + user := bc.LoadUser(priv) + if user == nil { + return nil + } + return user +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} diff --git a/blockchain/block.go b/blockchain/block.go index bea3b44..923b171 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -36,6 +36,9 @@ func (block *Block) Accept(chain *BlockChain, user *User, ch chan bool) error { } func (block *Block) AddTransaction(chain *BlockChain, tx *Transaction) error { + if tx == nil { + return errors.New("tx is null") + } if tx.Value == 0 { return errors.New("tx value = 0") } @@ -84,6 +87,16 @@ func (block *Block) IsValid(chain *BlockChain) bool { return true } +func (block *Block) addBalance(chain *BlockChain, receiver string, value uint64) { + var balanceInChain uint64 + if v, ok := block.Mapping[receiver]; ok { + balanceInChain = v + } else { + balanceInChain = chain.Balance(receiver) + } + block.Mapping[receiver] = balanceInChain + value +} + func (block *Block) timeIsValid(chain *BlockChain, index uint64) bool { btime, err := time.Parse(time.RFC3339, block.TimeStamp) if err != nil { @@ -128,11 +141,9 @@ func (block *Block) transactionsIsValid(chain *BlockChain) bool { } for i := 0; i < lentxs-1; i++ { for j := i + 1; j < lentxs; j++ { - // rand bytes not be equal if bytes.Equal(block.Transactions[i].RandBytes, block.Transactions[j].RandBytes) { return false } - // storage tx only one if block.Transactions[i].Sender == STORAGE_CHAIN && block.Transactions[j].Sender == STORAGE_CHAIN { return false @@ -141,7 +152,6 @@ func (block *Block) transactionsIsValid(chain *BlockChain) bool { } for i := 0; i < lentxs; i++ { tx := block.Transactions[i] - // storage tx has no hash and signature if tx.Sender == STORAGE_CHAIN { if tx.Receiver != block.Miner || tx.Value != STORAGE_REWARD { return false diff --git a/blockchain/chain.go b/blockchain/chain.go index 45fa61b..17da963 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1,10 +1,7 @@ package blockchain import ( - "bytes" "database/sql" - "errors" - "fmt" _ "github.com/mattn/go-sqlite3" "os" "time" @@ -13,12 +10,12 @@ import ( func NewChain(filename, receiver string) error { file, err := os.Create(filename) if err != nil { - return errors.New("create database") + return err } file.Close() db, err := sql.Open("sqlite3", filename) if err != nil { - return errors.New("open database") + return err } defer db.Close() _, err = db.Exec(CREATE_TABLE) @@ -56,82 +53,6 @@ func (chain *BlockChain) Size() uint64 { return index } -func (chain *BlockChain) PrintChain() error { - rows, err := chain.DB.Query("SELECT Id, Block FROM BlockChain") - if err != nil { - return err - } - defer rows.Close() - var ( - sblock string - block *Block - index uint64 - size uint64 - ) - for rows.Next() { - rows.Scan(&index, &sblock) - block = DeserializeBlock(sblock) - - if index == 1 { - if !bytes.Equal(block.CurrHash, []byte(GENESIS_BLOCK)) { - fmt.Printf("[%d][FAILED] Genesis block undefined\n", index) - } else { - fmt.Printf("[%d][SUCCESS] Genesis block found\n", index) - } - goto print - } - - if block.Difficulty != DIFFICULTY { - fmt.Printf("[%d][FAILED] difficulty is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] difficulty is valid\n", index) - } - - if !block.hashIsValid(chain, index-1) { - fmt.Printf("[%d][FAILED] hash is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] hash is valid\n", index) - } - - if !block.signIsValid() { - fmt.Printf("[%d][FAILED] sign is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] sign is valid\n", index) - } - - if !block.proofIsValid() { - fmt.Printf("[%d][FAILED] proof is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] proof is valid\n", index) - } - - if !block.mappingIsValid() { - fmt.Printf("[%d][FAILED] mapping is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] mapping is valid\n", index) - } - - if !block.timeIsValid(chain, index-1) { - fmt.Printf("[%d][FAILED] time is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] time is valid\n", index) - } - - size = chain.index - chain.index = index - 1 - if !block.transactionsIsValid(chain) { - fmt.Printf("[%d][FAILED] transactions is not valid\n", index) - } else { - fmt.Printf("[%d][SUCCESS] transactions is valid\n", index) - } - chain.index = size - - print: - fmt.Printf("[%d] => %s\n\n", index, sblock) - } - return nil -} - func (chain *BlockChain) Balance(address string) uint64 { var ( sblock string diff --git a/blockchain/crypto.go b/blockchain/crypto.go index 6525494..3a2c76f 100644 --- a/blockchain/crypto.go +++ b/blockchain/crypto.go @@ -15,7 +15,7 @@ import ( mrand "math/rand" ) -func GeneratePrivate(bits uint16) *rsa.PrivateKey { +func GeneratePrivate(bits uint) *rsa.PrivateKey { priv, err := rsa.GenerateKey(rand.Reader, int(bits)) if err != nil { return nil @@ -53,7 +53,7 @@ func ProofOfWork(blockHash []byte, difficulty uint8, ch chan bool) uint64 { var ( Target = big.NewInt(1) intHash = big.NewInt(1) - nonce = uint64(mrand.Intn(math.MaxInt32)) + nonce = uint64(mrand.Intn(math.MaxUint32)) hash []byte ) Target.Lsh(Target, 256-uint(difficulty)) diff --git a/blockchain/settings.go b/blockchain/settings.go index 6d3f935..8ca4c7e 100644 --- a/blockchain/settings.go +++ b/blockchain/settings.go @@ -2,12 +2,12 @@ package blockchain import ( "database/sql" - "math/rand" + mrand "math/rand" "time" ) func init() { - rand.Seed(time.Now().UnixNano()) + mrand.Seed(time.Now().UnixNano()) } const ( diff --git a/blockchain/transaction.go b/blockchain/transaction.go index e342d61..b3bc108 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -21,16 +21,6 @@ func NewTransaction(user *User, lasthash []byte, to string, value uint64) *Trans return tx } -func (block *Block) addBalance(chain *BlockChain, receiver string, value uint64) { - var balanceInChain uint64 - if v, ok := block.Mapping[receiver]; ok { - balanceInChain = v - } else { - balanceInChain = chain.Balance(receiver) - } - block.Mapping[receiver] = balanceInChain + value -} - func (tx *Transaction) hash() []byte { return HashSum(bytes.Join( [][]byte{ diff --git a/client.go b/client.go index fc16077..8bcc2d7 100644 --- a/client.go +++ b/client.go @@ -40,6 +40,10 @@ func init() { } } + if !(userNewExist || userLoadExist) || !addrExist { + panic("failed: !(userNewExist || userLoadExist) || !addrExist") + } + err := json.Unmarshal([]byte(readFile(addrStr)), &Addresses) if err != nil { panic("failed: load addresses") @@ -48,10 +52,6 @@ func init() { panic("failed: len(Addresses) == 0") } - if !(userNewExist || userLoadExist) || !addrExist { - panic("failed: !(userNewExist || userLoadExist) || !addrExist") - } - if userNewExist { User = userNew(userNewStr) } @@ -73,7 +73,7 @@ func handleClient() { splited []string ) for { - message = inputString("") + message = inputString("> ") splited = strings.Split(message, " ") switch splited[0] { case "/exit": diff --git a/node.go b/node.go index fab4c0e..774ae62 100644 --- a/node.go +++ b/node.go @@ -108,7 +108,6 @@ func main() { nt.Listen(Serve, handleServer) for { fmt.Scanln() - Chain.PrintChain() } }