├── .gitignore ├── LICENSE ├── README.md ├── lnode-src ├── duckcoin.go ├── go.mod ├── go.sum └── lchain.go ├── saved-term-experiment-for-constant-duckcoin-initial-bytes.txt ├── snode-src ├── duckcoin.go ├── go.mod └── go.sum └── util ├── address.go ├── crypto.go ├── crypto_test.go ├── emoji.go ├── go.mod ├── go.sum ├── misc.go └── storage.go /.gitignore: -------------------------------------------------------------------------------- 1 | /*/duckcoin.exe 2 | /*/duckcoin 3 | /duckcoin 4 | /duckcoin.exe 5 | *key* 6 | *.json 7 | .idea 8 | /icoin 9 | .DS_Store 10 | /lnode-src/chaindata-bolt.db 11 | /lnode-src/test 12 | **/duckchain.db 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ishan Goel 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Duckcoin - Crypto mining for everyone 2 | 3 | Duckcoin is a cryptocurrency that will have zero fees, be decentralized and easy to mine locally on your regular computer, no fancy setup needed. Duckcoin was initially made just to learn how cryptocurrencies work, but it's blossoming into a real currency. Remembering Duckcoin's origins, the code is written to be easy to understand. Feel free to make issues asking questions about the code! 4 | 5 | **Duckcoin is still under unstable development: If your miner seems to be getting a lot of errors, check if there's been any commit with "HARD FORK" in the commit message** 6 | 7 | ## Status, Plans and Differences. 8 | - [ ] Decentralized 9 | - [x] Easy to mine 10 | - [x] Zero fees 11 | - [x] Emoji addresses: 🦆🍩🍅💕🤧👔🦚🚥😐📬🎽💰🌹➰👒🩴😑🆓🚔👧 12 | 13 | Duckcoin is a cryptocurrency that uses PoW, a common consensus algorithm that is also used by Bitcoin, ETH Classic and Dogecoin, to give miners rewards. However, it is currently operating on a centralized server-client model, which will certainly get me canceled if I release this right now. 14 | 15 | Duckcoin works differently from other cryptocurrencies. They need miners to have their own public IPs, or to port forward certain ports so that miners can contact each other in a peer-to-peer way. Because of how widespread Carrier Grade NAT is, it is usually rarely possible to host a node at home, without a custom rig. 16 | 17 | As of now, Duckcoin has a central server that miners can send blocks to. Miners don't validate anything, a central server validates the chain. The miner gets a certain reward after the block is accepted. To make a transaction, you must mine your own block with that transaction. 18 | 19 | This makes it possible to mine Duckcoin on a regular computer. However, this means Duckcoin is _centralized_, which is unacceptable for anything called a cryptocurrency (besides, I don't want to deal with people trying to attack the central server). 20 | 21 | So, to decentralize Duckcoin, we'll have two kinds of miners: one for the people who do have custom rigs, and one for people mining at home. We'll call the first type "_Lnodes_" (Large Nodes) and the second "_Snodes_" (Small Nodes). Lnodes are like multiple instances of the current central server. 22 | 23 | The current plan is to have Lnodes set up [direct connections](https://tailscale.com/blog/how-nat-traversal-works/) between Snodes, allowing Snodes to act as complete participants in the network. Potential problems with this could be that a malicious Lnode could connect an Snode to a fake Snode, but this _should_ be fine since running nodes requires compute power. 24 | 25 | Another goal of Duckcoin is to have zero fees by essentially having the payer do the PoW for their own transaction as an Snode. The miner's reward is then what that Snode's PoW is worth. 26 | 27 | [//]: # (Lnodes receive blocks from Snodes, validate them, and _chunk them together into a field of a larger block we'll call the "Lblock"_. This Lblock gets added to another chain, which will function how usual cryptocurrency blockchains do, with PoW for consensus and rewards (unlike Snodes, which don't use PoW for consensus), P2P networking, maybe Merkle trees, etc. Another goal of Duckcoin is to have zero fees. This is done by "giving" the miner the PoW the reward that the Snodes normally get. Snodes still mine their own transactions and the "fees" to the Lnode is the reward that the Snode's PoW is worth.) 28 | 29 | 30 | -------------------------------------------------------------------------------- /lnode-src/duckcoin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/gorilla/mux" 14 | "github.com/jwalton/gchalk" 15 | "github.com/quackduck/duckcoin/util" 16 | ) 17 | 18 | var ( 19 | DefaultPort = "4213" // D U C => 4 21 3 20 | 21 | NewestBlock *util.Sblock 22 | 23 | ReCalcInterval = 100 // Recalculate difficulty every 100 sblocks 24 | Past100Durations = make([]time.Duration, 0, ReCalcInterval) 25 | NewestBlockTime = time.Now() 26 | TargetDuration = time.Second * 30 27 | 28 | // Difficulty is the number of hashes needed for an sblock to be valid on average. 29 | // See util.GetTarget for more information. 30 | Difficulty uint64 31 | 32 | Pubkey string 33 | Privkey string 34 | Addr util.Address 35 | 36 | PubkeyFile = getConfigDir() + "/pubkey.pem" 37 | PrivkeyFile = getConfigDir() + "/privkey.pem" 38 | LnodesFile = getConfigDir() + "/lnodes.txt" 39 | Lnodes []string 40 | ) 41 | 42 | func main() { 43 | var err error 44 | Pubkey, Privkey, Addr, err = util.LoadKeysAndAddr(PubkeyFile, PrivkeyFile) 45 | if err != nil { 46 | fmt.Println("Making you a fresh, new key pair and address!") 47 | Pubkey, Privkey, err = util.MakeKeyPair() 48 | if err != nil { 49 | fmt.Println("error", err) 50 | return 51 | } 52 | err = util.SaveKeyPair(Pubkey, Privkey, PubkeyFile, PrivkeyFile) 53 | if err != nil { 54 | fmt.Println("error", err) 55 | return 56 | } 57 | gchalk.BrightYellow("Your keys have been saved to " + PubkeyFile + "(pubkey) and " + PrivkeyFile + " (privkey)") 58 | gchalk.BrightRed("Do not tell anyone what's inside " + PrivkeyFile) 59 | } 60 | 61 | gchalk.BrightYellow("Loaded keys from " + PubkeyFile + " and " + PrivkeyFile) 62 | fmt.Println("Mining to this address:", gchalk.BrightBlue(Addr.Emoji)) 63 | Lnodes, err = parseLnodesFile(LnodesFile) 64 | if err != nil { 65 | fmt.Println("error", err) 66 | return 67 | } 68 | 69 | util.DBInit() // opens duckchain.db in current working dir 70 | 71 | Past100Durations = append(Past100Durations, TargetDuration) 72 | Difficulty = 1048576 * 6 // EDITT!!!! TODO 73 | 74 | if err := setup(); err != nil { 75 | fmt.Println("error: ", err) 76 | return 77 | } 78 | 79 | m := mux.NewRouter() 80 | m.HandleFunc("/sblocks", handleGetSblocks).Methods("GET") 81 | m.HandleFunc("/balances", getHandleGetBalancesFunc(func(address util.Address) string { 82 | return address.Emoji 83 | })).Methods("GET") 84 | m.HandleFunc("/balances-text", getHandleGetBalancesFunc(func(address util.Address) string { 85 | return address.Text 86 | })).Methods("GET") 87 | m.HandleFunc("/sblocks/new", handleWriteSblock).Methods("POST") 88 | m.HandleFunc("/sblocks/newest", handleGetNewest).Methods("GET") 89 | 90 | m.HandleFunc("/lblocks/new", handleWriteLblock).Methods("POST") 91 | 92 | m.HandleFunc("/difficulty", func(w http.ResponseWriter, r *http.Request) { 93 | _, err := w.Write([]byte(strconv.FormatInt(int64(Difficulty), 10))) 94 | if err != nil { 95 | fmt.Println("error: ", err) 96 | return 97 | } 98 | }).Methods("GET") 99 | 100 | port := os.Getenv("PORT") 101 | if port == "" { 102 | port = DefaultPort 103 | } 104 | fmt.Println("HTTP Server Listening on port", port) 105 | s := &http.Server{ 106 | Addr: "0.0.0.0:" + port, 107 | Handler: m, 108 | ReadTimeout: 10 * time.Minute, 109 | WriteTimeout: 10 * time.Minute, 110 | MaxHeaderBytes: 1 << 20, 111 | } 112 | if err := s.ListenAndServe(); err != nil { 113 | fmt.Println(err) 114 | return 115 | } 116 | } 117 | 118 | func setup() error { 119 | var err error 120 | NewestBlock, err = util.GetNewestSblock() 121 | if err != nil { 122 | return err 123 | } 124 | return nil 125 | } 126 | 127 | func addBlockToChain(b *util.Sblock) { 128 | fmt.Println("Adding a block with hash:", b.Hash+". This one came in", time.Since(NewestBlockTime), "after the previous block.") 129 | 130 | if len(Past100Durations) < ReCalcInterval { 131 | Past100Durations = append(Past100Durations, time.Since(NewestBlockTime)) 132 | } else { // trigger a recalculation of the difficulty 133 | reCalcDifficulty() 134 | Past100Durations = make([]time.Duration, 0, ReCalcInterval) 135 | } 136 | util.WriteBlockDB(b) 137 | NewestBlockTime = time.Now() 138 | NewestBlock = b 139 | } 140 | 141 | func handleGetSblocks(w http.ResponseWriter, r *http.Request) { 142 | w.Header().Set("Connection", "keep-alive") 143 | 144 | var i uint64 145 | blockData := make([]byte, 0, 500*1024) 146 | for i = 0; i <= NewestBlock.Index; i++ { 147 | b, err := util.GetSblockByIndex(i) 148 | if err != nil { 149 | http.Error(w, err.Error(), http.StatusInternalServerError) 150 | return 151 | } 152 | blockData = append(blockData, util.ToJSON(b)...) 153 | if i%1000 == 0 { 154 | w.Write(blockData) 155 | blockData = make([]byte, 0, 500*1024) 156 | } 157 | } 158 | w.Write(blockData) 159 | } 160 | 161 | func getHandleGetBalancesFunc(addrToString func(address util.Address) string) func(http.ResponseWriter, *http.Request) { 162 | return func(w http.ResponseWriter, _ *http.Request) { 163 | balances := util.GetAllBalancesFloats() 164 | balancesJSONMap := make(map[string]float64, len(balances)) 165 | for addr, balance := range balances { 166 | balancesJSONMap[addrToString(addr)] = balance 167 | } 168 | bytes, err := json.MarshalIndent(balancesJSONMap, "", " ") 169 | if err != nil { 170 | http.Error(w, err.Error(), http.StatusInternalServerError) 171 | return 172 | } 173 | _, err = w.Write(bytes) 174 | if err != nil { 175 | fmt.Println("error: ", err) 176 | return 177 | } 178 | } 179 | } 180 | 181 | func handleGetNewest(w http.ResponseWriter, _ *http.Request) { 182 | bytes, err := json.MarshalIndent(NewestBlock, "", " ") 183 | if err != nil { 184 | http.Error(w, err.Error(), http.StatusInternalServerError) 185 | return 186 | } 187 | _, err = w.Write(bytes) 188 | if err != nil { 189 | fmt.Println("error: ", err) 190 | return 191 | } 192 | } 193 | 194 | func handleWriteSblock(w http.ResponseWriter, r *http.Request) { 195 | defer r.Body.Close() 196 | 197 | w.Header().Set("Content-Type", "application/json") 198 | b := new(util.Sblock) 199 | 200 | decoder := json.NewDecoder(io.LimitReader(r.Body, 1e6)) 201 | if err := decoder.Decode(b); err != nil { 202 | //fmt.Println("Bad JSON request. This may be caused by a block that is too big (more than 1mb) but these are usually with malicious intent. " + err.Error()) 203 | respondWithJSON(w, http.StatusBadRequest, "Bad JSON request. This may be caused by a block that is too big (more than 1mb) but these are usually with malicious intent. "+err.Error()) 204 | return 205 | } 206 | err := util.IsValid(b, NewestBlock, util.GetTarget(Difficulty)) 207 | if err == nil { 208 | addBlockToChain(b) 209 | respondWithJSON(w, http.StatusCreated, "Sblock accepted.") 210 | return 211 | } 212 | respondWithJSON(w, http.StatusBadRequest, "Invalid block. "+err.Error()) 213 | fmt.Println("Rejected a block") 214 | 215 | //go func() { 216 | // err := sendToLnodes(&util.Lblock{ // just a test for now, no mining is happening 217 | // Index: 23, 218 | // Timestamp: b.Timestamp, 219 | // Data: "Yo bro, I just got a new block! " + b.Hash, 220 | // Hash: b.Hash, 221 | // PrevHash: b.PrevHash, 222 | // Solution: b.Solution, 223 | // Solver: b.Solver, 224 | // Sblocks: []*util.Sblock{b, b}, 225 | // }) 226 | // if err != nil { 227 | // fmt.Println("error:", err) 228 | // } 229 | //}() 230 | } 231 | 232 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 233 | w.Header().Set("Content-Type", "application/json") 234 | response, err := json.MarshalIndent(payload, "", " ") 235 | if err != nil { 236 | w.WriteHeader(http.StatusInternalServerError) 237 | _, err = w.Write([]byte("HTTP 500: Internal Server Error: " + err.Error())) 238 | if err != nil { 239 | fmt.Println("error: ", err) 240 | return 241 | } 242 | return 243 | } 244 | w.WriteHeader(code) 245 | _, err = w.Write(response) 246 | if err != nil { 247 | fmt.Println("error: ", err) 248 | return 249 | } 250 | } 251 | 252 | func reCalcDifficulty() { 253 | var avg uint64 = 0 254 | for _, v := range Past100Durations { 255 | avg += uint64(v) 256 | } 257 | avg /= uint64(len(Past100Durations)) 258 | fmt.Printf("The average duration between blocks for the past %d blocks was: %s\n", ReCalcInterval, time.Duration(avg).String()) 259 | // TargetDuration/avgDur is the scale factor for what the current target is 260 | // if avgDur is higher than TargetDuration, then the Difficulty will be made lower 261 | // if avgDur is lower, then the Difficulty will be made higher 262 | Difficulty *= uint64(TargetDuration) / avg 263 | fmt.Println("\nRecalculated difficulty. It is now", Difficulty) 264 | } 265 | 266 | func getConfigDir() string { 267 | home, err := os.UserHomeDir() 268 | if err != nil { 269 | fmt.Println("error:", err) 270 | os.Exit(0) 271 | } 272 | err = os.MkdirAll(home+"/.config/duckcoin", 0700) 273 | if err != nil { 274 | fmt.Println(err) 275 | os.Exit(0) 276 | } 277 | return home + "/.config/duckcoin" 278 | } 279 | 280 | func parseLnodesFile(f string) ([]string, error) { 281 | data, err := os.ReadFile(f) 282 | if err != nil { 283 | return nil, err 284 | } 285 | s := strings.Split(string(data), "\n") 286 | newArr := make([]string, 0, len(s)) 287 | for i := range s { 288 | if strings.TrimSpace(s[i]) != "" { 289 | newArr = append(newArr, strings.TrimRight(strings.TrimSpace(s[i]), "/")) 290 | } 291 | } 292 | return newArr, nil 293 | } 294 | -------------------------------------------------------------------------------- /lnode-src/go.mod: -------------------------------------------------------------------------------- 1 | module duckcoin 2 | 3 | go 1.18 4 | 5 | replace github.com/quackduck/duckcoin/util => ./../util 6 | 7 | require ( 8 | github.com/gorilla/mux v1.8.0 9 | github.com/jwalton/gchalk v1.3.0 10 | github.com/quackduck/duckcoin/util v0.0.0-20221029200842-2a7ddacac189 11 | ) 12 | 13 | require ( 14 | github.com/jwalton/go-supportscolor v1.1.0 // indirect 15 | go.etcd.io/bbolt v1.3.6 // indirect 16 | golang.org/x/sys v0.1.0 // indirect 17 | golang.org/x/term v0.1.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /lnode-src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | github.com/jwalton/gchalk v1.3.0 h1:uTfAaNexN8r0I9bioRTksuT8VGjrPs9YIXR1PQbtX/Q= 4 | github.com/jwalton/gchalk v1.3.0/go.mod h1:ytRlj60R9f7r53IAElbpq4lVuPOPNg2J4tJcCxtFqr8= 5 | github.com/jwalton/go-supportscolor v1.1.0 h1:HsXFJdMPjRUAx8cIW6g30hVSFYaxh9yRQwEWgkAR7lQ= 6 | github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= 7 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 8 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 9 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 10 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 11 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= 16 | golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 18 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 20 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 21 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= 22 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 23 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 24 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 25 | -------------------------------------------------------------------------------- /lnode-src/lchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/quackduck/duckcoin/util" 7 | "io" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | func sendToLnodes(s *util.Lblock) error { 13 | for _, lnode := range Lnodes { 14 | _, err := http.Post(lnode+"/lblocks/new", "application/json", strings.NewReader(util.ToJSON(s))) 15 | if err != nil { 16 | return err 17 | } 18 | } 19 | return nil 20 | } 21 | 22 | func handleWriteLblock(w http.ResponseWriter, r *http.Request) { 23 | defer r.Body.Close() 24 | 25 | w.Header().Set("Content-Type", "application/json") 26 | b := new(util.Lblock) 27 | 28 | decoder := json.NewDecoder(io.LimitReader(r.Body, 1e6)) 29 | if err := decoder.Decode(b); err != nil { 30 | //fmt.Println("Bad JSON request. This may be caused by a block that is too big (more than 1mb) but these are usually with malicious intent. " + err.Error()) 31 | respondWithJSON(w, http.StatusBadRequest, "Bad JSON request. This may be caused by a block that is too big (more than 1mb) but these are usually with malicious intent. "+err.Error()) 32 | return 33 | } 34 | 35 | fmt.Println("Received a new lblock from " + r.RemoteAddr + ":\n" + util.ToJSON(b)) 36 | 37 | //if err := isValid(b, NewestBlock); err == nil { 38 | // addBlockToChain(b) 39 | //} else { 40 | // respondWithJSON(w, http.StatusBadRequest, "Invalid block. "+err.Error()) 41 | // fmt.Println("Rejected a block") 42 | // return 43 | //} 44 | respondWithJSON(w, http.StatusCreated, "Lblock accepted.") 45 | } 46 | -------------------------------------------------------------------------------- /saved-term-experiment-for-constant-duckcoin-initial-bytes.txt: -------------------------------------------------------------------------------- 1 | [~/downloads] echo DUCK | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | aces ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 2 | DUCK 3 | [~/downloads] echo DUCK | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | hexdump 4 | 0000000 0d 40 8a 5 | 0000003 6 | [~/downloads] echo DUCK | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 7 | DUCK 8 | [~/downloads] echo DUCK | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 | base64 -d 9 | @�⏎ 10 | [~/downloads] echo DUCK | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 | base64 -d | hexdump 11 | 0000000 0d 40 8a 12 | 0000003 13 | [~/downloads] echo DUCKCOIN | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 | base64 -d | hexdump 14 | 0000000 0d 40 8a 08 e2 0d 15 | 0000006 16 | [~/downloads] echo DUCKCOIN | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 | base64 -d 17 | [~/downloads] echo DUCKCOIN | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 18 | DUCKCOIN 19 | [~/downloads] echo DUCKCOINduckcoin | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 20 | DUCKCOINduckcoin 21 | [~/downloads] echo DUCKCOINduckcoin | aces -d ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ | base64 | base64 -d | hexdump 22 | 0000000 0d 40 8a 08 e2 0d 76 e7 24 72 88 a7 23 | 000000c -------------------------------------------------------------------------------- /snode-src/duckcoin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "math" 8 | "math/big" 9 | "net/http" 10 | "os" 11 | "os/user" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/jwalton/gchalk" 18 | "github.com/quackduck/duckcoin/util" 19 | ) 20 | 21 | var ( 22 | URL = "http://devzat.hackclub.com:8080" 23 | 24 | Username = getUsername() 25 | PubkeyFile = getConfigDir() + "/pubkey.pem" 26 | PrivkeyFile = getConfigDir() + "/privkey.pem" 27 | URLFile = getConfigDir() + "/url.txt" 28 | 29 | // Difficulty is the on average number of hashes needed for a block to be valid. 30 | // 31 | // See util.GetTarget for more information on the relationship between targets and Difficulty. 32 | Difficulty uint64 33 | 34 | Pubkey string 35 | Privkey string 36 | Addr util.Address 37 | 38 | ArgReceiver util.Address // command line arguments 39 | ArgMessage string 40 | ArgAmount uint64 41 | ArgNumOfBlocks uint64 = math.MaxUint64 42 | ArgThreads uint = 1 43 | 44 | HelpMsg = `Duckcoin - quack money 45 | 46 | Usage: duckcoin [-h/--help] 47 | duckcoin [] [-s/--hide-user] [-t/--to ] 48 | [-a/--amount ] [-m/--message ] 49 | 50 | Duckcoin mines for the keypair in ~/.config/duckcoin. If the --message option is 51 | used in a block not containing a transaction, the block data field is set to it. 52 | Otherwise, the transaction's data field is used. 53 | 54 | Examples: 55 | duckcoin # mine blocks continuously 56 | duckcoin 4 -m "Mining cause I'm bored" # mine 4 blocks with a message 57 | duckcoin -s 4 # hide your username 58 | duckcoin 2 -t -a 7 -m "Mine 2 blocks sending 7 ducks each" 59 | duckcoin 1 -t nSvl+K7RauJ5IagU+ID/slhDoR+435+NSLHOXzFBRmo= -a 3.259 -m 60 | "send 3.259 ducks to Ishan Goel" 61 | 62 | For more info go to https://github.com/quackduck/duckcoin` 63 | ) 64 | 65 | // TODO: consider sending blocks in a really efficient binary way (like BTC and probably literally every other crypto, we already have a format for the DB) 66 | 67 | func main() { 68 | var err error 69 | parseArgs() 70 | Pubkey, Privkey, Addr, err = util.LoadKeysAndAddr(PubkeyFile, PrivkeyFile) 71 | if err != nil { 72 | fmt.Println("Making you a fresh, new key pair and address!") 73 | Pubkey, Privkey, err = util.MakeKeyPair() 74 | if err != nil { 75 | fmt.Println(err) 76 | return 77 | } 78 | err = util.SaveKeyPair(Pubkey, Privkey, PubkeyFile, PrivkeyFile) 79 | if err != nil { 80 | fmt.Println(err) 81 | return 82 | } 83 | gchalk.BrightYellow("Your keys have been saved to " + PubkeyFile + "(pubkey) and " + PrivkeyFile + " (privkey)") 84 | gchalk.BrightRed("Do not tell anyone what's inside " + PrivkeyFile) 85 | } 86 | gchalk.BrightYellow("Loaded keys from " + PubkeyFile + " and " + PrivkeyFile) 87 | fmt.Println("Mining to this address:", gchalk.BrightBlue(Addr.Emoji)) 88 | 89 | err = loadDifficultyAndURL() 90 | if err != nil { 91 | fmt.Println(err) 92 | return 93 | } 94 | blockMsg := "" 95 | if Username == "" { 96 | blockMsg = "" 97 | } else { 98 | blockMsg = Username 99 | } 100 | if ArgAmount == 0 && ArgMessage != "" { // non tx block, user supplied message 101 | blockMsg = ArgMessage 102 | } 103 | 104 | mine(ArgNumOfBlocks, ArgAmount, ArgReceiver, blockMsg, ArgMessage) 105 | } 106 | 107 | // mine mines numOfBlocks blocks, with the block's data field set to blockData and the 108 | // transaction's arbitrary data field set to txData (in this case if amount is not 0) 109 | // It also takes in the receiver's address and amount to send in each block, used if amount is not 0 110 | func mine(numOfBlocks, amount uint64, receiver util.Address, blockData, txData string) { 111 | var i uint64 112 | var b util.Sblock 113 | 114 | for ; i < numOfBlocks; i++ { 115 | doneChan := make(chan interface{}, 1) 116 | blockChan := make(chan util.Sblock, ArgThreads) 117 | r, err := http.Get(URL + "/sblocks/newest") 118 | if err != nil { 119 | fmt.Println(err) 120 | return 121 | } 122 | _ = json.NewDecoder(r.Body).Decode(&b) 123 | _ = r.Body.Close() 124 | go func() { 125 | blockChan <- b 126 | makeSblock( 127 | blockChan, Privkey, blockData, Addr, 128 | util.Transaction{ 129 | Data: txData, 130 | Sender: Addr, 131 | Receiver: receiver, 132 | Amount: amount, 133 | PubKey: Pubkey, 134 | Signature: "", // Signature filled in by the makeBlock function 135 | }) 136 | close(doneChan) 137 | }() 138 | 139 | currBlock := b 140 | Monitor: 141 | for { 142 | select { 143 | case <-doneChan: 144 | break Monitor 145 | default: 146 | r, err := http.Get(URL + "/sblocks/newest") 147 | if err != nil { 148 | fmt.Println(err) 149 | return 150 | } 151 | _ = json.NewDecoder(r.Body).Decode(&currBlock) 152 | _ = r.Body.Close() 153 | //fmt.Println("Newest block:", currBlock.Index) 154 | if currBlock.Index != b.Index { // a new block? 155 | fmt.Println(gchalk.RGB(255, 165, 0)("Gotta restart, someone else got block " + strconv.Itoa(int(currBlock.Index)))) 156 | b = currBlock 157 | for j := uint(0); j <= ArgThreads; j++ { // slightly hacky way to notify all threads to restart 158 | blockChan <- currBlock 159 | } 160 | } 161 | time.Sleep(time.Second / 2) 162 | } 163 | } 164 | } 165 | } 166 | 167 | // makeSblock creates one new block by accepting a block sent on blockChan as the latest block, 168 | // and restarting mining in case a new block is sent on blockChan. 169 | // It takes in the user's private key to be used in signing tx, the transaction, if tx.Amount is not 0. 170 | // It also takes in the arbitrary data to be included in the block and the user's Addr (solver). 171 | // 172 | // makeSblock also fills in the transaction's Signature field and the block's Hash field 173 | func makeSblock(blockChan chan util.Sblock, privkey string, data string, solver util.Address, tx util.Transaction) { 174 | err := loadDifficultyAndURL() 175 | if err != nil { 176 | fmt.Println("error: ", err) 177 | } 178 | fmt.Println(gchalk.BrightYellow(fmt.Sprint("Current difficulty: ", Difficulty))) 179 | target := util.GetTarget(Difficulty) 180 | oldBlock := <-blockChan 181 | stopChan := make(chan interface{}) 182 | wg := new(sync.WaitGroup) 183 | 184 | for n := 1; uint(n) <= ArgThreads; n++ { 185 | wg.Add(1) 186 | 187 | go mineThreadWorker(wg, oldBlock, target, n, stopChan, blockChan, privkey, data, solver, tx) 188 | time.Sleep(time.Millisecond * 100) // also helps each thread have a unique timestamp 189 | } 190 | wg.Wait() 191 | } 192 | 193 | func mineThreadWorker(wg *sync.WaitGroup, oldBlock util.Sblock, target *big.Int, threadNum int, stop chan interface{}, 194 | blockChan chan util.Sblock, privkey string, data string, solver util.Address, tx util.Transaction) { 195 | defer wg.Done() 196 | 197 | lastHashrate := new(float64) 198 | *lastHashrate = 0 199 | lastTime := new(time.Time) 200 | *lastTime = time.Now() 201 | 202 | newBlock := new(util.Sblock) 203 | 204 | Restart: 205 | t := time.Now() 206 | newBlock.Timestamp = uint64(t.UnixNano() / 1000 / 1000) 207 | newBlock.Index = oldBlock.Index + 1 208 | newBlock.Data = data 209 | newBlock.PrevHash = oldBlock.Hash 210 | newBlock.Solver = solver 211 | newBlock.Tx = tx 212 | 213 | if newBlock.Tx.Amount == 0 { 214 | newBlock.Tx.Data = "" 215 | newBlock.Tx.Sender = util.Address{} 216 | newBlock.Tx.Receiver = util.Address{} 217 | newBlock.Tx.PubKey = "" 218 | newBlock.Tx.Signature = "" 219 | } 220 | 221 | blockDataPreimage := newBlock.PreimageWOSolution() 222 | Mine: 223 | for i := uint64(0); ; i++ { // stuff in this loop needs to be super optimized 224 | select { 225 | case b := <-blockChan: 226 | if oldBlock != b { 227 | oldBlock = b 228 | goto Restart 229 | } 230 | case <-stop: 231 | return 232 | default: 233 | newBlock.Solution = i 234 | if i&(1<<19-1) == 0 && i != 0 { // optimize to check every 131072*2 iterations (bitwise ops are faster) 235 | //fmt.Println(lastTime, lastHashrate, threadNum, i) 236 | statusUpdate(lastTime, lastHashrate, threadNum, i) 237 | //fmt.Println(lastTime, lastHashrate, threadNum, i) 238 | } 239 | if !util.IsHashValidBytes( 240 | util.DoubleShasumBytes(append(blockDataPreimage, strconv.FormatUint(newBlock.Solution, 10)...)), 241 | target) { 242 | continue 243 | } else { 244 | close(stop) 245 | fmt.Println("\nSblock made! It took", time.Since(t).Round(time.Second/100)) 246 | //fmt.Printf("%x", util.DoubleShasumBytes(append(blockDataPreimage, strconv.FormatUint(newBlock.Solution, 10)...))) 247 | newBlock.Hash = newBlock.CalculateHash() 248 | if newBlock.Tx.Amount != 0 { 249 | signature, err := util.MakeSignature(privkey, newBlock.Hash) 250 | if err != nil { 251 | fmt.Println(err) 252 | return 253 | } 254 | newBlock.Tx.Signature = signature 255 | } 256 | fmt.Println(gchalk.BrightYellow(util.ToJSON(newBlock))) 257 | if sendBlock(newBlock) != nil { 258 | return 259 | } 260 | break Mine 261 | } 262 | } 263 | } 264 | } 265 | 266 | func statusUpdate(lastTime *time.Time, lastHashrate *float64, threadNum int, i uint64) { 267 | var arrow string 268 | curr := 1 << 19 / time.Since(*lastTime).Seconds() / 1000.0 // iterations since last time / time since last time / 1000 = kHashes 269 | *lastTime = time.Now() 270 | if *lastHashrate-curr > 50 { 271 | arrow = gchalk.RGB(255, 165, 0)("↓") 272 | *lastHashrate = curr 273 | } else if curr-(*lastHashrate) > 50 { 274 | arrow = gchalk.BrightCyan("↑") 275 | *lastHashrate = curr 276 | } else { 277 | arrow = " " 278 | } 279 | fmt.Printf("%d: %s Rate: %s kH/s, Checked: %s\n", threadNum, arrow, gchalk.BrightYellow(fmt.Sprintf("%d", int(math.Round(curr)))), gchalk.BrightGreen(fmt.Sprint(i))) 280 | } 281 | 282 | func sendBlock(newBlock *util.Sblock) error { 283 | r, err := http.Post(URL+"/sblocks/new", "application/json", strings.NewReader(util.ToJSON(newBlock))) 284 | if err != nil { 285 | return err 286 | } 287 | fmt.Println("Sent block to server") 288 | resp, ierr := io.ReadAll(r.Body) 289 | if ierr != nil { 290 | return err 291 | } 292 | fmt.Println("Server returned", gchalk.BrightGreen(string(resp)), "\n") 293 | r.Body.Close() 294 | return nil 295 | } 296 | 297 | // loadDifficultyAndURL loads the server URL from the config file, and then loads the difficulty by contacting that server. 298 | func loadDifficultyAndURL() error { 299 | data, err := os.ReadFile(URLFile) 300 | if err != nil { 301 | _ = os.WriteFile(URLFile, []byte(URL), 0644) 302 | return nil 303 | } 304 | URL = strings.TrimRight(strings.TrimSpace(string(data)), "/") 305 | r, err := http.Get(URL + "/difficulty") 306 | if err != nil { 307 | return err 308 | } 309 | defer r.Body.Close() 310 | 311 | b, err := io.ReadAll(r.Body) 312 | if err != nil { 313 | return err 314 | } 315 | difficultyInt64, err := strconv.ParseInt(string(b), 10, 64) 316 | if err != nil { 317 | return err 318 | } 319 | Difficulty = uint64(difficultyInt64) 320 | return nil 321 | } 322 | 323 | func parseArgs() { 324 | if ok, i := util.ArgsHaveOption("threads", "N"); ok { 325 | if len(os.Args) < i+2 { 326 | fmt.Println("Too few arguments to --threads: need an amount to send") 327 | os.Exit(1) 328 | } 329 | n, err := strconv.ParseInt(os.Args[i+1], 10, 64) 330 | if err != nil { 331 | fmt.Println(err) 332 | os.Exit(1) 333 | } 334 | ArgThreads = uint(n) 335 | } 336 | if ok, _ := util.ArgsHaveOption("help", "h"); ok { 337 | fmt.Println(HelpMsg) 338 | os.Exit(0) 339 | } 340 | if ok, i := util.ArgsHaveOption("config", "c"); ok { 341 | if len(os.Args) >= i+1 { // we want the next item to be available too 342 | fmt.Println("Too few arguments to --config: need a directory") 343 | os.Exit(0) 344 | } 345 | PubkeyFile = os.Args[i+1] + "/pubkey.pem" 346 | PrivkeyFile = os.Args[i+1] + "/privkey.pem" 347 | URLFile = os.Args[i+1] + "/url.txt" 348 | } 349 | if ok, i := util.ArgsHaveOption("to", "t"); ok { 350 | if len(os.Args) < i+2 { 351 | fmt.Println("Too few arguments to --to: need an address (emoji or text)") 352 | os.Exit(1) 353 | } 354 | var err error 355 | ArgReceiver, err = util.EmojiOrTextToAddress(os.Args[i+1]) 356 | if err != nil { 357 | fmt.Println("error: could not parse address: " + err.Error()) 358 | } 359 | if err = ArgReceiver.IsValid(); err != nil { 360 | fmt.Println("error: invalid receiver address, check if you mistyped it: " + err.Error()) 361 | os.Exit(1) 362 | } 363 | } 364 | if ok, _ := util.ArgsHaveOption("hide-user", "s"); ok { 365 | Username = "" 366 | } 367 | if ok, i := util.ArgsHaveOption("message", "m"); ok { 368 | if len(os.Args) < i+2 { 369 | fmt.Println("Too few arguments to --message: need a message") 370 | os.Exit(1) 371 | } 372 | ArgMessage = os.Args[i+1] 373 | } 374 | if ok, i := util.ArgsHaveOption("amount", "a"); ok { 375 | if len(os.Args) < i+2 { 376 | fmt.Println("Too few arguments to --amount: need an amount to send") 377 | os.Exit(1) 378 | } 379 | ducks, err := strconv.ParseFloat(os.Args[i+1], 64) 380 | if err != nil { 381 | fmt.Println(err) 382 | os.Exit(1) 383 | } 384 | if ducks < 0 { 385 | fmt.Println("Can't send negative money, mate. Good try tho.") 386 | os.Exit(1) 387 | } 388 | ArgAmount = uint64(ducks * float64(util.MicroquacksPerDuck)) 389 | } 390 | if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") { 391 | i, err := strconv.ParseInt(os.Args[1], 10, 64) 392 | if err == nil { 393 | ArgNumOfBlocks = uint64(i) // can cause overflow with negative amounts 394 | } else { 395 | fmt.Println(err) 396 | os.Exit(1) 397 | } 398 | } 399 | } 400 | 401 | func getConfigDir() string { 402 | home, err := os.UserHomeDir() 403 | if err != nil { 404 | fmt.Println("error:", err) 405 | os.Exit(0) 406 | } 407 | err = os.MkdirAll(home+"/.config/duckcoin", 0700) 408 | if err != nil { 409 | fmt.Println(err) 410 | os.Exit(0) 411 | } 412 | return home + "/.config/duckcoin" 413 | } 414 | 415 | func getUsername() string { 416 | u, err := user.Current() 417 | if err != nil { 418 | fmt.Println("error:", err) 419 | os.Exit(0) 420 | } 421 | return u.Username 422 | } 423 | -------------------------------------------------------------------------------- /snode-src/go.mod: -------------------------------------------------------------------------------- 1 | module duckcoin 2 | 3 | go 1.18 4 | 5 | replace github.com/quackduck/duckcoin/util => ./../util 6 | 7 | require ( 8 | github.com/jwalton/gchalk v1.3.0 9 | github.com/quackduck/duckcoin/util v0.0.0-20221029200842-2a7ddacac189 10 | ) 11 | 12 | require ( 13 | github.com/jwalton/go-supportscolor v1.1.0 // indirect 14 | go.etcd.io/bbolt v1.3.6 // indirect 15 | golang.org/x/sys v0.1.0 // indirect 16 | golang.org/x/term v0.1.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /snode-src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/jwalton/gchalk v1.3.0 h1:uTfAaNexN8r0I9bioRTksuT8VGjrPs9YIXR1PQbtX/Q= 2 | github.com/jwalton/gchalk v1.3.0/go.mod h1:ytRlj60R9f7r53IAElbpq4lVuPOPNg2J4tJcCxtFqr8= 3 | github.com/jwalton/go-supportscolor v1.1.0 h1:HsXFJdMPjRUAx8cIW6g30hVSFYaxh9yRQwEWgkAR7lQ= 4 | github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= 5 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 6 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 7 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 8 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 9 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 10 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 12 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= 14 | golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 16 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 19 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= 20 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 21 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 22 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 23 | -------------------------------------------------------------------------------- /util/address.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | "strings" 10 | ) 11 | 12 | type Address struct { 13 | Emoji string `json:",omitempty"` 14 | Text string `json:",omitempty"` 15 | bytes [addrBytesLen]byte 16 | } 17 | 18 | // TODO: implement the custom json marshaller interfaces so that addr.bytes is automatically populated 19 | 20 | var ( 21 | emojiCoder = encoding{set: emoji, dataLen: addrBytesLen} 22 | textCoder = encoding{set: []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), dataLen: addrBytesLen} 23 | ) 24 | 25 | const ( 26 | addrBytesLen = 24 27 | checksumLen = 4 28 | textAddressPrefixChar = 'Q' 29 | emojiAddressPrefixChar = '🦆' 30 | //versionChar = '0' 31 | ) 32 | 33 | func (a *Address) UnmarshalJSON(bytes []byte) error { 34 | type address Address // prevent infinite unmarshal loop 35 | addr := address{} 36 | err := json.Unmarshal(bytes, &addr) 37 | if err != nil { 38 | return err 39 | } 40 | //fmt.Println(addr, []rune(addr.Emoji), len([]rune(addr.Emoji))) 41 | if len(addr.Emoji) > 0 || len(addr.Text) > 0 { 42 | emojiBytes, err := EmojiToBytes(addr.Emoji) 43 | if err != nil { 44 | return err 45 | } 46 | textBytes, err := TextToBytes(addr.Text) 47 | if err != nil { 48 | return err 49 | } 50 | if textBytes != emojiBytes { 51 | return errors.New("inconsistent address: decoding of emoji is not the same as the decoding of text") 52 | } 53 | addr.bytes = textBytes 54 | } else { 55 | var b [addrBytesLen]byte 56 | addr.bytes = b 57 | } 58 | //fmt.Println(addr, Address(addr)) 59 | *a = Address(addr) 60 | return nil 61 | } 62 | 63 | // KeyToAddress derives a Duckcoin Address from a Duckcoin Public Key 64 | func KeyToAddress(key string) Address { 65 | return BytesToAddress(sliceToAddrBytes(addChecksum( 66 | DoubleShasumBytes([]byte(key))[:20], // Truncation is fine. SHA256 is designed to be comparable to a random oracle and the US Government itself is okay with it: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf page 32 (section 7) 67 | ))) 68 | } 69 | 70 | func EmojiOrTextToAddress(addr string) (Address, error) { 71 | if addr[0] == byte(textAddressPrefixChar) { 72 | return TextToAddress(addr) 73 | } else { 74 | return EmojiToAddress(addr) 75 | } 76 | } 77 | 78 | func EmojiToBytes(emoji string) ([addrBytesLen]byte, error) { 79 | var result [addrBytesLen]byte 80 | slice, err := emojiCoder.Decode(string([]rune(emoji)[1:])) 81 | if err != nil { 82 | return result, err 83 | } 84 | copy(result[:], slice) 85 | return result, nil 86 | } 87 | 88 | func EmojiToAddress(emoji string) (Address, error) { 89 | bytes, err := EmojiToBytes(emoji) 90 | if err != nil { 91 | return Address{}, err 92 | } 93 | return BytesToAddress(bytes), nil 94 | } 95 | 96 | func TextToBytes(text string) ([addrBytesLen]byte, error) { 97 | var result [addrBytesLen]byte 98 | slice, err := textCoder.Decode(string([]rune(text)[1:])) // remove first char: the prefix 99 | if err != nil { 100 | return result, err 101 | } 102 | copy(result[:], slice) 103 | return result, nil 104 | } 105 | 106 | func TextToAddress(text string) (Address, error) { 107 | bytes, err := TextToBytes(text) 108 | if err != nil { 109 | return Address{}, err 110 | } 111 | return BytesToAddress(bytes), nil 112 | } 113 | 114 | func BytesToAddress(b [addrBytesLen]byte) Address { 115 | addr := Address{ 116 | bytes: b, 117 | } 118 | addr.Text = string(textAddressPrefixChar) + textCoder.Encode(addr.bytes[:]) // len(base64(20 + 4 bytes)) + len("q" + versionChar) = 24 * 4/3 + 2 = 34 len addrs 119 | addr.Emoji = string(emojiAddressPrefixChar) + emojiCoder.Encode(addr.bytes[:]) 120 | return addr 121 | } 122 | 123 | func sliceToAddrBytes(addrSlice []byte) [addrBytesLen]byte { 124 | var arr [addrBytesLen]byte 125 | copy(arr[:], addrSlice) 126 | return arr 127 | } 128 | 129 | func addChecksum(data []byte) []byte { 130 | dataCopy := make([]byte, len(data), cap(data)) 131 | copy(dataCopy, data) // don't modify original data 132 | 133 | hash := DoubleShasumBytes(data) 134 | return append(dataCopy, hash[:checksumLen]...) 135 | } 136 | 137 | func verifyChecksum(data []byte) bool { 138 | if len(data) < checksumLen { // == checksumLen is fine 139 | return false 140 | } 141 | b := string(data) == string(addChecksum(data[:len(data)-checksumLen])) 142 | return b // hack to compare byte slices by byte values 143 | } 144 | 145 | // IsValid verifies the checksum built into addresses, and checks the address format 146 | func (a *Address) IsValid() error { 147 | fromEmoji, err := EmojiToAddress(a.Emoji) 148 | if err != nil { 149 | return err 150 | } 151 | fromText, err := TextToAddress(a.Text) 152 | if err != nil { 153 | return err 154 | } 155 | fromBytes := BytesToAddress(a.bytes) 156 | 157 | if !(fromBytes == fromText && fromText == fromEmoji) { 158 | return errors.New("invalid address: inconsistent formats: " + fmt.Sprint(a)) 159 | } 160 | 161 | if !verifyChecksum(fromText.bytes[:]) { 162 | return errors.New("invalid address: checksum verification failed: " + fmt.Sprint(a)) 163 | } 164 | return nil 165 | } 166 | 167 | type encoding struct { 168 | set []rune 169 | // dataLen is the length of the byte data used. In duckcoin, this is always 24. 170 | dataLen int 171 | } 172 | 173 | func (e *encoding) Encode(data []byte) string { 174 | convertedBase := toBase(new(big.Int).SetBytes(data), "", e.set) 175 | // repeat emoji[0] is to normalize result length. 0 because that char has zero value in the set 176 | return strings.Repeat(string(e.set[0]), e.EncodedLen()-len([]rune(convertedBase))) + convertedBase 177 | } 178 | 179 | func (e *encoding) Decode(data string) ([]byte, error) { 180 | if len([]rune(data)) != e.EncodedLen() { 181 | return nil, errors.New("could not decode: invalid length of data: " + data) 182 | } 183 | num, err := fromBase(data, e.set) 184 | if err != nil { 185 | return nil, err 186 | } 187 | return num.FillBytes(make([]byte, e.dataLen)), nil 188 | } 189 | 190 | func (e *encoding) EncodedLen() int { 191 | return int(math.Ceil( 192 | float64(e.dataLen) * math.Log2(256) / math.Log2(float64(len(e.set))), 193 | )) 194 | } 195 | 196 | //func encodeEmojiAddress(addr [addrBytesLen]byte) string { 197 | // return emojiCoder.Encode(addr[:]) 198 | // //convertedBase := toBase(new(big.Int).SetBytes(addr[:]), "", emoji) 199 | // //// repeat emoji[0] is to normalize result length. 0 because that char has zero value in the set 200 | // //return strings.Repeat(string(emoji[0]), emojiLen-len([]rune(convertedBase))) + convertedBase 201 | //} 202 | 203 | func toBase(num *big.Int, buf string, set []rune) string { 204 | base := int64(len(set)) 205 | div, rem := new(big.Int), new(big.Int) 206 | div.QuoRem(num, big.NewInt(base), rem) 207 | if div.Cmp(big.NewInt(0)) != 0 { 208 | buf += toBase(div, buf, set) 209 | } 210 | return buf + string(set[rem.Uint64()]) 211 | } 212 | 213 | //func decodeEmojiAddress(emojiAddr string) ([addrBytesLen]byte, error) { 214 | // var result [addrBytesLen]byte 215 | // 216 | // slice, err := emojiCoder.Decode(string([]rune(emojiAddr)[1:])) // remove prefix 217 | // if err != nil { 218 | // return result, err 219 | // } 220 | // copy(result[:], slice) 221 | // return result, nil 222 | //} 223 | 224 | func fromBase(enc string, set []rune) (*big.Int, error) { 225 | result := new(big.Int) 226 | setlen := len(set) 227 | encRune := []rune(enc) 228 | numOfDigits := len(encRune) 229 | for i := 0; i < numOfDigits; i++ { 230 | mult := new(big.Int).Exp( // setlen ^ numOfDigits-i-1 = the "place value" 231 | big.NewInt(int64(setlen)), 232 | big.NewInt(int64(numOfDigits-i-1)), 233 | nil, 234 | ) 235 | idx := findRuneIndex(encRune[i], set) 236 | if idx == -1 { 237 | return nil, errors.New("could not decode " + enc + ": rune " + string(encRune[i]) + " is not in charset") 238 | } 239 | mult.Mul(mult, big.NewInt(idx)) // multiply "place value" with the digit at spot i 240 | result.Add(result, mult) 241 | } 242 | return result, nil 243 | } 244 | 245 | // findRuneIndex returns the index of rune r in slice a, or -1 if not found 246 | func findRuneIndex(r rune, a []rune) int64 { 247 | for i := range a { 248 | if a[i] == r { 249 | return int64(i) 250 | } 251 | } 252 | return -1 253 | } 254 | -------------------------------------------------------------------------------- /util/crypto.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "crypto/x509" 9 | "encoding/base64" 10 | "encoding/hex" 11 | "errors" 12 | "fmt" 13 | "math/big" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | const ( 19 | // MicroquacksPerDuck is the number of microquacks equal to one duck. 20 | // A microquack is a billionth of a quack, which is a hundredth of a duck. 21 | MicroquacksPerDuck uint64 = 1e8 22 | ) 23 | 24 | type Lblock struct { 25 | // Index is the Lblock number 26 | Index uint64 27 | // Timestamp is the Unix timestamp in milliseconds of the date of creation of this Lblock 28 | Timestamp uint64 29 | // Data stores any (arbitrary) additional data <= 250 kb long. 30 | Data string 31 | // Hash stores the hash of this Lblock as computed by CalculateHash 32 | Hash string 33 | // PrevHash is the hash of the previous Lblock 34 | PrevHash string 35 | // Solution is the nonce value that makes the Hash be under some target value 36 | Solution uint64 37 | // Solver is the address of the sender. Address format: Q + version char + base64(shasum(pubkey)[:20]) 38 | Solver Address `json:",omitempty"` 39 | // Sblocks contains the Sblocks part of this Lblock 40 | Sblocks []*Sblock 41 | } 42 | 43 | // An Sblock is one block in the chain of some Lnode. It optionally contains a transaction and arbitrary data. 44 | type Sblock struct { 45 | // Index is the Sblock number 46 | Index uint64 47 | // Timestamp is the Unix timestamp in milliseconds of the date of creation of this Sblock 48 | Timestamp uint64 49 | // Data stores any (arbitrary) additional data <= 250 kb long. 50 | Data string 51 | // Hash stores the hash of this Sblock as computed by CalculateHash 52 | Hash string 53 | // PrevHash is the hash of the previous Sblock 54 | PrevHash string 55 | // Solution is the nonce value that makes the Hash be under some target value 56 | Solution uint64 57 | // Solver is the address of the sender. Address format: Q + version char + base64(shasum(pubkey)[:20]) 58 | Solver Address `json:",omitempty"` 59 | Tx Transaction `json:",omitempty"` 60 | } 61 | 62 | // A Transaction is a transfer of any amount of duckcoin from one address to another. 63 | type Transaction struct { 64 | // Data is any (arbitrary) additional data <= 250 kb long. 65 | Data string `json:",omitempty"` 66 | // Sender is the address of the sender. 67 | Sender Address `json:",omitempty"` 68 | // Receiver is the address of the receiver. 69 | Receiver Address `json:",omitempty"` 70 | // Amount is the amount to be payed by the Sender to the Receiver. It is always a positive number. 71 | Amount uint64 `json:",omitempty"` 72 | // PubKey is the Duckcoin formatted public key of the sender 73 | PubKey string `json:",omitempty"` 74 | // Signature is a base64 encoded cryptographic signature that uses the hash of the Sblock 75 | // as the data encrypted by the private key to prevent replay attacks 76 | Signature string `json:",omitempty"` 77 | } 78 | 79 | // GetTarget returns a "target" which block hashes must be lower than to be valid. 80 | // This is calculated such that miners will need to compute difficulty hashes on average 81 | // for a valid hash. 82 | func GetTarget(difficulty uint64) *big.Int { 83 | d := new(big.Int) 84 | // this is the number of possible hashes: 16^64 = 2^256 85 | d.Lsh(big.NewInt(1), 256) 86 | // now divide by t so that there's a 1/t chance that a hash is smaller than Difficulty 87 | // this works because the total pool of valid hashes will become t times smaller than the max size 88 | d.Quo(d, big.NewInt(int64(difficulty))) 89 | return d 90 | } 91 | 92 | // CalculateHash calculates the hash of a Sblock. 93 | func (b *Sblock) CalculateHash() string { 94 | return hex.EncodeToString(b.CalculateHashBytes()) 95 | } 96 | 97 | // CalculateHashBytes calculates the hash of a Sblock. 98 | func (b *Sblock) CalculateHashBytes() []byte { 99 | return DoubleShasumBytes(b.Preimage()) 100 | } 101 | 102 | // PreimageWOSolution returns the data to be hashed to create the hash of an Sblock, but without the Solution field taken into account. 103 | // This is useful when mining. 104 | func (b *Sblock) PreimageWOSolution() []byte { 105 | // lenCtrl hashes the bytes that a represents 106 | lenCtrl := func(a string) string { return string(DoubleShasumBytes([]byte(a))) } 107 | // all data fields are length-controlled so that the preimage always has around the same size (amount + timestamp + solution sizes can change, but not much) 108 | return []byte(strconv.FormatUint(b.Index, 10) + strconv.FormatUint(b.Timestamp, 10) + lenCtrl(b.Data) + b.PrevHash + string(b.Solver.bytes[:]) + // b.Hash is left out cause that's what's set later as the result of this func 109 | lenCtrl(b.Tx.Data) + string(b.Tx.Sender.bytes[:]) + string(b.Tx.Receiver.bytes[:]) + strconv.FormatUint(b.Tx.Amount, 10), // notice b.Tx.Signature is left out, that's also set later depending on this function's result 110 | ) 111 | } 112 | 113 | func (b *Sblock) Preimage() []byte { 114 | return append(b.PreimageWOSolution(), strconv.FormatUint(b.Solution, 10)...) 115 | } 116 | 117 | // CalculateHash calculates the hash of an Lblock. 118 | func (b *Lblock) CalculateHash() string { 119 | return hex.EncodeToString(b.CalculateHashBytes()) 120 | } 121 | 122 | // CalculateHashBytes calculates the hash of an Lblock. 123 | func (b *Lblock) CalculateHashBytes() []byte { 124 | return DoubleShasumBytes(b.Preimage()) 125 | } 126 | 127 | func (b *Lblock) PreimageWOSolution() []byte { 128 | sblocksConcatenated := "" 129 | for i := range b.Sblocks { 130 | sblocksConcatenated += string(b.Sblocks[i].Preimage()) 131 | } 132 | // lenCtrl hashes the bytes that a represents 133 | // see comments in PreimageWOSolution for why lenCtrl is used 134 | lenCtrl := func(a string) string { return string(DoubleShasumBytes([]byte(a))) } 135 | 136 | return []byte(strconv.FormatUint(b.Index, 10) + strconv.FormatUint(b.Timestamp, 10) + lenCtrl(b.Data) + b.PrevHash + string(b.Solver.bytes[:]) + // b.Hash is left out cause that's what's set later as the result of this func 137 | lenCtrl(sblocksConcatenated), 138 | ) 139 | } 140 | 141 | func (b *Lblock) Preimage() []byte { 142 | return append(b.PreimageWOSolution(), strconv.FormatUint(b.Solution, 10)...) 143 | } 144 | 145 | // MakeSignature signs a message with a private key. 146 | func MakeSignature(privkey, message string) (string, error) { 147 | hash := DoubleShasumBytes([]byte(message)) 148 | key, err := duckToPrivateKey(privkey) 149 | if err != nil { 150 | return "", err 151 | } 152 | data, err := ecdsa.SignASN1(rand.Reader, key, hash[:]) 153 | if err != nil { 154 | return "", err 155 | } 156 | return base64.StdEncoding.EncodeToString(data), nil 157 | } 158 | 159 | // DoubleShasumBytes returns sha256(sha256(record)) 160 | func DoubleShasumBytes(record []byte) []byte { 161 | h := sha256.New() 162 | h.Write(record) 163 | hashed := h.Sum(nil) 164 | h.Reset() 165 | h.Write(hashed) 166 | hashed = h.Sum(nil) 167 | return hashed 168 | } 169 | 170 | // IsHashValid checks if a hash is a valid block hash 171 | func IsHashValid(hash string, target *big.Int) bool { 172 | if len(hash) != 64 { // 32 bytes == 64 hex chars 173 | return false 174 | } 175 | d, ok := new(big.Int).SetString(hash, 16) 176 | if !ok { 177 | return ok 178 | } 179 | return IsHashValidBytes(d.FillBytes(make([]byte, 32)), target) 180 | } 181 | 182 | // IsHashValidBytes checks if a hash is a valid block hash 183 | func IsHashValidBytes(hash []byte, target *big.Int) bool { 184 | if len(hash) != 32 { // 32 bytes, a normal sha256 hash 185 | return false 186 | } 187 | d := new(big.Int).SetBytes(hash) 188 | return target.Cmp(d) == 1 // is target greater than d 189 | } 190 | 191 | // CheckSignature checks if signature decodes to message using pubkey. This is useful in verifying identities. 192 | func CheckSignature(signature, pubkey, message string) (bool, error) { 193 | decodedSig, err := base64.StdEncoding.DecodeString(signature) 194 | if err != nil { 195 | return false, err 196 | } 197 | hash := DoubleShasumBytes([]byte(message)) 198 | key, err := duckToPublicKey(pubkey) 199 | if err != nil { 200 | return false, err 201 | } 202 | return ecdsa.VerifyASN1(key, hash, decodedSig), nil 203 | } 204 | 205 | // MakeKeyPair creates a new public and private key pair 206 | func MakeKeyPair() (pub, priv string, err error) { 207 | pubkeyCurve := elliptic.P256() // see http://golang.org/pkg/crypto/elliptic/#P256 208 | privkey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader) // this generates a public & private key pair 209 | 210 | if err != nil { 211 | return "", "", err 212 | } 213 | pubkey := &privkey.PublicKey 214 | pub, err = publicKeytoDuck(pubkey) 215 | if err != nil { 216 | return "", "", err 217 | } 218 | priv, err = privateKeytoDuck(privkey) 219 | if err != nil { 220 | return "", "", err 221 | } 222 | return pub, priv, nil 223 | } 224 | 225 | // privateKeytoDuck serializes private keys to a base64 string 226 | func privateKeytoDuck(privkey *ecdsa.PrivateKey) (string, error) { 227 | marshalled, err := x509.MarshalECPrivateKey(privkey) 228 | if err != nil { 229 | return "", err 230 | } 231 | return base64.StdEncoding.EncodeToString(marshalled), nil 232 | } 233 | 234 | // duckToPrivateKey deserializes private keys 235 | func duckToPrivateKey(duckkey string) (*ecdsa.PrivateKey, error) { 236 | d, err := base64.StdEncoding.DecodeString(duckkey) 237 | if err != nil { 238 | return nil, err 239 | } 240 | p, err := x509.ParseECPrivateKey(d) 241 | if err != nil { 242 | return nil, err 243 | } 244 | return p, nil 245 | } 246 | 247 | // publicKeytoDuck serializes public keys to a base64 string 248 | func publicKeytoDuck(pubkey *ecdsa.PublicKey) (string, error) { 249 | marshalled, err := x509.MarshalPKIXPublicKey(pubkey) 250 | if err != nil { 251 | return "", err 252 | } 253 | return base64.StdEncoding.EncodeToString(marshalled), nil 254 | } 255 | 256 | // duckToPublicKey deserializes public keys 257 | func duckToPublicKey(duckkey string) (*ecdsa.PublicKey, error) { 258 | d, err := base64.StdEncoding.DecodeString(duckkey) 259 | if err != nil { 260 | return nil, err 261 | } 262 | p, err := x509.ParsePKIXPublicKey(d) 263 | if err != nil { 264 | return nil, err 265 | } 266 | pubkey, ok := p.(*ecdsa.PublicKey) 267 | if !ok { 268 | return nil, errors.New("pubkey is not of type *ecdsa.PublicKey") 269 | } 270 | return pubkey, nil 271 | } 272 | 273 | func IsValid(newBlock, oldBlock *Sblock, target *big.Int) error { 274 | err := IsValidNoCheckDB(newBlock, oldBlock, target) 275 | if err != nil { 276 | return err 277 | } 278 | if uint64(time.Now().UnixMilli())-newBlock.Timestamp > 1e3*60*5 { // 5 minutes in millis 279 | return errors.New("Sblock timestamp is not within 5 minutes before current time. What are you trying to pull off here?") 280 | } 281 | if newBlock.Tx.Amount > 0 { 282 | senderBalance, err := GetBalanceByAddr(newBlock.Tx.Sender) 283 | if err != nil { 284 | return errors.New("Internal Server Error") 285 | } 286 | if senderBalance < newBlock.Tx.Amount { // notice that there is no reward for this block's PoW added to the sender's account first 287 | return fmt.Errorf("Insufficient balance %d microquacks (sender balance) is less than %d microquacks (tx amount)", senderBalance, newBlock.Tx.Amount) 288 | } 289 | } 290 | return nil 291 | } 292 | 293 | func IsValidNoCheckDB(newBlock, oldBlock *Sblock, target *big.Int) error { 294 | const blockDataLimit = 1e3 * 250 295 | const txDataLimit = 1e3 * 250 296 | 297 | //if newBlock.Tx.Amount < 0 { 298 | // return errors.New("Amount is negative") 299 | //} 300 | if oldBlock.Index+1 != newBlock.Index { 301 | return errors.New("Index should be " + strconv.FormatInt(int64(oldBlock.Index+1), 10)) 302 | } 303 | if oldBlock.Hash != newBlock.PrevHash { 304 | return errors.New("PrevHash should be " + oldBlock.Hash) 305 | } 306 | if err := newBlock.Solver.IsValid(); err != nil { 307 | return errors.New("Sender is invalid: " + err.Error()) 308 | } 309 | if newBlock.CalculateHash() != newBlock.Hash { 310 | return errors.New("Sblock Hash does not match actual hash.") 311 | } 312 | if !IsHashValid(newBlock.Hash, target) { 313 | return errors.New("Sblock is not a solution (does not have Difficulty zeros in hash)") 314 | } 315 | if len(newBlock.Data) > blockDataLimit { 316 | return errors.New("Sblock's Data field is too large. Should be >= 250 kb") 317 | } 318 | if len(newBlock.Tx.Data) > txDataLimit { 319 | return errors.New("Transaction's Data field is too large. Should be >= 250 kb") 320 | } 321 | if newBlock.Tx.Amount > 0 { 322 | if newBlock.Tx.Sender.IsValid() != nil || newBlock.Tx.Receiver.IsValid() != nil || !IsValidBase64(newBlock.Tx.PubKey) || 323 | !IsValidBase64(newBlock.Tx.Signature) { 324 | return errors.New("At least one of the Sender, Receiver, PubKey or Signature is not valid. What are you trying to pull here?") 325 | } 326 | if KeyToAddress(newBlock.Tx.PubKey) != newBlock.Tx.Sender { 327 | return errors.New("Pubkey does not match sender address") 328 | } 329 | if ok, err := CheckSignature(newBlock.Tx.Signature, newBlock.Tx.PubKey, newBlock.Hash); !ok { 330 | if err != nil { 331 | return err 332 | } else { 333 | return errors.New("Invalid signature") 334 | } 335 | } 336 | } 337 | return nil 338 | } 339 | -------------------------------------------------------------------------------- /util/crypto_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestAddress_IsValid(t *testing.T) { 11 | a := &Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}} 12 | if err := a.IsValid(); err != nil { 13 | t.Errorf("IsValid() error = %v", err) 14 | } 15 | // invalid address: look at first byte 16 | a = &Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xff, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}} 17 | if err := a.IsValid(); err == nil { 18 | t.Errorf("IsValid() error = %v", err) 19 | } 20 | } 21 | 22 | func TestCheckSignature(t *testing.T) { 23 | type args struct { 24 | signature string 25 | pubkey string 26 | message string 27 | } 28 | tests := []struct { 29 | name string 30 | args args 31 | want bool 32 | wantErr bool 33 | }{ 34 | // TODO: Add test cases. 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | got, err := CheckSignature(tt.args.signature, tt.args.pubkey, tt.args.message) 39 | if (err != nil) != tt.wantErr { 40 | t.Errorf("CheckSignature() error = %v, wantErr %v", err, tt.wantErr) 41 | return 42 | } 43 | if got != tt.want { 44 | t.Errorf("CheckSignature() got = %v, want %v", got, tt.want) 45 | } 46 | }) 47 | } 48 | } 49 | 50 | func TestDoubleShasumBytes(t *testing.T) { 51 | want := []byte{0x95, 0x95, 0xc9, 0xdf, 0x90, 0x7, 0x51, 0x48, 0xeb, 0x6, 0x86, 0x3, 0x65, 0xdf, 0x33, 0x58, 0x4b, 0x75, 0xbf, 0xf7, 0x82, 0xa5, 0x10, 0xc6, 0xcd, 0x48, 0x83, 0xa4, 0x19, 0x83, 0x3d, 0x50} 52 | if got := DoubleShasumBytes([]byte("hello")); !reflect.DeepEqual(got, want) { 53 | t.Errorf("DoubleShasumBytes() = %v, want %v", got, want) 54 | } 55 | } 56 | 57 | func TestGetTarget(t *testing.T) { 58 | want, _ := big.NewInt(0).SetString("4000000000000000000000000000000000000000000000000000000000000", 16) 59 | if got := GetTarget(1 << 14); got.Cmp(want) != 0 { 60 | t.Errorf("GetTarget() = %v, want %v", got.Text(16), want.Text(16)) 61 | } 62 | } 63 | 64 | func TestIsHashValidBytes(t *testing.T) { 65 | want := true 66 | target, _ := big.NewInt(0).SetString("4000000000000000000000000000000000000000000000000000000000000", 16) 67 | hashTg, _ := big.NewInt(0).SetString("3900000000000000000000000000000000000000000000000000000000000", 16) 68 | 69 | if got := IsHashValidBytes(hashTg.FillBytes(make([]byte, 32)), target); got != want { 70 | t.Errorf("IsHashValidBytes() = %v, want %v", got, want) 71 | } 72 | want = false 73 | hashTg, _ = big.NewInt(0).SetString("4100000000000000000000000000000000000000000000000000000000000", 16) 74 | if got := IsHashValidBytes(hashTg.FillBytes(make([]byte, 32)), target); got != want { 75 | t.Errorf("IsHashValidBytes() = %v, want %v", got, want) 76 | } 77 | } 78 | 79 | func TestIsSblockValidNoCheckDB(t *testing.T) { 80 | target, _ := big.NewInt(0).SetString("2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 16) 81 | tests := []struct { 82 | name string 83 | newBlock *Sblock 84 | oldBlock *Sblock 85 | target *big.Int 86 | wantErr bool 87 | }{ 88 | {"valid", 89 | &Sblock{ 90 | Index: 2, 91 | Timestamp: 0x181200d7a83, 92 | Data: "ishan", 93 | Hash: "0000008f071333349946244908d863ccd037ed0b905ffe914e0d4695d017645c", 94 | PrevHash: "00000255ddf50f2f637b982d714132706cfae2f9a723ead5f20c2b4b5e94737f", 95 | Solution: 0x6021d3, 96 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 97 | Tx: Transaction{}, 98 | }, 99 | &Sblock{ 100 | Index: 1, 101 | Timestamp: 0x181200d74a3, 102 | Data: "ishan", 103 | Hash: "00000255ddf50f2f637b982d714132706cfae2f9a723ead5f20c2b4b5e94737f", 104 | PrevHash: "0000000000000000000000000000000000000000000000000000000000000000", 105 | Solution: 0x431052, 106 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 107 | Tx: Transaction{}, 108 | }, 109 | target, 110 | false, 111 | //{""}, 112 | }, 113 | {"valid with tx", 114 | &Sblock{ 115 | Index: 10, 116 | Timestamp: 0x1812047e891, 117 | Data: "ishan", 118 | Hash: "0000019d96d49f7cdbcdac4f37ee77d9289c9e65508140799bea68af7650995e", 119 | PrevHash: "0000004f26163127496e875fd140105676a20b55c0358000e2902775ac78b55e", 120 | Solution: 0x518bda, 121 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 122 | Tx: Transaction{ 123 | Data: "hello there. why are you looking at this test case?", 124 | Sender: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 125 | Receiver: Address{Emoji: "🦆🍩🍅💕🤧👔🦚🚥😐📬🎽💰🌹➰👒🩴😑🆓🚔👧", Text: "QCERVti9HcuKhzJn0wmlRlNGzMcsP5yK1T", bytes: [24]uint8{0xbf, 0xc5, 0x39, 0xeb, 0xe4, 0x98, 0x54, 0xbb, 0xd6, 0xe, 0x6c, 0xcd, 0x9e, 0x69, 0xd8, 0x7d, 0xc2, 0xb5, 0x5e, 0x1d, 0xa, 0xc5, 0xe0, 0x31}}, 126 | Amount: 0x1e8480, 127 | PubKey: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEonWWCXkG0K8EYkt7xv/4CkZpxY1nMpeoT51oTPfXcuYcJ/eaZVipyGfh9ZitqfOQkiDhJ/NLgBj5MB/Jr5jJyw==", 128 | Signature: "MEYCIQDAoCLEmcPc/SvYGCMayJPQYR2KSm+0TfGvya/yTvZtfAIhAJYdLLh+vMNBFQtmZG0JznpQIm4kvRQPg26VzWOWX0Es", 129 | }, 130 | }, 131 | &Sblock{ 132 | Index: 9, 133 | Timestamp: 0x1812046a5fb, 134 | Data: "Hello there. why are you looking at this test case.", 135 | Hash: "0000004f26163127496e875fd140105676a20b55c0358000e2902775ac78b55e", 136 | PrevHash: "0000008bdc9ea7ef38661e10e79fd714b746c4c2fcb3d306b82e3f4ce15bfbb9", 137 | Solution: 0x221b2, 138 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 139 | Tx: Transaction{}, 140 | }, 141 | target, 142 | false, 143 | }, 144 | {"invalid with tx", 145 | &Sblock{ 146 | Index: 10, 147 | Timestamp: 0x1812047e892, // invalid timestamp, changes hash. 148 | Data: "ishan", 149 | Hash: "0000019d96d49f7cdbcdac4f37ee77d9289c9e65508140799bea68af7650995e", 150 | PrevHash: "0000004f26163127496e875fd140105676a20b55c0358000e2902775ac78b55e", 151 | Solution: 0x518bda, 152 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 153 | Tx: Transaction{ 154 | Data: "hello there. why are you looking at this test case?", 155 | Sender: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 156 | Receiver: Address{Emoji: "🦆🍩🍅💕🤧👔🦚🚥😐📬🎽💰🌹➰👒🩴😑🆓🚔👧", Text: "QCERVti9HcuKhzJn0wmlRlNGzMcsP5yK1T", bytes: [24]uint8{0xbf, 0xc5, 0x39, 0xeb, 0xe4, 0x98, 0x54, 0xbb, 0xd6, 0xe, 0x6c, 0xcd, 0x9e, 0x69, 0xd8, 0x7d, 0xc2, 0xb5, 0x5e, 0x1d, 0xa, 0xc5, 0xe0, 0x31}}, 157 | Amount: 0x1e8480, 158 | PubKey: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEonWWCXkG0K8EYkt7xv/4CkZpxY1nMpeoT51oTPfXcuYcJ/eaZVipyGfh9ZitqfOQkiDhJ/NLgBj5MB/Jr5jJyw==", 159 | Signature: "MEYCIQDAoCLEmcPc/SvYGCMayJPQYR2KSm+0TfGvya/yTvZtfAIhAJYdLLh+vMNBFQtmZG0JznpQIm4kvRQPg26VzWOWX0Es", 160 | }, 161 | }, 162 | &Sblock{ 163 | Index: 9, 164 | Timestamp: 0x1812046a5fb, 165 | Data: "Hello there. why are you looking at this test case.", 166 | Hash: "0000004f26163127496e875fd140105676a20b55c0358000e2902775ac78b55e", 167 | PrevHash: "0000008bdc9ea7ef38661e10e79fd714b746c4c2fcb3d306b82e3f4ce15bfbb9", 168 | Solution: 0x221b2, 169 | Solver: Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}}, 170 | Tx: Transaction{}, 171 | }, 172 | target, 173 | false, 174 | }, 175 | } 176 | for _, tt := range tests { 177 | t.Run(tt.name, func(t *testing.T) { 178 | if err := IsValidNoCheckDB(tt.newBlock, tt.oldBlock, tt.target); (err != nil) != tt.wantErr { 179 | t.Errorf("sblock IsValidNoCheckDB() error = %v, wantErr %v", err, tt.wantErr) 180 | } 181 | }) 182 | } 183 | } 184 | 185 | func TestIsValidBase64(t *testing.T) { 186 | tests := []struct { 187 | name string 188 | s string 189 | want bool 190 | }{ 191 | {"valid base64", "aGVsbG8K", true}, 192 | {"invalid base64", "aGVsbG8K;", false}, 193 | // TODO: Add test cases. 194 | } 195 | for _, tt := range tests { 196 | t.Run(tt.name, func(t *testing.T) { 197 | if got := IsValidBase64(tt.s); got != tt.want { 198 | t.Errorf("IsValidBase64() = %v, want %v", got, tt.want) 199 | } 200 | }) 201 | } 202 | } 203 | 204 | func TestKeyToAddress(t *testing.T) { 205 | want := Address{Emoji: "🦆😾📎🏥🐷🌇⛲💣🍾🌍👨🎣🤬🔅🗄💋🦿😧🦔😐", Text: "QClNW9qpw0zCYuO5q8lGU6SFQmCQ1T7EAm", bytes: [24]uint8{0xf1, 0x1, 0xe5, 0xa6, 0xc7, 0x77, 0x32, 0x4a, 0xc5, 0xbf, 0x4d, 0xba, 0xcb, 0x16, 0xc8, 0x9, 0xc8, 0xd8, 0xfb, 0x61, 0xa5, 0xbe, 0x5e, 0xee}} 206 | 207 | if got := KeyToAddress("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEonWWCXkG0K8EYkt7xv/4CkZpxY1nMpeoT51oTPfXcuYcJ/eaZVipyGfh9ZitqfOQkiDhJ/NLgBj5MB/Jr5jJyw=="); !reflect.DeepEqual(got, want) { 208 | t.Errorf("KeyToAddress() = %v, want %v", got, want) 209 | } 210 | } 211 | 212 | func TestLblock_CalculateHashBytes(t *testing.T) { 213 | type fields struct { 214 | Index uint64 215 | Timestamp uint64 216 | Data string 217 | Hash string 218 | PrevHash string 219 | Solution uint64 220 | Solver Address 221 | Sblocks []*Sblock 222 | } 223 | tests := []struct { 224 | name string 225 | fields fields 226 | want []byte 227 | }{ 228 | // TODO: Add test cases. 229 | } 230 | for _, tt := range tests { 231 | t.Run(tt.name, func(t *testing.T) { 232 | b := &Lblock{ 233 | Index: tt.fields.Index, 234 | Timestamp: tt.fields.Timestamp, 235 | Data: tt.fields.Data, 236 | Hash: tt.fields.Hash, 237 | PrevHash: tt.fields.PrevHash, 238 | Solution: tt.fields.Solution, 239 | Solver: tt.fields.Solver, 240 | Sblocks: tt.fields.Sblocks, 241 | } 242 | if got := b.CalculateHashBytes(); !reflect.DeepEqual(got, tt.want) { 243 | t.Errorf("CalculateHashBytes() = %v, want %v", got, tt.want) 244 | } 245 | }) 246 | } 247 | } 248 | 249 | func TestLblock_Preimage(t *testing.T) { 250 | type fields struct { 251 | Index uint64 252 | Timestamp uint64 253 | Data string 254 | Hash string 255 | PrevHash string 256 | Solution uint64 257 | Solver Address 258 | Sblocks []*Sblock 259 | } 260 | tests := []struct { 261 | name string 262 | fields fields 263 | want []byte 264 | }{ 265 | // TODO: Add test cases. 266 | } 267 | for _, tt := range tests { 268 | t.Run(tt.name, func(t *testing.T) { 269 | b := &Lblock{ 270 | Index: tt.fields.Index, 271 | Timestamp: tt.fields.Timestamp, 272 | Data: tt.fields.Data, 273 | Hash: tt.fields.Hash, 274 | PrevHash: tt.fields.PrevHash, 275 | Solution: tt.fields.Solution, 276 | Solver: tt.fields.Solver, 277 | Sblocks: tt.fields.Sblocks, 278 | } 279 | if got := b.Preimage(); !reflect.DeepEqual(got, tt.want) { 280 | t.Errorf("Preimage() = %v, want %v", got, tt.want) 281 | } 282 | }) 283 | } 284 | } 285 | 286 | func TestLblock_PreimageWOSolution(t *testing.T) { 287 | type fields struct { 288 | Index uint64 289 | Timestamp uint64 290 | Data string 291 | Hash string 292 | PrevHash string 293 | Solution uint64 294 | Solver Address 295 | Sblocks []*Sblock 296 | } 297 | tests := []struct { 298 | name string 299 | fields fields 300 | want []byte 301 | }{ 302 | // TODO: Add test cases. 303 | } 304 | for _, tt := range tests { 305 | t.Run(tt.name, func(t *testing.T) { 306 | b := &Lblock{ 307 | Index: tt.fields.Index, 308 | Timestamp: tt.fields.Timestamp, 309 | Data: tt.fields.Data, 310 | Hash: tt.fields.Hash, 311 | PrevHash: tt.fields.PrevHash, 312 | Solution: tt.fields.Solution, 313 | Solver: tt.fields.Solver, 314 | Sblocks: tt.fields.Sblocks, 315 | } 316 | if got := b.PreimageWOSolution(); !reflect.DeepEqual(got, tt.want) { 317 | t.Errorf("PreimageWOSolution() = %v, want %v", got, tt.want) 318 | } 319 | }) 320 | } 321 | } 322 | 323 | func TestLblock_serialize(t *testing.T) { 324 | type fields struct { 325 | Index uint64 326 | Timestamp uint64 327 | Data string 328 | Hash string 329 | PrevHash string 330 | Solution uint64 331 | Solver Address 332 | Sblocks []*Sblock 333 | } 334 | tests := []struct { 335 | name string 336 | fields fields 337 | want []byte 338 | }{ 339 | // TODO: Add test cases. 340 | } 341 | for _, tt := range tests { 342 | t.Run(tt.name, func(t *testing.T) { 343 | b := &Lblock{ 344 | Index: tt.fields.Index, 345 | Timestamp: tt.fields.Timestamp, 346 | Data: tt.fields.Data, 347 | Hash: tt.fields.Hash, 348 | PrevHash: tt.fields.PrevHash, 349 | Solution: tt.fields.Solution, 350 | Solver: tt.fields.Solver, 351 | Sblocks: tt.fields.Sblocks, 352 | } 353 | if got := b.serialize(); !reflect.DeepEqual(got, tt.want) { 354 | t.Errorf("serialize() = %v, want %v", got, tt.want) 355 | } 356 | }) 357 | } 358 | } 359 | 360 | func TestSblock_CalculateHashBytes(t *testing.T) { 361 | type fields struct { 362 | Index uint64 363 | Timestamp uint64 364 | Data string 365 | Hash string 366 | PrevHash string 367 | Solution uint64 368 | Solver Address 369 | Tx Transaction 370 | } 371 | tests := []struct { 372 | name string 373 | fields fields 374 | want []byte 375 | }{ 376 | // TODO: Add test cases. 377 | } 378 | for _, tt := range tests { 379 | t.Run(tt.name, func(t *testing.T) { 380 | b := &Sblock{ 381 | Index: tt.fields.Index, 382 | Timestamp: tt.fields.Timestamp, 383 | Data: tt.fields.Data, 384 | Hash: tt.fields.Hash, 385 | PrevHash: tt.fields.PrevHash, 386 | Solution: tt.fields.Solution, 387 | Solver: tt.fields.Solver, 388 | Tx: tt.fields.Tx, 389 | } 390 | if got := b.CalculateHashBytes(); !reflect.DeepEqual(got, tt.want) { 391 | t.Errorf("CalculateHashBytes() = %v, want %v", got, tt.want) 392 | } 393 | }) 394 | } 395 | } 396 | 397 | func TestSblock_serialize(t *testing.T) { 398 | type fields struct { 399 | Index uint64 400 | Timestamp uint64 401 | Data string 402 | Hash string 403 | PrevHash string 404 | Solution uint64 405 | Solver Address 406 | Tx Transaction 407 | } 408 | tests := []struct { 409 | name string 410 | fields fields 411 | want []byte 412 | }{ 413 | // TODO: Add test cases. 414 | } 415 | for _, tt := range tests { 416 | t.Run(tt.name, func(t *testing.T) { 417 | b := &Sblock{ 418 | Index: tt.fields.Index, 419 | Timestamp: tt.fields.Timestamp, 420 | Data: tt.fields.Data, 421 | Hash: tt.fields.Hash, 422 | PrevHash: tt.fields.PrevHash, 423 | Solution: tt.fields.Solution, 424 | Solver: tt.fields.Solver, 425 | Tx: tt.fields.Tx, 426 | } 427 | if got := b.serialize(); !reflect.DeepEqual(got, tt.want) { 428 | t.Errorf("serialize() = %v, want %v", got, tt.want) 429 | } 430 | }) 431 | } 432 | } 433 | 434 | func Test_deserializeLblock(t *testing.T) { 435 | type args struct { 436 | buf []byte 437 | } 438 | tests := []struct { 439 | name string 440 | args args 441 | want *Lblock 442 | }{ 443 | // TODO: Add test cases. 444 | } 445 | for _, tt := range tests { 446 | t.Run(tt.name, func(t *testing.T) { 447 | if got := deserializeLblock(tt.args.buf); !reflect.DeepEqual(got, tt.want) { 448 | t.Errorf("deserializeLblock() = %v, want %v", got, tt.want) 449 | } 450 | }) 451 | } 452 | } 453 | 454 | func Test_deserializeSblock(t *testing.T) { 455 | type args struct { 456 | buf []byte 457 | } 458 | tests := []struct { 459 | name string 460 | args args 461 | want *Sblock 462 | }{ 463 | // TODO: Add test cases. 464 | } 465 | for _, tt := range tests { 466 | t.Run(tt.name, func(t *testing.T) { 467 | if got := deserializeSblock(tt.args.buf); !reflect.DeepEqual(got, tt.want) { 468 | t.Errorf("deserializeSblock() = %v, want %v", got, tt.want) 469 | } 470 | }) 471 | } 472 | } 473 | 474 | func Test_duckToPrivateKey(t *testing.T) { 475 | type args struct { 476 | duckkey string 477 | } 478 | tests := []struct { 479 | name string 480 | args args 481 | want *ecdsa.PrivateKey 482 | wantErr bool 483 | }{ 484 | // TODO: Add test cases. 485 | } 486 | for _, tt := range tests { 487 | t.Run(tt.name, func(t *testing.T) { 488 | got, err := duckToPrivateKey(tt.args.duckkey) 489 | if (err != nil) != tt.wantErr { 490 | t.Errorf("duckToPrivateKey() error = %v, wantErr %v", err, tt.wantErr) 491 | return 492 | } 493 | if !reflect.DeepEqual(got, tt.want) { 494 | t.Errorf("duckToPrivateKey() got = %v, want %v", got, tt.want) 495 | } 496 | }) 497 | } 498 | } 499 | 500 | func Test_duckToPublicKey(t *testing.T) { 501 | type args struct { 502 | duckkey string 503 | } 504 | tests := []struct { 505 | name string 506 | args args 507 | want *ecdsa.PublicKey 508 | wantErr bool 509 | }{ 510 | // TODO: Add test cases. 511 | } 512 | for _, tt := range tests { 513 | t.Run(tt.name, func(t *testing.T) { 514 | got, err := duckToPublicKey(tt.args.duckkey) 515 | if (err != nil) != tt.wantErr { 516 | t.Errorf("duckToPublicKey() error = %v, wantErr %v", err, tt.wantErr) 517 | return 518 | } 519 | if !reflect.DeepEqual(got, tt.want) { 520 | t.Errorf("duckToPublicKey() got = %v, want %v", got, tt.want) 521 | } 522 | }) 523 | } 524 | } 525 | 526 | func TestTextToAddress(t *testing.T) { 527 | type args struct { 528 | text string 529 | } 530 | tests := []struct { 531 | name string 532 | args args 533 | want Address 534 | wantErr bool 535 | }{ 536 | // TODO: Add test cases. 537 | } 538 | for _, tt := range tests { 539 | t.Run(tt.name, func(t *testing.T) { 540 | got, err := TextToAddress(tt.args.text) 541 | if (err != nil) != tt.wantErr { 542 | t.Errorf("TextToAddress() error = %v, wantErr %v", err, tt.wantErr) 543 | return 544 | } 545 | if !reflect.DeepEqual(got, tt.want) { 546 | t.Errorf("TextToAddress() got = %v, want %v", got, tt.want) 547 | } 548 | }) 549 | } 550 | } 551 | 552 | func TestTextToBytes(t *testing.T) { 553 | type args struct { 554 | text string 555 | } 556 | tests := []struct { 557 | name string 558 | args args 559 | want [addrBytesLen]byte 560 | wantErr bool 561 | }{ 562 | // TODO: Add test cases. 563 | } 564 | for _, tt := range tests { 565 | t.Run(tt.name, func(t *testing.T) { 566 | got, err := TextToBytes(tt.args.text) 567 | if (err != nil) != tt.wantErr { 568 | t.Errorf("TextToBytes() error = %v, wantErr %v", err, tt.wantErr) 569 | return 570 | } 571 | if !reflect.DeepEqual(got, tt.want) { 572 | t.Errorf("TextToBytes() got = %v, want %v", got, tt.want) 573 | } 574 | }) 575 | } 576 | } 577 | 578 | func Test_addChecksum(t *testing.T) { 579 | type args struct { 580 | data []byte 581 | } 582 | tests := []struct { 583 | name string 584 | args args 585 | want []byte 586 | }{ 587 | // TODO: Add test cases. 588 | } 589 | for _, tt := range tests { 590 | t.Run(tt.name, func(t *testing.T) { 591 | if got := addChecksum(tt.args.data); !reflect.DeepEqual(got, tt.want) { 592 | t.Errorf("addChecksum() = %v, want %v", got, tt.want) 593 | } 594 | }) 595 | } 596 | } 597 | 598 | func Test_verifyChecksum(t *testing.T) { 599 | type args struct { 600 | data []byte 601 | } 602 | tests := []struct { 603 | name string 604 | args args 605 | want bool 606 | }{ 607 | // TODO: Add test cases. 608 | } 609 | for _, tt := range tests { 610 | t.Run(tt.name, func(t *testing.T) { 611 | if got := verifyChecksum(tt.args.data); got != tt.want { 612 | t.Errorf("verifyChecksum() = %v, want %v", got, tt.want) 613 | } 614 | }) 615 | } 616 | } 617 | 618 | func Test_decodeVarintBytes(t *testing.T) { 619 | type args struct { 620 | readFrom []byte 621 | } 622 | tests := []struct { 623 | name string 624 | args args 625 | wantNewBuf []byte 626 | wantData []byte 627 | }{ 628 | // TODO: Add test cases. 629 | } 630 | for _, tt := range tests { 631 | t.Run(tt.name, func(t *testing.T) { 632 | gotNewBuf, gotData := decodeVarintBytes(tt.args.readFrom) 633 | if !reflect.DeepEqual(gotNewBuf, tt.wantNewBuf) { 634 | t.Errorf("decodeVarintBytes() gotNewBuf = %v, want %v", gotNewBuf, tt.wantNewBuf) 635 | } 636 | if !reflect.DeepEqual(gotData, tt.wantData) { 637 | t.Errorf("decodeVarintBytes() gotData = %v, want %v", gotData, tt.wantData) 638 | } 639 | }) 640 | } 641 | } 642 | 643 | func Test_encodeVarintBytes(t *testing.T) { 644 | type args struct { 645 | writeTo []byte 646 | data [][]byte 647 | } 648 | tests := []struct { 649 | name string 650 | args args 651 | want []byte 652 | }{ 653 | // TODO: Add test cases. 654 | } 655 | for _, tt := range tests { 656 | t.Run(tt.name, func(t *testing.T) { 657 | if got := encodeVarintBytes(tt.args.writeTo, tt.args.data...); !reflect.DeepEqual(got, tt.want) { 658 | t.Errorf("encodeVarintBytes() = %v, want %v", got, tt.want) 659 | } 660 | }) 661 | } 662 | } 663 | 664 | func Test_privateKeytoDuck(t *testing.T) { 665 | type args struct { 666 | privkey *ecdsa.PrivateKey 667 | } 668 | tests := []struct { 669 | name string 670 | args args 671 | want string 672 | wantErr bool 673 | }{ 674 | // TODO: Add test cases. 675 | } 676 | for _, tt := range tests { 677 | t.Run(tt.name, func(t *testing.T) { 678 | got, err := privateKeytoDuck(tt.args.privkey) 679 | if (err != nil) != tt.wantErr { 680 | t.Errorf("privateKeytoDuck() error = %v, wantErr %v", err, tt.wantErr) 681 | return 682 | } 683 | if got != tt.want { 684 | t.Errorf("privateKeytoDuck() got = %v, want %v", got, tt.want) 685 | } 686 | }) 687 | } 688 | } 689 | 690 | func Test_publicKeytoDuck(t *testing.T) { 691 | type args struct { 692 | pubkey *ecdsa.PublicKey 693 | } 694 | tests := []struct { 695 | name string 696 | args args 697 | want string 698 | wantErr bool 699 | }{ 700 | // TODO: Add test cases. 701 | } 702 | for _, tt := range tests { 703 | t.Run(tt.name, func(t *testing.T) { 704 | got, err := publicKeytoDuck(tt.args.pubkey) 705 | if (err != nil) != tt.wantErr { 706 | t.Errorf("publicKeytoDuck() error = %v, wantErr %v", err, tt.wantErr) 707 | return 708 | } 709 | if got != tt.want { 710 | t.Errorf("publicKeytoDuck() got = %v, want %v", got, tt.want) 711 | } 712 | }) 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /util/emoji.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | var emoji = []rune(`🦆🐣🐤🐥🎟🪐🤖🌥🎯🦋🟪🛷📸🤾🔞🎀🔈🪦🦢➖🥊⚧🌲🪗😚🎌🌡🐁👨🎗🌝⛄💨🖕🧨📏🦒🎨🔦🧆🚊🗑🏅🛸🤿🐶🚂🚀🦨🪲🍵🤶🎥💟🥷🦟🐞😳🥦📎🏕🐅🏇🕚🤫🕧🌀😫🤣🕘🥱🐠📣🌙🏒💱🎏🟡🚅🏝🔧🧃😹🅰🕤🥼🚍🫂🦩🖨😺🌗🌭📢🖐💎🚣🌉🤽🦻🤦🧱🌵💦📳🔚⚾😡😻🤠🐦⛰🕕🐂💫📿🌚🤺⏰😰📬🛄🤴➰🍰🙋🍇🔹🏷👱🦀🚬🏣💇⚫🥑💃🚰🦶🐟😒📩🐹💋🐵🥣📴⛱💅🤑🦎🦜🤐👌🚧🏈💗🎋🩰🛌🏹😩🏺🏏🗽🫀🎅🧘🎁🪥😤📘🥇🛻🤌⏭🕺💻⛪🚦🟨❌💽🙆💡🪒🔛🍾🦉🖲💬🚡👯⚡🧁🏉🌪💁🌎🦫💔🧭🏬🍒🧦🍩🐖😞⏯🛶⛓🥳◽⏺🚜🥈🦭👆🤲🏩🛺⚪🌇🧶👻🪙➗🚆🍤👣📪🤳🔫🏟🧗🪄🐎🚮🚹🎾🕜👒🔨🌊🧎💲🌸⌚🍘🖋⏸🍣🌤📖💐🪓🧳🪰😾👪👸💩♿🚝🦮🍈🔘🚋🔱🛬🦄🏑👘🩸💪👥👋🛏🎚🏰🩲🔩🔰🎪🐔🌒🧓🧯🔗👟🦍🎍🤹🥽🟤🏯📇🟥🛀🕗🔴🪶🔄🧤🌴🚨🌑🥮👗🐍🔖🕞⏱📮🪟🐘🍌🕰🪴📲🐌🔶📰🍴🏜😪🍔⛳🦽🏂👫🔉✊📯👈🐴📡🎱🐽🏌🕹😄🥌🪆✋🤞🤧⛏🎐🚓🥥🐃🚉😶🥖🛑🚛🔇💿🍿🛰🐊📋🪁🛂🚚🚙🏭🧫👵🆖🏍🌽🧲⏫🔳😔🥒🤷🪱⬜👤🧷🅾🏊🎡🧼🧧🧄🍉🍪👚🔎🐛🍅🎽🤍🆚🌦🚔🛅😓🦘👢🥠🎠🪡👇🍷🤱📕🧥🎰👉🪣🥟🐐🔵🥐💧🦠👛🥗🎿🛣🕳🧔🪠🍆🚩💥🎦🚐🏨💌🧢🙂✅📚🏦🦬🕑👖🦹🧑🦖😌🐸🍐🧿😿🎇🥫✨👝🧖🍗🎼🕡🚈🟩⚓🪚❔🦇🔻🚃💠🟧🐒🚠😛🗣🦊🐱💷🦂🟫🔬👧🫖📟🍼🟦🧋🏁🚳🆗🧩🚿🫑🚺🤭🐿🚎📁💤🏤🤎📄👜🐜🌅🔁🔃😀💘🔐🚘🕶🕣🚼💾🥧📧❕🔼🗄🌼🪞⏪🕢📔👺🕒👶🏠💕🥓🎉🦙🛤🪘📥🧏🫁🦑🥁🩴🔕🌫🥰🦾📼🥯⛹🐕🛳🕟🦯🙀📜🍸🤥💆🕓🔓🍲🥴🐬🛃🧣👦🐨🪨😱🛩👓👹🩹⛸🛋🪜⛴🦁🏛🚾🦚👎🧙🥉🦛🧮🤤🎴🕊🍜🧪🤵🏓💯🍯🚫🧬💮🟣🛁😉🗨🦺🤏🎆😊🤗🚑🌺🦈🕔😬🦏🗾🚪😥🫒🦵😷🥭🚽🔌😝🎤🐚🪂🆘🍺🍀🛼🦤💖🍦🕸🆙⛑💴🐙❎🥻🍕🥡🚢👽😑🥜🔺🐉⬛🐢🛎🍻🅱⌛🥺🏚🐯🧞🔋⚽📀🍂🪝🍮🪖🐪👿🏵🥙💰🗺🧺➿🌓🗡🧾🐫🌱📉👙🌕🚥🤩🍨🦓🌧🥶📆🏮🧜🐗🌠🧟🚯🤙🐭🚏🚕🦡🔲🚲📂🌟📵📻🪵👲🎢🧚🔟👩🚻😎🧐😨📍🎞😯❗🥎🐄🦦🍱🎃🖼⛎🪔🪃🌰🗞🆕👍🤒💼❓🥬💈🙊🛗🎄📺🥚🍓🥤💸🤼🌜⭕🤰🗯🏸🎊⛩🧒🗜🦅🤮🔂📝🩳😈🥿🍎🚴🧹🥢🍡🎖👳😭☔🆓🎙📑🍑🦪🏗🥨🐮🐝🥘🌋🔆🌿👭🚗🔜🍟👐🚱🍢🏐🌶🖊🦥🃏🚒🥀🖇🖱🕠🥅📨🏞📅🗳🐻🧸🦗🥾🙉🌐🛢😐😁🎷🦌😣🏪🚶🌘🚭💄🥍🥕😏🍭📒💳🗓🤓🚞💂🙁🏴🍛🦣🔷🙅👔🆎💝🎣🌏👠😃🎭🤝📗🏙📈🗿🤪🍍🍫🏆👊🕥🔥😟🐆🤬🪅📽🐺⛔🌾🦔💢🤡🌷🌆🙏📌💉😆🗝👾🖍🐩👃🀄🦴🧵🗂🛫🕦🚁🦸🍝🥃🆒🕝⛈🌯🧠🤚🛕🆔🧡👬😅🔙🦞🎈🧕🏖📤🕙😴💓⛲🛥🐡💣🕛🌻🎮🎹😂👏🪤🦐🛴🧀🚌🔮💍🥸🏡🚵🐑⛷🛍🌛🍊🍹⏮🛹🔊📙🏎👡🫓🍋🐏🎳👮⏬🦃🐋🐾⛅🪧😖💊🥩😘👰🏥🏢🆑📊🍚👑🧴🌁🟢🧊⛽👂🧽🏃💶🍄🙎👞⏹🛠🧝🫐🏔👕⏳🎩🍠⭐🥲😵💹🕵🚷📫📓🪢🥞🩺🧛🙌🔭🚄🙍📛🕷😢🔝🎓💚💞😦🧍🔍😽⛵🐲🗒🎎🐓💀👀📐📠🦧🎫🍥🔒😧🗼👼🌳🏘📶🗃🏄💛😇🛡🙇🌄🧻😲😗🚤🌬👷🏧🤟🕯🎂⏲🥂💙🧰🐼🌌📃💵😮🔏👴🐷🌨➕🤘🍬🛖👅🫔🌂🦼🧂🍁☕🩱🧈🥄🔪◾🚇🥔📱🦝🕖🥋💺🥏📦🤨🕐💭💑🍞🌖⏩🍽🟠🥛🐰🔀🍃📷🗻🤢😕🐇🔸🪕🏋🦷🏫👁🌔🔔🎵👄🍏🦿🎲🔅🐳🚟😠🌮🖖🤛🅿⛺🌩🎬🏳😍🐀😜🙈📭🫕🎺📞🧅🖌🪀🎑🌹🌞💜🌈🪑🙃😋🖥😼🪛🍙💏🚖🥵🤔🙄🤜🎻🌃🍶🥝🎧🤯🔑🖤🛵😸📹🚸🛒🥪🎛🌍🐈🍖🏀🤕🎸🍧🎶🐧🦕🧇💒🎒🕴🤸🍳😙🧉🪳`) 4 | -------------------------------------------------------------------------------- /util/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/quackduck/duckcoin/util 2 | 3 | go 1.16 4 | 5 | require ( 6 | go.etcd.io/bbolt v1.3.6 7 | golang.org/x/sys v0.1.0 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /util/go.sum: -------------------------------------------------------------------------------- 1 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 2 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 3 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d h1:L/IKR6COd7ubZrs2oTnTi73IhgqJ71c9s80WsQnh0Es= 4 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 5 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 6 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 8 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 9 | -------------------------------------------------------------------------------- /util/misc.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "encoding/pem" 7 | "errors" 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | // ToJSON is a convenience method for serializing to JSON. 13 | func ToJSON(v interface{}) string { 14 | s, _ := json.MarshalIndent(v, "", " ") 15 | return string(s) 16 | } 17 | 18 | // ArgsHaveOption checks command line arguments for an option 19 | func ArgsHaveOption(long, short string) (hasOption bool, foundAt int) { 20 | for i, arg := range os.Args { 21 | if arg == "--"+long || arg == "-"+short { 22 | return true, i 23 | } 24 | } 25 | return false, 0 26 | } 27 | 28 | // SaveKeyPair saves a key pair using the PEM format 29 | func SaveKeyPair(pubkey, privkey, pubfile, privfile string) error { 30 | // saveKeyPair decodes the keys because PEM base64s them too, and decoding means that the pubkey in duck format is the same as the data in the PEM file. (which is nice but an arbitrary decision) 31 | d, _ := base64.StdEncoding.DecodeString(privkey) 32 | b := pem.EncodeToMemory(&pem.Block{ 33 | Type: "DUCKCOIN (ECDSA) PRIVATE KEY", 34 | Bytes: d, 35 | }) 36 | if err := ioutil.WriteFile(privfile, b, 0600); err != nil { 37 | return err 38 | } 39 | 40 | d, _ = base64.StdEncoding.DecodeString(pubkey) 41 | b = pem.EncodeToMemory(&pem.Block{ 42 | Type: "DUCKCOIN (ECDSA) PUBLIC KEY", 43 | Bytes: d, 44 | }) 45 | if err := ioutil.WriteFile(pubfile, b, 0644); err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | // loadKeyPair loads a key pair from pubfile and privfile 53 | func loadKeyPair(pubfile, privfile string) (pub, priv string, err error) { 54 | // see comment in util.SaveKeyPair for why the keys are base64 encoded before returning 55 | data, err := ioutil.ReadFile(pubfile) 56 | if err != nil { 57 | return "", "", err 58 | } 59 | key, _ := pem.Decode(data) 60 | if key == nil { 61 | return "", "", errors.New("could not decode PEM data from " + pubfile) 62 | } 63 | pubkey := base64.StdEncoding.EncodeToString(key.Bytes) 64 | data, err = ioutil.ReadFile(privfile) 65 | if err != nil { 66 | return "", "", err 67 | } 68 | key, _ = pem.Decode(data) 69 | if key == nil { 70 | return "", "", errors.New("could not decode PEM data from " + privfile) 71 | } 72 | privkey := base64.StdEncoding.EncodeToString(key.Bytes) 73 | return pubkey, privkey, nil 74 | } 75 | 76 | func LoadKeysAndAddr(pubfile, privfile string) (pub, priv string, addr Address, err error) { 77 | pub, priv, err = loadKeyPair(pubfile, privfile) 78 | if err != nil { 79 | return "", "", Address{}, err 80 | } 81 | return pub, priv, KeyToAddress(pub), nil 82 | } 83 | 84 | // IsValidBase64 checks if a string is valid base64 85 | func IsValidBase64(s string) bool { 86 | _, err := base64.StdEncoding.DecodeString(s) 87 | return err == nil 88 | } 89 | -------------------------------------------------------------------------------- /util/storage.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | "strconv" 10 | 11 | bolt "go.etcd.io/bbolt" 12 | ) 13 | 14 | var ( 15 | db *bolt.DB 16 | numToBlock = []byte("num -> block") 17 | hashToNum = []byte("hash -> num") 18 | addrToBalances = []byte("addr -> balances") 19 | 20 | Reward uint64 = 1e6 21 | 22 | genesis = &Sblock{ 23 | Index: 0, 24 | Timestamp: 1633231790000, 25 | Data: "This is the genesis block. Made by Ishan Goel: @quackduck on GitHub. " + 26 | "Thank you to Jason Antwi-Appah for the name \"Duckcoin\", to Arcade Wise, and to Cedric Hutchings." + 27 | "Thank you to friends at Hack Club, and to the Internet. QUACK!", 28 | Hash: "0000000000000000000000000000000000000000000000000000000000000000", 29 | PrevHash: "0000000000000000000000000000000000000000000000000000000000000000", 30 | Solution: 42, // the answer to life, the universe, and everything 31 | Solver: Address{}, // replace with ishan's actual address 32 | Tx: Transaction{ 33 | Data: "Genesis transaction", 34 | Sender: Address{}, 35 | Receiver: Address{}, 36 | Amount: 100000 * 1e6, 37 | PubKey: "", 38 | Signature: "", 39 | }, 40 | } 41 | unconfirmedRewardMap = make(map[Address]uint64, 10) 42 | genesisBalances = map[Address]uint64{ 43 | Address{}: 1000 * MicroquacksPerDuck, 44 | } 45 | ) 46 | 47 | func DBInit() { 48 | var err error 49 | o := bolt.DefaultOptions 50 | o.FreelistType = bolt.FreelistMapType 51 | db, err = bolt.Open("duckchain.db", 0600, o) 52 | if err != nil { 53 | panic(err) 54 | } 55 | if err = db.Update(func(tx *bolt.Tx) error { 56 | if tx.Bucket(numToBlock) != nil { 57 | return nil 58 | } 59 | _, err := tx.CreateBucket(numToBlock) 60 | if err != nil { 61 | return err 62 | } 63 | _, err = tx.CreateBucket(hashToNum) 64 | if err != nil { 65 | return err 66 | } 67 | _, err = tx.CreateBucket(addrToBalances) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if err := tx.Bucket(numToBlock).Put([]byte("newest"), genesis.serialize()); err != nil { 73 | panic(err) 74 | //return err 75 | } 76 | if err := tx.Bucket(numToBlock).Put([]byte("0"), genesis.serialize()); err != nil { 77 | panic(err) 78 | //return err 79 | } 80 | if err := tx.Bucket(hashToNum).Put(serializeHash(genesis.Hash), []byte("0")); err != nil { 81 | panic(err) 82 | //return err 83 | } 84 | for k, v := range genesisBalances { 85 | buf := make([]byte, binary.MaxVarintLen64) 86 | n := binary.PutUvarint(buf, v) 87 | err = tx.Bucket(addrToBalances).Put(k.bytes[:], buf[:n]) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | return err 93 | }); err != nil { 94 | panic(err) 95 | } 96 | err = initUnconfirmedRewardMap() 97 | if err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | func WriteBlockDB(blks ...*Sblock) { 103 | if err := db.Update(func(tx *bolt.Tx) error { 104 | for _, v := range blks { 105 | err := updateUnconfirmedRewardMap(v.Solver, v.Index) 106 | if err != nil { 107 | return err 108 | } 109 | // store the newest block in idx -1 110 | //newestIsGenesis = false 111 | if err = tx.Bucket(numToBlock).Put([]byte("newest"), v.serialize()); err != nil { 112 | panic(err) 113 | //return err 114 | } 115 | 116 | num := []byte(strconv.FormatUint(v.Index, 10)) // TODO: serialize idx num too 117 | if err = tx.Bucket(numToBlock).Put(num, v.serialize()); err != nil { 118 | panic(err) 119 | //return err 120 | } 121 | if err = tx.Bucket(hashToNum).Put(serializeHash(v.Hash), num); err != nil { 122 | panic(err) 123 | //return err 124 | } 125 | if v.Tx.Amount > 0 && v.Tx.Sender != v.Tx.Receiver { 126 | // no reward for solver so we can give that reward to the lnodes (TODO). 127 | err = removeFromBalance(tx, v.Tx.Sender, v.Tx.Amount) 128 | if err != nil { 129 | return err 130 | } 131 | addToBalance(tx, v.Tx.Receiver, v.Tx.Amount) 132 | } else { 133 | addToBalance(tx, v.Solver, Reward) 134 | } 135 | } 136 | return nil 137 | }); err != nil { 138 | panic(err) 139 | } 140 | } 141 | 142 | func setAddressBalance(tx *bolt.Tx, address Address, balance uint64) { 143 | buf := make([]byte, binary.MaxVarintLen64) 144 | n := binary.PutUvarint(buf, balance) 145 | if err := tx.Bucket(addrToBalances).Put(address.bytes[:], buf[:n]); err != nil { 146 | panic(err) 147 | } 148 | } 149 | 150 | func getAddressBalanceWithUnconfirmed(tx *bolt.Tx, address Address) uint64 { 151 | balanceBytes := tx.Bucket(addrToBalances).Get(address.bytes[:]) 152 | if balanceBytes == nil { 153 | return 0 154 | } 155 | balance, _ := binary.Uvarint(balanceBytes) 156 | return balance 157 | } 158 | 159 | func getAddressBalance(tx *bolt.Tx, address Address) uint64 { 160 | return getAddressBalanceWithUnconfirmed(tx, address) - Reward*unconfirmedRewardMap[address] // subtract the unconfirmed reward 161 | } 162 | 163 | func addToBalance(tx *bolt.Tx, address Address, delta uint64) { 164 | if delta == 0 { 165 | return 166 | } 167 | setAddressBalance(tx, address, getAddressBalanceWithUnconfirmed(tx, address)+delta) 168 | } 169 | 170 | func removeFromBalance(tx *bolt.Tx, address Address, delta uint64) error { 171 | if delta == 0 { 172 | return nil 173 | } 174 | currBalance := getAddressBalance(tx, address) 175 | if currBalance < delta { 176 | return errors.New(fmt.Sprint("insufficient balance ", currBalance, " to remove ", delta)) 177 | } 178 | setAddressBalance(tx, address, currBalance-delta) 179 | return nil 180 | } 181 | 182 | func GetSblockByIndex(i uint64) (*Sblock, error) { 183 | ret := new(Sblock) 184 | if err := db.View(func(tx *bolt.Tx) error { 185 | b := tx.Bucket(numToBlock) 186 | data := b.Get([]byte(strconv.FormatUint(i, 10))) 187 | ret = deserializeSblock(data) 188 | if ret == nil { 189 | panic("Nil deserialization at block " + strconv.FormatUint(i, 10)) 190 | } 191 | return nil 192 | }); err != nil { 193 | return nil, err 194 | } 195 | return ret, nil 196 | } 197 | 198 | func GetSblockByHash(hash string) (*Sblock, error) { 199 | ret := new(Sblock) 200 | if err := db.View(func(tx *bolt.Tx) error { 201 | b := tx.Bucket(hashToNum) 202 | data := b.Get(serializeHash(hash)) 203 | i, err := strconv.ParseUint(string(data), 10, 64) 204 | if err != nil { 205 | return err 206 | } 207 | ret, err = GetSblockByIndex(i) 208 | return err 209 | }); err != nil { 210 | return nil, err 211 | } 212 | return ret, nil 213 | } 214 | 215 | func GetNewestSblock() (*Sblock, error) { 216 | ret := new(Sblock) 217 | if err := db.View(func(tx *bolt.Tx) error { 218 | b := tx.Bucket(numToBlock) 219 | data := b.Get([]byte("newest")) 220 | ret = deserializeSblock(data) 221 | if ret == nil { 222 | panic("Nil deserialization at newest block") 223 | } 224 | return nil 225 | }); err != nil { 226 | return nil, err 227 | } 228 | return ret, nil 229 | } 230 | 231 | func GetBalanceByAddr(addr Address) (uint64, error) { 232 | var ret uint64 233 | if err := db.View(func(tx *bolt.Tx) error { 234 | ret = getAddressBalance(tx, addr) 235 | return nil 236 | }); err != nil { 237 | return 0, err 238 | } 239 | return ret, nil 240 | } 241 | 242 | // GetAllBalances returns a map of addresses to the balance in microquacks 243 | func GetAllBalances() map[Address]uint64 { 244 | ret := make(map[Address]uint64, 1000) 245 | // inside func never returns an error 246 | _ = db.View(func(tx *bolt.Tx) error { 247 | b := tx.Bucket(addrToBalances) 248 | return b.ForEach(func(addr, balanceData []byte) error { 249 | balance, _ := binary.Uvarint(balanceData) 250 | address := BytesToAddress(sliceToAddrBytes(addr)) 251 | ret[address] = balance - Reward*unconfirmedRewardMap[address] 252 | return nil 253 | }) 254 | }) 255 | return ret 256 | } 257 | 258 | func initUnconfirmedRewardMap() error { 259 | unconfirmedRewardMap = make(map[Address]uint64, 10) 260 | latest, err := GetNewestSblock() 261 | if err != nil { 262 | return err 263 | } 264 | if latest.Index != 0 { 265 | unconfirmedRewardMap[latest.Solver]++ 266 | } else { 267 | return nil 268 | } 269 | 270 | for i := latest.Index - 1; i > 0 && (i < 10 || i > latest.Index-10); i-- { 271 | block, err := GetSblockByIndex(i) 272 | if err != nil { 273 | return err 274 | } 275 | unconfirmedRewardMap[block.Solver]++ 276 | } 277 | return nil 278 | } 279 | 280 | // allows not-counting the rewards offered by the latest 10 blocks to encourage node cooperation on longest chain 281 | func updateUnconfirmedRewardMap(minerOfNewBlock Address, latestIndex uint64) error { 282 | unconfirmedRewardMap[minerOfNewBlock]++ 283 | // get the 11th block from the top and mark as confirmed 284 | if latestIndex > 10 { 285 | block, err := GetSblockByIndex(latestIndex - 10) // eg. 0, 1 ... 10, and 11 is added. mark 1 as confirmed 286 | if err != nil { 287 | return err 288 | } 289 | unconfirmedRewardMap[block.Solver]-- 290 | } 291 | return nil 292 | } 293 | 294 | // GetAllBalancesFloats returns a map of addresses to the balance in ducks 295 | func GetAllBalancesFloats() map[Address]float64 { 296 | l := GetAllBalances() 297 | balances := make(map[Address]float64, len(l)) 298 | for address, balance := range l { 299 | balances[address] = float64(balance) / float64(MicroquacksPerDuck) 300 | } 301 | return balances 302 | } 303 | 304 | func (b *Sblock) serialize() []byte { 305 | ret := make([]byte, 0, 1024) 306 | 307 | buf := make([]byte, binary.MaxVarintLen64) 308 | n := binary.PutUvarint(buf, b.Index) 309 | ret = append(ret, buf[:n]...) 310 | 311 | buf = make([]byte, binary.MaxVarintLen64) 312 | n = binary.PutUvarint(buf, b.Timestamp) 313 | ret = append(ret, buf[:n]...) 314 | 315 | ret = encodeVarintBytes(ret, []byte(b.Data)) 316 | 317 | hash, ok := new(big.Int).SetString(b.Hash, 16) 318 | if !ok { 319 | panic("Setting big.Int to hash value as hexadecimal failed") 320 | } 321 | hashBytes := hash.Bytes() 322 | ret = encodeVarintBytes(ret, hashBytes) 323 | 324 | hash, ok = new(big.Int).SetString(b.PrevHash, 16) 325 | if !ok { 326 | panic("Setting big.Int to hash value as hexadecimal failed") 327 | } 328 | hashBytes = hash.Bytes() 329 | ret = encodeVarintBytes(ret, hashBytes) 330 | 331 | buf = make([]byte, binary.MaxVarintLen64) 332 | n = binary.PutUvarint(buf, b.Solution) 333 | ret = append(ret, buf[:n]...) 334 | 335 | ret = encodeVarintBytes(ret, b.Solver.bytes[:]) 336 | if b.Tx.Amount != 0 { 337 | ret = append(ret, 1) // marker that Tx exists and should be deserialized 338 | ret = encodeVarintBytes(ret, []byte(b.Tx.Data), b.Tx.Sender.bytes[:], b.Tx.Receiver.bytes[:]) 339 | 340 | buf = make([]byte, binary.MaxVarintLen64) 341 | n = binary.PutUvarint(buf, b.Tx.Amount) 342 | ret = append(ret, buf[:n]...) 343 | ret = encodeVarintBytes(ret, deb64(b.Tx.PubKey), deb64(b.Tx.Signature)) 344 | } else { 345 | ret = append(ret, 0) // marker that Tx does not exist and should not be deserialized 346 | } 347 | return ret 348 | } 349 | 350 | func serializeHash(hash string) []byte { 351 | hashInt, ok := new(big.Int).SetString(hash, 16) 352 | if !ok { 353 | panic("Setting big.Int to hash value as hexadecimal failed") 354 | } 355 | return encodeVarintBytes(make([]byte, 0, 10), hashInt.Bytes()) 356 | } 357 | 358 | func encodeVarintBytes(writeTo []byte, data ...[]byte) []byte { 359 | //buf := make([]byte, binary.MaxVarintLen64) 360 | var buf []byte 361 | for _, elem := range data { 362 | buf = make([]byte, binary.MaxVarintLen64) 363 | n := binary.PutUvarint(buf, uint64(len(elem))) 364 | writeTo = append(writeTo, buf[:n]...) 365 | writeTo = append(writeTo, elem...) 366 | } 367 | return writeTo 368 | } 369 | 370 | func deserializeSblock(buf []byte) *Sblock { 371 | if len(buf) == 0 { 372 | return nil 373 | } 374 | var data []byte 375 | ret := new(Sblock) 376 | 377 | i, length := binary.Uvarint(buf) 378 | ret.Index = i 379 | buf = buf[length:] 380 | 381 | i, length = binary.Uvarint(buf) 382 | ret.Timestamp = i 383 | buf = buf[length:] 384 | 385 | buf, data = decodeVarintBytes(buf) 386 | ret.Data = string(data) 387 | 388 | buf, data = decodeVarintBytes(buf) 389 | ret.Hash = fmt.Sprintf("%064s", new(big.Int).SetBytes(data).Text(16)) 390 | 391 | buf, data = decodeVarintBytes(buf) 392 | ret.PrevHash = fmt.Sprintf("%064s", new(big.Int).SetBytes(data).Text(16)) 393 | 394 | i, length = binary.Uvarint(buf) 395 | ret.Timestamp = i 396 | buf = buf[length:] 397 | ret.Solution = i 398 | 399 | buf, data = decodeVarintBytes(buf) 400 | ret.Solver = BytesToAddress(sliceToAddrBytes(data)) 401 | 402 | if buf[0] == 1 { // marker that tx data exists 403 | buf = buf[1:] 404 | buf, data = decodeVarintBytes(buf) 405 | ret.Tx.Data = string(data) 406 | 407 | buf, data = decodeVarintBytes(buf) 408 | ret.Tx.Sender = BytesToAddress(sliceToAddrBytes(data)) 409 | 410 | buf, data = decodeVarintBytes(buf) 411 | ret.Tx.Receiver = BytesToAddress(sliceToAddrBytes(data)) 412 | 413 | i, length = binary.Uvarint(buf) 414 | ret.Tx.Amount = i 415 | buf = buf[length:] 416 | 417 | buf, data = decodeVarintBytes(buf) 418 | ret.Tx.PubKey = b64(data) 419 | 420 | _, data = decodeVarintBytes(buf) 421 | ret.Tx.Signature = b64(data) 422 | } 423 | return ret 424 | } 425 | 426 | func (b *Lblock) serialize() []byte { 427 | ret := make([]byte, 0, 1024) 428 | 429 | buf := make([]byte, binary.MaxVarintLen64) 430 | n := binary.PutUvarint(buf, b.Index) 431 | ret = append(ret, buf[:n]...) 432 | 433 | buf = make([]byte, binary.MaxVarintLen64) 434 | n = binary.PutUvarint(buf, b.Timestamp) 435 | ret = append(ret, buf[:n]...) 436 | 437 | ret = encodeVarintBytes(ret, []byte(b.Data)) 438 | 439 | hash, ok := new(big.Int).SetString(b.Hash, 16) 440 | if !ok { 441 | panic("Setting big.Int to hash value as hexadecimal failed") 442 | } 443 | hashBytes := hash.Bytes() 444 | ret = encodeVarintBytes(ret, hashBytes) 445 | 446 | hash, ok = new(big.Int).SetString(b.PrevHash, 16) 447 | if !ok { 448 | panic("Setting big.Int to hash value as hexadecimal failed") 449 | } 450 | hashBytes = hash.Bytes() 451 | ret = encodeVarintBytes(ret, hashBytes) 452 | 453 | buf = make([]byte, binary.MaxVarintLen64) 454 | n = binary.PutUvarint(buf, b.Solution) 455 | ret = append(ret, buf[:n]...) 456 | 457 | ret = encodeVarintBytes(ret, b.Solver.bytes[:]) 458 | if len(b.Sblocks) != 0 { 459 | ret = append(ret, 1) // marker that Sblocks exist and should be deserialized 460 | 461 | blocks := make([]byte, 0, 10*len(b.Sblocks)*512) 462 | 463 | buf = make([]byte, binary.MaxVarintLen64) 464 | n = binary.PutUvarint(buf, uint64(len(b.Sblocks))) 465 | blocks = append(blocks, buf[:n]...) 466 | 467 | for _, sb := range b.Sblocks { 468 | blocks = encodeVarintBytes(blocks, sb.serialize()) 469 | } 470 | ret = encodeVarintBytes(ret, blocks) 471 | } else { 472 | ret = append(ret, 0) // marker that sblocks do not exist and should not be deserialized 473 | } 474 | return ret 475 | } 476 | 477 | func deserializeLblock(buf []byte) *Lblock { 478 | if len(buf) == 0 { 479 | return nil 480 | } 481 | var data []byte 482 | ret := new(Lblock) 483 | 484 | i, length := binary.Uvarint(buf) 485 | ret.Index = i 486 | buf = buf[length:] 487 | 488 | i, length = binary.Uvarint(buf) 489 | ret.Timestamp = i 490 | buf = buf[length:] 491 | 492 | buf, data = decodeVarintBytes(buf) 493 | ret.Data = string(data) 494 | 495 | buf, data = decodeVarintBytes(buf) 496 | ret.Hash = fmt.Sprintf("%064s", new(big.Int).SetBytes(data).Text(16)) 497 | 498 | buf, data = decodeVarintBytes(buf) 499 | ret.PrevHash = fmt.Sprintf("%064s", new(big.Int).SetBytes(data).Text(16)) 500 | 501 | i, length = binary.Uvarint(buf) 502 | ret.Timestamp = i 503 | buf = buf[length:] 504 | ret.Solution = i 505 | 506 | buf, data = decodeVarintBytes(buf) 507 | ret.Solver = BytesToAddress(sliceToAddrBytes(data)) 508 | 509 | if buf[0] == 1 { // marker that sblock data exists 510 | buf = buf[1:] 511 | 512 | _, blocks := decodeVarintBytes(buf) 513 | 514 | i, length = binary.Uvarint(blocks) 515 | ret.Sblocks = make([]*Sblock, i) 516 | blocks = blocks[length:] 517 | 518 | for j := uint64(0); j < i; j++ { 519 | blocks, data = decodeVarintBytes(blocks) 520 | ret.Sblocks[j] = deserializeSblock(data) 521 | } 522 | } 523 | return ret 524 | } 525 | 526 | func deb64(s string) []byte { 527 | b, err := base64.StdEncoding.DecodeString(s) 528 | if err != nil { 529 | //fmt.Println(s, err) 530 | panic(err) 531 | } 532 | return b 533 | } 534 | 535 | func b64(b []byte) string { 536 | return base64.StdEncoding.EncodeToString(b) 537 | } 538 | 539 | // decodeVarintBytes returns the next chunk decoded from the format chunk len (as varint) + chunk. 540 | // It advances buf to the next chunk and returns it too. 541 | func decodeVarintBytes(readFrom []byte) (newBuf []byte, data []byte) { 542 | i, length := binary.Uvarint(readFrom) 543 | dataLen := int(i) 544 | readFrom = readFrom[length:] 545 | dataBytes := readFrom[:dataLen] 546 | readFrom = readFrom[dataLen:] 547 | return readFrom, dataBytes 548 | } 549 | --------------------------------------------------------------------------------