├── .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 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{range .Menu}}
32 | {{.Title | ToTitle}}
33 |
34 | {{range .Pages}}
35 | {{. | ToTitle}}
37 | {{end}}
38 |
39 | {{end}}
40 |
41 |
42 |
43 | {{range .App}}
44 | {{.}}
45 | {{end}}
46 |
47 |
48 |
50 |
54 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/GUI/Front/html/1_account.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LICENSE
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 |
46 |
47 |
48 |
49 |
50 |
51 |
COPY YOUR SEEDFY
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 |
ENTER YOUR SEEDFY
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 |
99 |
100 |
110 |
111 |
112 |
113 |
114 |
CHOOSE YOUR ADDRESS
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/GUI/Front/html/2_nanollet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 |
23 |
24 | YOUR ADDRESS
25 |
26 |
27 | YOUR QR CODE
28 |
29 |
30 |
31 |
32 |
33 |
34 |
47 |
48 |
49 |
50 |
51 |
BALANCE
52 |
55 |
56 |
TRANSACTIONS
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 |
FILE
7 |
Drop the file here
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
FILE
19 |
Drop the file here
20 |
21 |
22 |
ADDRESS
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/GUI/Front/html/4_nanoalias.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
--------------------------------------------------------------------------------
/GUI/Front/html/6_settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WALLET SEEDFY
6 |
7 |
8 | ADDRESS PRIVATE-KEY
9 |
10 |
11 |
12 |
13 |
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 | 
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<