├── .gitignore ├── LICENSE ├── README.md ├── auth.go ├── main.go └── rpc └── rpc.go /.gitignore: -------------------------------------------------------------------------------- 1 | zmsg 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 whyrusleeping 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zmsg 2 | A zero knowledge messaging system built on zcash. 3 | 4 | zmsg uses the encrypted memo field of zcash sheilded transactions to send 5 | messages to other parties. The sent messages get stored in the zcash blockchain 6 | and the recipient can check for messages at any time (no need to be online at 7 | the same time). Since the messages get stored in the blockchain, they are on 8 | *every* full zcash node. The implication here is that its not possible to tell 9 | who the target of any given message is. 10 | 11 | Currently, each message sends 0.0001 ZEC. You can change this value by setting 12 | the `--txval` flag on `sendmsg`. 13 | 14 | ## Installation 15 | First, make sure you have [go](https://golang.org/doc/install) installed, then: 16 | ```sh 17 | go get github.com/whyrusleeping/zmsg 18 | ``` 19 | 20 | ## Usage 21 | Note: To use zmsg, you'll need a running zcash daemon, a z_address, and some 22 | spare ZEC in that address. 23 | 24 | ### sendmsg 25 | To send a message, use `zmsg sendmsg`: 26 | ```sh 27 | $ export TARGET_ZADDR=zchfvC6iubfsAxaNrbM4kkGDSpwjafECjqQ1BZBFXtotXyXARz2NoYRVEyfLEKGCFRY7Xfj2Q3jFueoHHmQKb63C3zumYnU 28 | $ zmsg sendmsg -to=$TARGET_ZADDR "Hello zmsg! welcome to pretty secure messaging" 29 | message: "Hello zmsg! welcome to pretty secure messaging" 30 | sending message from 31 | sending message... 32 | Message sent successfuly! 33 | message sent! (txid = ) 34 | ``` 35 | 36 | Note that this will take about a minute to compute the zero-knowledge proof, 37 | and another few minutes before the transaction gets propagated and confirmed 38 | for the other side to see it. 39 | 40 | ### check 41 | To check for messages, run `zmsg check`: 42 | 43 | ```sh 44 | ================================================================================ 45 | > Got 2 messages. 46 | ================================================================================ 47 | | Message #0 (val = 0.000010) 48 | | To: zchfvC6iubfsAxaNrbM4kkGDSpwjafECjqQ1BZBFXtotXyXARz2NoYRVEyfLEKGCFRY7Xfj2Q3jFueoHHmQKb63C3zumYnU 49 | | Date: 2016-11-11 17:36:31 -0800 PST 50 | | 51 | | This is a test of zmsg, hello everyone! 52 | ================================================================================ 53 | | Message #1 (val = 0.000010) 54 | | To: zchfvC6iubfsAxaNrbM4kkGDSpwjafECjqQ1BZBFXtotXyXARz2NoYRVEyfLEKGCFRY7Xfj2Q3jFueoHHmQKb63C3zumYnU 55 | | Date: 2016-11-11 17:44:44 -0800 PST 56 | | 57 | | This is message number 'two', i'm sitting in a coffee shop. Don't tell anyone. 58 | ================================================================================ 59 | ``` 60 | 61 | ## Send me a message! 62 | If you're trying this out and want to say hi, send me a message at `zchfvC6iubfsAxaNrbM4kkGDSpwjafECjqQ1BZBFXtotXyXARz2NoYRVEyfLEKGCFRY7Xfj2Q3jFueoHHmQKb63C3zumYnU`. 63 | 64 | ## License 65 | MIT, whyrusleeping 66 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | rpc "github.com/whyrusleeping/zmsg/rpc" 11 | ) 12 | 13 | func init() { 14 | u, p, err := readAuthCreds() 15 | if err != nil { 16 | fmt.Println("Error reading zcash config: ", err) 17 | } 18 | 19 | rpc.DefaultClient.User = u 20 | rpc.DefaultClient.Pass = p 21 | } 22 | 23 | func readAuthCreds() (string, string, error) { 24 | homedir := os.Getenv("HOME") 25 | confpath := filepath.Join(homedir, ".zcash/zcash.conf") 26 | fi, err := os.Open(confpath) 27 | if err != nil { 28 | return "", "", err 29 | } 30 | defer fi.Close() 31 | 32 | var user string 33 | var pass string 34 | scan := bufio.NewScanner(fi) 35 | for scan.Scan() { 36 | parts := strings.SplitN(scan.Text(), "=", 2) 37 | if len(parts) < 2 { 38 | continue 39 | } 40 | 41 | key := strings.TrimSpace(parts[0]) 42 | val := strings.TrimSpace(parts[1]) 43 | 44 | switch key { 45 | case "rpcuser": 46 | user = val 47 | case "rpcpassword": 48 | pass = val 49 | } 50 | } 51 | 52 | return user, pass, nil 53 | } 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | cli "github.com/urfave/cli" 13 | rpc "github.com/whyrusleeping/zmsg/rpc" 14 | ) 15 | 16 | var Verbose = false 17 | 18 | type Message struct { 19 | To string 20 | Timestamp time.Time 21 | Content string 22 | Val float64 23 | } 24 | 25 | func getMyAddresses() ([]string, error) { 26 | req := &rpc.Request{ 27 | Method: "z_listaddresses", 28 | } 29 | 30 | var out struct { 31 | Result []string 32 | } 33 | err := rpc.Do(req, &out) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return out.Result, nil 39 | } 40 | 41 | type TxDesc struct { 42 | Txid string 43 | Amount float64 44 | Memo string 45 | } 46 | 47 | func parseTypedData(d []byte) (uint64, []byte, error) { 48 | mtyp, n1 := binary.Uvarint(d) 49 | mlen, n2 := binary.Uvarint(d[n1:]) 50 | if 1+n1+n2+int(mlen) > 512 { 51 | return 0, nil, fmt.Errorf("specified message length exceeded limit") 52 | } 53 | 54 | return mtyp, d[n1+n2 : n1+n2+int(mlen)], nil 55 | } 56 | 57 | // getReceivedForAddr returns all received messages for a given address 58 | func getReceivedForAddr(addr string, noconf bool) ([]*Message, error) { 59 | req := &rpc.Request{ 60 | Method: "z_listreceivedbyaddress", 61 | } 62 | 63 | params := []interface{}{addr} 64 | if noconf { 65 | params = append(params, 0) 66 | } 67 | 68 | req.Params = params 69 | 70 | var out struct { 71 | Result []*TxDesc 72 | } 73 | 74 | err := rpc.Do(req, &out) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | var msgs []*Message 80 | for _, txd := range out.Result { 81 | decmemo, err := hex.DecodeString(txd.Memo) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | var content string 87 | switch { 88 | case decmemo[0] == 0xff: 89 | if Verbose { 90 | fmt.Println(" == warning: memo contained unformatted data") 91 | } 92 | continue 93 | default: 94 | if Verbose { 95 | fmt.Printf(" == warning: memo contained data marked with future protocol extension codes (%d)\n", decmemo[0]) 96 | } 97 | continue 98 | case decmemo[0] == 0xf5: 99 | // typed length prefixed data 100 | mtyp, buf, err := parseTypedData(decmemo[1:]) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | // 0xa0 == ascii type flag 106 | if mtyp != 0xa0 { 107 | if Verbose { 108 | fmt.Println(" == warning: can only handle type of 'ascii' (0xa0)") 109 | } 110 | continue 111 | } 112 | 113 | content = string(buf) 114 | 115 | case decmemo[0] <= 0xf4: 116 | // acceptable, utf encoded text 117 | content = string(bytes.TrimRight(decmemo, "\x00")) 118 | } 119 | 120 | tx, err := getTransaction(txd.Txid) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | msg := &Message{ 126 | Timestamp: time.Unix(tx.Time, 0), 127 | Val: txd.Amount, 128 | Content: content, 129 | To: addr, 130 | } 131 | 132 | msgs = append(msgs, msg) 133 | } 134 | 135 | return msgs, nil 136 | } 137 | 138 | // CheckMessages returns all messages that the local zcash daemon has received 139 | func CheckMessages(noconf bool) ([]*Message, error) { 140 | addrs, err := getMyAddresses() 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | var allmsgs []*Message 146 | for _, myaddr := range addrs { 147 | msgs, err := getReceivedForAddr(myaddr, noconf) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | allmsgs = append(allmsgs, msgs...) 153 | } 154 | 155 | return allmsgs, nil 156 | } 157 | 158 | var ErrNoAddresses = fmt.Errorf("no addresses to send message from! (create one with the zcash-cli)") 159 | 160 | // SendMessage sends a message to a given zcash address using a shielded transaction. 161 | // It returns the transaction ID. 162 | func SendMessage(from, to, msg string, msgval float64) (string, error) { 163 | if from == "" { 164 | // if no from address is specified, use the first local address 165 | myaddrs, err := getMyAddresses() 166 | if err != nil { 167 | return "", err 168 | } 169 | 170 | if len(myaddrs) == 0 { 171 | return "", ErrNoAddresses 172 | } 173 | 174 | from = myaddrs[0] 175 | fmt.Printf("sending message from %s\n", from) 176 | } 177 | 178 | req := &rpc.Request{ 179 | Method: "z_sendmany", 180 | Params: []interface{}{ 181 | from, // first parameter is address to send from (where the ZEC comes from) 182 | []interface{}{ 183 | map[string]interface{}{ 184 | "amount": msgval, 185 | "address": to, 186 | "memo": hex.EncodeToString([]byte(msg)), 187 | }, 188 | }, 189 | }, 190 | } 191 | 192 | var out struct { 193 | Result string 194 | } 195 | err := rpc.Do(req, &out) 196 | if err != nil { 197 | return "", err 198 | } 199 | 200 | opid := out.Result 201 | txid, err := WaitForOperation(opid) 202 | if err != nil { 203 | return "", err 204 | } 205 | 206 | return txid, nil 207 | } 208 | 209 | type opStatus struct { 210 | Id string 211 | Status string 212 | CreationTime uint64 `json:"creation_time"` 213 | Error rpc.Error 214 | Result struct { 215 | Txid string 216 | } 217 | } 218 | 219 | func checkOperationStatus(opid string) (*opStatus, error) { 220 | req := &rpc.Request{ 221 | Method: "z_getoperationstatus", 222 | Params: []interface{}{[]string{opid}}, 223 | } 224 | 225 | var out struct { 226 | Result []*opStatus 227 | } 228 | err := rpc.Do(req, &out) 229 | if err != nil { 230 | return nil, err 231 | } 232 | 233 | return out.Result[0], nil 234 | } 235 | 236 | type Transaction struct { 237 | Time int64 238 | Confirmations int 239 | } 240 | 241 | func getTransaction(txid string) (*Transaction, error) { 242 | req := &rpc.Request{ 243 | Method: "gettransaction", 244 | Params: []string{txid}, 245 | } 246 | 247 | var out struct { 248 | Result Transaction 249 | } 250 | 251 | err := rpc.Do(req, &out) 252 | if err != nil { 253 | return nil, err 254 | } 255 | 256 | return &out.Result, nil 257 | } 258 | 259 | // WaitForOperation polls the operations status until it either fails or 260 | // succeeds. 261 | func WaitForOperation(opid string) (string, error) { 262 | i := 0 263 | for range time.Tick(time.Second) { 264 | status, err := checkOperationStatus(opid) 265 | if err != nil { 266 | return "", err 267 | } 268 | 269 | switch status.Status { 270 | case "failed": 271 | fmt.Println("operation failed!") 272 | fmt.Println("reason: ", status.Error.Message) 273 | return "", fmt.Errorf(status.Error.Message) 274 | case "executing": 275 | // in progress, print a progress thingy? 276 | fmt.Printf("\r \r") 277 | fmt.Print("sending message") 278 | for j := 0; j <= (i % 4); j++ { 279 | fmt.Print(".") 280 | } 281 | case "success": 282 | fmt.Println("\nMessage sent successfuly!") 283 | return status.Result.Txid, nil 284 | default: 285 | fmt.Printf("%#v\n", status) 286 | } 287 | i++ 288 | } 289 | return "", nil 290 | } 291 | 292 | var CheckCmd = cli.Command{ 293 | Name: "check", 294 | Usage: "check for messages.", 295 | Flags: []cli.Flag{ 296 | cli.BoolFlag{ 297 | Name: "verbose", 298 | Usage: "enable verbose log output", 299 | }, 300 | cli.BoolFlag{ 301 | Name: "noconf", 302 | Usage: "show messages with no transaction confirmations", 303 | }, 304 | }, 305 | Action: func(c *cli.Context) error { 306 | Verbose = c.Bool("verbose") 307 | msgs, err := CheckMessages(c.Bool("noconf")) 308 | if err != nil { 309 | return err 310 | } 311 | 312 | div := strings.Repeat("=", 80) 313 | fmt.Println(div) 314 | fmt.Printf("> Got %d messages.\n", len(msgs)) 315 | fmt.Println(div) 316 | for i, m := range msgs { 317 | fmt.Printf("| Message #%d (val = %f)\n", i, m.Val) 318 | fmt.Printf("| To: %s\n", m.To) 319 | fmt.Printf("| Date: %s\n|\n", m.Timestamp) 320 | fmt.Println("| ", strings.Replace(m.Content, "\n", "\n| ", -1)) 321 | fmt.Println(div) 322 | } 323 | 324 | return nil 325 | }, 326 | } 327 | 328 | var SendCmd = cli.Command{ 329 | Name: "sendmsg", 330 | Usage: "send a message to another zmsg user.", 331 | Flags: []cli.Flag{ 332 | cli.StringFlag{ 333 | Name: "from", 334 | Usage: "address to send message from", 335 | }, 336 | cli.StringFlag{ 337 | Name: "to", 338 | Usage: "address to send message to", 339 | }, 340 | cli.Float64Flag{ 341 | Name: "txval", 342 | Value: 0.00001, 343 | Usage: "specify the amount of ZEC to send with messages.", 344 | }, 345 | }, 346 | Action: func(c *cli.Context) error { 347 | to := c.String("to") 348 | from := c.String("from") 349 | msg := strings.Join(c.Args(), " ") 350 | 351 | if to == "" { 352 | return fmt.Errorf("please specify an address to send the message to") 353 | } 354 | 355 | if msg == "" { 356 | return fmt.Errorf("no message specified") 357 | } 358 | fmt.Printf("message: %q\n", msg) 359 | 360 | txid, err := SendMessage(from, to, msg, c.Float64("txval")) 361 | if err != nil { 362 | return err 363 | } 364 | 365 | fmt.Printf("message sent! (txid = %s)\n", txid) 366 | return nil 367 | }, 368 | } 369 | 370 | func main() { 371 | app := cli.NewApp() 372 | app.Version = "0.1.0" 373 | app.Author = "whyrusleeping" 374 | app.Email = "why@ipfs.io" 375 | app.Usage = "send and receive zero knowledge messages" 376 | app.Commands = []cli.Command{ 377 | CheckCmd, 378 | SendCmd, 379 | } 380 | 381 | err := app.Run(os.Args) 382 | if err != nil { 383 | fmt.Println("Error: ", err) 384 | os.Exit(1) 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | var DefaultUser string 13 | var DefaultPass string 14 | 15 | type Error struct { 16 | Code int `json:"code"` 17 | Message string `json:"message"` 18 | } 19 | 20 | func (e Error) Error() string { 21 | return fmt.Sprintf("error %d: %s", e.Code, e.Message) 22 | } 23 | 24 | type Response struct { 25 | Result interface{} `json:"result"` 26 | Error *Error `json:"error"` 27 | } 28 | 29 | type Request struct { 30 | Method string `json:"method"` 31 | Params interface{} `json:"params"` 32 | } 33 | 34 | type Client struct { 35 | Host string 36 | User string 37 | Pass string 38 | } 39 | 40 | var DefaultClient = &Client{ 41 | Host: "http://localhost:8232", 42 | } 43 | 44 | func Do(obj *Request, out interface{}) error { 45 | return DefaultClient.Do(obj, out) 46 | } 47 | 48 | func (c *Client) Do(obj *Request, out interface{}) error { 49 | data, err := json.Marshal(obj) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | body := bytes.NewReader(data) 55 | 56 | req, err := http.NewRequest("POST", c.Host, body) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // auth auth baby 62 | if c.User != "" { 63 | req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.User+":"+c.Pass))) 64 | } 65 | 66 | resp, err := http.DefaultClient.Do(req) 67 | if err != nil { 68 | if strings.Contains(err.Error(), "connection refused") { 69 | return fmt.Errorf("failed to connect to zcash daemon, is it running?") 70 | } 71 | return err 72 | } 73 | defer resp.Body.Close() 74 | 75 | if resp.StatusCode != 200 { 76 | var res Response 77 | err := json.NewDecoder(resp.Body).Decode(&res) 78 | if err != nil { 79 | fmt.Println("error reading http body: ", err) 80 | } 81 | 82 | return res.Error 83 | } 84 | 85 | return json.NewDecoder(resp.Body).Decode(out) 86 | } 87 | --------------------------------------------------------------------------------