From 5cf3152dd66ab7f26c16307a09e47f65c0c233dd Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 23 Aug 2019 13:51:21 -0700 Subject: [PATCH] Initial Commit --- .gitignore | 5 ++ LICENSE | 2 +- client/client.go | 51 ++++++++++++++++++++ cmd/main.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 17 +++++++ go.sum | 49 ++++++++++++++++++++ transaction.go | 9 ++++ zcash_client.go | 66 ++++++++++++++++++++++++++ 8 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 client/client.go create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 transaction.go create mode 100644 zcash_client.go diff --git a/.gitignore b/.gitignore index d3beee5..c64cd63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + # ---> Go # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o @@ -24,3 +25,7 @@ _testmain.go *.test *.prof +config.json +.idea/ +tor/ +vendor/ diff --git a/LICENSE b/LICENSE index 472ac23..1cbab89 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ MIT License -Copyright (c) +Copyright (c) Open Privacy Research Society 2019 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..82bcafc --- /dev/null +++ b/client/client.go @@ -0,0 +1,51 @@ +package main + +import ( + "cwtch.im/tapir" + "cwtch.im/tapir/applications" + "cwtch.im/tapir/networks/tor" + "cwtch.im/tapir/primitives" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "strings" + "time" +) + +type PublicMemoClient struct { + applications.AuthApp +} + +func (ma PublicMemoClient) NewInstance() tapir.Application { + return new(PublicMemoClient) +} + +// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability +// or the connection is closed. +func (ma *PublicMemoClient) Init(connection tapir.Connection) { + ma.AuthApp.Init(connection) + + if connection.HasCapability(applications.AuthCapability) { + memo := connection.Expect() + for string(memo) != "{}" { + log.Infof("Received Public Memo: %s", strings.ReplaceAll(string(memo), "\n", "\\n")) + memo = connection.Expect() + } + } +} + +func main() { + log.SetLevel(log.LevelDebug) + var acn connectivity.ACN + acn, _ = connectivity.StartTor("./", "") + acn.WaitTillBootstrapped() + + id, sk := primitives.InitializeEphemeralIdentity() + var client tapir.Service + client = new(tor.BaseOnionService) + client.Init(acn, sk, &id) + client.Connect("a6lh4dju3jqmh6tghnfjdqqohnp3lvww3wgbtvkez4weswartvv75qqd", &PublicMemoClient{}) + + // Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo + // we will wait a little while then exit. + time.Sleep(time.Second * 10) +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..72d1a3d --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "cwtch.im/tapir" + "cwtch.im/tapir/applications" + "cwtch.im/tapir/networks/tor" + "cwtch.im/tapir/primitives" + "cwtch.im/zcash2cwtch" + "encoding/base64" + "encoding/hex" + "encoding/json" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "golang.org/x/crypto/ed25519" + "io/ioutil" + "sync" + "time" +) + +type ZcashConfig struct { + Username string `json: "username"` + Password string `json: "password"` + Onion string `json: "onion"` + ZAddress string `json: "zaddress"` + PublicKey string `json: "publickey"` + PrivateKey string `json: "privatekey"` +} + +type TapirServer struct { + acn connectivity.ACN + seenMap sync.Map + transactions []zcash2cwtch.ZcashTransaction + service tapir.Service +} + +type MemoApp struct { + applications.AuthApp + service *TapirServer +} + +func (ma MemoApp) NewInstance() tapir.Application { + memoApp := new(MemoApp) + memoApp.service = ma.service + return memoApp +} + +// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability +// or the connection is closed. +func (ma MemoApp) Init(connection tapir.Connection) { + ma.AuthApp.Init(connection) + + if connection.HasCapability(applications.AuthCapability) { + for _, transaction := range ma.service.transactions { + memo, _ := hex.DecodeString(transaction.Memo) + connection.Send([]byte("{\"public\": \"" + string(memo) + "\"}")) + } + connection.Send([]byte("{}")) + } +} + +func StartTapirServer() *TapirServer { + ts := new(TapirServer) + acn, _ := connectivity.StartTor("", "") + acn.WaitTillBootstrapped() + ts.acn = acn + + configFile, _ := ioutil.ReadFile("config.json") + config := ZcashConfig{} + _ = json.Unmarshal([]byte(configFile), &config) + + var service tapir.Service + service = new(tor.BaseOnionService) + // Initialize an onion service with one identity, but the auth app with another, this should + // trigger a failure in authentication protocol + sk, _ := base64.StdEncoding.DecodeString(config.PrivateKey) + pk, _ := base64.StdEncoding.DecodeString(config.PublicKey) + publicKey := ed25519.PublicKey(pk) + privateKey := ed25519.PrivateKey(sk) + id := primitives.InitializeIdentity("", &privateKey, &publicKey) + service.Init(acn, ed25519.PrivateKey(sk), &id) + ts.service = service + return ts +} + +func (ts *TapirServer) Listen() { + ts.service.Listen(MemoApp{service: ts}) +} + +func (ts *TapirServer) Refresh() { + for { + configFile, _ := ioutil.ReadFile("config.json") + config := ZcashConfig{} + _ = json.Unmarshal([]byte(configFile), &config) + + zc := zcash2cwtch.NewClient(config.Onion, config.Username, config.Password, ts.acn) + transactions, err := zc.ListReceivedTransactionsByAddress(config.ZAddress) + if err != nil { + log.Errorf("Error fetching zcash transactions: %v", err) + } + for _, transaction := range transactions { + _, exists := ts.seenMap.Load(transaction.TransactionID) + if !exists { + log.Infof("Got a new transaction txid:%s, amount %f", transaction.TransactionID, transaction.Amount) + ts.transactions = append(ts.transactions, transaction) + ts.seenMap.Store(transaction.TransactionID, true) + } + } + time.Sleep(time.Minute) + } +} + +func main() { + log.SetLevel(log.LevelDebug) + ts := StartTapirServer() + + go ts.Refresh() + ts.Listen() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a51ad9c --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module cwtch.im/zcash2cwtch + +go 1.12 + +require ( + cwtch.im/tapir v0.1.10 + git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 + github.com/golang/protobuf v1.3.2 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.4.0 // indirect + golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 + golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..70049e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,49 @@ +cwtch.im/tapir v0.1.10 h1:V+TkmwXNd6gySZqlVw468wMYEkmDwMSyvhkkpOfUw7w= +cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34= +git.openprivacy.ca/openprivacy/libricochet-go v1.0.4/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY= +git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 h1:5o4K2qn3otEE1InC5v5CzU0yL7Wl7DhVp4s8H3K6mXY= +git.openprivacy.ca/openprivacy/libricochet-go v1.0.6/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/cretz/bine v0.1.0 h1:1/fvhLE+fk0bPzjdO5Ci+0ComYxEMuB1JhM4X5skT3g= +github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..6634223 --- /dev/null +++ b/transaction.go @@ -0,0 +1,9 @@ +package zcash2cwtch + +type ZcashTransaction struct { + TransactionID string `json:"txid"` + Amount float64 `json:"amount"` + Memo string `json:"memo"` + Change bool `json:"change"` + OutIndex int `json:"outindex"` +} diff --git a/zcash_client.go b/zcash_client.go new file mode 100644 index 0000000..bfc6077 --- /dev/null +++ b/zcash_client.go @@ -0,0 +1,66 @@ +package zcash2cwtch + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "io/ioutil" + "net" + "net/http" +) + +type ZcashResult struct { + Result interface{} `json:"result"` +} + +type ZcashClient interface { + ListReceivedTransactionsByAddress(string) ([]ZcashTransaction, error) +} + +type zcashOnionClient struct { + client http.Client + onion string + auth string +} + +// NewClient creates a new Zcash rpc client over an onion address +func NewClient(onion string, username, password string, acn connectivity.ACN) ZcashClient { + zc := new(zcashOnionClient) + zc.onion = onion + zc.client = http.Client{ + Transport: &http.Transport{ + Dial: func(network, addr string) (net.Conn, error) { + conn, _, err := acn.Open(onion) + return conn, err + }, + }, + } + zc.auth = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + return zc +} + +func (zc *zcashOnionClient) ListReceivedTransactionsByAddress(address string) ([]ZcashTransaction, error) { + var jsonStr = []byte(`{"jsonrpc": "1.0", "id":"zcash2cwtch", "method": "z_listreceivedbyaddress", "params": ["` + address + `"]}`) + req, err := http.NewRequest("POST", "http://"+zc.onion+".onion:9878", bytes.NewBuffer(jsonStr)) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", "Basic "+zc.auth) + log.Debugf("Sending request to zcash server %v", req) + resp, err := zc.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + log.Debugf("Got response from zcash server %s", body) + transactions := []ZcashTransaction{} + result := &ZcashResult{Result: &transactions} + err = json.Unmarshal(body, &result) + return transactions, err +}