├── .gitignore ├── .dockerignore ├── utils ├── utils.go └── utils_test.go ├── nano.go ├── Dockerfile ├── node ├── alarm.go ├── node_vote.go ├── node_net.go ├── node_blocks.go ├── node.go └── node_test.go ├── store ├── store_test.go └── store.go ├── types └── types.go ├── LICENSE ├── blocks ├── blocks_test.go └── blocks.go ├── address ├── address_test.go └── address.go ├── README.md ├── uint128 ├── uint128.go └── uint128_test.go └── wallet ├── wallet_test.go └── wallet.go /.gitignore: -------------------------------------------------------------------------------- 1 | /nano 2 | /DATA 3 | /*/TESTDATA 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | /nano 3 | /DATA 4 | /*/TESTDATA 5 | Dockerfile 6 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func Reversed(str []byte) (result []byte) { 4 | for i := len(str) - 1; i >= 0; i-- { 5 | result = append(result, str[i]) 6 | } 7 | return result 8 | } 9 | -------------------------------------------------------------------------------- /nano.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/frankh/nano/node" 7 | "github.com/frankh/nano/store" 8 | ) 9 | 10 | func main() { 11 | store.Init(store.LiveConfig) 12 | 13 | keepAliveSender := node.NewAlarm(node.AlarmFn(node.SendKeepAlives), []interface{}{node.PeerList}, 20*time.Second) 14 | node.ListenForUdp() 15 | 16 | keepAliveSender.Stop() 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.9 AS gobuild 2 | 3 | WORKDIR /go/src/github.com/frankh/nano 4 | RUN go get \ 5 | github.com/frankh/crypto/ed25519 \ 6 | github.com/golang/crypto/blake2b \ 7 | github.com/pkg/errors \ 8 | github.com/dgraph-io/badger 9 | 10 | COPY . ./ 11 | 12 | RUN go build -o nano . 13 | 14 | 15 | FROM debian:8-slim 16 | 17 | COPY --from=gobuild /go/src/github.com/frankh/nano/nano /nano 18 | 19 | ENTRYPOINT ["/nano"] 20 | -------------------------------------------------------------------------------- /node/alarm.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type AlarmFn func([]interface{}) 8 | 9 | type Alarm struct { 10 | fn AlarmFn 11 | fnParams []interface{} 12 | duration time.Duration 13 | done chan bool 14 | } 15 | 16 | func NewAlarm(fn AlarmFn, fnParams []interface{}, d time.Duration) *Alarm { 17 | a := &Alarm{fn, fnParams, d, make(chan bool)} 18 | 19 | go a.Run() 20 | 21 | return a 22 | } 23 | 24 | func (a *Alarm) Run() { 25 | ticker := time.NewTicker(a.duration) 26 | defer ticker.Stop() 27 | 28 | for { 29 | select { 30 | case <-a.done: 31 | return 32 | case _ = <-ticker.C: 33 | a.fn(a.fnParams) 34 | } 35 | } 36 | 37 | } 38 | 39 | func (a *Alarm) Stop() { 40 | a.done <- true 41 | } 42 | -------------------------------------------------------------------------------- /store/store_test.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/frankh/nano/blocks" 8 | ) 9 | 10 | func TestInit(t *testing.T) { 11 | Init(TestConfigLive) 12 | 13 | os.RemoveAll(TestConfigLive.Path) 14 | } 15 | 16 | func TestGenesisBalance(t *testing.T) { 17 | Init(TestConfigLive) 18 | 19 | block := FetchBlock(blocks.LiveGenesisBlockHash) 20 | 21 | if GetBalance(block).String() != "ffffffffffffffffffffffffffffffff" { 22 | t.Errorf("Genesis block has invalid initial balance") 23 | } 24 | os.RemoveAll(TestConfigLive.Path) 25 | } 26 | 27 | func TestMissingBlock(t *testing.T) { 28 | Init(TestConfig) 29 | 30 | block := FetchBlock(blocks.LiveGenesisBlockHash) 31 | 32 | if block != nil { 33 | t.Errorf("Found live genesis on test config") 34 | } 35 | os.RemoveAll(TestConfig.Path) 36 | } 37 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEmpty(t *testing.T) { 9 | str := []byte{} 10 | 11 | if !bytes.Equal(Reversed(str), []byte{}) { 12 | t.Errorf("Failed on empty slice") 13 | } 14 | } 15 | 16 | func TestSingle(t *testing.T) { 17 | str := []byte{1} 18 | 19 | if !bytes.Equal(Reversed(str), []byte{1}) { 20 | t.Errorf("Failed on slice with single element") 21 | } 22 | } 23 | 24 | func TestReverseOrder(t *testing.T) { 25 | str := []byte{1, 2, 3} 26 | 27 | if !bytes.Equal(Reversed(str), []byte{3, 2, 1}) { 28 | t.Errorf("Failed on slice with single element") 29 | } 30 | } 31 | 32 | func TestReverseUnordered(t *testing.T) { 33 | str := []byte{1, 2, 1, 3, 1} 34 | 35 | if !bytes.Equal(Reversed(str), []byte{1, 3, 1, 2, 1}) { 36 | t.Errorf("Failed on slice with single element") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | 7 | "github.com/frankh/crypto/ed25519" 8 | ) 9 | 10 | type BlockHash string 11 | type Account string 12 | type Work string 13 | type Signature string 14 | 15 | func (hash BlockHash) ToBytes() []byte { 16 | bytes, err := hex.DecodeString(string(hash)) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return bytes 21 | } 22 | 23 | func (sig Signature) ToBytes() []byte { 24 | bytes, err := hex.DecodeString(string(sig)) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return bytes 29 | } 30 | 31 | func (hash BlockHash) Sign(private_key ed25519.PrivateKey) Signature { 32 | sig := hex.EncodeToString(ed25519.Sign(private_key, hash.ToBytes())) 33 | return Signature(strings.ToUpper(sig)) 34 | } 35 | 36 | func BlockHashFromBytes(b []byte) BlockHash { 37 | return BlockHash(strings.ToUpper(hex.EncodeToString(b))) 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Frank Hamand 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /node/node_vote.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/golang/crypto/blake2b" 8 | ) 9 | 10 | type MessageVote struct { 11 | Account [32]byte 12 | Signature [64]byte 13 | Sequence [8]byte 14 | MessageBlock 15 | } 16 | 17 | func (m *MessageVote) Hash() []byte { 18 | hash, _ := blake2b.New(32, nil) 19 | 20 | hash.Write(m.MessageBlock.ToBlock().Hash().ToBytes()) 21 | hash.Write(m.Sequence[:]) 22 | 23 | return hash.Sum(nil) 24 | } 25 | 26 | func (m *MessageVote) Read(messageBlockType byte, buf *bytes.Buffer) error { 27 | n1, err1 := buf.Read(m.Account[:]) 28 | n2, err2 := buf.Read(m.Signature[:]) 29 | n3, err3 := buf.Read(m.Sequence[:]) 30 | 31 | err4 := m.MessageBlock.Read(messageBlockType, buf) 32 | 33 | if err1 != nil || err2 != nil || err3 != nil || err4 != nil { 34 | return errors.New("Failed to read message vote") 35 | } 36 | 37 | if n1 != 32 || n2 != 64 || n3 != 8 { 38 | return errors.New("Failed to read message vote") 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (m *MessageVote) Write(buf *bytes.Buffer) error { 45 | n1, err1 := buf.Write(m.Account[:]) 46 | n2, err2 := buf.Write(m.Signature[:]) 47 | n3, err3 := buf.Write(m.Sequence[:]) 48 | 49 | err4 := m.MessageBlock.Write(buf) 50 | 51 | if err1 != nil || err2 != nil || err3 != nil || err4 != nil { 52 | return errors.New("Failed to read message vote") 53 | } 54 | 55 | if n1 != 32 || n2 != 64 || n3 != 8 { 56 | return errors.New("Failed to read message vote") 57 | } 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /node/node_net.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "math/rand" 7 | "net" 8 | "time" 9 | ) 10 | 11 | const packetSize = 512 12 | const numberOfPeersToShare = 8 13 | 14 | var DefaultPeer = Peer{ 15 | net.ParseIP("::ffff:192.168.0.70"), 16 | 7075, 17 | nil, 18 | } 19 | 20 | var PeerList = []Peer{DefaultPeer} 21 | var PeerSet = map[string]bool{DefaultPeer.String(): true} 22 | 23 | func (p *Peer) SendMessage(m Message) error { 24 | now := time.Now() 25 | p.LastReachout = &now 26 | 27 | outConn, err := net.DialUDP("udp", nil, p.Addr()) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | buf := bytes.NewBuffer(nil) 33 | err = m.Write(buf) 34 | if err != nil { 35 | return err 36 | } 37 | outConn.Write(buf.Bytes()) 38 | 39 | return nil 40 | } 41 | 42 | func ListenForUdp() { 43 | log.Printf("Listening for udp packets on 7075") 44 | ln, err := net.ListenPacket("udp", ":7075") 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | buf := make([]byte, packetSize) 50 | 51 | for { 52 | n, _, err := ln.ReadFrom(buf) 53 | if err != nil { 54 | continue 55 | } 56 | if n > 0 { 57 | handleMessage(bytes.NewBuffer(buf[:n])) 58 | } 59 | } 60 | } 61 | 62 | func SendKeepAlive(peer Peer) error { 63 | randomPeers := make([]Peer, 0) 64 | randIndices := rand.Perm(len(PeerList)) 65 | for n, i := range randIndices { 66 | if n == numberOfPeersToShare { 67 | break 68 | } 69 | randomPeers = append(randomPeers, PeerList[i]) 70 | } 71 | 72 | m := CreateKeepAlive(randomPeers) 73 | return peer.SendMessage(m) 74 | } 75 | 76 | func SendKeepAlives(params []interface{}) { 77 | peers := params[0].([]Peer) 78 | timeCutoff := time.Now().Add(-5 * time.Minute) 79 | 80 | for _, peer := range peers { 81 | if peer.LastReachout == nil || peer.LastReachout.Before(timeCutoff) { 82 | SendKeepAlive(peer) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /blocks/blocks_test.go: -------------------------------------------------------------------------------- 1 | package blocks 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/frankh/nano/address" 9 | "github.com/frankh/nano/utils" 10 | ) 11 | 12 | func TestSignMessage(t *testing.T) { 13 | test_private_key_data := "34F0A37AAD20F4A260F0A5B3CB3D7FB50673212263E58A380BC10474BB039CE4" 14 | 15 | block := FromJson([]byte(`{ 16 | "type": "open", 17 | "source": "B0311EA55708D6A53C75CDBF88300259C6D018522FE3D4D0A242E431F9E8B6D0", 18 | "representative": "nano_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpiij4txtdo", 19 | "account": "nano_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpiij4txtdo", 20 | "work": "9680625b39d3363d", 21 | "signature": "ECDA914373A2F0CA1296475BAEE40500A7F0A7AD72A5A80C81D7FAB7F6C802B2CC7DB50F5DD0FB25B2EF11761FA7344A158DD5A700B21BD47DE5BD0F63153A02" 22 | }`)) 23 | 24 | signature_bytes := SignMessage(test_private_key_data, block.Hash().ToBytes()) 25 | signature := strings.ToUpper(hex.EncodeToString(signature_bytes)) 26 | 27 | if signature != string(block.GetSignature()) { 28 | t.Errorf("Signature %s was expected to be %s", signature, block.GetSignature()) 29 | } 30 | 31 | } 32 | 33 | func TestGenerateWork(t *testing.T) { 34 | WorkThreshold = 0xfff0000000000000 35 | GenerateWork(LiveGenesisBlock) 36 | } 37 | 38 | func BenchmarkGenerateWork(b *testing.B) { 39 | WorkThreshold = 0xfff0000000000000 40 | for n := 0; n < b.N; n++ { 41 | GenerateWork(LiveGenesisBlock) 42 | } 43 | } 44 | 45 | func TestValidateWork(t *testing.T) { 46 | WorkThreshold = 0xffffffc000000000 47 | 48 | live_block_hash, _ := address.AddressToPub(LiveGenesisBlock.Account) 49 | live_work_bytes, _ := hex.DecodeString(string(LiveGenesisBlock.Work)) 50 | live_bad_work, _ := hex.DecodeString("0000000000000000") 51 | 52 | if !ValidateBlockWork(LiveGenesisBlock) { 53 | t.Errorf("Work validation failed for genesis block") 54 | return 55 | } 56 | 57 | // A bit of a redundandy test to ensure ValidateBlockWork is correct 58 | if !ValidateWork(live_block_hash, utils.Reversed(live_work_bytes)) { 59 | t.Errorf("Work validation failed for genesis block") 60 | } 61 | 62 | if ValidateWork(live_block_hash, live_bad_work) { 63 | t.Errorf("Work validation passed for bad work") 64 | } 65 | 66 | } 67 | 68 | func TestHashOpen(t *testing.T) { 69 | if LiveGenesisBlock.Hash() != LiveGenesisBlockHash { 70 | t.Errorf("Genesis block hash is not correct, expected %s, got %s", LiveGenesisBlockHash, LiveGenesisBlock.Hash()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /address/address_test.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/frankh/nano/types" 8 | ) 9 | 10 | var valid_addresses = []types.Account{ 11 | "nano_38nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 12 | "nano_1awsn43we17c1oshdru4azeqjz9wii41dy8npubm4rg11so7dx3jtqgoeahy", 13 | "nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4", 14 | "nano_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh", 15 | "nano_3hd4ezdgsp15iemx7h81in7xz5tpxi43b6b41zn3qmwiuypankocw3awes5k", 16 | "nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs", 17 | "xrb_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs", 18 | } 19 | 20 | var invalid_addresses = []types.Account{ 21 | "nano_38nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r7", 22 | "xrc_38nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 23 | "nano38nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 24 | "nano8nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 25 | "nano_8nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 26 | "xrb_8nm8t5rimw6h6j7wyokbs8jiygzs7baoha4pqzhfw1k79npyr1km8w6y7r8", 27 | } 28 | 29 | func TestAddressToPub(t *testing.T) { 30 | pub, _ := AddressToPub(types.Account("nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3")) 31 | 32 | if hex.EncodeToString(pub) != "e89208dd038fbb269987689621d52292ae9c35941a7484756ecced92a65093ba" { 33 | t.Errorf("Address got wrong public key") 34 | } 35 | 36 | } 37 | 38 | func TestValidateAddress(t *testing.T) { 39 | for _, addr := range valid_addresses { 40 | if !ValidateAddress(addr) { 41 | t.Errorf("Valid address did not validate") 42 | } 43 | } 44 | 45 | for _, addr := range invalid_addresses { 46 | if ValidateAddress(addr) { 47 | t.Errorf("Invalid address was validated") 48 | } 49 | } 50 | } 51 | 52 | func TestKeypairFromSeed(t *testing.T) { 53 | seed := "1234567890123456789012345678901234567890123456789012345678901234" 54 | 55 | // Generated from the official RaiBlocks wallet using above seed. 56 | expected := map[uint32]types.Account{ 57 | 0: "nano_3iwi45me3cgo9aza9wx5f7rder37hw11xtc1ek8psqxw5oxb8cujjad6qp9y", 58 | 1: "nano_3a9d1h6wt3zp8cqd6dhhgoyizmk1ciemqkrw97ysrphn7anm6xko1wxakaa1", 59 | 2: "nano_1dz36wby1azyjgh7t9nopjm3k5rduhmntercoz545my9s8nm7gcuthuq9fmq", 60 | 3: "nano_1fb7kaqaue49kf9w4mb9w3scuxipbdm3ez6ibnri4w8qexzg5f4r7on1dmxb", 61 | 4: "nano_3h9a64yqueuij1j9odt119r3ymm8n83wyyz7o9u7ram1tgfhsh1zqwjtzid9", 62 | } 63 | 64 | for i := uint32(0); i < uint32(len(expected)); i++ { 65 | pub, _ := KeypairFromSeed(seed, i) 66 | if PubKeyToAddress(pub) != expected[i] { 67 | t.Errorf("Wallet generation from seed created the wrong address") 68 | } 69 | } 70 | } 71 | 72 | func BenchmarkGenerateAddress(b *testing.B) { 73 | for n := 0; n < b.N; n++ { 74 | pub, _ := GenerateKey() 75 | PubKeyToAddress(pub) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Nano 2 | ======= 3 | 4 | An implementation of the [Nano](https://nano.org/) protocol written from scratch in Go (golang). 5 | 6 | About the Project 7 | ----------------- 8 | 9 | A crypto currency has to be resilient to survive, and the network is only as resilient as the weakest link. With only one implementation of the protocol, any bugs that are found affect the entire network. The aim of this project is to create an alternative implementation that is 100% compatible with the reference implementation to create a more robust network. 10 | 11 | Additionally, there is no reference specification for the Nano protocol, only a high level overview. I've had to learn the protocol from reading the source-code. I'm hoping a second implementation will be useful for others to learn the protocol. 12 | 13 | Components 14 | ---------- 15 | 16 | Eventually the project will contain the following components: 17 | 18 | * [GoNano](https://github.com/frankh/nano) 19 | > A support library containing common functions, e.g. block validation, hashing, proof of work, etc 20 | * [Nano Vanity](https://github.com/frankh/nano-vanity) 21 | > A tool to generate vanity addresses (See https://en.bitcoin.it/wiki/Vanitygen) 22 | * [GoNano Node](#) - Coming Soon... 23 | > A full node implementation compatible with the official Nano wallet, but with faster initial sync times out of the box. 24 | * [GoNano Wallet](#) - Coming Soon... 25 | > A GUI Wallet that can either run as a node or as a light wallet. 26 | 27 | Milestones 28 | ---------- 29 | 30 | * ~Vanity Address Generator~ 31 | > A simple project to get the basic public-key cryptography functions working and tested. 32 | - Done! ([Nano Vanity](https://github.com/frankh/nano-vanity)) 33 | * GoNano Node 34 | * A basic node that can validate and store blocks sent to it 35 | * ~Data structures~ 36 | * ~Database~ 37 | * ~Proof of work~ 38 | * ~Cryptographic functions~ 39 | * ~Basic wallet functions~ 40 | * Networking 41 | * ~Receiving keepalives and blocks~ 42 | - ~keepalives~ 43 | - ~publish~ 44 | - ~confirm_ack~ 45 | * Sending keepalives 46 | * Add broadcasting and discovery 47 | * Add RPC interface 48 | * Add voting 49 | * Add compatibility with existing Nano Nodes 50 | * Add spam defence and blacklisting of bad nodes 51 | * Add complete testing harness 52 | * Add fast syncing 53 | * GoNano Wallet 54 | * Basic UI, creating/sending/receiving transactions 55 | * Add seed restore, account generation, changing representatives 56 | * Add bundled node and light wallet/node selection option 57 | * UI Polish and distributables 58 | 59 | Contributing 60 | ============ 61 | 62 | Any pull requests would be welcome! 63 | 64 | I haven't been using Go for very long so any style/organisation fixes would be greatly appreciated. 65 | 66 | Feel free to donate some Nano to nano_1frankh36p3e4cy4xrtj79d5rmcgce9wh4zke366gik19gifb5kxcnoju3y5 to help keep me motivated :beers: 67 | 68 | -------------------------------------------------------------------------------- /uint128/uint128.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package uint128 16 | 17 | import ( 18 | "encoding/binary" 19 | "encoding/hex" 20 | 21 | "github.com/pkg/errors" 22 | ) 23 | 24 | // Uint128 is a big-endian 128 bit unsigned integer which wraps two uint64s. 25 | type Uint128 struct { 26 | Hi, Lo uint64 27 | } 28 | 29 | // GetBytes returns a big-endian byte representation. 30 | func (u Uint128) GetBytes() []byte { 31 | buf := make([]byte, 16) 32 | binary.BigEndian.PutUint64(buf[:8], u.Hi) 33 | binary.BigEndian.PutUint64(buf[8:], u.Lo) 34 | return buf 35 | } 36 | 37 | // String returns a hexadecimal string representation. 38 | func (u Uint128) String() string { 39 | return hex.EncodeToString(u.GetBytes()) 40 | } 41 | 42 | // Equal returns whether or not the Uint128 are equivalent. 43 | func (u Uint128) Equal(o Uint128) bool { 44 | return u.Hi == o.Hi && u.Lo == o.Lo 45 | } 46 | 47 | // Compare compares the two Uint128. 48 | func (u Uint128) Compare(o Uint128) int { 49 | if u.Hi > o.Hi { 50 | return 1 51 | } else if u.Hi < o.Hi { 52 | return -1 53 | } else if u.Lo > o.Lo { 54 | return 1 55 | } else if u.Lo < o.Lo { 56 | return -1 57 | } 58 | return 0 59 | } 60 | 61 | // Add returns a new Uint128 incremented by n. 62 | func (u Uint128) Add(n Uint128) Uint128 { 63 | lo := u.Lo + n.Lo 64 | hi := u.Hi + n.Hi 65 | if u.Lo > lo { 66 | hi++ 67 | } 68 | return Uint128{hi, lo} 69 | } 70 | 71 | func (u Uint128) Sub(n Uint128) Uint128 { 72 | lo := u.Lo - n.Lo 73 | hi := u.Hi - n.Hi 74 | if u.Lo < lo { 75 | hi-- 76 | } 77 | return Uint128{hi, lo} 78 | } 79 | 80 | // FromBytes parses the byte slice as a 128 bit big-endian unsigned integer. 81 | func FromBytes(b []byte) Uint128 { 82 | hi := binary.BigEndian.Uint64(b[:8]) 83 | lo := binary.BigEndian.Uint64(b[8:]) 84 | return Uint128{hi, lo} 85 | } 86 | 87 | // FromString parses a hexadecimal string as a 128-bit big-endian unsigned integer. 88 | func FromString(s string) (Uint128, error) { 89 | if len(s) > 32 { 90 | return Uint128{}, errors.Errorf("input string %s too large for uint128", s) 91 | } 92 | bytes, err := hex.DecodeString(s) 93 | if err != nil { 94 | return Uint128{}, errors.Wrapf(err, "could not decode %s as hex", s) 95 | } 96 | 97 | // Grow the byte slice if it's smaller than 16 bytes, by prepending 0s 98 | if len(bytes) < 16 { 99 | bytesCopy := make([]byte, 16) 100 | copy(bytesCopy[(16-len(bytes)):], bytes) 101 | bytes = bytesCopy 102 | } 103 | 104 | return FromBytes(bytes), nil 105 | } 106 | 107 | // FromInts takes in two unsigned 64-bit integers and constructs a Uint128. 108 | func FromInts(hi uint64, lo uint64) Uint128 { 109 | return Uint128{hi, lo} 110 | } 111 | -------------------------------------------------------------------------------- /wallet/wallet_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/frankh/nano/address" 8 | "github.com/frankh/nano/blocks" 9 | "github.com/frankh/nano/store" 10 | "github.com/frankh/nano/uint128" 11 | ) 12 | 13 | func TestNew(t *testing.T) { 14 | store.Init(store.TestConfig) 15 | 16 | w := New(blocks.TestPrivateKey) 17 | if w.GetBalance() != blocks.GenesisAmount { 18 | t.Errorf("Genesis block doesn't have correct balance") 19 | } 20 | } 21 | 22 | func TestPoW(t *testing.T) { 23 | blocks.WorkThreshold = 0xff00000000000000 24 | store.Init(store.TestConfig) 25 | w := New(blocks.TestPrivateKey) 26 | 27 | if w.GeneratePoWAsync() != nil || !w.WaitingForPoW() { 28 | t.Errorf("Failed to start PoW generation") 29 | } 30 | 31 | if w.GeneratePoWAsync() == nil { 32 | t.Errorf("Started PoW while already in progress") 33 | } 34 | 35 | _, err := w.Send(blocks.TestGenesisBlock.Account, uint128.FromInts(0, 1)) 36 | 37 | if err == nil { 38 | t.Errorf("Created send block without PoW") 39 | } 40 | 41 | w.WaitPoW() 42 | 43 | send, _ := w.Send(blocks.TestGenesisBlock.Account, uint128.FromInts(0, 1)) 44 | 45 | if !blocks.ValidateBlockWork(send) { 46 | t.Errorf("Invalid work") 47 | } 48 | 49 | } 50 | 51 | func TestSend(t *testing.T) { 52 | blocks.WorkThreshold = 0xff00000000000000 53 | store.Init(store.TestConfig) 54 | w := New(blocks.TestPrivateKey) 55 | 56 | w.GeneratePowSync() 57 | amount := uint128.FromInts(1, 1) 58 | 59 | send, _ := w.Send(blocks.TestGenesisBlock.Account, amount) 60 | 61 | if w.GetBalance() != blocks.GenesisAmount.Sub(amount) { 62 | t.Errorf("Balance unchanged after send") 63 | } 64 | 65 | _, err := w.Send(blocks.TestGenesisBlock.Account, blocks.GenesisAmount) 66 | if err == nil { 67 | t.Errorf("Sent more than account balance") 68 | } 69 | 70 | w.GeneratePowSync() 71 | store.StoreBlock(send) 72 | receive, _ := w.Receive(send.Hash()) 73 | store.StoreBlock(receive) 74 | 75 | if w.GetBalance() != blocks.GenesisAmount { 76 | t.Errorf("Balance not updated after receive, %x != %x", w.GetBalance().GetBytes(), blocks.GenesisAmount.GetBytes()) 77 | } 78 | 79 | } 80 | 81 | func TestOpen(t *testing.T) { 82 | blocks.WorkThreshold = 0xff00000000000000 83 | store.Init(store.TestConfig) 84 | amount := uint128.FromInts(1, 1) 85 | 86 | sendW := New(blocks.TestPrivateKey) 87 | sendW.GeneratePowSync() 88 | 89 | _, priv := address.GenerateKey() 90 | openW := New(hex.EncodeToString(priv)) 91 | send, _ := sendW.Send(openW.Address(), amount) 92 | openW.GeneratePowSync() 93 | 94 | _, err := openW.Open(send.Hash(), openW.Address()) 95 | if err == nil { 96 | t.Errorf("Expected error for referencing unstored send") 97 | } 98 | 99 | if openW.GetBalance() != uint128.FromInts(0, 0) { 100 | t.Errorf("Open should start at zero balance") 101 | } 102 | 103 | store.StoreBlock(send) 104 | _, err = openW.Open(send.Hash(), openW.Address()) 105 | if err != nil { 106 | t.Errorf("Open block failed: %s", err) 107 | } 108 | 109 | if openW.GetBalance() != amount { 110 | t.Errorf("Open balance didn't equal send amount") 111 | } 112 | 113 | _, err = openW.Open(send.Hash(), openW.Address()) 114 | if err == nil { 115 | t.Errorf("Expected error for creating duplicate open block") 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /uint128/uint128_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package uint128 16 | 17 | import ( 18 | "bytes" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestBytes(t *testing.T) { 24 | b := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 25 | 26 | i := FromBytes(b) 27 | 28 | if !bytes.Equal(i.GetBytes(), b) { 29 | t.Errorf("incorrect bytes representation for num: %v", i) 30 | } 31 | } 32 | 33 | func TestString(t *testing.T) { 34 | s := "a95e31998f38490651c02b97c7f2acca" 35 | 36 | i, _ := FromString(s) 37 | 38 | if s != i.String() { 39 | t.Errorf("incorrect string representation for num: %v", i) 40 | } 41 | } 42 | 43 | func TestStringTooLong(t *testing.T) { 44 | s := "ba95e31998f38490651c02b97c7f2acca" 45 | 46 | _, err := FromString(s) 47 | 48 | if err == nil || !strings.Contains(err.Error(), "too large") { 49 | t.Error("did not get error for encoding invalid uint128 string") 50 | } 51 | } 52 | 53 | func TestStringInvalidHex(t *testing.T) { 54 | s := "bazz95e31998849051c02b97c7f2acca" 55 | 56 | _, err := FromString(s) 57 | 58 | if err == nil || !strings.Contains(err.Error(), "could not decode") { 59 | t.Error("did not get error for encoding invalid uint128 string") 60 | } 61 | } 62 | 63 | func TestSub(t *testing.T) { 64 | testData := []struct { 65 | num Uint128 66 | expected Uint128 67 | sub Uint128 68 | }{ 69 | {Uint128{0, 1}, Uint128{0, 0}, Uint128{0, 1}}, 70 | {Uint128{18446744073709551615, 18446744073709551615}, Uint128{18446744073709551615, 18446744073709551614}, Uint128{0, 1}}, 71 | {Uint128{0, 18446744073709551615}, Uint128{0, 18446744073709551614}, Uint128{0, 1}}, 72 | {Uint128{18446744073709551615, 0}, Uint128{18446744073709551614, 18446744073709551615}, Uint128{0, 1}}, 73 | {Uint128{18446744073709551615, 0}, Uint128{18446744073709551614, 18446744073709551591}, Uint128{0, 25}}, 74 | } 75 | 76 | for _, test := range testData { 77 | res := test.num.Sub(test.sub) 78 | if res != test.expected { 79 | t.Errorf("expected: %v - %d = %v but got %v", test.num, test.sub, test.expected, res) 80 | } 81 | } 82 | } 83 | 84 | func TestAdd(t *testing.T) { 85 | testData := []struct { 86 | num Uint128 87 | expected Uint128 88 | add Uint128 89 | }{ 90 | {Uint128{0, 0}, Uint128{0, 1}, Uint128{0, 1}}, 91 | {Uint128{18446744073709551615, 18446744073709551614}, Uint128{18446744073709551615, 18446744073709551615}, Uint128{0, 1}}, 92 | {Uint128{0, 18446744073709551615}, Uint128{1, 0}, Uint128{0, 1}}, 93 | {Uint128{18446744073709551615, 0}, Uint128{18446744073709551615, 1}, Uint128{0, 1}}, 94 | {Uint128{0, 18446744073709551615}, Uint128{1, 24}, Uint128{0, 25}}, 95 | } 96 | 97 | for _, test := range testData { 98 | res := test.num.Add(test.add) 99 | if res != test.expected { 100 | t.Errorf("expected: %v + %d = %v but got %v", test.num, test.add, test.expected, res) 101 | } 102 | } 103 | } 104 | 105 | func TestEqual(t *testing.T) { 106 | testData := []struct { 107 | u1 Uint128 108 | u2 Uint128 109 | expected bool 110 | }{ 111 | {Uint128{0, 0}, Uint128{0, 1}, false}, 112 | {Uint128{1, 0}, Uint128{0, 1}, false}, 113 | {Uint128{18446744073709551615, 18446744073709551614}, Uint128{18446744073709551615, 18446744073709551615}, false}, 114 | {Uint128{0, 1}, Uint128{0, 1}, true}, 115 | {Uint128{0, 0}, Uint128{0, 0}, true}, 116 | {Uint128{314, 0}, Uint128{314, 0}, true}, 117 | {Uint128{18446744073709551615, 18446744073709551615}, Uint128{18446744073709551615, 18446744073709551615}, true}, 118 | } 119 | 120 | for _, test := range testData { 121 | 122 | if actual := test.u1.Equal(test.u2); actual != test.expected { 123 | t.Errorf("expected: %v.Equal(%v) expected %v but got %v", test.u1, test.u2, test.expected, actual) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /address/address.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base32" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "errors" 9 | 10 | "github.com/frankh/nano/types" 11 | "github.com/frankh/nano/utils" 12 | "github.com/golang/crypto/blake2b" 13 | // We've forked golang's ed25519 implementation 14 | // to use blake2b instead of sha3 15 | "github.com/frankh/crypto/ed25519" 16 | ) 17 | 18 | // nano uses a non-standard base32 character set. 19 | const EncodeNano = "13456789abcdefghijkmnopqrstuwxyz" 20 | 21 | var NanoEncoding = base32.NewEncoding(EncodeNano) 22 | 23 | func ValidateAddress(account types.Account) bool { 24 | _, err := AddressToPub(account) 25 | 26 | return err == nil 27 | } 28 | 29 | func AddressToPub(account types.Account) (public_key []byte, err error) { 30 | address := string(account) 31 | 32 | if address[:4] == "xrb_" { 33 | address = address[4:] 34 | } else if address[:5] == "nano_" { 35 | address = address[5:] 36 | } else { 37 | return nil, errors.New("Invalid address format") 38 | } 39 | // A valid nano address is 64 bytes long 40 | // First 5 are simply a hard-coded string nano_ for ease of use 41 | // The following 52 characters form the address, and the final 42 | // 8 are a checksum. 43 | // They are base 32 encoded with a custom encoding. 44 | if len(address) == 60 { 45 | // The nano address string is 260bits which doesn't fall on a 46 | // byte boundary. pad with zeros to 280bits. 47 | // (zeros are encoded as 1 in nano's 32bit alphabet) 48 | key_b32nano := "1111" + address[0:52] 49 | input_checksum := address[52:] 50 | 51 | key_bytes, err := NanoEncoding.DecodeString(key_b32nano) 52 | if err != nil { 53 | return nil, err 54 | } 55 | // strip off upper 24 bits (3 bytes). 20 padding was added by us, 56 | // 4 is unused as account is 256 bits. 57 | key_bytes = key_bytes[3:] 58 | 59 | // nano checksum is calculated by hashing the key and reversing the bytes 60 | valid := NanoEncoding.EncodeToString(GetAddressChecksum(key_bytes)) == input_checksum 61 | if valid { 62 | return key_bytes, nil 63 | } else { 64 | return nil, errors.New("Invalid address checksum") 65 | } 66 | } 67 | 68 | return nil, errors.New("Invalid address format") 69 | } 70 | 71 | func GetAddressChecksum(pub ed25519.PublicKey) []byte { 72 | hash, err := blake2b.New(5, nil) 73 | if err != nil { 74 | panic("Unable to create hash") 75 | } 76 | 77 | hash.Write(pub) 78 | return utils.Reversed(hash.Sum(nil)) 79 | } 80 | 81 | func PubKeyToAddress(pub ed25519.PublicKey) types.Account { 82 | // Pubkey is 256bits, base32 must be multiple of 5 bits 83 | // to encode properly. 84 | // Pad the start with 0's and strip them off after base32 encoding 85 | padded := append([]byte{0, 0, 0}, pub...) 86 | address := NanoEncoding.EncodeToString(padded)[4:] 87 | checksum := NanoEncoding.EncodeToString(GetAddressChecksum(pub)) 88 | 89 | return types.Account("nano_" + address + checksum) 90 | } 91 | 92 | func KeypairFromPrivateKey(private_key string) (ed25519.PublicKey, ed25519.PrivateKey) { 93 | private_bytes, _ := hex.DecodeString(private_key) 94 | pub, priv, _ := ed25519.GenerateKey(bytes.NewReader(private_bytes)) 95 | 96 | return pub, priv 97 | } 98 | 99 | func KeypairFromSeed(seed string, index uint32) (ed25519.PublicKey, ed25519.PrivateKey) { 100 | // This seems to be the standard way of producing wallets. 101 | 102 | // We hash together the seed with an address index and use 103 | // that as the private key. Whenever you "add" an address 104 | // to your wallet the wallet software increases the index 105 | // and generates a new address. 106 | hash, err := blake2b.New(32, nil) 107 | if err != nil { 108 | panic("Unable to create hash") 109 | } 110 | 111 | seed_data, err := hex.DecodeString(seed) 112 | if err != nil { 113 | panic("Invalid seed") 114 | } 115 | 116 | bs := make([]byte, 4) 117 | binary.BigEndian.PutUint32(bs, index) 118 | 119 | hash.Write(seed_data) 120 | hash.Write(bs) 121 | 122 | seed_bytes := hash.Sum(nil) 123 | pub, priv, err := ed25519.GenerateKey(bytes.NewReader(seed_bytes)) 124 | 125 | if err != nil { 126 | panic("Unable to generate ed25519 key") 127 | } 128 | 129 | return pub, priv 130 | } 131 | 132 | func GenerateKey() (ed25519.PublicKey, ed25519.PrivateKey) { 133 | pubkey, privkey, err := ed25519.GenerateKey(nil) 134 | if err != nil { 135 | panic("Unable to generate ed25519 key") 136 | } 137 | 138 | return pubkey, privkey 139 | } 140 | -------------------------------------------------------------------------------- /node/node_blocks.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "errors" 7 | 8 | "github.com/frankh/nano/address" 9 | "github.com/frankh/nano/blocks" 10 | "github.com/frankh/nano/types" 11 | "github.com/frankh/nano/uint128" 12 | "github.com/frankh/nano/utils" 13 | ) 14 | 15 | type MessageBlockCommon struct { 16 | Signature [64]byte 17 | Work [8]byte 18 | } 19 | 20 | type MessageBlock struct { 21 | Type byte 22 | SourceOrPrevious [32]byte // Source for open, previous for others 23 | RepDestOrSource [32]byte // Rep for open/change, dest for send, source for receive 24 | Account [32]byte // Account for open 25 | Balance [16]byte // Balance for send 26 | MessageBlockCommon 27 | } 28 | 29 | func (m *MessageBlockCommon) ReadCommon(buf *bytes.Buffer) error { 30 | n, err := buf.Read(m.Signature[:]) 31 | 32 | if n != len(m.Signature) { 33 | return errors.New("Wrong number of bytes in signature") 34 | } 35 | if err != nil { 36 | return err 37 | } 38 | 39 | work := make([]byte, 8) 40 | n, err = buf.Read(work) 41 | work = utils.Reversed(work) 42 | 43 | copy(m.Work[:], work) 44 | 45 | if n != len(m.Work) { 46 | return errors.New("Wrong number of bytes in work") 47 | } 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (m *MessageBlockCommon) WriteCommon(buf *bytes.Buffer) error { 56 | n, err := buf.Write(m.Signature[:]) 57 | 58 | if n != len(m.Signature) { 59 | return errors.New("Wrong number of bytes in signature") 60 | } 61 | if err != nil { 62 | return err 63 | } 64 | 65 | n, err = buf.Write(utils.Reversed(m.Work[:])) 66 | 67 | if n != len(m.Work) { 68 | return errors.New("Wrong number of bytes in work") 69 | } 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func (m *MessageBlock) ToBlock() blocks.Block { 78 | common := blocks.CommonBlock{ 79 | Work: types.Work(hex.EncodeToString(m.Work[:])), 80 | Signature: types.Signature(hex.EncodeToString(m.Signature[:])), 81 | } 82 | 83 | switch m.Type { 84 | case BlockType_open: 85 | block := blocks.OpenBlock{ 86 | types.BlockHash(hex.EncodeToString(m.SourceOrPrevious[:])), 87 | address.PubKeyToAddress(m.RepDestOrSource[:]), 88 | address.PubKeyToAddress(m.Account[:]), 89 | common, 90 | } 91 | return &block 92 | case BlockType_send: 93 | block := blocks.SendBlock{ 94 | types.BlockHash(hex.EncodeToString(m.SourceOrPrevious[:])), 95 | address.PubKeyToAddress(m.RepDestOrSource[:]), 96 | uint128.FromBytes(m.Balance[:]), 97 | common, 98 | } 99 | return &block 100 | case BlockType_receive: 101 | block := blocks.ReceiveBlock{ 102 | types.BlockHash(hex.EncodeToString(m.SourceOrPrevious[:])), 103 | types.BlockHash(hex.EncodeToString(m.RepDestOrSource[:])), 104 | common, 105 | } 106 | return &block 107 | case BlockType_change: 108 | block := blocks.ChangeBlock{ 109 | types.BlockHash(hex.EncodeToString(m.SourceOrPrevious[:])), 110 | address.PubKeyToAddress(m.RepDestOrSource[:]), 111 | common, 112 | } 113 | return &block 114 | default: 115 | return nil 116 | } 117 | } 118 | 119 | func (m *MessageBlock) Read(messageBlockType byte, buf *bytes.Buffer) error { 120 | m.Type = messageBlockType 121 | 122 | n1, err1 := buf.Read(m.SourceOrPrevious[:]) 123 | n2, err2 := buf.Read(m.RepDestOrSource[:]) 124 | 125 | if messageBlockType == BlockType_open { 126 | n, err := buf.Read(m.Account[:]) 127 | if err != nil || n != 32 { 128 | return errors.New("Failed to read account") 129 | } 130 | } 131 | 132 | if messageBlockType == BlockType_send { 133 | n, err := buf.Read(m.Balance[:]) 134 | if err != nil || n != 16 { 135 | return errors.New("Failed to read balance") 136 | } 137 | } 138 | 139 | err3 := m.MessageBlockCommon.ReadCommon(buf) 140 | 141 | if err1 != nil || err2 != nil || err3 != nil { 142 | return errors.New("Failed to read block") 143 | } 144 | 145 | if n1 != 32 || n2 != 32 { 146 | return errors.New("Wrong number of bytes read") 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func (m *MessageBlock) Write(buf *bytes.Buffer) error { 153 | n1, err1 := buf.Write(m.SourceOrPrevious[:]) 154 | n2, err2 := buf.Write(m.RepDestOrSource[:]) 155 | 156 | if m.Type == BlockType_open { 157 | n, err := buf.Write(m.Account[:]) 158 | if err != nil || n != 32 { 159 | return errors.New("Failed to write account") 160 | } 161 | } 162 | 163 | if m.Type == BlockType_send { 164 | n, err := buf.Write(m.Balance[:]) 165 | if err != nil || n != 16 { 166 | return errors.New("Failed to write balance") 167 | } 168 | } 169 | 170 | err3 := m.MessageBlockCommon.WriteCommon(buf) 171 | 172 | if err1 != nil || err2 != nil || err3 != nil { 173 | return errors.New("Failed to write block") 174 | } 175 | 176 | if n1 != 32 || n2 != 32 { 177 | return errors.New("Wrong number of bytes written") 178 | } 179 | 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /wallet/wallet.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/frankh/crypto/ed25519" 7 | "github.com/frankh/nano/address" 8 | "github.com/frankh/nano/blocks" 9 | "github.com/frankh/nano/store" 10 | "github.com/frankh/nano/types" 11 | "github.com/frankh/nano/uint128" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | type Wallet struct { 16 | privateKey ed25519.PrivateKey 17 | PublicKey ed25519.PublicKey 18 | Head blocks.Block 19 | Work *types.Work 20 | PoWchan chan types.Work 21 | } 22 | 23 | func (w *Wallet) Address() types.Account { 24 | return address.PubKeyToAddress(w.PublicKey) 25 | } 26 | 27 | func New(private string) (w Wallet) { 28 | w.PublicKey, w.privateKey = address.KeypairFromPrivateKey(private) 29 | account := address.PubKeyToAddress(w.PublicKey) 30 | 31 | open := store.FetchOpen(account) 32 | if open != nil { 33 | w.Head = open 34 | } 35 | 36 | return w 37 | } 38 | 39 | // Returns true if the wallet has prepared proof of work, 40 | func (w *Wallet) HasPoW() bool { 41 | select { 42 | case work := <-w.PoWchan: 43 | w.Work = &work 44 | w.PoWchan = nil 45 | return true 46 | default: 47 | return false 48 | } 49 | } 50 | 51 | func (w *Wallet) WaitPoW() { 52 | for !w.HasPoW() { 53 | } 54 | } 55 | 56 | func (w *Wallet) WaitingForPoW() bool { 57 | return w.PoWchan != nil 58 | } 59 | 60 | func (w *Wallet) GeneratePowSync() error { 61 | err := w.GeneratePoWAsync() 62 | if err != nil { 63 | return err 64 | } 65 | 66 | w.WaitPoW() 67 | return nil 68 | } 69 | 70 | // Triggers a goroutine to generate the next proof of work. 71 | func (w *Wallet) GeneratePoWAsync() error { 72 | if w.PoWchan != nil { 73 | return errors.Errorf("Already generating PoW") 74 | } 75 | 76 | w.PoWchan = make(chan types.Work) 77 | 78 | go func(c chan types.Work, w *Wallet) { 79 | if w.Head == nil { 80 | c <- blocks.GenerateWorkForHash(types.BlockHash(hex.EncodeToString(w.PublicKey))) 81 | } else { 82 | c <- blocks.GenerateWork(w.Head) 83 | } 84 | }(w.PoWchan, w) 85 | 86 | return nil 87 | } 88 | 89 | func (w *Wallet) GetBalance() uint128.Uint128 { 90 | if w.Head == nil { 91 | return uint128.FromInts(0, 0) 92 | } 93 | 94 | return store.GetBalance(w.Head) 95 | 96 | } 97 | 98 | func (w *Wallet) Open(source types.BlockHash, representative types.Account) (*blocks.OpenBlock, error) { 99 | if w.Head != nil { 100 | return nil, errors.Errorf("Cannot open a non empty account") 101 | } 102 | 103 | if w.Work == nil { 104 | return nil, errors.Errorf("No PoW") 105 | } 106 | 107 | existing := store.FetchOpen(w.Address()) 108 | if existing != nil { 109 | return nil, errors.Errorf("Cannot open account, open block already exists") 110 | } 111 | 112 | send_block := store.FetchBlock(source) 113 | if send_block == nil { 114 | return nil, errors.Errorf("Could not find references send") 115 | } 116 | 117 | common := blocks.CommonBlock{ 118 | Work: *w.Work, 119 | Signature: "", 120 | } 121 | 122 | block := blocks.OpenBlock{ 123 | source, 124 | representative, 125 | w.Address(), 126 | common, 127 | } 128 | 129 | block.Signature = block.Hash().Sign(w.privateKey) 130 | 131 | if !blocks.ValidateBlockWork(&block) { 132 | return nil, errors.Errorf("Invalid PoW") 133 | } 134 | 135 | w.Head = &block 136 | return &block, nil 137 | } 138 | 139 | func (w *Wallet) Send(destination types.Account, amount uint128.Uint128) (*blocks.SendBlock, error) { 140 | if w.Head == nil { 141 | return nil, errors.Errorf("Cannot send from empty account") 142 | } 143 | 144 | if w.Work == nil { 145 | return nil, errors.Errorf("No PoW") 146 | } 147 | 148 | if amount.Compare(w.GetBalance()) > 0 { 149 | return nil, errors.Errorf("Tried to send more than balance") 150 | } 151 | 152 | common := blocks.CommonBlock{ 153 | Work: *w.Work, 154 | Signature: "", 155 | } 156 | 157 | block := blocks.SendBlock{ 158 | w.Head.Hash(), 159 | destination, 160 | w.GetBalance().Sub(amount), 161 | common, 162 | } 163 | 164 | block.Signature = block.Hash().Sign(w.privateKey) 165 | 166 | w.Head = &block 167 | return &block, nil 168 | } 169 | 170 | func (w *Wallet) Receive(source types.BlockHash) (*blocks.ReceiveBlock, error) { 171 | if w.Head == nil { 172 | return nil, errors.Errorf("Cannot receive to empty account") 173 | } 174 | 175 | if w.Work == nil { 176 | return nil, errors.Errorf("No PoW") 177 | } 178 | 179 | send_block := store.FetchBlock(source) 180 | 181 | if send_block == nil { 182 | return nil, errors.Errorf("Source block not found") 183 | } 184 | 185 | if send_block.Type() != blocks.Send { 186 | return nil, errors.Errorf("Source block is not a send") 187 | } 188 | 189 | if send_block.(*blocks.SendBlock).Destination != w.Address() { 190 | return nil, errors.Errorf("Send is not for this account") 191 | } 192 | 193 | common := blocks.CommonBlock{ 194 | Work: *w.Work, 195 | Signature: "", 196 | } 197 | 198 | block := blocks.ReceiveBlock{ 199 | w.Head.Hash(), 200 | source, 201 | common, 202 | } 203 | 204 | block.Signature = block.Hash().Sign(w.privateKey) 205 | 206 | w.Head = &block 207 | return &block, nil 208 | } 209 | 210 | func (w *Wallet) Change(representative types.Account) (*blocks.ChangeBlock, error) { 211 | if w.Head == nil { 212 | return nil, errors.Errorf("Cannot change on empty account") 213 | } 214 | 215 | if w.Work == nil { 216 | return nil, errors.Errorf("No PoW") 217 | } 218 | 219 | common := blocks.CommonBlock{ 220 | Work: *w.Work, 221 | Signature: "", 222 | } 223 | 224 | block := blocks.ChangeBlock{ 225 | w.Head.Hash(), 226 | representative, 227 | common, 228 | } 229 | 230 | block.Signature = block.Hash().Sign(w.privateKey) 231 | 232 | w.Head = &block 233 | return &block, nil 234 | } 235 | -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "errors" 7 | "log" 8 | "sync" 9 | 10 | "github.com/dgraph-io/badger" 11 | "github.com/frankh/nano/address" 12 | "github.com/frankh/nano/blocks" 13 | "github.com/frankh/nano/types" 14 | "github.com/frankh/nano/uint128" 15 | ) 16 | 17 | type Config struct { 18 | Path string 19 | GenesisBlock *blocks.OpenBlock 20 | } 21 | 22 | const ( 23 | MetaOpen byte = iota 24 | MetaReceive 25 | MetaSend 26 | MetaChange 27 | ) 28 | 29 | type BlockItem struct { 30 | badger.Item 31 | } 32 | 33 | func (i *BlockItem) ToBlock() blocks.Block { 34 | meta := i.UserMeta() 35 | value, _ := i.Value() 36 | 37 | dec := gob.NewDecoder(bytes.NewBuffer(value)) 38 | var result blocks.Block 39 | 40 | switch meta { 41 | case MetaOpen: 42 | var b blocks.OpenBlock 43 | dec.Decode(&b) 44 | result = &b 45 | case MetaReceive: 46 | var b blocks.ReceiveBlock 47 | dec.Decode(&b) 48 | result = &b 49 | case MetaSend: 50 | var b blocks.SendBlock 51 | dec.Decode(&b) 52 | result = &b 53 | case MetaChange: 54 | var b blocks.ChangeBlock 55 | dec.Decode(&b) 56 | result = &b 57 | } 58 | 59 | return result 60 | } 61 | 62 | var LiveConfig = Config{ 63 | "DATA", 64 | blocks.LiveGenesisBlock, 65 | } 66 | 67 | var TestConfig = Config{ 68 | "TESTDATA", 69 | blocks.TestGenesisBlock, 70 | } 71 | 72 | var TestConfigLive = Config{ 73 | "TESTDATA", 74 | blocks.LiveGenesisBlock, 75 | } 76 | 77 | // Blocks that we cannot store due to not having their parent 78 | // block stored 79 | var unconnectedBlockPool map[types.BlockHash]blocks.Block 80 | 81 | var Conf *Config 82 | var globalConn *badger.DB 83 | var currentTxn *badger.Txn 84 | var connLock sync.Mutex 85 | 86 | func getConn() *badger.Txn { 87 | connLock.Lock() 88 | 89 | if currentTxn != nil { 90 | return currentTxn 91 | } 92 | 93 | if globalConn == nil { 94 | opts := badger.DefaultOptions 95 | opts.Dir = Conf.Path 96 | opts.ValueDir = Conf.Path 97 | conn, err := badger.Open(opts) 98 | if err != nil { 99 | panic(err) 100 | } 101 | globalConn = conn 102 | } 103 | 104 | currentTxn = globalConn.NewTransaction(true) 105 | return currentTxn 106 | } 107 | 108 | func releaseConn(conn *badger.Txn) { 109 | currentTxn.Commit(nil) 110 | currentTxn = nil 111 | connLock.Unlock() 112 | } 113 | 114 | func Init(config Config) { 115 | var err error 116 | unconnectedBlockPool = make(map[types.BlockHash]blocks.Block) 117 | 118 | if globalConn != nil { 119 | globalConn.Close() 120 | globalConn = nil 121 | } 122 | Conf = &config 123 | conn := getConn() 124 | defer releaseConn(conn) 125 | 126 | _, err = conn.Get(blocks.LiveGenesisBlockHash.ToBytes()) 127 | 128 | if err != nil { 129 | uncheckedStoreBlock(conn, config.GenesisBlock) 130 | } 131 | } 132 | 133 | func FetchOpen(account types.Account) (b *blocks.OpenBlock) { 134 | conn := getConn() 135 | defer releaseConn(conn) 136 | return fetchOpen(conn, account) 137 | } 138 | 139 | func fetchOpen(conn *badger.Txn, account types.Account) (b *blocks.OpenBlock) { 140 | account_bytes, err := address.AddressToPub(account) 141 | if err != nil { 142 | return nil 143 | } 144 | 145 | item, err := conn.Get(account_bytes) 146 | if err != nil { 147 | return nil 148 | } 149 | 150 | blockItem := BlockItem{*item} 151 | return blockItem.ToBlock().(*blocks.OpenBlock) 152 | } 153 | 154 | func FetchBlock(hash types.BlockHash) (b blocks.Block) { 155 | conn := getConn() 156 | defer releaseConn(conn) 157 | return fetchBlock(conn, hash) 158 | } 159 | 160 | func fetchBlock(conn *badger.Txn, hash types.BlockHash) (b blocks.Block) { 161 | item, err := conn.Get(hash.ToBytes()) 162 | if err != nil { 163 | return nil 164 | } 165 | 166 | blockItem := BlockItem{*item} 167 | return blockItem.ToBlock() 168 | } 169 | 170 | func GetBalance(block blocks.Block) uint128.Uint128 { 171 | conn := getConn() 172 | defer releaseConn(conn) 173 | return getBalance(conn, block) 174 | } 175 | 176 | func getSendAmount(conn *badger.Txn, block *blocks.SendBlock) uint128.Uint128 { 177 | prev := fetchBlock(conn, block.PreviousHash) 178 | 179 | return getBalance(conn, prev).Sub(getBalance(conn, block)) 180 | } 181 | 182 | func getBalance(conn *badger.Txn, block blocks.Block) uint128.Uint128 { 183 | switch block.Type() { 184 | case blocks.Open: 185 | b := block.(*blocks.OpenBlock) 186 | if b.SourceHash == Conf.GenesisBlock.SourceHash { 187 | return blocks.GenesisAmount 188 | } 189 | source := fetchBlock(conn, b.SourceHash).(*blocks.SendBlock) 190 | return getSendAmount(conn, source) 191 | 192 | case blocks.Send: 193 | b := block.(*blocks.SendBlock) 194 | return b.Balance 195 | 196 | case blocks.Receive: 197 | b := block.(*blocks.ReceiveBlock) 198 | prev := fetchBlock(conn, b.PreviousHash) 199 | source := fetchBlock(conn, b.SourceHash).(*blocks.SendBlock) 200 | received := getSendAmount(conn, source) 201 | return getBalance(conn, prev).Add(received) 202 | 203 | case blocks.Change: 204 | b := block.(*blocks.ChangeBlock) 205 | return getBalance(conn, fetchBlock(conn, b.PreviousHash)) 206 | 207 | default: 208 | panic("Unknown block type") 209 | } 210 | 211 | } 212 | 213 | // Validate and store a block 214 | // TODO: Validate signature and balance 215 | func StoreBlock(block blocks.Block) error { 216 | conn := getConn() 217 | defer releaseConn(conn) 218 | return storeBlock(conn, block) 219 | } 220 | 221 | func storeBlock(conn *badger.Txn, block blocks.Block) error { 222 | if !blocks.ValidateBlockWork(block) { 223 | return errors.New("Invalid work for block") 224 | } 225 | 226 | if block.Type() != blocks.Open && block.Type() != blocks.Change && block.Type() != blocks.Send && block.Type() != blocks.Receive { 227 | return errors.New("Unknown block type") 228 | } 229 | 230 | if fetchBlock(conn, block.PreviousBlockHash()) == nil { 231 | if unconnectedBlockPool[block.PreviousBlockHash()] == nil { 232 | unconnectedBlockPool[block.PreviousBlockHash()] = block 233 | log.Printf("Added block to unconnected pool, now %d", len(unconnectedBlockPool)) 234 | } 235 | return errors.New("Cannot find parent block") 236 | } 237 | 238 | uncheckedStoreBlock(conn, block) 239 | dependentBlock := unconnectedBlockPool[block.Hash()] 240 | 241 | if dependentBlock != nil { 242 | // We have an unconnected block dependent on this: Store it now that 243 | // it's connected 244 | delete(unconnectedBlockPool, block.Hash()) 245 | storeBlock(conn, dependentBlock) 246 | } 247 | 248 | return nil 249 | } 250 | 251 | // Store a block without checking whether it's valid 252 | // The block should be pre-checked to ensure it has a valid signature, 253 | // parent block, balance, etc. 254 | func uncheckedStoreBlock(conn *badger.Txn, block blocks.Block) { 255 | var buf bytes.Buffer 256 | var meta byte 257 | enc := gob.NewEncoder(&buf) 258 | switch block.Type() { 259 | case blocks.Open: 260 | b := block.(*blocks.OpenBlock) 261 | meta = MetaOpen 262 | err := enc.Encode(b) 263 | if err != nil { 264 | panic(err) 265 | } 266 | // Open blocks need to be stored twice, once keyed on account, 267 | // once keyed on hash. 268 | err = conn.SetWithMeta(b.RootHash().ToBytes(), buf.Bytes(), meta) 269 | if err != nil { 270 | panic(err) 271 | } 272 | case blocks.Send: 273 | b := block.(*blocks.SendBlock) 274 | meta = MetaSend 275 | err := enc.Encode(b) 276 | if err != nil { 277 | panic(err) 278 | } 279 | case blocks.Receive: 280 | b := block.(*blocks.ReceiveBlock) 281 | meta = MetaReceive 282 | err := enc.Encode(b) 283 | if err != nil { 284 | panic(err) 285 | } 286 | case blocks.Change: 287 | b := block.(*blocks.ChangeBlock) 288 | meta = MetaChange 289 | err := enc.Encode(b) 290 | if err != nil { 291 | panic(err) 292 | } 293 | default: 294 | panic("Unknown block type") 295 | } 296 | 297 | err := conn.SetWithMeta(block.Hash().ToBytes(), buf.Bytes(), meta) 298 | if err != nil { 299 | panic("Failed to store block") 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "net" 10 | "time" 11 | 12 | "github.com/frankh/nano/store" 13 | ) 14 | 15 | var MagicNumber = [2]byte{'R', 'C'} 16 | 17 | const VersionMax = 0x05 18 | const VersionUsing = 0x05 19 | const VersionMin = 0x04 20 | 21 | // Non-idiomatic constant names to keep consistent with reference implentation 22 | const ( 23 | Message_invalid byte = iota 24 | Message_not_a_type 25 | Message_keepalive 26 | Message_publish 27 | Message_confirm_req 28 | Message_confirm_ack 29 | Message_bulk_pull 30 | Message_bulk_push 31 | Message_frontier_req 32 | ) 33 | 34 | const ( 35 | BlockType_invalid byte = iota 36 | BlockType_not_a_block 37 | BlockType_send 38 | BlockType_receive 39 | BlockType_open 40 | BlockType_change 41 | ) 42 | 43 | type Peer struct { 44 | IP net.IP 45 | Port uint16 46 | LastReachout *time.Time 47 | } 48 | 49 | type MessageHeader struct { 50 | MagicNumber [2]byte 51 | VersionMax byte 52 | VersionUsing byte 53 | VersionMin byte 54 | MessageType byte 55 | Extensions byte 56 | BlockType byte 57 | } 58 | 59 | type MessageKeepAlive struct { 60 | MessageHeader 61 | Peers []Peer 62 | } 63 | 64 | type MessageConfirmAck struct { 65 | MessageHeader 66 | MessageVote 67 | } 68 | 69 | type MessageConfirmReq struct { 70 | MessageHeader 71 | MessageBlock 72 | } 73 | 74 | type MessagePublish struct { 75 | MessageHeader 76 | MessageBlock 77 | } 78 | 79 | type Message interface { 80 | Write(buf *bytes.Buffer) error 81 | } 82 | 83 | func CreateKeepAlive(peers []Peer) *MessageKeepAlive { 84 | var m MessageKeepAlive 85 | m.MessageHeader.MagicNumber = MagicNumber 86 | m.MessageHeader.VersionMax = VersionMax 87 | m.MessageHeader.VersionUsing = VersionUsing 88 | m.MessageHeader.VersionMin = VersionMin 89 | m.MessageHeader.MessageType = Message_keepalive 90 | return &m 91 | } 92 | 93 | func (p *Peer) Addr() *net.UDPAddr { 94 | addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", p.IP.String(), p.Port)) 95 | return addr 96 | } 97 | 98 | func (p *Peer) String() string { 99 | return fmt.Sprintf("%s:%d", p.IP.String(), p.Port) 100 | } 101 | 102 | func handleMessage(buf *bytes.Buffer) { 103 | var header MessageHeader 104 | header.ReadHeader(bytes.NewBuffer(buf.Bytes())) 105 | if header.MagicNumber != MagicNumber { 106 | log.Printf("Ignored message. Wrong magic number %s", header.MagicNumber) 107 | return 108 | } 109 | 110 | switch header.MessageType { 111 | case Message_keepalive: 112 | var m MessageKeepAlive 113 | err := m.Read(buf) 114 | if err != nil { 115 | log.Printf("Failed to read keepalive: %s", err) 116 | } 117 | log.Println("Read keepalive") 118 | err = m.Handle() 119 | if err != nil { 120 | log.Printf("Failed to handle keepalive") 121 | } 122 | case Message_publish: 123 | var m MessagePublish 124 | err := m.Read(buf) 125 | if err != nil { 126 | log.Printf("Failed to read publish: %s", err) 127 | } else { 128 | store.StoreBlock(m.ToBlock()) 129 | } 130 | case Message_confirm_ack: 131 | var m MessageConfirmAck 132 | err := m.Read(buf) 133 | if err != nil { 134 | log.Printf("Failed to read confirm: %s", err) 135 | } else { 136 | store.StoreBlock(m.ToBlock()) 137 | } 138 | default: 139 | log.Printf("Ignored message. Cannot handle message type %d\n", header.MessageType) 140 | } 141 | } 142 | 143 | func (m *MessageKeepAlive) Handle() error { 144 | for _, peer := range m.Peers { 145 | if !PeerSet[peer.String()] { 146 | PeerSet[peer.String()] = true 147 | PeerList = append(PeerList, peer) 148 | log.Printf("Added new peer to list: %s, now %d peers", peer.String(), len(PeerList)) 149 | } 150 | } 151 | return nil 152 | } 153 | 154 | func (m *MessageKeepAlive) Read(buf *bytes.Buffer) error { 155 | var header MessageHeader 156 | err := header.ReadHeader(buf) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | if header.MessageType != Message_keepalive { 162 | return errors.New("Tried to read wrong message type") 163 | } 164 | 165 | m.MessageHeader = header 166 | m.Peers = make([]Peer, 0) 167 | 168 | for { 169 | peerPort := make([]byte, 2) 170 | peerIp := make(net.IP, net.IPv6len) 171 | n, err := buf.Read(peerIp) 172 | if n == 0 { 173 | break 174 | } 175 | if err != nil { 176 | return err 177 | } 178 | n2, err := buf.Read(peerPort) 179 | if err != nil { 180 | return err 181 | } 182 | if n < net.IPv6len || n2 < 2 { 183 | return errors.New("Not enough ip bytes") 184 | } 185 | 186 | m.Peers = append(m.Peers, Peer{peerIp, binary.LittleEndian.Uint16(peerPort), nil}) 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (m *MessageKeepAlive) Write(buf *bytes.Buffer) error { 193 | err := m.MessageHeader.WriteHeader(buf) 194 | if err != nil { 195 | return err 196 | } 197 | 198 | for _, peer := range m.Peers { 199 | _, err = buf.Write(peer.IP) 200 | if err != nil { 201 | return err 202 | } 203 | portBytes := make([]byte, 2) 204 | binary.LittleEndian.PutUint16(portBytes, peer.Port) 205 | if err != nil { 206 | return err 207 | } 208 | _, err = buf.Write(portBytes) 209 | if err != nil { 210 | return err 211 | } 212 | } 213 | 214 | return nil 215 | } 216 | 217 | func (m *MessageConfirmAck) Read(buf *bytes.Buffer) error { 218 | err := m.MessageHeader.ReadHeader(buf) 219 | if err != nil { 220 | return err 221 | } 222 | 223 | if m.MessageHeader.MessageType != Message_confirm_ack { 224 | return errors.New("Tried to read wrong message type") 225 | } 226 | err = m.MessageVote.Read(m.MessageHeader.BlockType, buf) 227 | if err != nil { 228 | return err 229 | } 230 | 231 | return nil 232 | } 233 | 234 | func (m *MessageConfirmAck) Write(buf *bytes.Buffer) error { 235 | err := m.MessageHeader.WriteHeader(buf) 236 | if err != nil { 237 | return err 238 | } 239 | 240 | err = m.MessageVote.Write(buf) 241 | if err != nil { 242 | return err 243 | } 244 | 245 | return nil 246 | } 247 | 248 | func (m *MessageConfirmReq) Read(buf *bytes.Buffer) error { 249 | err := m.MessageHeader.ReadHeader(buf) 250 | if err != nil { 251 | return err 252 | } 253 | 254 | if m.MessageHeader.MessageType != Message_confirm_req { 255 | return errors.New("Tried to read wrong message type") 256 | } 257 | err = m.MessageBlock.Read(m.MessageHeader.BlockType, buf) 258 | if err != nil { 259 | return err 260 | } 261 | 262 | return nil 263 | } 264 | 265 | func (m *MessageConfirmReq) Write(buf *bytes.Buffer) error { 266 | err := m.MessageHeader.WriteHeader(buf) 267 | if err != nil { 268 | return err 269 | } 270 | 271 | err = m.MessageBlock.Write(buf) 272 | if err != nil { 273 | return err 274 | } 275 | 276 | return nil 277 | } 278 | 279 | func (m *MessagePublish) Read(buf *bytes.Buffer) error { 280 | err := m.MessageHeader.ReadHeader(buf) 281 | if err != nil { 282 | return err 283 | } 284 | 285 | if m.MessageHeader.MessageType != Message_publish { 286 | return errors.New("Tried to read wrong message type") 287 | } 288 | err = m.MessageBlock.Read(m.MessageHeader.BlockType, buf) 289 | if err != nil { 290 | return err 291 | } 292 | 293 | return nil 294 | } 295 | 296 | func (m *MessagePublish) Write(buf *bytes.Buffer) error { 297 | err := m.MessageHeader.WriteHeader(buf) 298 | if err != nil { 299 | return err 300 | } 301 | 302 | err = m.MessageBlock.Write(buf) 303 | if err != nil { 304 | return err 305 | } 306 | 307 | return nil 308 | } 309 | 310 | func (m *MessageHeader) WriteHeader(buf *bytes.Buffer) error { 311 | var errs []error 312 | errs = append(errs, 313 | buf.WriteByte(m.MagicNumber[0]), 314 | buf.WriteByte(m.MagicNumber[1]), 315 | buf.WriteByte(m.VersionMax), 316 | buf.WriteByte(m.VersionUsing), 317 | buf.WriteByte(m.VersionMin), 318 | buf.WriteByte(m.MessageType), 319 | buf.WriteByte(m.Extensions), 320 | buf.WriteByte(m.BlockType), 321 | ) 322 | 323 | for _, err := range errs { 324 | if err != nil { 325 | return err 326 | } 327 | } 328 | return nil 329 | } 330 | 331 | func (m *MessageHeader) ReadHeader(buf *bytes.Buffer) error { 332 | var errs []error 333 | var err error 334 | // I really hate go error handling sometimes 335 | m.MagicNumber[0], err = buf.ReadByte() 336 | errs = append(errs, err) 337 | m.MagicNumber[1], err = buf.ReadByte() 338 | errs = append(errs, err) 339 | m.VersionMax, err = buf.ReadByte() 340 | errs = append(errs, err) 341 | m.VersionUsing, err = buf.ReadByte() 342 | errs = append(errs, err) 343 | m.VersionMin, err = buf.ReadByte() 344 | errs = append(errs, err) 345 | m.MessageType, err = buf.ReadByte() 346 | errs = append(errs, err) 347 | m.Extensions, err = buf.ReadByte() 348 | errs = append(errs, err) 349 | m.BlockType, err = buf.ReadByte() 350 | errs = append(errs, err) 351 | 352 | for _, err := range errs { 353 | if err != nil { 354 | return err 355 | } 356 | } 357 | return nil 358 | } 359 | -------------------------------------------------------------------------------- /blocks/blocks.go: -------------------------------------------------------------------------------- 1 | package blocks 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "encoding/json" 7 | "hash" 8 | "strings" 9 | 10 | "github.com/frankh/nano/address" 11 | "github.com/frankh/nano/types" 12 | "github.com/frankh/nano/uint128" 13 | "github.com/frankh/nano/utils" 14 | "github.com/golang/crypto/blake2b" 15 | // We've forked golang's ed25519 implementation 16 | // to use blake2b instead of sha3 17 | "github.com/frankh/crypto/ed25519" 18 | ) 19 | 20 | const LiveGenesisBlockHash types.BlockHash = "991CF190094C00F0B68E2E5F75F6BEE95A2E0BD93CEAA4A6734DB9F19B728948" 21 | const LiveGenesisSourceHash types.BlockHash = "E89208DD038FBB269987689621D52292AE9C35941A7484756ECCED92A65093BA" 22 | 23 | var GenesisAmount uint128.Uint128 = uint128.FromInts(0xffffffffffffffff, 0xffffffffffffffff) 24 | var WorkThreshold = uint64(0xffffffc000000000) 25 | 26 | const TestPrivateKey string = "34F0A37AAD20F4A260F0A5B3CB3D7FB50673212263E58A380BC10474BB039CE4" 27 | 28 | var TestGenesisBlock = FromJson([]byte(`{ 29 | "type": "open", 30 | "source": "B0311EA55708D6A53C75CDBF88300259C6D018522FE3D4D0A242E431F9E8B6D0", 31 | "representative": "nano_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpiij4txtdo", 32 | "account": "nano_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpiij4txtdo", 33 | "work": "9680625b39d3363d", 34 | "signature": "ECDA914373A2F0CA1296475BAEE40500A7F0A7AD72A5A80C81D7FAB7F6C802B2CC7DB50F5DD0FB25B2EF11761FA7344A158DD5A700B21BD47DE5BD0F63153A02" 35 | }`)).(*OpenBlock) 36 | 37 | var LiveGenesisBlock = FromJson([]byte(`{ 38 | "type": "open", 39 | "source": "E89208DD038FBB269987689621D52292AE9C35941A7484756ECCED92A65093BA", 40 | "representative": "nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3", 41 | "account": "nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3", 42 | "work": "62f05417dd3fb691", 43 | "signature": "9F0C933C8ADE004D808EA1985FA746A7E95BA2A38F867640F53EC8F180BDFE9E2C1268DEAD7C2664F356E37ABA362BC58E46DBA03E523A7B5A19E4B6EB12BB02" 44 | }`)).(*OpenBlock) 45 | 46 | type BlockType string 47 | 48 | const ( 49 | Open BlockType = "open" 50 | Receive = "receive" 51 | Send = "send" 52 | Change = "change" 53 | ) 54 | 55 | type Block interface { 56 | Type() BlockType 57 | GetSignature() types.Signature 58 | GetWork() types.Work 59 | RootHash() types.BlockHash 60 | Hash() types.BlockHash 61 | PreviousBlockHash() types.BlockHash 62 | } 63 | 64 | type CommonBlock struct { 65 | Work types.Work 66 | Signature types.Signature 67 | Confirmed bool 68 | } 69 | 70 | type OpenBlock struct { 71 | SourceHash types.BlockHash 72 | Representative types.Account 73 | Account types.Account 74 | CommonBlock 75 | } 76 | 77 | type SendBlock struct { 78 | PreviousHash types.BlockHash 79 | Destination types.Account 80 | Balance uint128.Uint128 81 | CommonBlock 82 | } 83 | 84 | type ReceiveBlock struct { 85 | PreviousHash types.BlockHash 86 | SourceHash types.BlockHash 87 | CommonBlock 88 | } 89 | 90 | type ChangeBlock struct { 91 | PreviousHash types.BlockHash 92 | Representative types.Account 93 | CommonBlock 94 | } 95 | 96 | func (b *OpenBlock) Hash() types.BlockHash { 97 | return types.BlockHashFromBytes(HashOpen(b.SourceHash, b.Representative, b.Account)) 98 | } 99 | 100 | func (b *ReceiveBlock) Hash() types.BlockHash { 101 | return types.BlockHashFromBytes(HashReceive(b.PreviousHash, b.SourceHash)) 102 | } 103 | 104 | func (b *ChangeBlock) Hash() types.BlockHash { 105 | return types.BlockHashFromBytes(HashChange(b.PreviousHash, b.Representative)) 106 | } 107 | 108 | func (b *SendBlock) Hash() types.BlockHash { 109 | return types.BlockHashFromBytes(HashSend(b.PreviousHash, b.Destination, b.Balance)) 110 | } 111 | 112 | func (b *ReceiveBlock) PreviousBlockHash() types.BlockHash { 113 | return b.PreviousHash 114 | } 115 | 116 | func (b *ChangeBlock) PreviousBlockHash() types.BlockHash { 117 | return b.PreviousHash 118 | } 119 | 120 | func (b *SendBlock) PreviousBlockHash() types.BlockHash { 121 | return b.PreviousHash 122 | } 123 | 124 | func (b *OpenBlock) PreviousBlockHash() types.BlockHash { 125 | return b.SourceHash 126 | } 127 | 128 | func (b *OpenBlock) RootHash() types.BlockHash { 129 | pub, _ := address.AddressToPub(b.Account) 130 | return types.BlockHash(hex.EncodeToString(pub)) 131 | } 132 | 133 | func (b *ReceiveBlock) RootHash() types.BlockHash { 134 | return b.PreviousHash 135 | } 136 | 137 | func (b *ChangeBlock) RootHash() types.BlockHash { 138 | return b.PreviousHash 139 | } 140 | 141 | func (b *SendBlock) RootHash() types.BlockHash { 142 | return b.PreviousHash 143 | } 144 | 145 | func (b *CommonBlock) GetSignature() types.Signature { 146 | return b.Signature 147 | } 148 | 149 | func (b *CommonBlock) GetWork() types.Work { 150 | return b.Work 151 | } 152 | 153 | func (*SendBlock) Type() BlockType { 154 | return Send 155 | } 156 | 157 | func (*OpenBlock) Type() BlockType { 158 | return Open 159 | } 160 | 161 | func (*ChangeBlock) Type() BlockType { 162 | return Change 163 | } 164 | 165 | func (*ReceiveBlock) Type() BlockType { 166 | return Receive 167 | } 168 | 169 | func (b *OpenBlock) VerifySignature() (bool, error) { 170 | pub, _ := address.AddressToPub(b.Account) 171 | res := ed25519.Verify(pub, b.Hash().ToBytes(), b.Signature.ToBytes()) 172 | return res, nil 173 | } 174 | 175 | type RawBlock struct { 176 | Type BlockType 177 | Source types.BlockHash 178 | Representative types.Account 179 | Account types.Account 180 | Work types.Work 181 | Signature types.Signature 182 | Previous types.BlockHash 183 | Balance uint128.Uint128 184 | Destination types.Account 185 | } 186 | 187 | func FromJson(b []byte) (block Block) { 188 | var raw RawBlock 189 | json.Unmarshal(b, &raw) 190 | common := CommonBlock{ 191 | Work: raw.Work, 192 | Signature: raw.Signature, 193 | } 194 | 195 | switch raw.Type { 196 | case Open: 197 | b := OpenBlock{ 198 | raw.Source, 199 | raw.Representative, 200 | raw.Account, 201 | common, 202 | } 203 | block = &b 204 | case Send: 205 | b := SendBlock{ 206 | raw.Previous, 207 | raw.Destination, 208 | raw.Balance, 209 | common, 210 | } 211 | block = &b 212 | case Receive: 213 | b := ReceiveBlock{ 214 | raw.Previous, 215 | raw.Source, 216 | common, 217 | } 218 | block = &b 219 | case Change: 220 | b := ChangeBlock{ 221 | raw.Previous, 222 | raw.Representative, 223 | common, 224 | } 225 | block = &b 226 | default: 227 | panic("Unknown block type") 228 | } 229 | 230 | return block 231 | 232 | } 233 | 234 | func (b RawBlock) Hash() (result []byte) { 235 | switch b.Type { 236 | case Open: 237 | return HashOpen(b.Source, b.Representative, b.Account) 238 | case Send: 239 | return HashSend(b.Previous, b.Destination, b.Balance) 240 | case Receive: 241 | return HashReceive(b.Previous, b.Source) 242 | case Change: 243 | return HashChange(b.Previous, b.Representative) 244 | default: 245 | panic("Unknown block type! " + b.Type) 246 | } 247 | } 248 | 249 | func (b RawBlock) HashToString() (result types.BlockHash) { 250 | return types.BlockHash(strings.ToUpper(hex.EncodeToString(b.Hash()))) 251 | } 252 | 253 | func SignMessage(private_key string, message []byte) (signature []byte) { 254 | _, priv := address.KeypairFromPrivateKey(private_key) 255 | return ed25519.Sign(priv, message) 256 | } 257 | 258 | func HashBytes(inputs ...[]byte) (result []byte) { 259 | hash, err := blake2b.New(32, nil) 260 | if err != nil { 261 | panic("Unable to create hash") 262 | } 263 | 264 | for _, b := range inputs { 265 | hash.Write(b) 266 | } 267 | 268 | return hash.Sum(nil) 269 | } 270 | 271 | func HashReceive(previous types.BlockHash, source types.BlockHash) (result []byte) { 272 | previous_bytes, _ := hex.DecodeString(string(previous)) 273 | source_bytes, _ := hex.DecodeString(string(source)) 274 | return HashBytes(previous_bytes, source_bytes) 275 | } 276 | 277 | func HashChange(previous types.BlockHash, representative types.Account) (result []byte) { 278 | previous_bytes, _ := hex.DecodeString(string(previous)) 279 | repr_bytes, _ := address.AddressToPub(representative) 280 | return HashBytes(previous_bytes, repr_bytes) 281 | } 282 | 283 | func HashSend(previous types.BlockHash, destination types.Account, balance uint128.Uint128) (result []byte) { 284 | previous_bytes, _ := hex.DecodeString(string(previous)) 285 | dest_bytes, _ := address.AddressToPub(destination) 286 | balance_bytes := balance.GetBytes() 287 | 288 | return HashBytes(previous_bytes, dest_bytes, balance_bytes) 289 | } 290 | 291 | func HashOpen(source types.BlockHash, representative types.Account, account types.Account) (result []byte) { 292 | source_bytes, _ := hex.DecodeString(string(source)) 293 | repr_bytes, _ := address.AddressToPub(representative) 294 | account_bytes, _ := address.AddressToPub(account) 295 | return HashBytes(source_bytes, repr_bytes, account_bytes) 296 | } 297 | 298 | // ValidateWork takes the "work" value (little endian from hex) 299 | // and block hash and verifies that the work passes the difficulty. 300 | // To verify this, we create a new 8 byte hash of the 301 | // work and the block hash and convert this to a uint64 302 | // which must be higher (or equal) than the difficulty 303 | // (0xffffffc000000000) to be valid. 304 | func ValidateWork(block_hash []byte, work []byte) bool { 305 | hash, err := blake2b.New(8, nil) 306 | if err != nil { 307 | panic("Unable to create hash") 308 | } 309 | return validateWork(hash, block_hash, work) 310 | } 311 | 312 | func validateWork(digest hash.Hash, block []byte, work []byte) bool { 313 | digest.Reset() 314 | digest.Write(work) 315 | digest.Write(block) 316 | 317 | sum := digest.Sum(nil) 318 | return binary.LittleEndian.Uint64(sum) >= WorkThreshold 319 | } 320 | 321 | func validateNonce(digest hash.Hash, block []byte, nonce uint64) bool { 322 | b := make([]byte, 8) 323 | binary.LittleEndian.PutUint64(b, nonce) 324 | return validateWork(digest, block, b) 325 | } 326 | 327 | func ValidateBlockWork(b Block) bool { 328 | hash_bytes := b.RootHash().ToBytes() 329 | work_bytes, _ := hex.DecodeString(string(b.GetWork())) 330 | 331 | res := ValidateWork(hash_bytes, utils.Reversed(work_bytes)) 332 | return res 333 | } 334 | 335 | func GenerateWorkForHash(b types.BlockHash) types.Work { 336 | block_hash := b.ToBytes() 337 | digest, err := blake2b.New(8, nil) 338 | if err != nil { 339 | panic("Unable to create hash") 340 | } 341 | var nonce uint64 342 | for ; !validateNonce(digest, block_hash, nonce); nonce++ { 343 | } 344 | work := make([]byte, 8) 345 | binary.BigEndian.PutUint64(work, nonce) 346 | return types.Work(hex.EncodeToString(work)) 347 | } 348 | 349 | func GenerateWork(b Block) types.Work { 350 | return GenerateWorkForHash(b.Hash()) 351 | } 352 | -------------------------------------------------------------------------------- /node/node_test.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/frankh/crypto/ed25519" 9 | "github.com/frankh/nano/blocks" 10 | "github.com/frankh/nano/store" 11 | "github.com/frankh/nano/types" 12 | ) 13 | 14 | var publishSend, _ = hex.DecodeString("5243050501030002B6460102018F076CC32FF2F65AD397299C47F8CA2BE784D5DE394D592C22BE8BFFBE91872F1D2A2BCC1CB47FB854D6D31E43C6391EADD5750BB9689E5DF0D6CB0000003D11C83DBCFF748EB4B7F7A3C059DDEEE5C8ECCC8F20DEF3AF3C4F0726F879082ED051D0C62A54CD69C4A66B020369B7033C5B0F77654173AB24D5C7A64CC4FFF0BDB368FCC989E41A656569047627C49A2A6D2FBC") 15 | var publishReceive, _ = hex.DecodeString("5243050501030003233FF43F2ADE055D4D4BCC1C19A3100B720C21E5548A547B9B21938BBDBB19EE28A1763099135DADB3F223C0A4138269C7146A6431AF0597D24276BB0A24BAFCBA254A264BAA0BCBA5962A77E15D4EB021043FFFEA9E4391E179D467C66C69675E9634F9C124060FC65D5B2F67FCA38E8BA93BF910EB337010BC51E652B0640D62F2642CB37BCD7C") 16 | var publishTest, _ = hex.DecodeString("52430505010300030AFC4456F1A54722B101E41B1C2E3F7AF0EFD456EAE3621786C021D72C0BA9880FD491C3FF52227C8CDF76C88CE8F650320042349210AD2681134FD74080675C60734FAA7F89DDF5BDA156A5C7996A79F2CBD22E244B4E39D497261D356A30BE70973313A71A7D52700A560191B8A926FCE44B987A96FE61A8C469BBE383340831783CA6A6511D6A") 17 | var publishOpen, _ = hex.DecodeString("5243040501030004FBC1F34CF9EF42FB137A909873BD3FDEC047CB8A6D4448B43C0610931E268F012298FAB7C61058E77EA554CB93EDEEDA0692CBFCC540AB213B2836B29029E23A0A3E8B35979AC58F7A0AB42656B28294F5968EB059749EA36BC372DDCDFDBB0134086DB608D63F4A086FD92E0BB4AC6A05926CEC84E4D7D99A86F81D90EA9669A9E02B4E907D5E09491206D76E4787F6F2C26B8FD9932315B10EC005A8B4F60DDA9D288B1C14A4CB") 18 | var publishChange, _ = hex.DecodeString("5243050501030005611A6FA8736497E6C1BD9AE42090F0F646F56B32B6E02F804C2295B3888A2FEDE196157A3B52034755CA905AD0C365B192A40203D8983E077093BCD6C9757A64A772CD1736F8DF3C6E382BDC7EED1D48628A65263CE50B12A603B6782D2C3E5EE2280B3C97ACEA67FF003CA3690B2BBEE160E375D0CAA220109D63ED35BBAD0F1DE013836D3471C1") 19 | var publishWrongBlock, _ = hex.DecodeString("5243050501030002611A6FA8736497E6C1BD9AE42090F0F646F56B32B6E02F804C2295B3888A2FEDE196157A3B52034755CA905AD0C365B192A40203D8983E077093BCD6C9757A64A772CD1736F8DF3C6E382BDC7EED1D48628A65263CE50B12A603B6782D2C3E5EE2280B3C97ACEA67FF003CA3690B2BBEE160E375D0CAA220109D63ED35BBAD0F1DE013836D3471C1") 20 | var publishWrongMagic, _ = hex.DecodeString("5242050501030005611A6FA8736497E6C1BD9AE42090F0F646F56B32B6E02F804C2295B3888A2FEDE196157A3B52034755CA905AD0C365B192A40203D8983E077093BCD6C9757A64A772CD1736F8DF3C6E382BDC7EED1D48628A65263CE50B12A603B6782D2C3E5EE2280B3C97ACEA67FF003CA3690B2BBEE160E375D0CAA220109D63ED35BBAD0F1DE013836D3471C1") 21 | var publishWrongSig, _ = hex.DecodeString("5243040501030004FBC1F34CF9EF42FB137A909873BD3FDEC047CB8A6D4448B43C0610931E268F012298FAB7C61058E77EA554CB93EDEEDA0692CBFCC540AB213B2836B29029E23A0A3E8B35979AC58F7A0AB42656B28294F5968EB059749EA36BC372DDCDFDBB0134086DB608D63F4A086FD92E0BB4AC6A05926CEC84E4D7D99A86F81D90EA9669A9E02B4E907D5E09491206D76E4787F6F2C26B8FD9932315B10EC015A8B4F60DDA9D288B1C14A4CB") 22 | var publishWrongWork, _ = hex.DecodeString("5242050501030005611A6FA8736497E6C1BD9AE42090F0F646F56B32B6E02F804C2295B3888A2FEDE196157A3B52034755CA905AD0C365B192A40203D8983E077093BCD6C9757A64A772CD1736F8DF3C6E382BDC7EED1D48628A65263CE50B12A603B6782D2C3E5EE2280B3C97ACEA67FF003CA3690B2BBEE160E375D0CAA220109D63ED34BBAD0F1DE013836D3471C0") 23 | var keepAlive, _ = hex.DecodeString("524305050102000000000000000000000000FFFF49B13E26A31B00000000000000000000FFFF637887DF340400000000000000000000FFFFCC2C6D15A31B00000000000000000000FFFF5EC16857239C00000000000000000000FFFF23BD2D1FA31B00000000000000000000FFFF253B710AA31B00000000000000000000FFFF50740256A7E500000000000000000000FFFF4631D644A31B") 24 | 25 | var confirmAck, _ = hex.DecodeString("524305050105000289aaf8e5f19f60ebc9476f382dbee256deae2695b47934700d9aad49d86ccb249ceb5c2840fe3fdf2dcb9c40e142181e7bd158d07ca3f8388dc3b3c0acd395d85b38e04ce1dac45b070957046d31eb7f58caaa777a5e13d85fe2aae7514b490e9c1dd00100000000aef053ab1832d41df356290a704e6c6c47787c6da4710ee2399e60e0ab607e9e51380a2c22710ed4018392474228b4e7c80f1c6714dcc3c9ef4befa563ecc35905bd9a62bd5b7ebdc5ebc9f576392e00445a07742dc4b2bc1355aef245522b19ae5640985f7759954ebf5147a125fec7e9f1973cf1d2a9d182c9223392b4cc10cdb11bca27c455ec8b13f4482b506d02576cfad0046c5f1c") 26 | var confirmReq, _ = hex.DecodeString("52430505010400030c32f8cac423ec13236e09db435a18471ef39274959e6f8b44f005577614190e6e470adf874730bb15f067e04ec4ccd77426e69166a72d57d592a4e15eff1df97560262045e5a612c015205a5e73a53fe3775bd5809f6723641b31c7b103ebb30adc93932c7fba8c0a29d8ca1fb22514a2490552dcdb028401975cd8c9014b0fccd88343ef983eae") 27 | 28 | func TestReadWriteMessageKeepAlive(t *testing.T) { 29 | var message MessageKeepAlive 30 | buf := bytes.NewBuffer(keepAlive) 31 | err := message.Read(buf) 32 | if err != nil { 33 | t.Errorf("Failed to read keepalive: %s", err) 34 | } 35 | if len(message.Peers) != 8 { 36 | t.Errorf("Wrong number of keepalive peers %d", len(message.Peers)) 37 | } 38 | 39 | buf = bytes.NewBuffer(publishChange) 40 | if message.Read(buf) == nil { 41 | t.Errorf("Should fail to read wrong message type") 42 | } 43 | 44 | if message.Peers[0].Port != 7075 { 45 | t.Errorf("Wrong port deserialization") 46 | } 47 | 48 | if message.Peers[0].IP.String() != "73.177.62.38" { 49 | t.Errorf("Wrong IP deserialization") 50 | } 51 | 52 | var writeBuf bytes.Buffer 53 | err = message.Write(&writeBuf) 54 | if err != nil { 55 | t.Errorf("Failed to write keepalive: %s", err) 56 | } 57 | 58 | if !bytes.Equal(keepAlive, writeBuf.Bytes()) { 59 | t.Errorf("Failed to rewrite keepalive message\n%x\n%x\n", keepAlive, writeBuf.Bytes()) 60 | } 61 | } 62 | 63 | func TestReadWriteConfirmAck(t *testing.T) { 64 | var m MessageConfirmAck 65 | buf := bytes.NewBuffer(confirmAck) 66 | err := m.Read(buf) 67 | if err != nil { 68 | t.Errorf("Failed to read message") 69 | } 70 | 71 | var writeBuf bytes.Buffer 72 | err = m.Write(&writeBuf) 73 | if err != nil { 74 | t.Errorf("Failed to write message") 75 | } 76 | 77 | if bytes.Compare(confirmAck, writeBuf.Bytes()) != 0 { 78 | t.Errorf("Wrote message badly") 79 | } 80 | 81 | block := m.ToBlock().(*blocks.SendBlock) 82 | if !blocks.ValidateBlockWork(block) { 83 | t.Errorf("Work validation failed") 84 | } 85 | 86 | publicKey := ed25519.PublicKey(m.Account[:]) 87 | if !ed25519.Verify(publicKey, m.MessageVote.Hash(), m.Signature[:]) { 88 | t.Errorf("Failed to validate signature") 89 | } 90 | } 91 | 92 | func TestReadWriteConfirmReq(t *testing.T) { 93 | var m MessageConfirmReq 94 | buf := bytes.NewBuffer(confirmReq) 95 | err := m.Read(buf) 96 | if err != nil { 97 | t.Errorf("Failed to read message") 98 | } 99 | 100 | var writeBuf bytes.Buffer 101 | err = m.Write(&writeBuf) 102 | if err != nil { 103 | t.Errorf("Failed to write message") 104 | } 105 | 106 | if bytes.Compare(confirmReq, writeBuf.Bytes()) != 0 { 107 | t.Errorf("Wrote message badly") 108 | } 109 | 110 | block := m.ToBlock().(*blocks.ReceiveBlock) 111 | if !blocks.ValidateBlockWork(block) { 112 | t.Errorf("Work validation failed") 113 | } 114 | } 115 | 116 | func TestReadWriteMessagePublish(t *testing.T) { 117 | var m MessagePublish 118 | buf := bytes.NewBuffer(publishOpen) 119 | err := m.Read(buf) 120 | if err != nil { 121 | t.Errorf("Failed to read message") 122 | } 123 | 124 | var writeBuf bytes.Buffer 125 | err = m.Write(&writeBuf) 126 | if err != nil { 127 | t.Errorf("Failed to write message") 128 | } 129 | 130 | if bytes.Compare(publishOpen, writeBuf.Bytes()) != 0 { 131 | t.Errorf("Wrote message badly") 132 | } 133 | 134 | block := m.ToBlock().(*blocks.OpenBlock) 135 | if !blocks.ValidateBlockWork(block) { 136 | t.Errorf("Work validation failed") 137 | } 138 | 139 | if block.Account != "nano_14jyjetsh8p7jxx1of38ctsa779okt9d1pdnmtjpqiukuq8zugr3bxpxf1zu" { 140 | t.Errorf("Deserialised account badly") 141 | } 142 | } 143 | 144 | func validateTestBlock(t *testing.T, b blocks.Block, expectedHash types.BlockHash) { 145 | if b.Hash() != expectedHash { 146 | t.Errorf("Wrong blockhash %s", b.Hash()) 147 | } 148 | if !blocks.ValidateBlockWork(b) { 149 | t.Errorf("Bad PoW") 150 | } 151 | if b.Type() == blocks.Open { 152 | passed, _ := b.(*blocks.OpenBlock).VerifySignature() 153 | if !passed { 154 | t.Errorf("Failed to verify signature") 155 | } 156 | } 157 | } 158 | 159 | func TestReadPublish(t *testing.T) { 160 | var m MessagePublish 161 | err := m.Read(bytes.NewBuffer(publishSend)) 162 | if err != nil { 163 | t.Errorf("Failed to read send message %s", err) 164 | } 165 | validateTestBlock(t, m.ToBlock(), types.BlockHash("687DCB9C8EB8AF9F39D8107C3432A8732EDBED1E3B5E2E0F6B86643D1EB5E24F")) 166 | 167 | err = m.Read(bytes.NewBuffer(publishReceive)) 168 | if err != nil { 169 | t.Errorf("Failed to read receive message %s", err) 170 | } 171 | validateTestBlock(t, m.ToBlock(), types.BlockHash("7D3E9D79342AA73B7148CB46706D23ED8BB0041A5316D67A053F336ABF0E6B60")) 172 | 173 | err = m.Read(bytes.NewBuffer(publishOpen)) 174 | if err != nil { 175 | t.Errorf("Failed to read open message %s", err) 176 | } 177 | validateTestBlock(t, m.ToBlock(), types.BlockHash("5F73CF212E58563734D57CCFCCEFE481DE40C96F097F594F4FA32C5585D84AA4")) 178 | 179 | err = m.Read(bytes.NewBuffer(publishChange)) 180 | if err != nil { 181 | t.Errorf("Failed to read change message %s", err) 182 | } 183 | validateTestBlock(t, m.ToBlock(), types.BlockHash("4AABA9923AC794B635B8C3CC275C37F0D28E43D44EB5E27F8B23955E335D5DD3")) 184 | 185 | err = m.Read(bytes.NewBuffer(publishWrongWork)) 186 | if blocks.ValidateBlockWork(m.ToBlock()) { 187 | t.Errorf("Invalid work should fail") 188 | } 189 | 190 | err = m.Read(bytes.NewBuffer(publishWrongSig)) 191 | passed, _ := m.ToBlock().(*blocks.OpenBlock).VerifySignature() 192 | if passed { 193 | t.Errorf("Invalid signature should fail") 194 | } 195 | } 196 | 197 | func TestHandleMessage(t *testing.T) { 198 | store.Init(store.TestConfig) 199 | handleMessage(bytes.NewBuffer(publishTest)) 200 | } 201 | 202 | func TestReadWriteHeader(t *testing.T) { 203 | var message MessageHeader 204 | buf := bytes.NewBuffer(publishOpen) 205 | message.ReadHeader(buf) 206 | 207 | if message.MagicNumber != MagicNumber { 208 | t.Errorf("Unexpected magic number") 209 | } 210 | 211 | if message.VersionMax != 4 { 212 | t.Errorf("Wrong VersionMax") 213 | } 214 | 215 | if message.VersionUsing != 5 { 216 | t.Errorf("Wrong VersionUsing") 217 | } 218 | 219 | if message.VersionMin != 1 { 220 | t.Errorf("Wrong VersionMin") 221 | } 222 | 223 | if message.MessageType != Message_publish { 224 | t.Errorf("Wrong Message Type") 225 | } 226 | 227 | if message.Extensions != 0 { 228 | t.Errorf("Wrong Extension") 229 | } 230 | 231 | if message.BlockType != BlockType_open { 232 | t.Errorf("Wrong Blocktype") 233 | } 234 | 235 | var writeBuf bytes.Buffer 236 | message.WriteHeader(&writeBuf) 237 | if bytes.Compare(publishOpen[:8], writeBuf.Bytes()) != 0 { 238 | t.Errorf("Wrote header badly") 239 | } 240 | } 241 | --------------------------------------------------------------------------------