Merge branch 'init' of openprivacy/zcashtokenservice into master
This commit is contained in:
commit
227b4f4a01
|
@ -1,2 +1,3 @@
|
|||
config.json
|
||||
tokens.db
|
||||
vendor/
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue