├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchclient ├── README.md ├── accountcmds.go ├── bankcmds.go ├── benchclient.go ├── channelcmds.go ├── ordercmds.go └── protocolcmds.go ├── chainutils ├── coins.go ├── hostparams.go └── scripts.go ├── cmd ├── frred │ ├── README.md │ ├── frred.go │ └── frredinit.go ├── ocx │ ├── README.md │ ├── accountcmds.go │ ├── auctioncmds.go │ ├── bankcmds.go │ ├── channelcmds.go │ ├── ocx.go │ ├── ocxinit.go │ ├── ordercmds.go │ └── shell.go └── opencxd │ ├── README.md │ ├── opencxd.go │ └── opencxdinit.go ├── crypto ├── README.md ├── hashtimelock │ ├── hashtimelock.go │ └── hashtimelock_test.go ├── provisions │ └── assets.go ├── rsw │ ├── rswtimelock.go │ ├── rswtimelock_test.go │ └── timelockverify.go ├── timelock.go └── timelockencoders │ ├── asymmetric.go │ ├── asymmetric_test.go │ ├── blockciphers.go │ └── blockciphers_test.go ├── cxauctionrpc ├── cxauctionrpc.go ├── listener.go ├── ordercmds.go └── paramcmds.go ├── cxauctionserver ├── auctionserver.go ├── auctionserver_test.go ├── batcher.go ├── clock.go ├── orders.go └── orders_test.go ├── cxbenchmark ├── .gitignore ├── README.md ├── aucmatchingbenchmark.go ├── aucmatchingbenchmark_test.go ├── matchingbenchmark.go ├── matchingbenchmark_test.go ├── serversetup.go ├── setup.go └── whitelist.go ├── cxdb ├── README.md ├── cxdb.go ├── cxdbmemory │ ├── auctionengine.go │ ├── auctionorderbook.go │ ├── auctionorders.go │ ├── bank.go │ ├── db.go │ ├── pinkyswear.go │ ├── pinkyswear_test.go │ ├── puzzlestore.go │ └── settlementengine.go └── cxdbsql │ ├── README.md │ ├── auctionengine.go │ ├── auctionengine_test.go │ ├── auctionorderbook.go │ ├── db_test.go │ ├── dbconfig.go │ ├── depositstore.go │ ├── depositstore_test.go │ ├── limitengine.go │ ├── limitengine_test.go │ ├── limitorderbook.go │ ├── puzzlestore.go │ ├── settlementengine.go │ └── settlementstore.go ├── cxnoise ├── LICENSE ├── README.md ├── conn.go ├── listener.go ├── noise.go └── noise_test.go ├── cxrpc ├── README.md ├── accountcmds.go ├── bankcmds.go ├── channelcmds.go ├── cxrpc.go ├── listener.go ├── opencxclient.go └── ordercmds.go ├── cxserver ├── balance.go ├── debitcredit.go ├── deposits.go ├── handlers.go ├── ingests.go ├── keymgmt.go ├── keys.go ├── lightning.go ├── orders.go ├── price.go ├── server.go ├── wallets.go └── withdraw.go ├── go.mod ├── go.sum ├── logging └── logging.go └── match ├── algorithms.go ├── algorithms_test.go ├── auctionbatch.go ├── auctionid.go ├── auctionid_test.go ├── auctionorder.go ├── auctionorder_test.go ├── book.go ├── cancel.go ├── commitresponse.go ├── consts.go ├── deposit.go ├── encryptedauctionorder.go ├── encryptedauctionorder_test.go ├── encsolorder.go ├── engine.go ├── entry.go ├── execution.go ├── limitorder.go ├── limitorderbook.go ├── limitorderidpair.go ├── nfrtranscript.go ├── nfrtranscript_test.go ├── orderid.go ├── pair.go ├── pair_test.go ├── price.go ├── price_test.go ├── pricetime.go ├── puzsolorder.go ├── settlementexec.go ├── settleresult.go ├── settletype.go ├── side.go ├── side_test.go └── withdrawal.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Unignore all dirs 2 | !*.* 3 | 4 | # Unignore all files with extensions 5 | !*/ 6 | 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # Test binary, build with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Don't include the binaries 21 | opencxd 22 | ocx 23 | frred 24 | 25 | !opencxd/ 26 | !ocx/ 27 | !frred/ 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | # This starts mysql 4 | services: 5 | - mysql 6 | 7 | # This creates the opencx test user 8 | before_install: 9 | - mysql -u root -e "GRANT SELECT, INSERT, UPDATE, CREATE, DROP, DELETE ON *.* TO 'opencx'@'localhost' IDENTIFIED BY 'testpass';" 10 | 11 | # Force-enable Go modules. 12 | # This will be unnecessary when Go 1.13 lands. 13 | env: 14 | - GO111MODULE=on 15 | 16 | go: 17 | - 1.12.x 18 | 19 | # script always runs to completion 20 | script: 21 | - go test -v -race -timeout 35m ./... # Run all the tests with the race detector enabled 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to OpenCX 2 | 3 | Thanks so much for considering to help out with OpenCX! 4 | 5 | Please create issues for bugs and feature requests, and pull requests if you think you've solved one of the issues or have some other valuable contribution. 6 | 7 | In any case, if there's something that you would like to see in OpenCX, **please make an issue for it!** If you want to change the code in any way, **please make a pull request!** 8 | 9 | That being said, there are some things that would make a PR more likely to be merged: 10 | * You must use [gofmt](https://golang.org/cmd/gofmt/) on your code. 11 | * Please adhere to the [official commentary guidelines](https://golang.org/doc/effective_go.html#commentary). 12 | * Pull requests should be based off of, and should merge into the master branch. 13 | * Please run [go vet](https://golang.org/cmd/vet/) on your code before submitting a pull request. It helps the code to stay clean and consistent. 14 | 15 | If you see anything that does not adhere to this, please make an issue for it. 16 | Pull requests that improve anything on the [go report card](https://goreportcard.com/report/github.com/mit-dci/opencx) are welcomed! 17 | 18 | For security-related issues, please do not file an issue, and instead contact the maintainer. A SECURITY.md file is being worked on. 19 | 20 | ## Good content for pull requests 21 | 22 | If you don't know what to work on, here are some things that generally increase the quality of the codebase: 23 | * **TESTS!** 24 | * Anything that fixes an important issue 25 | * Anything that increases modularity and/or robustness in some part of the code 26 | * Documentation for packages and other things 27 | * Updates to this document that may help other contributors or developers get started 28 | * Anything that fixes a TODO in the code 29 | - If you find a TODO, feel free to create an issue for it! Sometimes they're just left there and get forgotten. Some TODOs might already be fixed, so if you create an issue and it turns out the TODO was out of date, then you probably shouldn't work on it. 30 | 31 | ## Who is the maintainer? 32 | 33 | Currently, [rjected](https://github.com/rjected) is the maintainer. 34 | You can contact them by [email!](mailto:dan@dancline.net) 35 | 36 | ## How can I get my pull request merged? 37 | 38 | 1 reviewer will have to review your code, and someone with write access will merge it. 39 | Discussion on pull requests is expected to happen in the comments of the pull request. 40 | 41 | ## Some small style notes 42 | 43 | Much of the repository has named function parameters and return variables, as well as one-liner if statements. Here's an example of all three in action: 44 | ```golang 45 | // AvoidHelloWorld is an example function that errors out when input is "hello world" 46 | func AvoidHelloWorld(named string) (err error) { 47 | if named == "hello world" { 48 | err = fmt.Error("The forbidden string appeared in AvoidHelloWorld") 49 | return 50 | } 51 | return 52 | } 53 | 54 | // SomethingElseUseful takes the named string, passes it through AvoidHelloWorld, and appends " world" to it. 55 | func SomethingElseUseful(named string) (value string, err error) { 56 | if err = AvoidHelloWorld(named); err != nil { 57 | err = fmt.Errorf("Error calling AvoidHelloWorld for SomethingElseUseful: %s", err) 58 | return 59 | } 60 | 61 | value = named + " world" 62 | 63 | return 64 | } 65 | ``` 66 | 67 | A lot of the code should be consistent in style, so you may be asked to modify code in pull requests that do not adhere to this style. 68 | 69 | The standard libraries are pretty good, and cryptography tends to be hard, so please don't reinvent the wheel unless you have to. However, there are some things that aren't robust, compatible, or don't exist yet, and those are the exceptions to the rule. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 The MIT Digital Currency Initiative @ Media Lab 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 | # opencx 2 | 3 | [![Build Status](https://travis-ci.org/mit-dci/opencx.svg?branch=master)](https://travis-ci.org/mit-dci/opencx) 4 | [![License](http://img.shields.io/badge/License-MIT-brightgreen.svg)](./LICENSE) 5 | [![GoDoc](https://godoc.org/github.com/mit-dci/opencx?status.svg)](https://godoc.org/github.com/mit-dci/opencx) 6 | 7 | 8 | Cryptocurrency exchanges are some of the largest businesses in the cryptocurrency space, and their reserves are often viewed as "honeypots" of cryptocurrencies. 9 | Because of this, cryptocurrency exchanges have been a hotbed of crime in the form of hacks, front-running, wash trading, fake orderbooks, and much more. 10 | In order for cryptocurrency to be successful, we need safe, trustworthy ways to exchange cryptocurrencies, without fear that coins will be stolen, or trades executed unfairly. 11 | Additionally, the vast majority of exchange software is closed-source, and exchanges have historically not implemented technological upgrades that would substantially decrease risk for users. 12 | 13 | OpenCX hopes to solve this problem by making it trivially easy to run a secure, scalable cryptocurrency exchange which implements many of these features, including: 14 | 15 | - Layer two compatibility 16 | - Non-custodial exchange 17 | - Anti front-running 18 | - Public orderbook auditability 19 | - *More to come...* 20 | 21 | Additionally, all of the components of OpenCX are designed to be swappable, secure, and scalable. 22 | The goal is to fit those requirements and implement features similar to that of modern cryptocurrency exchanges, while keeping high quality software. 23 | 24 | **DO NOT use in a production environment, this project is a work in progress!** 25 | 26 | *Pull requests and issues encouraged!* 27 | 28 | ## Demo 29 | 30 | ![gif of program in normal use](../assets/opencxdemo.gif?raw=true) 31 | 32 | ## Contributing 33 | 34 | Please see the 35 | [contributing](./CONTRIBUTING.md) 36 | file to get started with contributing! 37 | 38 | # Setup 39 | 40 | ## Requirements 41 | 42 | - Go 1.12+ 43 | - A MySQL Database (not needed for client) 44 | - GMP (GNU Multiple Precision Arithmetic Library) 45 | 46 | ## Installing 47 | 48 | ### Installing GMP 49 | 50 | #### Debian 51 | 52 | ```sh 53 | sudo apt-get install libgmp3-dev 54 | ``` 55 | 56 | #### macOS 57 | 58 | ```sh 59 | brew install gmp 60 | ``` 61 | 62 | ### Clone repo and install dependencies 63 | 64 | ```sh 65 | git clone git@github.com/mit-dci/opencx.git 66 | cd opencx 67 | go get -v ./... 68 | ``` 69 | 70 | ## Running opencx server / exchange 71 | 72 | You will need to run MariaDB or any other MySQL database in-order to run the server. You can configure authentication details for your database at `~/.opencx/db/sqldb.conf` 73 | 74 | ### Start your database (MariaDB in this case) 75 | 76 | #### Linux 77 | 78 | ```sh 79 | sudo systemctl start mariadb 80 | ``` 81 | 82 | #### macOS 83 | 84 | ```sh 85 | mysql.server start 86 | ``` 87 | 88 | ### Now build and run opencx 89 | 90 | ```sh 91 | go build ./cmd/opencxd/... 92 | ./opencxd 93 | ``` 94 | 95 | ## Running opencx CLI client 96 | 97 | ```sh 98 | go build ./cmd/ocx/... 99 | ./ocx 100 | ``` 101 | 102 | You can now issue any of the commands in the cxrpc README.md file. 103 | 104 | ## Configuration 105 | 106 | There are configuration options (both command line and .conf) for the client and the server, and by default home folders for these files will be created at `~/.opencx/opencxd/` and `~/.opencx/ocx/` respectively. You can decide whether or not to use the 107 | [NOISE protocol](http://www.noiseprotocol.org/) 108 | for authentication, which hostnames and ports to use for connecting to certain clients, which coins you would like to support, and whether or not to support lightning. 109 | 110 | If you'd like to add your own coins, just add a coinparam struct like in `lit`. 111 | -------------------------------------------------------------------------------- /benchclient/README.md: -------------------------------------------------------------------------------- 1 | # BenchClient 2 | 3 | BenchClient is a go API for use in benchmarking. All of its methods call RPC Commands from a running server. You could use it as a generic golang client API as well, if you'd like to build your own client. It supports the same methods that `ocx` does, and shares a lot of the same code, but it doesn't take argument lists and instead takes in actual arguments. -------------------------------------------------------------------------------- /benchclient/accountcmds.go: -------------------------------------------------------------------------------- 1 | package benchclient 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/opencx/cxrpc" 7 | ) 8 | 9 | // Register registers for an account 10 | func (cl *BenchClient) Register(signature []byte) (registerReply *cxrpc.RegisterReply, err error) { 11 | 12 | if cl.PrivKey == nil { 13 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 14 | return 15 | } 16 | 17 | // sign it 18 | 19 | // now send it back to prove your knowledge of discrete logarithm of your public key, AKA Prove you know your privkey by signing this message 20 | registerReply = new(cxrpc.RegisterReply) 21 | registerArgs := &cxrpc.RegisterArgs{ 22 | Signature: signature, 23 | } 24 | 25 | if err = cl.Call("OpencxRPC.Register", registerArgs, registerReply); err != nil { 26 | return 27 | } 28 | 29 | return 30 | } 31 | 32 | // GetRegistrationString gets the registration string that needs to be signed in order to be registered on the exchange 33 | func (cl *BenchClient) GetRegistrationString() (getRegistrationStringReply *cxrpc.GetRegistrationStringReply, err error) { 34 | 35 | getRegistrationStringReply = new(cxrpc.GetRegistrationStringReply) 36 | getRegistrationStringArgs := &cxrpc.GetRegistrationStringArgs{} 37 | 38 | if err = cl.Call("OpencxRPC.GetRegistrationString", getRegistrationStringArgs, getRegistrationStringReply); err != nil { 39 | return 40 | } 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /benchclient/bankcmds.go: -------------------------------------------------------------------------------- 1 | package benchclient 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/crypto/koblitz" 7 | "github.com/mit-dci/opencx/cxrpc" 8 | "github.com/mit-dci/opencx/match" 9 | "golang.org/x/crypto/sha3" 10 | ) 11 | 12 | // GetBalance calls the getbalance rpc command 13 | func (cl *BenchClient) GetBalance(asset string) (getBalanceReply *cxrpc.GetBalanceReply, err error) { 14 | 15 | if cl.PrivKey == nil { 16 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 17 | return 18 | } 19 | 20 | getBalanceReply = new(cxrpc.GetBalanceReply) 21 | getBalanceArgs := &cxrpc.GetBalanceArgs{ 22 | Asset: asset, 23 | } 24 | 25 | // create e = hash(m) 26 | sha3 := sha3.New256() 27 | sha3.Write([]byte(asset)) 28 | e := sha3.Sum(nil) 29 | 30 | // Sign 31 | compactSig, err := koblitz.SignCompact(koblitz.S256(), cl.PrivKey, e, false) 32 | 33 | // set signature in args 34 | getBalanceArgs.Signature = compactSig 35 | 36 | if err = cl.Call("OpencxRPC.GetBalance", getBalanceArgs, getBalanceReply); err != nil { 37 | return 38 | } 39 | 40 | return 41 | } 42 | 43 | // GetDepositAddress calls the getdepositaddress rpc command 44 | func (cl *BenchClient) GetDepositAddress(asset string) (getDepositAddressReply *cxrpc.GetDepositAddressReply, err error) { 45 | 46 | if cl.PrivKey == nil { 47 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 48 | return 49 | } 50 | 51 | getDepositAddressReply = new(cxrpc.GetDepositAddressReply) 52 | getDepositAddressArgs := &cxrpc.GetDepositAddressArgs{ 53 | Asset: asset, 54 | } 55 | 56 | // create e = hash(m) 57 | sha3 := sha3.New256() 58 | sha3.Write([]byte(asset)) 59 | e := sha3.Sum(nil) 60 | 61 | // Sign order 62 | compactSig, err := koblitz.SignCompact(koblitz.S256(), cl.PrivKey, e, false) 63 | 64 | // set signature in args 65 | getDepositAddressArgs.Signature = compactSig 66 | 67 | if err = cl.Call("OpencxRPC.GetDepositAddress", getDepositAddressArgs, getDepositAddressReply); err != nil { 68 | return 69 | } 70 | 71 | return 72 | } 73 | 74 | // GetAllBalances get the balance for every token 75 | func (cl *BenchClient) GetAllBalances() (balances map[string]uint64, err error) { 76 | 77 | if cl.PrivKey == nil { 78 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 79 | return 80 | } 81 | 82 | balances = make(map[string]uint64) 83 | var reply *cxrpc.GetBalanceReply 84 | if reply, err = cl.GetBalance("regtest"); err != nil { 85 | return 86 | } 87 | 88 | balances["regtest"] = reply.Amount 89 | 90 | if reply, err = cl.GetBalance("vtcreg"); err != nil { 91 | return 92 | } 93 | balances["vtcreg"] = reply.Amount 94 | 95 | if reply, err = cl.GetBalance("litereg"); err != nil { 96 | return 97 | } 98 | balances["litereg"] = reply.Amount 99 | 100 | return 101 | } 102 | 103 | // Withdraw calls the withdraw rpc command 104 | func (cl *BenchClient) Withdraw(amount uint64, asset match.Asset, address string) (withdrawReply *cxrpc.WithdrawReply, err error) { 105 | 106 | if cl.PrivKey == nil { 107 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 108 | return 109 | } 110 | 111 | withdrawReply = new(cxrpc.WithdrawReply) 112 | withdrawArgs := &cxrpc.WithdrawArgs{ 113 | Withdrawal: &match.Withdrawal{ 114 | Amount: amount, 115 | Asset: asset, 116 | Address: address, 117 | Lightning: false, 118 | }, 119 | } 120 | 121 | // create e = hash(m) 122 | sha3 := sha3.New256() 123 | sha3.Write(withdrawArgs.Withdrawal.Serialize()) 124 | e := sha3.Sum(nil) 125 | 126 | // Sign order 127 | compactSig, err := koblitz.SignCompact(koblitz.S256(), cl.PrivKey, e, false) 128 | 129 | // set signature in args 130 | withdrawArgs.Signature = compactSig 131 | 132 | if err = cl.Call("OpencxRPC.Withdraw", withdrawArgs, withdrawReply); err != nil { 133 | return 134 | } 135 | 136 | if withdrawReply.Txid == "" { 137 | err = fmt.Errorf("Error: Unsupported Asset") 138 | return 139 | } 140 | 141 | return 142 | } 143 | 144 | // WithdrawLightning calls the withdraw rpc command, but with the lightning boolean set to true 145 | func (cl *BenchClient) WithdrawLightning(amount uint64, asset match.Asset) (withdrawReply *cxrpc.WithdrawReply, err error) { 146 | 147 | if cl.PrivKey == nil { 148 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 149 | return 150 | } 151 | 152 | withdrawReply = new(cxrpc.WithdrawReply) 153 | withdrawArgs := &cxrpc.WithdrawArgs{ 154 | Withdrawal: &match.Withdrawal{ 155 | Amount: amount, 156 | Asset: asset, 157 | Lightning: true, 158 | }, 159 | } 160 | 161 | // create e = hash(m) 162 | sha3 := sha3.New256() 163 | sha3.Write(withdrawArgs.Withdrawal.Serialize()) 164 | e := sha3.Sum(nil) 165 | 166 | // Sign order 167 | compactSig, err := koblitz.SignCompact(koblitz.S256(), cl.PrivKey, e, false) 168 | 169 | // set signature in args 170 | withdrawArgs.Signature = compactSig 171 | 172 | if err = cl.Call("OpencxRPC.Withdraw", withdrawArgs, withdrawReply); err != nil { 173 | return 174 | } 175 | 176 | if withdrawReply.Txid == "" { 177 | err = fmt.Errorf("Error: Unsupported Asset") 178 | return 179 | } 180 | 181 | return 182 | } 183 | -------------------------------------------------------------------------------- /benchclient/benchclient.go: -------------------------------------------------------------------------------- 1 | package benchclient 2 | 3 | import ( 4 | "github.com/mit-dci/lit/crypto/koblitz" 5 | "github.com/mit-dci/opencx/cxrpc" 6 | ) 7 | 8 | // BenchClient holds the RPC Client and defines many methods that can be called 9 | type BenchClient struct { 10 | hostname string 11 | port uint16 12 | RPCClient cxrpc.OpencxClient 13 | PrivKey *koblitz.PrivateKey 14 | } 15 | 16 | // SetupBenchClient creates a new BenchClient for use as an RPC Client 17 | func (cl *BenchClient) SetupBenchClient(server string, port uint16) (err error) { 18 | cl.RPCClient = new(cxrpc.OpencxRPCClient) 19 | cl.hostname = server 20 | cl.port = port 21 | 22 | // we set the privkey here because we aren't using a command line to send orders 23 | if err = cl.RPCClient.SetupConnection(server, port); err != nil { 24 | return 25 | } 26 | 27 | return 28 | } 29 | 30 | // SetupBenchNoiseClient create a new BenchClient for use as an RPC-Noise Client 31 | func (cl *BenchClient) SetupBenchNoiseClient(server string, port uint16) (err error) { 32 | 33 | // Authenticate with the same key that BenchClient uses for signatures 34 | noiseClient := new(cxrpc.OpencxNoiseClient) 35 | if err = noiseClient.SetKey(cl.PrivKey); err != nil { 36 | return 37 | } 38 | 39 | // Now that the key is set we can start doing stuff. 40 | cl.RPCClient = noiseClient 41 | cl.hostname = server 42 | cl.port = port 43 | 44 | // we set the privkey here because we aren't using a command line to send orders 45 | if err = cl.RPCClient.SetupConnection(server, port); err != nil { 46 | return 47 | } 48 | 49 | return 50 | } 51 | 52 | // Call calls a method from the rpc client 53 | func (cl *BenchClient) Call(serviceMethod string, args interface{}, reply interface{}) error { 54 | return cl.RPCClient.Call(serviceMethod, args, reply) 55 | } 56 | 57 | // GetHostname returns the hostname, used for convenience I guess, maybe move out of benchclient and into ocx? 58 | func (cl *BenchClient) GetHostname() string { 59 | return cl.hostname 60 | } 61 | 62 | // GetPort returns the port, used for convenience I guess, maybe move out of benchclient and into ocx? 63 | func (cl *BenchClient) GetPort() uint16 { 64 | return cl.port 65 | } 66 | -------------------------------------------------------------------------------- /benchclient/channelcmds.go: -------------------------------------------------------------------------------- 1 | package benchclient 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/opencx/cxrpc" 7 | ) 8 | 9 | // GetLitConnection gets the lit con to pass in to lit. Maybe do this more automatically later on 10 | func (cl *BenchClient) GetLitConnection() (getLitConnectionReply *cxrpc.GetLitConnectionReply, err error) { 11 | getLitConnectionReply = new(cxrpc.GetLitConnectionReply) 12 | getLitConnectionArgs := &cxrpc.GetLitConnectionArgs{} 13 | 14 | if err = cl.Call("OpencxRPC.GetLitConnection", getLitConnectionArgs, getLitConnectionReply); err != nil { 15 | return 16 | } 17 | 18 | return 19 | } 20 | 21 | // WithdrawToLightningNode takes in some arguments such as public key, amount, and ln node address 22 | func (cl *BenchClient) WithdrawToLightningNode() (withdrawToLightningNodeReply *cxrpc.WithdrawToLightningNodeReply, err error) { 23 | 24 | if cl.PrivKey == nil { 25 | err = fmt.Errorf("Private key nonexistent, set or specify private key so the client can sign commands") 26 | return 27 | } 28 | 29 | withdrawToLightningNodeReply = new(cxrpc.WithdrawToLightningNodeReply) 30 | withdrawToLightningNodeArgs := &cxrpc.WithdrawToLightningNodeArgs{} 31 | 32 | if err = cl.Call("OpencxRPC.WithdrawToLightningNode", withdrawToLightningNodeArgs, withdrawToLightningNodeReply); err != nil { 33 | return 34 | } 35 | 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /benchclient/protocolcmds.go: -------------------------------------------------------------------------------- 1 | package benchclient 2 | 3 | import ( 4 | "github.com/mit-dci/opencx/cxauctionrpc" 5 | "github.com/mit-dci/opencx/match" 6 | ) 7 | 8 | // GetPublicParameters returns the public parameters like the auction time and current auction ID 9 | func (cl *BenchClient) GetPublicParameters(pair *match.Pair) (getPublicParametersReply *cxauctionrpc.GetPublicParametersReply, err error) { 10 | getPublicParametersReply = new(cxauctionrpc.GetPublicParametersReply) 11 | getPublicParametersArgs := &cxauctionrpc.GetPublicParametersArgs{ 12 | Pair: *pair, 13 | } 14 | 15 | // Actually use the RPC Client to call the method 16 | if err = cl.Call("OpencxAuctionRPC.GetPublicParameters", getPublicParametersArgs, getPublicParametersReply); err != nil { 17 | return 18 | } 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /chainutils/coins.go: -------------------------------------------------------------------------------- 1 | package chainutils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | ) 8 | 9 | // GetParamFromName gets coin params from a name 10 | func GetParamFromName(name string) (coinType *coinparam.Params, err error) { 11 | // create map for that O(1) access 12 | coinMap := map[string]*coinparam.Params{ 13 | coinparam.BitcoinParams.Name: &coinparam.BitcoinParams, 14 | coinparam.VertcoinParams.Name: &coinparam.VertcoinParams, 15 | // coinparam.LitecoinParams.Name: &coinparam.LitecoinParams, 16 | coinparam.TestNet3Params.Name: &coinparam.TestNet3Params, 17 | coinparam.VertcoinTestNetParams.Name: &coinparam.VertcoinTestNetParams, 18 | coinparam.LiteCoinTestNet4Params.Name: &coinparam.LiteCoinTestNet4Params, 19 | coinparam.RegressionNetParams.Name: &coinparam.RegressionNetParams, 20 | coinparam.VertcoinRegTestParams.Name: &coinparam.VertcoinRegTestParams, 21 | coinparam.LiteRegNetParams.Name: &coinparam.LiteRegNetParams, 22 | } 23 | 24 | // grab from map 25 | var found bool 26 | if coinType, found = coinMap[name]; !found { 27 | err = fmt.Errorf("Coin not found when trying to get from name, maybe it's not supported yet") 28 | return 29 | } 30 | 31 | return 32 | } 33 | 34 | // GetParamFromHDCoinType gets coin params from a hdCoinType 35 | func GetParamFromHDCoinType(hdCoinType uint32) (coinType *coinparam.Params, err error) { 36 | // create map for that O(1) access 37 | coinMap := map[uint32]*coinparam.Params{ 38 | coinparam.BitcoinParams.HDCoinType: &coinparam.BitcoinParams, 39 | coinparam.VertcoinParams.HDCoinType: &coinparam.VertcoinParams, 40 | // coinparam.LitecoinParams.HDCoinType: &coinparam.LitecoinParams, 41 | coinparam.TestNet3Params.HDCoinType: &coinparam.TestNet3Params, 42 | coinparam.VertcoinTestNetParams.HDCoinType: &coinparam.VertcoinTestNetParams, 43 | coinparam.LiteCoinTestNet4Params.HDCoinType: &coinparam.LiteCoinTestNet4Params, 44 | coinparam.RegressionNetParams.HDCoinType: &coinparam.RegressionNetParams, 45 | coinparam.VertcoinRegTestParams.HDCoinType: &coinparam.VertcoinRegTestParams, 46 | coinparam.LiteRegNetParams.HDCoinType: &coinparam.LiteRegNetParams, 47 | } 48 | 49 | // grab from map 50 | var found bool 51 | if coinType, found = coinMap[hdCoinType]; !found { 52 | err = fmt.Errorf("Coin not found when trying to get from hdCoinType, maybe it's not supported yet") 53 | return 54 | } 55 | 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /chainutils/hostparams.go: -------------------------------------------------------------------------------- 1 | package chainutils 2 | 3 | import ( 4 | "github.com/mit-dci/lit/coinparam" 5 | ) 6 | 7 | // HostParams are a struct that hold a param and a host, so we can automate the creation of wallets and chainhooks, 8 | // as well as better keep track of stuff. 9 | type HostParams struct { 10 | Param *coinparam.Params 11 | Host string 12 | } 13 | 14 | // HostParamList exists so we can easily, with utils, get a list of coin params separated from the hosts. 15 | type HostParamList []*HostParams 16 | 17 | // CoinListFromHostParams generates a list of coinparams from an existing host param list. 18 | func (hpList HostParamList) CoinListFromHostParams() (coinList []*coinparam.Params) { 19 | for _, hostParam := range hpList { 20 | if hostParam.Host != "" { 21 | coinList = append(coinList, hostParam.Param) 22 | } 23 | } 24 | return 25 | } 26 | 27 | func HostParamsFromCoinList(coinList []*coinparam.Params) (hParams []*HostParams) { 28 | hParams = make([]*HostParams, len(coinList)) 29 | // if you want to go in circles with this method and CoinListFromHostParams, you're losing information 30 | for i, coin := range coinList { 31 | hParams[i] = &HostParams{ 32 | Param: coin, 33 | // Default host 34 | Host: "", 35 | } 36 | } 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /chainutils/scripts.go: -------------------------------------------------------------------------------- 1 | package chainutils 2 | 3 | // ScriptType takes in a script and returns "P2PKH", "P2WPKH", "P2SH", "P2WSH", "P2PK", "INVALID" denoting the type of transaction it is, and the relevant non opcode data 4 | func ScriptType(pkScript []byte) (string, []byte) { 5 | 6 | if len(pkScript) == 22 && pkScript[0] == 0x00 && pkScript[1] == 0x14 { 7 | return "P2WPKH", pkScript[2:22] 8 | } 9 | 10 | if len(pkScript) == 23 && pkScript[0] == 0xa9 && pkScript[1] == 0x14 && pkScript[22] == 0x87 { 11 | return "P2SH", pkScript[2:22] 12 | } 13 | 14 | if len(pkScript) == 25 && pkScript[0] == 0x76 && pkScript[1] == 0xa9 && pkScript[2] == 0x14 && pkScript[23] == 0x88 && pkScript[24] == 0xac { 15 | return "P2PKH", pkScript[3:23] 16 | } 17 | 18 | if len(pkScript) == 34 && pkScript[0] == 0x00 && pkScript[1] == 0x20 { 19 | return "P2WSH", pkScript[2:34] 20 | } 21 | 22 | if len(pkScript) == 67 && pkScript[0] == 0x41 && pkScript[66] == 0xac { 23 | return "P2PK", pkScript[1:66] 24 | } 25 | 26 | return "INVALID", nil 27 | } 28 | -------------------------------------------------------------------------------- /cmd/frred/frredinit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/mit-dci/lit/coinparam" 9 | util "github.com/mit-dci/opencx/chainutils" 10 | 11 | flags "github.com/jessevdk/go-flags" 12 | "github.com/mit-dci/lit/lnutil" 13 | litLogging "github.com/mit-dci/lit/logging" 14 | "github.com/mit-dci/opencx/logging" 15 | ) 16 | 17 | var ( 18 | 19 | // used in init file, so separate 20 | defaultLogLevel = 0 21 | defaultLitLogLevel = 0 22 | defaultConfigFilename = "frred.conf" 23 | defaultLogFilename = "dblog.txt" 24 | defaultKeyFileName = "privkey.hex" 25 | ) 26 | 27 | // createDefaultConfigFile creates a config file -- only call this if the 28 | // config file isn't already there 29 | func createDefaultConfigFile(destinationPath string) error { 30 | 31 | dest, err := os.OpenFile(filepath.Join(destinationPath, defaultConfigFilename), 32 | os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 33 | if err != nil { 34 | return err 35 | } 36 | defer dest.Close() 37 | 38 | writer := bufio.NewWriter(dest) 39 | defaultArgs := []byte("tn3=y\ntvtc=y") 40 | _, err = writer.Write(defaultArgs) 41 | if err != nil { 42 | return err 43 | } 44 | writer.Flush() 45 | return nil 46 | } 47 | 48 | func opencxSetup(conf *frredConfig) *[32]byte { 49 | // Pre-parse the command line options to see if an alternative config 50 | // file or the version flag was specified. Config file will be read later 51 | // and cli options would be parsed again below 52 | 53 | parser := newConfigParser(conf, flags.Default) 54 | 55 | if _, err := parser.ParseArgs(os.Args); err != nil { 56 | // catch all cli argument errors 57 | logging.Fatal(err) 58 | } 59 | 60 | // set default log level 61 | logging.SetLogLevel(defaultLogLevel) 62 | 63 | // create home directory 64 | _, err := os.Stat(conf.FrredHomeDir) 65 | if err != nil { 66 | logging.Infof("Creating a home directory at %s", conf.FrredHomeDir) 67 | } 68 | if os.IsNotExist(err) { 69 | os.MkdirAll(conf.FrredHomeDir, 0700) 70 | logging.Infof("Creating a new config file") 71 | if err := createDefaultConfigFile(conf.FrredHomeDir); err != nil { 72 | logging.Fatalf("Error creating a default config file in %v: %s", conf.FrredHomeDir, err) 73 | } 74 | } 75 | 76 | if _, err := os.Stat(filepath.Join(conf.FrredHomeDir, defaultConfigFilename)); os.IsNotExist(err) { 77 | // if there is no config file found over at the directory, create one 78 | logging.Infof("Creating a new config file") 79 | err := createDefaultConfigFile(filepath.Join(conf.FrredHomeDir)) // Source of error 80 | if err != nil { 81 | logging.Fatal(err) 82 | } 83 | } 84 | conf.ConfigFile = filepath.Join(conf.FrredHomeDir, defaultConfigFilename) 85 | // lets parse the config file provided, if any 86 | err = flags.NewIniParser(parser).ParseFile(conf.ConfigFile) 87 | if err != nil { 88 | _, ok := err.(*os.PathError) 89 | if !ok { 90 | logging.Fatal(err) 91 | } 92 | } 93 | 94 | // Parse command line options again to ensure they take precedence. 95 | _, err = parser.ParseArgs(os.Args) // returns invalid flags 96 | if err != nil { 97 | logging.Fatal(err) 98 | } 99 | 100 | logFilePath := filepath.Join(conf.FrredHomeDir, conf.LogFilename) 101 | logFile, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 102 | defer logFile.Close() 103 | logging.SetLogFile(logFile) 104 | 105 | logLevel := defaultLogLevel 106 | if len(conf.LogLevel) == 1 { // -v 107 | logLevel = 1 108 | } else if len(conf.LogLevel) == 2 { // -vv 109 | logLevel = 2 110 | } else if len(conf.LogLevel) >= 3 { // -vvv 111 | logLevel = 3 112 | } 113 | logging.SetLogLevel(logLevel) // defaults to defaultLogLevel 114 | 115 | litLogLevel := defaultLitLogLevel 116 | if len(conf.LitLogLevel) == 1 { // -w 117 | litLogLevel = 1 118 | } else if len(conf.LitLogLevel) == 2 { // -ww 119 | litLogLevel = 2 120 | } else if len(conf.LitLogLevel) >= 3 { // -www 121 | litLogLevel = 3 122 | } 123 | litLogging.SetLogLevel(litLogLevel) // defaults to defaultLitLogLevel 124 | 125 | keyPath := filepath.Join(conf.FrredHomeDir, defaultKeyFileName) 126 | privkey, err := lnutil.ReadKeyFile(keyPath) 127 | if err != nil { 128 | logging.Fatalf("Error reading key from file: \n%s", err) 129 | } 130 | 131 | return privkey 132 | } 133 | 134 | func generateCoinList(conf *frredConfig) []*coinparam.Params { 135 | return util.HostParamList(generateHostParams(conf)).CoinListFromHostParams() 136 | } 137 | 138 | func generateHostParams(conf *frredConfig) (hostParamList []*util.HostParams) { 139 | // Regular networks (Just like don't use any of these, I support them though) 140 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.BitcoinParams, Host: conf.Btchost}) 141 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.VertcoinParams, Host: conf.Vtchost}) 142 | // Wait until supported by lit 143 | // hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.LitecoinParams, Host: conf.Ltchost}) 144 | 145 | // Test nets 146 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.TestNet3Params, Host: conf.Tn3host}) 147 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.VertcoinTestNetParams, Host: conf.Tvtchost}) 148 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.LiteCoinTestNet4Params, Host: conf.Lt4host}) 149 | 150 | // Regression nets 151 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.RegressionNetParams, Host: conf.Reghost}) 152 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.VertcoinRegTestParams, Host: conf.Rtvtchost}) 153 | hostParamList = append(hostParamList, &util.HostParams{Param: &coinparam.LiteRegNetParams, Host: conf.Litereghost}) 154 | return 155 | } 156 | -------------------------------------------------------------------------------- /cmd/ocx/README.md: -------------------------------------------------------------------------------- 1 | # ocx 2 | 3 | **ocx** is a command-line client for many RPC commands which OpenCX RPC packages support. 4 | **ocx** is currently compatible with both commands in `cxrpc` as well as some in `cxauctionrpc`, so it can be used for both servers running `frred` or `opencxd`. -------------------------------------------------------------------------------- /cmd/ocx/accountcmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/lnutil" 7 | "github.com/mit-dci/opencx/cxrpc" 8 | "github.com/mit-dci/opencx/logging" 9 | ) 10 | 11 | var registerCommand = &Command{ 12 | Format: fmt.Sprintf("%s\n", lnutil.Red("register")), 13 | Description: fmt.Sprintf("%s\n%s\n", 14 | "Register the public key associated with your private key as an identity on the exchange.", 15 | "You will use your private key to sign commands that require authorization.", 16 | ), 17 | ShortDescription: fmt.Sprintf("%s\n", "Register yourself on the exchange"), 18 | } 19 | 20 | // Register registers the user for an account with a username and password 21 | func (cl *ocxClient) Register(args []string) (err error) { 22 | if err = cl.UnlockKey(); err != nil { 23 | logging.Fatalf("Could not unlock key! Fatal!") 24 | } 25 | var regStringReply *cxrpc.GetRegistrationStringReply 26 | if regStringReply, err = cl.RPCClient.GetRegistrationString(); err != nil { 27 | return 28 | } 29 | 30 | var sig []byte 31 | if sig, err = cl.SignBytes([]byte(regStringReply.RegistrationString)); err != nil { 32 | return 33 | } 34 | 35 | // if there is ever a reply for register uncomment this and replace the _ 36 | // var registerReply *cxrpc.RegisterReply 37 | if _, err = cl.RPCClient.Register(sig); err != nil { 38 | return 39 | } 40 | 41 | logging.Infof("Successfully registered\n") 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /cmd/ocx/auctioncmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | "github.com/mit-dci/lit/lnutil" 9 | "github.com/mit-dci/opencx/cxauctionrpc" 10 | "github.com/mit-dci/opencx/logging" 11 | "github.com/mit-dci/opencx/match" 12 | ) 13 | 14 | var placeAuctionOrderCommand = &Command{ 15 | Format: fmt.Sprintf("%s%s%s%s%s\n", lnutil.Red("placeauctionorder"), lnutil.ReqColor("side"), lnutil.ReqColor("pair"), lnutil.ReqColor("amounthave"), lnutil.ReqColor("price")), 16 | Description: fmt.Sprintf("%s\n%s\n", 17 | "Submit a front-running resistant auction order with side \"buy\" or side \"sell\", for pair \"asset1\"/\"asset2\", where you give up amounthave of \"asset1\" (if on buy side) or \"asset2\" if on sell side, for the other token at a specific price.", 18 | "This will return an order ID which can be used as input to cancelorder, or getorder.", 19 | ), 20 | ShortDescription: fmt.Sprintf("%s\n", "Place a front-running resistant order on the exchange."), 21 | } 22 | 23 | // OrderCommand submits an order (for now) 24 | func (cl *ocxClient) AuctionOrderCommand(args []string) (err error) { 25 | if err = cl.UnlockKey(); err != nil { 26 | logging.Fatalf("Could not unlock key! Fatal!") 27 | } 28 | 29 | side := args[0] 30 | pair := args[1] 31 | 32 | amountHave, err := strconv.ParseUint(args[2], 10, 64) 33 | if err != nil { 34 | err = fmt.Errorf("Error parsing amountHave, please enter something valid:\n%s", err) 35 | return 36 | } 37 | 38 | price, err := strconv.ParseFloat(args[3], 64) 39 | if err != nil { 40 | err = fmt.Errorf("Error parsing price: \n%s", err) 41 | return 42 | } 43 | 44 | var pubkey *koblitz.PublicKey 45 | if pubkey, err = cl.RetrievePublicKey(); err != nil { 46 | return 47 | } 48 | 49 | pairParam := new(match.Pair) 50 | if err = pairParam.FromString(pair); err != nil { 51 | err = fmt.Errorf("Error parsing pair, please enter something valid: %s", err) 52 | return 53 | } 54 | 55 | var paramreply *cxauctionrpc.GetPublicParametersReply 56 | if paramreply, err = cl.RPCClient.GetPublicParameters(pairParam); err != nil { 57 | err = fmt.Errorf("Error getting public parameters before placing auction order: %s", err) 58 | return 59 | } 60 | 61 | // we ignore reply because there's nothing in it and we don't use it 62 | // var reply *cxauctionrpc.SubmitPuzzledOrderReply 63 | if _, err = cl.RPCClient.AuctionOrderCommand(pubkey, side, pair, amountHave, price, paramreply.AuctionTime, paramreply.AuctionID); err != nil { 64 | return 65 | } 66 | 67 | logging.Infof("Successfully placed auction order") 68 | 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /cmd/ocx/bankcmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | 8 | "github.com/mit-dci/lit/lnutil" 9 | 10 | "github.com/mit-dci/opencx/cxrpc" 11 | "github.com/mit-dci/opencx/logging" 12 | "github.com/mit-dci/opencx/match" 13 | ) 14 | 15 | var getBalanceCommand = &Command{ 16 | Format: fmt.Sprintf("%s%s\n", lnutil.Red("getbalance"), lnutil.ReqColor("asset")), 17 | Description: fmt.Sprintf("%s\n", 18 | "Get your balance of asset. You must be registered.", 19 | ), 20 | ShortDescription: fmt.Sprintf("%s\n", "Get your balance of asset. You must be registered to run this command."), 21 | } 22 | 23 | func (cl *ocxClient) GetBalance(args []string) (err error) { 24 | if err = cl.UnlockKey(); err != nil { 25 | logging.Fatalf("Could not unlock key! Fatal!") 26 | } 27 | 28 | asset := args[0] 29 | 30 | var balanceReply *cxrpc.GetBalanceReply 31 | if balanceReply, err = cl.RPCClient.GetBalance(asset); err != nil { 32 | return 33 | } 34 | 35 | logging.Infof("Balance for token %s: %f %s\n", asset, float64(balanceReply.Amount)/math.Pow10(8), asset) 36 | return 37 | } 38 | 39 | var getDepositAddressCommand = &Command{ 40 | Format: fmt.Sprintf("%s%s\n", lnutil.Red("getdepositaddress"), lnutil.ReqColor("asset")), 41 | Description: fmt.Sprintf("%s\n%s\n", 42 | "Get the deposit address for the given asset.", 43 | "Once you send to this, you will have to wait a certain number of confirmations and then you will be able to trade your coins.", 44 | ), 45 | ShortDescription: fmt.Sprintf("%s\n", "Get the deposit address for the given asset."), 46 | } 47 | 48 | func (cl *ocxClient) GetDepositAddress(args []string) (err error) { 49 | if err = cl.UnlockKey(); err != nil { 50 | logging.Fatalf("Could not unlock key! Fatal!") 51 | } 52 | 53 | asset := args[0] 54 | 55 | var getDepositAddressReply *cxrpc.GetDepositAddressReply 56 | if getDepositAddressReply, err = cl.RPCClient.GetDepositAddress(asset); err != nil { 57 | return 58 | } 59 | 60 | logging.Infof("DepositAddress for token %s: %s\n", asset, getDepositAddressReply.Address) 61 | return 62 | } 63 | 64 | var getAllBalancesCommand = &Command{ 65 | Format: fmt.Sprintf("%s\n", lnutil.Red("getallbalances")), 66 | Description: fmt.Sprintf("%s\n", 67 | "Get balances for all tokens supported on the exchange.", 68 | ), 69 | ShortDescription: fmt.Sprintf("%s\n", "Get balances for all tokens supported on the exchange."), 70 | } 71 | 72 | // GetAllBalances get the balance for every token 73 | func (cl *ocxClient) GetAllBalances(args []string) (err error) { 74 | if err = cl.UnlockKey(); err != nil { 75 | logging.Fatalf("Could not unlock key! Fatal!") 76 | } 77 | 78 | var getAllBalancesReply map[string]uint64 79 | if getAllBalancesReply, err = cl.RPCClient.GetAllBalances(); err != nil { 80 | return 81 | } 82 | 83 | for asset, amount := range getAllBalancesReply { 84 | logging.Infof("Balance for token %s: %f %s\n", asset, float64(amount)/math.Pow10(8), asset) 85 | } 86 | 87 | return 88 | } 89 | 90 | var withdrawCommand = &Command{ 91 | Format: fmt.Sprintf("%s%s%s%s\n", lnutil.Red("withdraw"), lnutil.ReqColor("amount"), lnutil.ReqColor("asset"), lnutil.ReqColor("recvaddress")), 92 | Description: fmt.Sprintf("%s\n%s\n", 93 | "Withdraw amount of asset into recvaddress.", 94 | "Make sure you feel your asset has enough confirmations such that it has been confirmed.", 95 | ), 96 | ShortDescription: fmt.Sprintf("%s\n", "Withdraw amount of asset into recvaddress."), 97 | } 98 | 99 | func (cl *ocxClient) Withdraw(args []string) (err error) { 100 | if err = cl.UnlockKey(); err != nil { 101 | logging.Fatalf("Could not unlock key! Fatal!") 102 | } 103 | 104 | var amount uint64 105 | if amount, err = strconv.ParseUint(args[0], 10, 64); err != nil { 106 | return 107 | } 108 | 109 | var asset match.Asset 110 | if asset, err = match.AssetFromString(args[1]); err != nil { 111 | return 112 | } 113 | address := args[2] 114 | 115 | var withdrawReply *cxrpc.WithdrawReply 116 | if withdrawReply, err = cl.RPCClient.Withdraw(amount, asset, address); err != nil { 117 | return 118 | } 119 | 120 | logging.Infof("Withdraw transaction ID: %s\n", withdrawReply.Txid) 121 | return 122 | } 123 | 124 | var litWithdrawCommand = &Command{ 125 | Format: fmt.Sprintf("%s%s%s\n", lnutil.Red("litwithdraw"), lnutil.ReqColor("amount"), lnutil.ReqColor("asset")), 126 | Description: fmt.Sprintf("%s\n%s\n%s\n", 127 | "This assumes you're using the same keys as your lightning node, and your node should be running.", 128 | "This creates a channel with you with the amount you specified.", 129 | "Make sure you feel your asset has enough confirmations such that it has been confirmed.", 130 | ), 131 | ShortDescription: fmt.Sprintf("%s\n", "Withdraw amount of asset for lightning."), 132 | } 133 | 134 | func (cl *ocxClient) LitWithdraw(args []string) (err error) { 135 | if err = cl.UnlockKey(); err != nil { 136 | logging.Fatalf("Could not unlock key! Fatal!") 137 | } 138 | 139 | var amount uint64 140 | if amount, err = strconv.ParseUint(args[0], 10, 64); err != nil { 141 | return 142 | } 143 | 144 | var asset match.Asset 145 | if asset, err = match.AssetFromString(args[1]); err != nil { 146 | return 147 | } 148 | 149 | var withdrawReply *cxrpc.WithdrawReply 150 | if withdrawReply, err = cl.RPCClient.WithdrawLightning(amount, asset); err != nil { 151 | return 152 | } 153 | 154 | logging.Infof("Withdraw transaction ID: %s\n", withdrawReply.Txid) 155 | return 156 | } 157 | -------------------------------------------------------------------------------- /cmd/ocx/channelcmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/lnutil" 7 | "github.com/mit-dci/opencx/cxrpc" 8 | "github.com/mit-dci/opencx/logging" 9 | ) 10 | 11 | var getLitConnectionCommand = &Command{ 12 | Format: fmt.Sprintf("%s\n", lnutil.Red("getlitconnection")), 13 | Description: fmt.Sprintf("%s\n%s\n", 14 | "Get the lightning node address and hostname in order to connect to the exchange and open up a channel.", 15 | "Once connected, you can push funds to the exchange as a deposit.", 16 | ), 17 | ShortDescription: fmt.Sprintf("%s\n", "Get the lightning node address and hostname in order to connect to the exchange and open up a channel."), 18 | } 19 | 20 | func (cl *ocxClient) GetLitConnection(args []string) (err error) { 21 | getLitConnectionReply := new(cxrpc.GetLitConnectionReply) 22 | 23 | if getLitConnectionReply, err = cl.RPCClient.GetLitConnection(); err != nil { 24 | return 25 | } 26 | 27 | for _, port := range getLitConnectionReply.Ports { 28 | logging.Infof("Exchange Listener: con %s@%s:%d", getLitConnectionReply.PubKeyHash, cl.RPCClient.GetHostname(), port) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ocx/ocxinit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "path/filepath" 7 | 8 | flags "github.com/jessevdk/go-flags" 9 | "github.com/mit-dci/opencx/logging" 10 | ) 11 | 12 | // createDefaultConfigFile creates a config file -- only call this if the 13 | // config file isn't already there 14 | func createDefaultConfigFile(destinationPath string) error { 15 | 16 | dest, err := os.OpenFile(filepath.Join(destinationPath, defaultConfigFilename), 17 | os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 18 | if err != nil { 19 | return err 20 | } 21 | defer dest.Close() 22 | 23 | writer := bufio.NewWriter(dest) 24 | defaultArgs := []byte("rpchost=hubris.media.mit.edu\nrpcport=12345") 25 | 26 | if _, err = writer.Write(defaultArgs); err != nil { 27 | return err 28 | } 29 | writer.Flush() 30 | return nil 31 | } 32 | 33 | func ocxSetup(conf *ocxConfig) (help bool) { 34 | // Pre-parse the command line options to see if an alternative config 35 | // file or the version flag was specified. Config file will be read later 36 | // and cli options would be parsed again below 37 | 38 | parser := newConfigParser(conf, flags.Default) 39 | 40 | if _, err := parser.ParseArgs(os.Args); err != nil { 41 | // catch all cli argument errors 42 | // logging.Fatal(err) 43 | // this just prints it out twice 44 | if help = flags.WroteHelp(err); help { 45 | return 46 | } 47 | logging.Fatalf("Error parsing args: \n%s", err) 48 | } 49 | 50 | // create home directory 51 | _, err := os.Stat(conf.OcxHomeDir) 52 | if err != nil { 53 | logging.Infof("Creating a home directory at %s", conf.OcxHomeDir) 54 | } 55 | if os.IsNotExist(err) { 56 | os.MkdirAll(conf.OcxHomeDir, 0700) 57 | logging.Infof("Creating a new config file") 58 | if err := createDefaultConfigFile(conf.OcxHomeDir); err != nil { 59 | logging.Fatalf("Error creating a default config file in %v: %s", conf.OcxHomeDir, err) 60 | } 61 | } 62 | 63 | if _, err := os.Stat(filepath.Join(conf.OcxHomeDir, defaultConfigFilename)); os.IsNotExist(err) { 64 | // if there is no config file found over at the directory, create one 65 | logging.Infof("Creating a new config file") 66 | err := createDefaultConfigFile(filepath.Join(conf.OcxHomeDir)) // Source of error 67 | if err != nil { 68 | logging.Fatal(err) 69 | } 70 | } 71 | conf.ConfigFile = filepath.Join(conf.OcxHomeDir, defaultConfigFilename) 72 | // lets parse the config file provided, if any 73 | err = flags.NewIniParser(parser).ParseFile(conf.ConfigFile) 74 | if err != nil { 75 | _, ok := err.(*os.PathError) 76 | if !ok { 77 | logging.Fatal(err) 78 | } 79 | } 80 | 81 | // Parse command line options again to ensure they take precedence. 82 | _, err = parser.ParseArgs(os.Args) // returns invalid flags 83 | if err != nil { 84 | // logging.Fatal(err) 85 | // This just prints it out again. 86 | return 87 | } 88 | 89 | logFilePath := filepath.Join(conf.OcxHomeDir, conf.LogFilename) 90 | logFile, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 91 | defer logFile.Close() 92 | logging.SetLogFile(logFile) 93 | 94 | logLevel := 2 95 | if len(conf.LogLevel) == 1 { // -v 96 | logLevel = 1 97 | } else if len(conf.LogLevel) == 2 { // -vv 98 | logLevel = 2 99 | } else if len(conf.LogLevel) >= 3 { // -vvv 100 | logLevel = 3 101 | } 102 | logging.SetLogLevel(logLevel) // defaults to 2 103 | 104 | return 105 | } 106 | -------------------------------------------------------------------------------- /cmd/opencxd/README.md: -------------------------------------------------------------------------------- 1 | # opencxd 2 | 3 | **opencxd** is the OpenCX Daemon. It runs a cryptocurrency exchange with various configurable features. 4 | **opencxd** is closest to a "normal" centralized cryptocurrency exchange. -------------------------------------------------------------------------------- /crypto/README.md: -------------------------------------------------------------------------------- 1 | # crypto 2 | 3 | The crypto package currently has an interface for Timelock Puzzles, and an implementation of both the RCW96 timelock puzzle and a simple hash-based timelock puzzle. In the case of the hash-based timelock puzzle, it takes just as long to create the puzzle (if you are encrypting information with the result) as it does to solve it. With RCW96, this is not the case. It's supposed to be similar to interact with as the golang built-in `crypto` library. -------------------------------------------------------------------------------- /crypto/hashtimelock/hashtimelock.go: -------------------------------------------------------------------------------- 1 | package hashtimelock 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "hash" 8 | 9 | "github.com/mit-dci/opencx/crypto" 10 | ) 11 | 12 | // HashTimelock is a struct that holds all data necessary to implement a timelock 13 | type HashTimelock struct { 14 | // timelockSeed is the initial data that then gets hashed by a hash function 15 | TimelockSeed []byte 16 | // hashFunction is the hash function to be used by this hash timelock 17 | hashFunction hash.Hash 18 | // timeToRun is the amount of iterations needed to run 19 | TimeToRun uint64 20 | } 21 | 22 | func (ht *HashTimelock) setupHashPuzzle(seed []byte, hashFunction hash.Hash) (err error) { 23 | if hashFunction == nil { 24 | err = fmt.Errorf("Error, annot create new timelock puzzle with nil hash function") 25 | return 26 | } 27 | ht.TimelockSeed = seed 28 | ht.hashFunction = hashFunction 29 | return 30 | } 31 | 32 | // New creates a new hash timelock with seed bytes and a hash function 33 | func New(seed []byte, hashFunction hash.Hash) (hashTimelock crypto.Timelock, err error) { 34 | if hashFunction == nil { 35 | err = fmt.Errorf("Error, annot create new timelock puzzle with nil hash function") 36 | return 37 | } 38 | ht := &HashTimelock{} 39 | if err = ht.setupHashPuzzle(seed, hashFunction); err != nil { 40 | err = fmt.Errorf("Error setting up hash puzzle while creating a timelock: %s", err) 41 | return 42 | } 43 | hashTimelock = ht 44 | return 45 | } 46 | 47 | // SetHashFunction sets the hash function for the timelock puzzle 48 | func (ht *HashTimelock) SetHashFunction(hashFunction hash.Hash) { 49 | ht.hashFunction = hashFunction 50 | return 51 | } 52 | 53 | // SetupTimelockPuzzle sends key k to the future in time t, returning a puzzle based on sequential hashing and an answer 54 | func (ht *HashTimelock) SetupTimelockPuzzle(t uint64) (puzzle crypto.Puzzle, answer []byte, err error) { 55 | if ht.hashFunction == nil { 56 | err = fmt.Errorf("Error, hash function is nil, cannot setup timelock puzzle") 57 | return 58 | } 59 | ht.TimeToRun = t 60 | answer = make([]byte, ht.hashFunction.Size()) 61 | 62 | copy(answer[:], ht.TimelockSeed) 63 | 64 | for i := uint64(0); i < ht.TimeToRun; i++ { 65 | ht.hashFunction.Reset() 66 | ht.hashFunction.Write(answer[:]) 67 | copy(answer[:], ht.hashFunction.Sum(nil)) 68 | } 69 | // hash time lock puzzles are their own timelocks as well as puzzles 70 | puzzle = ht 71 | return 72 | } 73 | 74 | // Solve solves the hash puzzle and returns the answer, or fails 75 | func (ht *HashTimelock) Solve() (answer []byte, err error) { 76 | if ht.hashFunction == nil { 77 | err = fmt.Errorf("Error, hash function is nil, cannot setup timelock puzzle") 78 | return 79 | } 80 | answer = make([]byte, ht.hashFunction.Size()) 81 | copy(answer[:], ht.TimelockSeed) 82 | for i := uint64(0); i < ht.TimeToRun; i++ { 83 | ht.hashFunction.Reset() 84 | ht.hashFunction.Write(answer[:]) 85 | copy(answer[:], ht.hashFunction.Sum(nil)) 86 | } 87 | ht.hashFunction.Reset() 88 | return 89 | } 90 | 91 | // Serialize turns the hash timelock puzzle into something that can be sent over the wire 92 | func (ht *HashTimelock) Serialize() (raw []byte, err error) { 93 | var b bytes.Buffer 94 | 95 | // register hashTimelock interface 96 | gob.Register(HashTimelock{}) 97 | 98 | // create a new encoder writing to the buffer 99 | enc := gob.NewEncoder(&b) 100 | 101 | // encode the puzzle in the buffer 102 | if err = enc.Encode(ht); err != nil { 103 | err = fmt.Errorf("Error encoding puzzle: %s", err) 104 | return 105 | } 106 | 107 | // Get the bytes from the buffer 108 | raw = b.Bytes() 109 | 110 | return 111 | } 112 | 113 | // Deserialize turns the hash timelock puzzle into something that can be sent over the wire 114 | func (ht *HashTimelock) Deserialize(raw []byte) (err error) { 115 | var b *bytes.Buffer 116 | b = bytes.NewBuffer(raw) 117 | 118 | // register hashTimelock interface 119 | gob.Register(HashTimelock{}) 120 | 121 | // create a new encoder writing to the buffer 122 | dec := gob.NewDecoder(b) 123 | 124 | // encode the puzzle in the buffer 125 | if err = dec.Decode(ht); err != nil { 126 | err = fmt.Errorf("Error encoding puzzle: %s", err) 127 | return 128 | } 129 | 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /crypto/provisions/assets.go: -------------------------------------------------------------------------------- 1 | package provisions 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "fmt" 8 | "math/big" 9 | "sync" 10 | 11 | "github.com/mit-dci/zksigma" 12 | ) 13 | 14 | // BalProofMachine represents a single iteration of the proof of assets protocol, and is 15 | // used to compute individual balance commitments, as well as calculate things like 16 | // responses to challenges 17 | type BalProofMachine struct { 18 | u1 *big.Int 19 | u2 *big.Int 20 | u3 *big.Int 21 | u4 *big.Int 22 | ci *big.Int 23 | ti *big.Int 24 | xi *big.Int 25 | } 26 | 27 | // NewBalProofMachine creates a new balance proof machine 28 | func NewBalProofMachine(curve elliptic.Curve) (machine *BalProofMachine, err error) { 29 | order := curve.Params().P 30 | if machine.u1, err = rand.Int(rand.Reader, order); err != nil { 31 | err = fmt.Errorf("Error getting random u_1 for balance proof machine: %s", err) 32 | return 33 | } 34 | 35 | if machine.u2, err = rand.Int(rand.Reader, order); err != nil { 36 | err = fmt.Errorf("Error getting random u_2 for balance proof machine: %s", err) 37 | return 38 | } 39 | 40 | if machine.u3, err = rand.Int(rand.Reader, order); err != nil { 41 | err = fmt.Errorf("Error getting random u_3 for balance proof machine: %s", err) 42 | return 43 | } 44 | 45 | if machine.u4, err = rand.Int(rand.Reader, order); err != nil { 46 | err = fmt.Errorf("Error getting random u_4 for balance proof machine: %s", err) 47 | return 48 | } 49 | 50 | return 51 | } 52 | 53 | // SetChallenge sets the challenge so we can generate responses 54 | func (machine *BalProofMachine) SetChallenge(ci *big.Int) (err error) { 55 | if ci == nil { 56 | err = fmt.Errorf("Challenge cannot be nil") 57 | return 58 | } 59 | machine.ci = ci 60 | return 61 | } 62 | 63 | // SetPrivKey sets the private key for this iteration 64 | func (machine *BalProofMachine) SetPrivKey(privkey *ecdsa.PrivateKey) (err error) { 65 | if privkey == nil { 66 | err = fmt.Errorf("Private key cannot be nil") 67 | return 68 | } 69 | machine.xi = privkey.D 70 | return 71 | } 72 | 73 | // SResponse generates the response r_(s_i) with the balance proof machine and si (s_i). The challenge must be set. 74 | func (machine *BalProofMachine) SResponse(si bool) (rs *big.Int, err error) { 75 | if machine.ci == nil { 76 | err = fmt.Errorf("Cannot generate a response to a challenge if the challenge has not been set") 77 | return 78 | } 79 | 80 | if si { 81 | rs = new(big.Int).Add(machine.u1, machine.ci) 82 | } else { 83 | rs = new(big.Int).Set(machine.u1) 84 | } 85 | 86 | return 87 | } 88 | 89 | // AssetsProofMachine is the state machine that is used to create a privacy preserving proof of assets 90 | type AssetsProofMachine struct { 91 | curve elliptic.Curve 92 | // This is the anonymity set PK in the paper, !(privkey == nil) => s_i = 0 93 | // We don't use a privkey set because we are going to want to index by pubkey a lot 94 | PubKeyAnonSet map[*ecdsa.PublicKey]*ecdsa.PrivateKey 95 | pkAnonSetMutex *sync.Mutex 96 | BalanceRetreiver func(pubkey *ecdsa.PublicKey) (balance uint64, err error) 97 | } 98 | 99 | // NewAssetsProofMachine creates a new state machine for the asset proof. TODO: Use the anonymity set choosing from 100 | // the paper to initialize 101 | func NewAssetsProofMachine(curve elliptic.Curve) (machine *AssetsProofMachine, err error) { 102 | 103 | machine = &AssetsProofMachine{ 104 | curve: curve, 105 | PubKeyAnonSet: make(map[*ecdsa.PublicKey]*ecdsa.PrivateKey), 106 | pkAnonSetMutex: new(sync.Mutex), 107 | BalanceRetreiver: bal, 108 | } 109 | 110 | return 111 | } 112 | 113 | // bal is supposed to find the balance of a pubkey. I'm going to assume that all addresses have 10 bitcoin (1000000000) 114 | // in them for now. TODO: Make bal() work in a production environment 115 | func bal(pubkey *ecdsa.PublicKey) (bal uint64, err error) { 116 | 117 | // all pubkeys have 10 btc -- we measure in satoshis 118 | bal = 1000000000 119 | 120 | return 121 | } 122 | 123 | // calculateAssets calculates the assets that the exchange owns. This will be committed to. 124 | func (machine *AssetsProofMachine) calculateAssets() (totalAssets uint64, err error) { 125 | 126 | machine.pkAnonSetMutex.Lock() 127 | for pub, priv := range machine.PubKeyAnonSet { 128 | // priv == nil => s_i == 0 129 | // so if priv != nil then add to the total assets 130 | if priv != nil { 131 | var currBal uint64 132 | if currBal, err = machine.BalanceRetreiver(pub); err != nil { 133 | machine.pkAnonSetMutex.Unlock() 134 | err = fmt.Errorf("Error getting balance of pubkey while calulating assets: %s", err) 135 | return 136 | } 137 | totalAssets += currBal 138 | } 139 | // otherwise don't add anything 140 | } 141 | 142 | machine.pkAnonSetMutex.Unlock() 143 | 144 | return 145 | } 146 | 147 | // CalculateAssetCommitment calculates the commitment to the Z_Assets to Assets 148 | // Are we sure on returning an ECPoint? 149 | func (machine *AssetsProofMachine) CalculateAssetCommitment() (assetCommitment *zksigma.ECPoint, err error) { 150 | 151 | // TODO 152 | return 153 | } 154 | -------------------------------------------------------------------------------- /crypto/rsw/timelockverify.go: -------------------------------------------------------------------------------- 1 | package rsw 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | // VerifyPuzzleOutput verifies that the timelock puzzle PuzzleRSW 10 | func VerifyPuzzleOutput(p *big.Int, q *big.Int, pz *PuzzleRSW, claimedKey []byte) (valid bool, err error) { 11 | if p == nil { 12 | err = fmt.Errorf("p pointer cannot be nil, please investigate") 13 | return 14 | } 15 | if q == nil { 16 | err = fmt.Errorf("q pointer cannot be nil, please investigate") 17 | return 18 | } 19 | 20 | tempN := new(big.Int) 21 | tempN.Mul(p, q) 22 | if tempN.Cmp(pz.N) != 0 { 23 | err = fmt.Errorf("The p and q given do not multiply to the puzzle modulus") 24 | return 25 | } 26 | 27 | // compute trapdoor 28 | // phi(n) = (p-1)(q-1). We assume p and q are prime, and n = pq. 29 | phi := new(big.Int).Mul(new(big.Int).Sub(p, big.NewInt(1)), new(big.Int).Sub(q, big.NewInt(1))) 30 | 31 | // e = 2^t mod phi() 32 | e := new(big.Int).Exp(big.NewInt(2), pz.T, phi) 33 | 34 | // b = a^(e()) (mod n()) 35 | b := new(big.Int).Exp(pz.A, e, pz.N) 36 | 37 | // now xor with ck, getting the bytes 38 | // if this is xor then the ck, err = blah like needs to be xor as well 39 | var answer []byte 40 | xorBytes := new(big.Int).Xor(pz.CK, b).Bytes() 41 | if len(xorBytes) <= 16 { 42 | answerBacking := [16]byte{} 43 | answer = answerBacking[:] 44 | } else { 45 | answer = make([]byte, len(xorBytes)) 46 | } 47 | copy(answer, xorBytes) 48 | 49 | if res := bytes.Compare(answer, claimedKey); res != 0 { 50 | err = fmt.Errorf("The claimed key:\n\t%x\nIs not equal to the puzzle solution:\n\t%x\nSo the claimed solution is invalid", claimedKey, answer) 51 | return 52 | } 53 | 54 | valid = true 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /crypto/timelock.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | // Timelock is an interface that all timelock implementations should conform to. 4 | type Timelock interface { 5 | // SetupTimelockPuzzle sends key k to the future in time t, returning a puzzle and an answer, or fails 6 | SetupTimelockPuzzle(t uint64) (puzzle Puzzle, answer []byte, err error) 7 | } 8 | 9 | // Puzzle is what can actually be solved. It should return the same answer that was the result of SetupTimelockPuzzle. 10 | type Puzzle interface { 11 | // Solve solves the puzzle and returns the answer, or fails 12 | Solve() (answer []byte, err error) 13 | // Serialize turns the puzzle into something that's able to be sent over the wire 14 | Serialize() (raw []byte, err error) 15 | } 16 | -------------------------------------------------------------------------------- /crypto/timelockencoders/asymmetric_test.go: -------------------------------------------------------------------------------- 1 | package timelockencoders 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestRSWRSA(t *testing.T) { 9 | message := make([]byte, 32) 10 | copy(message, []byte("RSW96 Full Scheme but with RSA")) 11 | // This should take a couple seconds 12 | ciphertext, puzzle, err := CreateRSW2048A2PuzzleRSA(1000, message) 13 | if err != nil { 14 | t.Fatalf("Error creating puzzle: %s", err) 15 | } 16 | 17 | newMessage, err := SolvePuzzleRSA(ciphertext, puzzle) 18 | if err != nil { 19 | t.Fatalf("Error solving puzzle: %s", err) 20 | } 21 | 22 | if !bytes.Equal(newMessage, message) { 23 | t.Fatalf("Messages not equal") 24 | } 25 | 26 | t.Logf("We got message: %s!", newMessage) 27 | 28 | return 29 | } 30 | 31 | func TestRSWECIES(t *testing.T) { 32 | message := make([]byte, 32) 33 | copy(message, []byte("RSW96 Full Scheme but with ECIES")) 34 | // This should take a couple seconds 35 | ciphertext, puzzle, err := CreateRSW2048A2PuzzleECIES(1000000, message) 36 | if err != nil { 37 | t.Fatalf("Error creating puzzle: %s", err) 38 | } 39 | 40 | newMessage, err := SolvePuzzleECIES(ciphertext, puzzle) 41 | if err != nil { 42 | t.Fatalf("Error solving puzzle: %s", err) 43 | } 44 | 45 | if !bytes.Equal(newMessage, message) { 46 | t.Fatalf("Messages not equal") 47 | } 48 | 49 | t.Logf("We got message: %s!", newMessage) 50 | 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /cxauctionrpc/cxauctionrpc.go: -------------------------------------------------------------------------------- 1 | package cxauctionrpc 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/mit-dci/opencx/cxauctionserver" 7 | ) 8 | 9 | // AuctionRPCCaller is a listener for RPC commands 10 | type AuctionRPCCaller struct { 11 | caller *OpencxAuctionRPC 12 | listener net.Listener 13 | killers []chan bool 14 | } 15 | 16 | // OpencxAuctionRPC is what is registered and called 17 | type OpencxAuctionRPC struct { 18 | Server *cxauctionserver.OpencxAuctionServer 19 | } 20 | -------------------------------------------------------------------------------- /cxauctionrpc/listener.go: -------------------------------------------------------------------------------- 1 | package cxauctionrpc 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/rpc" 7 | 8 | "github.com/mit-dci/lit/crypto/koblitz" 9 | "github.com/mit-dci/opencx/cxauctionserver" 10 | "github.com/mit-dci/opencx/cxnoise" 11 | "github.com/mit-dci/opencx/logging" 12 | ) 13 | 14 | func CreateRPCForServer(server *cxauctionserver.OpencxAuctionServer) (rpc1 *AuctionRPCCaller, err error) { 15 | rpc1 = &AuctionRPCCaller{ 16 | caller: &OpencxAuctionRPC{ 17 | Server: server, 18 | }, 19 | } 20 | return 21 | } 22 | 23 | // NoiseListen is a synchronous version of RPCListenAsync 24 | func (rpc1 *AuctionRPCCaller) NoiseListen(privkey *koblitz.PrivateKey, host string, port uint16) (err error) { 25 | 26 | doneChan := make(chan bool, 1) 27 | errChan := make(chan error, 1) 28 | go rpc1.NoiseListenAsync(doneChan, errChan, privkey, host, port) 29 | select { 30 | case err = <-errChan: 31 | case <-doneChan: 32 | } 33 | 34 | return 35 | } 36 | 37 | // NoiseListenAsync listens on socket host and port 38 | func (rpc1 *AuctionRPCCaller) NoiseListenAsync(doneChan chan bool, errChan chan error, privkey *koblitz.PrivateKey, host string, port uint16) { 39 | var err error 40 | if rpc1.caller == nil { 41 | errChan <- fmt.Errorf("Error, rpc caller cannot be nil, please create caller correctly") 42 | close(errChan) 43 | return 44 | } 45 | 46 | // Start noise rpc server (need to do this since the client is a rpc newclient) 47 | noiseRPCServer := rpc.NewServer() 48 | 49 | logging.Infof("Registering RPC API over Noise protocol ...") 50 | // Register rpc 51 | if err = noiseRPCServer.Register(rpc1.caller); err != nil { 52 | errChan <- fmt.Errorf("Error registering RPC Interface:\n%s", err) 53 | close(errChan) 54 | return 55 | } 56 | 57 | logging.Infof("Starting RPC Server over noise protocol") 58 | // Start RPC Server 59 | if rpc1.listener, err = cxnoise.NewListener(privkey, int(port)); err != nil { 60 | errChan <- fmt.Errorf("Error creating noise listener for NoiseListenAsync: %s", err) 61 | close(errChan) 62 | return 63 | } 64 | logging.Infof("Running RPC-Noise server on %s\n", rpc1.listener.Addr().String()) 65 | 66 | // We don't need to do anything fancy here either because the noise protocol 67 | // is built in to the listener as well. 68 | go noiseRPCServer.Accept(rpc1.listener) 69 | doneChan <- true 70 | close(doneChan) 71 | return 72 | } 73 | 74 | // RPCListen is a synchronous version of RPCListenAsync 75 | func (rpc1 *AuctionRPCCaller) RPCListen(host string, port uint16) (err error) { 76 | 77 | doneChan := make(chan bool, 1) 78 | errChan := make(chan error, 1) 79 | go rpc1.RPCListenAsync(doneChan, errChan, host, port) 80 | select { 81 | case err = <-errChan: 82 | case <-doneChan: 83 | } 84 | 85 | return 86 | } 87 | 88 | // KillServerNoWait kills the server, stops the clock, everything, doesn't 89 | func (rpc1 *AuctionRPCCaller) KillServerNoWait() (err error) { 90 | if err = rpc1.caller.Server.StopClock(); err != nil { 91 | err = fmt.Errorf("Error stopping clock, not waiting for results: %s", err) 92 | return 93 | } 94 | if err = rpc1.Stop(); err != nil { 95 | err = fmt.Errorf("Error stopping listener for KillServer: %s", err) 96 | return 97 | } 98 | return 99 | } 100 | 101 | // KillServerWait kills the server, stops the clock, everything, but waits for stuff 102 | func (rpc1 *AuctionRPCCaller) KillServerWait() (err error) { 103 | if err = rpc1.caller.Server.StopClockAndWait(); err != nil { 104 | err = fmt.Errorf("Error stopping clock, waiting for results for KillServer: %s", err) 105 | return 106 | } 107 | if err = rpc1.Stop(); err != nil { 108 | err = fmt.Errorf("Error stopping listener for KillServer: %s", err) 109 | return 110 | } 111 | return 112 | } 113 | 114 | // RPCListenAsync listens on socket host and port 115 | func (rpc1 *AuctionRPCCaller) RPCListenAsync(doneChan chan bool, errChan chan error, host string, port uint16) { 116 | var err error 117 | if rpc1.caller == nil { 118 | errChan <- fmt.Errorf("Error, rpc caller cannot be nil, please create caller correctly") 119 | close(errChan) 120 | return 121 | } 122 | 123 | logging.Infof("Registering RPC API...") 124 | // Register rpc 125 | if err = rpc.Register(rpc1.caller); err != nil { 126 | errChan <- fmt.Errorf("Error registering RPC Interface:\n%s", err) 127 | close(errChan) 128 | return 129 | } 130 | 131 | logging.Infof("Starting RPC Server") 132 | // Start RPC Server 133 | serverAddr := net.JoinHostPort(host, fmt.Sprintf("%d", port)) 134 | if rpc1.listener, err = net.Listen("tcp", serverAddr); err != nil { 135 | errChan <- fmt.Errorf("Error listening for RPCListenAsync: %s", err) 136 | close(errChan) 137 | return 138 | } 139 | logging.Infof("Running RPC server on %s\n", rpc1.listener.Addr().String()) 140 | 141 | go rpc.Accept(rpc1.listener) 142 | doneChan <- true 143 | close(doneChan) 144 | return 145 | } 146 | 147 | // WaitUntilDead waits until the Stop() method is called 148 | func (rpc1 *AuctionRPCCaller) WaitUntilDead() { 149 | dedchan := make(chan bool, 1) 150 | rpc1.killers = append(rpc1.killers, dedchan) 151 | <-dedchan 152 | return 153 | } 154 | 155 | // Stop closes the RPC listener and notifies those from WaitUntilDead 156 | func (rpc1 *AuctionRPCCaller) Stop() (err error) { 157 | if rpc1.listener == nil { 158 | err = fmt.Errorf("Error, cannot stop a listener that doesn't exist") 159 | return 160 | } 161 | logging.Infof("Stopping RPC!!") 162 | if err = rpc1.listener.Close(); err != nil { 163 | err = fmt.Errorf("Error closing listener: \n%s", err) 164 | return 165 | } 166 | // kill the guy waiting 167 | for _, killer := range rpc1.killers { 168 | // send the signals, but even if they don't send, close the channel 169 | select { 170 | case killer <- true: 171 | close(killer) 172 | default: 173 | close(killer) 174 | } 175 | } 176 | return 177 | } 178 | -------------------------------------------------------------------------------- /cxauctionrpc/ordercmds.go: -------------------------------------------------------------------------------- 1 | package cxauctionrpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/opencx/logging" 7 | "github.com/mit-dci/opencx/match" 8 | ) 9 | 10 | // SubmitPuzzledOrderArgs holds the args for the submitpuzzledorder command 11 | type SubmitPuzzledOrderArgs struct { 12 | // Use the serialize method on match.EncryptedAuctionOrder 13 | EncryptedOrderBytes []byte 14 | } 15 | 16 | // SubmitPuzzledOrderReply holds the reply for the submitpuzzledorder command 17 | type SubmitPuzzledOrderReply struct { 18 | // empty 19 | } 20 | 21 | // SubmitPuzzledOrder submits an order to the order book or throws an error 22 | func (cl *OpencxAuctionRPC) SubmitPuzzledOrder(args SubmitPuzzledOrderArgs, reply *SubmitPuzzledOrderReply) (err error) { 23 | 24 | logging.Infof("Received timelocked order!") 25 | 26 | order := new(match.EncryptedAuctionOrder) 27 | if err = order.Deserialize(args.EncryptedOrderBytes); err != nil { 28 | err = fmt.Errorf("Error deserializing puzzled order: %s", err) 29 | return 30 | } 31 | 32 | if err = cl.Server.PlacePuzzledOrder(order); err != nil { 33 | err = fmt.Errorf("Error placing order while submitting order: \n%s", err) 34 | return 35 | } 36 | 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /cxauctionrpc/paramcmds.go: -------------------------------------------------------------------------------- 1 | package cxauctionrpc 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/mit-dci/opencx/match" 8 | ) 9 | 10 | // GetPublicParametersArgs holds the args for the getpublicparameters command 11 | type GetPublicParametersArgs struct { 12 | Pair match.Pair 13 | } 14 | 15 | // GetPublicParametersReply holds the reply for the getpublicparameters command 16 | type GetPublicParametersReply struct { 17 | AuctionID [32]byte 18 | // This is the time that it will take the auction to run. We need to make sure it doesn't 19 | // take any less than this, and can actually verify that the exchange isn't running it 20 | // for extra time. 21 | AuctionTime uint64 22 | StartTime time.Time 23 | } 24 | 25 | // GetPublicParameters gets public parameters from the exchange, like time and auctionID 26 | func (cl *OpencxAuctionRPC) GetPublicParameters(args GetPublicParametersArgs, reply *GetPublicParametersReply) (err error) { 27 | if reply.AuctionID, reply.StartTime, err = cl.Server.GetIDTimeFromPair(&args.Pair); err != nil { 28 | err = fmt.Errorf("Error getting public param auction id: %s", err) 29 | return 30 | } 31 | 32 | if reply.AuctionTime, err = cl.Server.CurrentAuctionTime(); err != nil { 33 | err = fmt.Errorf("Error getting public param auction time: %s", err) 34 | return 35 | } 36 | 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /cxauctionserver/auctionserver_test.go: -------------------------------------------------------------------------------- 1 | package cxauctionserver 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/mit-dci/lit/coinparam" 9 | "github.com/mit-dci/opencx/cxdb" 10 | "github.com/mit-dci/opencx/cxdb/cxdbmemory" 11 | "github.com/mit-dci/opencx/match" 12 | ) 13 | 14 | const ( 15 | testOrderChanSize = uint64(100) 16 | testStandardAuctionTime = uint64(10000) 17 | testMaxBatchSize = uint64(100) 18 | ) 19 | 20 | var ( 21 | testCoins = []*coinparam.Params{ 22 | &coinparam.BitcoinParams, 23 | &coinparam.VertcoinTestNetParams, 24 | &coinparam.RegressionNetParams, 25 | &coinparam.LiteRegNetParams, 26 | } 27 | ) 28 | 29 | // initTestServerTime initializes the server. This is mostly setting up the db 30 | func initTestServerTime(t uint64) (s *OpencxAuctionServer, err error) { 31 | 32 | // get number of cores 33 | numcpu := uint64(runtime.NumCPU()) 34 | 35 | // Initialize the test server 36 | if s, err = createUltraLightAuctionServer(testCoins, testOrderChanSize, t, numcpu*100); err != nil { 37 | err = fmt.Errorf("Error initializing server for tests: %s", err) 38 | return 39 | } 40 | 41 | return 42 | } 43 | 44 | // initTestServer initializes the server. This is mostly setting up the db 45 | func initTestServer() (s *OpencxAuctionServer, err error) { 46 | 47 | // Initialize the test server 48 | if s, err = createUltraLightAuctionServer(testCoins, testOrderChanSize, testStandardAuctionTime, testMaxBatchSize); err != nil { 49 | err = fmt.Errorf("Error initializing server for tests: %s", err) 50 | return 51 | } 52 | 53 | return 54 | } 55 | 56 | // createUltraLightAuctionServer creates a server with "pinky swear settlement" after starting the database with a bunch of parameters for everything else 57 | // This one literally has no settlement 58 | func createUltraLightAuctionServer(coinList []*coinparam.Params, orderChanSize uint64, auctionTime uint64, maxBatchSize uint64) (server *OpencxAuctionServer, err error) { 59 | 60 | var pairList []*match.Pair 61 | if pairList, err = match.GenerateAssetPairs(coinList); err != nil { 62 | err = fmt.Errorf("Could not generate asset pairs from coin list: %s", err) 63 | return 64 | } 65 | 66 | var mengines map[match.Pair]match.AuctionEngine 67 | if mengines, err = cxdbmemory.CreateAuctionEngineMap(pairList); err != nil { 68 | err = fmt.Errorf("Error creating auction engine map with coinlist for createUltraLightAuctionServer: %s", err) 69 | return 70 | } 71 | 72 | // These lines are the only difference between the LightAuctionServer and the FullAuctionServer 73 | var setEngines map[*coinparam.Params]match.SettlementEngine 74 | if setEngines, err = cxdbmemory.CreatePinkySwearEngineMap(make(map[*coinparam.Params][][33]byte), true); err != nil { 75 | err = fmt.Errorf("Error creating pinky swear settlement engine map for createUltraLightAuctionServer: %s", err) 76 | return 77 | } 78 | 79 | var aucBooks map[match.Pair]match.AuctionOrderbook 80 | if aucBooks, err = cxdbmemory.CreateAuctionOrderbookMap(pairList); err != nil { 81 | err = fmt.Errorf("Error creating auction orderbook map for createUltraLightAuctionServer: %s", err) 82 | return 83 | } 84 | 85 | var pzEngines map[match.Pair]cxdb.PuzzleStore 86 | if pzEngines, err = cxdbmemory.CreatePuzzleStoreMap(pairList); err != nil { 87 | err = fmt.Errorf("Error creating puzzle store map for createUltraLightAuctionServer: %s", err) 88 | return 89 | } 90 | 91 | var batchers map[match.Pair]match.AuctionBatcher 92 | if batchers, err = CreateAuctionBatcherMap(pairList, maxBatchSize); err != nil { 93 | err = fmt.Errorf("Error creating batcher map for createUltraLightAuctionServer: %s", err) 94 | return 95 | } 96 | 97 | // orderChanSize = 100 because uh why not? 98 | if server, err = InitServer(setEngines, mengines, aucBooks, pzEngines, batchers, orderChanSize, auctionTime); err != nil { 99 | err = fmt.Errorf("Error initializing server for createUltraLightAuctionServer: %s", err) 100 | return 101 | } 102 | 103 | return 104 | } 105 | 106 | func TestCreateServer(t *testing.T) { 107 | var err error 108 | 109 | if _, err = initTestServer(); err != nil { 110 | t.Errorf("Error initializing test server for TestCreateServer: %s", err) 111 | } 112 | 113 | return 114 | } 115 | -------------------------------------------------------------------------------- /cxauctionserver/clock.go: -------------------------------------------------------------------------------- 1 | package cxauctionserver 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | 7 | "github.com/mit-dci/opencx/logging" 8 | "github.com/mit-dci/opencx/match" 9 | ) 10 | 11 | type timeID struct { 12 | time time.Time 13 | id [32]byte 14 | } 15 | 16 | // AuctionClock should be run in a goroutine and just commit to puzzles after some time 17 | // What this does is first makes a channel called doneChan. 18 | // This channel keeps track of the time that each auction is committed to. 19 | func (s *OpencxAuctionServer) AuctionClock(pair match.Pair, startID [32]byte) { 20 | logging.Infof("Starting Auction Clock!") 21 | 22 | // We make the variables here because we don't want to fill up our memory with stuff in the loop 23 | doneChan := make(chan timeID, 1) 24 | var tickResult timeID 25 | 26 | // FOR STATS / DEBUG 27 | var m *runtime.MemStats 28 | m = new(runtime.MemStats) 29 | 30 | // This takes currAuctionID, plugs it in to auctionTick, gets back a new auction id from the channel so it 31 | // can continue the loop 32 | var currAuctionID [32]byte = startID 33 | for { 34 | 35 | // Before we commit to anything, let's check that we should turn the clock off 36 | select { 37 | case <-s.clockOffButton: 38 | logging.Infof("Stopping clock at %s", time.Now()) 39 | // this is probably the jankiest code I've written but I don't care because 40 | // this will all probably get rewritten and is the least important part 41 | // of the system 42 | s.clockOffButton <- true 43 | return 44 | default: 45 | } 46 | 47 | // Read mem stats FOR STATISTICS 48 | runtime.ReadMemStats(m) 49 | 50 | // TODO: configurable time, work out schedule, base it on the AuctionTime option 51 | time.AfterFunc(time.Duration(s.t)*time.Microsecond, func() { 52 | // Before we commit to anything, let's check that we should turn the clock off 53 | select { 54 | case <-s.clockOffButton: 55 | // this is probably the jankiest code I've written but I don't care because 56 | // this will all probably get rewritten and is the least important part 57 | // of the system 58 | s.clockOffButton <- true 59 | return 60 | default: 61 | } 62 | s.auctionTick(pair, currAuctionID, doneChan) 63 | }) 64 | 65 | // retrieve the tick from the channel 66 | tickResult = <-doneChan 67 | 68 | // Now set the new id to the result 69 | currAuctionID = tickResult.id 70 | 71 | logging.Infof("Tick done at %s", tickResult.time.String()) 72 | } 73 | } 74 | 75 | // auctionTick commits to orders and creates a new auction, while making sure to send a "done" time to a channel afterwards 76 | func (s *OpencxAuctionServer) auctionTick(pair match.Pair, oldID [32]byte, doneChan chan timeID) { 77 | var err error 78 | 79 | var tickRes timeID 80 | 81 | select { 82 | case <-s.clockOffButton: 83 | logging.Infof("trying to not do commitments") 84 | s.clockOffButton <- true 85 | doneChan <- tickRes 86 | logging.Infof("not doing commitments") 87 | return 88 | default: 89 | } 90 | 91 | // this basically makes sure we send something to doneChan 92 | // when we're done 93 | defer func() { 94 | doneChan <- tickRes 95 | }() 96 | 97 | // batcher solves puzzles, puzzle engine stores puzzles. 98 | if tickRes.id, err = s.CommitOrdersNewAuction(&pair, oldID); err != nil { 99 | // TODO: What should happen in this case? How can we prevent this case? 100 | logging.Fatalf("Exchange commitment for %x failed!!! Fatal error: %s", oldID, err) 101 | } 102 | 103 | // Now set the time 104 | tickRes.time = time.Now() 105 | 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /cxbenchmark/.gitignore: -------------------------------------------------------------------------------- 1 | # Because inevitably someone will run `go test -bench=.` and make these directories, then try to 2 | # commit them. Making them is fine, committing them is not 3 | benchmarkInfo/ 4 | .benchmarkInfo/ 5 | -------------------------------------------------------------------------------- /cxbenchmark/aucmatchingbenchmark_test.go: -------------------------------------------------------------------------------- 1 | package cxbenchmark 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/mit-dci/opencx/benchclient" 9 | "github.com/mit-dci/opencx/cxauctionrpc" 10 | ) 11 | 12 | func TestSetupDualClientNoAuth(t *testing.T) { 13 | var err error 14 | 15 | t.Logf("Start noauth server -- time: %s", time.Now()) 16 | 17 | var rpcListener *cxauctionrpc.AuctionRPCCaller 18 | if _, _, rpcListener, err = setupEasyAuctionBenchmarkDualClient(false); err != nil { 19 | t.Errorf("Could not start noauth server: %s", err) 20 | return 21 | } 22 | 23 | t.Logf("Server noauth started -- time: %s", time.Now()) 24 | if err = rpcListener.KillServerNoWait(); err != nil { 25 | t.Errorf("Error killing server for TestSetupDualClientNoAuth: %s", err) 26 | return 27 | } 28 | return 29 | } 30 | 31 | // BenchmarkEasyAuctionPlaceOrders places orders on an unauthenticated auction server with "pinky swear" settlement and full matching capabilites. 32 | func BenchmarkEasyAuctionPlaceOrders(b *testing.B) { 33 | var err error 34 | 35 | b.Logf("Test start -- time: %s", time.Now()) 36 | 37 | var client1 *benchclient.BenchClient 38 | var client2 *benchclient.BenchClient 39 | var rpcListener *cxauctionrpc.AuctionRPCCaller 40 | if client1, client2, rpcListener, err = setupEasyAuctionBenchmarkDualClient(false); err != nil { 41 | b.Errorf("Could not start dual client benchmark: \n%s", err) 42 | return 43 | } 44 | 45 | b.Logf("Test started client - %s", time.Now()) 46 | // ugh when we run this benchmark and the server is noise then we basically crash the rpc server... need to figure out how to have that not happen, why is that fatal? 47 | runs := []int{1} 48 | for _, varRuns := range runs { 49 | placeFillTitle := fmt.Sprintf("AuctionPlaceAndFill%d", varRuns) 50 | b.Logf("Running %s", placeFillTitle) 51 | AuctionPlaceFillX(client1, client2, varRuns) 52 | placeBuySellTitle := fmt.Sprintf("AuctionPlaceBuyThenSell%d", varRuns) 53 | b.Logf("Running %s", placeBuySellTitle) 54 | AuctionPlaceBuySellX(client1, varRuns) 55 | } 56 | b.Logf("waiting for results - %s", time.Now()) 57 | if err = rpcListener.KillServerWait(); err != nil { 58 | b.Errorf("Error killing server for BenchmarkEasyAuctionPlaceOrders: %s", err) 59 | return 60 | } 61 | 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /cxbenchmark/matchingbenchmark.go: -------------------------------------------------------------------------------- 1 | package cxbenchmark 2 | 3 | import ( 4 | "github.com/mit-dci/opencx/benchclient" 5 | "github.com/mit-dci/opencx/cxrpc" 6 | "github.com/mit-dci/opencx/logging" 7 | "github.com/mit-dci/opencx/match" 8 | ) 9 | 10 | // PlaceAndFill places a whole bunch of orders (in goroutines of course) and fills a whole bunch of orders. 11 | func PlaceAndFill(client1 *benchclient.BenchClient, client2 *benchclient.BenchClient, pair string, howMany int) { 12 | for i := 0; i < howMany; i++ { 13 | // This shouldnt make any change in balance but each account should have at least 2000 satoshis (or the smallest unit in whatever chain) 14 | bufErrChan := make(chan error, 4) 15 | orderChan := make(chan *cxrpc.SubmitOrderReply) 16 | go client1.OrderAsync(client1.PrivKey.PubKey(), match.Buy, pair, 1000, 1.0, orderChan, bufErrChan) 17 | go client2.OrderAsync(client2.PrivKey.PubKey(), match.Sell, pair, 1000, 1.0, orderChan, bufErrChan) 18 | go client1.OrderAsync(client1.PrivKey.PubKey(), match.Sell, pair, 2000, 2.0, orderChan, bufErrChan) 19 | go client2.OrderAsync(client2.PrivKey.PubKey(), match.Buy, pair, 1000, 2.0, orderChan, bufErrChan) 20 | 21 | for i := 0; i < cap(bufErrChan); i++ { 22 | select { 23 | case err := <-bufErrChan: 24 | if err != nil { 25 | logging.Errorf("Error placing and filling") 26 | } 27 | case order := <-orderChan: 28 | if order != nil { 29 | logging.Infof("Placed order %s", order.OrderID) 30 | } 31 | } 32 | if err := <-bufErrChan; err != nil { 33 | logging.Errorf("Error placing and filling: %s", err) 34 | } 35 | } 36 | } 37 | return 38 | } 39 | 40 | // PlaceManyBuy places many orders at once 41 | func PlaceManyBuy(client *benchclient.BenchClient, pair string, howMany int) { 42 | bufErrChan := make(chan error, howMany) 43 | orderChan := make(chan *cxrpc.SubmitOrderReply) 44 | for i := 0; i < howMany; i++ { 45 | go client.OrderAsync(client.PrivKey.PubKey(), match.Buy, pair, 1000, 1.0, orderChan, bufErrChan) 46 | } 47 | 48 | for i := 0; i < cap(bufErrChan); i++ { 49 | select { 50 | case err := <-bufErrChan: 51 | if err != nil { 52 | logging.Errorf("Error placing many: %s", err) 53 | } 54 | case order := <-orderChan: 55 | if order != nil { 56 | logging.Infof("Placed order %s", order.OrderID) 57 | } 58 | } 59 | } 60 | 61 | return 62 | } 63 | 64 | // PlaceManySell places many orders at once 65 | func PlaceManySell(client *benchclient.BenchClient, pair string, howMany int) { 66 | bufErrChan := make(chan error, howMany) 67 | orderChan := make(chan *cxrpc.SubmitOrderReply) 68 | for i := 0; i < howMany; i++ { 69 | go client.OrderAsync(client.PrivKey.PubKey(), match.Sell, pair, 1000, 1.0, orderChan, bufErrChan) 70 | } 71 | 72 | for i := 0; i < cap(bufErrChan); i++ { 73 | select { 74 | case err := <-bufErrChan: 75 | if err != nil { 76 | logging.Errorf("Error placing many: %s", err) 77 | } 78 | case order := <-orderChan: 79 | if order != nil { 80 | logging.Infof("Placed order %s", order.OrderID) 81 | } 82 | } 83 | } 84 | 85 | return 86 | } 87 | 88 | // PlaceBuySellX runs functions with predone parameters, you only tell it how many times it should run and what client to use 89 | func PlaceBuySellX(client *benchclient.BenchClient, varRuns int) { 90 | PlaceManyBuy(client, "regtest/litereg", varRuns) 91 | PlaceManyBuy(client, "regtest/vtcreg", varRuns) 92 | PlaceManyBuy(client, "vtcreg/litereg", varRuns) 93 | PlaceManySell(client, "regtest/litereg", varRuns) 94 | PlaceManySell(client, "regtest/vtcreg", varRuns) 95 | PlaceManySell(client, "vtcreg/litereg", varRuns) 96 | return 97 | } 98 | 99 | // PlaceFillX runs functions with predone parameters, you only tell it how many times it should run and what client to use 100 | func PlaceFillX(client1 *benchclient.BenchClient, client2 *benchclient.BenchClient, varRuns int) { 101 | PlaceAndFill(client1, client2, "regtest/litereg", varRuns) 102 | PlaceAndFill(client1, client2, "regtest/vtcreg", varRuns) 103 | PlaceAndFill(client1, client2, "vtcreg/litereg", varRuns) 104 | } 105 | -------------------------------------------------------------------------------- /cxbenchmark/whitelist.go: -------------------------------------------------------------------------------- 1 | package cxbenchmark 2 | 3 | import ( 4 | "github.com/mit-dci/lit/coinparam" 5 | "github.com/mit-dci/lit/crypto/koblitz" 6 | ) 7 | 8 | // createWhitelistMap creates a map from each coin provided to the list of pukeys, in the correct format to be passed into a PinkySwear settlement engine 9 | func createWhitelistMap(coinList []*coinparam.Params, pubkeys []*koblitz.PublicKey) (wlMap map[*coinparam.Params][][33]byte) { 10 | wlMap = make(map[*coinparam.Params][][33]byte) 11 | 12 | for _, coin := range coinList { 13 | wlMap[coin] = kobPubToBytes(pubkeys) 14 | } 15 | 16 | return 17 | } 18 | 19 | // kobPubToBytes turns a []*koblitz.PublicKey to a [][33]byte by serializing each key 20 | func kobPubToBytes(pubList []*koblitz.PublicKey) (retList [][33]byte) { 21 | // We know the length so we can do fun indexing rather than appending all the time 22 | // and actually preserve order 23 | retList = make([][33]byte, len(pubList)) 24 | var currSerialized [33]byte 25 | for i, pubkey := range pubList { 26 | copy(currSerialized[:], pubkey.SerializeCompressed()) 27 | retList[i] = currSerialized 28 | } 29 | 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /cxdb/README.md: -------------------------------------------------------------------------------- 1 | # cxdb 2 | [![Build Status](https://travis-ci.org/mit-dci/opencx.svg?branch=master)](https://travis-ci.org/mit-dci/opencx) 3 | [![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/mit-dci/opencx/blob/master/LICENSE) 4 | [![GoDoc](https://godoc.org/github.com/mit-dci/opencx/cxdb?status.svg)](https://godoc.org/github.com/mit-dci/opencx/cxdb) 5 | 6 | 7 | The code which manages exchange datastore interactions is split into a set of useful interfaces: 8 | ### SettlementEngine 9 | SettlementEngine has two methods. One method checks whether or not a settlement execution could take place if it were executed (think of this as "can we credit this user X of an asset"). 10 | The second method actually executes the settlement execution(s). 11 | 12 | ### AuctionEngine 13 | AuctionEngine is the matching engine for auction orders. It has a place method, a cancel method, and a match method. The match method takes an auction ID as input, since orders cannot be matched cross-auction. This matches according to a clearing price based auction matching algorithm. 14 | ### LimitEngine 15 | LimitEngine is the matching engine for limit orders. It has a place method, a cancel method, and a match method. This matches according to a price-time priority auction matching algorithm. 16 | ### AuctionOrderbook 17 | AuctionOrderbook gets updated by the auction matching engine, and is viewable by the user. This also has other methods that may be useful, for example methods to get orders by ID, pubkey, or auction. 18 | ### LimitOrderbook 19 | LimitOrderbook is very similar to AuctionOrderbook except it does not have methods dependent on a specific auction, since limit orderbooks do not have auctions. 20 | ### PuzzleStore 21 | PuzzleStore is a simple store for storing timelock puzzles, as well as marking specific timelock puzzles to commit to or match. 22 | ### DepositStore 23 | DepositStore stores the mapping from pubkey to deposit address. This also keeps track of pending deposits. Pending deposits do not have a fixed number of confirmations, and can be set arbitrarily. 24 | 25 | ### DB interface implementation status 26 | - SettlementEngine 27 | - [x] cxdbsql 28 | - [x] cxdbmemory 29 | - [ ] cxdbredis 30 | - AuctionEngine 31 | - [x] cxdbsql 32 | - [ ] cxdbemory (partial) 33 | - [ ] cxdbredis 34 | - LimitEngine 35 | - [x] cxdbsql 36 | - [ ] cxdbmemory 37 | - [ ] cxdbredis 38 | - AuctionOrderbook 39 | - [x] cxdbsql 40 | - [ ] cxdbmemory 41 | - [ ] cxdbredis 42 | - LimitOrderbook 43 | - [x] cxdbsql 44 | - [ ] cxdbmemory 45 | - [ ] cxdbredis 46 | - PuzzleStore 47 | - [x] cxdbsql 48 | - [x] cxdbmemory 49 | - [ ] cxdbredis 50 | - DepositStore 51 | - [x] cxdbsql 52 | - [ ] cxdbmemory 53 | - [ ] cxdbredis 54 | 55 | Some old code still exists in `cxdbmemory`. 56 | The issues related to refactoring cxdb are [#16](https://github.com/mit-dci/opencx/issues/16). 57 | -------------------------------------------------------------------------------- /cxdb/cxdb.go: -------------------------------------------------------------------------------- 1 | package cxdb 2 | 3 | import ( 4 | "github.com/mit-dci/lit/crypto/koblitz" 5 | "github.com/mit-dci/opencx/match" 6 | ) 7 | 8 | // SettlementStore is like a frontend for settlements, it contains balances that are readable 9 | type SettlementStore interface { 10 | // UpdateBalances updates the balances from the settlement executions 11 | UpdateBalances(settlementExecs []*match.SettlementResult) (err error) 12 | // GetBalance gets the balance for a pubkey and an asset. 13 | GetBalance(pubkey *koblitz.PublicKey) (balance uint64, err error) 14 | } 15 | 16 | type DepositStore interface { 17 | // RegisterUser takes in a pubkey, and an address for the pubkey 18 | RegisterUser(pubkey *koblitz.PublicKey, address string) (err error) 19 | // UpdateDeposits updates the deposits when a block comes in 20 | UpdateDeposits(deposits []match.Deposit, blockheight uint64) (depositExecs []*match.SettlementExecution, err error) 21 | // GetDepositAddressMap gets a map of the deposit addresses we own to pubkeys 22 | GetDepositAddressMap() (depAddrMap map[string]*koblitz.PublicKey, err error) 23 | // GetDepositAddress gets the deposit address for a pubkey and an asset. 24 | GetDepositAddress(pubkey *koblitz.PublicKey) (addr string, err error) 25 | } 26 | 27 | // PuzzleStore is an interface for defining a storage layer for auction order puzzles. 28 | type PuzzleStore interface { 29 | // ViewAuctionPuzzleBook takes in an auction ID, and returns encrypted auction orders, and puzzles. 30 | // You don't know what auction IDs should be in the orders encrypted in the puzzle book, but this is 31 | // what was submitted. 32 | ViewAuctionPuzzleBook(auctionID *match.AuctionID) (puzzles []*match.EncryptedAuctionOrder, err error) 33 | // PlaceAuctionPuzzle puts an encrypted auction order in the datastore. 34 | PlaceAuctionPuzzle(puzzledOrder *match.EncryptedAuctionOrder) (err error) 35 | } 36 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/auctionengine.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/mit-dci/opencx/logging" 8 | "github.com/mit-dci/opencx/match" 9 | "golang.org/x/crypto/sha3" 10 | ) 11 | 12 | type MemoryAuctionEngine struct { 13 | orders map[match.AuctionID]map[float64][]*match.AuctionOrderIDPair 14 | auctionMtx *sync.Mutex 15 | pair *match.Pair 16 | } 17 | 18 | // PlaceAuctionOrder should place an order for a specific auction ID, and produce a response output. 19 | // This response output should be used in case the matching engine dies, and this can be replayed to build the state. 20 | // This method assumes that the auction order is valid, and has the same pair as all of the other orders that have been placed for this matching engine. 21 | func (me *MemoryAuctionEngine) PlaceAuctionOrder(order *match.AuctionOrder, auctionID *match.AuctionID) (idRes *match.AuctionOrderIDPair, err error) { 22 | me.auctionMtx.Lock() 23 | 24 | idCopy := *auctionID 25 | 26 | // First get the price of the order, if this errors then that's really bad 27 | var pr float64 28 | if pr, err = order.Price(); err != nil { 29 | err = fmt.Errorf("Critical error when placing order for matching engine: %s", err) 30 | me.auctionMtx.Unlock() 31 | return 32 | } 33 | 34 | // Now create an ID. If this errors then that's really bad 35 | var id [32]byte 36 | hasher := sha3.New256() 37 | hasher.Write(order.SerializeSignable()) 38 | copy(id[:], hasher.Sum(nil)) 39 | 40 | idRes = &match.AuctionOrderIDPair{ 41 | OrderID: id, 42 | Order: order, 43 | } 44 | 45 | // We assume that the order has been properly validated when it goes in to the auction orderbook 46 | var ok bool 47 | // If the map for the auction isn't there, create it 48 | if _, ok = me.orders[idCopy]; !ok { 49 | 50 | // Since we assume the order is valid, place it in the auction 51 | me.orders[idCopy] = map[float64][]*match.AuctionOrderIDPair{ 52 | pr: []*match.AuctionOrderIDPair{ 53 | idRes, 54 | }, 55 | } 56 | return 57 | } 58 | 59 | // if the map for the auction is there but the price index isn't, create it 60 | if _, ok = me.orders[idCopy][pr]; !ok { 61 | me.orders[idCopy][pr] = []*match.AuctionOrderIDPair{ 62 | idRes, 63 | } 64 | return 65 | } 66 | 67 | // if both are fine then awesome 68 | me.orders[idCopy][pr] = append(me.orders[idCopy][pr], idRes) 69 | 70 | me.auctionMtx.Unlock() 71 | return 72 | } 73 | 74 | // CancelAuctionOrder should cancel an order for a specific order ID, and produce a response output. 75 | // This response output should be used in case the matching engine dies, and this can be replayed to build the state. 76 | func (me *MemoryAuctionEngine) CancelAuctionOrder(id *match.OrderID) (cancelled *match.CancelledOrder, cancelSettlement *match.SettlementExecution, err error) { 77 | me.auctionMtx.Lock() 78 | // Go through the maps and, since we don't have an order ID => order index just delete em all 79 | var deletedOrder *match.AuctionOrderIDPair 80 | for _, orderMap := range me.orders { 81 | for pr, orderIDPairList := range orderMap { 82 | var deletedIdx int 83 | var deleted bool 84 | for idx, orderIDPair := range orderIDPairList { 85 | if orderIDPair.OrderID == *id { 86 | deletedOrder = orderIDPair 87 | deleted = true 88 | deletedIdx = idx 89 | } 90 | } 91 | 92 | // If deleted remove from thing 93 | if deleted { 94 | oidLen := len(orderIDPairList) 95 | orderIDPairList[oidLen-1], orderIDPairList[deletedIdx] = orderIDPairList[deletedIdx], orderIDPairList[oidLen-1] 96 | orderMap[pr] = orderIDPairList[:oidLen-1] 97 | } 98 | } 99 | } 100 | // Get side from string rip 101 | var orderSide *match.Side 102 | orderSide = new(match.Side) 103 | // because we just init the pointer we can get the value and set 104 | *orderSide = deletedOrder.Order.Side 105 | 106 | var debitAsset match.Asset 107 | if *orderSide == match.Buy { 108 | debitAsset = me.pair.AssetHave 109 | } else { 110 | debitAsset = me.pair.AssetWant 111 | } 112 | cancelled = &match.CancelledOrder{ 113 | OrderID: id, 114 | } 115 | 116 | cancelSettlement = &match.SettlementExecution{ 117 | Pubkey: deletedOrder.Order.Pubkey, 118 | Amount: deletedOrder.Order.AmountHave, 119 | Asset: debitAsset, 120 | Type: match.Debit, 121 | } 122 | me.auctionMtx.Unlock() 123 | return 124 | } 125 | 126 | // MatchAuctionOrders matches the auction orders for a specific auction ID 127 | func (me *MemoryAuctionEngine) MatchAuctionOrders(auctionID *match.AuctionID) (orderExecs []*match.OrderExecution, settlementExecs []*match.SettlementExecution, err error) { 128 | // TODO 129 | logging.Fatalf("UNIMPLEMENTED!") 130 | return 131 | } 132 | 133 | func CreateAuctionEngineMap(pairList []*match.Pair) (mengines map[match.Pair]match.AuctionEngine, err error) { 134 | mengines = make(map[match.Pair]match.AuctionEngine) 135 | 136 | // We just create a new struct because that's all we really need, we satisfy the interface 137 | for _, pair := range pairList { 138 | mengines[*pair] = new(MemoryAuctionEngine) 139 | } 140 | 141 | return 142 | } 143 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/auctionorderbook.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/crypto/koblitz" 7 | "github.com/mit-dci/opencx/logging" 8 | "github.com/mit-dci/opencx/match" 9 | ) 10 | 11 | // MemoryAuctionOrderbook is the representation of a auction orderbook for SQL 12 | type MemoryAuctionOrderbook struct { 13 | // TODO: implement this 14 | 15 | // this pair 16 | pair *match.Pair 17 | } 18 | 19 | // CreateAuctionOrderbook creates a auction orderbook based on a pair 20 | func CreateAuctionOrderbook(pair *match.Pair) (book match.AuctionOrderbook, err error) { 21 | // Set values for auction engine 22 | mo := &MemoryAuctionOrderbook{ 23 | pair: pair, 24 | } 25 | // We can connect, now set return 26 | book = mo 27 | return 28 | } 29 | 30 | // UpdateBookExec takes in an order execution and updates the orderbook. 31 | func (mo *MemoryAuctionOrderbook) UpdateBookExec(exec *match.OrderExecution) (err error) { 32 | // TODO: Implement 33 | logging.Fatalf("UNIMPLEMENTED!") 34 | return 35 | } 36 | 37 | // UpdateBookCancel takes in an order cancellation and updates the orderbook. 38 | func (mo *MemoryAuctionOrderbook) UpdateBookCancel(cancel *match.CancelledOrder) (err error) { 39 | // TODO: Implement 40 | logging.Fatalf("UNIMPLEMENTED!") 41 | return 42 | } 43 | 44 | // UpdateBookPlace takes in an order, ID, auction ID, and adds the order to the orderbook. 45 | func (mo *MemoryAuctionOrderbook) UpdateBookPlace(auctionIDPair *match.AuctionOrderIDPair) (err error) { 46 | // TODO: Implement 47 | logging.Fatalf("UNIMPLEMENTED!") 48 | return 49 | } 50 | 51 | // GetOrder gets an order from an OrderID 52 | func (mo *MemoryAuctionOrderbook) GetOrder(orderID *match.OrderID) (aucOrder *match.AuctionOrderIDPair, err error) { 53 | // TODO: Implement 54 | logging.Fatalf("UNIMPLEMENTED!") 55 | return 56 | } 57 | 58 | // CalculatePrice takes in a pair and returns the calculated price based on the orderbook. 59 | func (mo *MemoryAuctionOrderbook) CalculatePrice(auctionID *match.AuctionID) (price float64, err error) { 60 | // TODO: Implement 61 | logging.Fatalf("UNIMPLEMENTED!") 62 | return 63 | } 64 | 65 | // GetOrdersForPubkey gets orders for a specific pubkey. 66 | func (mo *MemoryAuctionOrderbook) GetOrdersForPubkey(pubkey *koblitz.PublicKey) (orders map[float64][]*match.AuctionOrderIDPair, err error) { 67 | // TODO: Implement 68 | logging.Fatalf("UNIMPLEMENTED!") 69 | return 70 | } 71 | 72 | // ViewAuctionOrderbook takes in a trading pair and returns the orderbook as a map 73 | func (mo *MemoryAuctionOrderbook) ViewAuctionOrderBook() (book map[float64][]*match.AuctionOrderIDPair, err error) { 74 | // TODO: Implement 75 | logging.Fatalf("UNIMPLEMENTED!") 76 | return 77 | } 78 | 79 | // CreateAuctionOrderbookMap creates a map of pair to auction engine, given a list of pairs. 80 | func CreateAuctionOrderbookMap(pairList []*match.Pair) (aucMap map[match.Pair]match.AuctionOrderbook, err error) { 81 | 82 | aucMap = make(map[match.Pair]match.AuctionOrderbook) 83 | var curAucEng match.AuctionOrderbook 84 | for _, pair := range pairList { 85 | if curAucEng, err = CreateAuctionOrderbook(pair); err != nil { 86 | err = fmt.Errorf("Error creating single auction engine while creating auction engine map: %s", err) 87 | return 88 | } 89 | aucMap[*pair] = curAucEng 90 | } 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/auctionorders.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/opencx/logging" 7 | "github.com/mit-dci/opencx/match" 8 | "golang.org/x/crypto/sha3" 9 | ) 10 | 11 | // PlaceAuctionPuzzle puts a puzzle and ciphertext in the datastore. 12 | func (db *CXDBMemory) PlaceAuctionPuzzle(encryptedOrder *match.EncryptedAuctionOrder) (err error) { 13 | 14 | db.puzzleMtx.Lock() 15 | db.puzzles[encryptedOrder.IntendedAuction] = append(db.puzzles[encryptedOrder.IntendedAuction], encryptedOrder) 16 | db.puzzleMtx.Unlock() 17 | return 18 | } 19 | 20 | // PlaceAuctionOrder places an order in the unencrypted datastore. 21 | func (db *CXDBMemory) PlaceAuctionOrder(order *match.AuctionOrder) (err error) { 22 | 23 | // TODO: Determine where order validation should go if not here 24 | // What determines a valid order should be in one place 25 | if _, err = order.Price(); err != nil { 26 | err = fmt.Errorf("No price can be determined, so invalid order") 27 | return 28 | } 29 | 30 | db.ordersMtx.Lock() 31 | db.orders[order.AuctionID] = append(db.orders[order.AuctionID], order) 32 | db.ordersMtx.Unlock() 33 | return 34 | } 35 | 36 | // ViewAuctionOrderBook takes in a trading pair and auction ID, and returns auction orders. 37 | func (db *CXDBMemory) ViewAuctionOrderBook(tradingPair *match.Pair, auctionID [32]byte) (book map[float64][]*match.AuctionOrderIDPair, err error) { 38 | 39 | db.ordersMtx.Lock() 40 | var allOrders []*match.AuctionOrder 41 | var ok bool 42 | if allOrders, ok = db.orders[auctionID]; !ok { 43 | db.ordersMtx.Unlock() 44 | err = fmt.Errorf("Could not find auctionID in the auction orderbook") 45 | return 46 | } 47 | var orderPrice float64 48 | var thisOrderPair *match.AuctionOrderIDPair 49 | for _, order := range allOrders { 50 | if order.TradingPair == *tradingPair { 51 | if orderPrice, err = order.Price(); err != nil { 52 | db.ordersMtx.Unlock() 53 | err = fmt.Errorf("Error getting price for order while viewing auction orderbook: %s", err) 54 | return 55 | } 56 | 57 | // get hash of order lol 58 | hasher := sha3.New256() 59 | hasher.Write(order.SerializeSignable()) 60 | thisOrderPair = new(match.AuctionOrderIDPair) 61 | copy(thisOrderPair.OrderID[:], hasher.Sum(nil)) 62 | book[orderPrice] = append(book[orderPrice], thisOrderPair) 63 | } 64 | } 65 | 66 | db.ordersMtx.Unlock() 67 | return 68 | } 69 | 70 | // ViewAuctionPuzzleBook takes in an auction ID, and returns encrypted auction orders, and puzzles. 71 | // You don't know what auction IDs should be in the orders encrypted in the puzzle book, but this is 72 | // what was submitted. This also doesn't error out because if there are no orders with the auctionID 73 | // then it doesn't really matter, we just return an empty list? Maybe we should have an API method 74 | // to return auctionIDs. 75 | func (db *CXDBMemory) ViewAuctionPuzzleBook(auctionID [32]byte) (orders []*match.EncryptedAuctionOrder, err error) { 76 | 77 | db.puzzleMtx.Lock() 78 | var ok bool 79 | if orders, ok = db.puzzles[auctionID]; !ok { 80 | logging.Debugf("Tried to find puzzles matching %x but none were found in DB.", auctionID) 81 | } 82 | db.puzzleMtx.Unlock() 83 | return 84 | } 85 | 86 | // NewAuction takes in an auction ID, and creates a new auction, returning the "height" 87 | // of the auction. 88 | func (db *CXDBMemory) NewAuction(auctionID [32]byte) (height uint64, err error) { 89 | // TODO 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/bank.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | ) 9 | 10 | // RegisterUser takes in a pubkey, and a map of asset to addresses for the pubkey. It inserts the necessary information in databases to register the pubkey. 11 | func (db *CXDBMemory) RegisterUser(pubkey *koblitz.PublicKey, addressMap map[*coinparam.Params]string) (err error) { 12 | 13 | db.balancesMtx.Lock() 14 | // We don't care about addresses because deposit stuff is not implemented yet and this shouldn't be used for production 15 | for coin := range addressMap { 16 | 17 | var pkpair pubkeyCoinPair 18 | copy(pkpair.pubkey[:], pubkey.SerializeCompressed()) 19 | pkpair.coin = coin 20 | 21 | db.balances[&pkpair] = 0 22 | } 23 | db.balancesMtx.Unlock() 24 | 25 | return 26 | } 27 | 28 | // GetBalance gets the balance for a pubkey and an asset. 29 | func (db *CXDBMemory) GetBalance(pubkey *koblitz.PublicKey, coin *coinparam.Params) (amount uint64, err error) { 30 | 31 | var pkpair pubkeyCoinPair 32 | copy(pkpair.pubkey[:], pubkey.SerializeCompressed()) 33 | pkpair.coin = coin 34 | 35 | db.balancesMtx.Lock() 36 | var found bool 37 | if amount, found = db.balances[&pkpair]; !found { 38 | db.balancesMtx.Unlock() 39 | err = fmt.Errorf("Could not find balance, register please") 40 | return 41 | } 42 | db.balancesMtx.Unlock() 43 | 44 | return 45 | } 46 | 47 | // AddToBalance adds to the balance of a user 48 | func (db *CXDBMemory) AddToBalance(pubkey *koblitz.PublicKey, amount uint64, coin *coinparam.Params) (err error) { 49 | 50 | var pkpair pubkeyCoinPair 51 | copy(pkpair.pubkey[:], pubkey.SerializeCompressed()) 52 | pkpair.coin = coin 53 | 54 | db.balancesMtx.Lock() 55 | var found bool 56 | var oldAmt uint64 57 | if oldAmt, found = db.balances[&pkpair]; !found { 58 | db.balancesMtx.Unlock() 59 | err = fmt.Errorf("Could not find balance, register please") 60 | return 61 | } 62 | db.balances[&pkpair] = oldAmt + amount 63 | db.balancesMtx.Unlock() 64 | 65 | return 66 | } 67 | 68 | // Withdraw checks the user's balance against the amount and if valid, reduces the balance by that amount. 69 | func (db *CXDBMemory) Withdraw(pubkey *koblitz.PublicKey, coin *coinparam.Params, amount uint64) (err error) { 70 | 71 | var pkpair pubkeyCoinPair 72 | copy(pkpair.pubkey[:], pubkey.SerializeCompressed()) 73 | pkpair.coin = coin 74 | 75 | db.balancesMtx.Lock() 76 | var found bool 77 | var oldAmt uint64 78 | if oldAmt, found = db.balances[&pkpair]; !found { 79 | db.balancesMtx.Unlock() 80 | err = fmt.Errorf("Could not find balance, register please") 81 | return 82 | } 83 | 84 | if oldAmt < amount { 85 | err = fmt.Errorf("You do not have enough balance to withdraw this amount") 86 | return 87 | } 88 | 89 | db.balances[&pkpair] = oldAmt - amount 90 | db.balancesMtx.Unlock() 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/db.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/opencx/match" 8 | ) 9 | 10 | // CXDBMemory is a super basic data store that just uses golang types for everything 11 | // there's no persistence, it's just used to build out the outer layers of a feature 12 | // before the persistent database details are worked out 13 | type CXDBMemory struct { 14 | balances map[*pubkeyCoinPair]uint64 15 | balancesMtx *sync.Mutex 16 | puzzles map[[32]byte][]*match.EncryptedAuctionOrder 17 | puzzleMtx *sync.Mutex 18 | orders map[[32]byte][]*match.AuctionOrder 19 | ordersMtx *sync.Mutex 20 | } 21 | 22 | type pubkeyCoinPair struct { 23 | pubkey [33]byte 24 | coin *coinparam.Params 25 | } 26 | 27 | // SetupClient makes sure that whatever things need to be done before we use the datastore can be done before we need to use the datastore. 28 | func (db *CXDBMemory) SetupClient(coins []*coinparam.Params) (err error) { 29 | 30 | db.puzzles = make(map[[32]byte][]*match.EncryptedAuctionOrder) 31 | db.puzzleMtx = new(sync.Mutex) 32 | 33 | db.balances = make(map[*pubkeyCoinPair]uint64) 34 | db.balancesMtx = new(sync.Mutex) 35 | 36 | db.orders = make(map[[32]byte][]*match.AuctionOrder) 37 | db.ordersMtx = new(sync.Mutex) 38 | 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/pinkyswear.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/mit-dci/lit/coinparam" 8 | "github.com/mit-dci/opencx/logging" 9 | "github.com/mit-dci/opencx/match" 10 | ) 11 | 12 | // PinkySwearEngine is a settlement engine that does not actually do any settlement. 13 | // It just sort of whitelists pubkeys that can / can not make orders 14 | type PinkySwearEngine struct { 15 | // the pubkey whitelist 16 | whitelist map[[33]byte]bool 17 | whitelistMtx *sync.Mutex 18 | 19 | // this coin 20 | coin *coinparam.Params 21 | 22 | // acceptAll, if true, accepts all transactions 23 | acceptAll bool 24 | } 25 | 26 | // CreatePinkySwearEngine creates a "pinky swear" engine for a specific coin 27 | func CreatePinkySwearEngine(coin *coinparam.Params, whitelist [][33]byte, acceptAll bool) (engine match.SettlementEngine, err error) { 28 | pe := &PinkySwearEngine{ 29 | coin: coin, 30 | whitelist: make(map[[33]byte]bool), 31 | whitelistMtx: new(sync.Mutex), 32 | acceptAll: acceptAll, 33 | } 34 | pe.whitelistMtx.Lock() 35 | for _, pubkey := range whitelist { 36 | pe.whitelist[pubkey] = true 37 | } 38 | pe.whitelistMtx.Unlock() 39 | engine = pe 40 | return 41 | } 42 | 43 | // ApplySettlementExecution applies the settlementExecution, this assumes that the settlement execution is 44 | // valid 45 | func (pe *PinkySwearEngine) ApplySettlementExecution(setExec *match.SettlementExecution) (setRes *match.SettlementResult, err error) { 46 | // this highlights potentially how not generic the SettlementResult struct is, what to put in the NewBal field??? 47 | setRes = &match.SettlementResult{ 48 | // this is where we have sort of undefined on what we do 49 | NewBal: 0, 50 | SuccessfulExec: setExec, 51 | } 52 | return 53 | } 54 | 55 | // CheckValid returns true if the settlement execution would be valid 56 | func (pe *PinkySwearEngine) CheckValid(setExec *match.SettlementExecution) (valid bool, err error) { 57 | // Finally a case that we can handle 58 | // if the coin is not the same then CheckValid fails 59 | var execAsset match.Asset 60 | if execAsset, err = match.AssetFromCoinParam(pe.coin); err != nil { 61 | err = fmt.Errorf("Error getting asset for engine coin: %s", err) 62 | return 63 | } 64 | 65 | logging.Infof("Exec: \n%+v\nExecAsset: %s", setExec, execAsset.String()) 66 | if execAsset != setExec.Asset { 67 | valid = false 68 | return 69 | } 70 | 71 | if pe.acceptAll { 72 | valid = true 73 | return 74 | } 75 | 76 | pe.whitelistMtx.Lock() 77 | var ok bool 78 | if valid, ok = pe.whitelist[setExec.Pubkey]; !ok { 79 | // just being really explicit here, all this does is check if you're in the whitelist and your value 80 | // is true. There's no reason for it to be false. 81 | logging.Infof("invalid pubkey") 82 | valid = false 83 | pe.whitelistMtx.Unlock() 84 | return 85 | } 86 | pe.whitelistMtx.Unlock() 87 | return 88 | } 89 | 90 | // CreatePinkySwearEngineMap creates a map of coin to settlement engine, given a map of coins to whitelists. 91 | // This creates pinky swear settlement engines, so beware because those let anyone on the 92 | // whitelist do settlement. 93 | // acceptAll will just accept all, pretty much ignores the whitelist 94 | func CreatePinkySwearEngineMap(whitelistMap map[*coinparam.Params][][33]byte, acceptAll bool) (setMap map[*coinparam.Params]match.SettlementEngine, err error) { 95 | 96 | setMap = make(map[*coinparam.Params]match.SettlementEngine) 97 | var curSetEng match.SettlementEngine 98 | for coin, whitelist := range whitelistMap { 99 | if curSetEng, err = CreatePinkySwearEngine(coin, whitelist, acceptAll); err != nil { 100 | err = fmt.Errorf("Error creating single settlement engine while creating pinky swear engine map: %s", err) 101 | return 102 | } 103 | setMap[coin] = curSetEng 104 | } 105 | 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/pinkyswear_test.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/opencx/match" 8 | ) 9 | 10 | var ( 11 | testWhitelist = [][33]byte{ 12 | // The "zero" pubkey 13 | [33]byte{}, 14 | } 15 | btc, _ = match.AssetFromCoinParam(&coinparam.BitcoinParams) 16 | testExecByZero = &match.SettlementExecution{ 17 | // The person that is on the whitelist 18 | Pubkey: [33]byte{}, 19 | // Just 1BTC because why not 20 | Amount: uint64(100000000), 21 | // This could be the right or wrong param depending on our engine 22 | Asset: btc, 23 | Type: match.Debit, 24 | } 25 | ) 26 | 27 | func TestCreatePinkySwearEngine(t *testing.T) { 28 | var err error 29 | 30 | if _, err = CreatePinkySwearEngine(&coinparam.BitcoinParams, testWhitelist, false); err != nil { 31 | t.Errorf("Error creating pinky swear engine for TestCreatePinkySwear: %s", err) 32 | return 33 | } 34 | 35 | return 36 | } 37 | 38 | func TestPinkySwearWrongAsset(t *testing.T) { 39 | var err error 40 | 41 | var engine match.SettlementEngine 42 | if engine, err = CreatePinkySwearEngine(&coinparam.VertcoinParams, testWhitelist, false); err != nil { 43 | t.Errorf("Error creating pinky swear engine for TestPinkySwearWrongAsset: %s", err) 44 | return 45 | } 46 | 47 | var valid bool 48 | if valid, err = engine.CheckValid(testExecByZero); err != nil { 49 | t.Errorf("Error checking valid for TestPinkySwearWrongAsset") 50 | return 51 | } 52 | 53 | expected := false 54 | // I know I can just do if valid, but this is more explicit and it's a test. It should be explicit, 55 | // and tests are easy to write. The last thing we want is a test that nobody can understand 56 | if valid != expected { 57 | t.Errorf("testExecByZero input to CheckValid should have been %t but was %t", expected, valid) 58 | return 59 | } 60 | 61 | } 62 | 63 | func TestPinkySwearRightAsset(t *testing.T) { 64 | var err error 65 | 66 | var engine match.SettlementEngine 67 | if engine, err = CreatePinkySwearEngine(&coinparam.BitcoinParams, testWhitelist, false); err != nil { 68 | t.Errorf("Error creating pinky swear engine for TestPinkySwearRightAsset: %s", err) 69 | return 70 | } 71 | 72 | var valid bool 73 | if valid, err = engine.CheckValid(testExecByZero); err != nil { 74 | t.Errorf("Error checking valid for TestPinkySwearRightAsset") 75 | return 76 | } 77 | 78 | expected := true 79 | if valid != expected { 80 | t.Errorf("testExecByZero input to CheckValid should have been %t but was %t", expected, valid) 81 | return 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/puzzlestore.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/mit-dci/opencx/cxdb" 8 | "github.com/mit-dci/opencx/match" 9 | ) 10 | 11 | // MemoryPuzzleStore is a puzzle store representation for an in memory database 12 | type MemoryPuzzleStore struct { 13 | puzzles map[match.AuctionID][]*match.EncryptedAuctionOrder 14 | puzzleMtx *sync.Mutex 15 | // the pair for this puzzle store 16 | // this is just for convenience, the protocol still works if you have one massive puzzle store 17 | // but if you run many markets at once then you may want to invalidate orders that weren't submitted 18 | // for the pair they said they were 19 | pair *match.Pair 20 | } 21 | 22 | // CreatePuzzleStore creates a puzzle store for a specific coin. 23 | func CreatePuzzleStore(pair *match.Pair) (store cxdb.PuzzleStore, err error) { 24 | // Set values 25 | mp := &MemoryPuzzleStore{ 26 | puzzles: make(map[match.AuctionID][]*match.EncryptedAuctionOrder), 27 | puzzleMtx: new(sync.Mutex), 28 | pair: pair, 29 | } 30 | // Now we actually set the engine 31 | store = mp 32 | return 33 | } 34 | 35 | // ViewAuctionPuzzleBook takes in an auction ID, and returns encrypted auction orders, and puzzles. 36 | // You don't know what auction IDs should be in the orders encrypted in the puzzle book, but this is 37 | // what was submitted. 38 | func (mp *MemoryPuzzleStore) ViewAuctionPuzzleBook(auctionID *match.AuctionID) (puzzles []*match.EncryptedAuctionOrder, err error) { 39 | mp.puzzleMtx.Lock() 40 | for _, pzList := range mp.puzzles { 41 | puzzles = append(puzzles, pzList...) 42 | } 43 | mp.puzzleMtx.Unlock() 44 | return 45 | } 46 | 47 | // PlaceAuctionPuzzle puts an encrypted auction order in the datastore. 48 | func (mp *MemoryPuzzleStore) PlaceAuctionPuzzle(puzzledOrder *match.EncryptedAuctionOrder) (err error) { 49 | mp.puzzleMtx.Lock() 50 | mp.puzzles[puzzledOrder.IntendedAuction] = append(mp.puzzles[puzzledOrder.IntendedAuction], puzzledOrder) 51 | mp.puzzleMtx.Unlock() 52 | return 53 | } 54 | 55 | // CreatePuzzleStoreMap creates a map of pair to pair list, given a list of pairs. 56 | func CreatePuzzleStoreMap(pairList []*match.Pair) (pzMap map[match.Pair]cxdb.PuzzleStore, err error) { 57 | 58 | pzMap = make(map[match.Pair]cxdb.PuzzleStore) 59 | var curPzEng cxdb.PuzzleStore 60 | for _, pair := range pairList { 61 | if curPzEng, err = CreatePuzzleStore(pair); err != nil { 62 | err = fmt.Errorf("Error creating single puzzle store while creating puzzle store map: %s", err) 63 | return 64 | } 65 | pzMap[*pair] = curPzEng 66 | } 67 | 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /cxdb/cxdbmemory/settlementengine.go: -------------------------------------------------------------------------------- 1 | package cxdbmemory 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/mit-dci/lit/coinparam" 8 | "github.com/mit-dci/opencx/match" 9 | ) 10 | 11 | type MemorySettlementEngine struct { 12 | // Balances 13 | balances map[[33]byte]uint64 14 | balancesMtx *sync.Mutex 15 | 16 | // this coin 17 | coin *coinparam.Params 18 | } 19 | 20 | // CreateSettlementEngine creates a settlement engine for a specific coin 21 | func CreateSettlementEngine(coin *coinparam.Params) (engine match.SettlementEngine, err error) { 22 | 23 | // Set values 24 | me := &MemorySettlementEngine{ 25 | balances: make(map[[33]byte]uint64), 26 | balancesMtx: new(sync.Mutex), 27 | coin: coin, 28 | } 29 | 30 | // Now we actually set what we want 31 | engine = me 32 | return 33 | } 34 | 35 | // ApplySettlementExecution applies the settlementExecution, this assumes that the settlement execution is 36 | // valid 37 | func (me *MemorySettlementEngine) ApplySettlementExecution(setExec *match.SettlementExecution) (setRes *match.SettlementResult, err error) { 38 | 39 | me.balancesMtx.Lock() 40 | var curBal uint64 41 | var ok bool 42 | if curBal, ok = me.balances[setExec.Pubkey]; !ok && setExec.Type == match.Credit { 43 | err = fmt.Errorf("Trying to apply settlement execution credit to order with no balance") 44 | me.balancesMtx.Unlock() 45 | return 46 | } 47 | 48 | var newBal uint64 49 | if setExec.Type == match.Debit { 50 | newBal = curBal + setExec.Amount 51 | } else if setExec.Type == match.Credit { 52 | newBal = curBal - setExec.Amount 53 | } 54 | 55 | me.balances[setExec.Pubkey] = newBal 56 | me.balancesMtx.Unlock() 57 | // Finally set return value 58 | setRes = &match.SettlementResult{ 59 | NewBal: newBal, 60 | SuccessfulExec: setExec, 61 | } 62 | 63 | return 64 | } 65 | 66 | // CheckValid returns true if the settlement execution would be valid 67 | func (me *MemorySettlementEngine) CheckValid(setExec *match.SettlementExecution) (valid bool, err error) { 68 | if setExec.Type == match.Debit { 69 | // No settlement will be an invalid debit 70 | valid = true 71 | return 72 | } 73 | me.balancesMtx.Lock() 74 | curBal := me.balances[setExec.Pubkey] 75 | me.balancesMtx.Unlock() 76 | valid = setExec.Amount > curBal 77 | return 78 | } 79 | 80 | // CreateSettlementEngineMap creates a map of coin to settlement engine, given a list of coins. 81 | func CreateSettlementEngineMap(coins []*coinparam.Params) (setMap map[*coinparam.Params]match.SettlementEngine, err error) { 82 | 83 | setMap = make(map[*coinparam.Params]match.SettlementEngine) 84 | var curSetEng match.SettlementEngine 85 | for _, coin := range coins { 86 | if curSetEng, err = CreateSettlementEngine(coin); err != nil { 87 | err = fmt.Errorf("Error creating single settlement engine while creating settlement engine map: %s", err) 88 | return 89 | } 90 | setMap[coin] = curSetEng 91 | } 92 | 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /cxdb/cxdbsql/README.md: -------------------------------------------------------------------------------- 1 | # cxdbsql 2 | [![Build Status](https://travis-ci.org/mit-dci/opencx.svg?branch=master)](https://travis-ci.org/mit-dci/opencx) 3 | [![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/mit-dci/opencx/blob/master/LICENSE) 4 | [![GoDoc](https://godoc.org/github.com/mit-dci/opencx/cxdb/cxdbsql?status.svg)](https://godoc.org/github.com/mit-dci/opencx/cxdb/cxdbsql) 5 | 6 | 7 | The cxdbsql packages implements any storage interfaces defined in `cxdb`, as well as some interfaces in `match` using MySQL. 8 | We may want to move all remaining interfaces from cxdb to match 9 | -------------------------------------------------------------------------------- /cxdb/cxdbsql/depositstore_test.go: -------------------------------------------------------------------------------- 1 | package cxdbsql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCreateDepositStoreAllParams(t *testing.T) { 8 | var err error 9 | 10 | var tc *testerContainer 11 | if tc, err = CreateTesterContainer(); err != nil { 12 | t.Errorf("Error creating tester container: %s", err) 13 | } 14 | 15 | defer func() { 16 | if err = tc.Kill(); err != nil { 17 | t.Errorf("Error killing tester container: %s", err) 18 | return 19 | } 20 | }() 21 | 22 | var ds *SQLDepositStore 23 | for _, coin := range constCoinParams() { 24 | if ds, err = CreateDepositStoreStructWithConf(coin, testConfig()); err != nil { 25 | t.Errorf("Error creating deposit store for coin: %s", err) 26 | } 27 | 28 | if err = ds.DestroyHandler(); err != nil { 29 | t.Errorf("Error destroying handler for deposit store: %s", err) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cxnoise/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2018 The Lightning Network Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /cxnoise/README.md: -------------------------------------------------------------------------------- 1 | cxnoise 2 | ========== 3 | 4 | The cxnoise package implements a secure crypto messaging protocol based off of 5 | the [Noise Protocol Framework](http://noiseprotocol.org/noise.html). The 6 | package exposes the raw state machine that handles the handshake and subsequent 7 | message encryption/decryption scheme. Additionally, the package exposes a 8 | [net.Conn](https://golang.org/pkg/net/#Conn) and a 9 | [net.Listener](https://golang.org/pkg/net/#Listener) interface implementation 10 | which allows the encrypted transport to be seamlessly integrated into a 11 | codebase. 12 | 13 | The secure messaging scheme implemented within this package uses `NOISE_XX` as the handshake for authenticated key exchange. Please note that this is not the same as [brontide](https://github.com/lightningnetwork/lnd/tree/master/brontide) which uses the `NOISE_XK` protocol for handshakes and `cxnoise` is not compatible with the same. 14 | 15 | This package has intentionally been designed so it can be used as a standalone 16 | package for any projects needing secure encrypted+authenticated communications 17 | between network enabled programs. 18 | 19 | This package requires additional attribution to that of lit since it is adapted from the original [brontide](https://github.com/lightningnetwork/lnd/tree/master/brontide) package. Please see [license](LICENSE) for details. 20 | -------------------------------------------------------------------------------- /cxrpc/README.md: -------------------------------------------------------------------------------- 1 | # cxrpc 2 | 3 | This package handles RPC requests coming in to the exchange. Here are all the commands supported so far: 4 | RPC is just a starting point for being able to accept network I/O 5 | 6 | ## register 7 | Register registers an account if that username does not exist already 8 | 9 | `ocx register name` 10 | 11 | Arguments: 12 | - Name (string) 13 | 14 | Outputs: 15 | - A message that says you successfully registered or an error 16 | 17 | ## vieworderbook 18 | Vieworderbook shows you the current orderbook 19 | 20 | `ocx vieworderbook pair [buy/sell]` 21 | 22 | Arguments: 23 | - Asset pair (string) 24 | - buy or sell (optional string) 25 | 26 | Outputs: 27 | - The orderbook in a nice little command-line table 28 | 29 | If you specify buy or sell you will be given only the buy or sell side of the order book for the specified pair. 30 | 31 | ## getprice 32 | Getprice will get the price of a pair, based on midpoint of volume of bids and asks 33 | 34 | `ocx getprice pair` 35 | 36 | Arguments: 37 | - Asset pair (string) 38 | 39 | Outputs: 40 | - The price / conversion rate of the asset 41 | 42 | ## placeorder 43 | This will print a description of the order after making it, and prompt the user before actually sending it. 44 | 45 | `ocx placeorder name {buy|sell} pair amountHave price` 46 | 47 | The price is price, amountHave is the amount of the asset you have. If you're on the selling side, that will be the first asset1 in the asset1/asset2 pair. If you're on the buying side, that will be the second, asset2. 48 | 49 | Arguments: 50 | - Name (string) 51 | - buy or sell (string) 52 | - Asset pair (string) 53 | - AmountHave (uint) 54 | - Price (float) 55 | 56 | Outputs: 57 | - Order submitted successfully (or error) 58 | - An order ID (or error) 59 | 60 | ## getdepositaddress 61 | Getdepositaddress will return the deposit address that is assigned to the user's account for a certain asset. 62 | 63 | `ocx getdepositaddress name asset` 64 | 65 | Arguments 66 | - Name (string) 67 | - Asset (string) 68 | 69 | Outputs: 70 | - A deposit address for the specified name and asset (or error) 71 | 72 | ## withdraw 73 | Withdraw will send a withdraw transaction to the blockchain. 74 | 75 | `ocx withdrawtoaddress name amount asset recvaddress` 76 | 77 | Arguments: 78 | - Name (string) 79 | - Amount (uint, satoshis) 80 | - Asset (string) 81 | - Receive address (string) 82 | 83 | Outputs: 84 | - Transaction ID (or error) 85 | 86 | ## getbalance 87 | Getbalance will get your balance 88 | 89 | `ocx getbalance name asset` 90 | 91 | Arguments: 92 | - Name (string) 93 | - Asset (string) 94 | 95 | Outputs: 96 | - Your balance for specified asset (or error) 97 | 98 | ## getallbalances 99 | Getallbalances will get balances for all of your assets. 100 | 101 | `ocx getallbalances name` 102 | 103 | Arguments: 104 | - Name (string) 105 | 106 | Outputs: 107 | - Balances for all of your assets (or error) 108 | -------------------------------------------------------------------------------- /cxrpc/accountcmds.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/crypto/koblitz" 7 | "github.com/mit-dci/opencx/logging" 8 | ) 9 | 10 | // RegisterArgs holds the args for register 11 | type RegisterArgs struct { 12 | Signature []byte 13 | } 14 | 15 | // RegisterReply holds the data for the register reply 16 | type RegisterReply struct { 17 | // empty 18 | } 19 | 20 | // Register registers a pubkey into the db, verifies that the action was signed by that pubkey. A valid signature for the string "register" is considered a valid registration. 21 | func (cl *OpencxRPC) Register(args RegisterArgs, reply *RegisterReply) (err error) { 22 | 23 | var pubkey *koblitz.PublicKey 24 | if pubkey, err = cl.Server.RegistrationStringVerify(args.Signature); err != nil { 25 | err = fmt.Errorf("Error verifying registration string for register RPC command: %s", err) 26 | return 27 | } 28 | 29 | if err = cl.Server.RegisterUser(pubkey); err != nil { 30 | err = fmt.Errorf("Error registering user for register RPC command: %s", err) 31 | return 32 | } 33 | 34 | logging.Infof("Registering user with pubkey %x\n", pubkey.SerializeCompressed()) 35 | // put this in database 36 | 37 | return 38 | } 39 | 40 | // GetRegistrationStringArgs holds the args for register 41 | type GetRegistrationStringArgs struct { 42 | // empty 43 | } 44 | 45 | // GetRegistrationStringReply holds the data for the register reply 46 | type GetRegistrationStringReply struct { 47 | RegistrationString string 48 | } 49 | 50 | // GetRegistrationString returns a string to the client which is a valid string to sign to indicate they want their pubkey to be registered. This is like kinda weird but whatever 51 | func (cl *OpencxRPC) GetRegistrationString(args GetRegistrationStringArgs, reply *GetRegistrationStringReply) (err error) { 52 | reply.RegistrationString = cl.Server.GetRegistrationString() 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /cxrpc/bankcmds.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | util "github.com/mit-dci/opencx/chainutils" 9 | "github.com/mit-dci/opencx/match" 10 | "golang.org/x/crypto/sha3" 11 | ) 12 | 13 | // GetBalanceArgs hold the arguments for GetBalance 14 | type GetBalanceArgs struct { 15 | Asset string 16 | Signature []byte 17 | } 18 | 19 | // GetBalanceReply holds the reply for GetBalance 20 | type GetBalanceReply struct { 21 | Amount uint64 22 | } 23 | 24 | // GetBalance is the RPC Interface for GetBalance 25 | func (cl *OpencxRPC) GetBalance(args GetBalanceArgs, reply *GetBalanceReply) (err error) { 26 | 27 | // e = h(asset) 28 | sha3 := sha3.New256() 29 | sha3.Write([]byte(args.Asset)) 30 | e := sha3.Sum(nil) 31 | 32 | var pubkey *koblitz.PublicKey 33 | if pubkey, _, err = koblitz.RecoverCompact(koblitz.S256(), args.Signature, e); err != nil { 34 | err = fmt.Errorf("Error verifying order, invalid signature: \n%s", err) 35 | return 36 | } 37 | 38 | var param *coinparam.Params 39 | if param, err = util.GetParamFromName(args.Asset); err != nil { 40 | err = fmt.Errorf("Error getting coin type from name, pass in a different asset") 41 | return 42 | } 43 | 44 | if reply.Amount, err = cl.Server.GetBalance(pubkey, param); err != nil { 45 | err = fmt.Errorf("Error getting balance for pubkey in GetBalance RPC command: %s", err) 46 | return 47 | } 48 | 49 | return 50 | } 51 | 52 | // GetDepositAddressArgs hold the arguments for GetDepositAddress 53 | type GetDepositAddressArgs struct { 54 | Asset string 55 | Signature []byte 56 | } 57 | 58 | // GetDepositAddressReply holds the reply for GetDepositAddress 59 | type GetDepositAddressReply struct { 60 | Address string 61 | } 62 | 63 | // GetDepositAddress is the RPC Interface for GetDepositAddress 64 | func (cl *OpencxRPC) GetDepositAddress(args GetDepositAddressArgs, reply *GetDepositAddressReply) (err error) { 65 | 66 | // e = h(asset) 67 | sha3 := sha3.New256() 68 | sha3.Write([]byte(args.Asset)) 69 | e := sha3.Sum(nil) 70 | 71 | var pubkey *koblitz.PublicKey 72 | if pubkey, _, err = koblitz.RecoverCompact(koblitz.S256(), args.Signature, e); err != nil { 73 | err = fmt.Errorf("Error, invalid signature with GetDepositAddress RPC command: %s", err) 74 | return 75 | } 76 | 77 | var param *coinparam.Params 78 | if param, err = util.GetParamFromName(args.Asset); err != nil { 79 | err = fmt.Errorf("Error getting param from name for asset: %s", err) 80 | return 81 | } 82 | 83 | if reply.Address, err = cl.Server.GetDepositAddress(pubkey, param); err != nil { 84 | err = fmt.Errorf("Error getting deposit address from server for GetDepositAddress RPC: %s", err) 85 | return 86 | } 87 | 88 | return 89 | } 90 | 91 | // WithdrawArgs holds the args for Withdraw 92 | type WithdrawArgs struct { 93 | Withdrawal *match.Withdrawal 94 | Signature []byte 95 | } 96 | 97 | // WithdrawReply holds the reply for Withdraw 98 | type WithdrawReply struct { 99 | Txid string 100 | } 101 | 102 | // Withdraw is the RPC Interface for Withdraw 103 | func (cl *OpencxRPC) Withdraw(args WithdrawArgs, reply *WithdrawReply) (err error) { 104 | 105 | // e = h(asset) 106 | sha3 := sha3.New256() 107 | sha3.Write(args.Withdrawal.Serialize()) 108 | e := sha3.Sum(nil) 109 | 110 | pubkey, _, err := koblitz.RecoverCompact(koblitz.S256(), args.Signature, e) 111 | if err != nil { 112 | err = fmt.Errorf("Error verifying order, invalid signature: \n%s", err) 113 | return 114 | } 115 | 116 | var coinType *coinparam.Params 117 | if coinType, err = util.GetParamFromName(args.Withdrawal.Asset.String()); err != nil { 118 | return 119 | } 120 | 121 | // We just ignore the address if they specify lightning. 122 | 123 | if args.Withdrawal.Lightning { 124 | 125 | if reply.Txid, err = cl.Server.WithdrawLightning(pubkey, args.Withdrawal.Amount, coinType); err != nil { 126 | err = fmt.Errorf("Error with withdraw command (withdraw from lightning): \n%s", err) 127 | return 128 | } 129 | 130 | } else { 131 | 132 | if reply.Txid, err = cl.Server.WithdrawCoins(args.Withdrawal.Address, pubkey, args.Withdrawal.Amount, coinType); err != nil { 133 | err = fmt.Errorf("Error with withdraw command (withdraw from chain): \n%s", err) 134 | return 135 | } 136 | 137 | } 138 | 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /cxrpc/channelcmds.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | 8 | "github.com/mit-dci/opencx/logging" 9 | "github.com/mit-dci/opencx/match" 10 | ) 11 | 12 | // GetLitConnectionArgs holds the args for the getlitconnection RPC command 13 | type GetLitConnectionArgs struct { 14 | // empty 15 | } 16 | 17 | // GetLitConnectionReply holds the reply for the getlitconnection RPC command 18 | type GetLitConnectionReply struct { 19 | PubKeyHash string 20 | Ports []uint16 21 | } 22 | 23 | // GetLitConnection gets a pubkeyhash and port for connecting with lit, the hostname is assumed to be the same. 24 | func (cl *OpencxRPC) GetLitConnection(args GetLitConnectionArgs, reply *GetLitConnectionReply) (err error) { 25 | var hosts []string 26 | reply.PubKeyHash, hosts = cl.Server.ExchangeNode.GetLisAddressAndPorts() 27 | 28 | if len(hosts) == 0 { 29 | err = fmt.Errorf("Exchange not listening at the moment, sorry") 30 | return 31 | } 32 | logging.Infof("Sending connection to client, addr: %s len hosts: %d", reply.PubKeyHash, len(hosts)) 33 | 34 | reply.Ports = make([]uint16, len(hosts)) 35 | for i, hostport := range hosts { 36 | var port string 37 | // we don't care about the host 38 | if _, port, err = net.SplitHostPort(hostport); err != nil { 39 | return 40 | } 41 | 42 | var port64 uint64 43 | if port64, err = strconv.ParseUint(port, 10, 16); err != nil { 44 | return 45 | } 46 | 47 | reply.Ports[i] = uint16(port64) 48 | } 49 | 50 | return 51 | } 52 | 53 | // WithdrawToLightningNodeArgs holds the args for the withdrawtolightning RPC command 54 | type WithdrawToLightningNodeArgs struct { 55 | Withdrawal *match.Withdrawal 56 | Signature []byte 57 | } 58 | 59 | // WithdrawToLightningNodeReply holds the reply for the withdrawtolightning RPC command 60 | type WithdrawToLightningNodeReply struct { 61 | Txid string 62 | } 63 | 64 | // WithdrawToLightningNode creates a channel that pushes a certain amount to a lightning node through a lightning channel. 65 | func (cl *OpencxRPC) WithdrawToLightningNode(args WithdrawToLightningNodeArgs, reply *WithdrawToLightningNodeReply) (err error) { 66 | 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /cxrpc/cxrpc.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/mit-dci/opencx/cxserver" 7 | ) 8 | 9 | // OpencxRPC is what is registered and called 10 | type OpencxRPC struct { 11 | Server *cxserver.OpencxServer 12 | } 13 | 14 | // OpencxRPCCaller is a listener for RPC commands 15 | type OpencxRPCCaller struct { 16 | caller *OpencxRPC 17 | listener net.Listener 18 | killers []chan bool 19 | } 20 | -------------------------------------------------------------------------------- /cxrpc/listener.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/rpc" 7 | 8 | "github.com/mit-dci/lit/crypto/koblitz" 9 | "github.com/mit-dci/opencx/cxnoise" 10 | "github.com/mit-dci/opencx/cxserver" 11 | "github.com/mit-dci/opencx/logging" 12 | ) 13 | 14 | func CreateRPCForServer(server *cxserver.OpencxServer) (rpc1 *OpencxRPCCaller, err error) { 15 | rpc1 = &OpencxRPCCaller{ 16 | caller: &OpencxRPC{ 17 | Server: server, 18 | }, 19 | } 20 | return 21 | } 22 | 23 | // NoiseListen is a synchronous version of RPCListenAsync 24 | func (rpc1 *OpencxRPCCaller) NoiseListen(privkey *koblitz.PrivateKey, host string, port uint16) (err error) { 25 | 26 | doneChan := make(chan bool, 1) 27 | errChan := make(chan error, 1) 28 | go rpc1.NoiseListenAsync(doneChan, errChan, privkey, host, port) 29 | select { 30 | case err = <-errChan: 31 | case <-doneChan: 32 | } 33 | 34 | return 35 | } 36 | 37 | // NoiseListenAsync listens on socket host and port 38 | func (rpc1 *OpencxRPCCaller) NoiseListenAsync(doneChan chan bool, errChan chan error, privkey *koblitz.PrivateKey, host string, port uint16) { 39 | var err error 40 | if rpc1.caller == nil { 41 | errChan <- fmt.Errorf("Error, rpc caller cannot be nil, please create caller correctly") 42 | close(errChan) 43 | return 44 | } 45 | 46 | // Start noise rpc server (need to do this since the client is a rpc newclient) 47 | noiseRPCServer := rpc.NewServer() 48 | 49 | logging.Infof("Registering RPC API over Noise protocol ...") 50 | // Register rpc 51 | if err = noiseRPCServer.Register(rpc1.caller); err != nil { 52 | errChan <- fmt.Errorf("Error registering RPC Interface: %s", err) 53 | close(errChan) 54 | return 55 | } 56 | 57 | logging.Infof("Starting RPC Server over noise protocol") 58 | // Start RPC Server 59 | if rpc1.listener, err = cxnoise.NewListener(privkey, int(port)); err != nil { 60 | errChan <- fmt.Errorf("Error creating noise listener for NoiseListenAsync: %s", err) 61 | close(errChan) 62 | return 63 | } 64 | logging.Infof("Running RPC-Noise server on %s\n", rpc1.listener.Addr().String()) 65 | 66 | // We don't need to do anything fancy here either because the noise protocol 67 | // is built in to the listener as well. 68 | go noiseRPCServer.Accept(rpc1.listener) 69 | doneChan <- true 70 | close(doneChan) 71 | return 72 | } 73 | 74 | // RPCListen is a synchronous version of RPCListenAsync 75 | func (rpc1 *OpencxRPCCaller) RPCListen(host string, port uint16) (err error) { 76 | 77 | doneChan := make(chan bool, 1) 78 | errChan := make(chan error, 1) 79 | go rpc1.RPCListenAsync(doneChan, errChan, host, port) 80 | select { 81 | case err = <-errChan: 82 | case <-doneChan: 83 | } 84 | 85 | return 86 | } 87 | 88 | // RPCListenAsync listens on socket host and port 89 | func (rpc1 *OpencxRPCCaller) RPCListenAsync(doneChan chan bool, errChan chan error, host string, port uint16) { 90 | var err error 91 | if rpc1.caller == nil { 92 | errChan <- fmt.Errorf("Error, rpc caller cannot be nil, please create caller correctly") 93 | close(errChan) 94 | return 95 | } 96 | 97 | logging.Infof("Registering RPC API...") 98 | // Register rpc 99 | if err = rpc.Register(rpc1.caller); err != nil { 100 | errChan <- fmt.Errorf("Error registering RPC Interface: %s", err) 101 | close(errChan) 102 | return 103 | } 104 | 105 | logging.Infof("Starting RPC Server") 106 | // Start RPC Server 107 | serverAddr := net.JoinHostPort(host, fmt.Sprintf("%d", port)) 108 | if rpc1.listener, err = net.Listen("tcp", serverAddr); err != nil { 109 | errChan <- fmt.Errorf("Error listening for RPCListenAsync: %s", err) 110 | close(errChan) 111 | return 112 | } 113 | logging.Infof("Running RPC server on %s\n", rpc1.listener.Addr().String()) 114 | 115 | go rpc.Accept(rpc1.listener) 116 | doneChan <- true 117 | close(doneChan) 118 | return 119 | } 120 | 121 | // WaitUntilDead waits until the Stop() method is called 122 | func (rpc1 *OpencxRPCCaller) WaitUntilDead() { 123 | dedchan := make(chan bool, 1) 124 | rpc1.killers = append(rpc1.killers, dedchan) 125 | <-dedchan 126 | return 127 | } 128 | 129 | // Stop closes the RPC listener and notifies those from WaitUntilDead 130 | func (rpc1 *OpencxRPCCaller) Stop() (err error) { 131 | if rpc1.listener == nil { 132 | err = fmt.Errorf("Error, cannot stop a listener that doesn't exist") 133 | return 134 | } 135 | logging.Infof("Stopping RPC!!") 136 | if err = rpc1.listener.Close(); err != nil { 137 | err = fmt.Errorf("Error closing listener: %s", err) 138 | return 139 | } 140 | // kill the guy waiting 141 | for _, killer := range rpc1.killers { 142 | // send the signals, but even if they don't send, close the channel 143 | select { 144 | case killer <- true: 145 | close(killer) 146 | default: 147 | close(killer) 148 | } 149 | } 150 | return 151 | } 152 | -------------------------------------------------------------------------------- /cxrpc/opencxclient.go: -------------------------------------------------------------------------------- 1 | package cxrpc 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/rpc" 7 | 8 | "github.com/mit-dci/lit/crypto/koblitz" 9 | "github.com/mit-dci/opencx/cxnoise" 10 | ) 11 | 12 | // OpencxClient is an interface defining the methods a client should implement. 13 | // This could be changed, but right now this is what a client is, and this is what benchclient supports. 14 | // This abstraction only allows us to use either authenticated or unauthenticated clients. 15 | type OpencxClient interface { 16 | // Call calls the service method with a name, arguments, and reply 17 | Call(string, interface{}, interface{}) error 18 | // SetupConnection sets up a connection with the server 19 | SetupConnection(string, uint16) error 20 | } 21 | 22 | // OpencxRPCClient is a RPC client for the opencx server 23 | type OpencxRPCClient struct { 24 | Conn *rpc.Client 25 | } 26 | 27 | // OpencxNoiseClient is an authenticated RPC Client for the opencx Server 28 | type OpencxNoiseClient struct { 29 | Conn *rpc.Client 30 | key *koblitz.PrivateKey 31 | } 32 | 33 | // Call calls the servicemethod with name string, args args, and reply reply 34 | func (cl *OpencxRPCClient) Call(serviceMethod string, args interface{}, reply interface{}) error { 35 | return cl.Conn.Call(serviceMethod, args, reply) 36 | } 37 | 38 | // SetupConnection creates a new RPC client 39 | func (cl *OpencxRPCClient) SetupConnection(server string, port uint16) (err error) { 40 | 41 | serverAddr := net.JoinHostPort(server, fmt.Sprintf("%d", port)) 42 | 43 | if cl.Conn, err = rpc.Dial("tcp", serverAddr); err != nil { 44 | return 45 | } 46 | 47 | return 48 | } 49 | 50 | // Call calls the servicemethod with name string, args args, and reply reply 51 | func (cl *OpencxNoiseClient) Call(serviceMethod string, args interface{}, reply interface{}) (err error) { 52 | 53 | // Create new client because the server is a rpc newserver 54 | // we don't need to do anything fancy here because the cl.Conn 55 | // already uses the noise protocol. 56 | if err = cl.Conn.Call(serviceMethod, args, reply); err != nil { 57 | return 58 | } 59 | 60 | return 61 | } 62 | 63 | // SetKey sets the private key for the noise client. 64 | func (cl *OpencxNoiseClient) SetKey(privkey *koblitz.PrivateKey) (err error) { 65 | if privkey == nil { 66 | err = fmt.Errorf("Cannot set nil key") 67 | return 68 | } 69 | cl.key = privkey 70 | return 71 | } 72 | 73 | // SetupConnection creates a new RPC Noise client 74 | func (cl *OpencxNoiseClient) SetupConnection(server string, port uint16) (err error) { 75 | 76 | if cl.key == nil { 77 | err = fmt.Errorf("Please set the key for the noise client to create a connection") 78 | return 79 | } 80 | 81 | serverAddr := net.JoinHostPort(server, fmt.Sprintf("%d", port)) 82 | 83 | // Dial a connection to the server 84 | var clientConn *cxnoise.Conn 85 | if clientConn, err = cxnoise.Dial(cl.key, serverAddr, []byte("opencx"), net.Dial); err != nil { 86 | return 87 | } 88 | 89 | cl.Conn = rpc.NewClient(clientConn) 90 | 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /cxserver/balance.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | "github.com/mit-dci/opencx/cxdb" 9 | ) 10 | 11 | // GetBalance gets the balance for a specific public key and coin. 12 | func (server *OpencxServer) GetBalance(pubkey *koblitz.PublicKey, coin *coinparam.Params) (amount uint64, err error) { 13 | 14 | // First get the settlement store 15 | // TODO: There should be two locks, one for critical things such as the matching and settlement 16 | // engine, and another for just the stuff we view. (like the orderbooks and stores) 17 | server.dbLock.Lock() 18 | var currSettlementStore cxdb.SettlementStore 19 | var ok bool 20 | if currSettlementStore, ok = server.SettlementStores[coin]; !ok { 21 | err = fmt.Errorf("Cannot find the settlement store for GetBalance") 22 | server.dbLock.Unlock() 23 | return 24 | } 25 | 26 | if amount, err = currSettlementStore.GetBalance(pubkey); err != nil { 27 | err = fmt.Errorf("Could not get balance for pubkey for GetBalance: %s", err) 28 | server.dbLock.Unlock() 29 | return 30 | } 31 | server.dbLock.Unlock() 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /cxserver/debitcredit.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | "github.com/mit-dci/opencx/cxdb" 9 | "github.com/mit-dci/opencx/match" 10 | ) 11 | 12 | // DebitUser adds to the balance of the pubkey by issuing a settlement exec and bringing it through 13 | // all of the required data stores. 14 | // DebitUser acquires dbLock so it can just be called. 15 | func (server *OpencxServer) DebitUser(pubkey *koblitz.PublicKey, amount uint64, param *coinparam.Params) (err error) { 16 | 17 | var assetToDebit match.Asset 18 | if assetToDebit, err = match.AssetFromCoinParam(param); err != nil { 19 | err = fmt.Errorf("Error getting asset from coin param for DebitUser: %s", err) 20 | return 21 | } 22 | 23 | // Lock! 24 | server.dbLock.Lock() 25 | 26 | // Get the settle store and the settle engine for the coin 27 | var currSettleStore cxdb.SettlementStore 28 | var ok bool 29 | if currSettleStore, ok = server.SettlementStores[param]; !ok { 30 | err = fmt.Errorf("Could not find settlement store for cointype %s", param.Name) 31 | server.dbLock.Unlock() 32 | return 33 | } 34 | 35 | var currSettleEngine match.SettlementEngine 36 | if currSettleEngine, ok = server.SettlementEngines[param]; !ok { 37 | err = fmt.Errorf("Could not find settlement engine for cointype %s", param.Name) 38 | server.dbLock.Unlock() 39 | return 40 | } 41 | 42 | setExecForPush := &match.SettlementExecution{ 43 | Type: match.Debit, 44 | Asset: assetToDebit, 45 | Amount: amount, 46 | } 47 | copy(setExecForPush.Pubkey[:], pubkey.SerializeCompressed()) 48 | 49 | var valid bool 50 | if valid, err = currSettleEngine.CheckValid(setExecForPush); err != nil { 51 | err = fmt.Errorf("Error checking valid exec for DebitUser: %s", err) 52 | server.dbLock.Unlock() 53 | return 54 | } 55 | 56 | var setRes *match.SettlementResult 57 | var settlementResults []*match.SettlementResult 58 | if valid { 59 | if setRes, err = currSettleEngine.ApplySettlementExecution(setExecForPush); err != nil { 60 | err = fmt.Errorf("Error applying settlement exec for DebitUser: %s", err) 61 | server.dbLock.Unlock() 62 | return 63 | } 64 | } else { 65 | err = fmt.Errorf("Error, invalid settlement exec for DebitUser") 66 | server.dbLock.Unlock() 67 | return 68 | } 69 | 70 | settlementResults = append(settlementResults, setRes) 71 | 72 | if err = currSettleStore.UpdateBalances(settlementResults); err != nil { 73 | err = fmt.Errorf("Error updating balances for DebitUser: %s", err) 74 | server.dbLock.Unlock() 75 | return 76 | } 77 | 78 | server.dbLock.Unlock() 79 | return 80 | } 81 | 82 | // CreditUser subtracts the balance of the pubkey by issuing a settlement exec and bringing it through 83 | // all of the required data stores. 84 | // CreditUser acquires dbLock so it can just be called. 85 | func (server *OpencxServer) CreditUser(pubkey *koblitz.PublicKey, amount uint64, param *coinparam.Params) (err error) { 86 | 87 | var assetToCredit match.Asset 88 | if assetToCredit, err = match.AssetFromCoinParam(param); err != nil { 89 | err = fmt.Errorf("Error getting asset from coin param for CreditUser: %s", err) 90 | return 91 | } 92 | 93 | // Lock! 94 | server.dbLock.Lock() 95 | 96 | // Get the settle store and the settle engine for the coin 97 | var currSettleStore cxdb.SettlementStore 98 | var ok bool 99 | if currSettleStore, ok = server.SettlementStores[param]; !ok { 100 | err = fmt.Errorf("Could not find settlement store for cointype %s", param.Name) 101 | server.dbLock.Unlock() 102 | return 103 | } 104 | 105 | var currSettleEngine match.SettlementEngine 106 | if currSettleEngine, ok = server.SettlementEngines[param]; !ok { 107 | err = fmt.Errorf("Could not find settlement engine for cointype %s", param.Name) 108 | server.dbLock.Unlock() 109 | return 110 | } 111 | 112 | setExecForPush := &match.SettlementExecution{ 113 | Type: match.Credit, 114 | Asset: assetToCredit, 115 | Amount: amount, 116 | } 117 | copy(setExecForPush.Pubkey[:], pubkey.SerializeCompressed()) 118 | 119 | var valid bool 120 | if valid, err = currSettleEngine.CheckValid(setExecForPush); err != nil { 121 | err = fmt.Errorf("Error checking valid exec for CreditUser: %s", err) 122 | server.dbLock.Unlock() 123 | return 124 | } 125 | 126 | var setRes *match.SettlementResult 127 | var settlementResults []*match.SettlementResult 128 | if valid { 129 | if setRes, err = currSettleEngine.ApplySettlementExecution(setExecForPush); err != nil { 130 | err = fmt.Errorf("Error applying settlement exec for CreditUser: %s", err) 131 | server.dbLock.Unlock() 132 | return 133 | } 134 | } else { 135 | err = fmt.Errorf("Error, invalid settlement exec for CreditUser") 136 | server.dbLock.Unlock() 137 | return 138 | } 139 | settlementResults = append(settlementResults, setRes) 140 | 141 | if err = currSettleStore.UpdateBalances(settlementResults); err != nil { 142 | err = fmt.Errorf("Error updating balances for CreditUser: %s", err) 143 | server.dbLock.Unlock() 144 | return 145 | } 146 | 147 | server.dbLock.Unlock() 148 | return 149 | } 150 | -------------------------------------------------------------------------------- /cxserver/deposits.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/coinparam" 7 | "github.com/mit-dci/lit/crypto/koblitz" 8 | "github.com/mit-dci/opencx/cxdb" 9 | ) 10 | 11 | // RegisterUser gives the user a balance of 0, and gives them deposit addresses. This acquires locks 12 | // so do not try to do it on your own 13 | func (server *OpencxServer) RegisterUser(pubkey *koblitz.PublicKey) (err error) { 14 | 15 | // go through all params in settlement layer 16 | addrMap := make(map[*coinparam.Params]string) 17 | server.dbLock.Lock() 18 | for param, _ := range server.SettlementEngines { 19 | if addrMap[param], err = server.GetAddrForCoin(param, pubkey); err != nil { 20 | err = fmt.Errorf("Error getting address for pubkey and coin for RegisterUser: %s", err) 21 | server.dbLock.Unlock() 22 | return 23 | } 24 | } 25 | server.dbLock.Unlock() 26 | 27 | // TODO: might not need such a generous use of locks here 28 | // Should separate deposit/stores from the engines 29 | var currDepositStore cxdb.DepositStore 30 | var ok bool 31 | for param, addr := range addrMap { 32 | server.dbLock.Lock() 33 | if currDepositStore, ok = server.DepositStores[param]; !ok { 34 | err = fmt.Errorf("Could not find deposit store for %s coin", param.Name) 35 | server.dbLock.Unlock() 36 | return 37 | } 38 | server.dbLock.Unlock() 39 | 40 | if err = currDepositStore.RegisterUser(pubkey, addr); err != nil { 41 | err = fmt.Errorf("Error registering user for deposit address for ingestChannelFund: %s", err) 42 | return 43 | } 44 | 45 | if err = server.DebitUser(pubkey, 0, param); err != nil { 46 | err = fmt.Errorf("Error giving user a balance of zero for RegisterUser: %s", err) 47 | return 48 | } 49 | } 50 | 51 | return 52 | } 53 | 54 | // GetDepositAddress gets the deposit address for the pubkey and the param based on the storage 55 | // in the server 56 | func (server *OpencxServer) GetDepositAddress(pubkey *koblitz.PublicKey, coin *coinparam.Params) (address string, err error) { 57 | 58 | server.dbLock.Lock() 59 | // first get the deposit store for the boi 60 | var currDepositStore cxdb.DepositStore 61 | var ok bool 62 | if currDepositStore, ok = server.DepositStores[coin]; !ok { 63 | err = fmt.Errorf("Could not find DepositStore for %s for GetDepositAddress", coin.Name) 64 | server.dbLock.Unlock() 65 | return 66 | } 67 | 68 | if address, err = currDepositStore.GetDepositAddress(pubkey); err != nil { 69 | err = fmt.Errorf("Error getting deposit address from store for GetDepositAddress: %s", err) 70 | server.dbLock.Unlock() 71 | return 72 | } 73 | server.dbLock.Unlock() 74 | 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /cxserver/handlers.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "github.com/mit-dci/lit/coinparam" 5 | "github.com/mit-dci/lit/eventbus" 6 | "github.com/mit-dci/lit/lnutil" 7 | "github.com/mit-dci/lit/qln" 8 | "github.com/mit-dci/lit/wire" 9 | "github.com/mit-dci/opencx/logging" 10 | ) 11 | 12 | // GetPushHandler gets the handler func to add to the user's balance since they've pushed over a lightning channel 13 | func (server *OpencxServer) GetPushHandler() (hFunc func(event eventbus.Event) eventbus.EventHandleResult) { 14 | hFunc = func(event eventbus.Event) (res eventbus.EventHandleResult) { 15 | // We know this is a channel state update event 16 | ee, ok := event.(qln.ChannelStateUpdateEvent) 17 | if !ok { 18 | logging.Errorf("Wrong type of event, why are you making this the handler for that?") 19 | // Still don't know if this is the right thing to return when we have an error 20 | return eventbus.EHANDLE_CANCEL 21 | } 22 | 23 | logging.Infof("Their pubkey: %x", (&ee.TheirPub).SerializeCompressed()) 24 | 25 | var err error 26 | if !ee.State.Failed { 27 | if err = server.ingestChannelPush(uint64(ee.State.Delta), &ee.TheirPub, ee.CoinType); err != nil { 28 | logging.Errorf("ingesting channel push error: %s", err) 29 | return eventbus.EHANDLE_CANCEL 30 | } 31 | } 32 | 33 | return eventbus.EHANDLE_OK 34 | } 35 | 36 | return 37 | } 38 | 39 | // GetOPConfirmHandler gets the handler func to pass in an amount to the updatebalance function 40 | func (server *OpencxServer) GetOPConfirmHandler() (hFunc func(event eventbus.Event) eventbus.EventHandleResult) { 41 | hFunc = func(event eventbus.Event) (res eventbus.EventHandleResult) { 42 | // We know this is a channel state update event 43 | ee, ok := event.(qln.ChannelStateUpdateEvent) 44 | if !ok { 45 | logging.Errorf("Wrong type of event, why are you making this the handler for that?") 46 | // Still don't know if this is the right thing to return when we have an error 47 | return eventbus.EHANDLE_CANCEL 48 | } 49 | 50 | var err error 51 | if !ee.State.Failed { 52 | if err = server.ingestChannelConfirm(ee.State, &ee.TheirPub, ee.CoinType); err != nil { 53 | logging.Errorf("ingesting channel confirm error: %s", err) 54 | return eventbus.EHANDLE_CANCEL 55 | } 56 | } 57 | 58 | return eventbus.EHANDLE_OK 59 | } 60 | return 61 | } 62 | 63 | // GetSigProofHandler gets the handler func to pass in things to the register function. We get their pub key from the 64 | // QChan through the event bus and then use that to register a new user. 65 | func (server *OpencxServer) GetSigProofHandler() (hFunc func(event eventbus.Event) eventbus.EventHandleResult) { 66 | hFunc = func(event eventbus.Event) (res eventbus.EventHandleResult) { 67 | // We know this is a channel state update event 68 | ee, ok := event.(qln.ChannelStateUpdateEvent) 69 | if !ok { 70 | logging.Errorf("Wrong type of event, why are you making this the handler for that?") 71 | // Still don't know if this is the right thing to return 72 | return eventbus.EHANDLE_CANCEL 73 | } 74 | 75 | var err error 76 | if !ee.State.Failed { 77 | logging.Infof("Channel not failed, ingest") 78 | if err = server.ingestChannelFund(ee.State, &ee.TheirPub, ee.CoinType, ee.ChanIdx); err != nil { 79 | logging.Errorf("ingesting channel fund error: %s", err) 80 | return eventbus.EHANDLE_CANCEL 81 | } 82 | } 83 | 84 | return eventbus.EHANDLE_OK 85 | } 86 | return 87 | } 88 | 89 | // HeightHandler is a handler for when there is a height and block event for the wallet. We need both channels to work and be synchronized, which I'm assuming is the case in the lit repos. Will need to double check. 90 | func (server *OpencxServer) HeightHandler(incomingBlockHeight chan lnutil.HeightEvent, blockChan chan *wire.MsgBlock, coinType *coinparam.Params) { 91 | for { 92 | h := <-incomingBlockHeight 93 | block := <-blockChan 94 | server.CallIngest(h.Height, block, coinType) 95 | } 96 | } 97 | 98 | // ChainHookHeightHandler is a handler for when there is a height and block event. We need both channels to work and be synchronized, which I'm assuming is the case in the lit repos. Will need to double check. 99 | func (server *OpencxServer) ChainHookHeightHandler(incomingBlockHeight chan int32, blockChan chan *wire.MsgBlock, coinType *coinparam.Params) { 100 | for { 101 | 102 | // this used to be commented out. Since in lit the channels are buffered, we HAVE to make sure that this is cleared 103 | // otherwise lit will just completely block and wait for us to pull from the channel, and we will stop getting 104 | // headers and everything. IF it's needed, just always pulling from this would be fine if we don't care about it. 105 | h := <-incomingBlockHeight 106 | block := <-blockChan 107 | logging.Infof("Block %s from %s", block.Header.BlockHash(), coinType.Name) 108 | server.CallIngest(h, block, coinType) 109 | } 110 | } 111 | 112 | // CallIngest calls the ingest function. This is so we can make a bunch of different handlers that call this depending on which way they use channels. 113 | func (server *OpencxServer) CallIngest(blockHeight int32, block *wire.MsgBlock, coinType *coinparam.Params) { 114 | // logging.Debugf("Ingesting %d transactions at height %d\n", len(block.Transactions), blockHeight) 115 | if err := server.ingestTransactionListAndHeight(block.Transactions, uint64(blockHeight), coinType); err != nil { 116 | logging.Infof("something went horribly wrong with %s\n", coinType.Name) 117 | logging.Errorf("Here's what went horribly wrong: %s\n", err) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cxserver/keymgmt.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/btcutil/base58" 7 | "github.com/mit-dci/lit/coinparam" 8 | "github.com/mit-dci/lit/crypto/koblitz" 9 | ) 10 | 11 | // GetAddrForCoin gets an address based on a wallet and pubkey 12 | func (server *OpencxServer) GetAddrForCoin(coinType *coinparam.Params, pubkey *koblitz.PublicKey) (addr string, err error) { 13 | server.walletMtx.Lock() 14 | wallet, found := server.WalletMap[coinType] 15 | if !found { 16 | err = fmt.Errorf("Could not find wallet to create address for") 17 | server.walletMtx.Unlock() 18 | return 19 | } 20 | server.walletMtx.Unlock() 21 | 22 | pubKeyHashAddrID := wallet.Param.PubKeyHashAddrID 23 | 24 | // Create a new address 25 | var addrBytes [20]byte 26 | if addrBytes, err = wallet.NewAdr160(); err != nil { 27 | return 28 | } 29 | 30 | // encode it to store in own db 31 | addr = base58.CheckEncode(addrBytes[:], pubKeyHashAddrID) 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /cxserver/keys.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/btcutil/hdkeychain" 7 | "github.com/mit-dci/lit/coinparam" 8 | ) 9 | 10 | // SetupServerKeys just loads a private key from a file wallet 11 | func (server *OpencxServer) SetupServerKeys(privkey *[32]byte) (err error) { 12 | 13 | // for all settlement engines that we have, make keys 14 | for param, _ := range server.SettlementEngines { 15 | if err = server.SetupSingleKey(privkey, param); err != nil { 16 | return 17 | } 18 | } 19 | 20 | return 21 | } 22 | 23 | // SetupSingleKey sets up a single key based on a single param for the server. 24 | func (server *OpencxServer) SetupSingleKey(privkey *[32]byte, param *coinparam.Params) (err error) { 25 | var rootKey *hdkeychain.ExtendedKey 26 | if rootKey, err = hdkeychain.NewMaster(privkey[:], param); err != nil { 27 | err = fmt.Errorf("Error creating master %s key from private key: \n%s", param.Name, err) 28 | return 29 | } 30 | server.privKeyMtx.Lock() 31 | server.PrivKeyMap[param] = rootKey 32 | server.privKeyMtx.Unlock() 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /cxserver/price.go: -------------------------------------------------------------------------------- 1 | package cxserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/opencx/match" 7 | ) 8 | 9 | func (server *OpencxServer) GetPrice(pair *match.Pair) (price float64, err error) { 10 | server.dbLock.Lock() 11 | var currOrderbook match.LimitOrderbook 12 | var ok bool 13 | if currOrderbook, ok = server.Orderbooks[*pair]; !ok { 14 | err = fmt.Errorf("Could not find orderbooks for trading pair for GetPrice") 15 | server.dbLock.Unlock() 16 | return 17 | } 18 | 19 | if price, err = currOrderbook.CalculatePrice(); err != nil { 20 | err = fmt.Errorf("Error calculating price for server GetPrice: %s", err) 21 | server.dbLock.Unlock() 22 | return 23 | } 24 | server.dbLock.Unlock() 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mit-dci/opencx 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Rjected/gmp v1.0.4-0.20190521043342-9c9965578e96 7 | github.com/awalterschulze/gographviz v2.0.1+incompatible // indirect 8 | github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 9 | github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8 10 | github.com/dchest/siphash v1.2.1 11 | github.com/dgryski/go-rc5 v0.0.0-20181025211356-a14dd155920a 12 | github.com/dgryski/go-rc6 v0.0.0-20181026001059-5073bcd24073 13 | github.com/ethereum/go-ethereum v1.10.17 14 | github.com/fatih/color v1.9.0 15 | github.com/go-sql-driver/mysql v1.5.0 16 | github.com/jackpal/gateway v1.0.6 // indirect 17 | github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4 18 | github.com/minio/highwayhash v1.0.0 19 | github.com/mit-dci/lit v0.0.0-20200512190823-511d703a128d 20 | github.com/mit-dci/zksigma v0.0.0-20190313133734-a6a19e83b9cc 21 | github.com/olekukonko/tablewriter v0.0.5 22 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 23 | golang.org/x/text v0.3.7 24 | ) 25 | -------------------------------------------------------------------------------- /logging/logging.go: -------------------------------------------------------------------------------- 1 | // Package logging provides utilities to easily set a universal log level, as well as intuitively set a log file. 2 | package logging 3 | 4 | // Log Levels: 5 | // 3: DebugLevel prints Panics, Fatals, Errors, Warnings, Infos and Debugs 6 | // 2: InfoLevel prints Panics, Fatals, Errors, Warnings and Info 7 | // 1: WarnLevel prints Panics, Fatals, Errors and Warnings 8 | // 0: ErrorLevel prints Panics, Fatals and Errors 9 | // Default is level 0 10 | // Code for tagging logs: 11 | // Debug -> Useful debugging information 12 | // Info -> Something noteworthy happened 13 | // Warn -> You should probably take a look at this 14 | // Error -> Something failed but I'm not quitting 15 | // Fatal -> Bye 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "log" 21 | "os" 22 | ) 23 | 24 | // LogLevel indicates what to log based on the method called 25 | type LogLevel int 26 | 27 | const ( 28 | // LogLevelError is the log level that prints Panics, Fatals, and Errors. 29 | LogLevelError LogLevel = 0 30 | // LogLevelWarning is the log level that prints Panics, Fatals, Errors, and Warnings. 31 | LogLevelWarning LogLevel = 1 32 | // LogLevelInfo is the log level that prints Panics, Fatals, Errors, Warnings, and Infos. 33 | LogLevelInfo LogLevel = 2 34 | // LogLevelDebug is the log level that prints Panics, Fatals, Errors, Warnings, Infos, and Debugs. 35 | LogLevelDebug LogLevel = 3 36 | ) 37 | 38 | var logLevel = LogLevelError // the default 39 | 40 | // SetLogLevel sets the global log level 41 | func SetLogLevel(newLevel int) { 42 | logLevel = LogLevel(newLevel) 43 | } 44 | 45 | // SetLogFile sets a file to write to in addition to standard output. 46 | func SetLogFile(logFile io.Writer) { 47 | log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) 48 | logOutput := io.MultiWriter(os.Stdout, logFile) 49 | log.SetOutput(logOutput) 50 | } 51 | 52 | func getPrefix(level string) string { 53 | return fmt.Sprintf("[%s]", level) 54 | } 55 | 56 | // Fatalln prints a message and a new line, then calls os.Exit(1). 57 | func Fatalln(args ...interface{}) { 58 | log.Fatalln(args...) 59 | } 60 | 61 | // Fatalf prints a message with a formatting directive, then calls os.Exit(1). 62 | func Fatalf(format string, args ...interface{}) { 63 | log.Fatalf(format, args...) 64 | } 65 | 66 | // Fatal prints a message, then calls os.Exit(1). 67 | func Fatal(args ...interface{}) { 68 | log.Fatal(args...) 69 | } 70 | 71 | // Debugf prints debug logs with a formatting directive. 72 | func Debugf(format string, args ...interface{}) { 73 | if logLevel >= LogLevelDebug { 74 | log.Printf(fmt.Sprintf("%s %s", getPrefix("DEBUG"), format), args...) 75 | } 76 | } 77 | 78 | // Infof prints info logs with a formatting directive. 79 | func Infof(format string, args ...interface{}) { 80 | if logLevel >= LogLevelInfo { 81 | log.Printf(fmt.Sprintf("%s %s", getPrefix("INFO"), format), args...) 82 | } 83 | } 84 | 85 | // Warnf prints warning logs with a formatting directive. 86 | func Warnf(format string, args ...interface{}) { 87 | if logLevel >= LogLevelWarning { 88 | log.Printf(fmt.Sprintf("%s %s", getPrefix("WARN"), format), args...) 89 | } 90 | } 91 | 92 | // Errorf prints error logs with a formatting directive. 93 | func Errorf(format string, args ...interface{}) { 94 | if logLevel >= LogLevelError { 95 | log.Printf(fmt.Sprintf("%s %s", getPrefix("ERROR"), format), args...) 96 | } 97 | } 98 | 99 | // Debugln prints debug logs, followed by a new line. 100 | func Debugln(args ...interface{}) { 101 | if logLevel >= LogLevelDebug { 102 | args = append([]interface{}{getPrefix("DEBUG")}, args...) 103 | log.Println(args...) 104 | } 105 | } 106 | 107 | // Infoln prints info logs, followed by a new line. 108 | func Infoln(args ...interface{}) { 109 | if logLevel >= LogLevelInfo { 110 | args = append([]interface{}{getPrefix("INFO")}, args...) 111 | log.Println(args...) 112 | } 113 | } 114 | 115 | // Warnln prints warning logs, followed by a new line. 116 | func Warnln(args ...interface{}) { 117 | if logLevel >= LogLevelWarning { 118 | args = append([]interface{}{getPrefix("WARN")}, args...) 119 | log.Println(args...) 120 | } 121 | } 122 | 123 | // Errorln prints error logs, followed by a new line. 124 | func Errorln(args ...interface{}) { 125 | if logLevel >= LogLevelError { 126 | args = append([]interface{}{getPrefix("ERROR")}, args...) 127 | log.Println(args...) 128 | } 129 | } 130 | 131 | // Debug prints debug logs. 132 | func Debug(args ...interface{}) { 133 | if logLevel >= LogLevelDebug { 134 | args = append([]interface{}{getPrefix("DEBUG")}, args...) 135 | log.Print(args...) 136 | } 137 | } 138 | 139 | // Info prints info logs. 140 | func Info(args ...interface{}) { 141 | if logLevel >= LogLevelInfo { 142 | args = append([]interface{}{getPrefix("INFO")}, args...) 143 | log.Print(args...) 144 | } 145 | } 146 | 147 | // Warn prints warning logs. 148 | func Warn(args ...interface{}) { 149 | if logLevel >= LogLevelWarning { 150 | args = append([]interface{}{getPrefix("WARN")}, args...) 151 | log.Print(args...) 152 | } 153 | } 154 | 155 | // Error prints error logs. 156 | func Error(args ...interface{}) { 157 | if logLevel >= LogLevelError { 158 | args = append([]interface{}{getPrefix("ERROR")}, args...) 159 | log.Print(args...) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /match/algorithms.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | // AuctionOrderIDPair is a pair of order ID and auction order, used for generating executions in the auction matching algorithm 9 | type AuctionOrderIDPair struct { 10 | OrderID OrderID 11 | Price float64 12 | Order *AuctionOrder 13 | } 14 | 15 | // CalculateClearingPrice calculates the clearing price for orders based on their intersections. 16 | // In the future the error will be used potentially since the divide operation at the end might be real bad. 17 | func CalculateClearingPrice(book map[float64][]*AuctionOrderIDPair) (clearingPrice float64, err error) { 18 | 19 | // price that is the lowest sell price so far 20 | var lowestIntersectingPrice float64 21 | lowestIntersectingPrice = math.MaxFloat64 22 | 23 | // price that is the highest buy price so far 24 | var highestIntersectingPrice float64 25 | highestIntersectingPrice = 0 26 | 27 | // Now go through every price in the orderbook, finding the lowest sell order and highest buy order 28 | for pr, orderPairList := range book { 29 | for _, orderPair := range orderPairList { 30 | // make sure that we keep track of the highest buy order price 31 | if orderPair.Order.IsBuySide() { 32 | if pr < lowestIntersectingPrice { 33 | lowestIntersectingPrice = pr 34 | } 35 | // make sure we keep track of the lowest sell order price 36 | } else if orderPair.Order.IsSellSide() { 37 | if pr > highestIntersectingPrice { 38 | highestIntersectingPrice = pr 39 | } 40 | } 41 | } 42 | } 43 | 44 | // sellClearAmount and buyClearAmount are uint64's, and technically should be amounts of tokens (Issue #22). 45 | var sellClearAmount uint64 46 | var buyClearAmount uint64 47 | 48 | // same with totalBuyWant 49 | var totalBuyWant uint64 50 | var totalBuyHave uint64 51 | 52 | // same with totalBuyHave 53 | var totalSellWant uint64 54 | var totalSellHave uint64 55 | // now that we have the prices, we go through the book again to calculate the clearing price 56 | for pr, orderPairList := range book { 57 | // if there is an intersecting price, calculate clearing amounts for the price. 58 | for _, orderPair := range orderPairList { 59 | if pr <= highestIntersectingPrice && pr >= lowestIntersectingPrice { 60 | // for all intersecting prices in the orderbook, we add the amounts 61 | if orderPair.Order.IsBuySide() { 62 | buyClearAmount += orderPair.Order.AmountHave 63 | totalBuyHave += orderPair.Order.AmountHave 64 | totalBuyWant += orderPair.Order.AmountWant 65 | } else if orderPair.Order.IsSellSide() { 66 | sellClearAmount += orderPair.Order.AmountHave 67 | totalSellHave += orderPair.Order.AmountHave 68 | totalSellWant += orderPair.Order.AmountWant 69 | } 70 | } 71 | } 72 | } 73 | 74 | // Uncomment if looking for a slightly fancier but probably incorrect matching algorithm 75 | // clearingPrice = (float64(totalBuyWant)/float64(totalBuyHave) + float64(totalSellWant)/float64(totalSellHave)) / 2 76 | 77 | // TODO: this should be changed, I really don't like this floating point math (See Issue #6 and TODOs in match about price.) 78 | clearingPrice = float64(totalBuyWant+totalSellWant) / float64(totalBuyHave+totalSellHave) 79 | 80 | return 81 | } 82 | 83 | // GenerateClearingExecs goes through an orderbook with a clearing price, and generates executions 84 | // based on the clearing matching algorithm 85 | func GenerateClearingExecs(book map[float64][]*AuctionOrderIDPair, clearingPrice float64) (orderExecs []*OrderExecution, settlementExecs []*SettlementExecution, err error) { 86 | 87 | var resOrderExec *OrderExecution 88 | var resSetExec []*SettlementExecution 89 | // go through all orders and figure out which ones to match 90 | for price, orderPairList := range book { 91 | for _, orderPair := range orderPairList { 92 | if (orderPair.Order.IsBuySide() && price <= clearingPrice) || (orderPair.Order.IsSellSide() && price >= clearingPrice) { 93 | // Um so this is needed because of some weird memory issue TODO: remove this fix 94 | // and put in another fix if you understand pointer black magic 95 | resOrderExec = new(OrderExecution) 96 | resSetExec = []*SettlementExecution{} 97 | if *resOrderExec, resSetExec, err = orderPair.Order.GenerateOrderFill(&orderPair.OrderID, clearingPrice); err != nil { 98 | err = fmt.Errorf("Error generating execution from clearing price for buy: %s", err) 99 | return 100 | } 101 | orderExecs = append(orderExecs, resOrderExec) 102 | settlementExecs = append(settlementExecs, resSetExec...) 103 | } 104 | } 105 | } 106 | 107 | return 108 | } 109 | 110 | // MatchClearingAlgorithm runs the matching algorithm based on a uniform clearing price, first calculating the 111 | // clearing price and then generating executions based on it. 112 | func MatchClearingAlgorithm(book map[float64][]*AuctionOrderIDPair) (orderExecs []*OrderExecution, settlementExecs []*SettlementExecution, err error) { 113 | 114 | var clearingPrice float64 115 | if clearingPrice, err = CalculateClearingPrice(book); err != nil { 116 | err = fmt.Errorf("Error calculating clearing price while running clearing matching algorithm: %s", err) 117 | return 118 | } 119 | 120 | if orderExecs, settlementExecs, err = GenerateClearingExecs(book, clearingPrice); err != nil { 121 | err = fmt.Errorf("Error generating clearing execs while running match clearing algorithm: %s", err) 122 | return 123 | } 124 | 125 | return 126 | } 127 | 128 | // NumberOfOrders computes the number of order pairs in a map representation of an orderbook 129 | func NumberOfOrders(book map[float64][]*AuctionOrderIDPair) (numberOfOrders uint64) { 130 | for _, orderPairList := range book { 131 | numberOfOrders += uint64(len(orderPairList)) 132 | } 133 | return 134 | } 135 | -------------------------------------------------------------------------------- /match/auctionbatch.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "time" 4 | 5 | // AuctionBatch is a struct that represents a batch of auction order results 6 | type AuctionBatch struct { 7 | Batch []*OrderPuzzleResult 8 | AuctionID [32]byte 9 | } 10 | 11 | // AuctionBatcher is an interface for a service that collects orders and handles batching per auction. 12 | // This is abstracted because solving puzzles is a task that should be easily outsourceable, and should 13 | // not be integrated into the core logic. One could easily see a server that performs puzzle solving 14 | // that is separate from the actual exchange. The exchange doesn't need to schedule puzzle solving, 15 | // or worry about scaling it, but the auction batcher does. The auction batcher needs to involve solving 16 | // many puzzles at once. 17 | type AuctionBatcher interface { 18 | // RegisterAuction registers a new auction with a specified Auction ID, which will be an array of 19 | // 32 bytes. 20 | RegisterAuction(auctionID [32]byte) (err error) 21 | 22 | // AddEncrypted adds an encrypted order to an auction. This should error if either the auction doesn't 23 | // exist, or the auction is ended. 24 | AddEncrypted(order *EncryptedAuctionOrder) (err error) 25 | 26 | // EndAuction ends the auction with the specified auction ID, and returns the channel which will 27 | // receive a batch of orders puzzle results. This is like a promise. This channel should be of size 1. 28 | EndAuction(auctionID [32]byte) (batchChan chan *AuctionBatch, err error) 29 | 30 | // ActiveAuctions returns a map of auction id to time TODO: figure out if this is really necessary 31 | ActiveAuctions() (activeBatches map[[32]byte]time.Time) 32 | } 33 | 34 | // BatchResult is a struct that represents the result of a batch auction. 35 | type BatchResult struct { 36 | OriginalBatch *AuctionBatch 37 | // RejectedResults and AcceptedResults should be disjoint sets 38 | RejectedResults []*OrderPuzzleResult 39 | AcceptedResults []*OrderPuzzleResult 40 | } 41 | -------------------------------------------------------------------------------- /match/auctionid.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | // AuctionID represents an auction's unique ID. 9 | // This is a byte array alias because sometimes the ID will be 10 | // represented as text, and sometimes it will be represented as bytes. 11 | // We conform it to the BinaryMarshaler interface and TextMarshaler 12 | // interface. 13 | type AuctionID [32]byte 14 | 15 | // MarshalBinary encodes the receiver into a binary form and returns 16 | // the result. This conforms to the BinaryMarshaler interface 17 | func (a *AuctionID) MarshalBinary() (data []byte, err error) { 18 | data = make([]byte, 32) 19 | copy(data[:], a[:]) 20 | return 21 | } 22 | 23 | // UnmarshalBinary decodes the form generated by MarshalBinary. 24 | // This conforms to the BinaryMarshaler interface 25 | func (a *AuctionID) UnmarshalBinary(data []byte) (err error) { 26 | if len(data) != 32 { 27 | err = fmt.Errorf("Error unmarshalling AuctionID from binary, cannot unmarshal a slice that isn't 32 bytes long") 28 | return 29 | } 30 | copy(a[:], data) 31 | return 32 | } 33 | 34 | // MarshalText encodes the receiver into UTF-8-encoded text and 35 | // returns the result. 36 | // This conforms to the TextMarshaler interface 37 | func (a *AuctionID) MarshalText() (text []byte, err error) { 38 | text = []byte(hex.EncodeToString(a[:])) 39 | return 40 | } 41 | 42 | // UnmarshalText deocdes the form generated by MarshalText. 43 | // This conforms to the TextMarshaler interface 44 | func (a *AuctionID) UnmarshalText(text []byte) (err error) { 45 | if _, err = hex.Decode(a[:], text); err != nil { 46 | err = fmt.Errorf("Error unmarshalling text AuctionID: %s", err) 47 | return 48 | } 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /match/book.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "github.com/mit-dci/lit/crypto/koblitz" 5 | ) 6 | 7 | // LimitOrderbook is the interface for a limit order book. 8 | // The difference between the order book and the matching engine is that the matching engine is what processes orders and generates executions, whereas the limit orderbook does not. 9 | // The order book takes in executions, and allows read access to the state of the orderbook. 10 | // This can only be updated using the outputs of the matching engine. 11 | type LimitOrderbook interface { 12 | // UpdateBookExec takes in an order execution and updates the orderbook. 13 | UpdateBookExec(orderExec *OrderExecution) (err error) 14 | // UpdateBookCancel takes in an order cancellation and updates the orderbook. 15 | UpdateBookCancel(cancel *CancelledOrder) (err error) 16 | // UpdateBookPlace takes in an order, ID, timestamp, and adds the order to the orderbook. 17 | UpdateBookPlace(limitIDPair *LimitOrderIDPair) (err error) 18 | // GetOrder gets an order from an OrderID 19 | GetOrder(orderID *OrderID) (limOrder *LimitOrderIDPair, err error) 20 | // CalculatePrice takes in a pair and returns the calculated price based on the orderbook. 21 | CalculatePrice() (price float64, err error) 22 | // GetOrdersForPubkey gets orders for a specific pubkey. 23 | GetOrdersForPubkey(pubkey *koblitz.PublicKey) (orders map[float64][]*LimitOrderIDPair, err error) 24 | // ViewLimitOrderbook takes in a trading pair and returns the orderbook as a map 25 | ViewLimitOrderBook() (book map[float64][]*LimitOrderIDPair, err error) 26 | } 27 | 28 | // AuctionOrderbook is the interface for an auction order book. 29 | // The difference between the order book and the matching engine is that the matching engine is what processes orders and generates executions, whereas the limit orderbook does not. 30 | // The order book takes in executions, and allows read access to the state of the orderbook. 31 | type AuctionOrderbook interface { 32 | // UpdateBookExec takes in an order execution and updates the orderbook. 33 | UpdateBookExec(orderExec *OrderExecution) (err error) 34 | // UpdateBookCancel takes in an order cancellation and updates the orderbook. 35 | UpdateBookCancel(cancel *CancelledOrder) (err error) 36 | // UpdateBookPlace takes in an order, ID, auction ID and adds the order to the orderbook. 37 | UpdateBookPlace(auctionIDPair *AuctionOrderIDPair) (err error) 38 | // GetOrder gets an order from an OrderID 39 | GetOrder(orderID *OrderID) (limOrder *AuctionOrderIDPair, err error) 40 | // CalculatePrice takes in a pair and returns the calculated price based on the orderbook. 41 | // This only works for a specific auction 42 | CalculatePrice(auctionID *AuctionID) (price float64, err error) 43 | // GetOrdersForPubkey gets orders for a specific pubkey. 44 | GetOrdersForPubkey(pubkey *koblitz.PublicKey) (orders map[float64][]*AuctionOrderIDPair, err error) 45 | // ViewAuctionOrderBook takes in a trading pair and returns the orderbook as a map 46 | ViewAuctionOrderBook() (book map[float64][]*AuctionOrderIDPair, err error) 47 | } 48 | -------------------------------------------------------------------------------- /match/cancel.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | // CancelledOrder is broadcasted from the matching engine, marking an order as cancelled. 4 | type CancelledOrder struct { 5 | OrderID *OrderID 6 | } 7 | -------------------------------------------------------------------------------- /match/commitresponse.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | ) 8 | 9 | // CommitResponse is the commitment response. The sig is the 10 | // puzzleanswerreveal + the commitment + the commitsig 11 | type CommitResponse struct { 12 | CommResponseSig [65]byte `json:"commresponse"` 13 | PuzzleAnswerReveal SolutionOrder `json:"puzzleanswer"` 14 | } 15 | 16 | // Serialize uses gob encoding to turn the commit response into bytes. 17 | func (cr *CommitResponse) Serialize() (raw []byte, err error) { 18 | var b bytes.Buffer 19 | 20 | // register commit response interface 21 | gob.Register(CommitResponse{}) 22 | 23 | // create a new encoder writing to the buffer 24 | enc := gob.NewEncoder(&b) 25 | 26 | // encode the commit response in the buffer 27 | if err = enc.Encode(cr); err != nil { 28 | err = fmt.Errorf("Error encoding commit response: %s", err) 29 | return 30 | } 31 | 32 | // Get the bytes from the buffer 33 | raw = b.Bytes() 34 | return 35 | } 36 | 37 | // Deserialize turns the commit response from bytes into a usable 38 | // struct. 39 | func (cr *CommitResponse) Deserialize(raw []byte) (err error) { 40 | var b *bytes.Buffer 41 | b = bytes.NewBuffer(raw) 42 | 43 | // register CommitResponse 44 | gob.Register(CommitResponse{}) 45 | 46 | // create a new decoder writing to the buffer 47 | dec := gob.NewDecoder(b) 48 | 49 | // decode the commit response in the buffer 50 | if err = dec.Decode(cr); err != nil { 51 | err = fmt.Errorf("Error decoding commitresponse: %s", err) 52 | return 53 | } 54 | 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /match/consts.go: -------------------------------------------------------------------------------- 1 | // Package match provides utilities and useful structures for building exchange systems. 2 | package match 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/mit-dci/lit/coinparam" 8 | "github.com/mit-dci/opencx/chainutils" 9 | ) 10 | 11 | const ( 12 | // BTC is a constant used to represent a BTC token 13 | BTC Asset = 0x00 14 | // VTC is a constant used to represent a VTC token 15 | VTC Asset = 0x01 16 | 17 | // Wait until you have a LTC coinparam 18 | // LTC is a constant used to represent a LTC token 19 | // LTC Asset = 0x02 20 | 21 | // BTCTest is a constant used to represent a BTC Test net token 22 | BTCTest Asset = 0x03 23 | // VTCTest is a constant used to represent a VTC Test net token 24 | VTCTest Asset = 0x04 25 | // LTCTest is a constant used to represent a LTC Test net token 26 | LTCTest Asset = 0x05 27 | // BTCReg is a constant used to represent a BTC Reg net token 28 | BTCReg Asset = 0x06 29 | // VTCReg is a constant used to represent a VTC Reg net token 30 | VTCReg Asset = 0x07 31 | // LTCReg is a constant used to represent a LTC Reg net token 32 | LTCReg Asset = 0x08 33 | ) 34 | 35 | // largeAssetList is something used for testing the generateassetpairs function, this should be put into a unit test once tests are written 36 | func largeAssetList() []Asset { 37 | return []Asset{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a} 38 | } 39 | 40 | // AssetFromCoinParam gets a byte representation of an asset from a coinparam 41 | func AssetFromCoinParam(cpm *coinparam.Params) (a Asset, err error) { 42 | 43 | // create map for that O(1) access 44 | assetMap := map[*coinparam.Params]Asset{ 45 | &coinparam.BitcoinParams: BTC, 46 | &coinparam.VertcoinParams: VTC, 47 | // &coinparam.LitecoinParams: LTC, 48 | &coinparam.TestNet3Params: BTCTest, 49 | &coinparam.VertcoinTestNetParams: VTCTest, 50 | &coinparam.LiteCoinTestNet4Params: LTCTest, 51 | &coinparam.RegressionNetParams: BTCReg, 52 | &coinparam.VertcoinRegTestParams: VTCReg, 53 | &coinparam.LiteRegNetParams: LTCReg, 54 | } 55 | 56 | // grab from map 57 | var found bool 58 | if a, found = assetMap[cpm]; !found { 59 | err = fmt.Errorf("Could not get an asset for that coin param") 60 | return 61 | } 62 | 63 | return 64 | } 65 | 66 | // CoinParamFromAsset is the reverse of AssetFromCoinParam. TODO: change the coinparam so it has an inherent ID anyways? 67 | func (a Asset) CoinParamFromAsset() (coinType *coinparam.Params, err error) { 68 | // create map for that O(1) access 69 | assetMap := map[Asset]*coinparam.Params{ 70 | BTC: &coinparam.BitcoinParams, 71 | VTC: &coinparam.VertcoinParams, 72 | // LTC: &coinparam.LitecoinParams, 73 | BTCTest: &coinparam.TestNet3Params, 74 | VTCTest: &coinparam.VertcoinTestNetParams, 75 | LTCTest: &coinparam.LiteCoinTestNet4Params, 76 | BTCReg: &coinparam.RegressionNetParams, 77 | LTCReg: &coinparam.LiteRegNetParams, 78 | VTCReg: &coinparam.VertcoinRegTestParams, 79 | } 80 | 81 | // grab from map 82 | var found bool 83 | if coinType, found = assetMap[a]; !found { 84 | err = fmt.Errorf("Could not get a coin param for that asset") 85 | return 86 | } 87 | 88 | return 89 | 90 | } 91 | 92 | // AssetFromString returns an asset from a string 93 | func AssetFromString(name string) (a Asset, err error) { 94 | // we do this to enforce consistency sorta. I don't want this asset thing and the coin params that we support to be separated. They are right now, though. 95 | // the main reason they're separated is that I want the coin params to have their own unique byte so the client only sends a byte to indicate which asset 96 | // they want. Like an asset ID. TODO: Change this to HDCoinType, or magic bytes in the future. We need an asset id. 97 | var cpm *coinparam.Params 98 | if cpm, err = chainutils.GetParamFromName(name); err != nil { 99 | return 100 | } 101 | 102 | if a, err = AssetFromCoinParam(cpm); err != nil { 103 | return 104 | } 105 | 106 | return 107 | } 108 | 109 | func (a Asset) String() string { 110 | var err error 111 | var coinType *coinparam.Params 112 | if coinType, err = a.CoinParamFromAsset(); err != nil { 113 | return "UnknownAsset" 114 | } 115 | return coinType.Name 116 | } 117 | -------------------------------------------------------------------------------- /match/deposit.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mit-dci/lit/crypto/koblitz" 7 | 8 | "github.com/mit-dci/lit/coinparam" 9 | ) 10 | 11 | // Deposit is a struct that represents a deposit on chain 12 | type Deposit struct { 13 | Pubkey *koblitz.PublicKey 14 | Address string 15 | Amount uint64 16 | Txid string 17 | CoinType *coinparam.Params 18 | BlockHeightReceived uint64 19 | Confirmations uint64 20 | } 21 | 22 | func (d *Deposit) String() string { 23 | return fmt.Sprintf("Deposit: {\n\tPubkey: %x\n\tAddress: %s\n\tAmount: %d\n\tTxid: %s\n\tCoinType: %s\n\tBlockHeightReceived: %d\n\tConfirmations: %d\n}", 24 | d.Pubkey.SerializeCompressed(), d.Address, d.Amount, d.Txid, d.CoinType.Name, d.BlockHeightReceived, d.Confirmations) 25 | } 26 | 27 | // LightningDeposit is a struct that represents a deposit made with lightning 28 | type LightningDeposit struct { 29 | Pubkey *koblitz.PublicKey // maybe switch to real pubkey later 30 | Amount uint64 31 | CoinType *coinparam.Params 32 | ChanIdx uint32 33 | } 34 | 35 | func (ld *LightningDeposit) String() string { 36 | return fmt.Sprintf("Deposit: {\n\tPubkey: %x\n\tAmount: %d\n\tCoinType: %s\n\tChannelIdx: %d\n\t\n}", ld.Pubkey.SerializeCompressed(), ld.Amount, ld.CoinType.Name, ld.ChanIdx) 37 | } 38 | -------------------------------------------------------------------------------- /match/encryptedauctionorder.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/mit-dci/opencx/crypto" 9 | "github.com/mit-dci/opencx/crypto/hashtimelock" 10 | "github.com/mit-dci/opencx/crypto/rsw" 11 | "github.com/mit-dci/opencx/crypto/timelockencoders" 12 | ) 13 | 14 | // EncryptedAuctionOrder represents an encrypted Auction Order, so a ciphertext and a puzzle whos solution is a key, and an intended auction. 15 | type EncryptedAuctionOrder struct { 16 | OrderCiphertext []byte 17 | OrderPuzzle crypto.Puzzle 18 | IntendedAuction AuctionID 19 | IntendedPair Pair 20 | } 21 | 22 | // SolveRC5AuctionOrderAsync solves order puzzles and creates auction orders from them. This should be run in a goroutine. 23 | func SolveRC5AuctionOrderAsync(e *EncryptedAuctionOrder, puzzleResChan chan *OrderPuzzleResult) { 24 | var err error 25 | result := new(OrderPuzzleResult) 26 | result.Encrypted = e 27 | 28 | var orderBytes []byte 29 | if orderBytes, err = timelockencoders.SolvePuzzleRC5(e.OrderCiphertext, e.OrderPuzzle); err != nil { 30 | result.Err = fmt.Errorf("Error solving RC5 puzzle for auction order: %s", err) 31 | puzzleResChan <- result 32 | return 33 | } 34 | 35 | result.Auction = new(AuctionOrder) 36 | if err = result.Auction.Deserialize(orderBytes); err != nil { 37 | result.Err = fmt.Errorf("Error deserializing order gotten from puzzle: %s", err) 38 | puzzleResChan <- result 39 | return 40 | } 41 | 42 | puzzleResChan <- result 43 | 44 | return 45 | } 46 | 47 | // Serialize serializes the encrypted order using gob 48 | func (e *EncryptedAuctionOrder) Serialize() (raw []byte, err error) { 49 | var b bytes.Buffer 50 | 51 | // register the rsw puzzle and hashtimelock puzzle 52 | gob.Register(new(rsw.PuzzleRSW)) 53 | 54 | // register the hashtimelock (puzzle and timelock are same thing) 55 | gob.Register(new(hashtimelock.HashTimelock)) 56 | 57 | // register the pair 58 | gob.Register(new(Pair)) 59 | 60 | // register the puzzle interface 61 | gob.RegisterName("puzzle", new(crypto.Puzzle)) 62 | 63 | // register the encrypted auction order interface with gob 64 | gob.RegisterName("order", new(EncryptedAuctionOrder)) 65 | 66 | // create a new encoder writing to our buffer 67 | enc := gob.NewEncoder(&b) 68 | 69 | // encode the encrypted auction order in the buffer 70 | if err = enc.Encode(e); err != nil { 71 | err = fmt.Errorf("Error encoding encrypted auction order :%s", err) 72 | return 73 | } 74 | 75 | // Get the bytes finally 76 | raw = b.Bytes() 77 | 78 | return 79 | } 80 | 81 | // Deserialize deserializes the raw bytes into the encrypted auction order receiver 82 | func (e *EncryptedAuctionOrder) Deserialize(raw []byte) (err error) { 83 | var b *bytes.Buffer 84 | b = bytes.NewBuffer(raw) 85 | 86 | // register the rsw puzzle and hashtimelock puzzle 87 | gob.Register(new(rsw.PuzzleRSW)) 88 | 89 | // register the hashtimelock (puzzle and timelock are same thing) 90 | gob.Register(new(hashtimelock.HashTimelock)) 91 | 92 | // register the pair 93 | gob.Register(new(Pair)) 94 | 95 | // register the puzzle interface 96 | gob.RegisterName("puzzle", new(crypto.Puzzle)) 97 | 98 | // register the encrypted auction order interface with gob 99 | gob.RegisterName("order", new(EncryptedAuctionOrder)) 100 | 101 | // create a new decoder writing to the buffer 102 | dec := gob.NewDecoder(b) 103 | 104 | // decode the encrypted auction order in the buffer 105 | if err = dec.Decode(e); err != nil { 106 | err = fmt.Errorf("Error decoding encrypted auction order: %s", err) 107 | return 108 | } 109 | 110 | return 111 | } 112 | -------------------------------------------------------------------------------- /match/encryptedauctionorder_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "testing" 4 | 5 | func solveVariableRC5AuctionOrder(howMany uint64, timeToSolve uint64, t *testing.T) { 6 | 7 | var encOrder *EncryptedAuctionOrder 8 | var err error 9 | if encOrder, err = origOrder.TurnIntoEncryptedOrder(timeToSolve); err != nil { 10 | t.Errorf("Error turning original test order into encrypted order: %s", err) 11 | return 12 | } 13 | 14 | puzzleResChan := make(chan *OrderPuzzleResult, howMany) 15 | for i := uint64(0); i < howMany; i++ { 16 | go SolveRC5AuctionOrderAsync(encOrder, puzzleResChan) 17 | } 18 | for i := uint64(0); i < howMany; i++ { 19 | var res *OrderPuzzleResult 20 | res = <-puzzleResChan 21 | if res.Err != nil { 22 | t.Errorf("Solving order puzzle returned an error: %s", res.Err) 23 | return 24 | } 25 | } 26 | 27 | return 28 | } 29 | 30 | // This should be super quick. Takes 0.1 seconds on an i7 8700k, most of the time is probably 31 | // spent creating the test to solve. 32 | func TestConcurrentSolvesN10_T10000(t *testing.T) { 33 | solveVariableRC5AuctionOrder(uint64(10), uint64(10000), t) 34 | return 35 | } 36 | 37 | // This should be less quick but still quick. Takes about 0.7 seconds on an i7 8700k 38 | func TestConcurrentSolvesN10_T100000(t *testing.T) { 39 | solveVariableRC5AuctionOrder(uint64(10), uint64(100000), t) 40 | return 41 | } 42 | 43 | // TestConcurrentSolvesN10_T1000000 takes about 7.2 seconds on an i7 8700k 44 | func TestConcurrentSolvesN10_T1000000(t *testing.T) { 45 | solveVariableRC5AuctionOrder(uint64(10), uint64(1000000), t) 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /match/encsolorder.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/mit-dci/opencx/crypto/rsw" 9 | ) 10 | 11 | // EncryptedSolutionOrder represents an encrypted Solution Order, so a 12 | // ciphertext and a puzzle solution that is a key, and an intended auction. 13 | type EncryptedSolutionOrder struct { 14 | OrderCiphertext []byte `json:"orderciphertext"` 15 | OrderPuzzle rsw.PuzzleRSW `json:"orderpuzzle"` 16 | IntendedAuction AuctionID `json:"intendedauction"` 17 | IntendedPair Pair `json:"intendedpair"` 18 | } 19 | 20 | // SignedEncSolOrder is a signed EncryptedSolutionOrder 21 | type SignedEncSolOrder struct { 22 | EncSolOrder EncryptedSolutionOrder `json:"encsolorder"` 23 | Signature []byte `json:"signature"` 24 | } 25 | 26 | // Serialize uses gob encoding to turn the encrypted solution order 27 | // into bytes. 28 | func (es *EncryptedSolutionOrder) Serialize() (raw []byte, err error) { 29 | var b bytes.Buffer 30 | 31 | // register SolutionOrder interface 32 | gob.Register(EncryptedSolutionOrder{}) 33 | 34 | // create a new encoder writing to the buffer 35 | enc := gob.NewEncoder(&b) 36 | 37 | // encode the puzzle in the buffer 38 | if err = enc.Encode(es); err != nil { 39 | err = fmt.Errorf("Error encoding encryptedsolutionorder: %s", err) 40 | return 41 | } 42 | 43 | // Get the bytes from the buffer 44 | raw = b.Bytes() 45 | return 46 | } 47 | 48 | // Deserialize turns the encrypted solution order from bytes into a 49 | // usable struct. 50 | func (es *EncryptedSolutionOrder) Deserialize(raw []byte) (err error) { 51 | var b *bytes.Buffer 52 | b = bytes.NewBuffer(raw) 53 | 54 | // register SolutionOrder 55 | gob.Register(EncryptedSolutionOrder{}) 56 | 57 | // create a new decoder writing to the buffer 58 | dec := gob.NewDecoder(b) 59 | 60 | // decode the solutionorder in the buffer 61 | if err = dec.Decode(es); err != nil { 62 | err = fmt.Errorf("Error decoding encryptedsolutionorder: %s", err) 63 | return 64 | } 65 | 66 | return 67 | } 68 | 69 | // Serialize uses gob encoding to turn the encrypted solution order 70 | // into bytes. 71 | func (se *SignedEncSolOrder) Serialize() (raw []byte, err error) { 72 | var b bytes.Buffer 73 | 74 | // register SolutionOrder interface 75 | gob.Register(SignedEncSolOrder{}) 76 | 77 | // create a new encoder writing to the buffer 78 | enc := gob.NewEncoder(&b) 79 | 80 | // encode the puzzle in the buffer 81 | if err = enc.Encode(se); err != nil { 82 | err = fmt.Errorf("Error encoding encsolorder: %s", err) 83 | return 84 | } 85 | 86 | // Get the bytes from the buffer 87 | raw = b.Bytes() 88 | return 89 | } 90 | 91 | // Deserialize turns the signed encrypted solution order from bytes 92 | // into a usable struct. 93 | func (se *SignedEncSolOrder) Deserialize(raw []byte) (err error) { 94 | var b *bytes.Buffer 95 | b = bytes.NewBuffer(raw) 96 | 97 | // register SolutionOrder 98 | gob.Register(SignedEncSolOrder{}) 99 | 100 | // create a new decoder writing to the buffer 101 | dec := gob.NewDecoder(b) 102 | 103 | // decode the solutionorder in the buffer 104 | if err = dec.Decode(se); err != nil { 105 | err = fmt.Errorf("Error decoding encsolorder: %s", err) 106 | return 107 | } 108 | 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /match/engine.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | // The LimitEngine is the interface for the internal matching engine. This should be the lowest level 4 | // interface for the representation of a matching engine. 5 | // One of these should be made for every pair. 6 | type LimitEngine interface { 7 | PlaceLimitOrder(order *LimitOrder) (idRes *LimitOrderIDPair, err error) 8 | CancelLimitOrder(id *OrderID) (cancelled *CancelledOrder, cancelSettlement *SettlementExecution, err error) 9 | MatchLimitOrders() (orderExecs []*OrderExecution, settlementExecs []*SettlementExecution, err error) 10 | } 11 | 12 | // The AuctionEngine is the interface for the internal matching engine. This should be the lowest level 13 | // interface for the representation of a matching engine. 14 | // One of these should be made for every pair. 15 | type AuctionEngine interface { 16 | PlaceAuctionOrder(order *AuctionOrder, auctionID *AuctionID) (idRes *AuctionOrderIDPair, err error) 17 | CancelAuctionOrder(id *OrderID) (cancelled *CancelledOrder, cancelSettlement *SettlementExecution, err error) 18 | MatchAuctionOrders(auctionID *AuctionID) (orderExecs []*OrderExecution, settlementExecs []*SettlementExecution, err error) 19 | } 20 | 21 | // SettlementEngine is an interface for something that keeps track of balances for users for a 22 | // certain asset. 23 | // One of these should be made for every asset. 24 | type SettlementEngine interface { 25 | // ApplySettlementExecution is a method that applies a settlement execution. 26 | // TODO: switch this over to have setExec []*SettlementExecution as the param 27 | // and setRes []*SettlementResult as the return value 28 | ApplySettlementExecution(setExec *SettlementExecution) (setRes *SettlementResult, err error) 29 | // CheckValid is a method that returns true if the settlement execution would be valid. 30 | CheckValid(setExec *SettlementExecution) (valid bool, err error) 31 | } 32 | -------------------------------------------------------------------------------- /match/entry.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "encoding/json" 4 | 5 | // Entry represents either a credit or debit of some asset for some amount 6 | type Entry struct { 7 | Amount uint64 `json:"amount"` 8 | Asset Asset `json:"asset"` 9 | } 10 | 11 | // String returns a json representation of the Entry 12 | func (e *Entry) String() string { 13 | // we are ignoring this error because we know that the struct is marshallable 14 | jsonRepresentation, _ := json.Marshal(e) 15 | return string(jsonRepresentation) 16 | } 17 | -------------------------------------------------------------------------------- /match/execution.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | /* 8 | OrderExecution contains a simple order execution struct. This is what is being used in the clearing 9 | matching algorithm. We generate order executions so it stays independent of the settlement, and then 10 | pass those executions upwards. An execution is the output of a matching algorithm, so it's essentially 11 | the change in state from one orderbook matching step to the next. 12 | 13 | What won't change when an order is executed: 14 | - Pubkey 15 | - AuctionID 16 | - Nonce 17 | - Signature 18 | - TradingPair 19 | - Side 20 | 21 | What definitely will change: 22 | - AmountWant 23 | - AmountHave 24 | 25 | What can happen? 26 | - The order is completely filled and should be deleted from the orderbook 27 | In this case the amount debited or credited can be different than the AmountWant and AmountHave specified 28 | due to slippage. 29 | If the order is not deleted then it is partially filled. 30 | In the case that the order is not deleted and it is instead partially filled, the order will have an updated 31 | AmountWant and AmountHave, as well as assets credited and debited. 32 | 33 | So what data do we include in an execution? 34 | We don't want to assume anything about the format of the user information, but we can assume that two orders 35 | are somehow distinguishable. This is why we include an order ID. 36 | It's also probably safe to assume that there will be user information associated with an order. 37 | We include: 38 | - Order identifying information (so an order ID) 39 | - The amount and asset debited to the user associated with the order ID. 40 | - The amount and asset credited from the user associated with the order ID. 41 | - The updated AmountWant 42 | - The updated AmountHave 43 | - Whether or not the order was filled completely. 44 | - If yes, that means the order gets deleted. 45 | 46 | The asset for AmountWant and AmountHave can be determined from what they are in the order associated with 47 | the order ID. 48 | 49 | Ideally, we want to make sure that either the order is filled completely, or we have the updated 50 | AmountWant and AmountHave. This would be a great place for some sort of enum, but unfortunately 51 | we'll have to go with a bool. 52 | 53 | TODO: in the future, when AmountWant and AmountHave are replaced with a new price struct, this will need to 54 | change as well. The NewAmountWant and NewAmountHave can be replaced. 55 | 56 | On a typical exchange, say $ per btc, if you place a buy order at a high $/btc and someone else places a sell order 57 | at an even lower $/btc (want/have) after, then your buy order will be executed at your price. However if someone else 58 | places a sell order at a low-ish price, and you place a buy order at a price higher, then it will be executed at a lower price. 59 | 60 | Buy orders can only be matched at the price they are placed, or lower. 61 | Sell orders can only be matched at the price they are placed, or higher. 62 | You should never have an order be deleted and it yield 63 | less than you originally requested for the same value you provided. 64 | 65 | The good thing is, order executions do not depend on the type of order. 66 | */ 67 | type OrderExecution struct { 68 | OrderID OrderID `json:"orderid"` 69 | NewAmountWant uint64 `json:"newamtwant"` 70 | NewAmountHave uint64 `json:"newamthave"` 71 | Filled bool `json:"filled"` 72 | } 73 | 74 | // String returns a json representation of the OrderExecution 75 | func (oe *OrderExecution) String() string { 76 | // we are ignoring this error because we know that the struct is marshallable. All of the fields are. 77 | jsonRepresentation, _ := json.Marshal(oe) 78 | return string(jsonRepresentation) 79 | } 80 | 81 | // Equal compares one OrderExecution with another OrderExecution and returns true if all of the fields are the same. 82 | func (oe *OrderExecution) Equal(otherExec *OrderExecution) bool { 83 | if oe.OrderID != otherExec.OrderID { 84 | return false 85 | } 86 | if oe.NewAmountWant != otherExec.NewAmountWant { 87 | return false 88 | } 89 | if oe.NewAmountHave != otherExec.NewAmountHave { 90 | return false 91 | } 92 | if oe.Filled != otherExec.Filled { 93 | return false 94 | } 95 | return true 96 | } 97 | -------------------------------------------------------------------------------- /match/limitorderbook.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | // LimitBookStruct is a struct that represents a Limit Orderbook. 4 | // It tries to be very quick to access and manipulate, so the 5 | // orderbook is represented as an array. 6 | type LimitBookStruct struct { 7 | PriceLevels []LimitQueue 8 | } 9 | 10 | // LimitQueue represents a time-ordered queue for buy and sell orders 11 | type LimitQueue struct { 12 | BuyOrders []LimitOrder 13 | SellOrders []LimitOrder 14 | } 15 | -------------------------------------------------------------------------------- /match/limitorderidpair.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "time" 4 | 5 | // LimitOrderIDPair is order ID, order, price, and time, used for generating executions in limit order matching algorithms 6 | type LimitOrderIDPair struct { 7 | Timestamp time.Time `json:"timestamp"` 8 | Price float64 `json:"price"` 9 | OrderID *OrderID `json:"orderid"` 10 | Order *LimitOrder `json:"limitorder"` 11 | } 12 | -------------------------------------------------------------------------------- /match/orderid.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | // OrderID represents an order's unique ID. 9 | // This is a byte array alias because sometimes the ID will be represented as text, and sometimes it will be represented as bytes. 10 | // We conform it to the BinaryMarshaler interface and TextMarshaler interface. 11 | type OrderID [32]byte 12 | 13 | // MarshalBinary encodes the receiver into a binary form and returns the result. This conforms to the BinaryMarshaler interface 14 | func (o *OrderID) MarshalBinary() (data []byte, err error) { 15 | // size array, then copy 16 | data = make([]byte, 32) 17 | copy(data, o[:]) 18 | return 19 | } 20 | 21 | // UnmarshalBinary decodes the form generated by MarshalBinary. This conforms to the BinaryMarshaler interface 22 | func (o *OrderID) UnmarshalBinary(data []byte) (err error) { 23 | // no need to size because orderid is [32]byte 24 | copy(o[:], data) 25 | return 26 | } 27 | 28 | // MarshalText encodes the receiver into UTF-8-encoded text and returns the result. This conforms to the TextMarshaler interface 29 | func (o *OrderID) MarshalText() (text []byte, err error) { 30 | // size array, then copy 31 | oSlice := []byte(hex.EncodeToString(o[:])) 32 | text = make([]byte, len(oSlice)) 33 | copy(text, oSlice) 34 | return 35 | } 36 | 37 | // UnmarshalText deocdes the form generated by MarshalText. This conforms to the TextMarshaler interface 38 | func (o *OrderID) UnmarshalText(text []byte) (err error) { 39 | if _, err = hex.Decode(o[:], text); err != nil { 40 | err = fmt.Errorf("Error unmarshalling text OrderID: %s", err) 41 | return 42 | } 43 | return 44 | } 45 | 46 | // GobEncode returns a byte slice representing the encoding of the 47 | // receiver for transmission to a GobDecoder, usually of the same 48 | // concrete type. 49 | func (o *OrderID) GobEncode() (ret []byte, err error) { 50 | if ret, err = o.MarshalBinary(); err != nil { 51 | err = fmt.Errorf("Error gob encoding by marshalling binary: %s", err) 52 | return 53 | } 54 | return 55 | } 56 | 57 | // GobDecode overwrites the receiver, which must be a pointer, 58 | // with the value represented by the byte slice, which was written 59 | // by GobEncode, usually for the same concrete type. 60 | func (o *OrderID) GobDecode(in []byte) (err error) { 61 | if err = o.UnmarshalBinary(in); err != nil { 62 | err = fmt.Errorf("Error gob encoding by unmarshalling binary: %s", err) 63 | return 64 | } 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /match/pair.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/mit-dci/lit/coinparam" 8 | ) 9 | 10 | // AssetAmount represents an asset and amount pair. 11 | type AssetAmount struct { 12 | Asset Asset `json:"asset"` 13 | Amount uint64 `json:"amount"` 14 | } 15 | 16 | // Pair is a struct that represents a trading pair 17 | type Pair struct { 18 | // AssetWant is the asset that buyers want, and that sellers are selling. debit buyers with this. 19 | AssetWant Asset `json:"assetWant"` 20 | // AssetHave is the asset that sellers are buying, and that buyers have. debit sellers with this. 21 | AssetHave Asset `json:"assetHave"` 22 | } 23 | 24 | // type aliases are only usually used for codebase refactoring, so make this better when you have time. At some point a struct will probably need to be made. 25 | // like I'm probably going to replace this with just a master list of all the chainhooks / coinparams we could use 26 | // since everything should stem from that 27 | 28 | // Asset is a type which represents an asset 29 | type Asset byte 30 | 31 | // PrettyString is used to do asset1/asset2 rather than the database-safe asset1_asset2 32 | func (p *Pair) PrettyString() string { 33 | return p.AssetWant.String() + "/" + p.AssetHave.String() 34 | } 35 | 36 | // GenerateAssetPairs generates unique asset pairs based on the coinparams you pass it 37 | func GenerateAssetPairs(coinList []*coinparam.Params) (pairList []*Pair, err error) { 38 | coinListLen := len(coinList) 39 | numPairIndeces := coinListLen * (coinListLen - 1) / 2 40 | pairList = make([]*Pair, numPairIndeces) 41 | pairListIndex := 0 42 | for i, elem := range coinList { 43 | for lower := i + 1; lower < coinListLen; lower++ { 44 | var assetWant Asset 45 | if assetWant, err = AssetFromCoinParam(elem); err != nil { 46 | return 47 | } 48 | 49 | var assetHave Asset 50 | if assetHave, err = AssetFromCoinParam(coinList[lower]); err != nil { 51 | return 52 | } 53 | 54 | pairList[pairListIndex] = &Pair{ 55 | AssetWant: assetWant, 56 | AssetHave: assetHave, 57 | } 58 | pairListIndex++ 59 | } 60 | } 61 | 62 | return 63 | } 64 | 65 | // String is the tostring function for a pair 66 | func (p *Pair) String() string { 67 | return p.AssetWant.String() + "_" + p.AssetHave.String() 68 | } 69 | 70 | // FromString creates a pair object from a string. This is for user input only, hence the slash 71 | func (p *Pair) FromString(pairString string) (err error) { 72 | strSplit := strings.Split(pairString, "/") 73 | 74 | if p.AssetWant, err = AssetFromString(strSplit[0]); err != nil { 75 | return 76 | } 77 | 78 | if p.AssetHave, err = AssetFromString(strSplit[1]); err != nil { 79 | return 80 | } 81 | 82 | return 83 | } 84 | 85 | // Serialize serializes the pair into a byte array 86 | func (p *Pair) Serialize() []byte { 87 | pbuf := [2]byte{byte(p.AssetWant), byte(p.AssetHave)} 88 | return pbuf[:] 89 | } 90 | 91 | // Deserialize deserializes a byte array into a pair 92 | func (p *Pair) Deserialize(buf []byte) (err error) { 93 | if len(buf) != 2 { 94 | err = fmt.Errorf("Tried to deserialize, byte array length should be 2") 95 | return 96 | } 97 | p.AssetWant = Asset(buf[0]) 98 | p.AssetHave = Asset(buf[1]) 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /match/price.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | ) 7 | 8 | // Price represents an exchange rate. It's basically a fancy fraction. It follows the Want / Have method of doing things. 9 | // TODO: The price and side can be represented as a single struct that contains the Asset the user wants (AssetWant), the Asset that the user has (AssetHave), the amount of AssetHave the user is willing to give up, and the amount of AssetWant that the user would like. 10 | // This way there is no reason to have a "side" field, and prices can be represented more fairly, where you do not need to worry about precision other than the maximum precision of the asset, which is usually within a uint64. 11 | // We don't want this to be a big int because that means it can't really be sent over the wire. We're not multiple precision here, but we do want some 12 | // standard, reasonable level of precision 13 | type Price struct { 14 | AmountWant uint64 15 | AmountHave uint64 16 | } 17 | 18 | // Note on the Want / Have model: It makes sense from an exchange perspective, but in reality "side", "price", and "volume" are all connected. 19 | 20 | // ToFloat converts the price to a float value 21 | func (p *Price) ToFloat() (price float64, err error) { 22 | if p.AmountHave == 0 { 23 | err = fmt.Errorf("AmountHave cannot be 0 to convert to float") 24 | return 25 | } 26 | price = float64(p.AmountWant) / float64(p.AmountHave) 27 | return 28 | } 29 | 30 | // Cmp compares p and otherPrice and returns: 31 | // 32 | // -1 if x < y 33 | // 0 if x == y (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf) 34 | // +1 if x > y 35 | // 36 | func (p *Price) Cmp(otherPrice *Price) (compIndicator int) { 37 | // If we want to compare a/b and c/d, then we can just compare a*d 38 | // and b*c 39 | numeratorOne := new(big.Int).SetUint64(p.AmountWant) 40 | numeratorTwo := new(big.Int).SetUint64(otherPrice.AmountWant) 41 | denominatorOne := new(big.Int).SetUint64(p.AmountHave) 42 | denominatorTwo := new(big.Int).SetUint64(otherPrice.AmountHave) 43 | 44 | // because this actually sets, we don't need to assign 45 | numeratorOne.Mul(numeratorOne, denominatorTwo) 46 | numeratorTwo.Mul(numeratorTwo, denominatorOne) 47 | 48 | // a/b ?= c/d <=> ad ?= bc 49 | compIndicator = numeratorOne.Cmp(numeratorTwo) 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /match/puzsolorder.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "encoding/gob" 8 | "fmt" 9 | "math/big" 10 | 11 | "github.com/mit-dci/opencx/crypto/timelockencoders" 12 | ) 13 | 14 | // SolutionOrder is an order and modulus that are together. 15 | // This includes an order, and the puzzle modulus factors. 16 | type SolutionOrder struct { 17 | P *big.Int `json:"p"` 18 | Q *big.Int `json:"q"` 19 | } 20 | 21 | // NewSolutionOrder creates a new SolutionOrder from an already 22 | // existing AuctionOrder, with a specified number of bits for an rsa 23 | // key. 24 | func NewSolutionOrder(rsaKeyBits uint64) (solOrder SolutionOrder, err error) { 25 | rsaKeyBitsInt := int(rsaKeyBits) 26 | 27 | // generate primes p and q 28 | var rsaPrivKey *rsa.PrivateKey 29 | if rsaPrivKey, err = rsa.GenerateMultiPrimeKey(rand.Reader, 2, rsaKeyBitsInt); err != nil { 30 | err = fmt.Errorf("Could not generate primes for RSA: %s", err) 31 | return 32 | } 33 | if len(rsaPrivKey.Primes) != 2 { 34 | err = fmt.Errorf("For some reason the RSA Privkey has != 2 primes, this should not be the case for RSW, we only need p and q") 35 | return 36 | } 37 | 38 | // finally set p, q, and the auction order. 39 | solOrder.P = new(big.Int).SetBytes(rsaPrivKey.Primes[0].Bytes()) 40 | solOrder.Q = new(big.Int).SetBytes(rsaPrivKey.Primes[1].Bytes()) 41 | return 42 | } 43 | 44 | // EncryptSolutionOrder encrypts a solution order and creates a puzzle 45 | // along with the encrypted order 46 | func (so *SolutionOrder) EncryptSolutionOrder(auctionOrder AuctionOrder, t uint64) (encSolOrder EncryptedSolutionOrder, err error) { 47 | // Try serializing the solution order 48 | var rawSolOrder []byte = auctionOrder.Serialize() 49 | if encSolOrder.OrderCiphertext, encSolOrder.OrderPuzzle, err = timelockencoders.CreateRC5RSWPuzzleWithPrimes(uint64(2), t, rawSolOrder, so.P, so.Q); err != nil { 50 | err = fmt.Errorf("Error creating puzzle from auction order: %s", err) 51 | return 52 | } 53 | 54 | // make sure they match 55 | encSolOrder.IntendedAuction = auctionOrder.AuctionID 56 | encSolOrder.IntendedPair = auctionOrder.TradingPair 57 | return 58 | } 59 | 60 | // Serialize uses gob encoding to turn the solution order into bytes. 61 | func (so *SolutionOrder) Serialize() (raw []byte, err error) { 62 | var b bytes.Buffer 63 | 64 | // register SolutionOrder interface 65 | gob.Register(SolutionOrder{}) 66 | 67 | // create a new encoder writing to the buffer 68 | enc := gob.NewEncoder(&b) 69 | 70 | // encode the puzzle in the buffer 71 | if err = enc.Encode(so); err != nil { 72 | err = fmt.Errorf("Error encoding solutionorder: %s", err) 73 | return 74 | } 75 | 76 | // Get the bytes from the buffer 77 | raw = b.Bytes() 78 | return 79 | } 80 | 81 | // Deserialize turns the solution order from bytes into a usable 82 | // struct. 83 | func (so *SolutionOrder) Deserialize(raw []byte) (err error) { 84 | var b *bytes.Buffer 85 | b = bytes.NewBuffer(raw) 86 | 87 | // register SolutionOrder 88 | gob.Register(SolutionOrder{}) 89 | 90 | // create a new decoder writing to the buffer 91 | dec := gob.NewDecoder(b) 92 | 93 | // decode the solutionorder in the buffer 94 | if err = dec.Decode(so); err != nil { 95 | err = fmt.Errorf("Error decoding solutionorder: %s", err) 96 | return 97 | } 98 | 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /match/settlementexec.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | // TODO: replace with this once ready 9 | // SettlementExecution is the "settlement part" of an execution. 10 | // It defines the operations that should be done to the settlement engine. 11 | type SettlementExecution struct { 12 | Pubkey [33]byte `json:"pubkey"` 13 | Amount uint64 `json:"amount"` 14 | Asset Asset `json:"asset"` 15 | // SettleType is a type that determines whether or not this is a debit or credit 16 | Type SettleType `json:"settletype"` 17 | } 18 | 19 | // String returns a string representation of the SettlementExecution 20 | func (se *SettlementExecution) String() string { 21 | // We are ignoring this error because we know the struct is marshallable, since all of the fields are. 22 | jsonRepresentation, _ := json.Marshal(se) 23 | return string(jsonRepresentation) 24 | } 25 | 26 | // Equal compares one SettlementExecution with another SettlementExecution and returns true if all of the fields are the same. 27 | func (se *SettlementExecution) Equal(otherExec *SettlementExecution) bool { 28 | if !bytes.Equal(se.Pubkey[:], otherExec.Pubkey[:]) { 29 | return false 30 | } 31 | if se.Amount != otherExec.Amount { 32 | return false 33 | } 34 | if se.Asset != otherExec.Asset { 35 | return false 36 | } 37 | if se.Type != otherExec.Type { 38 | return false 39 | } 40 | return true 41 | } 42 | -------------------------------------------------------------------------------- /match/settleresult.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "encoding/json" 4 | 5 | // SettlementResult is the settlement exec and the new balance 6 | type SettlementResult struct { 7 | NewBal uint64 `json:"newbal"` 8 | SuccessfulExec *SettlementExecution `json:"successfulexec"` 9 | } 10 | 11 | // String returns the string representation of a settlement result 12 | func (sr *SettlementResult) String() string { 13 | // this will pass because both are marshallable 14 | jsonRepresentation, _ := json.Marshal(sr) 15 | return string(jsonRepresentation) 16 | } 17 | -------------------------------------------------------------------------------- /match/settletype.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // SettleType is a type that represents either a credit or a debit 10 | type SettleType bool 11 | 12 | const ( 13 | Debit = SettleType(iota%2 == 0) 14 | Credit 15 | ) 16 | const ( 17 | debitString = "debit" 18 | creditString = "credit" 19 | ) 20 | 21 | // String returns the string representation of a credit or debit 22 | func (st *SettleType) String() string { 23 | if *st { 24 | return debitString 25 | } 26 | return creditString 27 | } 28 | 29 | func (st *SettleType) UnmarshalJSON(b []byte) (err error) { 30 | var str string 31 | if err = json.Unmarshal(b, &str); err != nil { 32 | return 33 | } 34 | switch strings.ToLower(str) { 35 | default: 36 | err = fmt.Errorf("Cannot unmarshal settletype json, not credit or debit") 37 | return 38 | case debitString: 39 | *st = Debit 40 | case creditString: 41 | *st = Credit 42 | } 43 | 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /match/side.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Side is a representation of buy or sell side 10 | type Side bool 11 | 12 | const ( 13 | Buy = Side(true) // This should serialize to 0x01 14 | Sell = Side(false) // This should serialize to 0x00 15 | buyString = "buy" // just for string representation 16 | sellString = "sell" // just for string representation 17 | ) 18 | 19 | // TODO: Rather than use this string method, implement TextMarshaler 20 | // and TextUnmarshaler 21 | 22 | // String returns the string representation of a buy or sell side 23 | func (s Side) String() string { 24 | if s { 25 | return buyString 26 | } 27 | return sellString 28 | } 29 | 30 | // UnmarshalJSON implements the JSON unmarshalling interface 31 | func (s Side) UnmarshalJSON(b []byte) (err error) { 32 | var str string 33 | if err = json.Unmarshal(b, &str); err != nil { 34 | return 35 | } 36 | switch strings.ToLower(str) { 37 | default: 38 | err = fmt.Errorf("Cannot unmarshal side json, not buy or sell") 39 | return 40 | case buyString: 41 | s = Buy 42 | case sellString: 43 | s = Sell 44 | } 45 | return 46 | } 47 | 48 | // FromString takes a string and, if valid, sets the Side to the 49 | // correct value based on the string 50 | func (s Side) FromString(str string) (err error) { 51 | switch strings.ToLower(str) { 52 | default: 53 | err = fmt.Errorf("Cannot get side from string, not buy or sell") 54 | return 55 | case buyString: 56 | s = Buy 57 | case sellString: 58 | s = Sell 59 | } 60 | return 61 | } 62 | 63 | // MarshalBinary implements the BinaryMarshaler interface from 64 | // encoding. 65 | func (s Side) MarshalBinary() (data []byte, err error) { 66 | if s { 67 | data = []byte{0x01} 68 | return 69 | } 70 | data = []byte{0x00} 71 | return 72 | } 73 | 74 | // UnmarshalBinary implements the BinaryUnmarshaler interface from 75 | // encoding. 76 | // This takes a pointer as a receiver because it's not possible nor 77 | // does it make sense to try to modify the value being called. 78 | func (s *Side) UnmarshalBinary(data []byte) (err error) { 79 | if len(data) != 1 { 80 | err = fmt.Errorf("Error marshalling binary for a Side, length of data should be 1") 81 | return 82 | } 83 | if data[0] == 0x00 { 84 | *s = Sell 85 | return 86 | } else if data[0] == 0x01 { 87 | *s = Buy 88 | return 89 | } 90 | err = fmt.Errorf("Error unmarshalling Side, invalid data - should be 0x00 or 0x01") 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /match/withdrawal.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "encoding/binary" 4 | 5 | // Withdrawal is a representation of a withdrawal. This is because withdrawals are now signed. 6 | type Withdrawal struct { 7 | Asset Asset 8 | Amount uint64 9 | Address string 10 | // This tells whether or not this is a lightning withdrawal. Default value is false so that makes it easier to not mess up 11 | Lightning bool 12 | } 13 | 14 | // Serialize serializes the withdrawal 15 | func (w *Withdrawal) Serialize() (buf []byte) { 16 | // bool yes or no 17 | // Asset [1 byte] 18 | // Amount [64 bytes] 19 | // len(address) 20 | // Address [len(address)] 21 | 22 | var oneorzero byte 23 | if w.Lightning { 24 | oneorzero = 0xff 25 | } 26 | buf = make([]byte, 66+len(w.Address)) 27 | buf = append(buf, oneorzero) 28 | buf = append(buf, byte(w.Asset)) 29 | binary.LittleEndian.PutUint64(buf, w.Amount) 30 | binary.LittleEndian.PutUint64(buf, uint64(len(w.Address))) 31 | buf = append(buf, []byte(w.Address)...) 32 | return 33 | } 34 | --------------------------------------------------------------------------------