Merge branch 'init' of openprivacy/zcashtokenservice into master

This commit is contained in:
Sarah Jamie Lewis 2019-12-02 18:23:56 -08:00 committed by Gogs
commit 227b4f4a01
8 changed files with 396 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
config.json
tokens.db
vendor/

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Zcash Token Service
This repository contains a *prototype* implementation of a token service
(described in this [paper](./paper/)) and based on the privacypass implementation in Tapir.
`client/client.go` implements the client portion, generating a set of tokens and sending these
to the zcash token service through an [encrypted memo](https://electriccoin.co/blog/encrypted-memo-field/) along with payment and a return address.
`server/server.go` implements the server portion, polling zcash for new transactions, signing the
tokens it receives along with a proof and sending both back through an encrypted memo to the specified
return address.

106
api.go Normal file
View File

@ -0,0 +1,106 @@
package zcashtokenservice
import (
"cwtch.im/tapir/primitives/core"
"cwtch.im/tapir/primitives/privacypass"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/gtank/ristretto255"
"golang.org/x/crypto/sha3"
"strings"
)
type Context struct {
Y *ristretto255.Element
tokens []*privacypass.Token
blindedTokens []privacypass.BlindedToken
transcript *core.Transcript
ct *privacypass.Token
}
type ZcashTokenRequest struct {
BlindedTokens []privacypass.BlindedToken `json:"bts"`
ReturnAddress string `json:"ra"`
ConstraintToken []byte `json:"ct"`
}
// RequestTokens generates a zcash uri
func RequestTokenURI(tokenServiceZcashAddress string, tokenServicePublicKey *ristretto255.Element, zcashReturnAddress string, constraintToken *privacypass.Token) (Context, string) {
tokens, blinded := privacypass.GenerateBlindedTokenBatch(4)
transcript := core.NewTranscript("zcash-token-service")
transcript.AddToTranscript(zcashReturnAddress, []byte(zcashReturnAddress))
transcript.AddToTranscript("zcash-token-server", []byte(tokenServiceZcashAddress))
request := ZcashTokenRequest{BlindedTokens: blinded, ReturnAddress: zcashReturnAddress}
if constraintToken != nil {
transcript.AddToTranscript("constraint-token-t", constraintToken.GetT())
request.ConstraintToken = constraintToken.GetT()
}
data, _ := json.Marshal(request)
// NOTE: We are currently hardcoding this amount!
return Context{tokens: tokens, blindedTokens: blinded, Y: tokenServicePublicKey, transcript: transcript, ct: constraintToken}, fmt.Sprintf("zcash:%v?amt=%f&memo=%v", tokenServiceZcashAddress, 0.1, base64.StdEncoding.EncodeToString(data))
}
type ZcashTokenResponse struct {
privacypass.SignedBatchWithProof `json:"sb"`
Error error `json:",omitempty"`
}
func ProcessRequest(request string, tokenServiceZcashAddress string, server *privacypass.TokenServer) (string, string) {
log.Infof("Request: [%x]", request)
data, err := base64.StdEncoding.DecodeString(request)
log.Infof("Err: %s", data)
if err == nil {
zcashTokenRequest := new(ZcashTokenRequest)
err = json.Unmarshal(data, zcashTokenRequest)
if err == nil {
transcript := core.NewTranscript("zcash-token-service")
transcript.AddToTranscript(zcashTokenRequest.ReturnAddress, []byte(zcashTokenRequest.ReturnAddress))
transcript.AddToTranscript("zcash-token-server", []byte(tokenServiceZcashAddress))
if len(zcashTokenRequest.ConstraintToken) == 0 {
signedBatchWithProof := server.SignBlindedTokenBatch(zcashTokenRequest.BlindedTokens, transcript)
data, _ := json.Marshal(ZcashTokenResponse{signedBatchWithProof, nil})
return base64.StdEncoding.EncodeToString(data), zcashTokenRequest.ReturnAddress
} else {
transcript.AddToTranscript("constraint-token-t", zcashTokenRequest.ConstraintToken)
signedBatchWithProof := server.SignBlindedTokenBatchWithConstraint(zcashTokenRequest.BlindedTokens, zcashTokenRequest.ConstraintToken, transcript)
log.Debugf("Server Side Transcript: %s", transcript.OutputTranscriptToAudit())
data, _ := json.Marshal(ZcashTokenResponse{signedBatchWithProof, nil})
return base64.StdEncoding.EncodeToString(data), zcashTokenRequest.ReturnAddress
}
}
}
errorresponse, _ := json.Marshal(ZcashTokenResponse{privacypass.SignedBatchWithProof{}, err})
return base64.StdEncoding.EncodeToString(errorresponse), ""
}
func ProcessResponse(response string, ctx Context) ([]*privacypass.Token, error) {
response = strings.ReplaceAll(response, "\n", "")
response = strings.ReplaceAll(response, "\r", "")
data, err := base64.StdEncoding.DecodeString(response)
if err == nil {
zcashTokenResponse := new(ZcashTokenResponse)
err = json.Unmarshal(data, zcashTokenResponse)
if err == nil {
if ctx.ct == nil {
if privacypass.UnblindSignedTokenBatch(ctx.tokens, ctx.blindedTokens, zcashTokenResponse.SignedTokens, ctx.Y, zcashTokenResponse.Proof, ctx.transcript) {
return ctx.tokens, nil
}
} else {
Ht := sha3.Sum512(ctx.ct.GetT())
T := new(ristretto255.Element).FromUniformBytes(Ht[:])
if privacypass.UnblindSignedTokenBatch(ctx.tokens, append(ctx.blindedTokens, privacypass.BlindedToken{T}), append(zcashTokenResponse.SignedTokens, privacypass.SignedToken{ctx.ct.W}), ctx.Y, zcashTokenResponse.Proof, ctx.transcript) {
return ctx.tokens, nil
}
}
err = errors.New("failed to unblind signed tokens")
}
}
return nil, err
}

80
api_test.go Normal file
View File

@ -0,0 +1,80 @@
package zcashtokenservice
import (
"cwtch.im/tapir/primitives/privacypass"
"encoding/base64"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"net/url"
"testing"
)
func TestRequestTokenURI(t *testing.T) {
log.SetLevel(log.LevelDebug)
tokenServer := privacypass.NewTokenServer()
ctx, uri := RequestTokenURI("zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", tokenServer.Y, "zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", nil)
// Test that UI is Well Formed
t.Logf("URI: %v", uri)
url, err := url.Parse(uri)
if err != nil {
t.Fatalf("Cannot process uri %v", err)
}
// Check that Request & Response are well formed
memo := url.Query().Get("memo")
if len(memo) > 512 {
t.Fatalf(" request memo is too long %v", len(memo))
}
memostr, _ := base64.StdEncoding.DecodeString(memo)
t.Logf("request memo %s length is %v", memostr, len(memo))
response, _ := ProcessRequest(memo, "zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", tokenServer)
responsestr, _ := base64.StdEncoding.DecodeString(response)
if len(response) > 512 {
t.Fatalf("response memo is too long %s %v", responsestr, len(response))
}
t.Logf("response memo length is %s %v", response, len(response))
tokens, err := ProcessResponse(response, ctx)
if err != nil {
t.Fatalf("Tokens were not signed: %v", err)
}
// Test Tokens are Valid
for _, token := range tokens {
err = tokenServer.SpendToken(token.SpendToken([]byte("test")), []byte("test"))
if err == nil {
t.Logf("Spent token %v Successfully", token)
} else {
t.Errorf("Failed to spend token: %v", err)
}
}
t.Logf("Testing with Constraint Token: %v", tokens[0])
ctx, uri = RequestTokenURI("zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", tokenServer.Y, "zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", tokens[0])
url, _ = url.Parse(uri)
memo = url.Query().Get("memo")
if len(memo) > 512 {
t.Fatalf(" request memo is too long %v", len(memo))
}
response, _ = ProcessRequest(memo, "zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f", tokenServer)
tokens, err = ProcessResponse(response, ctx)
if err != nil {
t.Errorf("Failed to unblind tokens: %v", err)
}
// Test Tokens are Valid
for _, token := range tokens {
err = tokenServer.SpendToken(token.SpendToken([]byte("test")), []byte("test"))
if err == nil {
t.Logf("Spent token %v Successfully with Constraint Token", token)
} else {
t.Logf("Failed to spend token")
}
}
}

38
client/client.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"bufio"
"fmt"
"git.openprivacy.ca/openprivacy/zcashtokenservice"
"github.com/gtank/ristretto255"
"os"
)
func main() {
// NOTE: If you want to run this prototype then you will need to edit all of these parameters to match
// your own setup.
clientZcashAddress := "zs1kfwveu7w2tl395lfql5rhrmewylk6fqp7386wjl3qc40rvxuak5gj450gafnztc62jdm6suy4ey"
tokenServiceZcashAddress := "zs1252tvdk394cph9pg28hpy45atzwf67xpctuedhtksac6d4a654x77akr022cx943rup5z7zsw8r"
publicKey := ristretto255.NewElement()
publicKey.UnmarshalText([]byte("IFS9viFSlzaYOpuuPAoYc8aUk7NKlgrjbVml5qjKJzE="))
ctx, uri := zcashtokenservice.RequestTokenURI(tokenServiceZcashAddress, publicKey, clientZcashAddress)
fmt.Printf("Please use a zcash wallet to have these tokens signed: %v", uri)
reader := bufio.NewReader(os.Stdin)
fmt.Print("\nPlease paste in the servers response: ")
response, _ := reader.ReadString('\n')
tokens, err := zcashtokenservice.ProcessResponse(response, ctx)
if err == nil {
fmt.Printf("\nSuccessfully signed tokens using Zcash!!:\n")
for _, token := range tokens {
fmt.Printf("Token: %x\n", token)
}
return
}
fmt.Printf("Error: %v", err)
}

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module git.openprivacy.ca/openprivacy/zcashtokenservice
go 1.13
require (
cwtch.im/tapir v0.1.13
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6
git.openprivacy.ca/openprivacy/zcashrpc v0.0.0-20191201220044-80615a955ce3
github.com/gtank/ristretto255 v0.1.1
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
)

72
go.sum Normal file
View File

@ -0,0 +1,72 @@
cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
cwtch.im/tapir v0.1.11 h1:JLm1MIYq4VXKzhj68+P8OuVPllAU9U6G0DtUor2fbc4=
cwtch.im/tapir v0.1.11/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
cwtch.im/tapir v0.1.12 h1:SUfaMlzIsLqNB+/1O3up/V4EKAAyynGPo2hixZYwsNo=
cwtch.im/tapir v0.1.12/go.mod h1:1fu4d+cMCepVaMm5vHrp4N/romdjKR+R6P8cvdRXYtQ=
cwtch.im/tapir v0.1.13 h1:gGAtFyRnxLrs1SQRAeVXiO5MG2DUUTE621aTQQEJN7U=
cwtch.im/tapir v0.1.13/go.mod h1:1fu4d+cMCepVaMm5vHrp4N/romdjKR+R6P8cvdRXYtQ=
cwtch.im/zcash2cwtch v0.0.0-20190914011329-5b125554715c h1:QwoUtw8QrZDiIwD2jr4q9GX8mug8bXrD+65+t0BEmEM=
cwtch.im/zcash2cwtch v0.0.0-20190914011329-5b125554715c/go.mod h1:kWpm1gL3HFfBUBf/3iLRapF72t3kQO2vY/dqq/PxrHA=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4 h1:GWLMJ5jBSIC/gFXzdbbeVz7fIAn2FTgW8+wBci6/3Ek=
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=
git.openprivacy.ca/openprivacy/zcashrpc v0.0.0-20191201220044-80615a955ce3 h1:D//38WC9EXDkcwZd/vPRSLqSdfVd4nQDN1KQBAcr4aw=
git.openprivacy.ca/openprivacy/zcashrpc v0.0.0-20191201220044-80615a955ce3/go.mod h1:gy826ODOMq16jtoxh6xB1YLqVrnRYLKf+8kBH/aWG9M=
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/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/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM=
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.1-0.20191011164322-af147e8e15b6 h1:fYrfnLiiWLCPvmmKbH8AlOwZAtrV0QDKox1HsAEjygY=
github.com/gtank/ristretto255 v0.1.1-0.20191011164322-af147e8e15b6/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/gtank/ristretto255 v0.1.1 h1:A+VVUhf73TS5HRfCnfMBqTBujkbwY3Fo8sRSFvL3cIg=
github.com/gtank/ristretto255 v0.1.1/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
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 h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
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/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 h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
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=

77
server/server.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"cwtch.im/tapir/persistence"
"cwtch.im/tapir/primitives/privacypass"
"encoding/base64"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/zcashrpc"
"git.openprivacy.ca/openprivacy/zcashtokenservice"
"io/ioutil"
"strings"
"time"
)
type ZcashConfig struct {
Username string `json: "username"`
Password string `json: "password"`
ZAddress string `json: "zaddress"`
}
type TapirServer struct {
tokenServer *privacypass.TokenServer
db persistence.Service
}
func StartTapirServer() *TapirServer {
ts := new(TapirServer)
ts.db = new(persistence.BoltPersistence)
ts.db.Open("tokens.db")
ts.tokenServer = privacypass.NewTokenServerFromStore(ts.db)
ts.db.Setup([]string{"transactions"})
return ts
}
func (ts *TapirServer) Refresh() {
for {
configFile, _ := ioutil.ReadFile("config.json")
config := ZcashConfig{}
_ = json.Unmarshal(configFile, &config)
zc := zcashrpc.NewLocalClient(config.Username, config.Password)
transactions, err := zc.ListReceivedTransactionsByAddress(config.ZAddress)
if err != nil {
log.Errorf("Error fetching zcash transactions: %v", err)
}
for _, transaction := range transactions {
exists, _ := ts.db.Check("transactions", transaction.TransactionID)
// Only process transactions we haven't seen before, and that meet the cost requirement
if !exists && transaction.Amount >= 0.1 {
log.Infof("Got a new transaction txid:%s, amount %f", transaction.TransactionID, transaction.Amount)
decodedMemo, _ := transaction.Memo.Decode()
decodedMemoStr := strings.Trim(string(decodedMemo), "\000")
response, returnAddress := zcashtokenservice.ProcessRequest(decodedMemoStr, config.ZAddress, ts.tokenServer)
if returnAddress != "" {
log.Infof("Sending Response: %v", response)
// We send a memo back with a very low memo cost (covered by the initial request)
zc.SendOne(config.ZAddress, returnAddress, response, 0.001)
} else {
error, err := base64.StdEncoding.DecodeString(response)
log.Infof("Error: %s %v", error, err)
}
ts.db.Persist("transactions", transaction.TransactionID, transaction)
}
}
time.Sleep(time.Second * 10)
}
}
func main() {
log.SetLevel(log.LevelDebug)
ts := StartTapirServer()
log.Infof("Token Server Public Key: %v", ts.tokenServer.Y)
ts.Refresh()
}