├── .gitattributes ├── .gitignore ├── Block ├── block.go ├── block_test.go ├── create.go ├── createLegacy.go ├── get.go ├── hash.go ├── proof.go ├── proof_reference.go ├── proof_test.go └── serialize.go ├── GUI ├── App │ ├── Background │ │ ├── nodes.go │ │ ├── tracker.go │ │ └── transaction.go │ ├── DOM │ │ ├── change.go │ │ ├── create.go │ │ ├── create_js.go │ │ ├── dom.go │ │ ├── dom_js.go │ │ ├── get.go │ │ ├── get_js.go │ │ ├── init.go │ │ ├── modifier.go │ │ ├── modifier_js.go │ │ ├── select.go │ │ ├── select_js.go │ │ └── type.go │ ├── account.go │ ├── nanoalias.go │ ├── nanofy.go │ ├── nanollet.go │ └── settings.go ├── Front │ ├── css │ │ └── README.md │ ├── html │ │ ├── 0_base.html │ │ ├── 1_account.html │ │ ├── 2_nanollet.html │ │ ├── 3_nanofy.html │ │ ├── 4_nanoalias.html │ │ └── 6_settings.html │ └── less │ │ ├── style.less │ │ └── variable.less ├── Generator │ └── gen.go ├── window.go └── window_js.go ├── LICENSE ├── NanoAlias ├── address.go └── easy.go ├── Nanofy ├── easy.go ├── legacy.go ├── legacy_test.go ├── nanofier.go ├── state.go └── state_test.go ├── Nanollet.exe.manifest ├── Node ├── Packets │ ├── bulkpull.go │ ├── bulkpullaccount.go │ ├── bulkpullblocks.go │ ├── bulkpush.go │ ├── confirmack.go │ ├── confirmack_test.go │ ├── confirmreq.go │ ├── confirmreq_test.go │ ├── frontierreq.go │ ├── handshake.go │ ├── handshake_test.go │ ├── header.go │ ├── keepalive.go │ ├── keepalive_test.go │ ├── packet.go │ └── publish.go ├── Peer │ ├── peer.go │ └── vote.go ├── easy.go ├── easy_test.go ├── handler.go ├── nano.go ├── nano_js.go └── nano_mobile.go ├── Numbers ├── human.go ├── human_test.go ├── raw.go ├── raw_test.go └── rawmath.go ├── OpenCAP ├── address.go └── address_test.go ├── README-Image1.png ├── README.md ├── Storage ├── account.go ├── config.go ├── general.go ├── peers.go ├── storage.go ├── storage_js.go └── transactions.go ├── TwoFactor ├── Ephemeral │ └── x25519.go ├── README.md ├── envelope.go ├── envelope_test.go ├── network.go ├── network_js.go ├── network_test.go ├── request.go └── token.go ├── Util ├── base32.go ├── base32_test.go ├── bytes.go ├── dns.go ├── error.go ├── file.go ├── gc.go ├── gc_js.go ├── hash.go ├── hex.go ├── hex_test.go ├── number.go └── string.go ├── Wallet ├── address.go ├── address_test.go ├── deterministic.go ├── deterministic_legacy.go ├── deterministic_legacy_test.go ├── deterministic_test.go └── private.go ├── libsciter-gtk.so ├── logo.png ├── main.go ├── sciter-osx-64.dylib └── sciter.dll /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | 4 | # Test binary, build with `go test -c` 5 | *.test 6 | 7 | # Output of the go coverage tool, specifically when used with LiteIDE 8 | *.out 9 | 10 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 11 | .glide/ 12 | 13 | # IDE 14 | .idea 15 | /GUI/Front/.idea 16 | 17 | # Nanollet 18 | /GUI/Front/*.go 19 | *.css 20 | *.js 21 | *.sig 22 | *.syso 23 | *.zip 24 | *.wasm 25 | *.apk 26 | Nanollet 27 | Nanollet.exe 28 | Nanollet.html 29 | Nanollet.app/* 30 | test/ 31 | *.map 32 | -------------------------------------------------------------------------------- /Block/block.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Numbers" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "encoding" 7 | ) 8 | 9 | var ( 10 | GenesisAccount = Wallet.Address("xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3").MustGetPublicKey() 11 | BurnAccount = Wallet.Address("xrb_1111111111111111111111111111111111111111111111111111hifc8npp").MustGetPublicKey() 12 | Epoch = [32]byte{0x65, 0x70, 0x6f, 0x63, 0x68, 0x20, 0x76, 0x31, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} 13 | ) 14 | 15 | type Transaction interface { 16 | Encode() (data []byte) 17 | encoding.BinaryMarshaler 18 | Decode(data []byte) (err error) 19 | encoding.BinaryUnmarshaler 20 | SwitchToUniversalBlock(previousBlock *UniversalBlock, amm *Numbers.RawAmount) *UniversalBlock 21 | 22 | Work() Work 23 | Hash() BlockHash 24 | 25 | GetAccount() Wallet.PublicKey 26 | GetBalance() *Numbers.RawAmount 27 | GetType() BlockType 28 | GetTarget() (destination Wallet.PublicKey, source BlockHash) 29 | GetPrevious() BlockHash 30 | GetWork() Work 31 | GetSignature() Wallet.Signature 32 | 33 | IsValidPOW() bool 34 | //IsValidSignature() bool 35 | 36 | SetWork(pow Work) 37 | SetSignature(sig Wallet.Signature) 38 | SetFrontier(hash BlockHash) 39 | SetBalance(balance *Numbers.RawAmount) 40 | } 41 | 42 | //-------------- 43 | 44 | type DefaultBlock struct { 45 | PoW Work `json:"work"` 46 | Signature Wallet.Signature `json:"signature"` 47 | } 48 | 49 | //-------------- 50 | 51 | type SendBlock struct { 52 | Previous BlockHash `json:"previous"` 53 | Destination Wallet.PublicKey `json:"destination"` 54 | Balance *Numbers.RawAmount `json:"balance"` 55 | DefaultBlock 56 | } 57 | 58 | //-------------- 59 | 60 | type ReceiveBlock struct { 61 | Previous BlockHash `json:"previous"` 62 | Source BlockHash `json:"source"` 63 | DefaultBlock 64 | } 65 | 66 | //-------------- 67 | 68 | type OpenBlock struct { 69 | Account Wallet.PublicKey `json:"account"` 70 | Representative Wallet.PublicKey `json:"representative"` 71 | Source BlockHash `json:"source"` 72 | DefaultBlock 73 | } 74 | 75 | //-------------- 76 | 77 | type ChangeBlock struct { 78 | Previous BlockHash `json:"previous"` 79 | Representative Wallet.PublicKey `json:"representative"` 80 | DefaultBlock 81 | } 82 | 83 | //-------------- 84 | 85 | type UniversalBlock struct { 86 | Account Wallet.PublicKey `json:"account"` 87 | Previous BlockHash `json:"previous"` 88 | Representative Wallet.PublicKey `json:"representative"` 89 | Balance *Numbers.RawAmount `json:"balance"` 90 | Link BlockHash `json:"link"` 91 | DefaultBlock 92 | } 93 | -------------------------------------------------------------------------------- /Block/block_test.go: -------------------------------------------------------------------------------- 1 | package Block 2 | -------------------------------------------------------------------------------- /Block/create.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "errors" 5 | "github.com/brokenbydefault/Nanollet/Numbers" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "crypto/subtle" 8 | ) 9 | 10 | var ( 11 | ErrEndBlock = errors.New("not a block") 12 | ErrInvalidBlock = errors.New("invalid block") 13 | ErrInvalidBlockType = errors.New("invalid type") 14 | ) 15 | 16 | func NewTransaction(blockType BlockType) (blk Transaction, size int, err error) { 17 | switch blockType & 0x0F { 18 | case Send: 19 | blk = &SendBlock{} 20 | size = SendSize 21 | case Receive: 22 | blk = &ReceiveBlock{} 23 | size = ReceiveSize 24 | case Change: 25 | blk = &ChangeBlock{} 26 | size = ChangeSize 27 | case Open: 28 | blk = &OpenBlock{} 29 | size = OpenSize 30 | case State: 31 | blk = &UniversalBlock{} 32 | size = StateSize 33 | case Invalid: 34 | err = ErrInvalidBlock 35 | case NotABlock: 36 | err = ErrEndBlock 37 | default: 38 | err = ErrInvalidBlockType 39 | } 40 | 41 | return blk, size, err 42 | } 43 | 44 | func CreateUniversalSendBlock(sk *Wallet.SecretKey, representative Wallet.PublicKey, balance, sending *Numbers.RawAmount, previous BlockHash, destination Wallet.PublicKey) (Transaction, error) { 45 | blk := &UniversalBlock{ 46 | Account: sk.PublicKey(), 47 | Representative: representative, 48 | Balance: balance.Subtract(sending), 49 | Previous: previous, 50 | Link: BlockHash(destination), 51 | } 52 | 53 | if sk == nil { 54 | return blk, nil 55 | } 56 | 57 | return attachSignature(sk, blk) 58 | } 59 | 60 | func CreateUniversalOpenBlock(sk *Wallet.SecretKey, representative Wallet.PublicKey, receiving *Numbers.RawAmount, source BlockHash) (Transaction, error) { 61 | blk := &UniversalBlock{ 62 | Account: sk.PublicKey(), 63 | Representative: representative, 64 | Balance: receiving, 65 | //Previous: NewBlockHash(nil), 66 | Link: source, 67 | } 68 | 69 | if sk == nil { 70 | return blk, nil 71 | } 72 | 73 | return attachSignature(sk, blk) 74 | } 75 | 76 | func CreateUniversalReceiveBlock(sk *Wallet.SecretKey, representative Wallet.PublicKey, balance, receiving *Numbers.RawAmount, previous BlockHash, source BlockHash) (Transaction, error) { 77 | blk := &UniversalBlock{ 78 | Account: sk.PublicKey(), 79 | Representative: representative, 80 | Balance: balance.Add(receiving), 81 | Previous: previous, 82 | Link: source, 83 | } 84 | 85 | if sk == nil { 86 | return blk, nil 87 | } 88 | 89 | return attachSignature(sk, blk) 90 | } 91 | 92 | func CreateUniversalChangeBlock(sk *Wallet.SecretKey, representative Wallet.PublicKey, balance *Numbers.RawAmount, previous BlockHash) (Transaction, error) { 93 | blk := &UniversalBlock{ 94 | Account: sk.PublicKey(), 95 | Representative: representative, 96 | Balance: balance, 97 | Previous: previous, 98 | //Link: NewBlockHash(nil), 99 | } 100 | 101 | if sk == nil { 102 | return blk, nil 103 | } 104 | 105 | return attachSignature(sk, blk) 106 | } 107 | 108 | func CreateSignedUniversalReceiveOrOpenBlock(sk *Wallet.SecretKey, representative Wallet.PublicKey, balance, receiving *Numbers.RawAmount, previous BlockHash, source BlockHash) (Transaction, error) { 109 | pk := sk.PublicKey() 110 | 111 | if previous == NewBlockHash(nil) || subtle.ConstantTimeCompare(previous[:], pk[:]) == 1 { 112 | return CreateUniversalOpenBlock(sk, representative, receiving, source) 113 | } 114 | 115 | return CreateUniversalReceiveBlock(sk, representative, balance, receiving, previous, source) 116 | } 117 | 118 | func attachSignature(sk *Wallet.SecretKey, blk Transaction) (Transaction, error) { 119 | hash := blk.Hash() 120 | 121 | sig, err := sk.Sign(hash[:]) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | blk.SetSignature(sig) 127 | 128 | return blk, err 129 | } 130 | -------------------------------------------------------------------------------- /Block/createLegacy.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "crypto/subtle" 5 | "github.com/brokenbydefault/Nanollet/Numbers" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | ) 8 | 9 | var ( 10 | DefaultRepresentative, _ = Wallet.Address("xrb_1ywcdyz7djjdaqbextj4wh1db3wykze5ueh9wnmbgrcykg3t5k1se7zyjf95").GetPublicKey() 11 | ) 12 | 13 | func CreateSendBlock(sk *Wallet.SecretKey, sending, balance *Numbers.RawAmount, previous BlockHash, destination Wallet.PublicKey) (Transaction, error) { 14 | blk, err := CreateUniversalSendBlock(sk, Wallet.NewPublicKey(nil), balance, sending, previous, destination) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | legacy := blk.SwitchToUniversalBlock(nil, nil).SwitchTo(Send) 20 | 21 | if sk == nil { 22 | return legacy, nil 23 | } 24 | 25 | return attachSignature(sk, legacy) 26 | } 27 | 28 | func CreateOpenBlock(sk *Wallet.SecretKey, source BlockHash) (Transaction, error) { 29 | blk, err := CreateUniversalOpenBlock(sk, Wallet.NewPublicKey(nil), Numbers.NewRaw(), source) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | legacy := blk.SwitchToUniversalBlock(nil, nil).SwitchTo(Open) 35 | 36 | if sk == nil { 37 | return legacy, nil 38 | } 39 | 40 | return attachSignature(sk, legacy) 41 | } 42 | 43 | func CreateReceiveBlock(sk *Wallet.SecretKey, source, previous BlockHash) (Transaction, error) { 44 | blk, err := CreateUniversalReceiveBlock(sk, Wallet.NewPublicKey(nil), Numbers.NewRaw(), Numbers.NewRaw(), previous, source) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | legacy := blk.SwitchToUniversalBlock(nil, nil).SwitchTo(Receive) 50 | 51 | if sk == nil { 52 | return legacy, nil 53 | } 54 | 55 | return attachSignature(sk, legacy) 56 | } 57 | 58 | func CreateChangeBlock(sk *Wallet.SecretKey, previous BlockHash, representative Wallet.PublicKey) (Transaction, error) { 59 | blk, err := CreateUniversalChangeBlock(sk, representative, Numbers.NewRaw(), previous) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | legacy := blk.SwitchToUniversalBlock(nil, nil).SwitchTo(Change) 65 | 66 | if sk == nil { 67 | return legacy, nil 68 | } 69 | 70 | return attachSignature(sk, legacy) 71 | } 72 | 73 | func CreateReceiveOrOpenBlock(sk *Wallet.SecretKey, source, previous BlockHash) (blk Transaction, err error) { 74 | pk := sk.PublicKey() 75 | 76 | if previous == NewBlockHash(nil) || subtle.ConstantTimeCompare(previous[:], pk[:]) == 1 { 77 | return CreateOpenBlock(sk, source) 78 | } 79 | 80 | return CreateReceiveBlock(sk, source, previous) 81 | } 82 | -------------------------------------------------------------------------------- /Block/get.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Numbers" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "github.com/brokenbydefault/Nanollet/Util" 7 | ) 8 | 9 | func (d *DefaultBlock) SetWork(w Work) { 10 | d.PoW = w 11 | } 12 | 13 | func (d *DefaultBlock) GetWork() Work { 14 | return d.PoW 15 | } 16 | 17 | func (d *DefaultBlock) SetSignature(s Wallet.Signature) { 18 | d.Signature = s 19 | } 20 | 21 | func (d *DefaultBlock) GetSignature() Wallet.Signature { 22 | return d.Signature 23 | } 24 | 25 | func (s *SendBlock) GetType() BlockType { 26 | return Send 27 | } 28 | 29 | func (s *ReceiveBlock) GetType() BlockType { 30 | return Receive 31 | } 32 | 33 | func (s *OpenBlock) GetType() BlockType { 34 | return Open 35 | } 36 | 37 | func (s *ChangeBlock) GetType() BlockType { 38 | return Change 39 | } 40 | 41 | func (u *UniversalBlock) GetType() BlockType { 42 | return State 43 | } 44 | 45 | func (s *SendBlock) GetAccount() (pk Wallet.PublicKey) { 46 | return pk 47 | } 48 | 49 | func (s *ReceiveBlock) GetAccount() (pk Wallet.PublicKey) { 50 | return pk 51 | } 52 | 53 | func (s *OpenBlock) GetAccount() (pk Wallet.PublicKey) { 54 | return s.Account 55 | } 56 | 57 | func (s *ChangeBlock) GetAccount() (pk Wallet.PublicKey) { 58 | return pk 59 | } 60 | 61 | func (u *UniversalBlock) GetAccount() (pk Wallet.PublicKey) { 62 | return u.Account 63 | } 64 | 65 | func (s *SendBlock) SetFrontier(h BlockHash) { 66 | s.Previous = h 67 | } 68 | 69 | func (s *ReceiveBlock) SetFrontier(h BlockHash) { 70 | s.Previous = h 71 | } 72 | 73 | func (s *OpenBlock) SetFrontier(h BlockHash) { 74 | s.Source = h 75 | } 76 | 77 | func (s *ChangeBlock) SetFrontier(h BlockHash) { 78 | s.Previous = h 79 | } 80 | 81 | func (u *UniversalBlock) SetFrontier(h BlockHash) { 82 | copy(u.Previous[:], h[:]) 83 | } 84 | 85 | func (s *SendBlock) GetBalance() *Numbers.RawAmount { 86 | return Numbers.NewRawFromBytes(s.Balance.ToBytes()) 87 | } 88 | 89 | func (s *ReceiveBlock) GetBalance() *Numbers.RawAmount { 90 | // no-op 91 | return nil 92 | } 93 | 94 | func (s *OpenBlock) GetBalance() *Numbers.RawAmount { 95 | // no-op 96 | return nil 97 | } 98 | 99 | func (s *ChangeBlock) GetBalance() *Numbers.RawAmount { 100 | // no-op 101 | return nil 102 | } 103 | 104 | func (u *UniversalBlock) GetBalance() *Numbers.RawAmount { 105 | return Numbers.NewRawFromBytes(u.Balance.ToBytes()) 106 | } 107 | 108 | func (s *SendBlock) SetBalance(n *Numbers.RawAmount) { 109 | s.Balance = n 110 | } 111 | 112 | func (s *ReceiveBlock) SetBalance(n *Numbers.RawAmount) { 113 | // no-op 114 | } 115 | 116 | func (s *OpenBlock) SetBalance(n *Numbers.RawAmount) { 117 | // no-op 118 | } 119 | 120 | func (s *ChangeBlock) SetBalance(n *Numbers.RawAmount) { 121 | // no-op 122 | } 123 | 124 | func (u *UniversalBlock) SetBalance(n *Numbers.RawAmount) { 125 | u.Balance = n 126 | } 127 | 128 | func (s *SendBlock) GetTarget() (pk Wallet.PublicKey, hash BlockHash) { 129 | return s.Destination, hash 130 | } 131 | 132 | func (s *ReceiveBlock) GetTarget() (pk Wallet.PublicKey, hash BlockHash) { 133 | return pk, s.Source 134 | } 135 | 136 | func (s *OpenBlock) GetTarget() (pk Wallet.PublicKey, hash BlockHash) { 137 | return pk, s.Source 138 | } 139 | 140 | func (s *ChangeBlock) GetTarget() (pk Wallet.PublicKey, hash BlockHash) { 141 | return pk, hash 142 | } 143 | 144 | func (u *UniversalBlock) GetTarget() (destination Wallet.PublicKey, source BlockHash) { 145 | return Wallet.PublicKey(u.Link), u.Link 146 | } 147 | 148 | func GetSubType(tx, txPrevious Transaction) BlockType { 149 | if tx.GetType() != State { 150 | return tx.GetType() 151 | } 152 | 153 | if hashPrev := tx.GetPrevious(); Util.IsEmpty(hashPrev[:]) { 154 | return Open 155 | } 156 | 157 | if dest, source := tx.GetTarget(); Util.IsEmpty(dest[:]) && Util.IsEmpty(source[:]) { 158 | return Change 159 | } 160 | 161 | if txPrevious.GetBalance() == nil { 162 | return NotABlock 163 | } 164 | 165 | if tx.GetBalance().Compare(txPrevious.GetBalance()) == 1 { 166 | return Receive 167 | } else { 168 | return Send 169 | } 170 | 171 | return Invalid 172 | } 173 | 174 | func GetAmount(tx, txPrevious Transaction) *Numbers.RawAmount { 175 | if tx.GetBalance() == nil { 176 | return Numbers.NewMin() 177 | } 178 | 179 | switch GetSubType(tx, txPrevious) { 180 | case Open: 181 | return tx.GetBalance() 182 | case Change: 183 | return Numbers.NewMin() 184 | case Send: 185 | fallthrough 186 | case Receive: 187 | if txPrevious.GetBalance() == nil { 188 | return Numbers.NewMin() 189 | } 190 | return tx.GetBalance().Subtract(txPrevious.GetBalance()).Abs() 191 | } 192 | 193 | return Numbers.NewMin() 194 | } 195 | -------------------------------------------------------------------------------- /Block/hash.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Util" 5 | "golang.org/x/crypto/blake2b" 6 | ) 7 | 8 | type BlockHash [blake2b.Size256]byte 9 | 10 | func NewBlockHash(b []byte) (hash BlockHash) { 11 | copy(hash[:], b) 12 | return hash 13 | } 14 | 15 | var universalBlockFlag = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06} 16 | 17 | func (s *SendBlock) Hash() BlockHash { 18 | return NewBlockHash(Util.CreateHash(32, s.Encode()[1:SendHashableSize])) 19 | } 20 | 21 | func (s *ReceiveBlock) Hash() BlockHash { 22 | return NewBlockHash(Util.CreateHash(32, s.Encode()[1:ReceiveHashableSize])) 23 | } 24 | 25 | func (s *OpenBlock) Hash() BlockHash { 26 | return NewBlockHash(Util.CreateHash(32, s.Encode()[1:OpenHashableSize])) 27 | } 28 | 29 | func (s *ChangeBlock) Hash() BlockHash { 30 | return NewBlockHash(Util.CreateHash(32, s.Encode()[1:ChangeHashableSize])) 31 | } 32 | 33 | func (u *UniversalBlock) Hash() BlockHash { 34 | return NewBlockHash(Util.CreateHash(32, universalBlockFlag, u.Encode()[1:StateHashableSize])) 35 | } 36 | 37 | func (s *SendBlock) GetPrevious() (hash BlockHash) { 38 | return s.Previous 39 | } 40 | 41 | func (s *ReceiveBlock) GetPrevious() (hash BlockHash) { 42 | return s.Previous 43 | } 44 | 45 | func (s *OpenBlock) GetPrevious() (hash BlockHash) { 46 | return NewBlockHash(nil) 47 | } 48 | 49 | func (s *ChangeBlock) GetPrevious() (hash BlockHash) { 50 | return s.Previous 51 | } 52 | 53 | func (u *UniversalBlock) GetPrevious() (hash BlockHash) { 54 | if Util.IsEmpty(u.Previous[:]) { 55 | return NewBlockHash(nil) 56 | } 57 | 58 | return u.Previous 59 | } 60 | 61 | func IsEpoch(tx Transaction) bool { 62 | if tx.GetType() != State { 63 | return false 64 | } 65 | 66 | if dest, _ := tx.GetTarget(); dest != Epoch { 67 | return false 68 | } 69 | 70 | if acc := tx.GetAccount(); acc == BurnAccount { 71 | return false 72 | } 73 | 74 | if hash, sig := tx.Hash(), tx.GetSignature(); GenesisAccount.IsValidSignature(hash[:], &sig) { 75 | return true 76 | } 77 | 78 | return false 79 | } -------------------------------------------------------------------------------- /Block/proof.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "encoding/binary" 5 | "golang.org/x/crypto/blake2b" 6 | "runtime" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | ) 9 | 10 | type Work [8]byte 11 | 12 | func NewWork(b []byte) (work Work) { 13 | copy(work[:], b) 14 | return work 15 | } 16 | 17 | func (s *SendBlock) Work() Work { 18 | if !s.PoW.IsValid(&s.Previous) { 19 | s.PoW = GenerateProof(&s.Previous) 20 | } 21 | 22 | return s.PoW 23 | } 24 | 25 | func (s *ReceiveBlock) Work() Work { 26 | if !s.PoW.IsValid(&s.Previous) { 27 | s.PoW = GenerateProof(&s.Previous) 28 | } 29 | 30 | return s.PoW 31 | } 32 | 33 | func (s *OpenBlock) Work() Work { 34 | // Open operation uses the account instead of previous 35 | previous := NewBlockHash(s.Account[:]) 36 | 37 | if !s.PoW.IsValid(&previous) { 38 | s.PoW = GenerateProof(&previous) 39 | } 40 | 41 | return s.PoW 42 | } 43 | 44 | func (s *ChangeBlock) Work() Work { 45 | if !s.PoW.IsValid(&s.Previous) { 46 | s.PoW = GenerateProof(&s.Previous) 47 | } 48 | 49 | return s.PoW 50 | } 51 | 52 | func (u *UniversalBlock) Work() Work { 53 | var previous BlockHash 54 | 55 | // Open operation uses the account instead of previous 56 | if Util.IsEmpty(u.Previous[:]) { 57 | previous = NewBlockHash(u.Account[:]) 58 | } else { 59 | previous = u.Previous 60 | } 61 | 62 | if !u.PoW.IsValid(&previous) { 63 | u.PoW = GenerateProof(&previous) 64 | } 65 | 66 | return u.PoW 67 | } 68 | 69 | func (s *SendBlock) IsValidPOW() bool { 70 | return s.PoW.IsValid(&s.Previous) 71 | } 72 | 73 | func (s *ReceiveBlock) IsValidPOW() bool { 74 | return s.PoW.IsValid(&s.Previous) 75 | } 76 | 77 | func (s *OpenBlock) IsValidPOW() bool { 78 | hash := BlockHash(s.Account) 79 | return s.PoW.IsValid(&hash) 80 | } 81 | 82 | func (s *ChangeBlock) IsValidPOW() bool { 83 | return s.PoW.IsValid(&s.Previous) 84 | } 85 | 86 | func (u *UniversalBlock) IsValidPOW() bool { 87 | var previous BlockHash 88 | 89 | // Open operation uses the account instead of previous 90 | if Util.IsEmpty(u.Previous[:]) { 91 | previous = BlockHash(u.Account) 92 | } else { 93 | previous = u.Previous 94 | } 95 | 96 | return u.PoW.IsValid(&previous) 97 | } 98 | 99 | var MinimumWork = uint64(0xffffffc000000000) 100 | 101 | // GenerateProof will generate the proof of work for given hash, which must be the "previous" or "account" in case 102 | // of open. 103 | func GenerateProof(hash *BlockHash) (nonce Work) { 104 | limit := uint64(runtime.NumCPU()) 105 | shard := uint64(1<<64-1) / limit 106 | 107 | result := make(chan uint64, 32) 108 | stop := make(chan bool) 109 | 110 | for i := uint64(0); i < limit; i++ { 111 | go createProof(hash[:], i*shard, result, stop) 112 | } 113 | 114 | n := <-result 115 | close(stop) 116 | close(result) 117 | clear(result) 118 | 119 | binary.BigEndian.PutUint64(nonce[:], n) 120 | 121 | return nonce 122 | } 123 | 124 | func (w *Work) IsValid(previous *BlockHash) bool { 125 | nonce := make([]byte, 8) 126 | binary.LittleEndian.PutUint64(nonce, binary.BigEndian.Uint64(w[:])) 127 | 128 | return binary.LittleEndian.Uint64(Util.CreateHash(8, nonce, previous[:])) >= MinimumWork 129 | } 130 | 131 | func createProof(blockHash []byte, attempt uint64, result chan uint64, stop chan bool) { 132 | h, _ := blake2b.New(8, nil) 133 | nonce := make([]byte, 40) 134 | copy(nonce[8:], blockHash) 135 | 136 | for { 137 | select { 138 | default: 139 | binary.LittleEndian.PutUint64(nonce[:8], attempt) 140 | 141 | h.Reset() 142 | h.Write(nonce) 143 | 144 | if binary.LittleEndian.Uint64(h.Sum(nil)) >= MinimumWork { 145 | result <- attempt 146 | } 147 | 148 | attempt++ 149 | 150 | case <-stop: 151 | return 152 | } 153 | } 154 | } 155 | 156 | func clear(r chan uint64) { 157 | for len(r) > 0 { 158 | <-r 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Block/proof_reference.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package Block 4 | 5 | import ( 6 | "encoding/binary" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | ) 9 | 10 | func ReferenceGenerateProof(blockHash []byte) []byte { 11 | var attempt uint64 12 | var nonce = make([]byte, 8) 13 | 14 | for attempt = 0; attempt < 1<<64-1; attempt++ { 15 | 16 | binary.LittleEndian.PutUint64(nonce, attempt) 17 | hash := Util.CreateHash(8, nonce, blockHash) 18 | 19 | if binary.LittleEndian.Uint64(hash) >= MinimumWork { 20 | break 21 | } 22 | 23 | } 24 | 25 | return Util.ReverseBytes(nonce) 26 | } 27 | -------------------------------------------------------------------------------- /Block/proof_test.go: -------------------------------------------------------------------------------- 1 | package Block 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Util" 5 | "testing" 6 | ) 7 | 8 | var Genesis = NewBlockHash(Util.UnsafeHexMustDecode("991CF190094C00F0B68E2E5F75F6BEE95A2E0BD93CEAA4A6734DB9F19B728948")) 9 | 10 | func BenchmarkGenerateWork(b *testing.B) { 11 | for n := 0; n < b.N; n++ { 12 | GenerateProof(&Genesis) 13 | } 14 | } 15 | 16 | func TestIsValidProof(t *testing.T) { 17 | gen := GenerateProof(&Genesis) 18 | if !gen.IsValid(&Genesis) { 19 | t.Error("valid proof reported as invalid") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GUI/App/Background/nodes.go: -------------------------------------------------------------------------------- 1 | package Background 2 | 3 | import ( 4 | "time" 5 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 6 | ) 7 | 8 | func UpdateNodeCount(w *DOM.Window) { 9 | for range time.Tick(10 * time.Second) { 10 | DOM.UpdateNodesCount(w) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GUI/App/Background/tracker.go: -------------------------------------------------------------------------------- 1 | package Background 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 6 | "github.com/brokenbydefault/Nanollet/Storage" 7 | "github.com/brokenbydefault/Nanollet/Numbers" 8 | "github.com/brokenbydefault/Nanollet/Node" 9 | "github.com/brokenbydefault/Nanollet/Node/Packets" 10 | "time" 11 | "github.com/brokenbydefault/Nanollet/Node/Peer" 12 | ) 13 | 14 | var Connection Node.Node 15 | 16 | func init() { 17 | Connection = Node.NewServer( 18 | Storage.Configuration.Node.Header, 19 | &Storage.PeerStorage, 20 | &Storage.TransactionStorage, 21 | ) 22 | 23 | Handler := Node.NewHandler(Connection) 24 | Handler.PublishHandler = PublishHandler 25 | 26 | if err := Handler.Start(); err != nil { 27 | panic(err) 28 | } 29 | } 30 | 31 | func PublishHandler(node Node.Node, _ *Peer.Peer, rHeader *Packets.Header, msg []byte) { 32 | packet := new(Packets.PushPackage) 33 | 34 | if err := packet.Decode(rHeader, msg); err != nil { 35 | return 36 | } 37 | 38 | dest, _ := packet.Transaction.GetTarget() 39 | acc := packet.Transaction.GetAccount() 40 | if acc != Storage.AccountStorage.PublicKey && dest != Storage.AccountStorage.PublicKey { 41 | return 42 | } 43 | 44 | if !packet.Transaction.IsValidPOW() { 45 | return 46 | } 47 | 48 | node.Transactions().Add(packet.Transaction) 49 | } 50 | 51 | func StartAddress(w *DOM.Window) error { 52 | txs, err := Node.GetHistory(Connection, &Storage.AccountStorage.PublicKey, nil) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if len(txs) == 0 { 58 | Storage.AccountStorage.Frontier = Block.NewBlockHash(nil) 59 | Storage.AccountStorage.Representative = Storage.Configuration.Account.Representative 60 | Storage.AccountStorage.Balance = Numbers.NewMin() 61 | } else { 62 | 63 | if txs[0].GetType() == Block.State { 64 | Storage.AccountStorage.Frontier = txs[0].Hash() 65 | Storage.AccountStorage.Representative = txs[0].SwitchToUniversalBlock(nil, nil).Representative 66 | Storage.AccountStorage.Balance = txs[0].GetBalance() 67 | } else { 68 | balance, err := Node.GetBalance(Connection, &Storage.AccountStorage.PublicKey) 69 | if err == nil { 70 | return err 71 | } 72 | 73 | Storage.AccountStorage.Frontier = txs[0].Hash() 74 | for _, tx := range txs { 75 | if typ := tx.GetType(); typ == Block.Change || typ == Block.Open { 76 | Storage.AccountStorage.Representative = tx.SwitchToUniversalBlock(nil, nil).Representative 77 | break 78 | } 79 | } 80 | Storage.AccountStorage.Balance = balance 81 | } 82 | 83 | } 84 | 85 | go realtimeUpdate(w) 86 | 87 | Storage.TransactionStorage.Add(txs...) 88 | DOM.UpdateAmount(w) 89 | 90 | go pending(w) 91 | 92 | return nil 93 | } 94 | 95 | func realtimeUpdate(w *DOM.Window) { 96 | for t := range Storage.TransactionStorage.Listen() { 97 | tx := t 98 | 99 | if dest, _ := tx.GetTarget(); dest != Storage.AccountStorage.PublicKey { 100 | continue 101 | } 102 | 103 | hash := tx.Hash() 104 | if tx, ok := Storage.TransactionStorage.GetByLinkHash(&hash); ok { 105 | hash, sig := tx.Hash(), tx.GetSignature() 106 | if Storage.AccountStorage.PublicKey.IsValidSignature(hash[:], &sig) { 107 | // Ignore if it's already received. 108 | continue 109 | } 110 | } 111 | 112 | if !Storage.TransactionStorage.IsConfirmed(&hash, &Storage.Configuration.Account.Quorum) { 113 | DOM.UpdateNotification(w, "New payment identified, voting in progress.") 114 | } 115 | 116 | go acceptPending(w, tx) 117 | } 118 | } 119 | 120 | func acceptPending(w *DOM.Window, tx Block.Transaction) { 121 | hash := tx.Hash() 122 | 123 | if waitVotesConfirmation(tx, 6*time.Minute) { 124 | amount, err := Node.GetAmount(Connection, tx) 125 | if err != nil { 126 | return 127 | } 128 | 129 | blk, err := Block.CreateSignedUniversalReceiveOrOpenBlock(&Storage.AccountStorage.SecretKey, Storage.AccountStorage.Representative, Storage.AccountStorage.Balance, amount, Storage.AccountStorage.Frontier, hash) 130 | if err != nil { 131 | return 132 | } 133 | 134 | if err := PublishBlockToQueue(blk, Block.Receive, amount); err == nil { 135 | DOM.UpdateNotification(w, "You have received a new payment.") 136 | DOM.UpdateAmount(w) 137 | } 138 | } 139 | } 140 | 141 | func pending(w *DOM.Window) { 142 | txsPend, err := Node.GetPendings(Connection, &Storage.AccountStorage.PublicKey, Storage.Configuration.Account.MinimumAmount) 143 | if err != nil { 144 | return 145 | } 146 | 147 | Storage.TransactionStorage.Add(txsPend...) 148 | } 149 | -------------------------------------------------------------------------------- /GUI/App/Background/transaction.go: -------------------------------------------------------------------------------- 1 | package Background 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "github.com/brokenbydefault/Nanollet/Storage" 6 | "github.com/brokenbydefault/Nanollet/Numbers" 7 | "github.com/brokenbydefault/Nanollet/Node" 8 | "time" 9 | "errors" 10 | ) 11 | 12 | var ( 13 | ErrInsufficientVotes = errors.New("insufficient votes") 14 | ErrInvalidAmount = errors.New("invalid amount") 15 | ErrInvalidFrontier = errors.New("invalid frontier") 16 | ) 17 | 18 | type WaitConfirmation chan error 19 | 20 | type txs struct { 21 | blocks []Block.Transaction 22 | blockType Block.BlockType 23 | returning WaitConfirmation 24 | amount *Numbers.RawAmount 25 | } 26 | 27 | var queue = make(chan txs, 64) 28 | 29 | func init() { 30 | go listen() 31 | } 32 | 33 | func PublishBlocksToQueue(blk []Block.Transaction, blockType Block.BlockType, amounts ...*Numbers.RawAmount) error { 34 | var waitchan = make(WaitConfirmation) 35 | defer close(waitchan) 36 | 37 | // amount is optional, set default of 0 if missing 38 | var amount *Numbers.RawAmount 39 | if len(amounts) == 0 { 40 | amount = Numbers.NewMin() 41 | } else { 42 | amount = amounts[0] 43 | } 44 | 45 | queue <- txs{ 46 | blocks: blk, 47 | returning: waitchan, 48 | blockType: blockType, 49 | amount: amount, 50 | } 51 | 52 | return <-waitchan 53 | } 54 | 55 | func PublishBlockToQueue(blk Block.Transaction, blockType Block.BlockType, amount ...*Numbers.RawAmount) error { 56 | return PublishBlocksToQueue([]Block.Transaction{blk}, blockType, amount...) 57 | } 58 | 59 | func listen() { 60 | 61 | for tx := range queue { 62 | var err error 63 | for _, blk := range tx.blocks { 64 | err = processBlock(blk, tx.blockType, tx.amount) 65 | if err != nil { 66 | break 67 | } 68 | } 69 | 70 | tx.returning <- err 71 | } 72 | 73 | } 74 | 75 | func processBlock(tx Block.Transaction, blockType Block.BlockType, amm *Numbers.RawAmount) error { 76 | var err error = nil 77 | var balance *Numbers.RawAmount 78 | 79 | switch blockType { 80 | case Block.Send: 81 | balance = Storage.AccountStorage.Balance.Subtract(amm) 82 | case Block.Open: 83 | balance = Storage.AccountStorage.Balance.Add(amm) 84 | case Block.Receive: 85 | balance = Storage.AccountStorage.Balance.Add(amm) 86 | default: 87 | balance = Storage.AccountStorage.Balance 88 | } 89 | 90 | if !balance.IsValid() { 91 | return ErrInvalidAmount 92 | } 93 | 94 | tx.SetFrontier(Storage.AccountStorage.Frontier) 95 | //@TODO Support pre-computed PoW, again. 96 | //blk.SetWork(Storage.RetrievePrecomputedPoW()) 97 | 98 | tx.Work() 99 | tx.SetBalance(balance) 100 | 101 | hash := tx.Hash() 102 | sig, err := Storage.AccountStorage.SecretKey.Sign(hash[:]) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | tx.SetSignature(sig) 108 | 109 | if err = Node.PostBlock(Connection, tx); err != nil { 110 | return err 111 | } 112 | 113 | Storage.TransactionStorage.Add(tx) 114 | 115 | //@TODO improve if not reach the quorum 116 | if !waitVotesConfirmation(tx, 2*time.Minute) { 117 | Storage.TransactionStorage.Remove(tx) 118 | return ErrInsufficientVotes 119 | } 120 | 121 | Storage.AccountStorage.Balance = tx.GetBalance() 122 | Storage.AccountStorage.Representative = tx.SwitchToUniversalBlock(nil, nil).Representative 123 | Storage.AccountStorage.Frontier = tx.Hash() 124 | 125 | return nil 126 | } 127 | 128 | func waitVotesConfirmation(tx Block.Transaction, duration time.Duration) bool { 129 | start := time.Now() 130 | hash := tx.Hash() 131 | 132 | for range time.Tick(2 * time.Second) { 133 | if Storage.TransactionStorage.IsConfirmed(&hash, &Storage.Configuration.Account.Quorum) { 134 | return true 135 | } 136 | 137 | Node.RequestVotes(Connection, tx) 138 | 139 | if time.Since(start) > duration { 140 | break 141 | } 142 | } 143 | 144 | return false 145 | } 146 | -------------------------------------------------------------------------------- /GUI/App/DOM/change.go: -------------------------------------------------------------------------------- 1 | package DOM 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Numbers" 5 | "github.com/brokenbydefault/Nanollet/Storage" 6 | "strconv" 7 | ) 8 | 9 | func UpdateAmount(w *Window) error { 10 | humanAmm := Numbers.NewHumanFromRaw(Storage.AccountStorage.Balance) 11 | 12 | for el, scale := range map[string]int{ 13 | ".ammount": 6, 14 | } { 15 | balance, err := humanAmm.ConvertToBase(Numbers.MegaXRB, scale) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | display, err := w.root.SelectFirstElement(el) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | err = display.SetValue(balance) 26 | if err != nil { 27 | return err 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func UpdateNodesCount(w *Window) error { 35 | for _, el := range []string{".nodes"} { 36 | display, err := w.root.SelectFirstElement(el) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | active, all := Storage.PeerStorage.CountActive() 42 | 43 | if err = display.SetValue(strconv.Itoa(active) + " / " + strconv.Itoa(all)); err != nil { 44 | return err 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func UpdateNotification(w *Window, msg string) { 52 | box, _ := w.root.SelectFirstElement("section.notification") 53 | 54 | nt := box.CreateElementWithAttr("button", msg, Attrs{"class": "notification"}) 55 | nt.On(Click, func(_ string) { 56 | nt.Apply(DestroyHTML) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /GUI/App/DOM/create.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package DOM 4 | 5 | import ( 6 | "github.com/sciter-sdk/go-sciter" 7 | "encoding/base64" 8 | ) 9 | 10 | func (el *Element) CreateElement(tag, text string) *Element { 11 | e, _ := sciter.CreateElement(tag, text) 12 | 13 | el.el.Append(e) 14 | return NewElement(e) 15 | } 16 | 17 | type Attrs map[string]string 18 | 19 | func (el *Element) CreateElementWithAttr(tag, text string, attrs Attrs) *Element { 20 | e := el.CreateElement(tag, text) 21 | for name, value := range attrs { 22 | e.SetAttr(name, value) 23 | } 24 | 25 | return e 26 | } 27 | 28 | func (el *Element) CreateQRCode(png []byte) *Element { 29 | return el.CreateElementWithAttr("img", "", Attrs{"src": "data:image/png;base64, " + base64.StdEncoding.EncodeToString(png)}) 30 | } -------------------------------------------------------------------------------- /GUI/App/DOM/create_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package DOM 4 | 5 | import ( 6 | "honnef.co/go/js/dom" 7 | "encoding/base64" 8 | ) 9 | 10 | func (el *Element) CreateElement(tag, text string) *Element { 11 | root := dom.GetWindow().Document().(dom.HTMLDocument) 12 | 13 | e := root.CreateElement(tag) 14 | if text != "" { 15 | e.SetTextContent(text) 16 | } 17 | 18 | el.el.AppendChild(e) 19 | return NewElement(e) 20 | } 21 | 22 | type Attrs map[string]string 23 | 24 | func (el *Element) CreateElementWithAttr(tag, text string, attrs Attrs) *Element { 25 | e := el.CreateElement(tag, text) 26 | for name, value := range attrs { 27 | e.SetAttr(name, value) 28 | } 29 | 30 | return e 31 | } 32 | 33 | func (el *Element) CreateQRCode(png []byte) *Element { 34 | return el.CreateElementWithAttr("img", "", Attrs{"src": "data:image/png;base64, " + base64.StdEncoding.EncodeToString(png)}) 35 | } -------------------------------------------------------------------------------- /GUI/App/DOM/dom.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package DOM 4 | 5 | import ( 6 | "github.com/sciter-sdk/go-sciter/window" 7 | "github.com/sciter-sdk/go-sciter" 8 | ) 9 | 10 | type Window struct { 11 | win *window.Window 12 | root *DOM 13 | } 14 | 15 | type DOM struct { 16 | el *Element 17 | } 18 | 19 | type Element struct { 20 | el *sciter.Element 21 | } 22 | 23 | func NewWindow(w *window.Window) *Window { 24 | el, err := w.GetRootElement() 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | return &Window{ 30 | win: w, 31 | root: &DOM{el: &Element{el: el}}, 32 | } 33 | } 34 | 35 | func NewDOMApplication(app Application, w *Window) *DOM { 36 | el, err := w.win.GetRootElement() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | el, err = el.SelectFirst("[application=\"" + app.Name() + "\"]") 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | return &DOM{el: &Element{el}} 47 | } 48 | 49 | func NewDOMPage(page Page, dom *DOM) *DOM { 50 | el, err := dom.el.el.SelectFirst("[page=\"" + page.Name() + "\"]") 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | return &DOM{el: &Element{el}} 56 | } 57 | 58 | func NewElement(sciterEl *sciter.Element) *Element { 59 | return &Element{ 60 | sciterEl, 61 | } 62 | } 63 | 64 | func NewElements(sciterEls []*sciter.Element) []*Element { 65 | els := make([]*Element, len(sciterEls)) 66 | 67 | for i, el := range sciterEls { 68 | els[i] = &Element{el,} 69 | } 70 | 71 | return els 72 | } 73 | -------------------------------------------------------------------------------- /GUI/App/DOM/dom_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package DOM 4 | 5 | import ( 6 | "honnef.co/go/js/dom" 7 | ) 8 | 9 | type Window struct { 10 | win *dom.Window 11 | root *DOM 12 | } 13 | 14 | type DOM struct { 15 | el *Element 16 | } 17 | 18 | type Element struct { 19 | el dom.Element 20 | } 21 | 22 | func NewWindow(w dom.Window) *Window { 23 | return &Window{ 24 | win: &w, 25 | root: &DOM{el: &Element{el: w.Document().QuerySelector("body")}}, 26 | } 27 | } 28 | 29 | func NewDOMApplication(app Application, w *Window) *DOM { 30 | el, err := w.root.SelectFirstElement("[application=\"" + app.Name() + "\"]") 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | return &DOM{el: el} 36 | } 37 | 38 | func NewDOMPage(page Page, dom *DOM) *DOM { 39 | el, err := dom.SelectFirstElement("[page=\"" + page.Name() + "\"]") 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | return &DOM{el: el} 45 | } 46 | 47 | func NewElement(jsEl dom.Element) *Element { 48 | return &Element{jsEl} 49 | } 50 | 51 | func NewElements(jsEls []dom.Element) []*Element { 52 | els := make([]*Element, len(jsEls)) 53 | 54 | for i, el := range jsEls { 55 | els[i] = &Element{el} 56 | } 57 | 58 | return els 59 | } 60 | -------------------------------------------------------------------------------- /GUI/App/DOM/get.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package DOM 4 | 5 | import ( 6 | "bytes" 7 | "os" 8 | "io" 9 | ) 10 | 11 | func (el *Element) GetAttr(name string) (result string, err error) { 12 | return el.el.Attr(name) 13 | } 14 | 15 | func (dom *DOM) GetAttrOf(name string, css string) (result string, err error) { 16 | input, err := dom.SelectFirstElement(css) 17 | if err != nil { 18 | return 19 | } 20 | 21 | return input.GetAttr(name) 22 | } 23 | 24 | func (el *Element) GetText() (result string, err error) { 25 | return el.el.Text() 26 | } 27 | 28 | func (el *Element) GetStringValue() (result string, err error) { 29 | value, err := el.el.GetValue() 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return value.String(), nil 35 | } 36 | 37 | func (dom *DOM) GetStringValueOf(css string) (result string, err error) { 38 | input, err := dom.SelectFirstElement(css) 39 | if err != nil { 40 | return 41 | } 42 | 43 | return input.GetStringValue() 44 | } 45 | 46 | func (el *Element) GetBytesValue() (result []byte, err error) { 47 | value, err := el.el.GetValue() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return []byte(value.String()), nil 53 | } 54 | 55 | func (dom *DOM) GetBytesValueOf(css string) (result []byte, err error) { 56 | input, err := dom.SelectFirstElement(css) 57 | if err != nil { 58 | return 59 | } 60 | 61 | return input.GetBytesValue() 62 | } 63 | 64 | func (el *Element) GetFile() (io.Reader, error) { 65 | input, err := el.GetStringValue() 66 | if err != nil || input == "" { 67 | return nil, ErrInvalidElement 68 | } 69 | 70 | file, err := os.Open(input[7:]) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | defer file.Close() 76 | 77 | if stat, err := file.Stat(); err != nil || stat.IsDir() { 78 | return nil, ErrInvalidElement 79 | } 80 | 81 | r := bytes.NewBuffer(nil) 82 | if _, err := io.Copy(r, file); err != nil { 83 | return nil, err 84 | } 85 | 86 | return r, nil 87 | } 88 | 89 | func (dom *DOM) GetFileOf(css string) (io.Reader, error) { 90 | input, err := dom.SelectFirstElement(css) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return input.GetFile() 96 | } 97 | -------------------------------------------------------------------------------- /GUI/App/DOM/get_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package DOM 4 | 5 | import ( 6 | "honnef.co/go/js/dom" 7 | "strings" 8 | "io" 9 | "bytes" 10 | "github.com/gopherjs/gopherjs/js" 11 | ) 12 | 13 | func (el *Element) GetAttr(name string) (result string, err error) { 14 | return el.el.GetAttribute(name), nil 15 | } 16 | 17 | func (dom *DOM) GetAttrOf(name string, css string) (result string, err error) { 18 | input, err := dom.SelectFirstElement(css) 19 | if err != nil { 20 | return 21 | } 22 | 23 | return input.GetAttr(name) 24 | } 25 | 26 | func (el *Element) GetText() (result string, err error) { 27 | return el.el.TextContent(), nil 28 | } 29 | 30 | func (el *Element) GetStringValue() (result string, err error) { 31 | if e, ok := el.el.(*dom.HTMLTextAreaElement); ok { 32 | return e.Value, nil 33 | } 34 | 35 | e, ok := el.el.(*dom.HTMLInputElement) 36 | if !ok { 37 | return "", nil 38 | } 39 | 40 | if t := strings.ToUpper(e.Type); (t == "CHECKBOX" || t == "OPTION") && !e.Checked { 41 | return "", nil 42 | } 43 | 44 | return e.Value, nil 45 | } 46 | 47 | func (dom *DOM) GetStringValueOf(css string) (result string, err error) { 48 | input, err := dom.SelectFirstElement(css) 49 | if err != nil { 50 | return 51 | } 52 | 53 | return input.GetStringValue() 54 | } 55 | 56 | func (el *Element) GetBytesValue() (result []byte, err error) { 57 | r, err := el.GetStringValue() 58 | return []byte(r), err 59 | } 60 | 61 | func (dom *DOM) GetBytesValueOf(css string) (result []byte, err error) { 62 | input, err := dom.SelectFirstElement(css) 63 | if err != nil { 64 | return 65 | } 66 | 67 | return input.GetBytesValue() 68 | } 69 | 70 | func (el *Element) GetFile() (io.Reader, error) { 71 | input, ok := el.el.(*dom.HTMLInputElement) 72 | if !ok { 73 | return nil, ErrInvalidElement 74 | } 75 | 76 | var b = make(chan io.Reader) 77 | fileReader := js.Global.Get("FileReader").New() 78 | fileReader.Set("onload", func() { 79 | b <- bytes.NewReader(js.Global.Get("Uint8Array").New(fileReader.Get("result")).Interface().([]byte)) 80 | }) 81 | fileReader.Call("readAsArrayBuffer", input.Files()[0].Object) 82 | 83 | return <-b, nil 84 | } 85 | 86 | func (dom *DOM) GetFileOf(css string) (io.Reader, error) { 87 | input, err := dom.SelectFirstElement(css) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | return input.GetFile() 93 | } 94 | -------------------------------------------------------------------------------- /GUI/App/DOM/init.go: -------------------------------------------------------------------------------- 1 | package DOM 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func (w *Window) InitApplication(app Application) { 8 | w.StartApplication(app) 9 | 10 | if !app.HaveSidebar() { 11 | button, err := w.root.SelectFirstElement(`.control button[id="`+ strings.Title(app.Name()) +`"]`) 12 | if err == nil { 13 | DestroyHTML(button.el) 14 | } 15 | 16 | return 17 | } 18 | 19 | button, err := w.root.SelectFirstElement(`.control button[id="`+ strings.Title(app.Name()) +`"]`) 20 | if err != nil { 21 | panic("element .control button[id="+ strings.Title(app.Name()) +"] was not found") 22 | } 23 | 24 | button.On(Click, func(class string) { 25 | w.root.ApplyForAll(".control button", DisableElement) 26 | defer w.root.ApplyForAll(".control button", EnableElement) 27 | w.ViewApplication(app) 28 | }) 29 | 30 | for _, p := range app.Pages() { 31 | page := p 32 | 33 | pagebutton, err := w.root.SelectFirstElement(".control aside button."+strings.Title(page.Name())) 34 | if err != nil { 35 | panic("element .control aside button."+strings.Title(page.Name())+" was not found") 36 | } 37 | 38 | pagebutton.On(Click, func(class string) { 39 | w.root.ApplyForAll(".control button", DisableElement) 40 | defer w.root.ApplyForAll(".control button", EnableElement) 41 | w.ViewPage(page) 42 | }) 43 | } 44 | } 45 | 46 | func (w *Window) ViewApplication(app Application) error { 47 | w.root.ApplyForAll(".application button, [page]", HideElement) 48 | 49 | if app.HaveSidebar() { 50 | el, _ := w.root.SelectFirstElement("body") 51 | el.SetAttr("class", "") 52 | } 53 | 54 | w.root.ApplyForAll(".application#"+strings.Title(app.Name())+" button", ShowElement) 55 | 56 | return w.ViewPage(app.Pages()[0]) 57 | } 58 | 59 | func (w *Window) StartApplication(app Application) { 60 | domAPP := NewDOMApplication(app, w) 61 | 62 | for _, p := range app.Pages() { 63 | page := p 64 | dom := NewDOMPage(page, domAPP) 65 | 66 | btns, err := dom.SelectAllElement(`button, input[type="submit"]`) 67 | if err != nil { 68 | return 69 | } 70 | 71 | for _, btn := range btns { 72 | btn.On(Click, func(class string) { 73 | defer dom.ApplyForAll(`button, input[type="submit"]`, EnableElement) 74 | dom.ApplyForAll(`button, input[type="submit"]`, DisableElement) 75 | page.OnContinue(w, dom, class) 76 | }) 77 | } 78 | } 79 | 80 | return 81 | } 82 | 83 | func (w *Window) ViewPage(page Page) error { 84 | w.root.ApplyForAll(".control button", UnvisitedElement) 85 | w.root.ApplyFor(".control button."+strings.Title(page.Name()), VisitedElement) 86 | 87 | w.root.ApplyForAll("[page]", HideElement) 88 | 89 | dom := NewDOMPage(page, w.root) 90 | page.OnView(w, dom) 91 | dom.el.Apply(ShowElement) 92 | 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /GUI/App/DOM/modifier.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package DOM 4 | 5 | import ( 6 | "github.com/sciter-sdk/go-sciter" 7 | ) 8 | 9 | func (el *Element) Apply(mod Modifier) error { 10 | return mod(el.el) 11 | } 12 | 13 | func (el *Element) SetAttr(name, value string) error { 14 | return el.el.SetAttr(name, value) 15 | } 16 | 17 | func (el *Element) SetText(text string) error { 18 | return el.el.SetText(text) 19 | } 20 | 21 | func (el *Element) SetValue(value string) error { 22 | return el.el.SetValue(sciter.NewValue(value)) 23 | } 24 | 25 | func (el *Element) SetHTML(html string, method ReplaceMethod) error { 26 | return el.el.SetHtml(html, sciter.SET_ELEMENT_HTML(method)) 27 | } 28 | 29 | func (el *Element) On(method ActionMethod, f func(class string)) (err error) { 30 | class, _ := el.GetAttr("class") 31 | 32 | switch method { 33 | case Click: 34 | el.el.OnClick(func() { 35 | go f(class) 36 | }) 37 | default: 38 | err = ErrInvalidActionMethod 39 | } 40 | 41 | return err 42 | } 43 | 44 | func (dom *DOM) ApplyForAll(css string, mod Modifier) error { 45 | els, err := dom.SelectAllElement(css) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | for _, el := range els { 51 | if err := el.Apply(mod); err != nil { 52 | return err 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (dom *DOM) ApplyFor(css string, mod Modifier) error { 60 | el, err := dom.SelectFirstElement(css) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | return el.Apply(mod) 66 | } 67 | 68 | type Modifier func(el *sciter.Element) error 69 | 70 | func DisableElement(el *sciter.Element) error { 71 | return el.SetState(sciter.STATE_DISABLED, 0, true) 72 | } 73 | 74 | func EnableElement(el *sciter.Element) error { 75 | return el.SetState(0, sciter.STATE_DISABLED, true) 76 | } 77 | 78 | func HideElement(el *sciter.Element) error { 79 | return el.SetStyle("display", "none") 80 | } 81 | 82 | func ShowElement(el *sciter.Element) error { 83 | return el.SetStyle("display", "block") 84 | } 85 | 86 | func VisibleElement(el *sciter.Element) error { 87 | return el.SetStyle("visibility", "visible") 88 | } 89 | 90 | func InvisibleElement(el *sciter.Element) error { 91 | return el.SetStyle("visibility", "hidden") 92 | } 93 | 94 | func VisitedElement(el *sciter.Element) error { 95 | return el.SetState(sciter.STATE_VISITED, 0, true) 96 | } 97 | 98 | func UnvisitedElement(el *sciter.Element) error { 99 | return el.SetState(0, sciter.STATE_VISITED, true) 100 | } 101 | 102 | func ReadOnlyElement(el *sciter.Element) error { 103 | return el.SetState(sciter.STATE_READONLY, 0, true) 104 | } 105 | 106 | func WriteOnlyElement(el *sciter.Element) error { 107 | return el.SetState(0, sciter.STATE_READONLY, true) 108 | } 109 | 110 | func Checked(el *sciter.Element) error { 111 | return el.SetState(sciter.STATE_CHECKED, 0, true) 112 | } 113 | 114 | func Unchecked(el *sciter.Element) error { 115 | return el.SetState(0, sciter.STATE_CHECKED, true) 116 | } 117 | 118 | func ClearValue(el *sciter.Element) error { 119 | return el.SetValue(sciter.NewValue()) 120 | } 121 | 122 | func ClearHTML(el *sciter.Element) error { 123 | return el.SetHtml(" ", sciter.SIH_REPLACE_CONTENT) 124 | } 125 | 126 | func DestroyHTML(el *sciter.Element) error { 127 | el.SetHtml(" ", sciter.SOH_REPLACE) 128 | return el.Clear() 129 | } 130 | -------------------------------------------------------------------------------- /GUI/App/DOM/modifier_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package DOM 4 | 5 | import "honnef.co/go/js/dom" 6 | 7 | func (el *Element) Apply(mod Modifier) error { 8 | return mod(el.el) 9 | } 10 | 11 | func (el *Element) SetAttr(name, value string) error { 12 | el.el.SetAttribute(name, value) 13 | return nil 14 | } 15 | 16 | func (el *Element) SetText(text string) error { 17 | el.el.SetTextContent(text) 18 | return nil 19 | } 20 | 21 | func (el *Element) SetValue(value string) error { 22 | if _, ok := el.el.(*dom.HTMLTextAreaElement); ok { 23 | el.SetText(value) 24 | } 25 | el.el.SetNodeValue(value) 26 | return nil 27 | } 28 | 29 | func (el *Element) SetHTML(html string, method ReplaceMethod) (err error) { 30 | switch method { 31 | case InnerReplaceContent: 32 | el.el.SetInnerHTML(html) 33 | case InnerPrepend: 34 | el.el.SetInnerHTML(el.el.InnerHTML() + html) 35 | case InnerAppend: 36 | el.el.SetInnerHTML(html + el.el.InnerHTML()) 37 | case OuterReplace: 38 | el.el.SetOuterHTML(html) 39 | case OuterPrepend: 40 | el.el.SetOuterHTML(html + el.el.OuterHTML()) 41 | case OuterAppend: 42 | el.el.SetOuterHTML(el.el.OuterHTML() + html) 43 | default: 44 | err = ErrInvalidReplaceMethod 45 | } 46 | 47 | return err 48 | } 49 | 50 | func (el *Element) On(method ActionMethod, f func(class string)) (err error) { 51 | class, _ := el.GetAttr("class") 52 | 53 | switch method { 54 | case Click: 55 | el.el.AddEventListener("click", false, func(_ dom.Event) { 56 | go f(class) 57 | }) 58 | default: 59 | err = ErrInvalidActionMethod 60 | } 61 | 62 | return err 63 | } 64 | 65 | func (dom *DOM) ApplyForAll(css string, mod Modifier) error { 66 | els, err := dom.SelectAllElement(css) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | for _, el := range els { 72 | if err := el.Apply(mod); err != nil { 73 | return err 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func (dom *DOM) ApplyFor(css string, mod Modifier) error { 81 | el, err := dom.SelectFirstElement(css) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | return el.Apply(mod) 87 | } 88 | 89 | type Modifier func(el dom.Element) error 90 | 91 | func DisableElement(el dom.Element) error { 92 | el.SetAttribute("disabled", "") 93 | return nil 94 | } 95 | 96 | func EnableElement(el dom.Element) error { 97 | el.RemoveAttribute("disabled") 98 | return nil 99 | } 100 | 101 | func HideElement(el dom.Element) error { 102 | el.SetAttribute("style", "display: none;") 103 | return nil 104 | } 105 | 106 | func ShowElement(el dom.Element) error { 107 | el.SetAttribute("style", "display: block;") 108 | return nil 109 | } 110 | 111 | func VisibleElement(el dom.Element) error { 112 | el.SetAttribute("style", "visibility: visible;") 113 | return nil 114 | } 115 | 116 | func InvisibleElement(el dom.Element) error { 117 | el.SetAttribute("style", "visibility: hidden;") 118 | return nil 119 | } 120 | 121 | func VisitedElement(el dom.Element) error { 122 | return nil 123 | } 124 | 125 | func UnvisitedElement(el dom.Element) error { 126 | return nil 127 | } 128 | 129 | func ReadOnlyElement(el dom.Element) error { 130 | el.SetAttribute("readonly", "") 131 | return nil 132 | } 133 | 134 | func WriteOnlyElement(el dom.Element) error { 135 | el.RemoveAttribute("readonly") 136 | return nil 137 | } 138 | 139 | func Checked(el dom.Element) error { 140 | el.SetAttribute("checked", "") 141 | return nil 142 | } 143 | 144 | func Unchecked(el dom.Element) error { 145 | el.RemoveAttribute("checked") 146 | return nil 147 | } 148 | 149 | func ClearValue(el dom.Element) error { 150 | el.SetNodeValue("") 151 | return nil 152 | } 153 | 154 | func ClearHTML(el dom.Element) error { 155 | el.SetTextContent("") 156 | return nil 157 | } 158 | 159 | func DestroyHTML(el dom.Element) error { 160 | return ClearHTML(el) 161 | } 162 | -------------------------------------------------------------------------------- /GUI/App/DOM/select.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package DOM 4 | 5 | import "errors" 6 | 7 | var ( 8 | ErrInvalidElement = errors.New("invalid element") 9 | ) 10 | 11 | func (el *Element) SelectAllElement(css string) ([]*Element, error) { 12 | e, err := el.el.Select(css) 13 | if e == nil { 14 | err = ErrInvalidElement 15 | } 16 | return NewElements(e), err 17 | } 18 | 19 | func (el *Element) SelectFirstElement(css string) (*Element, error) { 20 | e, err := el.el.SelectFirst(css) 21 | if e == nil { 22 | err = ErrInvalidElement 23 | } 24 | return NewElement(e), err 25 | } 26 | 27 | func (dom *DOM) SelectAllElement(css string) ([]*Element, error) { 28 | return dom.el.SelectAllElement(css) 29 | } 30 | 31 | func (dom *DOM) SelectFirstElement(css string) (*Element, error) { 32 | return dom.el.SelectFirstElement(css) 33 | } -------------------------------------------------------------------------------- /GUI/App/DOM/select_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package DOM 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | var ( 10 | ErrInvalidElement = errors.New("invalid element") 11 | ) 12 | 13 | func (el *Element) SelectAllElement(css string) ([]*Element, error) { 14 | e := el.el.QuerySelectorAll(css) 15 | if len(e) == 0 { 16 | return nil, ErrInvalidElement 17 | } 18 | 19 | return NewElements(e), nil 20 | } 21 | 22 | func (el *Element) SelectFirstElement(css string) (*Element, error) { 23 | e, err := el.SelectAllElement(css) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return e[0], nil 29 | } 30 | 31 | func (dom *DOM) SelectAllElement(css string) ([]*Element, error) { 32 | return dom.el.SelectAllElement(css) 33 | } 34 | 35 | func (dom *DOM) SelectFirstElement(css string) (*Element, error) { 36 | return dom.el.SelectFirstElement(css) 37 | } -------------------------------------------------------------------------------- /GUI/App/DOM/type.go: -------------------------------------------------------------------------------- 1 | package DOM 2 | 3 | import "errors" 4 | 5 | type Application interface { 6 | Pages() []Page 7 | Name() string 8 | HaveSidebar() bool 9 | } 10 | 11 | type Page interface { 12 | Name() string 13 | OnView(w *Window, dom *DOM) 14 | OnContinue(w *Window, dom *DOM, action string) 15 | } 16 | 17 | type ActionMethod int 18 | 19 | const ( 20 | Click = iota 21 | ) 22 | 23 | var ( 24 | ErrInvalidActionMethod = errors.New("invalid action method") 25 | ) 26 | 27 | type ReplaceMethod int 28 | 29 | const ( 30 | InnerReplaceContent ReplaceMethod = iota 31 | InnerPrepend 32 | InnerAppend 33 | 34 | OuterReplace 35 | OuterPrepend 36 | OuterAppend 37 | ) 38 | 39 | var ( 40 | ErrInvalidReplaceMethod = errors.New("invalid replace method") 41 | ) 42 | -------------------------------------------------------------------------------- /GUI/App/nanoalias.go: -------------------------------------------------------------------------------- 1 | package App 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 5 | "github.com/brokenbydefault/Nanollet/NanoAlias" 6 | "github.com/brokenbydefault/Nanollet/Storage" 7 | "strings" 8 | ) 9 | 10 | type NanoAliasApp struct{} 11 | 12 | func (c *NanoAliasApp) Name() string { 13 | return "nanoalias" 14 | } 15 | 16 | func (c *NanoAliasApp) HaveSidebar() bool { 17 | return true 18 | } 19 | 20 | func (c *NanoAliasApp) Pages() []DOM.Page { 21 | return []DOM.Page{ 22 | &PageRegister{}, 23 | } 24 | } 25 | 26 | type PageRegister struct{} 27 | 28 | func (c *PageRegister) Name() string { 29 | return "register" 30 | } 31 | 32 | func (c *PageRegister) OnView(w *DOM.Window, dom *DOM.DOM) { 33 | return 34 | } 35 | 36 | func (c *PageRegister) OnContinue(w *DOM.Window, dom *DOM.DOM, action string) { 37 | alias, err := dom.GetStringValueOf(".alias") 38 | if err != nil || alias == "" { 39 | return 40 | } 41 | 42 | alias = strings.TrimSpace(alias) 43 | alias = strings.ToLower(alias) 44 | 45 | if rune(alias[0]) != '@' { 46 | alias = "@" + alias 47 | } 48 | 49 | if ok := NanoAlias.Address(alias).IsValid(); !ok { 50 | DOM.UpdateNotification(w, "You can't use this alias. Invalid characters on the alias") 51 | return 52 | } 53 | 54 | previous, ok := Storage.TransactionStorage.GetByHash(&Storage.AccountStorage.Frontier) 55 | if !ok { 56 | DOM.UpdateNotification(w, "Can't find your last block, you need a opened account") 57 | return 58 | } 59 | 60 | if ok := NanoAlias.IsAvailable(alias); !ok { 61 | DOM.UpdateNotification(w, "You can't use this alias. It's already registered") 62 | return 63 | } 64 | 65 | defer DOM.UpdateAmount(w) 66 | if err := NanoAlias.Register(&Storage.AccountStorage.SecretKey, previous, alias); err != nil { 67 | DOM.UpdateNotification(w, "Impossible to register due to some error") //@TODO improve error 68 | return 69 | } 70 | 71 | DOM.UpdateNotification(w, "Complete! Try now, send using "+alias) 72 | return 73 | } 74 | 75 | type PageLookup struct{} 76 | 77 | func (c *PageLookup) Name() string { 78 | return "lookup" 79 | } 80 | 81 | func (c *PageLookup) OnView(w *DOM.Window, dom *DOM.DOM) { 82 | return 83 | } 84 | 85 | func (c *PageLookup) OnContinue(w *DOM.Window, dom *DOM.DOM, action string) { 86 | panic("implement me") 87 | } 88 | -------------------------------------------------------------------------------- /GUI/App/nanofy.go: -------------------------------------------------------------------------------- 1 | package App 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Nanofy" 5 | "github.com/brokenbydefault/Nanollet/GUI/App/Background" 6 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 7 | "github.com/brokenbydefault/Nanollet/Storage" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "github.com/brokenbydefault/Nanollet/Node" 10 | "github.com/brokenbydefault/Nanollet/Block" 11 | ) 12 | 13 | type NanofyApp struct{} 14 | 15 | func (c *NanofyApp) Name() string { 16 | return "nanofy" 17 | } 18 | 19 | func (c *NanofyApp) HaveSidebar() bool { 20 | return true 21 | } 22 | 23 | func (c *NanofyApp) Pages() []DOM.Page { 24 | return []DOM.Page{ 25 | &PageSign{}, 26 | &PageVerify{}, 27 | } 28 | } 29 | 30 | type PageSign struct{} 31 | 32 | func (c *PageSign) Name() string { 33 | return "sign" 34 | } 35 | 36 | func (c *PageSign) OnView(w *DOM.Window, dom *DOM.DOM) { 37 | // no-op 38 | } 39 | 40 | func (c *PageSign) OnContinue(w *DOM.Window, dom *DOM.DOM, _ string) { 41 | file, err := dom.GetFileOf(".filepath") 42 | if err != nil { 43 | DOM.UpdateNotification(w, "There was a problem opening the file") 44 | return 45 | } 46 | 47 | previous, ok := Storage.TransactionStorage.GetByHash(&Storage.AccountStorage.Frontier) 48 | if !ok { 49 | DOM.UpdateNotification(w, "Previous block not found") 50 | return 51 | } 52 | 53 | nanofier, err := Nanofy.NewStateSigner(file, &Storage.AccountStorage.SecretKey, previous) 54 | if err != nil { 55 | DOM.UpdateNotification(w, "There was a problem creating a block") 56 | return 57 | } 58 | 59 | blks, err := nanofier.CreateBlocks() 60 | if err != nil { 61 | DOM.UpdateNotification(w, "There was a problem creating a block") 62 | return 63 | } 64 | 65 | err = Background.PublishBlocksToQueue(blks, Block.Send, nanofier.Amount()) 66 | if err != nil { 67 | DOM.UpdateNotification(w, "There was a problem sending a block") 68 | return 69 | } 70 | 71 | DOM.UpdateAmount(w) 72 | DOM.UpdateNotification(w, "Your signature was sent successfully.") 73 | 74 | nameBox, _ := dom.SelectFirstElement(".name") 75 | nameBox.SetHTML("Drop the file here", DOM.InnerReplaceContent) 76 | 77 | dom.ApplyFor(".filepath", DOM.ClearValue) 78 | } 79 | 80 | type PageVerify struct{} 81 | 82 | func (c *PageVerify) Name() string { 83 | return "verify" 84 | } 85 | 86 | func (c *PageVerify) OnView(w *DOM.Window, dom *DOM.DOM) { 87 | // no-op 88 | } 89 | 90 | func (c *PageVerify) OnContinue(w *DOM.Window, dom *DOM.DOM, _ string) { 91 | addr, _ := dom.GetStringValueOf(".address") 92 | if addr == "" { 93 | return 94 | } 95 | 96 | pk, err := Wallet.Address(addr).GetPublicKey() 97 | if !Wallet.Address(addr).IsValid() || err != nil { 98 | DOM.UpdateNotification(w, "The given address is invalid") 99 | return 100 | } 101 | 102 | file, err := dom.GetFileOf(".filepath") 103 | if err != nil { 104 | DOM.UpdateNotification(w, "There was a problem opening the file") 105 | return 106 | } 107 | 108 | txs, err := Node.GetHistory(Background.Connection, &pk, nil) 109 | if err != nil { 110 | DOM.UpdateNotification(w, "There was a problem retrieving the information") 111 | return 112 | } 113 | 114 | if Nanofy.VerifyFromHistory(file, pk, txs) { 115 | DOM.UpdateNotification(w, "Correct! This address signs this given file") 116 | } else { 117 | DOM.UpdateNotification(w, "Wrong! This address never had signed this file") 118 | } 119 | 120 | nameBox, _ := dom.SelectFirstElement(".name") 121 | nameBox.SetHTML("Drop the file here", DOM.InnerReplaceContent) 122 | 123 | dom.ApplyFor(".filepath", DOM.ClearValue) 124 | } 125 | -------------------------------------------------------------------------------- /GUI/App/settings.go: -------------------------------------------------------------------------------- 1 | package App 2 | 3 | import ( 4 | "github.com/Inkeliz/blakEd25519" 5 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 6 | "github.com/brokenbydefault/Nanollet/Storage" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "strings" 10 | ) 11 | 12 | type SettingsApp struct{} 13 | 14 | func (c *SettingsApp) Name() string { 15 | return "settings" 16 | } 17 | 18 | func (c *SettingsApp) HaveSidebar() bool { 19 | return true 20 | } 21 | 22 | func (c *SettingsApp) Pages() []DOM.Page { 23 | return []DOM.Page{ 24 | &PageSeed{}, 25 | &PageAuthorities{}, 26 | } 27 | } 28 | 29 | type PageSeed struct{} 30 | 31 | func (c *PageSeed) Name() string { 32 | return "seed" 33 | } 34 | 35 | func (c *PageSeed) OnView(w *DOM.Window, dom *DOM.DOM) { 36 | seedbox, err := dom.SelectFirstElement(".seed") 37 | if err != nil { 38 | return 39 | } 40 | 41 | skbox, err := dom.SelectFirstElement(".sk") 42 | if err != nil { 43 | return 44 | } 45 | 46 | seedbox.SetValue(Storage.PersistentStorage.SeedFY.String()) 47 | skbox.SetValue(Util.SecureHexEncode(Storage.AccountStorage.SecretKey[:blakEd25519.PublicKeySize])) 48 | } 49 | 50 | func (c *PageSeed) OnContinue(w *DOM.Window, dom *DOM.DOM, action string) { 51 | //no-op 52 | } 53 | 54 | type PageAuthorities struct{} 55 | 56 | func (c *PageAuthorities) Name() string { 57 | return "authorities" 58 | } 59 | 60 | func (c *PageAuthorities) OnView(w *DOM.Window, dom *DOM.DOM) { 61 | autbox, err := dom.SelectFirstElement(".aut") 62 | if err != nil { 63 | return 64 | } 65 | 66 | 67 | var auts string 68 | for _, pk := range Storage.Configuration.Account.Quorum.PublicKeys { 69 | auts += string(pk.CreateAddress()) 70 | auts += "\n" 71 | } 72 | 73 | autbox.SetValue(auts) 74 | } 75 | 76 | func (c *PageAuthorities) OnContinue(w *DOM.Window, dom *DOM.DOM, action string) { 77 | autbox, err := dom.SelectFirstElement(".aut") 78 | if err != nil { 79 | return 80 | } 81 | 82 | auts, err := autbox.GetStringValue() 83 | if err != nil { 84 | return 85 | } 86 | 87 | var pks []Wallet.PublicKey 88 | for _, addr := range strings.Split(strings.Replace(auts, "\r\n", "\n", -1), "\n") { 89 | address := Wallet.Address(strings.TrimSpace(addr)) 90 | if address.IsValid() { 91 | pks = append(pks, address.MustGetPublicKey()) 92 | } 93 | } 94 | 95 | Storage.Configuration.Account.Quorum.PublicKeys = pks 96 | Storage.PersistentStorage.Quorum = Storage.Configuration.Account.Quorum 97 | Storage.Engine.Save(&Storage.PersistentStorage) 98 | } 99 | -------------------------------------------------------------------------------- /GUI/Front/css/README.md: -------------------------------------------------------------------------------- 1 | _In this folder should be the CSS, transcript from LESS._ -------------------------------------------------------------------------------- /GUI/Front/html/0_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Nanollet 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 |
24 | 27 |
28 |
29 | 30 |
31 | {{range .Menu}} 32 | 33 | 39 | {{end}} 40 |
41 | 42 |
43 | {{range .App}} 44 | {{.}} 45 | {{end}} 46 |
47 | 48 |
49 |
50 | 54 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /GUI/Front/html/1_account.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | MIT License 8 | Copyright (c) 2018 Inkeliz 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 11 | associated documentation files (the "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 14 | following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial 17 | portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 20 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 21 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | ------------ 26 | 27 | This Application (or Component) uses Sciter Engine (http://sciter.com/), copyright Terra Informatica 28 | Software, Inc. 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Losing your SEEDFY, or even the password, prevents access the wallet. 59 | 60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Keep the SEEDFY in a safe place, don't throw it away after access your wallet. 76 | 77 | 78 |
79 |
80 | 81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | You must remember your password in order to access the same wallet. 96 | 97 |
98 |
99 | 100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 |
110 | 111 |
112 |
113 | 114 | 115 |
116 | 119 | 122 | 124 |
125 | 126 | 127 | 128 |
129 |
130 | 131 | 132 |
133 | -------------------------------------------------------------------------------- /GUI/Front/html/2_nanollet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 | 52 |
53 |
54 |
55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | No transactions were found. :( 67 |
68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 | 76 |
-------------------------------------------------------------------------------- /GUI/Front/html/3_nanofy.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 |
Drop the file here
8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 |
Drop the file here
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /GUI/Front/html/4_nanoalias.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
-------------------------------------------------------------------------------- /GUI/Front/html/6_settings.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /GUI/Front/less/variable.less: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------- 3 | 4 | @icon-font-path: "fonts"; 5 | @img-path: "front/imgs"; 6 | 7 | // ------------------------- 8 | 9 | @color-primary: #183a56; 10 | @color-secondary: #152360; 11 | @color-tertiary: #4a90e2; 12 | 13 | @color-input-dark: #000034; 14 | 15 | @color-dark: #08080e; 16 | @color-divider: #ea891f; 17 | @color-light: #efefef; 18 | @color-gray-light: #dcdcdf; 19 | @color-gray-dark: #c5c5c8; 20 | @color-light-blue: #4da7e5; 21 | @color-dark-blue: #2c3265; 22 | 23 | @color-font-secondary: #7f8088; 24 | // ------------------------- 25 | 26 | @w-nav: 100vw; 27 | @h-nav: 40px; 28 | 29 | // ------------------------- 30 | 31 | @w-sidebar: 160px; 32 | @h-sidebar: ~"calc(100vh - @{h-nav})"; 33 | 34 | @w-sidebar-content: 100%; 35 | @h-sidebar-content: 60px; 36 | 37 | @w-sidebar-menu: 100%; 38 | @h-sidebar-menu: 150px; 39 | 40 | // ------------------------- 41 | 42 | @w-sidebar-control: 76px; 43 | @h-sidebar-control: 100vh; 44 | 45 | @w-sidebar-control-button: 44px; 46 | @h-sidebar-control-button: 44px; 47 | 48 | //--------------------------- 49 | 50 | @w-account-outside: 0px; 51 | 52 | @w-account: calc(@w-sidebar + @w-sidebar-control + @w-account-outside + 3px); 53 | @w-account-logo: 50px; 54 | @w-account-ammount: calc(@w-account-logo - @w-sidebar + @w-sidebar-control + @w-account-outside + 3px - 30px); 55 | @h-account: 70px; 56 | 57 | // ------------------------- 58 | 59 | @h-topbar: 80px; 60 | @w-topbar: ~"calc(100vw - (@{w-sidebar}))"; 61 | @w-topbar-container: 100px; 62 | @hw-topbar-icon: 32px; 63 | 64 | // ------------------------- 65 | 66 | @h-main: ~"calc(100vh - @{h-nav})"; 67 | @w-main: ~"calc(100vw - (@{w-sidebar} + @{w-account-outside}))"; 68 | 69 | // ------------------------- 70 | 71 | @icon-logout: "\e900"; 72 | @icon-theme: "\e901"; 73 | @icon-message: "\e902"; 74 | @icon-post: "\e903"; 75 | @icon-app: "\e904"; 76 | @icon-setting: "\e905"; 77 | @icon-security: "\e98f"; 78 | 79 | 80 | -------------------------------------------------------------------------------- /GUI/window.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package GUI 4 | 5 | import ( 6 | "github.com/brokenbydefault/Nanollet/GUI/App" 7 | "github.com/brokenbydefault/Nanollet/GUI/Front" 8 | "github.com/brokenbydefault/Nanollet/Storage" 9 | "github.com/sciter-sdk/go-sciter" 10 | "github.com/sciter-sdk/go-sciter/window" 11 | "github.com/brokenbydefault/Nanollet/Wallet" 12 | "github.com/brokenbydefault/Nanollet/GUI/App/Background" 13 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 14 | ) 15 | 16 | func init() { 17 | path, err := Storage.Engine.Write("sciter.link", Front.Sciter) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | sciter.SetDLL(path) 23 | } 24 | 25 | func Start() { 26 | 27 | w, err := window.New(sciter.SW_MAIN|sciter.SW_RESIZEABLE|sciter.SW_TITLEBAR|sciter.SW_CONTROLS|sciter.SW_GLASSY|sciter.SW_OWNS_VM, sciter.NewRect(200, 200, 900, 600)) 28 | if err != nil { 29 | panic(err) 30 | } 31 | w.SetTitle("Nanollet") 32 | 33 | if Storage.Configuration.DebugStatus { 34 | w.SetOption(sciter.SCITER_SET_DEBUG_MODE, 1) 35 | } 36 | 37 | w.LoadHtml(Front.HTML, "/") 38 | w.SetCSS(Front.CSSStyle, "Nanollet.css", "text/css") 39 | 40 | win := DOM.NewWindow(w) 41 | win.InitApplication(new(App.NanolletApp)) 42 | win.InitApplication(new(App.NanofyApp)) 43 | win.InitApplication(new(App.AccountApp)) 44 | win.InitApplication(new(App.NanoAliasApp)) 45 | win.InitApplication(new(App.SettingsApp)) 46 | win.ViewApplication(new(App.AccountApp)) 47 | 48 | if Storage.PersistentStorage.SeedFY != *new(Wallet.SeedFY) { 49 | win.ViewPage(new(App.PagePassword)) 50 | } 51 | 52 | go Background.UpdateNodeCount(win) 53 | 54 | w.Show() 55 | w.Run() 56 | 57 | } 58 | -------------------------------------------------------------------------------- /GUI/window_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package GUI 4 | 5 | import ( 6 | "github.com/brokenbydefault/Nanollet/GUI/App" 7 | "github.com/brokenbydefault/Nanollet/Storage" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "github.com/brokenbydefault/Nanollet/GUI/App/Background" 10 | "github.com/brokenbydefault/Nanollet/GUI/App/DOM" 11 | "honnef.co/go/js/dom" 12 | ) 13 | 14 | func Start() { 15 | win := DOM.NewWindow(dom.GetWindow()) 16 | win.InitApplication(new(App.NanolletApp)) 17 | win.InitApplication(new(App.NanofyApp)) 18 | win.InitApplication(new(App.AccountApp)) 19 | win.InitApplication(new(App.NanoAliasApp)) 20 | win.InitApplication(new(App.SettingsApp)) 21 | win.ViewApplication(new(App.AccountApp)) 22 | 23 | if Storage.PersistentStorage.SeedFY != *new(Wallet.SeedFY) { 24 | win.ViewPage(new(App.PagePassword)) 25 | } 26 | 27 | go Background.UpdateNodeCount(win) 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Inkeliz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NanoAlias/easy.go: -------------------------------------------------------------------------------- 1 | package NanoAlias 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "github.com/brokenbydefault/Nanollet/GUI/App/Background" 6 | "github.com/brokenbydefault/Nanollet/Node" 7 | "github.com/brokenbydefault/Nanollet/Storage" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "time" 10 | ) 11 | 12 | func IsAvailable(alias string) bool { 13 | if _, err := Address(alias).GetPublicKey(); err == ErrNotFound { 14 | return true 15 | } 16 | 17 | return false 18 | } 19 | 20 | func Register(sk *Wallet.SecretKey, previous Block.Transaction, alias string) error { 21 | aliasPK, aliasSK, err := Address(alias).GetAliasKey() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | send, err := CreateSendBlock(aliasPK, sk, previous) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if err := Background.PublishBlockToQueue(send, Block.Send, Amount); err != nil { 32 | return err 33 | } 34 | 35 | txs, err := CreateAliasBlocks(&aliasSK, send) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | for _, tx := range txs { 41 | tx.Work() 42 | 43 | Storage.TransactionStorage.Add(tx) 44 | if err := Node.PostBlock(Background.Connection, tx); err != nil { 45 | return err 46 | } 47 | 48 | hash := tx.Hash() 49 | Node.RequestVotes(Background.Connection, tx) 50 | 51 | winner, ok := Storage.TransactionStorage.WaitConfirmation(&Storage.Configuration.Account.Quorum, 30 * time.Second, &hash) 52 | if !ok || *winner != hash { 53 | return ErrUnconfirmedRegister 54 | } 55 | 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func CreateSendBlock(aliasPK Wallet.PublicKey, sk *Wallet.SecretKey, previous Block.Transaction) (tx Block.Transaction, err error) { 62 | if previous.GetType() != Block.State { 63 | return nil, ErrUnsupportedBlock 64 | } 65 | 66 | return Block.CreateUniversalSendBlock(sk, previous.SwitchToUniversalBlock(nil, nil).Representative, Amount, previous.GetBalance(), previous.Hash(), aliasPK) 67 | } 68 | 69 | func CreateAliasBlocks(aliasSK *Wallet.SecretKey, sendTx Block.Transaction) (txs []Block.Transaction, err error) { 70 | if sendTx.GetType() != Block.State { 71 | return nil, ErrInvalidAliasBlock 72 | } 73 | 74 | txs = make([]Block.Transaction, 2) 75 | txs[0], err = Block.CreateUniversalOpenBlock(aliasSK, Representative, Amount, sendTx.Hash()) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | txs[1], err = Block.CreateUniversalSendBlock(aliasSK, Representative, txs[0].GetBalance(), txs[0].GetBalance(), txs[0].Hash(), sendTx.GetAccount()) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return txs, nil 86 | } 87 | 88 | func IsValidOpenBlock(txOpen Block.Transaction) bool { 89 | if txOpen.GetType() != Block.State { 90 | return false 91 | } 92 | 93 | if txOpen.GetBalance().Compare(Amount) != 0 { 94 | return false 95 | } 96 | 97 | if txOpen.SwitchToUniversalBlock(nil, nil).Representative != Representative { 98 | return false 99 | } 100 | 101 | return true 102 | } 103 | -------------------------------------------------------------------------------- /Nanofy/easy.go: -------------------------------------------------------------------------------- 1 | package Nanofy 2 | 3 | import ( 4 | "io" 5 | "github.com/brokenbydefault/Nanollet/Block" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | ) 8 | 9 | func VerifyFromHistory(file io.Reader, pk Wallet.PublicKey, txs []Block.Transaction) bool { 10 | if len(txs) < 3 { 11 | return false 12 | } 13 | 14 | fileHash, err := CreateHash(file) 15 | if err != nil { 16 | return false 17 | } 18 | 19 | for i, tx := range txs { 20 | dest, _ := tx.GetTarget() 21 | 22 | switch dest { 23 | case CreatePublicKey(0): 24 | if NewLegacyVerifierHash(fileHash, &pk, txs[i+1], tx).IsValid() { 25 | return true 26 | } 27 | case CreatePublicKey(1): 28 | if NewStateVerifierHash(fileHash, &pk, txs[i+2], txs[i+1], tx).IsValid() { 29 | return true 30 | } 31 | } 32 | } 33 | 34 | return false 35 | } 36 | -------------------------------------------------------------------------------- /Nanofy/legacy_test.go: -------------------------------------------------------------------------------- 1 | package Nanofy 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "github.com/brokenbydefault/Nanollet/Node" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "github.com/brokenbydefault/Nanollet/Block" 9 | "time" 10 | "github.com/brokenbydefault/Nanollet/Storage" 11 | "github.com/brokenbydefault/Nanollet/Node/Packets" 12 | ) 13 | 14 | func TestVersion0_VerifyBlock(t *testing.T) { 15 | 16 | con := Node.NewServer(Packets.Header{ 17 | MagicNumber: 82, 18 | NetworkType: Packets.Live, 19 | VersionMax: 13, 20 | VersionUsing: 13, 21 | VersionMin: 13, 22 | MessageType: Packets.Invalid, 23 | ExtensionType: 0, 24 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 25 | 26 | Node.NewHandler(con).Start() 27 | 28 | time.Sleep(2 * time.Second) 29 | 30 | flaghash := Block.NewBlockHash(Util.SecureHexMustDecode("AE95D7936A23D0671DD4E7E0736612F5304A18AD80B2827B2D69A3482A38F1EA")) 31 | flagblock, err := Node.GetBlock(con, &flaghash) 32 | if err != nil { 33 | t.Error(err) 34 | return 35 | } 36 | 37 | sighash := Block.NewBlockHash(Util.SecureHexMustDecode("7F0AEA1B6F2E9FD60B120DF01B0FBF4CC1B4B539A1D6B69DC50EAE81FE1A72E7")) 38 | sigblock, err := Node.GetBlock(con, &sighash) 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | 44 | pk := Wallet.Address("xrb_3w73pgb33ht1ws7hwaek5ywyjdteoj4qmcrzayiogpbabbo3i49dkerosn1z").MustGetPublicKey() 45 | nanofier, err := NewLegacyVerifier(nil, &pk, sigblock, flagblock) 46 | if err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | 51 | if !nanofier.IsCorrectlyFormatted() { 52 | t.Error("one valid block was report as invalid") 53 | } 54 | 55 | } 56 | 57 | func TestVersion0_VerifyBlock_Invalid(t *testing.T) { 58 | con := Node.NewServer(Packets.Header{ 59 | MagicNumber: 82, 60 | NetworkType: Packets.Live, 61 | VersionMax: 13, 62 | VersionUsing: 13, 63 | VersionMin: 13, 64 | MessageType: Packets.Invalid, 65 | ExtensionType: 0, 66 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 67 | 68 | Node.NewHandler(con).Start() 69 | time.Sleep(1 * time.Second) 70 | 71 | flaghash := Block.NewBlockHash(Util.SecureHexMustDecode("AE95D7936A23D0671DD4E7E0736612F5304A18AD80B2827B2D69A3482A38F1EA")) 72 | flagblock, err := Node.GetBlock(con, &flaghash) 73 | if err != nil { 74 | t.Error(err) 75 | return 76 | } 77 | 78 | sighash := Block.NewBlockHash(Util.SecureHexMustDecode("72DC2B79C307600FE8187521DB2C0AAA2929D6E10C1E3E3B058ACB6B617EB019")) 79 | sigblock, err := Node.GetBlock(con, &sighash) 80 | if err != nil { 81 | t.Error(err) 82 | return 83 | } 84 | 85 | pk := Wallet.Address("xrb_3w73pgb33ht1ws7hwaek5ywyjdteoj4qmcrzayiogpbabbo3i49dkerosn1z").MustGetPublicKey() 86 | nanofier, err := NewLegacyVerifier(nil, &pk, sigblock, flagblock) 87 | if err != nil { 88 | t.Error(err) 89 | return 90 | } 91 | 92 | if nanofier.IsCorrectlyFormatted() { 93 | t.Error("one valid block was report as invalid") 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Nanofy/nanofier.go: -------------------------------------------------------------------------------- 1 | package Nanofy 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Wallet" 5 | "github.com/brokenbydefault/Nanollet/Numbers" 6 | "github.com/brokenbydefault/Nanollet/Block" 7 | "io" 8 | "golang.org/x/crypto/blake2b" 9 | ) 10 | 11 | var addressBase = Wallet.PublicKey{0x51, 0x14, 0xab, 0x7c, 0x6a, 0xd0, 0xd6, 0xc3, 0x14, 0xc5, 0xc2, 0x8e, 0x36, 0xb0, 0x8a, 0x65, 0x0a, 0xd4, 0x2b, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 12 | 13 | type Nanofier interface { 14 | CreateBlocks() (txs []Block.Transaction, err error) 15 | CreateSignatureBlock() (tx Block.Transaction, err error) 16 | CreateFlagBlock() (tx Block.Transaction, err error) 17 | 18 | IsValid() (ok bool) 19 | IsCorrectlyFormatted() (ok bool) 20 | 21 | Flag() Wallet.PublicKey 22 | Amount() *Numbers.RawAmount 23 | } 24 | 25 | func CreatePublicKey(version uint64) Wallet.PublicKey { 26 | pk := Wallet.NewPublicKey(addressBase[:]) 27 | pk[24] = byte(version) 28 | pk[25] = byte(version >> 8) 29 | pk[26] = byte(version >> 16) 30 | pk[27] = byte(version >> 24) 31 | pk[28] = byte(version >> 32) 32 | pk[29] = byte(version >> 40) 33 | pk[30] = byte(version >> 48) 34 | pk[31] = byte(version >> 56) 35 | 36 | return pk 37 | } 38 | 39 | func CreateAddress(version uint64) Wallet.Address { 40 | return CreatePublicKey(version).CreateAddress() 41 | } 42 | 43 | func CreateHash(file io.Reader) (hash Wallet.PublicKey, err error) { 44 | blake, _ := blake2b.New(32, nil) 45 | 46 | _, err = io.Copy(blake, file) 47 | if err != nil { 48 | return hash, err 49 | } 50 | 51 | copy(hash[:], blake.Sum(nil)) 52 | return hash, nil 53 | } 54 | 55 | func isSignatureValid(pk *Wallet.PublicKey, tx Block.Transaction) bool { 56 | if hash, sig := tx.Hash(), tx.GetSignature(); pk.IsValidSignature(hash[:], &sig) { 57 | return true 58 | } 59 | 60 | return false 61 | } 62 | -------------------------------------------------------------------------------- /Nanofy/state_test.go: -------------------------------------------------------------------------------- 1 | package Nanofy 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Node" 6 | "time" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "github.com/brokenbydefault/Nanollet/Block" 9 | "github.com/brokenbydefault/Nanollet/Wallet" 10 | "github.com/brokenbydefault/Nanollet/Storage" 11 | "github.com/brokenbydefault/Nanollet/Node/Packets" 12 | ) 13 | 14 | func TestVersion1_VerifyBlock(t *testing.T) { 15 | 16 | con := Node.NewServer(Packets.Header{ 17 | MagicNumber: 82, 18 | NetworkType: Packets.Live, 19 | VersionMax: 13, 20 | VersionUsing: 13, 21 | VersionMin: 13, 22 | MessageType: Packets.Invalid, 23 | ExtensionType: 0, 24 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 25 | 26 | Node.NewHandler(con).Start() 27 | time.Sleep(1 * time.Second) 28 | 29 | flaghash := Block.NewBlockHash(Util.SecureHexMustDecode("A7CEB0E504E31D74CD87DB0990B9C44D8211987C464BE758C6007C6DA2D188FB")) 30 | flagblock, err := Node.GetBlock(con, &flaghash) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | sighash := Block.NewBlockHash(Util.SecureHexMustDecode("02C03EEFBFAC781971125D04EB0F28D510D7B303E86D1CAB22E0C86271862E9B")) 36 | sigblock, err := Node.GetBlock(con, &sighash) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | prevhash := Block.NewBlockHash(Util.SecureHexMustDecode("EF7A37D750DBB692F957C0EA2965B1F48EB2BC2504AE7AE07957904D83D5B267")) 42 | prevblock, err := Node.GetBlock(con, &prevhash) 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | 48 | 49 | pk := Wallet.Address("xrb_31hbrc4zary87ardrg74pd6xy157z71t4b9edmrqbj5tqgxnriba5e7cf3o6").MustGetPublicKey() 50 | nanofier, err := NewStateVerifier(nil, &pk, prevblock, sigblock, flagblock) 51 | if err != nil { 52 | t.Error(err) 53 | return 54 | } 55 | 56 | if !nanofier.IsCorrectlyFormatted() { 57 | t.Error("one valid block was report as invalid") 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Nanollet.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | true/pm 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Node/Packets/bulkpull.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "io" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "github.com/Inkeliz/blakEd25519" 8 | "golang.org/x/crypto/blake2b" 9 | "bufio" 10 | "encoding/binary" 11 | ) 12 | 13 | type BulkPullPackageResponse struct { 14 | Transactions []Block.Transaction 15 | } 16 | 17 | type BulkPullPackageRequest struct { 18 | PublicKey Wallet.PublicKey 19 | End Block.BlockHash 20 | } 21 | 22 | func NewBulkPullResponse(txs []Block.Transaction) (packet *BulkPullPackageResponse) { 23 | return &BulkPullPackageResponse{ 24 | Transactions: txs, 25 | } 26 | } 27 | 28 | func NewBulkPullPackageRequest(pk Wallet.PublicKey, end Block.BlockHash) (packet *BulkPullPackageRequest) { 29 | return &BulkPullPackageRequest{ 30 | PublicKey: pk, 31 | End: end, 32 | } 33 | } 34 | 35 | func (p *BulkPullPackageRequest) Encode(dst io.Writer) (err error) { 36 | if p == nil { 37 | return 38 | } 39 | 40 | if _, err = dst.Write(p.PublicKey[:]); err != nil { 41 | return err 42 | } 43 | 44 | if _, err = dst.Write(p.End[:]); err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (p *BulkPullPackageRequest) Decode(_ *Header, src io.Reader) (err error) { 52 | if p == nil { 53 | return 54 | } 55 | 56 | if n, err := src.Read(p.PublicKey[:]); n != blakEd25519.PublicKeySize || err != nil { 57 | return ErrInvalidMessageSize 58 | } 59 | 60 | if n, err := src.Read(p.End[:]); n != blake2b.Size256 || err != nil { 61 | return ErrInvalidMessageSize 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (p *BulkPullPackageResponse) Encode(dst io.Writer) (err error) { 68 | if p == nil { 69 | return 70 | } 71 | 72 | for _, tx := range p.Transactions { 73 | if _, err = dst.Write(tx.Encode()); err != nil { 74 | return err 75 | } 76 | } 77 | 78 | if _, err = dst.Write([]byte{byte(Block.NotABlock)}); err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (p *BulkPullPackageResponse) Decode(_ *Header, src io.Reader) (err error) { 86 | if p == nil { 87 | return 88 | } 89 | 90 | buf := bufio.NewReader(src) 91 | for { 92 | 93 | blockType := make([]byte, 1) 94 | if err := binary.Read(buf, binary.BigEndian, blockType[:]); err != nil { 95 | return nil 96 | } 97 | 98 | tx, size, err := Block.NewTransaction(Block.BlockType(blockType[0])) 99 | if err != nil { 100 | if err == Block.ErrInvalidBlock || err == Block.ErrEndBlock { 101 | return nil 102 | } 103 | return err 104 | } 105 | 106 | btx := make([]byte, size) 107 | if err := binary.Read(buf, binary.BigEndian, &btx); err != nil { 108 | return nil 109 | } 110 | 111 | if err = tx.Decode(btx); err != nil { 112 | return err 113 | } 114 | 115 | p.Transactions = append(p.Transactions, tx) 116 | } 117 | 118 | return nil 119 | } 120 | 121 | func (p *BulkPullPackageResponse) ModifyHeader(h *Header) { 122 | h.SetRemoveHeader(true) 123 | } 124 | 125 | func (p *BulkPullPackageRequest) ModifyHeader(h *Header) { 126 | h.MessageType = BulkPull 127 | } 128 | -------------------------------------------------------------------------------- /Node/Packets/bulkpullaccount.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "io" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "github.com/brokenbydefault/Nanollet/Numbers" 8 | "bufio" 9 | "github.com/brokenbydefault/Nanollet/Util" 10 | "encoding/binary" 11 | ) 12 | 13 | type BulkPullAccountPackageRequest struct { 14 | PublicKey Wallet.PublicKey 15 | MinimumPendingAmount *Numbers.RawAmount 16 | Flags uint8 17 | } 18 | 19 | type BulkPullAccountPackageResponse struct { 20 | Frontier Block.BlockHash 21 | Balance *Numbers.RawAmount 22 | Pending []Block.BlockHash 23 | } 24 | 25 | func NewBulkPullAccountPackageRequest(pk Wallet.PublicKey, minAmount *Numbers.RawAmount) (packet *BulkPullAccountPackageRequest) { 26 | return &BulkPullAccountPackageRequest{ 27 | PublicKey: pk, 28 | MinimumPendingAmount: minAmount, 29 | Flags: 0, 30 | } 31 | } 32 | 33 | func NewBulkPullAccountPackageResponse(frontier Block.BlockHash, balance *Numbers.RawAmount, pending []Block.BlockHash) (packet *BulkPullAccountPackageResponse) { 34 | return &BulkPullAccountPackageResponse{ 35 | Frontier: frontier, 36 | Balance: balance, 37 | Pending: pending, 38 | } 39 | } 40 | 41 | func (p *BulkPullAccountPackageRequest) Encode(dst io.Writer) (err error) { 42 | if p == nil { 43 | return 44 | } 45 | 46 | if _, err = dst.Write(p.PublicKey[:]); err != nil { 47 | return err 48 | } 49 | 50 | if _, err = dst.Write(p.MinimumPendingAmount.ToBytes()[:]); err != nil { 51 | return err 52 | } 53 | 54 | if _, err = dst.Write([]byte{byte(p.Flags)}); err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (p *BulkPullAccountPackageRequest) Decode(_ *Header, src io.Reader) (err error) { 62 | if p == nil { 63 | return 64 | } 65 | 66 | if _, err := src.Read(p.PublicKey[:]); err != nil { 67 | return ErrInvalidMessageSize 68 | } 69 | 70 | p.MinimumPendingAmount = new(Numbers.RawAmount) 71 | if err := p.MinimumPendingAmount.Read(src); err != nil { 72 | return ErrInvalidMessageSize 73 | } 74 | 75 | i := [1]byte{} 76 | if n, err := src.Read(i[:]); n != 1 || err != nil { 77 | return ErrInvalidMessageSize 78 | } 79 | 80 | p.Flags = i[0] 81 | 82 | return nil 83 | } 84 | 85 | //@TODO (inkeliz) implement Encode 86 | func (p *BulkPullAccountPackageResponse) Encode(dst io.Writer) (err error) { 87 | if p == nil { 88 | return 89 | } 90 | 91 | /** 92 | for _, tx := range p.Pending { 93 | if _, err = dst.Write(tx.Hash[:]); err != nil { 94 | return err 95 | } 96 | if err = tx.Amount.Write(dst); err != nil { 97 | return err 98 | } 99 | } 100 | 101 | if _, err = dst.Write([]byte{byte(Block.NotABlock)}); err != nil { 102 | return err 103 | } 104 | **/ 105 | 106 | return nil 107 | } 108 | 109 | func (p *BulkPullAccountPackageResponse) Decode(_ *Header, src io.Reader) (err error) { 110 | if p == nil { 111 | return 112 | } 113 | 114 | buf := bufio.NewReaderSize(src, 48) 115 | 116 | if _, err := buf.Read(p.Frontier[:]); err != nil { 117 | return err 118 | } 119 | 120 | p.Balance = new(Numbers.RawAmount) 121 | if err := p.Balance.Read(buf); err != nil { 122 | return err 123 | } 124 | 125 | for { 126 | hash := Block.BlockHash{} 127 | if err := binary.Read(buf, binary.BigEndian, &hash); err != nil { 128 | return nil 129 | } 130 | 131 | amount := make([]byte, 16) 132 | if err := binary.Read(buf, binary.BigEndian, &amount); err != nil { 133 | return nil 134 | } 135 | 136 | if Util.IsEmpty(hash[:]) { 137 | return nil 138 | } 139 | 140 | p.Pending = append(p.Pending, hash) 141 | } 142 | 143 | return nil 144 | } 145 | 146 | func (p *BulkPullAccountPackageResponse) ModifyHeader(h *Header) { 147 | h.SetRemoveHeader(true) 148 | } 149 | 150 | func (p *BulkPullAccountPackageRequest) ModifyHeader(h *Header) { 151 | h.MessageType = BulkPullAccount 152 | } 153 | -------------------------------------------------------------------------------- /Node/Packets/bulkpullblocks.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | // Deprecated 4 | // https://github.com/nanocurrency/raiblocks/commit/5b28ba412f01d4f69628a7f3b0cee48233c542f4 -------------------------------------------------------------------------------- /Node/Packets/bulkpush.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | -------------------------------------------------------------------------------- /Node/Packets/confirmack.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "time" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "errors" 9 | "golang.org/x/crypto/blake2b" 10 | "encoding/binary" 11 | ) 12 | 13 | type ConfirmACKPackage struct { 14 | PublicKey Wallet.PublicKey 15 | Signature Wallet.Signature 16 | Sequence [8]byte 17 | Hashes []Block.BlockHash 18 | } 19 | 20 | const ( 21 | ConfirmACKPackageSizeMin = 32 + 64 + 8 + 32 22 | ConfirmACKPackageSizeMax = MessageSize 23 | ) 24 | 25 | var ( 26 | ErrInvalidSignature = errors.New("invalid signature") 27 | ) 28 | 29 | var votePrefix = []byte("vote ") 30 | 31 | func NewConfirmACKPackage(sk *Wallet.SecretKey, txs ...Block.Transaction) (packet *ConfirmACKPackage) { 32 | packet = &ConfirmACKPackage{ 33 | PublicKey: sk.PublicKey(), 34 | Sequence: newSequence(), 35 | } 36 | 37 | for _, tx := range txs { 38 | packet.Hashes = append(packet.Hashes, tx.Hash()) 39 | } 40 | 41 | sig, err := sk.Sign(Util.CreateHash(32, votePrefix, concatHashes(packet.Hashes), packet.Sequence[:])) 42 | if err != nil { 43 | return 44 | } 45 | 46 | packet.Signature = sig 47 | 48 | return packet 49 | } 50 | 51 | func (p *ConfirmACKPackage) Encode(dst []byte) (n int, err error) { 52 | if p == nil { 53 | return 0, nil 54 | } 55 | 56 | if len(dst) < ConfirmACKPackageSizeMax { 57 | return 0, ErrDestinationLenghtNotEnough 58 | } 59 | 60 | if err != nil { 61 | return 0, err 62 | } 63 | 64 | n += copy(dst[n:], p.PublicKey[:]) 65 | n += copy(dst[n:], p.Signature[:]) 66 | n += copy(dst[n:], p.Sequence[:]) 67 | n += copy(dst[n:], concatHashes(p.Hashes)) 68 | 69 | return n, nil 70 | } 71 | 72 | func (p *ConfirmACKPackage) Decode(rHeader *Header, src []byte) (err error) { 73 | if p == nil { 74 | return 75 | } 76 | 77 | if rHeader == nil { 78 | return ErrInvalidHeaderParameters 79 | } 80 | 81 | if l := len(src); l < ConfirmACKPackageSizeMin || l > ConfirmACKPackageSizeMax { 82 | return ErrInvalidMessageSize 83 | } 84 | 85 | bi := 0 86 | bi += copy(p.PublicKey[:], src[bi:32]) 87 | bi += copy(p.Signature[:], src[bi:bi+64]) 88 | bi += copy(p.Sequence[:], src[bi:bi+8]) 89 | 90 | if blktype := rHeader.ExtensionType.GetBlockType(); blktype == Block.NotABlock { 91 | l := len(src) 92 | if l-bi <= 0 || (l-bi)%blake2b.Size256 != 0 { 93 | return ErrInvalidMessageSize 94 | } 95 | 96 | for i := bi; i < l; i += blake2b.Size256 { 97 | p.Hashes = append(p.Hashes, Block.NewBlockHash(src[i:])) 98 | } 99 | 100 | if ok := p.PublicKey.IsValidSignature(Util.CreateHash(32, votePrefix, src[bi:], p.Sequence[:]), &p.Signature); !ok { 101 | return ErrInvalidSignature 102 | } 103 | } else { 104 | tx, _, err := Block.NewTransaction(rHeader.ExtensionType.GetBlockType()) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | if err = tx.Decode(src[bi:]); err != nil { 110 | return err 111 | } 112 | 113 | p.Hashes = []Block.BlockHash{tx.Hash()} 114 | if ok := p.PublicKey.IsValidSignature(Util.CreateHash(32, p.Hashes[0][:], p.Sequence[:]), &p.Signature); !ok { 115 | return ErrInvalidSignature 116 | } 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func (p *ConfirmACKPackage) ModifyHeader(h *Header) { 123 | h.MessageType = ConfirmACK 124 | h.ExtensionType.Add(ExtensionType(Block.NotABlock) << 8) 125 | } 126 | 127 | func concatHashes(hashes []Block.BlockHash) (b []byte) { 128 | for _, h := range hashes { 129 | b = append(b, h[:]...) 130 | } 131 | 132 | return b 133 | } 134 | 135 | func newSequence() (b [8]byte) { 136 | binary.PutVarint(b[:], time.Now().Unix()) 137 | return b 138 | } 139 | -------------------------------------------------------------------------------- /Node/Packets/confirmreq.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | ) 6 | 7 | //@TODO Support votes by blocks 8 | 9 | type ConfirmReqPackage struct { 10 | Transaction Block.Transaction 11 | } 12 | 13 | const ( 14 | ConfirmReqPackageSizeMin = Block.ReceiveSize 15 | ConfirmReqPackageSizeMax = MessageSize 16 | ) 17 | 18 | func NewConfirmReqPackage(tx Block.Transaction) (packet *ConfirmReqPackage) { 19 | return &ConfirmReqPackage{ 20 | Transaction: tx, 21 | } 22 | } 23 | 24 | func (p *ConfirmReqPackage) Encode(dst []byte) (n int, err error) { 25 | if p == nil { 26 | return 27 | } 28 | 29 | if len(dst) < ConfirmReqPackageSizeMax { 30 | return 0, ErrDestinationLenghtNotEnough 31 | } 32 | 33 | n += copy(dst, p.Transaction.Encode()[1:]) 34 | 35 | return n, err 36 | } 37 | 38 | func (p *ConfirmReqPackage) Decode(rHeader *Header, src []byte) (err error) { 39 | if p == nil { 40 | return 41 | } 42 | 43 | if rHeader == nil { 44 | return ErrInvalidHeaderParameters 45 | } 46 | 47 | p.Transaction, _, err = Block.NewTransaction(rHeader.ExtensionType.GetBlockType()) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if err = p.Transaction.Decode(src); err != nil { 53 | return err 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (p *ConfirmReqPackage) ModifyHeader(h *Header) { 60 | h.MessageType = ConfirmReq 61 | h.ExtensionType.Add(ExtensionType(uint8(p.Transaction.GetType())) << 8) 62 | } 63 | -------------------------------------------------------------------------------- /Node/Packets/confirmreq_test.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Util" 6 | "github.com/brokenbydefault/Nanollet/Block" 7 | "github.com/brokenbydefault/Nanollet/Wallet" 8 | "github.com/brokenbydefault/Nanollet/Numbers" 9 | ) 10 | 11 | func TestConfirmReqPackage_Decode(t *testing.T) { 12 | expected := Util.SecureHexMustDecode("79BEC4E064ED076700A0DDA218FFA7322E4EAE83A5FC503EC968FE929FFAFAAB") 13 | udpMessage := Util.SecureHexMustDecode("52430d0d070400067cb593df1e997e986bf2b9cf15f65ca48fa72b74ced2820b58f0d210f6ae4ae5dfe37b58b3ca19ce168d6a2d878ac539a0ea51e037687d2c6dfed18c22a7aad87cb593df1e997e986bf2b9cf15f65ca48fa72b74ced2820b58f0d210f6ae4ae5000000087a84c94598b4e5525f000000843481c75fa40484a4b203b290990c0bf916e5df918bc9f2952b1dd99a5928c3ed46be47a5dde7e22f1fd9e15eef5e5793dfc4015b65256a023d6fa2c08e60416fa72111428e17b41591a7413f9fbadfafcd86fafae3e9189fed7709f5facb0be541dc0e9b3041c6") 14 | 15 | header := new(Header) 16 | err := header.Decode(udpMessage) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | pack := new(ConfirmReqPackage) 22 | if err = pack.Decode(header, udpMessage[HeaderSize:]); err != nil { 23 | t.Error(err) 24 | } 25 | 26 | if pack.Transaction.Hash() != Block.NewBlockHash(expected) { 27 | t.Error("decode error, invalid block") 28 | } 29 | } 30 | 31 | func TestConfirmReqPackage_Encode(t *testing.T) { 32 | expected := Util.SecureHexMustDecode("79BEC4E064ED076700A0DDA218FFA7322E4EAE83A5FC503EC968FE929FFAFAAB") 33 | tx := &Block.UniversalBlock{ 34 | Account: Wallet.Address("nano_1z7okhhjx8dym3oz7ggh4qu7sb6hnwoqbmpkia7ojw8k45ucwkq73irbtndz").MustGetPublicKey(), 35 | Representative: Wallet.Address("nano_1z7okhhjx8dym3oz7ggh4qu7sb6hnwoqbmpkia7ojw8k45ucwkq73irbtndz").MustGetPublicKey(), 36 | Previous: Block.NewBlockHash(Util.SecureHexMustDecode("DFE37B58B3CA19CE168D6A2D878AC539A0EA51E037687D2C6DFED18C22A7AAD8")), 37 | Link: Block.NewBlockHash(Util.SecureHexMustDecode("843481C75FA40484A4B203B290990C0BF916E5DF918BC9F2952B1DD99A5928C3")), 38 | Balance: Numbers.NewRawFromBytes(Util.SecureHexMustDecode("000000087A84C94598B4E5525F000000")), 39 | DefaultBlock: Block.DefaultBlock{ 40 | Signature: Wallet.NewSignature(Util.SecureHexMustDecode("ED46BE47A5DDE7E22F1FD9E15EEF5E5793DFC4015B65256A023D6FA2C08E60416FA72111428E17B41591A7413F9FBADFAFCD86FAFAE3E9189FED7709F5FACB0B")), 41 | PoW: Block.NewWork(Util.SecureHexMustDecode("E541DC0E9B3041C6")), 42 | }, 43 | } 44 | 45 | pack := NewConfirmReqPackage(tx) 46 | encoded := EncodePacketUDP(*NewHeader(), pack) 47 | 48 | header := new(Header) 49 | if err := header.Decode(encoded); err != nil { 50 | t.Error(err) 51 | } 52 | 53 | depack := new(ConfirmReqPackage) 54 | if err := depack.Decode(header, encoded[HeaderSize:]); err != nil { 55 | t.Error(err) 56 | } 57 | 58 | if depack.Transaction.Hash() != Block.NewBlockHash(expected) { 59 | t.Error("encode error, invalid block") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Node/Packets/frontierreq.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "io" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "github.com/Inkeliz/blakEd25519" 8 | "encoding/binary" 9 | "bufio" 10 | ) 11 | 12 | type FrontierReqPackageRequest struct { 13 | PublicKey Wallet.PublicKey 14 | Age uint32 15 | Count uint32 16 | } 17 | 18 | func NewFrontierReqPackageRequest(pk Wallet.PublicKey, age, end uint32) (packet *FrontierReqPackageRequest) { 19 | return &FrontierReqPackageRequest{ 20 | PublicKey: pk, 21 | Age: age, 22 | Count: end, 23 | } 24 | } 25 | 26 | func (p *FrontierReqPackageRequest) Encode(dst io.Writer) (err error) { 27 | if p == nil { 28 | return 29 | } 30 | 31 | if _, err = dst.Write(p.PublicKey[:]); err != nil { 32 | return err 33 | } 34 | 35 | if err = binary.Write(dst, binary.BigEndian, p.Age); err != nil { 36 | return err 37 | } 38 | 39 | if err = binary.Write(dst, binary.BigEndian, p.Count); err != nil { 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func (p *FrontierReqPackageRequest) Decode(_ *Header, src io.Reader) (err error) { 47 | if p == nil { 48 | return 49 | } 50 | 51 | src = bufio.NewReader(src) 52 | 53 | if n, err := src.Read(p.PublicKey[:]); n != blakEd25519.PublicKeySize || err != nil { 54 | return ErrInvalidMessageSize 55 | } 56 | 57 | b := make([]byte, 4) 58 | if n, err := src.Read(b); n != 8 || err != nil { 59 | return ErrInvalidMessageSize 60 | } 61 | 62 | p.Age = binary.BigEndian.Uint32(b) 63 | 64 | if n, err := src.Read(b); n != 8 || err != nil { 65 | return ErrInvalidMessageSize 66 | } 67 | 68 | p.Count = binary.BigEndian.Uint32(b) 69 | 70 | return nil 71 | } 72 | 73 | type Frontier struct { 74 | Account Wallet.PublicKey 75 | Hash Block.BlockHash 76 | } 77 | 78 | type FrontierReqPackageResponse struct { 79 | Frontiers []Frontier 80 | transactions []Block.Transaction 81 | } 82 | 83 | func NewFrontierReqPackageResponse(txs []Block.Transaction) (packet *FrontierReqPackageResponse) { 84 | return &FrontierReqPackageResponse{ 85 | transactions: txs, 86 | } 87 | } 88 | 89 | func (p *FrontierReqPackageResponse) Encode(dst io.Writer) (err error) { 90 | if p == nil { 91 | return 92 | } 93 | 94 | for _, tx := range p.transactions { 95 | hash := tx.Hash() 96 | if _, err = dst.Write(hash[:]); err != nil { 97 | return err 98 | } 99 | } 100 | 101 | if _, err = dst.Write([]byte{byte(Block.NotABlock)}); err != nil { 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func (p *FrontierReqPackageResponse) Decode(_ *Header, src io.Reader) (err error) { 109 | 110 | var fronts []Frontier 111 | for { 112 | frontier := Frontier{} 113 | 114 | blockType := make([]byte, 1) 115 | if _, err := src.Read(blockType); err != nil { 116 | return err 117 | } 118 | 119 | if blockType[0] == byte(Block.Invalid) { 120 | break 121 | } 122 | 123 | if _, err = src.Read(frontier.Account[:]); err != nil { 124 | return err 125 | } 126 | 127 | if _, err = src.Read(frontier.Hash[:]); err != nil { 128 | return err 129 | } 130 | 131 | fronts = append(fronts, frontier) 132 | } 133 | 134 | p.Frontiers = fronts 135 | 136 | return nil 137 | } 138 | 139 | func (p *FrontierReqPackageResponse) ModifyHeader(h *Header) { 140 | h.SetRemoveHeader(true) 141 | } 142 | 143 | func (p *FrontierReqPackageRequest) ModifyHeader(h *Header) { 144 | h.MessageType = FrontierReq 145 | } 146 | -------------------------------------------------------------------------------- /Node/Packets/handshake.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Wallet" 5 | "github.com/Inkeliz/blakEd25519" 6 | "github.com/brokenbydefault/Nanollet/Util" 7 | ) 8 | 9 | type HandshakePackage struct { 10 | Challenge [32]byte 11 | PublicKey Wallet.PublicKey 12 | Signature Wallet.Signature 13 | } 14 | 15 | const ( 16 | NodeHandshakePackageSizeMin = 32 17 | NodeHandshakePackageSizeMax = NodeHandshakePackageSizeMin + blakEd25519.PublicKeySize + blakEd25519.SignatureSize 18 | ) 19 | 20 | const ( 21 | _ ExtensionType = iota 22 | Challenge 23 | Response 24 | ) 25 | 26 | func NewHandshakePackage(lChallenge []byte, rChallenge []byte) (packet *HandshakePackage) { 27 | packet = new(HandshakePackage) 28 | 29 | if lChallenge != nil { 30 | copy(packet.Challenge[:], lChallenge) 31 | } 32 | 33 | if rChallenge != nil { 34 | pk, sk, _ := Wallet.GenerateRandomKeyPair() 35 | 36 | packet.PublicKey = pk 37 | sig, err := sk.Sign(rChallenge) 38 | if err != nil { 39 | return packet 40 | } 41 | packet.Signature = sig 42 | } 43 | 44 | return packet 45 | } 46 | 47 | func (p *HandshakePackage) Encode(dst []byte) (n int, err error) { 48 | if p == nil { 49 | return 50 | } 51 | 52 | if len(dst) < NodeHandshakePackageSizeMax { 53 | return 0, ErrDestinationLenghtNotEnough 54 | } 55 | 56 | if !Util.IsEmpty(p.Challenge[:]) { 57 | n += copy(dst[n:], p.Challenge[:]) 58 | } 59 | 60 | if !Util.IsEmpty(p.Signature[:]) && !Util.IsEmpty(p.PublicKey[:]) { 61 | n += copy(dst[n:], p.PublicKey[:]) 62 | n += copy(dst[n:], p.Signature[:]) 63 | } 64 | 65 | return n, err 66 | } 67 | 68 | func (p *HandshakePackage) Decode(rHeader *Header, src []byte) (err error) { 69 | if p == nil { 70 | return 71 | } 72 | 73 | if rHeader == nil { 74 | return ErrInvalidHeaderParameters 75 | } 76 | 77 | if l := len(src); l > NodeHandshakePackageSizeMax || l < NodeHandshakePackageSizeMin { 78 | return ErrInvalidMessageSize 79 | } 80 | 81 | bi := 0 82 | if rHeader.ExtensionType.Is(Challenge) { 83 | bi += copy(p.Challenge[:], src[bi:bi+32]) 84 | } 85 | 86 | if rHeader.ExtensionType.Is(Response) { 87 | bi += copy(p.PublicKey[:], src[bi:bi+32]) 88 | bi += copy(p.Signature[:], src[bi:bi+64]) 89 | } 90 | 91 | return nil 92 | } 93 | 94 | func (p *HandshakePackage) ModifyHeader(h *Header) { 95 | h.MessageType = NodeHandshake 96 | 97 | if !Util.IsEmpty(p.Challenge[:]) { 98 | h.ExtensionType.Add(Challenge) 99 | } 100 | 101 | if !Util.IsEmpty(p.Signature[:]) && !Util.IsEmpty(p.PublicKey[:]) { 102 | h.ExtensionType.Add(Response) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Node/Packets/handshake_test.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Util" 6 | "bytes" 7 | "crypto/rand" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | ) 10 | 11 | func TestHandshakePackage_Decode(t *testing.T) { 12 | expected := NewHandshakePackage(Util.SecureHexMustDecode("7929DF5BEBB4A10C2BA5C05D3851A1D989C4071618599DA1249D9D2CFE420BFB"), nil) 13 | udpMessage := Util.SecureHexMustDecode("52430d0d070a01007929df5bebb4a10c2ba5c05d3851a1d989c4071618599da1249d9d2cfe420bfb") 14 | 15 | header := new(Header) 16 | err := header.Decode(udpMessage) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | pack := new(HandshakePackage) 22 | if err = pack.Decode(header, udpMessage[HeaderSize:]); err != nil { 23 | t.Error(err) 24 | } 25 | 26 | if !bytes.Equal(pack.Challenge[:], expected.Challenge[:]) { 27 | t.Errorf("error decode, invalid challenge: got %X expecting %X", pack.Challenge[:], expected.Challenge[:]) 28 | } 29 | 30 | } 31 | 32 | func TestHandshakePackage_Decode_3(t *testing.T) { 33 | expected := NewHandshakePackage(Util.SecureHexMustDecode("C47A5890B727C97323B0089E1F3D8CB559B4CB70107CA3CD0351C6B7B02A7E81"), nil) 34 | expected.PublicKey = Wallet.NewPublicKey(Util.SecureHexMustDecode("8DEC937B67722A7DE508905674DAF6F5F7E99B676B304F0A0FC11966A08213CC")) 35 | expected.Signature = Wallet.NewSignature(Util.SecureHexMustDecode("C211B16511A9B5005CD484C9447DFA001F6F7916958D8C9348A49D124EAD144D8188EF8C5C70B967FF865CAB826EA7CC53E85C6CEA075258D3BDEE7B32EC7709")) 36 | 37 | udpMessage := Util.SecureHexMustDecode("52430d0d070a0300c47a5890b727c97323b0089e1f3d8cb559b4cb70107ca3cd0351c6b7b02a7e818dec937b67722a7de508905674daf6f5f7e99b676b304f0a0fc11966a08213ccc211b16511a9b5005cd484c9447dfa001f6f7916958d8c9348a49d124ead144d8188ef8c5c70b967ff865cab826ea7cc53e85c6cea075258d3bdee7b32ec7709") 38 | 39 | header := new(Header) 40 | err := header.Decode(udpMessage) 41 | if err != nil { 42 | t.Error(err) 43 | } 44 | 45 | pack := new(HandshakePackage) 46 | if err = pack.Decode(header, udpMessage[HeaderSize:]); err != nil { 47 | t.Error(err) 48 | } 49 | 50 | if !bytes.Equal(pack.Challenge[:], expected.Challenge[:]) { 51 | t.Errorf("error decode, invalid challenge: got %X expecting %X", pack.Challenge[:], expected.Challenge[:]) 52 | } 53 | 54 | if pack.PublicKey != expected.PublicKey { 55 | t.Errorf("error decode, invalid public-key: got %X expecting %X", pack.PublicKey, expected.PublicKey) 56 | } 57 | 58 | if pack.Signature != expected.Signature { 59 | t.Errorf("error decode, invalid public-key: got %X expecting %X", pack.Signature, expected.Signature) 60 | } 61 | 62 | } 63 | 64 | func TestHandshakePackage_Decode_2(t *testing.T) { 65 | expected := NewHandshakePackage(nil, nil) 66 | expected.PublicKey = Wallet.NewPublicKey(Util.SecureHexMustDecode("64C49362A7B0101F0434EC6AB06C8A28C93DAF9974F32A88405E220894AE2164")) 67 | expected.Signature = Wallet.NewSignature(Util.SecureHexMustDecode("498C9B66C11E5CD9AE492A0DBADC633BE2A8F375E51E51D0A6727B47F17F4E7489545204798D03E6D257A2A94A3C71387EBAE1F48C59D817D3CEDE54298AF600")) 68 | 69 | udpMessage := Util.SecureHexMustDecode("52430d0d070a020064c49362a7b0101f0434ec6ab06c8a28c93daf9974f32a88405e220894ae2164498c9b66c11e5cd9ae492a0dbadc633be2a8f375e51e51d0a6727b47f17f4e7489545204798d03e6d257a2a94a3c71387ebae1f48c59d817d3cede54298af600") 70 | 71 | header := new(Header) 72 | err := header.Decode(udpMessage) 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | 77 | pack := new(HandshakePackage) 78 | if err = pack.Decode(header, udpMessage[HeaderSize:]); err != nil { 79 | t.Error(err) 80 | } 81 | 82 | if !bytes.Equal(pack.Challenge[:], expected.Challenge[:]) { 83 | t.Errorf("error decode, invalid challenge: got %X expecting %X", pack.Challenge[:], expected.Challenge[:]) 84 | } 85 | 86 | if pack.PublicKey != expected.PublicKey { 87 | t.Errorf("error decode, invalid public-key: got %X expecting %X", pack.PublicKey, expected.PublicKey) 88 | } 89 | 90 | if pack.Signature != expected.Signature { 91 | t.Errorf("error decode, invalid public-key: got %X expecting %X", pack.Signature, expected.Signature) 92 | } 93 | } 94 | 95 | func TestHandshakePackage_Encode(t *testing.T) { 96 | challenge := make([]byte, 32) 97 | rand.Read(challenge) 98 | 99 | pack := NewHandshakePackage(challenge, nil) 100 | encoded := EncodePacketUDP(*NewHeader(), pack) 101 | 102 | header := new(Header) 103 | if err := header.Decode(encoded); err != nil { 104 | t.Error(err) 105 | } 106 | depack := new(HandshakePackage) 107 | if err := depack.Decode(header, encoded[HeaderSize:]); err != nil { 108 | t.Error(err) 109 | } 110 | 111 | if !bytes.Equal(depack.Challenge[:], pack.Challenge[:]) { 112 | t.Errorf("error decode, invalid challenge: got %X expecting %X", depack.Challenge[:], pack.Challenge[:]) 113 | } 114 | } 115 | 116 | func TestHandshakePackage_Encode_3(t *testing.T) { 117 | challenge := make([]byte, 32) 118 | rand.Read(challenge) 119 | 120 | rchallenge := make([]byte, 32) 121 | rand.Read(challenge) 122 | 123 | pack := NewHandshakePackage(challenge, rchallenge) 124 | encoded := EncodePacketUDP(*NewHeader(), pack) 125 | 126 | header := new(Header) 127 | if err := header.Decode(encoded); err != nil { 128 | t.Error(err) 129 | } 130 | 131 | depack := new(HandshakePackage) 132 | if err := depack.Decode(header, encoded[HeaderSize:]); err != nil { 133 | t.Error(err) 134 | } 135 | 136 | if !bytes.Equal(depack.Challenge[:], pack.Challenge[:]) { 137 | t.Errorf("error decode, invalid challenge: got %X expecting %X", depack.Challenge[:], pack.Challenge[:]) 138 | } 139 | 140 | if !pack.PublicKey.IsValidSignature(rchallenge, &pack.Signature) { 141 | t.Error("error encode, invalid signature") 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Node/Packets/header.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "errors" 5 | "github.com/brokenbydefault/Nanollet/Block" 6 | "io" 7 | ) 8 | 9 | // NetworkType is a one-byte which defines the network which is connected to, such as Live or Test. 10 | type NetworkType byte 11 | 12 | const ( 13 | Test NetworkType = iota + 65 14 | Beta 15 | Live 16 | ) 17 | 18 | // MessageType is one-byte which says what type of message was received or sent, such as Publish or ConfirmReq. 19 | type MessageType byte 20 | 21 | const ( 22 | Invalid MessageType = iota 23 | NotType 24 | KeepAlive 25 | Publish 26 | ConfirmReq 27 | ConfirmACK 28 | BulkPull 29 | BulkPush 30 | FrontierReq 31 | BulkPullBlocks 32 | NodeHandshake 33 | BulkPullAccount 34 | ) 35 | 36 | // Extensions is originally one uint16, in little-endian, which tells some specific behaviors and limitations of the 37 | // node and block usage. 38 | type ExtensionType uint16 39 | 40 | const ( 41 | ExtendedNode = ExtensionType(0x8000) 42 | ) 43 | 44 | func (t ExtensionType) Is(expected ExtensionType) (ok bool) { 45 | return t&expected == expected 46 | } 47 | 48 | func (t *ExtensionType) Add(extension ExtensionType) { 49 | *t |= extension 50 | } 51 | 52 | func (t ExtensionType) GetBlockType() Block.BlockType { 53 | return Block.BlockType((t >> 8) & 0x0F) 54 | } 55 | 56 | var ( 57 | ErrInvalidHeaderParameters = errors.New("invalid header parameters") 58 | ErrInvalidHeaderSize = errors.New("invalid header size") 59 | ) 60 | 61 | // HeaderSize represent the amount bytes used in the header 62 | const HeaderSize = 8 63 | 64 | type Header struct { 65 | MagicNumber byte 66 | NetworkType NetworkType 67 | VersionMax byte 68 | VersionUsing byte 69 | VersionMin byte 70 | MessageType MessageType 71 | ExtensionType ExtensionType 72 | 73 | removeHeader bool 74 | } 75 | 76 | func NewHeader() *Header { 77 | return &Header{ 78 | MagicNumber: 82, 79 | NetworkType: Live, 80 | VersionMax: 13, 81 | VersionUsing: 13, 82 | VersionMin: 13, 83 | MessageType: Invalid, 84 | ExtensionType: 0, 85 | removeHeader: false, 86 | } 87 | } 88 | 89 | func (h *Header) SetRemoveHeader(opt bool) { 90 | h.removeHeader = opt 91 | } 92 | 93 | func (h *Header) Encode(dst []byte) (n int, err error) { 94 | if h == nil { 95 | return 96 | } 97 | 98 | if len(dst) < HeaderSize { 99 | return 0, ErrDestinationLenghtNotEnough 100 | } 101 | 102 | if h.removeHeader { 103 | return 0, nil 104 | } 105 | 106 | n = copy(dst, []byte{ 107 | byte(h.MagicNumber), 108 | byte(h.NetworkType), 109 | byte(h.VersionMax), 110 | byte(h.VersionUsing), 111 | byte(h.VersionMin), 112 | byte(h.MessageType), 113 | byte(h.ExtensionType), 114 | byte(h.ExtensionType >> 8), 115 | }) 116 | 117 | return n, err 118 | } 119 | 120 | func (h *Header) Decode(src []byte) (err error) { 121 | if len(src) < HeaderSize { 122 | return ErrInvalidHeaderSize 123 | } 124 | 125 | h.MagicNumber = src[0] 126 | h.NetworkType = NetworkType(src[1]) 127 | h.VersionMax = src[2] 128 | h.VersionUsing = src[3] 129 | h.VersionMin = src[4] 130 | h.MessageType = MessageType(src[5]) 131 | h.ExtensionType = ExtensionType(uint16(src[6]) | uint16(src[7])<<8) 132 | 133 | if h.MagicNumber != []byte("R")[0] { 134 | return ErrInvalidHeaderParameters 135 | } 136 | 137 | //@TODO Verify network 138 | /** 139 | if h.NetworkType { 140 | } 141 | **/ 142 | 143 | //@TODO Verify version 144 | /** 145 | if h.VersionUsing { 146 | } 147 | **/ 148 | 149 | if h.MessageType >= BulkPullAccount { 150 | return ErrInvalidHeaderParameters 151 | } 152 | 153 | return nil 154 | } 155 | 156 | func (h *Header) Read(src io.Reader) (n int, err error) { 157 | b := make([]byte, HeaderSize) 158 | 159 | n, err = src.Read(b) 160 | if err != nil { 161 | return n, err 162 | } 163 | 164 | err = h.Decode(b) 165 | if err != nil { 166 | return n, err 167 | } 168 | 169 | return n, nil 170 | } 171 | 172 | func (h *Header) Write(dst io.Writer) (n int, err error) { 173 | if h.removeHeader { 174 | return 0, nil 175 | } 176 | 177 | b := make([]byte, HeaderSize) 178 | if n, err = h.Encode(b); err != nil { 179 | return n, err 180 | } 181 | 182 | if n, err = dst.Write(b); err != nil { 183 | return n, err 184 | } 185 | 186 | return HeaderSize, nil 187 | } 188 | -------------------------------------------------------------------------------- /Node/Packets/keepalive.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Node/Peer" 5 | ) 6 | 7 | const ( 8 | PeerSize = 18 9 | 10 | KeepAlivePackageNPeers = 8 11 | KeepAlivePackageSize = KeepAlivePackageNPeers * PeerSize 12 | ) 13 | 14 | type KeepAlivePackage struct { 15 | List []*Peer.Peer 16 | } 17 | 18 | func NewKeepAlivePackage(peer []*Peer.Peer) (packet *KeepAlivePackage) { 19 | return &KeepAlivePackage{ 20 | List: peer, 21 | } 22 | } 23 | 24 | func (p *KeepAlivePackage) Encode(dst []byte) (n int, err error) { 25 | if p == nil { 26 | return 27 | } 28 | 29 | if len(dst) < KeepAlivePackageSize { 30 | return 0, ErrDestinationLenghtNotEnough 31 | } 32 | 33 | max := len(p.List) 34 | if len(p.List) > KeepAlivePackageNPeers { 35 | max = KeepAlivePackageNPeers 36 | } 37 | 38 | bi := 0 39 | for _, peer := range p.List[:max] { 40 | bi += copy(dst[bi:], peer.IP) 41 | bi += copy(dst[bi:], []byte{byte(peer.Port), byte(peer.Port << 8)}) 42 | } 43 | 44 | return KeepAlivePackageSize, nil 45 | } 46 | 47 | func (p *KeepAlivePackage) Decode(_ *Header, src []byte) (err error) { 48 | if p == nil { 49 | return 50 | } 51 | 52 | // The packet should have at least 18 bytes and multiples by 18. 53 | l := len(src) 54 | if l%PeerSize != 0 { 55 | return ErrInvalidMessageSize 56 | } 57 | 58 | // Maximum should be KeepAlivePackageNPeers, or less than KeepAlivePackageNPeers if not have enough peers. 59 | // It ignores other peers, keeping only the firsts. 60 | max := l / PeerSize 61 | if KeepAlivePackageNPeers > max { 62 | max = KeepAlivePackageNPeers 63 | } 64 | 65 | bi := 0 66 | for i := 0; i < max; i++ { 67 | be := bi + PeerSize 68 | dataPeer := src[bi:be] 69 | 70 | p.List = append(p.List, Peer.NewPeer(dataPeer[:16], int(dataPeer[16])|int(dataPeer[17])<<8)) 71 | 72 | bi = be 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (p *KeepAlivePackage) ModifyHeader(h *Header) { 79 | h.MessageType = KeepAlive 80 | } 81 | -------------------------------------------------------------------------------- /Node/Packets/keepalive_test.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "testing" 5 | "net" 6 | "github.com/brokenbydefault/Nanollet/Util" 7 | "github.com/brokenbydefault/Nanollet/Node/Peer" 8 | "bytes" 9 | ) 10 | 11 | func BenchmarkKeepAlivePackage_Encode(b *testing.B) { 12 | server, err := net.ListenUDP("udp", &net.UDPAddr{ 13 | IP: net.ParseIP("127.0.0.1"), 14 | Port: 6000, 15 | }) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | dial, err := net.DialUDP("udp", nil, &net.UDPAddr{ 21 | IP: net.ParseIP("127.0.0.1"), 22 | Port: 6000, 23 | }) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | dst := make([]byte, PackageSize) 29 | 30 | packet := NewKeepAlivePackage(nil) 31 | packet.Encode(dst) 32 | 33 | for i := 0; i < b.N; i++ { 34 | dial.Write(dst) 35 | } 36 | 37 | server.Close() 38 | } 39 | 40 | func TestKeepAlivePackage_Decode(t *testing.T) { 41 | expected := []*Peer.Peer{ 42 | Peer.NewPeer(net.ParseIP("87.65.160.15"), 54000), 43 | Peer.NewPeer(net.ParseIP("185.45.113.124"), 54000), 44 | Peer.NewPeer(net.ParseIP("178.128.149.150"), 1024), 45 | Peer.NewPeer(net.ParseIP("80.25.160.217"), 54000), 46 | Peer.NewPeer(net.ParseIP("90.229.199.116"), 54000), 47 | Peer.NewPeer(net.ParseIP("81.169.243.90"), 54000), 48 | Peer.NewPeer(net.ParseIP("77.20.254.59"), 24946), 49 | Peer.NewPeer(net.ParseIP("13.59.162.102"), 54000), 50 | } 51 | 52 | udpMessage := Util.SecureHexMustDecode("52420d0d0702000000000000000000000000ffff5741a00ff0d200000000000000000000ffffb92d717cf0d200000000000000000000ffffb2809596000400000000000000000000ffff5019a0d9f0d200000000000000000000ffff5ae5c774f0d200000000000000000000ffff51a9f35af0d200000000000000000000ffff4d14fe3b726100000000000000000000ffff0d3ba266f0d2") 53 | 54 | header := new(Header) 55 | err := header.Decode(udpMessage) 56 | if err != nil { 57 | t.Error(err) 58 | } 59 | 60 | pack := new(KeepAlivePackage) 61 | if err = pack.Decode(header, udpMessage[HeaderSize:]); err != nil { 62 | t.Error(err) 63 | } 64 | 65 | for i, peer := range pack.List { 66 | if !bytes.Equal(expected[i].IP, peer.IP) { 67 | t.Errorf("invalid decode, wrong ip. Gets %s expecting %s", peer.IP, expected[i].IP) 68 | } 69 | 70 | if expected[i].Port != peer.Port { 71 | t.Errorf("invalid decode, wrong port. Gets %d expecting %d", peer.Port, expected[i].Port) 72 | } 73 | } 74 | 75 | } 76 | 77 | func TestKeepAlivePackage_Encode(t *testing.T) { 78 | peers := []*Peer.Peer{ 79 | Peer.NewPeer(net.ParseIP("87.65.160.15"), 54000), 80 | Peer.NewPeer(net.ParseIP("185.45.113.124"), 54000), 81 | Peer.NewPeer(net.ParseIP("178.128.149.150"), 54000), 82 | Peer.NewPeer(net.ParseIP("90.229.199.116"), 54000), 83 | Peer.NewPeer(net.ParseIP("81.169.243.90"), 54000), 84 | Peer.NewPeer(net.ParseIP("77.20.254.59"), 54000), 85 | Peer.NewPeer(net.ParseIP("13.59.162.102"), 54000), 86 | } 87 | 88 | pack := NewKeepAlivePackage(peers) 89 | encoded := EncodePacketUDP(*NewHeader(), pack) 90 | 91 | header := new(Header) 92 | if err := header.Decode(encoded); err != nil { 93 | t.Error(err) 94 | } 95 | 96 | depack := new(KeepAlivePackage) 97 | if err := depack.Decode(header, encoded[HeaderSize:]); err != nil { 98 | t.Error(err) 99 | } 100 | 101 | for i, peer := range pack.List { 102 | if !bytes.Equal(peers[i].IP, peer.IP) { 103 | t.Errorf("invalid decode, wrong ip. Gets %s expecting %s", peer.IP, peers[i].IP) 104 | } 105 | 106 | if peers[i].Port != peer.Port { 107 | t.Errorf("invalid decode, wrong port. Gets %d expecting %d", peer.Port, peers[i].Port) 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Node/Packets/packet.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | const ( 9 | MessageSize = 508 10 | PackageSize = HeaderSize + MessageSize 11 | ) 12 | 13 | var ( 14 | ErrUnsupportedMessage = errors.New("unsupported message type") 15 | ErrInvalidMessageSize = errors.New("invalid message") 16 | ErrDestinationLenghtNotEnough = errors.New("dst are smaller than message") 17 | ) 18 | 19 | type PacketUDP interface { 20 | Encode(dst []byte) (n int, err error) 21 | Decode(rHeader *Header, src []byte) (err error) 22 | 23 | ModifyHeader(h *Header) 24 | } 25 | 26 | type PacketTCP interface { 27 | Encode(dst io.Writer) (err error) 28 | Decode(rHeader *Header, src io.Reader) (err error) 29 | 30 | ModifyHeader(h *Header) 31 | } 32 | 33 | func EncodePacketUDP(lHeader Header, packet PacketUDP) []byte { 34 | dst := make([]byte, PackageSize) 35 | packet.ModifyHeader(&lHeader) 36 | 37 | nH, err := lHeader.Encode(dst) 38 | if err != nil { 39 | return nil 40 | } 41 | 42 | nP, err := packet.Encode(dst[nH:]) 43 | if err != nil { 44 | return nil 45 | } 46 | 47 | return dst[:nH+nP] 48 | } 49 | 50 | func EncodePacketTCP(lHeader Header, packet PacketTCP, writer io.Writer) (err error) { 51 | packet.ModifyHeader(&lHeader) 52 | 53 | _, err = lHeader.Write(writer) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | err = packet.Encode(writer) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func DecodeResponsePacketTCP(messageType MessageType) PacketTCP { 67 | switch messageType { 68 | case FrontierReq: 69 | return &FrontierReqPackageResponse{} 70 | case BulkPull: 71 | return &BulkPullPackageResponse{} 72 | case BulkPullBlocks: 73 | panic("not supported") 74 | case BulkPullAccount: 75 | return &BulkPullAccountPackageResponse{} 76 | default: 77 | panic("not supported") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Node/Packets/publish.go: -------------------------------------------------------------------------------- 1 | package Packets 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | ) 6 | 7 | type PushPackage struct { 8 | Transaction Block.Transaction 9 | } 10 | 11 | func NewPushPackage(transaction Block.Transaction) (packet *PushPackage) { 12 | return &PushPackage{ 13 | transaction, 14 | } 15 | } 16 | 17 | func (p *PushPackage) Encode(dst []byte) (n int, err error) { 18 | if p == nil { 19 | return 20 | } 21 | 22 | n += copy(dst, p.Transaction.Encode()[1:]) 23 | 24 | return n, err 25 | } 26 | 27 | func (p *PushPackage) Decode(rHeader *Header, data []byte) (err error) { 28 | if p == nil { 29 | return nil 30 | } 31 | 32 | if rHeader == nil { 33 | return ErrInvalidHeaderParameters 34 | } 35 | 36 | p.Transaction, _, err = Block.NewTransaction(rHeader.ExtensionType.GetBlockType()) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return p.Transaction.Decode(data) 42 | } 43 | 44 | func (p *PushPackage) ModifyHeader(h *Header) { 45 | h.MessageType = Publish 46 | h.ExtensionType |= ExtensionType(uint8(p.Transaction.GetType())) << 8 47 | } 48 | -------------------------------------------------------------------------------- /Node/Peer/peer.go: -------------------------------------------------------------------------------- 1 | package Peer 2 | 3 | import ( 4 | "net" 5 | "errors" 6 | "strconv" 7 | "time" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "github.com/brokenbydefault/Nanollet/Util" 10 | "crypto/rand" 11 | ) 12 | 13 | const ( 14 | BetaPort = 54000 15 | LivePort = 7075 16 | ) 17 | 18 | const ( 19 | Timeout = 2 * time.Minute 20 | ) 21 | 22 | var ( 23 | ErrInvalidIP = errors.New("invalid ip") 24 | ErrInvalidPort = errors.New("invalid port") 25 | ErrIncompleteData = errors.New("invalid IP:PORT, both are needed") 26 | ) 27 | 28 | type Peer struct { 29 | IP []byte 30 | Port int 31 | 32 | LastSeen time.Time 33 | PublicKey Wallet.PublicKey 34 | Header [8]byte 35 | Challenge [32]byte 36 | } 37 | 38 | func NewPeer(ip net.IP, port int) (peer *Peer) { 39 | peer = &Peer{ 40 | IP: ip, 41 | Port: port, 42 | LastSeen: time.Now(), 43 | } 44 | 45 | rand.Read(peer.Challenge[:]) 46 | 47 | return peer 48 | } 49 | 50 | func NewPeersFromString(hosts ...string) (peers []*Peer) { 51 | for _, host := range hosts { 52 | 53 | iph, porth, err := net.SplitHostPort(host) 54 | if err != nil { 55 | continue 56 | } 57 | 58 | port, err := strconv.Atoi(porth) 59 | if err != nil { 60 | continue 61 | } 62 | 63 | if ip := net.ParseIP(iph); ip != nil { 64 | peers = append(peers, NewPeer(ip, port)) 65 | } else { 66 | ips, _ := Util.LookupIP(iph) 67 | for _, ip := range ips { 68 | peers = append(peers, NewPeer(ip, port)) 69 | } 70 | } 71 | } 72 | 73 | return peers 74 | } 75 | 76 | func (p *Peer) IsActive() bool { 77 | if p == nil { 78 | return false 79 | } 80 | 81 | if time.Since(p.LastSeen) > Timeout { 82 | return false 83 | } 84 | 85 | return true 86 | } 87 | 88 | func (p *Peer) IsKnow() bool { 89 | if p == nil { 90 | return false 91 | } 92 | 93 | if Util.IsEmpty(p.PublicKey[:]) { 94 | return false 95 | } 96 | 97 | return true 98 | } 99 | -------------------------------------------------------------------------------- /Node/Peer/vote.go: -------------------------------------------------------------------------------- 1 | package Peer 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Wallet" 5 | ) 6 | 7 | type Quorum struct { 8 | PublicKeys []Wallet.PublicKey 9 | Common int 10 | Fork int 11 | } 12 | 13 | func (q *Quorum) CalcCommon() int { 14 | if q == nil { 15 | return (len(q.PublicKeys) * 50) / 100 16 | } 17 | 18 | return (len(q.PublicKeys) * q.Common) / 100 19 | } 20 | 21 | func (q *Quorum) CalcFork() int { 22 | if q == nil { 23 | return (len(q.PublicKeys) * 50) / 100 24 | } 25 | 26 | return (len(q.PublicKeys) * q.Fork) / 100 27 | } 28 | 29 | func (q *Quorum) Calc(possibleWinners int) int { 30 | if possibleWinners > 1 { 31 | return q.CalcFork() 32 | } 33 | 34 | return q.CalcCommon() 35 | } -------------------------------------------------------------------------------- /Node/easy_test.go: -------------------------------------------------------------------------------- 1 | package Node 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "github.com/brokenbydefault/Nanollet/Util" 7 | "time" 8 | "github.com/brokenbydefault/Nanollet/Node/Packets" 9 | "github.com/brokenbydefault/Nanollet/Block" 10 | "github.com/brokenbydefault/Nanollet/Numbers" 11 | "github.com/brokenbydefault/Nanollet/Storage" 12 | ) 13 | 14 | func TestGetBalance(t *testing.T) { 15 | c := NewServer(Packets.Header{ 16 | MagicNumber: 82, 17 | NetworkType: Packets.Live, 18 | VersionMax: 13, 19 | VersionUsing: 13, 20 | VersionMin: 13, 21 | MessageType: Packets.Invalid, 22 | ExtensionType: 0, 23 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 24 | 25 | NewHandler(c).Start() 26 | 27 | 28 | time.Sleep(2 * time.Second) 29 | 30 | pkx := Wallet.Address("xrb_1ipx847tk8o46pwxt5qjdbncjqcbwcc1rrmqnkztrfjy5k7z4imsrata9est").MustGetPublicKey() 31 | 32 | amount, err := GetBalance(c, &pkx) 33 | if err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | 38 | if amount.Compare(Numbers.NewMin()) == 0 || !amount.IsValid() { 39 | t.Error("invalid amount") 40 | } 41 | 42 | } 43 | 44 | func TestGetBlock(t *testing.T) { 45 | c := NewServer(Packets.Header{ 46 | MagicNumber: 82, 47 | NetworkType: Packets.Live, 48 | VersionMax: 13, 49 | VersionUsing: 13, 50 | VersionMin: 13, 51 | MessageType: Packets.Invalid, 52 | ExtensionType: 0, 53 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 54 | 55 | NewHandler(c).Start() 56 | time.Sleep(1 * time.Second) 57 | 58 | hash := Block.NewBlockHash(Util.SecureHexMustDecode("8FF14E8A184F43B63B048C5D20862B7A9D91DA1275BC2F77E8149633B157BCB8")) 59 | tx, err := GetBlock(c, &hash) 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | 65 | if tx == nil { 66 | t.Error("no block found") 67 | return 68 | } 69 | } 70 | 71 | func TestGetHistory(t *testing.T) { 72 | c := NewServer(Packets.Header{ 73 | MagicNumber: 82, 74 | NetworkType: Packets.Live, 75 | VersionMax: 13, 76 | VersionUsing: 13, 77 | VersionMin: 13, 78 | MessageType: Packets.Invalid, 79 | ExtensionType: 0, 80 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 81 | 82 | NewHandler(c).Start() 83 | time.Sleep(1 * time.Second) 84 | 85 | pk := Wallet.Address("xrb_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc").MustGetPublicKey() 86 | 87 | txs, err := GetHistory(c, &pk, nil) 88 | if err != nil { 89 | t.Error(err) 90 | return 91 | } 92 | 93 | if len(txs) <= 0 { 94 | t.Error("blocks not found") 95 | return 96 | } 97 | 98 | open := txs[len(txs)-1].Hash() 99 | if open != Block.NewBlockHash(Util.SecureHexMustDecode("C4977221708A90790665432F51CDE3B1E248F876448B35E7EBEF9285036D90C0")) { 100 | t.Error("open not found") 101 | return 102 | } 103 | 104 | } 105 | 106 | func TestGetPendings(t *testing.T) { 107 | c := NewServer(Packets.Header{ 108 | MagicNumber: 82, 109 | NetworkType: Packets.Live, 110 | VersionMax: 13, 111 | VersionUsing: 13, 112 | VersionMin: 13, 113 | MessageType: Packets.Invalid, 114 | ExtensionType: 0, 115 | }, &Storage.PeerStorage, &Storage.TransactionStorage) 116 | 117 | NewHandler(c).Start() 118 | time.Sleep(1 * time.Second) 119 | 120 | pk := Wallet.Address("xrb_1nanofy8on8preceding8transaction11111111411111111111pqdyc8af").MustGetPublicKey() 121 | 122 | txs, err := GetPendings(c, &pk, Numbers.NewMin()) 123 | if err != nil { 124 | t.Error(err) 125 | return 126 | } 127 | 128 | if len(txs) < 10 { 129 | t.Error("not enough information") 130 | return 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /Node/nano.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package Node 4 | 5 | import ( 6 | "net" 7 | "github.com/brokenbydefault/Nanollet/Node/Packets" 8 | "time" 9 | "github.com/brokenbydefault/Nanollet/Node/Peer" 10 | "github.com/brokenbydefault/Nanollet/Storage" 11 | "errors" 12 | "context" 13 | "sync" 14 | ) 15 | 16 | var ( 17 | ErrTCPNotAvailable = errors.New("this node doesn't support TCP") 18 | ) 19 | 20 | type Server struct { 21 | udp *net.UDPConn 22 | //TCP *net.TCPListener 23 | 24 | Peer *Storage.PeersBox 25 | Transaction *Storage.TransactionBox 26 | Header Packets.Header 27 | } 28 | 29 | func NewServer(Header Packets.Header, Peer *Storage.PeersBox, Tx *Storage.TransactionBox) Node { 30 | return &Server{ 31 | Peer: Peer, 32 | Transaction: Tx, 33 | Header: Header, 34 | } 35 | } 36 | 37 | func (srv *Server) Peers() *Storage.PeersBox { 38 | return srv.Peer 39 | } 40 | 41 | func (srv *Server) Transactions() *Storage.TransactionBox { 42 | return srv.Transaction 43 | } 44 | 45 | func (srv *Server) ListenTCP() (ch <-chan RawTCP, err error) { 46 | // no-op 47 | return nil, nil 48 | } 49 | 50 | func (srv *Server) ListenUDP() (ch <-chan RawUDP, err error) { 51 | if srv.udp == nil { 52 | srv.initUDP() 53 | } 54 | 55 | c := make(chan RawUDP, 1<<15) 56 | 57 | go func() { 58 | for { 59 | b := make([]byte, Packets.HeaderSize+Packets.MessageSize) 60 | end, dest, err := srv.udp.ReadFromUDP(b) 61 | if err != nil { 62 | continue 63 | } 64 | 65 | c <- RawUDP{ 66 | Raw: b[:end], 67 | Source: Peer.NewPeer(dest.IP, dest.Port), 68 | } 69 | } 70 | }() 71 | 72 | return c, nil 73 | } 74 | 75 | // SendUDPTo sends a package to specific peer, this peer must accept UDP. 76 | func (srv *Server) SendUDPTo(packet Packets.PacketUDP, dest *Peer.Peer) (err error) { 77 | if dest == nil { 78 | return 79 | } 80 | 81 | if srv.udp == nil { 82 | srv.initUDP() 83 | } 84 | 85 | 86 | if _, err := srv.udp.WriteTo(Packets.EncodePacketUDP(srv.Header, packet), &net.UDPAddr{IP: dest.IP, Port: dest.Port}); err != nil { 87 | return err 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // SendUDP sends a package to all known active peer. 94 | func (srv *Server) SendUDP(packet Packets.PacketUDP) (err error) { 95 | for _, dest := range srv.Peer.GetAll() { 96 | srv.SendUDPTo(packet, dest) 97 | } 98 | 99 | // @TODO (inkeliz) add a error handler 100 | return nil 101 | } 102 | 103 | // SendTCPTo sends a package to specific peer, this peer must accept TCP. 104 | func (srv *Server) SendTCPTo(request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 105 | if dest == nil { 106 | return packet, ErrTCPNotAvailable 107 | } 108 | 109 | packet = Packets.DecodeResponsePacketTCP(responseType) 110 | 111 | tcp, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: dest.IP, Port: dest.Port}) 112 | if err != nil { 113 | return packet, err 114 | } 115 | 116 | defer tcp.Close() 117 | 118 | tcp.SetReadDeadline(time.Now().Add(10 * time.Second)) 119 | 120 | if err := Packets.EncodePacketTCP(srv.Header, request, tcp); err != nil { 121 | return packet, err 122 | } 123 | 124 | if err := packet.Decode(nil, tcp); err != nil { 125 | return packet, err 126 | } 127 | 128 | return packet, nil 129 | } 130 | 131 | // SendTCP sends a package to one random known peer. 132 | func (srv *Server) SendTCP(request Packets.PacketTCP, responseType Packets.MessageType) (packets <-chan Packets.PacketTCP, cancel context.CancelFunc) { 133 | peers := srv.Peer.GetRandom(0) 134 | 135 | ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second)) 136 | c := make(chan Packets.PacketTCP, len(peers)) 137 | 138 | go func() { 139 | wg := new(sync.WaitGroup) 140 | 141 | for _, peer := range peers { 142 | if peer.IsActive() && peer.IsKnow() { 143 | wg.Add(1) 144 | go func(peer *Peer.Peer) { 145 | if packet, err := srv.sendTCPToContext(ctx, request, responseType, peer); err == nil && ctx.Err() == nil { 146 | c <- packet 147 | } else { 148 | } 149 | wg.Done() 150 | }(peer) 151 | } 152 | } 153 | 154 | wg.Wait() 155 | close(c) 156 | }() 157 | 158 | return c, cancel 159 | } 160 | 161 | func (srv *Server) sendTCPToContext(ctx context.Context, request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 162 | dialer := new(net.Dialer) 163 | 164 | packet = Packets.DecodeResponsePacketTCP(responseType) 165 | 166 | addr := &net.TCPAddr{IP: dest.IP, Port: dest.Port} 167 | tcp, err := dialer.DialContext(ctx, "tcp", addr.String()) 168 | if err != nil { 169 | return packet, err 170 | } 171 | 172 | tcp.SetReadDeadline(time.Now().Add(10 * time.Second)) 173 | defer tcp.Close() 174 | 175 | if err := Packets.EncodePacketTCP(srv.Header, request, tcp); err != nil { 176 | return packet, err 177 | } 178 | 179 | if err := packet.Decode(nil, tcp); err != nil { 180 | return packet, err 181 | } 182 | 183 | return packet, nil 184 | } 185 | 186 | func (srv *Server) initUDP() { 187 | udp, err := net.ListenUDP("udp", nil) 188 | if udp == nil || err != nil { 189 | panic(err) 190 | } 191 | 192 | srv.udp = udp 193 | } 194 | -------------------------------------------------------------------------------- /Node/nano_js.go: -------------------------------------------------------------------------------- 1 | // +build js, !android 2 | 3 | package Node 4 | 5 | import ( 6 | "github.com/brokenbydefault/Nanollet/Node/Packets" 7 | "github.com/brokenbydefault/Nanollet/Node/Peer" 8 | "github.com/brokenbydefault/Nanollet/Storage" 9 | "errors" 10 | "context" 11 | ) 12 | 13 | var ( 14 | ErrTCPNotAvailable = errors.New("this node doesn't support TCP") 15 | ) 16 | 17 | type Server struct { 18 | Peer *Storage.PeersBox 19 | Transaction *Storage.TransactionBox 20 | Header Packets.Header 21 | } 22 | 23 | func NewServer(Header Packets.Header, Peer *Storage.PeersBox, Tx *Storage.TransactionBox) Node { 24 | return &Server{ 25 | Peer: Peer, 26 | Transaction: Tx, 27 | Header: Header, 28 | } 29 | } 30 | 31 | func (srv *Server) Peers() *Storage.PeersBox { 32 | return srv.Peer 33 | } 34 | 35 | func (srv *Server) Transactions() *Storage.TransactionBox { 36 | return srv.Transaction 37 | } 38 | 39 | func (srv *Server) ListenTCP() (ch <-chan RawTCP, err error) { 40 | // no-op 41 | return nil, nil 42 | } 43 | 44 | func (srv *Server) ListenUDP() (ch <-chan RawUDP, err error) { 45 | c := make(chan RawUDP, 1<<15) 46 | return c, nil 47 | } 48 | 49 | // SendUDPTo sends a package to specific peer, this peer must accept UDP. 50 | func (srv *Server) SendUDPTo(packet Packets.PacketUDP, dest *Peer.Peer) (err error) { 51 | if dest == nil { 52 | return 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // SendUDP sends a package to all known active peer. 59 | func (srv *Server) SendUDP(packet Packets.PacketUDP) (err error) { 60 | return nil 61 | } 62 | 63 | // SendTCPTo sends a package to specific peer, this peer must accept TCP. 64 | func (srv *Server) SendTCPTo(request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 65 | if dest == nil { 66 | return packet, ErrTCPNotAvailable 67 | } 68 | 69 | return packet, nil 70 | } 71 | 72 | // SendTCP sends a package to one random known peer. 73 | func (srv *Server) SendTCP(request Packets.PacketTCP, responseType Packets.MessageType) (packets <-chan Packets.PacketTCP, cancel context.CancelFunc) { 74 | 75 | return nil, cancel 76 | } 77 | 78 | func (srv *Server) sendTCPToContext(ctx context.Context, request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 79 | return packet, nil 80 | } 81 | 82 | func (srv *Server) initUDP() { 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Node/nano_mobile.go: -------------------------------------------------------------------------------- 1 | // +build js, android 2 | 3 | package Node 4 | 5 | import ( 6 | "net" 7 | "github.com/brokenbydefault/Nanollet/Node/Packets" 8 | "github.com/brokenbydefault/Nanollet/Node/Peer" 9 | "github.com/brokenbydefault/Nanollet/Storage" 10 | "errors" 11 | "context" 12 | ) 13 | 14 | var ( 15 | ErrTCPNotAvailable = errors.New("this node doesn't support TCP") 16 | ) 17 | 18 | type Server struct { 19 | Peer *Storage.PeersBox 20 | Transaction *Storage.TransactionBox 21 | Header Packets.Header 22 | } 23 | 24 | func NewServer(Header Packets.Header, Peer *Storage.PeersBox, Tx *Storage.TransactionBox) Node { 25 | return &Server{ 26 | Peer: Peer, 27 | Transaction: Tx, 28 | Header: Header, 29 | } 30 | } 31 | 32 | func (srv *Server) Peers() *Storage.PeersBox { 33 | return srv.Peer 34 | } 35 | 36 | func (srv *Server) Transactions() *Storage.TransactionBox { 37 | return srv.Transaction 38 | } 39 | 40 | func (srv *Server) ListenTCP() (ch <-chan RawTCP, err error) { 41 | // no-op 42 | return nil, nil 43 | } 44 | 45 | func (srv *Server) ListenUDP() (ch <-chan RawUDP, err error) { 46 | c := make(chan RawUDP, 1<<15) 47 | return c, nil 48 | } 49 | 50 | // SendUDPTo sends a package to specific peer, this peer must accept UDP. 51 | func (srv *Server) SendUDPTo(packet Packets.PacketUDP, dest *Peer.Peer) (err error) { 52 | 53 | return nil 54 | } 55 | 56 | // SendUDP sends a package to all known active peer. 57 | func (srv *Server) SendUDP(packet Packets.PacketUDP) (err error) { 58 | return nil 59 | } 60 | 61 | // SendTCPTo sends a package to specific peer, this peer must accept TCP. 62 | func (srv *Server) SendTCPTo(request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 63 | if dest == nil { 64 | return packet, ErrTCPNotAvailable 65 | } 66 | 67 | return packet, nil 68 | } 69 | 70 | // SendTCP sends a package to one random known peer. 71 | func (srv *Server) SendTCP(request Packets.PacketTCP, responseType Packets.MessageType) (packets <-chan Packets.PacketTCP, cancel context.CancelFunc) { 72 | 73 | return nil, cancel 74 | } 75 | 76 | func (srv *Server) sendTCPToContext(ctx context.Context, request Packets.PacketTCP, responseType Packets.MessageType, dest *Peer.Peer) (packet Packets.PacketTCP, err error) { 77 | return packet, nil 78 | } 79 | 80 | func (srv *Server) initUDP() { 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Numbers/human.go: -------------------------------------------------------------------------------- 1 | package Numbers 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | "strings" 7 | ) 8 | 9 | type UnitBase int 10 | type HumanAmount struct { 11 | whole string 12 | decimal string 13 | base UnitBase 14 | } 15 | 16 | const ( 17 | MicroXRB UnitBase = 18 + (3 * iota) 18 | MiliXRB 19 | XRB 20 | KiloXRB 21 | MegaXRB 22 | GigaXRB 23 | RAW UnitBase = 0 24 | ) 25 | 26 | func NewHumanFromString(n string, base UnitBase) *HumanAmount { 27 | values := make([]string, 2) 28 | 29 | split := strings.Split(n, ".") 30 | if len(split) > 2 { 31 | panic("value is invalid, having more than one dot") 32 | } 33 | 34 | copy(values, split) 35 | 36 | r := HumanAmount{ 37 | whole: values[0], 38 | decimal: values[1], 39 | base: base, 40 | } 41 | 42 | return &r 43 | } 44 | 45 | func NewHumanFromRaw(n *RawAmount) *HumanAmount { 46 | return NewHumanFromString(n.ToString(), 0) 47 | } 48 | 49 | func (n *HumanAmount) ConvertToRawAmount() (*RawAmount, error) { 50 | exp := int(n.base) - len(n.decimal) 51 | if exp < 0 { 52 | return nil, errors.New("lowest decimal already reached") 53 | } 54 | 55 | amm, err := NewRawFromString(n.whole + n.decimal) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | r := amm.Multiply(pow(10, exp)) 61 | 62 | return r, nil 63 | } 64 | 65 | func (n *HumanAmount) ConvertToBase(base UnitBase, scale int) (string, error) { 66 | exp := int(base) - scale 67 | if exp < 0 { 68 | return "", errors.New("lowest decimal already reached") 69 | } 70 | 71 | raw, err := n.ConvertToRawAmount() 72 | if err != nil { 73 | return "", err 74 | } 75 | 76 | ramm := raw.Divide(pow(10, exp)).ToString() 77 | r := whole(ramm, scale) + decimal(ramm, scale) 78 | 79 | return r, nil 80 | } 81 | 82 | func pow(a, b int) *RawAmount { 83 | return &RawAmount{new(big.Int).Exp(new(big.Int).SetInt64(int64(a)), new(big.Int).SetInt64(int64(b)), nil)} 84 | } 85 | 86 | func whole(s string, scale int) string { 87 | if scale == 0 { 88 | return s 89 | } 90 | 91 | if len(s) > scale { 92 | return s[:len(s)-scale] 93 | } 94 | 95 | return "0" 96 | } 97 | 98 | func decimal(s string, scale int) string { 99 | if scale == 0 { 100 | return "" 101 | } 102 | 103 | if len(s) >= scale { 104 | return "." + s[len(s)-scale:] 105 | } 106 | 107 | r := make([]byte, scale) 108 | for i := range r { 109 | r[i] = 0x30 110 | } 111 | 112 | copy(r[scale-len(s):], s) 113 | 114 | return "." + string(r) 115 | } 116 | -------------------------------------------------------------------------------- /Numbers/human_test.go: -------------------------------------------------------------------------------- 1 | package Numbers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHumanToRaw(t *testing.T) { 8 | 9 | input := "0.000001" 10 | amm := NewHumanFromString(input, MegaXRB) 11 | output, _ := amm.ConvertToBase(MegaXRB, 6) 12 | 13 | if output != input { 14 | t.Error("wrong value") 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Numbers/raw.go: -------------------------------------------------------------------------------- 1 | package Numbers 2 | 3 | import ( 4 | "errors" 5 | "github.com/brokenbydefault/Nanollet/Util" 6 | "math/big" 7 | "io" 8 | ) 9 | 10 | type RawAmount struct { 11 | bigint *big.Int 12 | } 13 | 14 | var ( 15 | ErrInvalidAmount = errors.New("invalid amount") 16 | ErrInvalidInput = errors.New("impossible to convert the input to RawNumbers") 17 | ) 18 | 19 | var ( 20 | max = new(big.Int).SetBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) 21 | min = new(big.Int).SetInt64(0) 22 | ) 23 | 24 | // NewRaw creates an RawAmount with lowest valid value. 25 | func NewRaw() *RawAmount { 26 | return NewMin() 27 | } 28 | 29 | // NewMax is a wrapper to create a new amount with maximum possible amount. 30 | func NewMax() *RawAmount { 31 | return &RawAmount{ 32 | bigint: max, 33 | } 34 | } 35 | 36 | // NewMax is a wrapper to create a new amount with minimum possible amount. 37 | func NewMin() *RawAmount { 38 | return &RawAmount{ 39 | bigint: min, 40 | } 41 | } 42 | 43 | // NewRawFromString creates an RawAmount from numeric string. It returns an error if 44 | // the string is invalid and nil successful. 45 | func NewRawFromString(s string) (*RawAmount, error) { 46 | i, ok := new(big.Int).SetString(s, 10) 47 | r := &RawAmount{i} 48 | 49 | if !ok || !r.IsValid() { 50 | return nil, ErrInvalidInput 51 | } 52 | 53 | return r, nil 54 | } 55 | 56 | // NewRawFromHex creates an RawAmount from hexadecimal string. It returns an non-nil error if 57 | // the value is invalid. 58 | func NewRawFromHex(h string) (*RawAmount, error) { 59 | b, err := Util.UnsafeHexDecode(h) 60 | if err != nil { 61 | return nil, ErrInvalidInput 62 | } 63 | 64 | return NewRawFromBytes(b), nil 65 | } 66 | 67 | // NewRawFromBytes creates an RawAmount from byte-array. It returns an non-nil error if 68 | // the string is invalid and nil successful. 69 | func NewRawFromBytes(b []byte) *RawAmount { 70 | return &RawAmount{ 71 | bigint: new(big.Int).SetBytes(b), 72 | } 73 | } 74 | 75 | // ToString transforms the RawAmount to string, which can be printable. 76 | func (a *RawAmount) ToString() string { 77 | if a == nil { 78 | return "" 79 | } 80 | 81 | return new(big.Int).Set(a.bigint).String() 82 | } 83 | 84 | // ToPaddedHex transforms the RawAmount to hexadecimal string with 16 byte, 85 | // left zero-padded. It can be used in RPC. 86 | func (a *RawAmount) ToHex() string { 87 | if a == nil { 88 | return "" 89 | } 90 | 91 | return Util.UnsafeHexEncode(a.ToBytes()) 92 | } 93 | 94 | // ToBytes transforms the RawAmount to 16 byte, left zero-padded. It can be used in 95 | // block signature and in RPC. 96 | func (a *RawAmount) ToBytes() []byte { 97 | b := make([]byte, 16) 98 | 99 | if a == nil { 100 | return b 101 | } 102 | 103 | bi := a.bigint.Bytes() 104 | 105 | offset := 16 - len(bi) 106 | if offset < 0 { 107 | offset = 0 108 | } 109 | 110 | copy(b[offset:], bi) 111 | 112 | return b 113 | } 114 | 115 | func (a *RawAmount) MarshalBinary() (data []byte, err error) { 116 | return a.ToBytes(), nil 117 | } 118 | 119 | func (a *RawAmount) UnmarshalBinary(data []byte) error { 120 | *a = *NewRawFromBytes(data) 121 | 122 | if !a.IsValid() { 123 | return ErrInvalidAmount 124 | } 125 | 126 | return nil 127 | } 128 | 129 | func (a *RawAmount) Read(reader io.Reader) (err error) { 130 | b := make([]byte, 16) 131 | if n, err := reader.Read(b); n != 16 || err != nil { 132 | return ErrInvalidInput 133 | } 134 | 135 | a.bigint = new(big.Int).SetBytes(b) 136 | 137 | return 138 | } 139 | 140 | func (a *RawAmount) Write(writer io.Writer) (err error) { 141 | if n, err := writer.Write(a.ToBytes()); n != 16 || err != nil { 142 | return ErrInvalidInput 143 | } 144 | 145 | return 146 | } 147 | -------------------------------------------------------------------------------- /Numbers/raw_test.go: -------------------------------------------------------------------------------- 1 | package Numbers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewRawFromString(t *testing.T) { 8 | n, err := NewRawFromString("340282366920937463463374607431768211456") 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | x, err := NewRawFromString("340282366920938463463374607431768211455") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | n = n.Add(x) 17 | 18 | } 19 | 20 | func TestRawAmount_IsValid(t *testing.T) { 21 | 22 | if _, err := NewRawFromString("-1"); err == nil { 23 | t.Error("is valid a invalid amount") 24 | } 25 | 26 | amm, err := NewRawFromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") 27 | if err != nil || amm.IsValid() { 28 | t.Error("is valid a invalid amount") 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Numbers/rawmath.go: -------------------------------------------------------------------------------- 1 | package Numbers 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Divide divides the a by b 8 | func (a *RawAmount) Divide(b *RawAmount) *RawAmount { 9 | return &RawAmount{new(big.Int).Div(a.bigint, b.bigint)} 10 | } 11 | 12 | // Multiply multiplies the a by b 13 | func (a *RawAmount) Multiply(b *RawAmount) *RawAmount { 14 | return &RawAmount{new(big.Int).Mul(a.bigint, b.bigint)} 15 | } 16 | 17 | // Subtract subtracts the a by b 18 | func (a *RawAmount) Subtract(b *RawAmount) *RawAmount { 19 | return &RawAmount{new(big.Int).Sub(a.bigint, b.bigint)} 20 | } 21 | 22 | // Add adds the a by b 23 | func (a *RawAmount) Add(b *RawAmount) *RawAmount { 24 | return &RawAmount{new(big.Int).Add(a.bigint, b.bigint)} 25 | } 26 | 27 | // Abs returns the absolute value 28 | func (a *RawAmount) Abs() *RawAmount { 29 | return &RawAmount{new(big.Int).Abs(a.bigint)} 30 | } 31 | 32 | // Compare compares a with b, return 33 | // -1 if a < b 34 | // 0 if a == b 35 | // +1 if a > b 36 | func (a *RawAmount) Compare(b *RawAmount) int { 37 | return a.bigint.Cmp(b.bigint) 38 | } 39 | 40 | // IsValid returns true if the value is between 0 to 1<<128-1 41 | func (a *RawAmount) IsValid() bool { 42 | return a.bigint.Cmp(min) >= 0 && a.bigint.Cmp(max) <= 0 43 | } 44 | -------------------------------------------------------------------------------- /OpenCAP/address.go: -------------------------------------------------------------------------------- 1 | // Package OpenCAP is highly inspired in https://github.com/opencap/go-opencap. This packages follows the same 2 | // construction of Wallet. 3 | package OpenCAP 4 | 5 | import ( 6 | "strings" 7 | "github.com/brokenbydefault/Nanollet/Wallet" 8 | "net/http" 9 | "encoding/json" 10 | "errors" 11 | "time" 12 | "github.com/brokenbydefault/Nanollet/Util" 13 | ) 14 | 15 | type Address string 16 | 17 | var ( 18 | ErrInvalidAlias = errors.New("invalid alias provided") 19 | ErrUnexpectedResponse = errors.New("bad data returned from alias host") 20 | ErrCAPPNotSupported = errors.New("the CAPP is not currently supported") 21 | ErrNotFoundAlias = errors.New("no host found for the given alias") 22 | ) 23 | 24 | // GetPublicKey gets the Ed25519 public-key requesting OpenCAP server, the server should present in the address, 25 | // returns the public-key. It's return an non-nil error if something bad happens. 26 | func (addr Address) GetPublicKey() (pk Wallet.PublicKey, err error) { 27 | host := addr.GetHost() 28 | if !strings.HasSuffix(host, addr.GetHost()) { 29 | return pk, ErrCAPPNotSupported 30 | } 31 | 32 | client := &http.Client{Timeout: 3 * time.Second} 33 | 34 | respRaw, err := client.Get("https://" + addr.LookupHost() + "/v1/addresses?alias=" + addr.GetAlias() + "&address_type=300") 35 | if err != nil { 36 | return pk, ErrUnexpectedResponse 37 | } 38 | 39 | defer respRaw.Body.Close() 40 | 41 | resp := struct { 42 | AddressType int `json:"address_type"` 43 | Address Wallet.Address `json:"address"` 44 | }{} 45 | 46 | if err = json.NewDecoder(respRaw.Body).Decode(&resp); err != nil { 47 | return pk, ErrUnexpectedResponse 48 | } 49 | 50 | pk, err = resp.Address.GetPublicKey() 51 | if err != nil || resp.AddressType != 300 { 52 | return pk, ErrUnexpectedResponse 53 | } 54 | 55 | return pk, nil 56 | } 57 | 58 | // MustGetPublicKey is a wrapper from GetPublicKey, which removes the error response and throws panic if error. 59 | func (addr Address) MustGetPublicKey() Wallet.PublicKey { 60 | pk, err := addr.GetPublicKey() 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | return pk 66 | } 67 | 68 | // IsValid returns true if the given encoded address have an correct formatted with the OpenCAP format. 69 | func (addr Address) IsValid() bool { 70 | if len(addr) < 5 { 71 | // The address must be at least a$b.c 72 | return false 73 | } 74 | 75 | split := strings.Split(string(addr), "$") 76 | if len(split) != 2 || Util.HasEmptyString(split) { 77 | return false 78 | } 79 | 80 | domain := strings.Split(split[1], ".") 81 | if len(domain) < 2 || Util.HasEmptyString(domain) { 82 | return false 83 | } 84 | 85 | return true 86 | } 87 | 88 | // GetAlias extract the existing alias of the address, everything before $. 89 | func (addr Address) GetAlias() string { 90 | return strings.SplitN(string(addr), "$", 1)[0] 91 | } 92 | 93 | // GetHost extract the existing alias of the address, everything after $. 94 | func (addr Address) GetHost() string { 95 | split := strings.Split(string(addr), "$") 96 | if len(split) != 2 { 97 | return "" 98 | } 99 | 100 | return split[1] 101 | } 102 | 103 | // LookupHost lookup the host, by SRV DNS query, based the given host from the address. 104 | func (addr Address) LookupHost() string { 105 | host := addr.GetHost() 106 | if host == "" { 107 | return "" 108 | } 109 | 110 | _, srvAddrs, err := Util.LookupSRV("opencap", "tcp", host) 111 | if err != nil || len(srvAddrs) < 1 { 112 | return "" 113 | } 114 | 115 | target := strings.TrimRight(srvAddrs[0].Target, ".") 116 | if len(target) == 0 { 117 | return "" 118 | } 119 | 120 | return target 121 | } 122 | -------------------------------------------------------------------------------- /OpenCAP/address_test.go: -------------------------------------------------------------------------------- 1 | package OpenCAP 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | ) 7 | 8 | func TestAddress_GetPublicKey(t *testing.T) { 9 | expected := Wallet.Address("nano_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc").MustGetPublicKey() 10 | 11 | pk, err := Address("nanollet-gotest$ogdolo.com").GetPublicKey() 12 | if err != nil { 13 | t.Error(err) 14 | return 15 | } 16 | 17 | if pk != expected { 18 | t.Error("invalid public-key received") 19 | return 20 | } 21 | } 22 | 23 | func TestAddress_IsValid(t *testing.T) { 24 | addrs := []Address{ 25 | Address("nanollet-gotest$ogdolo.com"), 26 | Address("name$site.com.br"), 27 | Address("name$subdomain.site.com.br"), 28 | } 29 | 30 | for _, addr := range addrs { 31 | if !addr.IsValid() { 32 | t.Errorf("returns invalid a valid address: %s", addr ) 33 | return 34 | } 35 | } 36 | } 37 | 38 | func TestAddress_IsValid_Invalid(t *testing.T) { 39 | addrs := []Address{ 40 | Address("nano_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc"), 41 | Address(""), 42 | Address("$ogdolo.com"), 43 | Address("abc$.com"), 44 | Address("abc$site."), 45 | Address("$.com"), 46 | Address("ac$$.com"), 47 | Address("ac$$."), 48 | Address("ac$....."), 49 | Address("ac$.."), 50 | Address("$b.c.d"), 51 | } 52 | 53 | for _, addr := range addrs { 54 | if addr.IsValid() { 55 | t.Errorf("return valid a invalid address: %s", addr) 56 | return 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /README-Image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenbydefault/Nanollet/478f2677574e667cb5868d9a79780635b6e4e38c/README-Image1.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nanollet](./README-Image1.png) 2 | 3 | # Nanollet 4 | 5 | Nanollet is decentralized, easy to use and secure! The wallet needs less than 20MB of disk space and doesn't need a centralized server. 6 | 7 | ---------- 8 | 9 | ### Wiki 10 | 11 | In the wiki you can find some resources about Nanollet, or any app that is supported. 12 | 13 | ---------- 14 | 15 | ### Available features: 16 | 17 | *Yes, you can send and receive payments, just like any other wallet. However, we have more features developed for Nanolet.* 18 | 19 | #### Apps: 20 | 21 | - **Nanofy:** 22 | That allows you to sign one file, or arbitrary data, using Nano. This is so easy, simple and fast to be verified, also it’s almost free. 23 | 24 | - **Nanollet 2FA**: 25 | You can only send one transaction with hold your mobile phone. If someone stole one of your keys they don’t have the ability to move your money. It’s not secure as the multi-sig, but better than a password alone. 26 | 27 | - **OpenCAP** 28 | You can send money using a OpenCAP alias, for instance send money using nanollet$ogdolo.com. You can create a alias using any provider (like [ogdolo.com](https://ogdolo.com)) and the Nanollet will discover the address and send the funds. 29 | 30 | - **NanoAlias**: 31 | You can create your own alias inside the wallet, stored on the blockchain and without any centralized server. With this alias is possible to receive money by using a simple @nanollet, rather than use xrb_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc. 32 | 33 | #### Internal: 34 | 35 | - **SeedFY:** 36 | That is the secure seed. The SeedFY can be considered as one part of your seed that combines with your password to generate the seed. Because of that, holding only the SeedFY doesn’t guarantee access to your funds in isolation. 37 | 38 | --------- 39 | 40 | ### Support the project: 41 | 42 | If you like the project you can help it gets better and better. We really need people to review the code, searching for flaws or security issues, also to improve the code in general. 43 | 44 | However, donations are accepted under **xrb_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc**, or **@nanollet** or **nanollet$ogdolo.com**. ;) 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Storage/account.go: -------------------------------------------------------------------------------- 1 | package Storage 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Block" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | "github.com/brokenbydefault/Nanollet/Numbers" 7 | ) 8 | 9 | var AccessStorage Access 10 | 11 | type Access struct { 12 | Token [32]byte 13 | Password []byte 14 | Seed Wallet.Seed 15 | } 16 | 17 | var AccountStorage Account 18 | 19 | type Account struct { 20 | SecretKey Wallet.SecretKey 21 | PublicKey Wallet.PublicKey 22 | 23 | Representative Wallet.PublicKey 24 | Frontier Block.BlockHash 25 | Balance *Numbers.RawAmount 26 | 27 | //PreComputedWork Block.Work 28 | } 29 | -------------------------------------------------------------------------------- /Storage/general.go: -------------------------------------------------------------------------------- 1 | package Storage 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Node/Peer" 5 | "github.com/brokenbydefault/Nanollet/Wallet" 6 | ) 7 | 8 | var Engine FileStorage 9 | var PersistentStorage Persistent 10 | 11 | type Persistent struct { 12 | SeedFY Wallet.SeedFY 13 | AllowedKeys []Wallet.PublicKey 14 | Quorum Peer.Quorum 15 | } 16 | 17 | func (p *Persistent) AddSeedFY(seedfy Wallet.SeedFY) { 18 | p.SeedFY = seedfy 19 | Engine.Save(p) 20 | } 21 | 22 | func (p *Persistent) AddAllowedKey(newKey Wallet.PublicKey) { 23 | for _, key := range p.AllowedKeys { 24 | if key == newKey { 25 | return 26 | } 27 | } 28 | 29 | if p.AllowedKeys == nil { 30 | p.AllowedKeys = []Wallet.PublicKey{newKey} 31 | } else { 32 | p.AllowedKeys = append(p.AllowedKeys, newKey) 33 | } 34 | 35 | Engine.Save(p) 36 | } 37 | 38 | type FileStorage interface { 39 | Save(*Persistent) 40 | Load(*Persistent) 41 | Write(name string, data []byte) (path string, err error) 42 | Read(name string) (data []byte, err error) 43 | } 44 | -------------------------------------------------------------------------------- /Storage/peers.go: -------------------------------------------------------------------------------- 1 | package Storage 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Node/Peer" 5 | "net" 6 | "math/rand" 7 | "sync" 8 | ) 9 | 10 | var PeerStorage PeersBox 11 | 12 | func init() { 13 | PeerStorage.Add(Configuration.Node.Peers...) 14 | } 15 | 16 | type PeersBox struct { 17 | list *sync.Map 18 | } 19 | 20 | func (h *PeersBox) Count() (len int) { 21 | if h == nil || h.list == nil { 22 | return 0 23 | } 24 | 25 | h.list.Range(func(_, _ interface{}) bool { 26 | len++ 27 | return true 28 | }) 29 | 30 | return len 31 | } 32 | 33 | func (h *PeersBox) CountActive() (lenActive, lenAll int) { 34 | if h == nil || h.list == nil { 35 | return 0, 0 36 | } 37 | 38 | h.list.Range(func(_, value interface{}) bool { 39 | lenAll++ 40 | 41 | item, ok := value.(*Peer.Peer) 42 | if ok && item.IsActive() && item.IsKnow() { 43 | lenActive++ 44 | } 45 | 46 | return true 47 | }) 48 | 49 | return lenActive, lenAll 50 | } 51 | 52 | func (h *PeersBox) GetAll() (items []*Peer.Peer) { 53 | if h == nil || h.list == nil { 54 | return 55 | } 56 | 57 | h.list.Range(func(key, value interface{}) bool { 58 | item, ok := value.(*Peer.Peer) 59 | if ok { 60 | items = append(items, item) 61 | } 62 | 63 | return true 64 | }) 65 | 66 | return items 67 | } 68 | 69 | func (h *PeersBox) GetRandom(n int) (items []*Peer.Peer) { 70 | if h == nil || h.list == nil { 71 | return 72 | } 73 | 74 | list := h.GetAll() 75 | 76 | l := len(list) 77 | if l == 0 { 78 | return 79 | } 80 | 81 | if n <= 0 || n > l { 82 | n = l 83 | } 84 | 85 | var random = map[int]int{} 86 | for i := 0; i < n; i++ { 87 | n := rand.Intn(l) 88 | random[n] = n 89 | } 90 | 91 | for i := range random { 92 | items = append(items, list[i]) 93 | } 94 | 95 | return 96 | } 97 | 98 | func (h *PeersBox) Get(ip net.IP) (peer *Peer.Peer, ok bool) { 99 | if h == nil || h.list == nil { 100 | return 101 | } 102 | 103 | val, ok := h.list.Load(string(ip)) 104 | if !ok { 105 | return nil, false 106 | } 107 | 108 | peer, ok = val.(*Peer.Peer) 109 | 110 | return peer, ok 111 | } 112 | 113 | func (h *PeersBox) IsAllowedIP(ip net.IP) bool { 114 | if h == nil || h.list == nil { 115 | return false 116 | } 117 | 118 | peer, ok := h.Get(ip) 119 | if !ok { 120 | return false 121 | } 122 | 123 | if !peer.IsActive() || !peer.IsKnow() { 124 | return false 125 | } 126 | 127 | return true 128 | } 129 | 130 | func (h *PeersBox) Add(peers ...*Peer.Peer) (n int) { 131 | if h == nil { 132 | return 133 | } 134 | 135 | if h.list == nil { 136 | h.list = new(sync.Map) 137 | } 138 | 139 | for _, peer := range peers { 140 | if h.Count() < 1024 { 141 | if _, old := h.list.LoadOrStore(string(peer.IP), peer); !old { 142 | n++ 143 | } 144 | } 145 | } 146 | 147 | return n 148 | } 149 | 150 | func (h *PeersBox) Remove(peers ...*Peer.Peer) { 151 | if h == nil || h.list == nil { 152 | return 153 | } 154 | 155 | for _, peer := range peers { 156 | if h.Count() <= 32 { 157 | break 158 | } 159 | 160 | h.list.Delete(string(peer.IP)) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Storage/storage.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package Storage 4 | 5 | import ( 6 | "github.com/shibukawa/configdir" 7 | "github.com/brokenbydefault/Nanollet/Wallet" 8 | "encoding/gob" 9 | "github.com/brokenbydefault/Nanollet/Util" 10 | "bytes" 11 | "path/filepath" 12 | ) 13 | 14 | func init() { 15 | Engine = &DesktopStorage{ 16 | dir: configdir.New("BrokenByDefault", Configuration.Storage.Folder).QueryFolders(configdir.Global)[0], 17 | } 18 | 19 | Engine.Load(&PersistentStorage) 20 | 21 | if len(PersistentStorage.Quorum.PublicKeys) != 0 { 22 | Configuration.Account.Quorum = PersistentStorage.Quorum 23 | } 24 | } 25 | 26 | type DesktopStorage struct { 27 | dir *configdir.Config 28 | } 29 | 30 | func (s *DesktopStorage) Save(from *Persistent) { 31 | b := new(bytes.Buffer) 32 | if err := gob.NewEncoder(b).Encode(from); err != nil { 33 | panic(err) 34 | } 35 | 36 | s.dir.WriteFile("storage.nanollet", b.Bytes()) 37 | } 38 | 39 | func (s *DesktopStorage) Load(to *Persistent) { 40 | if s.dir.Exists("storage.nanollet") { 41 | r, err := s.dir.ReadFile("storage.nanollet") 42 | if err != nil { 43 | return 44 | } 45 | 46 | if err := gob.NewDecoder(bytes.NewReader(r)).Decode(to); err != nil { 47 | panic(err) 48 | } 49 | } else if s.dir.Exists("mfa.dat") || s.dir.Exists("wallet.dat") { 50 | s.loadLegacy(to) 51 | s.Save(to) 52 | } 53 | } 54 | 55 | func (s *DesktopStorage) Write(name string, data []byte) (path string, err error) { 56 | return filepath.Join(s.dir.Path, name), s.dir.WriteFile(name, data) 57 | } 58 | 59 | func (s *DesktopStorage) Read(name string) ([]byte, error) { 60 | return s.dir.ReadFile(name) 61 | } 62 | 63 | // Backward compatibility for Nanollet 1.0 and Nanollet 2.0 64 | func (s *DesktopStorage) loadLegacy(to *Persistent) { 65 | if s.dir.Exists("mfa.dat") { 66 | if file, err := s.dir.ReadFile("mfa.dat"); err == nil { 67 | if b, ok := Util.SecureHexDecode(string(file)); ok && len(b) == 32 { 68 | to.AddAllowedKey(Wallet.NewPublicKey(b)) 69 | } 70 | } 71 | } 72 | 73 | if s.dir.Exists("wallet.dat") { 74 | if data, err := s.dir.ReadFile("wallet.dat"); err == nil { 75 | if seedfy, err := Wallet.ReadSeedFY(string(data)); err == nil { 76 | to.SeedFY = seedfy 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Storage/storage_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package Storage 4 | 5 | import ( 6 | "encoding/gob" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "bytes" 9 | "github.com/gopherjs/gopherjs/js" 10 | "github.com/Inkeliz/goco/nativestorage" 11 | ) 12 | 13 | func init() { 14 | Engine = &JSStorage{} 15 | Engine.Load(&PersistentStorage) 16 | } 17 | 18 | type JSStorage struct{} 19 | 20 | func (s *JSStorage) Save(from *Persistent) { 21 | b := new(bytes.Buffer) 22 | if err := gob.NewEncoder(b).Encode(from); err != nil { 23 | panic(err) 24 | } 25 | 26 | // Prevent save binary directly on LocalStorage and so on. 27 | bHex := Util.SecureHexEncode(b.Bytes()) 28 | 29 | switch { 30 | case js.Global.Get("NativeStorage") != js.Undefined: 31 | nativestorage.SetItem("storage.nanollet", bHex) 32 | case js.Global.Get("localStorage") != js.Undefined: 33 | js.Global.Get("localStorage").Call("setItem", "storage.nanollet", bHex) 34 | default: 35 | panic("Unsupported storage available") 36 | } 37 | } 38 | 39 | func (s *JSStorage) Load(to *Persistent) { 40 | var bHex string 41 | 42 | switch { 43 | case js.Global.Get("NativeStorage") != js.Undefined: 44 | bHex, _ = nativestorage.GetString("storage.nanollet") 45 | case js.Global.Get("localStorage") != js.Undefined: 46 | obj := js.Global.Get("localStorage").Call("getItem", "storage.nanollet") 47 | if obj != nil { 48 | bHex = obj.String() 49 | } 50 | default: 51 | panic("Unsupported storage available") 52 | } 53 | 54 | if bHex == "" { 55 | return 56 | } 57 | 58 | b, _ := Util.SecureHexDecode(bHex) 59 | 60 | if err := gob.NewDecoder(bytes.NewReader(b)).Decode(to); err != nil { 61 | panic(err) 62 | } 63 | 64 | } 65 | 66 | func (s *JSStorage) Write(name string, data []byte) (path string, err error) { 67 | // No-op 68 | return "", nil 69 | } 70 | 71 | func (s *JSStorage) Read(name string) ([]byte, error) { 72 | // No-op 73 | return nil, nil 74 | } 75 | -------------------------------------------------------------------------------- /TwoFactor/Ephemeral/x25519.go: -------------------------------------------------------------------------------- 1 | package Ephemeral 2 | 3 | import ( 4 | "golang.org/x/crypto/curve25519" 5 | "crypto/rand" 6 | "golang.org/x/crypto/blake2b" 7 | ) 8 | 9 | const ( 10 | X25519Size = 32 11 | ) 12 | 13 | type PublicKey [X25519Size]byte 14 | type SecretKey [X25519Size]byte 15 | 16 | func NewEphemeral() (sk SecretKey) { 17 | if _, err := rand.Read(sk[:]); err != nil { 18 | panic("impossible create key") 19 | } 20 | 21 | return sk 22 | } 23 | 24 | func (e SecretKey) PublicKey() (pk PublicKey) { 25 | pkb, sk := [32]byte{}, [32]byte(e) 26 | curve25519.ScalarBaseMult(&pkb, &sk) 27 | 28 | return PublicKey(pkb) 29 | } 30 | 31 | func (e SecretKey) Exchange(partner PublicKey) (key [32]byte) { 32 | exkey, pk, sk := [32]byte{}, [32]byte(partner), [32]byte(e) 33 | curve25519.ScalarMult(&exkey, &sk, &pk) 34 | 35 | return blake2b.Sum256(exkey[:]) 36 | } 37 | -------------------------------------------------------------------------------- /TwoFactor/README.md: -------------------------------------------------------------------------------- 1 | # MFA 2 | -------------------------------------------------------------------------------- /TwoFactor/envelope.go: -------------------------------------------------------------------------------- 1 | package TwoFactor 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 5 | "github.com/aead/poly1305" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "github.com/Inkeliz/blakEd25519" 8 | "bytes" 9 | "encoding/binary" 10 | "github.com/aead/chacha20poly1305" 11 | "github.com/brokenbydefault/Nanollet/Util" 12 | ) 13 | 14 | const ( 15 | EnvelopeSize = 1 + (Ephemeral.X25519Size * 2) + (blakEd25519.PublicKeySize * 2) + blakEd25519.SignatureSize + poly1305.TagSize 16 | EnvelopeSignedSize = EnvelopeSize - (blakEd25519.SignatureSize + poly1305.TagSize) 17 | ) 18 | 19 | type Version uint8 20 | 21 | type Envelope struct { 22 | Version Version 23 | Sender Ephemeral.PublicKey 24 | Receiver Ephemeral.PublicKey 25 | Capsule Capsule 26 | } 27 | 28 | type Capsule struct { 29 | Device Wallet.PublicKey 30 | Token Token 31 | Signature Wallet.Signature 32 | 33 | Tag [poly1305.TagSize]byte 34 | } 35 | 36 | func NewEnvelope(device Wallet.PublicKey, sender Ephemeral.PublicKey, receiver Ephemeral.PublicKey, token Token) Envelope { 37 | return Envelope{ 38 | Version: 1, 39 | Sender: sender, 40 | Receiver: receiver, 41 | 42 | Capsule: Capsule{ 43 | Device: device, 44 | Token: token, 45 | }, 46 | } 47 | } 48 | 49 | func (e *Envelope) Sign(device *Wallet.SecretKey) { 50 | msg, err := e.MarshalBinary() 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | e.Capsule.Signature = device.MustSign(msg[:EnvelopeSignedSize]) 56 | } 57 | 58 | func (e *Envelope) IsValidSignature(allowedDevices []Wallet.PublicKey) bool { 59 | if allowedDevices == nil { 60 | allowedDevices = append(allowedDevices, e.Capsule.Device) 61 | } 62 | 63 | msg, err := e.MarshalBinary() 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | for _, device := range allowedDevices { 69 | if e.Capsule.Device == device { 70 | return device.IsValidSignature(msg[:EnvelopeSignedSize], &e.Capsule.Signature) 71 | } 72 | } 73 | 74 | return false 75 | } 76 | 77 | func (e *Envelope) Encrypt(sender *Ephemeral.SecretKey) error { 78 | key := sender.Exchange(e.Receiver) 79 | cipher, err := chacha20poly1305.NewXCipher(key[:]) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | nonce := Util.CreateHash(24, e.Sender[:], e.Receiver[:]) 85 | msg, err := e.Capsule.MarshalBinary() 86 | if err != nil { 87 | return err 88 | } 89 | 90 | enc := cipher.Seal(msg[:0], nonce, msg[:len(msg)-poly1305.TagSize], nil) 91 | 92 | if err := binary.Read(bytes.NewReader(enc), binary.BigEndian, &e.Capsule); err != nil { 93 | return err 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func (e *Envelope) Decrypt(receiver *Ephemeral.SecretKey) error { 100 | key := receiver.Exchange(e.Sender) 101 | cipher, err := chacha20poly1305.NewXCipher(key[:]) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | nonce := Util.CreateHash(24, e.Sender[:], e.Receiver[:]) 107 | msg, err := e.Capsule.MarshalBinary() 108 | if err != nil { 109 | return err 110 | } 111 | 112 | if _, err := cipher.Open(msg[:0], nonce, msg, nil); err != nil { 113 | return err 114 | } 115 | 116 | if err := binary.Read(bytes.NewReader(msg), binary.BigEndian, &e.Capsule); err != nil { 117 | return err 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func (e *Envelope) MarshalBinary() ([]byte, error) { 124 | buf := new(bytes.Buffer) 125 | if err := binary.Write(buf, binary.BigEndian, e); err != nil { 126 | return nil, err 127 | } 128 | 129 | return buf.Bytes(), nil 130 | } 131 | 132 | func (e *Envelope) UnmarshalBinary(b []byte) error { 133 | return binary.Read(bytes.NewReader(b), binary.BigEndian, e) 134 | } 135 | 136 | func (c *Capsule) MarshalBinary() ([]byte, error) { 137 | buf := new(bytes.Buffer) 138 | if err := binary.Write(buf, binary.BigEndian, c); err != nil { 139 | return nil, err 140 | } 141 | 142 | return buf.Bytes(), nil 143 | } 144 | 145 | func (c *Capsule) UnmarshalBinary(b []byte) error { 146 | return binary.Read(bytes.NewReader(b), binary.BigEndian, c) 147 | } 148 | -------------------------------------------------------------------------------- /TwoFactor/envelope_test.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package TwoFactor 4 | 5 | import ( 6 | "testing" 7 | "bytes" 8 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 9 | "github.com/brokenbydefault/Nanollet/Wallet" 10 | ) 11 | 12 | func TestEncapsulateMFA(t *testing.T) { 13 | devicepk, devicesk, _ := Wallet.GenerateRandomKeyPair() 14 | 15 | computer := Ephemeral.NewEphemeral() 16 | smartphone := Ephemeral.NewEphemeral() 17 | 18 | seedfy, err := NewSeedFY() 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | 23 | token, err := NewToken(seedfy.String(), []byte("123456789")) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | envelope := NewEnvelope(devicepk, smartphone.PublicKey(), computer.PublicKey(), token) 29 | envelope.Sign(&devicesk) 30 | envelope.Encrypt(&smartphone) 31 | smartphoneEnvelope, _ := envelope.MarshalBinary() 32 | 33 | rEnvelope := new(Envelope) 34 | rEnvelope.UnmarshalBinary(smartphoneEnvelope) 35 | rEnvelope.Decrypt(&computer) 36 | 37 | if !bytes.Equal(rEnvelope.Capsule.Token[:], token[:]) || bytes.Equal(envelope.Capsule.Token[:], token[:]) { 38 | t.Error("encryption wrong") 39 | } 40 | 41 | if !rEnvelope.IsValidSignature(nil) { 42 | t.Error("invalid signature") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TwoFactor/network.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package TwoFactor 4 | 5 | import ( 6 | "net" 7 | "encoding/binary" 8 | "github.com/brokenbydefault/Nanollet/Wallet" 9 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 10 | ) 11 | 12 | func NewRequesterServer(sk *Ephemeral.SecretKey, allowedDevices []Wallet.PublicKey) (req Request, ch <-chan *Envelope, err error) { 13 | tcp, err := net.ListenTCP("tcp", &net.TCPAddr{IP: getIP()}) 14 | if err != nil { 15 | return req, nil, err 16 | } 17 | 18 | c := make(chan *Envelope, 1) 19 | go listen(tcp, sk, allowedDevices, c) 20 | 21 | addr := tcp.Addr().(*net.TCPAddr) 22 | 23 | return NewRequester(sk.PublicKey(), addr.IP, addr.Port), c, nil 24 | } 25 | 26 | func ReplyRequest(device *Wallet.SecretKey, token Token, request Request) error { 27 | sk := Ephemeral.NewEphemeral() 28 | 29 | envelope := NewEnvelope(device.PublicKey(), sk.PublicKey(), request.Receiver, token) 30 | envelope.Sign(device) 31 | envelope.Encrypt(&sk) 32 | 33 | tcp, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: request.IP[:], Port: int(request.Port)}) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if err := binary.Write(tcp, binary.BigEndian, &envelope); err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func listen(tcp *net.TCPListener, sk *Ephemeral.SecretKey, devices []Wallet.PublicKey, c chan *Envelope) { 46 | for { 47 | con, err := tcp.AcceptTCP() 48 | if err != nil { 49 | continue 50 | } 51 | 52 | go readEnvelope(tcp, con, sk, devices, c) 53 | } 54 | 55 | } 56 | 57 | func readEnvelope(tcp *net.TCPListener, con *net.TCPConn, sk *Ephemeral.SecretKey, devices []Wallet.PublicKey, c chan *Envelope) { 58 | envelope := Envelope{} 59 | defer con.Close() 60 | 61 | if err := binary.Read(con, binary.BigEndian, &envelope); err != nil { 62 | return 63 | } 64 | 65 | if envelope.Receiver != sk.PublicKey() { 66 | return 67 | } 68 | 69 | if err := envelope.Decrypt(sk); err != nil { 70 | return 71 | } 72 | 73 | if !envelope.IsValidSignature(devices) { 74 | return 75 | } 76 | 77 | c <- &envelope 78 | tcp.Close() 79 | } 80 | 81 | func getIP() net.IP { 82 | conn, err := net.Dial("udp", "1.1.1.1:80") 83 | if err != nil { 84 | panic(err) 85 | } 86 | defer conn.Close() 87 | 88 | localAddr := conn.LocalAddr().(*net.UDPAddr) 89 | 90 | return localAddr.IP 91 | } 92 | -------------------------------------------------------------------------------- /TwoFactor/network_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package TwoFactor 4 | 5 | import ( 6 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 7 | "net" 8 | "encoding/binary" 9 | "github.com/brokenbydefault/Nanollet/Wallet" 10 | "github.com/jaracil/goco/chrome/tcpsockets" 11 | "errors" 12 | ) 13 | 14 | func NewRequesterServer(sk *Ephemeral.SecretKey, allowedDevices []Wallet.PublicKey) (req Request, ch <-chan Envelope, err error) { 15 | // NO-OP 16 | return req, nil, errors.New("not supported") 17 | } 18 | 19 | func ReplyRequest(device *Wallet.SecretKey, token Token, request Request) error { 20 | sk := Ephemeral.NewEphemeral() 21 | 22 | envelope := NewEnvelope(device.PublicKey(), sk.PublicKey(), request.Receiver, token) 23 | envelope.Sign(device) 24 | envelope.Encrypt(&sk) 25 | 26 | tcp, err := tcpsockets.Create() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if err := tcp.Connect(net.IP(request.IP[:]).String(), int(request.Port)); err != nil { 32 | return err 33 | } 34 | 35 | if err := binary.Write(tcp, binary.BigEndian, &envelope); err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /TwoFactor/network_test.go: -------------------------------------------------------------------------------- 1 | package TwoFactor 2 | 3 | import ( 4 | "testing" 5 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 6 | "github.com/brokenbydefault/Nanollet/Wallet" 7 | "time" 8 | ) 9 | 10 | func TestNewRequesterServer(t *testing.T) { 11 | 12 | var received bool 13 | 14 | computer := Ephemeral.NewEphemeral() 15 | _, smartphone, _ := Wallet.GenerateRandomKeyPair() 16 | 17 | seedfy, _ := NewSeedFY() 18 | token, _ := NewToken(seedfy.String(), []byte("123456")) 19 | 20 | request, response, _ := NewRequesterServer(&computer, []Wallet.PublicKey{smartphone.PublicKey()}) 21 | go func() { 22 | envelope := <-response 23 | received = true 24 | 25 | if envelope.Capsule.Token != token { 26 | t.Error("token not equal") 27 | } 28 | 29 | }() 30 | 31 | if err := ReplyRequest(&smartphone, token, request); err != nil { 32 | t.Error(err) 33 | } 34 | 35 | time.Sleep(5 * time.Second) 36 | 37 | if !received { 38 | t.Error("package not received/sent") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TwoFactor/request.go: -------------------------------------------------------------------------------- 1 | package TwoFactor 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/TwoFactor/Ephemeral" 5 | "net" 6 | "github.com/skip2/go-qrcode" 7 | "image/color" 8 | "github.com/brokenbydefault/Nanollet/Util" 9 | "encoding/binary" 10 | "bytes" 11 | ) 12 | 13 | type Request struct { 14 | Version Version 15 | IP [net.IPv6len]byte 16 | Port int64 17 | Receiver Ephemeral.PublicKey 18 | } 19 | 20 | func NewRequester(pk Ephemeral.PublicKey, ip net.IP, port int) (requester Request) { 21 | return Request{ 22 | Version: 1, 23 | IP: toIPV6(ip), 24 | Port: int64(port), 25 | Receiver: pk, 26 | } 27 | } 28 | 29 | func (r *Request) QRCode(size int, color color.Color) (png []byte, err error) { 30 | qr, err := qrcode.New(r.String(), qrcode.Highest) 31 | if err != nil { 32 | panic(err) 33 | } 34 | qr.BackgroundColor = color 35 | 36 | return qr.PNG(size) 37 | } 38 | 39 | func (r *Request) MarshalBinary() ([]byte, error) { 40 | buf := new(bytes.Buffer) 41 | if err := binary.Write(buf, binary.BigEndian, r); err != nil { 42 | return nil, err 43 | } 44 | 45 | return buf.Bytes(), nil 46 | } 47 | 48 | func (r *Request) UnmarshalBinary(b []byte) (error) { 49 | if err := binary.Read(bytes.NewReader(b), binary.BigEndian, r); err != nil { 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func (r *Request) String() string { 57 | b, _ := r.MarshalBinary() 58 | 59 | return Util.SecureHexEncode(b) 60 | } 61 | 62 | func toIPV6(ip net.IP) (ipV6 [net.IPv6len]byte) { 63 | copy(ipV6[:], ip.To16()) 64 | return ipV6 65 | } 66 | -------------------------------------------------------------------------------- /TwoFactor/token.go: -------------------------------------------------------------------------------- 1 | package TwoFactor 2 | 3 | import ( 4 | "github.com/brokenbydefault/Nanollet/Wallet" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | NotDesignedUse = errors.New("seedfy not intended to be used in MFA") 10 | ) 11 | 12 | type Token [32]byte 13 | 14 | func NewSeedFY() (Wallet.SeedFY, error) { 15 | return Wallet.NewCustomFY(Wallet.V0, Wallet.MFA, 2, 10) 16 | } 17 | 18 | func NewToken(seedfy string, pass []byte) (token Token, err error) { 19 | sf, err := Wallet.ReadSeedFY(seedfy) 20 | if err != nil { 21 | return token, err 22 | } 23 | 24 | if !sf.IsValid(Wallet.V0, Wallet.MFA) { 25 | return token, NotDesignedUse 26 | } 27 | 28 | seed := sf.RecoverSeed(pass, nil) 29 | 30 | _, sk, err := seed.CreateKeyPair(Wallet.Base, 0) 31 | if err != nil { 32 | return token, err 33 | } 34 | 35 | copy(token[:], sk[:32]) 36 | 37 | return token, nil 38 | } -------------------------------------------------------------------------------- /Util/base32.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import "encoding/base32" 4 | 5 | var unsafeb32 = base32.NewEncoding("13456789abcdefghijkmnopqrstuwxyz").WithPadding(base32.NoPadding) 6 | 7 | // UnsafeBase32Encode encodes an byte in non-constant time. It's results 8 | // the Base32 string. 9 | func UnsafeBase32Encode(src []byte) string { 10 | return unsafeb32.EncodeToString(src) 11 | } 12 | 13 | // UnsafeBase32Decode decodes an string in non-constant time. It's results 14 | // the bytes of decoded Base32. 15 | func UnsafeBase32Decode(src string) ([]byte, error) { 16 | return unsafeb32.DecodeString(src) 17 | } 18 | 19 | //@TODO SecureBase32Encode (LowPriority: base32 is used with public-data) 20 | //@TODO SecureBase32Decode (LowPriority: base32 is used with public-data) 21 | -------------------------------------------------------------------------------- /Util/base32_test.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUnsafeBase32Encode(t *testing.T) { 8 | 9 | table := []struct { 10 | Message string 11 | Result string 12 | }{ 13 | {"Testing the base32", "cjkq8x5bfsmk1x5aeni86rdmensm6"}, 14 | } 15 | 16 | for _, v := range table { 17 | if UnsafeBase32Encode([]byte(v.Message)) != v.Result { 18 | t.Errorf("UnsafeBase32Encode failed") 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Util/bytes.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import "bytes" 4 | 5 | func ReverseBytes(b []byte) []byte { 6 | length := len(b) 7 | last := length - 1 8 | 9 | for i := 0; i < length/2; i++ { 10 | b[i], b[last-i] = b[last-i], b[i] 11 | } 12 | 13 | return b 14 | } 15 | 16 | func ConcatBytes(slice ...[]byte) []byte { 17 | var l int 18 | 19 | for _, b := range slice { 20 | l += len(b) 21 | } 22 | 23 | tmp := make([]byte, l) 24 | 25 | var i int 26 | for _, b := range slice { 27 | i += copy(tmp[i:], b) 28 | } 29 | 30 | return tmp 31 | 32 | } 33 | 34 | func IsEmpty(slice []byte) bool { 35 | return bytes.Equal(slice, make([]byte, len(slice))) 36 | } -------------------------------------------------------------------------------- /Util/dns.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/url" 7 | "strconv" 8 | "encoding/json" 9 | "strings" 10 | "errors" 11 | "time" 12 | ) 13 | 14 | type rr int 15 | 16 | func (rr rr) String() string { 17 | return strconv.Itoa(int(rr)) 18 | } 19 | 20 | var ( 21 | a rr = 1 22 | aaaa rr = 28 23 | any rr = 255 24 | srv rr = 33 25 | ) 26 | 27 | type response struct { 28 | Answer []struct { 29 | Type rr `json:"type"` 30 | Data string `json:"data"` 31 | } `json:"Answer"` 32 | } 33 | 34 | func LookupIP(name string) (ips [][]byte, err error) { 35 | ips = make([][]byte, 0) 36 | 37 | ipv4, err := lookup(a, name) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | for _, ip := range ipv4.Answer { 43 | ips = append(ips, net.ParseIP(ip.Data)) 44 | } 45 | 46 | ipv6, err := lookup(aaaa, name) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | for _, ip := range ipv6.Answer { 52 | ips = append(ips, net.ParseIP(ip.Data)) 53 | } 54 | 55 | return ips, nil 56 | } 57 | 58 | func LookupSRV(service, proto, name string) (cname string, addrs []*net.SRV, err error) { 59 | raw, err := lookup(srv, "_"+service+"._"+proto+"."+name) 60 | 61 | for _, answer := range raw.Answer { 62 | sa := strings.Split(answer.Data, " ") 63 | if len(sa) != 4 { 64 | return cname, addrs, errors.New("invalid response") 65 | } 66 | 67 | addrs = append(addrs, &net.SRV{ 68 | Target: sa[3], 69 | Port: mustInt16(sa[2]), 70 | Priority: mustInt16(sa[1]), 71 | Weight: mustInt16(sa[0]), 72 | }) 73 | } 74 | 75 | return cname, addrs, err 76 | } 77 | 78 | func lookup(rr rr, host string) (*response, error) { 79 | client := &http.Client{Timeout: 3 * time.Second} 80 | 81 | raw, err := client.Get("https://1.1.1.1/dns-query?" + url.Values{ 82 | "name": []string{host}, 83 | "type": []string{rr.String()}, 84 | "ct": []string{"application/dns-json"}, 85 | }.Encode()) 86 | 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | defer raw.Body.Close() 92 | 93 | resp := new(response) 94 | if err := json.NewDecoder(raw.Body).Decode(resp); err != nil { 95 | return nil, err 96 | } 97 | 98 | return resp, nil 99 | } 100 | 101 | func mustInt16(s string) uint16 { 102 | i, _ := strconv.Atoi(s) 103 | return uint16(i) 104 | } -------------------------------------------------------------------------------- /Util/error.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | // CheckError will return error if one of the input are non-nil. 4 | func CheckError(errs []error) error { 5 | for _, err := range errs { 6 | if err != nil { 7 | return err 8 | } 9 | } 10 | return nil 11 | } 12 | 13 | // ExistEmpty will return true if one of the input is empty. 14 | func ExistEmpty(s ...string) bool { 15 | for _, ss := range s { 16 | if ss == "" { 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /Util/file.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package Util 4 | 5 | import "io/ioutil" 6 | 7 | func FileToString(filepath string) string { 8 | file, err := ioutil.ReadFile(filepath) 9 | if err != nil { 10 | panic(err) 11 | } 12 | 13 | return string(file) 14 | } 15 | -------------------------------------------------------------------------------- /Util/gc.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package Util 4 | 5 | import "runtime/debug" 6 | 7 | func FreeMemory(){ 8 | debug.FreeOSMemory() 9 | } 10 | -------------------------------------------------------------------------------- /Util/gc_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package Util 4 | 5 | func FreeMemory() { 6 | // no-op 7 | } 8 | -------------------------------------------------------------------------------- /Util/hash.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "golang.org/x/crypto/blake2b" 5 | ) 6 | 7 | // CreateHash returns the Blake2b hash of message with specified size. 8 | func CreateHash(size int, messagebyte ...[]byte) []byte { 9 | return CreateKeyedHash(size, nil, messagebyte...) 10 | } 11 | 12 | // CreateHash returns the Blake2b hash of message with specified size using a secret-key. 13 | func CreateKeyedHash(size int, key []byte, messagebyte ...[]byte) []byte { 14 | hash, err := blake2b.New(size, key) 15 | if err != nil { 16 | panic("hashing failed") 17 | } 18 | 19 | for _, mb := range messagebyte { 20 | hash.Write(mb) 21 | } 22 | 23 | return hash.Sum(nil) 24 | } 25 | 26 | // CreateKeyedXOFHash returns the Blake2b-XOF hash of message using an secret-key, with specified size. 27 | func CreateKeyedXOFHash(size uint32, key []byte, messagebyte ...[]byte) []byte { 28 | hash, err := blake2b.NewXOF(size, key) 29 | if err != nil { 30 | panic("hashing failed") 31 | } 32 | 33 | for _, mb := range messagebyte { 34 | hash.Write(mb) 35 | } 36 | 37 | r := make([]byte, size) 38 | 39 | _, err = hash.Read(r) 40 | if err != nil { 41 | panic("hashing failed") 42 | } 43 | 44 | return r 45 | } 46 | -------------------------------------------------------------------------------- /Util/hex.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | ) 7 | 8 | // UnsafeBase32Encode encodes an byte in non-constant time. It's results 9 | // the Base32 string. 10 | func UnsafeHexEncode(b []byte) string { 11 | return strings.ToUpper(hex.EncodeToString(b)) 12 | } 13 | 14 | // UnsafeHexDecode decodes an byte without table-lookup. It's results 15 | // the byte-array of decoded hex. 16 | func UnsafeHexDecode(s string) ([]byte, error) { 17 | return hex.DecodeString(s) 18 | } 19 | 20 | // UnsafeHexMustDecode is a wrapper from UnsafeHexDecode, which panic if error. 21 | func UnsafeHexMustDecode(s string) []byte { 22 | b, err := UnsafeHexDecode(s) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | return b 28 | } 29 | 30 | // SecureHexEncode decodes an byte without table-lookup. It's results 31 | // the hexadecimal string representation. 32 | func SecureHexEncode(b []byte) string { 33 | length := len(b) 34 | r := make([]byte, length*2) 35 | 36 | for i := 0; i < length; i++ { 37 | 38 | // Splits a single byte into two bytes, resulting in 4 bits for each byte. 39 | p1, p2 := int(b[i]>>4), int(b[i]&0x0F) 40 | 41 | b1 := 48 + p1 42 | b2 := 48 + p2 43 | 44 | // The ASCII have a gap between "9" and "A", we need to jump it if the byte is between 10 and 15. 45 | // if(byte > 9) { b += 7 } 46 | b1 += ((9 - p1) >> 8) & 7 47 | b2 += ((9 - p2) >> 8) & 7 48 | 49 | copy(r[i*2:], []byte{byte(b1), byte(b2)}) 50 | } 51 | 52 | return string(r) 53 | } 54 | 55 | // SecureHexDecode decodes an byte without table-lookup. It's results 56 | // the byte-array of decoded hex. 57 | func SecureHexDecode(s string) (r []byte, ok bool) { 58 | length := len(s) 59 | 60 | if (length & 1) == 1 { 61 | return nil, false 62 | } 63 | 64 | r = make([]byte, length/2) 65 | e, st := 0, -1 66 | 67 | for i := 0; i < length; i++ { 68 | 69 | // "0" - (48) = 0 70 | // "A" - 48 - (7) = 10 71 | // "a" - 48 - 7 - (32) = 10 72 | b := int(s[i] - 48) 73 | b -= ((9 - b) >> 8) & 7 74 | b -= ((41 - b) >> 8) & 32 75 | 76 | // if (b < 0 | b > 15) { err = 1 } 77 | e |= ((b >> 8) | (15-b)>>8) & 1 78 | 79 | st += (i + 1) & 1 80 | r[st] <<= 4 81 | r[st] |= byte(b) 82 | 83 | } 84 | 85 | return r, e == 0 86 | } 87 | 88 | // SecureHexMustDecode is a wrapper from SecureHexDecode, which panic if error. 89 | func SecureHexMustDecode(s string) []byte { 90 | b, ok := SecureHexDecode(s) 91 | if !ok { 92 | panic("decode error") 93 | } 94 | 95 | return b 96 | } 97 | -------------------------------------------------------------------------------- /Util/hex_test.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | ) 8 | 9 | func TestUnsafeHexEncode(t *testing.T) { 10 | 11 | table := []struct { 12 | Message string 13 | Result string 14 | }{ 15 | {"Testing the hex", "54657374696E672074686520686578"}, 16 | {"Testing the hex?", "54657374696E6720746865206865783F"}, 17 | } 18 | 19 | for _, v := range table { 20 | 21 | if r := UnsafeHexEncode([]byte(v.Message)); r != v.Result { 22 | t.Errorf("UnsafeHexEncode failed given %s expecting %s", r, v.Result) 23 | } 24 | } 25 | 26 | } 27 | 28 | func TestSecureHexEncode(t *testing.T) { 29 | 30 | table := []struct { 31 | Message string 32 | Result string 33 | }{ 34 | {"Testing the hex", "54657374696E672074686520686578"}, 35 | {"Testing the hex?", "54657374696E6720746865206865783F"}, 36 | } 37 | 38 | for _, v := range table { 39 | 40 | if r := SecureHexEncode([]byte(v.Message)); r != v.Result { 41 | t.Errorf("SecureHexEncode failed given %s expecting %s", r, v.Result) 42 | } 43 | } 44 | 45 | random := make([]byte, 12) 46 | rand.Read(random) 47 | 48 | hex := SecureHexEncode(random) 49 | decoded, ok := SecureHexDecode(hex) 50 | 51 | if !ok || !bytes.Equal(decoded, random) { 52 | t.Error("error") 53 | } 54 | 55 | } 56 | 57 | func TestSecureHexDecode(t *testing.T) { 58 | 59 | table := []struct { 60 | Message string 61 | Result string 62 | }{ 63 | {"54657374696E672074686520686578", "Testing the hex"}, 64 | {"54657374696E6720746865206865783F", "Testing the hex?"}, 65 | {"6E3F", "n?"}, 66 | {"6e3f", "n?"}, 67 | } 68 | 69 | for _, v := range table { 70 | 71 | r, ok := SecureHexDecode(v.Message) 72 | 73 | if string(r) != v.Result { 74 | t.Error(r, v.Result, ok) 75 | } 76 | 77 | if !ok { 78 | t.Error(r, v.Result, ok) 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Util/number.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | // StringIsNumeric return false if the input is not between 0 to 9. 4 | func StringIsNumeric(s string) (ok bool) { 5 | var err int32 6 | 7 | for _, b := range s { 8 | err |= (((b - 48) >> 8) | (57-b)>>8) & 1 9 | } 10 | 11 | return err == 0 12 | } 13 | 14 | type Order int8 15 | 16 | const ( 17 | LittleEndian Order = iota 18 | BigEndian 19 | ) 20 | 21 | func BytesToUint(b []byte, order Order) uint64 { 22 | if order == LittleEndian { 23 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 24 | } else { 25 | return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 26 | } 27 | } 28 | 29 | func UintToBytes(i uint64, order Order) []byte { 30 | if order == LittleEndian { 31 | return []byte{byte(i), byte(i >> 8), byte(i >> 16), byte(i >> 24), byte(i >> 32), byte(i >> 40), byte(i >> 48), byte(i >> 56)} 32 | } else { 33 | return []byte{byte(i >> 56), byte(i >> 48), byte(i >> 40), byte(i >> 32), byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Util/string.go: -------------------------------------------------------------------------------- 1 | package Util 2 | 3 | func HasEmptyString(strs []string) bool { 4 | for _, str := range strs { 5 | if str == "" { 6 | return true 7 | } 8 | } 9 | 10 | return false 11 | } 12 | 13 | func IsEmptyString(str string) bool { 14 | return str == "" 15 | } 16 | -------------------------------------------------------------------------------- /Wallet/address.go: -------------------------------------------------------------------------------- 1 | package Wallet 2 | 3 | import ( 4 | "errors" 5 | "github.com/brokenbydefault/Nanollet/Util" 6 | "strings" 7 | "github.com/skip2/go-qrcode" 8 | "image/color" 9 | ) 10 | 11 | const ADDRESS_PREFIX = "nano" 12 | 13 | var ALLOWED_PREFIX = [...]string{"xrb", "nano"} 14 | 15 | type Address string 16 | 17 | // CreateAddress creates the encoded address using the public-key. It returns 18 | // the address (with identifier, public-key and checksum) as string, encoded 19 | // with base32. 20 | func (pk PublicKey) CreateAddress() (addr Address) { 21 | addr = ADDRESS_PREFIX 22 | addr += "_" 23 | addr += Address(Util.UnsafeBase32Encode(append([]byte{0, 0, 0}, pk[:]...))[4:]) 24 | addr += Address(Util.UnsafeBase32Encode(pk.Checksum())) 25 | 26 | return addr 27 | } 28 | 29 | // QRCode creates a QRCode from the address, following https://developers.nano.org/docs/uri-qr-code-standard/. 30 | func (addr Address) QRCode(size int, color color.Color) (png []byte, err error) { 31 | qr, err := qrcode.New("xrb:" + string(addr), qrcode.Highest) 32 | if err != nil { 33 | panic(err) 34 | } 35 | qr.BackgroundColor = color 36 | 37 | return qr.PNG(size) 38 | } 39 | 40 | // GetPublicKey gets the Ed25519 public-key from the encoded address, 41 | // returning the public-key. It's return an non-nil error if something bad happens. 42 | func (addr Address) GetPublicKey() (pk PublicKey, err error) { 43 | if addr.IsCorrectlyFormatted() == false { 44 | return pk, errors.New("invalid address") 45 | } 46 | 47 | addr = "1111" + addr.RemovePrefix() 48 | 49 | pkb, err := Util.UnsafeBase32Decode(string(addr[:56])) 50 | if err != nil { 51 | return pk, err 52 | } 53 | 54 | return NewPublicKey(pkb[3:]), nil 55 | } 56 | 57 | // MustGetPublicKey is a wrapper from GePublicKey, which removes the error response and throws panic if error. 58 | func (addr Address) MustGetPublicKey() PublicKey { 59 | pk, err := addr.GetPublicKey() 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | return pk 65 | } 66 | 67 | // GetChecksum extract the existing checksum of the address, returns the checksum 68 | // as byte-array. 69 | func (addr Address) GetChecksum() ([]byte, error) { 70 | if addr.IsCorrectlyFormatted() == false { 71 | return nil, errors.New("invalid address") 72 | } 73 | 74 | addr = "1111" + addr.RemovePrefix() 75 | 76 | checksum, err := Util.UnsafeBase32Decode(string(addr[len(addr)-8:])) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return checksum, nil 82 | } 83 | 84 | // GetPrefix extract the existing prefix of the address, everything before 85 | // the first underscore. 86 | func (addr Address) GetPrefix() string { 87 | return strings.SplitN(string(addr), "_", 2)[0] 88 | } 89 | 90 | // UpdateAddress modify the prefix of the address returning the address with new 91 | // prefix identifier. (Can be used if "xrb_" be replaced by "nano_" in future) 92 | func (addr Address) UpdatePrefix() Address { 93 | return Address(ADDRESS_PREFIX+"_") + addr.RemovePrefix() 94 | } 95 | 96 | // RemovePrefix remove the prefix of the address, returns an address 97 | // without the prefix. 98 | func (addr Address) RemovePrefix() Address { 99 | split := strings.SplitN(string(addr), "_", 2) 100 | if len(split) != 2 { 101 | return addr 102 | } 103 | 104 | return Address(split[1]) 105 | } 106 | 107 | // IsValid returns true if the given encoded address have an correct formatting and 108 | // also the checksum is correct. 109 | func (addr Address) IsValid() bool { 110 | pk, err := addr.GetPublicKey() 111 | if err != nil { 112 | return false 113 | } 114 | 115 | checksum, err := addr.GetChecksum() 116 | if err != nil { 117 | return false 118 | } 119 | 120 | return pk.IsValidChecksum(checksum) 121 | } 122 | 123 | // IsCorrectlyFormatted returns true if the given encoded address have an correct 124 | // format. It return true if had an valid prefix and length, but checksum doesn't matter. 125 | func (addr Address) IsCorrectlyFormatted() bool { 126 | if len(addr) == 0 || len(addr.RemovePrefix()) != 60 { 127 | return false 128 | } 129 | 130 | for _, chr := range addr { 131 | if !strings.Contains("_13456789abcdefghijkmnopqrstuwxyz", string(chr)) { 132 | return false 133 | } 134 | } 135 | 136 | prefix := addr.GetPrefix() 137 | for _, allowed := range ALLOWED_PREFIX { 138 | if prefix == allowed { 139 | return true 140 | } 141 | } 142 | 143 | return false 144 | } 145 | -------------------------------------------------------------------------------- /Wallet/address_test.go: -------------------------------------------------------------------------------- 1 | package Wallet 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAddress_IsValid(t *testing.T) { 8 | addr := Address("xrb_1kmp7wp1muz4ct3pzyceorxjm7pzeuszqrm86ky8ysytmfjxe9awfe8ngc8i") 9 | if !addr.IsValid() { 10 | t.Error("valid address is invalid") 11 | } 12 | } 13 | 14 | func TestAddress_IsValid_Invalid(t *testing.T) { 15 | addrs := []Address{ 16 | Address("_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc"), 17 | Address("3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsafc"), 18 | Address("nano_3tz9pdfskx934ce36cf6h17uspp4hzsamr5hk7u1wd6em1gfsnb618hfsqfc"), 19 | Address(""), 20 | Address("nano$ogdolo.com"), 21 | Address("nano_$ogdolo.com"), 22 | } 23 | 24 | for _, addr := range addrs { 25 | if addr.IsValid() { 26 | t.Errorf("return valid a invalid address: %s", addr) 27 | return 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Wallet/deterministic.go: -------------------------------------------------------------------------------- 1 | package Wallet 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "errors" 7 | "github.com/brokenbydefault/Nanollet/Util" 8 | "golang.org/x/crypto/argon2" 9 | "runtime" 10 | "bytes" 11 | ) 12 | 13 | type Type uint8 14 | 15 | const ( 16 | Nanollet Type = iota 17 | MFA 18 | ) 19 | 20 | var SupportedTypes = [...]Type{Nanollet, MFA} 21 | 22 | type Version uint8 23 | 24 | const ( 25 | V0 Version = iota 26 | ) 27 | 28 | var SupportedVersions = [...]Version{V0} 29 | 30 | type Currency uint32 31 | 32 | const ( 33 | Base Currency = iota 34 | Nano 35 | Banano 36 | ) 37 | 38 | var ErrImpossibleDecode = errors.New("impossible to decode the seed") 39 | 40 | type SeedFY struct { 41 | Version uint8 42 | Type uint8 43 | Time uint8 44 | Memory uint8 45 | Thread uint8 46 | Salt [32]byte 47 | } 48 | 49 | // NewSeedFY generate the SeedFY, which is the random salt and the default computational cost parameters 50 | // used in the Argon2id derivation in combination with the password. 51 | func NewSeedFY(v Version, t Type) (sf SeedFY, err error) { 52 | sf = SeedFY{ 53 | Version: uint8(v), 54 | Type: uint8(t), 55 | Time: 15, 56 | Memory: 21, 57 | Thread: uint8(runtime.NumCPU()), 58 | } 59 | 60 | _, err = rand.Read(sf.Salt[:]) 61 | return 62 | } 63 | 64 | func NewCustomFY(v Version, t Type, time uint8, memory uint8) (sf SeedFY, err error) { 65 | sf = SeedFY{ 66 | Version: uint8(v), 67 | Type: uint8(t), 68 | Time: time, 69 | Memory: memory, 70 | 71 | Thread: uint8(runtime.NumCPU()), 72 | } 73 | 74 | _, err = rand.Read(sf.Salt[:]) 75 | 76 | if !sf.IsValid(v, t) { 77 | err = ErrImpossibleDecode 78 | } 79 | 80 | return 81 | } 82 | 83 | //ReadSeedFY act like to NewSeedFY, however it creates the struct based on the given hex-encoded SeedFY. 84 | func ReadSeedFY(s string) (sf SeedFY, err error) { 85 | sb, ok := Util.SecureHexDecode(s) 86 | if !ok { 87 | return sf, ErrImpossibleDecode 88 | } 89 | 90 | if len(sb) < 6 { 91 | return sf, ErrImpossibleDecode 92 | } 93 | 94 | if err := binary.Read(bytes.NewReader(sb), binary.BigEndian, &sf); err != nil { 95 | return sf, err 96 | } 97 | 98 | sf.Memory &= 0x1F 99 | 100 | if !sf.IsValid(Version(sf.Version), Type(sf.Type)) { 101 | return sf, ErrImpossibleDecode 102 | } 103 | 104 | return 105 | } 106 | 107 | // String will return the hexadecimal representation of the given SeedFY. 108 | func (sf *SeedFY) String() string { 109 | b := new(bytes.Buffer) 110 | binary.Write(b, binary.BigEndian, sf) 111 | 112 | return Util.SecureHexEncode(b.Bytes()) 113 | } 114 | 115 | // IsValid will return false if the SeedFY is not supported or don't have 116 | // enough seed-length 117 | func (sf *SeedFY) IsValid(v Version, t Type) bool { 118 | var r bool 119 | 120 | for _, val := range SupportedVersions { 121 | if uint8(val) == sf.Version && uint8(val) == uint8(v) { 122 | r = true 123 | } 124 | } 125 | if !r { 126 | return r 127 | } 128 | 129 | for _, val := range SupportedTypes { 130 | if uint8(val) == sf.Type && uint8(val) == uint8(t) { 131 | r = true 132 | } 133 | } 134 | if !r { 135 | return r 136 | } 137 | 138 | if len(sf.Salt) != 32 || Util.IsEmpty(sf.Salt[:]) { 139 | return false 140 | } 141 | 142 | if sf.Memory > 31 { 143 | return false 144 | } 145 | 146 | if sf.Type != uint8(t) { 147 | return false 148 | } 149 | 150 | return true 151 | } 152 | 153 | type Seed []byte 154 | 155 | // RecoverSeedFromSeedFY returns the Seed based on given password and hex-encoded SeedFY. 156 | // SEEDFY: [version][type][time][memory][thread][salt] 157 | func (sf *SeedFY) RecoverSeed(password []byte, additionalData []byte) Seed { 158 | defer Util.FreeMemory() 159 | 160 | salt := Util.CreateKeyedHash(32, sf.Salt[:], additionalData) 161 | return argon2.IDKey(password, salt, uint32(sf.Time), uint32(1<