forked from cwtch.im/tapir
Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
Dan Ballard | a3262d3478 | |
Dan Ballard | 65697b1efb | |
Dan Ballard | 883a5424c6 | |
Sarah Jamie Lewis | b2975e7224 | |
erinn | 89ba421cd9 | |
Sarah Jamie Lewis | c5e3837a5d | |
Sarah Jamie Lewis | bcaeb969e4 | |
Sarah Jamie Lewis | dcf0635f5f | |
Sarah Jamie Lewis | a0e58b8736 | |
Sarah Jamie Lewis | a19204caf4 | |
Sarah Jamie Lewis | ff7a32722d | |
Sarah Jamie Lewis | 345d11f506 | |
Sarah Jamie Lewis | 5b64c2d708 |
23
.drone.yml
23
.drone.yml
|
@ -4,6 +4,10 @@ workspace:
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
fetch:
|
fetch:
|
||||||
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
|
event: [ push, pull_request ]
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
|
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
|
||||||
|
@ -13,21 +17,33 @@ pipeline:
|
||||||
- go mod vendor
|
- go mod vendor
|
||||||
- go get -u golang.org/x/lint/golint
|
- go get -u golang.org/x/lint/golint
|
||||||
quality:
|
quality:
|
||||||
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
|
event: [ push, pull_request ]
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- go list ./... | xargs go vet
|
- go list ./... | xargs go vet
|
||||||
- go list ./... | xargs golint -set_exit_status
|
- go list ./... | xargs golint -set_exit_status
|
||||||
units-tests:
|
units-tests:
|
||||||
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
|
event: [ push, pull_request ]
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- export PATH=$PATH:/go/src/cwtch.im/tapir
|
- export PATH=$PATH:/go/src/cwtch.im/tapir
|
||||||
- sh testing/tests.sh
|
- sh testing/tests.sh
|
||||||
integ-test:
|
integ-test:
|
||||||
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
|
event: [ push, pull_request ]
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- ./tor -f ./torrc
|
- ./tor -f ./torrc
|
||||||
- sleep 15
|
- sleep 15
|
||||||
- go test -v cwtch.im/tapir/testing
|
- go test -race -v cwtch.im/tapir/testing
|
||||||
notify-email:
|
notify-email:
|
||||||
image: drillster/drone-email
|
image: drillster/drone-email
|
||||||
host: build.openprivacy.ca
|
host: build.openprivacy.ca
|
||||||
|
@ -35,10 +51,15 @@ pipeline:
|
||||||
skip_verify: true
|
skip_verify: true
|
||||||
from: drone@openprivacy.ca
|
from: drone@openprivacy.ca
|
||||||
when:
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
|
event: [ push, pull_request ]
|
||||||
status: [ failure ]
|
status: [ failure ]
|
||||||
notify-gogs:
|
notify-gogs:
|
||||||
image: openpriv/drone-gogs
|
image: openpriv/drone-gogs
|
||||||
when:
|
when:
|
||||||
|
repo: cwtch.im/tapir
|
||||||
|
branch: master
|
||||||
event: pull_request
|
event: pull_request
|
||||||
status: [ success, changed, failure ]
|
status: [ success, changed, failure ]
|
||||||
secrets: [gogs_account_token]
|
secrets: [gogs_account_token]
|
||||||
|
|
|
@ -5,3 +5,5 @@ coverage.out
|
||||||
/testing/tor/
|
/testing/tor/
|
||||||
/applications/tor/
|
/applications/tor/
|
||||||
*.db
|
*.db
|
||||||
|
/applications/tokenboard/tor/
|
||||||
|
fuzzing/
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (ea *AuthApp) Init(connection tapir.Connection) {
|
||||||
challengeRemote, _ := json.Marshal(remoteAuthMessage)
|
challengeRemote, _ := json.Marshal(remoteAuthMessage)
|
||||||
challengeLocal, _ := json.Marshal(authMessage)
|
challengeLocal, _ := json.Marshal(authMessage)
|
||||||
|
|
||||||
// Define canonical labels so both sides of the
|
// Define canonical labels so both sides of the connection can generate the same key
|
||||||
var outboundAuthMessage []byte
|
var outboundAuthMessage []byte
|
||||||
var outboundHostname string
|
var outboundHostname string
|
||||||
var inboundAuthMessage []byte
|
var inboundAuthMessage []byte
|
||||||
|
@ -96,14 +96,16 @@ func (ea *AuthApp) Init(connection tapir.Connection) {
|
||||||
|
|
||||||
// Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
|
// Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
|
||||||
// To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
|
// To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
|
||||||
|
// along with our hostname
|
||||||
// We expect the remote to do the same, and compare the two.
|
// We expect the remote to do the same, and compare the two.
|
||||||
// If successful we extend our auth capability to the connection and reassert the hostname.
|
// If successful we extend our auth capability to the connection and reassert the hostname.
|
||||||
// We note that the only successful scenario here requires that the remote peer have successfully derived the same
|
// We note that the only successful scenario here requires that the remote peer have successfully derived the same
|
||||||
// encryption key and the same transcript challenge.
|
// encryption key and the same transcript challenge.
|
||||||
connection.Send(challengeBytes)
|
connection.Send(append(challengeBytes, []byte(connection.ID().Hostname())...))
|
||||||
remoteChallenge := connection.Expect()
|
remoteChallenge := connection.Expect()
|
||||||
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
|
assertedHostname := utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||||
connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
if subtle.ConstantTimeCompare(append(challengeBytes, []byte(assertedHostname)...), remoteChallenge) == 1 {
|
||||||
|
connection.SetHostname(assertedHostname)
|
||||||
connection.SetCapability(AuthCapability)
|
connection.SetCapability(AuthCapability)
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core
|
||||||
solve := make([]byte, len(challenge)+32)
|
solve := make([]byte, len(challenge)+32)
|
||||||
for !solved {
|
for !solved {
|
||||||
|
|
||||||
solution = prng.Next().Bytes()
|
solution = prng.Next().Encode(nil)
|
||||||
|
|
||||||
copy(solve[0:], solution[:])
|
copy(solve[0:], solution[:])
|
||||||
copy(solve[len(solution):], challenge[:])
|
copy(solve[len(solution):], challenge[:])
|
||||||
|
|
|
@ -16,41 +16,49 @@ type TokenApplication struct {
|
||||||
|
|
||||||
// HasTokensCapability is granted once the client has obtained signed tokens
|
// HasTokensCapability is granted once the client has obtained signed tokens
|
||||||
const HasTokensCapability = tapir.Capability("HasTokensCapability")
|
const HasTokensCapability = tapir.Capability("HasTokensCapability")
|
||||||
|
const numTokens = 10
|
||||||
|
|
||||||
// NewInstance should always return a new instantiation of the application.
|
// NewInstance should always return a new instantiation of the application.
|
||||||
func (powapp *TokenApplication) NewInstance() tapir.Application {
|
func (tokenapp *TokenApplication) NewInstance() tapir.Application {
|
||||||
app := new(TokenApplication)
|
app := new(TokenApplication)
|
||||||
app.TokenService = powapp.TokenService
|
app.TokenService = tokenapp.TokenService
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init is run when the connection is first started.
|
// Init is run when the connection is first started.
|
||||||
func (powapp *TokenApplication) Init(connection tapir.Connection) {
|
func (tokenapp *TokenApplication) Init(connection tapir.Connection) {
|
||||||
powapp.Transcript().NewProtocol("token-app")
|
tokenapp.Transcript().NewProtocol("token-app")
|
||||||
|
log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit())
|
||||||
if connection.IsOutbound() {
|
if connection.IsOutbound() {
|
||||||
tokens, blinded := privacypass.GenerateBlindedTokenBatch(10)
|
tokens, blinded := privacypass.GenerateBlindedTokenBatch(numTokens)
|
||||||
data, _ := json.Marshal(blinded)
|
data, _ := json.Marshal(blinded)
|
||||||
connection.Send(data)
|
connection.Send(data)
|
||||||
var signedBatch privacypass.SignedBatchWithProof
|
var signedBatch privacypass.SignedBatchWithProof
|
||||||
err := json.Unmarshal(connection.Expect(), &signedBatch)
|
err := json.Unmarshal(connection.Expect(), &signedBatch)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, powapp.TokenService.Y, signedBatch.Proof, powapp.Transcript())
|
verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, tokenapp.TokenService.Y, signedBatch.Proof, tokenapp.Transcript())
|
||||||
if verified {
|
if verified {
|
||||||
log.Debugf("Successfully obtained signed tokens")
|
log.Debugf("Successfully obtained signed tokens")
|
||||||
powapp.Tokens = tokens
|
tokenapp.Tokens = tokens
|
||||||
connection.SetCapability(HasTokensCapability)
|
connection.SetCapability(HasTokensCapability)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// This will close the connection by default and no tokens will be available.
|
||||||
|
// This usecase can be checked by the existing WaitForCapabilityOrClose() function using the HasTokensCapability
|
||||||
|
// If the connection closes without the HasTokensCapability then the error can be handled by whatever client needs it
|
||||||
log.Debugf("Failed to verify signed token batch")
|
log.Debugf("Failed to verify signed token batch")
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
var blinded []privacypass.BlindedToken
|
}
|
||||||
err := json.Unmarshal(connection.Expect(), &blinded)
|
|
||||||
if err == nil {
|
// We are the server
|
||||||
batchProof := powapp.TokenService.SignBlindedTokenBatch(blinded, powapp.Transcript())
|
var blinded []privacypass.BlindedToken
|
||||||
data, _ := json.Marshal(batchProof)
|
err := json.Unmarshal(connection.Expect(), &blinded)
|
||||||
connection.Send(data)
|
if err == nil {
|
||||||
return
|
batchProof := tokenapp.TokenService.SignBlindedTokenBatch(blinded, tokenapp.Transcript())
|
||||||
}
|
log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit())
|
||||||
|
data, _ := json.Marshal(batchProof)
|
||||||
|
connection.Send(data)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,6 @@ func (ta *Client) Listen() {
|
||||||
|
|
||||||
var message Message
|
var message Message
|
||||||
json.Unmarshal(data, &message)
|
json.Unmarshal(data, &message)
|
||||||
log.Debugf("Received a Message: %v", message)
|
|
||||||
switch message.MessageType {
|
switch message.MessageType {
|
||||||
case postResultMessage:
|
case postResultMessage:
|
||||||
log.Debugf("Post result: %x", message.PostResult.Proof)
|
log.Debugf("Post result: %x", message.PostResult.Proof)
|
||||||
|
@ -101,7 +100,7 @@ func (ta *Client) PurchaseTokens() {
|
||||||
|
|
||||||
// Post sends a Post Request to the server
|
// Post sends a Post Request to the server
|
||||||
func (ta *Client) Post(message auditable.Message) bool {
|
func (ta *Client) Post(message auditable.Message) bool {
|
||||||
token, err := ta.paymentHandler.NextToken(message)
|
token, err := ta.paymentHandler.NextToken(message, ta.connection.Hostname())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
data, _ := json.Marshal(Message{MessageType: postRequestMessage, PostRequest: postRequest{Token: token, Message: message}})
|
data, _ := json.Marshal(Message{MessageType: postRequestMessage, PostRequest: postRequest{Token: token, Message: message}})
|
||||||
ta.connection.Send(data)
|
ta.connection.Send(data)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package tokenboard
|
package tokenboard
|
||||||
|
|
||||||
|
// NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/tapir"
|
"cwtch.im/tapir"
|
||||||
"cwtch.im/tapir/applications"
|
"cwtch.im/tapir/applications"
|
||||||
|
@ -55,7 +57,7 @@ func (ta *Server) Listen() {
|
||||||
|
|
||||||
var message Message
|
var message Message
|
||||||
json.Unmarshal(data, &message)
|
json.Unmarshal(data, &message)
|
||||||
log.Debugf("Received a Message: %v", message)
|
|
||||||
switch message.MessageType {
|
switch message.MessageType {
|
||||||
case postRequestMessage:
|
case postRequestMessage:
|
||||||
postrequest := message.PostRequest
|
postrequest := message.PostRequest
|
||||||
|
@ -77,7 +79,7 @@ func (ta *Server) Listen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ta *Server) postMessageRequest(token privacypass.SpentToken, message auditable.Message) {
|
func (ta *Server) postMessageRequest(token privacypass.SpentToken, message auditable.Message) {
|
||||||
if err := ta.TokenService.SpendToken(token, message); err == nil {
|
if err := ta.TokenService.SpendToken(token, append(message, ta.connection.ID().Hostname()...)); err == nil {
|
||||||
log.Debugf("Token is valid")
|
log.Debugf("Token is valid")
|
||||||
signedproof := ta.AuditableStore.Add(message)
|
signedproof := ta.AuditableStore.Add(message)
|
||||||
data, _ := json.Marshal(Message{MessageType: postResultMessage, PostResult: postResult{true, signedproof}})
|
data, _ := json.Marshal(Message{MessageType: postResultMessage, PostResult: postResult{true, signedproof}})
|
||||||
|
|
|
@ -47,21 +47,24 @@ func (fph *FreePaymentHandler) MakePayment() {
|
||||||
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||||
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||||
client.Connect(fph.ServerHostname, powTokenApp)
|
client.Connect(fph.ServerHostname, powTokenApp)
|
||||||
client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability)
|
conn, err := client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability)
|
||||||
conn, _ := client.GetConnection(fph.ServerHostname)
|
if err == nil {
|
||||||
powtapp, _ := conn.App().(*applications.TokenApplication)
|
powtapp, _ := conn.App().(*applications.TokenApplication)
|
||||||
fph.tokens = append(fph.tokens, powtapp.Tokens...)
|
fph.tokens = append(fph.tokens, powtapp.Tokens...)
|
||||||
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
|
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("Error making payment: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fph *FreePaymentHandler) NextToken(data []byte) (privacypass.SpentToken, error) {
|
func (fph *FreePaymentHandler) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) {
|
||||||
if len(fph.tokens) == 0 {
|
if len(fph.tokens) == 0 {
|
||||||
return privacypass.SpentToken{}, errors.New("No more tokens")
|
return privacypass.SpentToken{}, errors.New("No more tokens")
|
||||||
}
|
}
|
||||||
token := fph.tokens[0]
|
token := fph.tokens[0]
|
||||||
fph.tokens = fph.tokens[1:]
|
fph.tokens = fph.tokens[1:]
|
||||||
return token.SpendToken(data), nil
|
return token.SpendToken(append(data, hostname...)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenBoardApp(t *testing.T) {
|
func TestTokenBoardApp(t *testing.T) {
|
||||||
|
@ -94,7 +97,7 @@ func TestTokenBoardApp(t *testing.T) {
|
||||||
sg := new(sync.WaitGroup)
|
sg := new(sync.WaitGroup)
|
||||||
sg.Add(1)
|
sg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
service.Listen(NewTokenBoardServer(&tokenService, serverAuditableStore))
|
service.Listen(NewTokenBoardServer(tokenService, serverAuditableStore))
|
||||||
sg.Done()
|
sg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -106,7 +109,7 @@ func TestTokenBoardApp(t *testing.T) {
|
||||||
sg.Add(1)
|
sg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
tokenApplication := new(applications.TokenApplication)
|
tokenApplication := new(applications.TokenApplication)
|
||||||
tokenApplication.TokenService = &tokenService
|
tokenApplication.TokenService = tokenService
|
||||||
powTokenApp := new(applications.ApplicationChain).
|
powTokenApp := new(applications.ApplicationChain).
|
||||||
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||||
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||||
|
@ -114,12 +117,12 @@ func TestTokenBoardApp(t *testing.T) {
|
||||||
sg.Done()
|
sg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(time.Second * 30) // wait for server to initialize
|
time.Sleep(time.Second * 60) // wait for server to initialize
|
||||||
id, sk := primitives.InitializeEphemeralIdentity()
|
id, sk := primitives.InitializeEphemeralIdentity()
|
||||||
var client tapir.Service
|
var client tapir.Service
|
||||||
client = new(tor.BaseOnionService)
|
client = new(tor.BaseOnionService)
|
||||||
client.Init(acn, sk, &id)
|
client.Init(acn, sk, &id)
|
||||||
client.Connect(sid.Hostname(), NewTokenBoardClient(clientAuditableStore, Handler{Store: clientAuditableStore}, &FreePaymentHandler{ACN: acn, TokenService: &tokenService, ServerHostname: spowid.Hostname()}))
|
client.Connect(sid.Hostname(), NewTokenBoardClient(clientAuditableStore, Handler{Store: clientAuditableStore}, &FreePaymentHandler{ACN: acn, TokenService: tokenService, ServerHostname: spowid.Hostname()}))
|
||||||
client.WaitForCapabilityOrClose(sid.Hostname(), applications.AuthCapability)
|
client.WaitForCapabilityOrClose(sid.Hostname(), applications.AuthCapability)
|
||||||
conn, _ := client.GetConnection(sid.Hostname())
|
conn, _ := client.GetConnection(sid.Hostname())
|
||||||
tba, _ := conn.App().(*Client)
|
tba, _ := conn.App().(*Client)
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -2,5 +2,11 @@ module cwtch.im/tapir
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4
|
||||||
|
github.com/gtank/merlin v0.1.1
|
||||||
|
github.com/gtank/ristretto255 v0.1.2
|
||||||
|
go.etcd.io/bbolt v1.3.3
|
||||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
11
go.sum
11
go.sum
|
@ -8,11 +8,19 @@ 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
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.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
|
||||||
|
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
|
||||||
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
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-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-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 h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||||
|
@ -20,8 +28,9 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
|
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-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 h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
@ -54,7 +54,6 @@ func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name tapir.Capab
|
||||||
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
|
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
|
||||||
var conn tapir.Connection
|
var conn tapir.Connection
|
||||||
s.connections.Range(func(key, value interface{}) bool {
|
s.connections.Range(func(key, value interface{}) bool {
|
||||||
log.Debugf("Checking %v", key)
|
|
||||||
connection := value.(tapir.Connection)
|
connection := value.(tapir.Connection)
|
||||||
if connection.Hostname() == hostname {
|
if connection.Hostname() == hostname {
|
||||||
if !connection.IsClosed() {
|
if !connection.IsClosed() {
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (bp *BoltPersistence) Check(bucket string, name string) (bool, error) {
|
||||||
val = b.Get([]byte(name))
|
val = b.Get([]byte(name))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if val != nil {
|
} else if val != nil {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package auditable
|
package auditable
|
||||||
|
|
||||||
|
// WARNING NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/tapir/persistence"
|
"cwtch.im/tapir/persistence"
|
||||||
"cwtch.im/tapir/primitives"
|
"cwtch.im/tapir/primitives"
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package primitives
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BloomFilter implements a bloom filter
|
|
||||||
type BloomFilter struct {
|
|
||||||
B []bool
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init constructs a bloom filter of size m
|
|
||||||
func (bf *BloomFilter) Init(m int64) {
|
|
||||||
bf.B = make([]bool, m)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash transforms a message to a set of bit flips
|
|
||||||
func (bf *BloomFilter) Hash(msg []byte) []int {
|
|
||||||
|
|
||||||
// Not the fastest hash function ever, but cryptographic security is more important than speed.
|
|
||||||
hash1 := sha256.Sum256(append([]byte("h1"), msg...))
|
|
||||||
hash2 := sha256.Sum256(append([]byte("h2"), msg...))
|
|
||||||
hash3 := sha256.Sum256(append([]byte("h3"), msg...))
|
|
||||||
hash4 := sha256.Sum256(append([]byte("h4"), msg...))
|
|
||||||
|
|
||||||
m := int64(len(bf.B))
|
|
||||||
// Number of bytes needed to pick a position from [0,m)
|
|
||||||
B := int(math.Ceil(math.Log2(float64(m)) / 8.0))
|
|
||||||
|
|
||||||
p1 := big.NewInt(0).SetBytes(hash1[:B]).Int64()
|
|
||||||
p2 := big.NewInt(0).SetBytes(hash2[:B]).Int64()
|
|
||||||
p3 := big.NewInt(0).SetBytes(hash3[:B]).Int64()
|
|
||||||
p4 := big.NewInt(0).SetBytes(hash4[:B]).Int64()
|
|
||||||
|
|
||||||
return []int{int(p1), int(p2), int(p3), int(p4)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert updates the BloomFilter (suitable for concurrent use)
|
|
||||||
func (bf *BloomFilter) Insert(msg []byte) {
|
|
||||||
pos := bf.Hash(msg)
|
|
||||||
bf.lock.Lock()
|
|
||||||
defer bf.lock.Unlock()
|
|
||||||
bf.B[pos[0]] = true
|
|
||||||
bf.B[pos[1]] = true
|
|
||||||
bf.B[pos[2]] = true
|
|
||||||
bf.B[pos[3]] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check returns true if the messages might be in the BloomFilter
|
|
||||||
// (No false positives, possible false negatives due to the probabilistic nature of the filter)
|
|
||||||
func (bf *BloomFilter) Check(msg []byte) bool {
|
|
||||||
pos := bf.Hash(msg)
|
|
||||||
if bf.B[pos[0]] && bf.B[pos[1]] && bf.B[pos[2]] && bf.B[pos[3]] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package primitives
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBloomFilter_Insert(t *testing.T) {
|
|
||||||
bf := new(BloomFilter)
|
|
||||||
bf.Init(256)
|
|
||||||
|
|
||||||
fp := 0
|
|
||||||
for i := 0; i < 256; i++ {
|
|
||||||
input := []byte("test" + strconv.Itoa(256+i))
|
|
||||||
if bf.Check(input) {
|
|
||||||
t.Log("False Positive!")
|
|
||||||
fp++
|
|
||||||
}
|
|
||||||
bf.Insert(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Num false positives %v %v%%", fp, (float64(fp)/256.0)*100)
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,32 +2,28 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bwesterb/go-ristretto"
|
"github.com/gtank/merlin"
|
||||||
|
ristretto "github.com/gtank/ristretto255"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
"hash"
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transcript implements a transcript of a public coin argument.
|
// Transcript provides a consistent transcript primitive for our protocols
|
||||||
//
|
//
|
||||||
// We have the following goals:
|
// We have the following goals:
|
||||||
// - Provide a consisted transcript API for our zero knowledge protocols
|
|
||||||
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
|
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
|
||||||
// - produce an auditable human-readable transcript.
|
// - be able to produce a human-readable transcript for auditing.
|
||||||
//
|
//
|
||||||
// The design of this API was inspired by Merlin: https://docs.rs/crate/merlin/
|
// The design of this API was inspired by Merlin: https://docs.rs/crate/merlin/
|
||||||
//
|
|
||||||
// At some point we might want to extend this to be compatible with Merlin transcripts, built on STROBE
|
|
||||||
type Transcript struct {
|
type Transcript struct {
|
||||||
hash hash.Hash
|
merlinTranscript *merlin.Transcript
|
||||||
transcript string
|
transcript string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTranscript creates a new Transcript with the given Label, the label should be unique to the application
|
// NewTranscript creates a new Transcript with the given Label, the label should be unique to the application
|
||||||
func NewTranscript(label string) *Transcript {
|
func NewTranscript(label string) *Transcript {
|
||||||
transcript := new(Transcript)
|
transcript := new(Transcript)
|
||||||
transcript.hash = sha3.New256()
|
transcript.merlinTranscript = merlin.NewTranscript(label)
|
||||||
transcript.AddToTranscript("protocol", []byte(label))
|
|
||||||
return transcript
|
return transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +32,13 @@ func NewTranscript(label string) *Transcript {
|
||||||
func (t *Transcript) AddToTranscript(label string, b []byte) {
|
func (t *Transcript) AddToTranscript(label string, b []byte) {
|
||||||
op := fmt.Sprintf("%s (%d) %x;", label, len(b), b)
|
op := fmt.Sprintf("%s (%d) %x;", label, len(b), b)
|
||||||
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
|
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
|
||||||
t.hash.Write([]byte(op))
|
t.merlinTranscript.AppendMessage([]byte(label), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddElementToTranscript appends a value to the transcript with the given label
|
||||||
|
// This binds the given data to the label.
|
||||||
|
func (t *Transcript) AddElementToTranscript(label string, element *ristretto.Element) {
|
||||||
|
t.AddToTranscript(label, element.Encode([]byte{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
|
// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
|
||||||
|
@ -49,14 +51,13 @@ func (t Transcript) OutputTranscriptToAudit() string {
|
||||||
func (t *Transcript) NewProtocol(label string) {
|
func (t *Transcript) NewProtocol(label string) {
|
||||||
op := fmt.Sprintf("---- new-protcol: %s ----", label)
|
op := fmt.Sprintf("---- new-protcol: %s ----", label)
|
||||||
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
|
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
|
||||||
t.hash.Write([]byte(op))
|
t.merlinTranscript.AppendMessage([]byte("protocol"), []byte(label))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
|
// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
|
||||||
func (t *Transcript) CommitToTranscript(label string) []byte {
|
func (t *Transcript) CommitToTranscript(label string) []byte {
|
||||||
t.AddToTranscript("commit", []byte(label))
|
b := t.merlinTranscript.ExtractBytes([]byte(label), 64)
|
||||||
b := t.hash.Sum([]byte{})
|
t.transcript = fmt.Sprintf("%v\nextract %v: %v", t.transcript, label, b)
|
||||||
t.AddToTranscript(label, b)
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,25 +68,39 @@ type PRNG struct {
|
||||||
|
|
||||||
// Next returns the next "random" scalar from the PRNG
|
// Next returns the next "random" scalar from the PRNG
|
||||||
func (prng *PRNG) Next() *ristretto.Scalar {
|
func (prng *PRNG) Next() *ristretto.Scalar {
|
||||||
buf := [32]byte{}
|
buf := [64]byte{}
|
||||||
io.ReadFull(prng.prng, buf[:])
|
io.ReadFull(prng.prng, buf[:])
|
||||||
return new(ristretto.Scalar).SetBytes(&buf)
|
next := new(ristretto.Scalar)
|
||||||
|
next.FromUniformBytes(buf[:])
|
||||||
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitToPRNG commits the label to the transcript and derives a PRNG from the transcript.
|
// CommitToPRNG commits the label to the transcript and derives a PRNG from the transcript.
|
||||||
func (t *Transcript) CommitToPRNG(label string) PRNG {
|
func (t *Transcript) CommitToPRNG(label string) PRNG {
|
||||||
t.AddToTranscript("commit-prng", []byte(label))
|
b := t.merlinTranscript.ExtractBytes([]byte(label), 64)
|
||||||
b := t.hash.Sum([]byte{})
|
|
||||||
t.AddToTranscript(label, b)
|
|
||||||
prng := sha3.NewShake256()
|
prng := sha3.NewShake256()
|
||||||
prng.Write(b)
|
prng.Write(b)
|
||||||
return PRNG{prng: prng}
|
return PRNG{prng: prng}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitToGenerator derives a verifiably random generator from the transcript
|
||||||
|
func (t *Transcript) CommitToGenerator(label string) *ristretto.Element {
|
||||||
|
c := t.CommitToTranscript(label)
|
||||||
|
return new(ristretto.Element).FromUniformBytes(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitToGenerators derives a set of verifiably random generators from the transcript
|
||||||
|
func (t *Transcript) CommitToGenerators(label string, n int) (generators []*ristretto.Element) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
generators = append(generators, t.CommitToGenerator(fmt.Sprintf("%v-%d", label, i)))
|
||||||
|
}
|
||||||
|
return generators
|
||||||
|
}
|
||||||
|
|
||||||
// CommitToTranscriptScalar is a convenience method for CommitToTranscript which returns a ristretto Scalar
|
// CommitToTranscriptScalar is a convenience method for CommitToTranscript which returns a ristretto Scalar
|
||||||
func (t *Transcript) CommitToTranscriptScalar(label string) *ristretto.Scalar {
|
func (t *Transcript) CommitToTranscriptScalar(label string) *ristretto.Scalar {
|
||||||
c := t.CommitToTranscript(label)
|
c := t.CommitToTranscript(label)
|
||||||
cs := [32]byte{}
|
s := new(ristretto.Scalar)
|
||||||
copy(cs[:], c[:])
|
s.FromUniformBytes(c[:])
|
||||||
return new(ristretto.Scalar).SetBytes(&cs)
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package primitives
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cwtch.im/tapir/primitives/core"
|
|
||||||
"github.com/bwesterb/go-ristretto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DLProof Encapsulates a Discrete Log / Schnorr Proof
|
|
||||||
// Note that these parameters are read-only.
|
|
||||||
type DLProof struct {
|
|
||||||
V, A ristretto.Point
|
|
||||||
R ristretto.Scalar
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscreteLogProof - Proof of Knowledge of Exponent
|
|
||||||
// Given V = xG
|
|
||||||
// Peggy: z := choose randomly from Zq
|
|
||||||
// A := zG
|
|
||||||
// c := H(transcript(G,V,A)) mod q
|
|
||||||
// r := (z + cx) mod q
|
|
||||||
//
|
|
||||||
// Sends A,r,V to Vicky
|
|
||||||
func DiscreteLogProof(x ristretto.Scalar, v ristretto.Point, transcript *core.Transcript) (proof DLProof) {
|
|
||||||
|
|
||||||
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes())
|
|
||||||
|
|
||||||
// We bind the proof to our public V
|
|
||||||
proof.V = v
|
|
||||||
transcript.AddToTranscript("V", proof.V.Bytes())
|
|
||||||
|
|
||||||
// Generate a random z
|
|
||||||
// A := zG
|
|
||||||
z := new(ristretto.Scalar).Rand()
|
|
||||||
proof.A = *new(ristretto.Point).ScalarMultBase(z)
|
|
||||||
transcript.AddToTranscript("A", proof.A.Bytes())
|
|
||||||
|
|
||||||
// Derive Challenge
|
|
||||||
c := transcript.CommitToTranscriptScalar("c")
|
|
||||||
|
|
||||||
// r := (z + cx) mod p
|
|
||||||
cx := new(ristretto.Scalar).Mul(c, &x)
|
|
||||||
proof.R = *new(ristretto.Scalar).Add(z, cx)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDiscreteLogProof validates a given Schnorr Proof
|
|
||||||
// Vicky gets A,r,V from Peggy
|
|
||||||
// Vicky computes c := H(transcript(G,V,A)) mod q
|
|
||||||
// Vicky checks rG := A + cV
|
|
||||||
// rG ?= zG + cV
|
|
||||||
// (z+cx)G ?= zG + cV
|
|
||||||
// ?= zG + cxG
|
|
||||||
// Thus demonstrating that Peggy knows the discrete log to V
|
|
||||||
func VerifyDiscreteLogProof(proof DLProof, transcript *core.Transcript) bool {
|
|
||||||
|
|
||||||
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes())
|
|
||||||
transcript.AddToTranscript("V", proof.V.Bytes())
|
|
||||||
transcript.AddToTranscript("A", proof.A.Bytes())
|
|
||||||
c := transcript.CommitToTranscriptScalar("c")
|
|
||||||
|
|
||||||
// Compute left hand side
|
|
||||||
lhs := new(ristretto.Point).ScalarMultBase(&proof.R)
|
|
||||||
|
|
||||||
// Compute right hand side
|
|
||||||
cV := new(ristretto.Point).ScalarMult(&proof.V, c)
|
|
||||||
rhs := new(ristretto.Point).Add(&proof.A, cV)
|
|
||||||
|
|
||||||
// Result of verification: lhs ?= rhs
|
|
||||||
return lhs.Equals(rhs)
|
|
||||||
}
|
|
|
@ -1,19 +1,20 @@
|
||||||
package privacypass
|
package privacypass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"cwtch.im/tapir/primitives/core"
|
"cwtch.im/tapir/primitives/core"
|
||||||
"github.com/bwesterb/go-ristretto"
|
ristretto "github.com/gtank/ristretto255"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
|
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
|
||||||
// David Chaum and Torben P. Pedersen. Wallet databaseswith observers. In Ernest F. Brickell, editor,CRYPTO’92,volume 740 ofLNCS, pages 89–105. Springer, Heidelberg,August 1993
|
//gut In Ernest F. Brickell, editor,CRYPTO’92,volume 740 ofLNCS, pages 89–105. Springer, Heidelberg,August 1993
|
||||||
type DLEQProof struct {
|
type DLEQProof struct {
|
||||||
C *ristretto.Scalar
|
C *ristretto.Scalar
|
||||||
S *ristretto.Scalar
|
S *ristretto.Scalar
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript
|
// DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript
|
||||||
// Given P = kX, Q = kP, Y=kX
|
// Given Y = kX & Q = kP
|
||||||
// Peggy: t := choose randomly from Zq
|
// Peggy: t := choose randomly from Zq
|
||||||
// A := tX
|
// A := tX
|
||||||
// B := tP
|
// B := tP
|
||||||
|
@ -21,49 +22,52 @@ type DLEQProof struct {
|
||||||
// s := (t + ck) mod q
|
// s := (t + ck) mod q
|
||||||
//
|
//
|
||||||
// Sends c,s to Vicky
|
// Sends c,s to Vicky
|
||||||
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) DLEQProof {
|
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Element, Y *ristretto.Element, P *ristretto.Element, Q *ristretto.Element, transcript *core.Transcript) DLEQProof {
|
||||||
t := new(ristretto.Scalar).Rand()
|
private := make([]byte, 64)
|
||||||
A := new(ristretto.Point).ScalarMult(X, t)
|
rand.Read(private)
|
||||||
B := new(ristretto.Point).ScalarMult(P, t)
|
t := new(ristretto.Scalar)
|
||||||
|
t.FromUniformBytes(private)
|
||||||
|
A := new(ristretto.Element).ScalarMult(t, X)
|
||||||
|
B := new(ristretto.Element).ScalarMult(t, P)
|
||||||
|
|
||||||
transcript.AddToTranscript(DLEQX, X.Bytes())
|
transcript.AddToTranscript(DLEQX, X.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQY, Y.Bytes())
|
transcript.AddToTranscript(DLEQY, Y.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQP, P.Bytes())
|
transcript.AddToTranscript(DLEQP, P.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQQ, Q.Bytes())
|
transcript.AddToTranscript(DLEQQ, Q.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQA, A.Bytes())
|
transcript.AddToTranscript(DLEQA, A.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQB, B.Bytes())
|
transcript.AddToTranscript(DLEQB, B.Encode(nil))
|
||||||
|
|
||||||
c := transcript.CommitToTranscriptScalar("c")
|
c := transcript.CommitToTranscriptScalar("c")
|
||||||
s := new(ristretto.Scalar).Sub(t, new(ristretto.Scalar).Mul(c, k))
|
s := new(ristretto.Scalar).Subtract(t, new(ristretto.Scalar).Multiply(c, k))
|
||||||
return DLEQProof{c, s}
|
return DLEQProof{c, s}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyDiscreteLogEquivalenceProof verifies the DLEQ for the given parameters and transcript
|
// VerifyDiscreteLogEquivalenceProof verifies the DLEQ for the given parameters and transcript
|
||||||
// Given P = kX, Q = kP, Y=kX, and Proof = (c,s)
|
// Given Y = kX & Q = kP and Proof = (c,s)
|
||||||
// Vicky: X' := sX
|
// Vicky: X' := sX
|
||||||
// Y' := cY
|
// Y' := cY
|
||||||
// P' := sP
|
// P' := sP
|
||||||
// Q' := cQ
|
// Q' := cQ
|
||||||
// A' = X'+Y' == sX + cY ?= sX + ckX == (s+ck)X == tX == A
|
// A' = X'+Y' == sX + cY ?= sG + ckG == (s+ck)X == tX == A
|
||||||
// B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B
|
// B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B
|
||||||
// c' := H(transcript(X,Y,P,Q,A',B'))
|
// c' := H(transcript(X,Y,P,Q,A',B'))
|
||||||
// Tests c ?= c
|
// Tests c ?= c
|
||||||
func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) bool {
|
func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Element, Y *ristretto.Element, P *ristretto.Element, Q *ristretto.Element, transcript *core.Transcript) bool {
|
||||||
|
|
||||||
Xs := new(ristretto.Point).ScalarMult(X, dleq.S)
|
Xs := new(ristretto.Element).ScalarMult(dleq.S, X)
|
||||||
Yc := new(ristretto.Point).ScalarMult(Y, dleq.C)
|
Yc := new(ristretto.Element).ScalarMult(dleq.C, Y)
|
||||||
Ps := new(ristretto.Point).ScalarMult(P, dleq.S)
|
Ps := new(ristretto.Element).ScalarMult(dleq.S, P)
|
||||||
Qc := new(ristretto.Point).ScalarMult(Q, dleq.C)
|
Qc := new(ristretto.Element).ScalarMult(dleq.C, Q)
|
||||||
|
|
||||||
A := new(ristretto.Point).Add(Xs, Yc)
|
A := new(ristretto.Element).Add(Xs, Yc)
|
||||||
B := new(ristretto.Point).Add(Ps, Qc)
|
B := new(ristretto.Element).Add(Ps, Qc)
|
||||||
|
|
||||||
transcript.AddToTranscript(DLEQX, X.Bytes())
|
transcript.AddToTranscript(DLEQX, X.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQY, Y.Bytes())
|
transcript.AddToTranscript(DLEQY, Y.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQP, P.Bytes())
|
transcript.AddToTranscript(DLEQP, P.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQQ, Q.Bytes())
|
transcript.AddToTranscript(DLEQQ, Q.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQA, A.Bytes())
|
transcript.AddToTranscript(DLEQA, A.Encode(nil))
|
||||||
transcript.AddToTranscript(DLEQB, B.Bytes())
|
transcript.AddToTranscript(DLEQB, B.Encode(nil))
|
||||||
|
|
||||||
return transcript.CommitToTranscriptScalar("c").Equals(dleq.C)
|
return transcript.CommitToTranscriptScalar("c").Equal(dleq.C) == 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"cwtch.im/tapir/primitives/core"
|
"cwtch.im/tapir/primitives/core"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
"github.com/bwesterb/go-ristretto"
|
ristretto "github.com/gtank/ristretto255"
|
||||||
|
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,17 +16,22 @@ import (
|
||||||
type Token struct {
|
type Token struct {
|
||||||
t []byte
|
t []byte
|
||||||
r *ristretto.Scalar
|
r *ristretto.Scalar
|
||||||
W *ristretto.Point
|
W *ristretto.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetT returns the underlying bytes for token for use in constraint proofs.
|
||||||
|
func (t Token) GetT() []byte {
|
||||||
|
return t.t
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlindedToken encapsulates a Blinded Token
|
// BlindedToken encapsulates a Blinded Token
|
||||||
type BlindedToken struct {
|
type BlindedToken struct {
|
||||||
P *ristretto.Point
|
P *ristretto.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignedToken encapsulates a Signed (Blinded) Token
|
// SignedToken encapsulates a Signed (Blinded) Token
|
||||||
type SignedToken struct {
|
type SignedToken struct {
|
||||||
Q *ristretto.Point
|
Q *ristretto.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpentToken encapsulates the parameters needed to spend a Token
|
// SpentToken encapsulates the parameters needed to spend a Token
|
||||||
|
@ -37,7 +43,8 @@ type SpentToken struct {
|
||||||
// TokenPaymentHandler defines an interface with external payment processors
|
// TokenPaymentHandler defines an interface with external payment processors
|
||||||
type TokenPaymentHandler interface {
|
type TokenPaymentHandler interface {
|
||||||
MakePayment()
|
MakePayment()
|
||||||
NextToken(data []byte) (SpentToken, error)
|
// Next Token
|
||||||
|
NextToken(data []byte, hostname string) (SpentToken, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenBlindedToken initializes the Token
|
// GenBlindedToken initializes the Token
|
||||||
|
@ -45,23 +52,25 @@ type TokenPaymentHandler interface {
|
||||||
func (t *Token) GenBlindedToken() BlindedToken {
|
func (t *Token) GenBlindedToken() BlindedToken {
|
||||||
t.t = make([]byte, 32)
|
t.t = make([]byte, 32)
|
||||||
rand.Read(t.t)
|
rand.Read(t.t)
|
||||||
t.r = new(ristretto.Scalar).Rand()
|
t.r = new(ristretto.Scalar)
|
||||||
|
b := make([]byte, 64)
|
||||||
|
rand.Read(b)
|
||||||
|
t.r.FromUniformBytes(b)
|
||||||
|
|
||||||
Ht := sha3.Sum256(t.t)
|
Ht := sha3.Sum512(t.t)
|
||||||
log.Debugf("token: %x", Ht)
|
T := new(ristretto.Element).FromUniformBytes(Ht[:])
|
||||||
T := new(ristretto.Point).SetElligator(&Ht)
|
P := new(ristretto.Element).ScalarMult(t.r, T)
|
||||||
P := new(ristretto.Point).ScalarMult(T, t.r)
|
|
||||||
return BlindedToken{P}
|
return BlindedToken{P}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unblindSignedToken unblinds a token that has been signed by a server
|
// unblindSignedToken unblinds a token that has been signed by a server
|
||||||
func (t *Token) unblindSignedToken(token SignedToken) {
|
func (t *Token) unblindSignedToken(token SignedToken) {
|
||||||
t.W = new(ristretto.Point).ScalarMult(token.Q, new(ristretto.Scalar).Inverse(t.r))
|
t.W = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(t.r), token.Q)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpendToken binds the token with data and then redeems the token
|
// SpendToken binds the token with data and then redeems the token
|
||||||
func (t *Token) SpendToken(data []byte) SpentToken {
|
func (t *Token) SpendToken(data []byte) SpentToken {
|
||||||
key := sha3.Sum256(append(t.t, t.W.Bytes()...))
|
key := sha3.Sum256(append(t.t, t.W.Encode(nil)...))
|
||||||
mac := hmac.New(sha3.New512, key[:])
|
mac := hmac.New(sha3.New512, key[:])
|
||||||
return SpentToken{t.t, mac.Sum(data)}
|
return SpentToken{t.t, mac.Sum(data)}
|
||||||
}
|
}
|
||||||
|
@ -76,28 +85,29 @@ func GenerateBlindedTokenBatch(num int) (tokens []*Token, blindedTokens []Blinde
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyBatchProof verifies a given batch proof (see also UnblindSignedTokenBatch)
|
// verifyBatchProof verifies a given batch proof (see also UnblindSignedTokenBatch)
|
||||||
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Point, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
|
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Element, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
|
||||||
transcript.NewProtocol(BatchProofProtocol)
|
transcript.NewProtocol(BatchProofProtocol)
|
||||||
transcript.AddToTranscript(BatchProofX, new(ristretto.Point).SetBase().Bytes())
|
transcript.AddToTranscript(BatchProofX, new(ristretto.Element).Base().Encode(nil))
|
||||||
transcript.AddToTranscript(BatchProofY, Y.Bytes())
|
transcript.AddToTranscript(BatchProofY, Y.Encode(nil))
|
||||||
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
||||||
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
||||||
prng := transcript.CommitToPRNG("w")
|
prng := transcript.CommitToPRNG("w")
|
||||||
M := new(ristretto.Point).SetZero()
|
M := new(ristretto.Element).Zero()
|
||||||
Z := new(ristretto.Point).SetZero()
|
Z := new(ristretto.Element).Zero()
|
||||||
for i := range blindedTokens {
|
for i := range blindedTokens {
|
||||||
c := prng.Next()
|
c := prng.Next()
|
||||||
M = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(blindedTokens[i].P, c), M)
|
M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M)
|
||||||
Z = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(signedTokens[i].Q, c), Z)
|
Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z)
|
||||||
}
|
}
|
||||||
return VerifyDiscreteLogEquivalenceProof(dleq, new(ristretto.Point).SetBase(), Y, M, Z, transcript)
|
return VerifyDiscreteLogEquivalenceProof(dleq, new(ristretto.Element).Base(), Y, M, Z, transcript)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnblindSignedTokenBatch taking in a set of tokens, their blinded & signed counterparts, a server public key (Y), a DLEQ proof and a transcript
|
// UnblindSignedTokenBatch taking in a set of tokens, their blinded & signed counterparts, a server public key (Y), a DLEQ proof and a transcript
|
||||||
// verifies that the signing procedure has taken place correctly and unblinds the tokens.
|
// verifies that the signing procedure has taken place correctly and unblinds the tokens.
|
||||||
func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, signedTokens []SignedToken, Y *ristretto.Point, proof DLEQProof, transcript *core.Transcript) bool {
|
func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, signedTokens []SignedToken, Y *ristretto.Element, proof DLEQProof, transcript *core.Transcript) bool {
|
||||||
verified := verifyBatchProof(proof, Y, blindedTokens, signedTokens, transcript)
|
verified := verifyBatchProof(proof, Y, blindedTokens, signedTokens, transcript)
|
||||||
if !verified {
|
if !verified {
|
||||||
|
log.Debugf("Failed to unblind tokens: %v", transcript.OutputTranscriptToAudit())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, t := range tokens {
|
for i, t := range tokens {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"cwtch.im/tapir/persistence"
|
"cwtch.im/tapir/persistence"
|
||||||
"cwtch.im/tapir/primitives/core"
|
"cwtch.im/tapir/primitives/core"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
"github.com/gtank/ristretto255"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,10 +33,41 @@ func TestToken_SpendToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToken_ConstrainToToken(t *testing.T) {
|
||||||
|
server := NewTokenServer()
|
||||||
|
|
||||||
|
token := new(Token)
|
||||||
|
blindedToken := token.GenBlindedToken()
|
||||||
|
|
||||||
|
signedToken := server.SignBlindedToken(blindedToken)
|
||||||
|
token.unblindSignedToken(signedToken)
|
||||||
|
|
||||||
|
spentToken := token.SpendToken([]byte("Hello"))
|
||||||
|
|
||||||
|
if server.SpendToken(spentToken, []byte("Hello World")) == nil {
|
||||||
|
t.Errorf("Token Should be InValid")
|
||||||
|
}
|
||||||
|
|
||||||
|
token2 := new(Token)
|
||||||
|
blindedToken2 := token2.GenBlindedToken()
|
||||||
|
Ht := sha3.Sum512(token.t)
|
||||||
|
T := new(ristretto255.Element).FromUniformBytes(Ht[:])
|
||||||
|
// Constraint forces T = kW to be part of the batch proof
|
||||||
|
// And because the batch proof must prove that *all* inputs share the same key and also checks the servers public key
|
||||||
|
// We get a consistency check for almost free.
|
||||||
|
signedTokens := server.SignBlindedTokenBatchWithConstraint([]BlindedToken{blindedToken2}, token.t, core.NewTranscript(""))
|
||||||
|
transcript := core.NewTranscript("")
|
||||||
|
|
||||||
|
// NOTE: For this to work token.t and token.W need to be obtain by the client from known source e.g. a public message board.
|
||||||
|
t.Logf("Result of constaint proof %v", UnblindSignedTokenBatch([]*Token{token2}, []BlindedToken{blindedToken2, {P: T}}, append(signedTokens.SignedTokens, SignedToken{token.W}), server.Y, signedTokens.Proof, transcript))
|
||||||
|
t.Log(transcript.OutputTranscriptToAudit())
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateBlindedTokenBatch(t *testing.T) {
|
func TestGenerateBlindedTokenBatch(t *testing.T) {
|
||||||
log.SetLevel(log.LevelDebug)
|
log.SetLevel(log.LevelDebug)
|
||||||
db := new(persistence.BoltPersistence)
|
db := new(persistence.BoltPersistence)
|
||||||
db.Open("tokens.db")
|
db.Open("tokens.db")
|
||||||
|
defer db.Close()
|
||||||
server := NewTokenServerFromStore(db)
|
server := NewTokenServerFromStore(db)
|
||||||
|
|
||||||
clientTranscript := core.NewTranscript("privacyPass")
|
clientTranscript := core.NewTranscript("privacyPass")
|
||||||
|
@ -65,5 +98,4 @@ func TestGenerateBlindedTokenBatch(t *testing.T) {
|
||||||
if verified {
|
if verified {
|
||||||
t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit())
|
t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit())
|
||||||
}
|
}
|
||||||
db.Close()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ package privacypass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
"cwtch.im/tapir/persistence"
|
"cwtch.im/tapir/persistence"
|
||||||
"cwtch.im/tapir/primitives/core"
|
"cwtch.im/tapir/primitives/core"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bwesterb/go-ristretto"
|
ristretto "github.com/gtank/ristretto255"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
// TokenServer implements a token server.
|
// TokenServer implements a token server.
|
||||||
type TokenServer struct {
|
type TokenServer struct {
|
||||||
k *ristretto.Scalar
|
k *ristretto.Scalar
|
||||||
Y *ristretto.Point
|
Y *ristretto.Element
|
||||||
seen map[string]bool
|
seen map[string]bool
|
||||||
persistanceService persistence.Service
|
persistanceService persistence.Service
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
@ -22,28 +23,53 @@ type TokenServer struct {
|
||||||
|
|
||||||
// SignedBatchWithProof encapsulates a signed batch of blinded tokens with a batch proof for verification
|
// SignedBatchWithProof encapsulates a signed batch of blinded tokens with a batch proof for verification
|
||||||
type SignedBatchWithProof struct {
|
type SignedBatchWithProof struct {
|
||||||
SignedTokens []SignedToken
|
SignedTokens []SignedToken `json:"st"`
|
||||||
Proof DLEQProof
|
Proof DLEQProof `json:"dp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenBucket = "tokens"
|
const tokenBucket = "tokens"
|
||||||
|
const keyBucket = "keys"
|
||||||
|
|
||||||
// NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances)
|
// NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances)
|
||||||
func NewTokenServer() TokenServer {
|
func NewTokenServer() *TokenServer {
|
||||||
k := new(ristretto.Scalar).Rand()
|
k := new(ristretto.Scalar)
|
||||||
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool), nil, sync.Mutex{}}
|
b := make([]byte, 64)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
// unable to generate secure random numbers
|
||||||
|
panic("unable to generate secure random numbers")
|
||||||
|
}
|
||||||
|
k.FromUniformBytes(b)
|
||||||
|
return &TokenServer{k, new(ristretto.Element).ScalarBaseMult(k), make(map[string]bool), nil, sync.Mutex{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTokenServerFromStore generates a new TokenServer backed by a persistence service.
|
// NewTokenServerFromStore generates a new TokenServer backed by a persistence service.
|
||||||
func NewTokenServerFromStore(persistenceService persistence.Service) TokenServer {
|
func NewTokenServerFromStore(persistenceService persistence.Service) *TokenServer {
|
||||||
k := new(ristretto.Scalar).Rand()
|
tokenServer := NewTokenServer()
|
||||||
persistenceService.Setup([]string{tokenBucket})
|
persistenceService.Setup([]string{tokenBucket})
|
||||||
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool), persistenceService, sync.Mutex{}}
|
persistenceService.Setup([]string{keyBucket})
|
||||||
|
exists, err := persistenceService.Check(keyBucket, "k")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// if we don't have a stored k then save the one we have generated
|
||||||
|
// otherwise use the k we have stored
|
||||||
|
if !exists {
|
||||||
|
persistenceService.Persist(keyBucket, "k", tokenServer.k)
|
||||||
|
} else {
|
||||||
|
persistenceService.Load(keyBucket, "k", tokenServer.k)
|
||||||
|
|
||||||
|
// recalculate public key from stored k
|
||||||
|
tokenServer.Y = new(ristretto.Element).ScalarBaseMult(tokenServer.k)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenServer.persistanceService = persistenceService
|
||||||
|
return tokenServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignBlindedToken calculates kP for the given BlindedToken P
|
// SignBlindedToken calculates kP for the given BlindedToken P
|
||||||
func (ts *TokenServer) SignBlindedToken(bt BlindedToken) SignedToken {
|
func (ts *TokenServer) SignBlindedToken(bt BlindedToken) SignedToken {
|
||||||
Q := new(ristretto.Point).ScalarMult(bt.P, ts.k)
|
Q := new(ristretto.Element).ScalarMult(ts.k, bt.P)
|
||||||
return SignedToken{Q}
|
return SignedToken{Q}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,24 +82,39 @@ func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, trans
|
||||||
return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)}
|
return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignBlindedTokenBatchWithConstraint signs a batch of blinded tokens under a given transcript given a constraint that the tokens must be signed
|
||||||
|
// by the same public key as an existing token
|
||||||
|
func (ts *TokenServer) SignBlindedTokenBatchWithConstraint(blindedTokens []BlindedToken, constraintToken []byte, transcript *core.Transcript) SignedBatchWithProof {
|
||||||
|
var signedTokens []SignedToken
|
||||||
|
for _, bt := range blindedTokens {
|
||||||
|
signedTokens = append(signedTokens, ts.SignBlindedToken(bt))
|
||||||
|
}
|
||||||
|
Ht := sha3.Sum512(constraintToken)
|
||||||
|
T := new(ristretto.Element).FromUniformBytes(Ht[:])
|
||||||
|
// W == kT
|
||||||
|
W := new(ristretto.Element).ScalarMult(ts.k, T)
|
||||||
|
blindedTokens = append(blindedTokens, BlindedToken{P: T})
|
||||||
|
return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, append(signedTokens, SignedToken{Q: W}), transcript)}
|
||||||
|
}
|
||||||
|
|
||||||
// constructBatchProof construct a batch proof that all the signed tokens have been signed correctly
|
// constructBatchProof construct a batch proof that all the signed tokens have been signed correctly
|
||||||
func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof {
|
func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof {
|
||||||
transcript.NewProtocol(BatchProofProtocol)
|
transcript.NewProtocol(BatchProofProtocol)
|
||||||
transcript.AddToTranscript(BatchProofX, new(ristretto.Point).SetBase().Bytes())
|
transcript.AddToTranscript(BatchProofX, new(ristretto.Element).Base().Encode(nil))
|
||||||
transcript.AddToTranscript(BatchProofY, ts.Y.Bytes())
|
transcript.AddToTranscript(BatchProofY, ts.Y.Encode(nil))
|
||||||
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
||||||
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
||||||
prng := transcript.CommitToPRNG("w")
|
prng := transcript.CommitToPRNG("w")
|
||||||
|
|
||||||
M := new(ristretto.Point).SetZero()
|
M := new(ristretto.Element).Zero()
|
||||||
Z := new(ristretto.Point).SetZero()
|
Z := new(ristretto.Element).Zero()
|
||||||
|
|
||||||
for i := range blindedTokens {
|
for i := range blindedTokens {
|
||||||
c := prng.Next()
|
c := prng.Next()
|
||||||
M = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(blindedTokens[i].P, c), M)
|
M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M)
|
||||||
Z = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(signedTokens[i].Q, c), Z)
|
Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z)
|
||||||
}
|
}
|
||||||
return DiscreteLogEquivalenceProof(ts.k, new(ristretto.Point).SetBase(), ts.Y, M, Z, transcript)
|
return DiscreteLogEquivalenceProof(ts.k, new(ristretto.Element).Base(), ts.Y, M, Z, transcript)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpendToken returns true a SpentToken is valid and has never been spent before, false otherwise.
|
// SpendToken returns true a SpentToken is valid and has never been spent before, false otherwise.
|
||||||
|
@ -90,13 +131,13 @@ func (ts *TokenServer) SpendToken(token SpentToken, data []byte) error {
|
||||||
return fmt.Errorf("token: %v has already been spent", token)
|
return fmt.Errorf("token: %v has already been spent", token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ht := sha3.Sum256(token.T)
|
Ht := sha3.Sum512(token.T)
|
||||||
T := new(ristretto.Point).SetElligator(&Ht)
|
T := new(ristretto.Element).FromUniformBytes(Ht[:])
|
||||||
W := new(ristretto.Point).ScalarMult(T, ts.k)
|
W := new(ristretto.Element).ScalarMult(ts.k, T)
|
||||||
key := sha3.Sum256(append(token.T, W.Bytes()...))
|
key := sha3.Sum256(append(token.T, W.Encode(nil)...))
|
||||||
mac := hmac.New(sha3.New512, key[:])
|
mac := hmac.New(sha3.New512, key[:])
|
||||||
K := mac.Sum(data)
|
computedMAC := mac.Sum(data)
|
||||||
result := hmac.Equal(token.MAC, K)
|
result := hmac.Equal(token.MAC, computedMAC)
|
||||||
if result == true {
|
if result == true {
|
||||||
if ts.persistanceService == nil {
|
if ts.persistanceService == nil {
|
||||||
ts.seen[hex.EncodeToString(token.T)] = true
|
ts.seen[hex.EncodeToString(token.T)] = true
|
||||||
|
|
32
service.go
32
service.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ type Connection interface {
|
||||||
// Connection defines a Tapir Connection
|
// Connection defines a Tapir Connection
|
||||||
type connection struct {
|
type connection struct {
|
||||||
hostname string
|
hostname string
|
||||||
conn net.Conn
|
conn io.ReadWriteCloser
|
||||||
capabilities sync.Map
|
capabilities sync.Map
|
||||||
encrypted bool
|
encrypted bool
|
||||||
key [32]byte
|
key [32]byte
|
||||||
|
@ -52,10 +51,11 @@ type connection struct {
|
||||||
outbound bool
|
outbound bool
|
||||||
closed bool
|
closed bool
|
||||||
MaxLength int
|
MaxLength int
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConnection creates a new Connection
|
// NewConnection creates a new Connection
|
||||||
func NewConnection(id *primitives.Identity, hostname string, outbound bool, conn net.Conn, app Application) Connection {
|
func NewConnection(id *primitives.Identity, hostname string, outbound bool, conn io.ReadWriteCloser, app Application) Connection {
|
||||||
connection := new(connection)
|
connection := new(connection)
|
||||||
connection.hostname = hostname
|
connection.hostname = hostname
|
||||||
connection.conn = conn
|
connection.conn = conn
|
||||||
|
@ -74,17 +74,23 @@ func (c *connection) ID() *primitives.Identity {
|
||||||
|
|
||||||
// App returns the overarching application using this Connection.
|
// App returns the overarching application using this Connection.
|
||||||
func (c *connection) App() Application {
|
func (c *connection) App() Application {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
return c.app
|
return c.app
|
||||||
}
|
}
|
||||||
|
|
||||||
// App returns the overarching application using this Connection.
|
// App returns the overarching application using this Connection.
|
||||||
func (c *connection) SetApp(application Application) {
|
func (c *connection) SetApp(application Application) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
c.app = application
|
c.app = application
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hostname returns the hostname of the connection (if the connection has not been authorized it will return the
|
// Hostname returns the hostname of the connection (if the connection has not been authorized it will return the
|
||||||
// temporary hostname identifier)
|
// temporary hostname identifier)
|
||||||
func (c *connection) Hostname() string {
|
func (c *connection) Hostname() string {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
return c.hostname
|
return c.hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,11 +102,15 @@ func (c *connection) IsOutbound() bool {
|
||||||
|
|
||||||
// IsClosed returns true if the connection is closed (connections cannot be reopened)
|
// IsClosed returns true if the connection is closed (connections cannot be reopened)
|
||||||
func (c *connection) IsClosed() bool {
|
func (c *connection) IsClosed() bool {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
return c.closed
|
return c.closed
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHostname sets the hostname on the connection
|
// SetHostname sets the hostname on the connection
|
||||||
func (c *connection) SetHostname(hostname string) {
|
func (c *connection) SetHostname(hostname string) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.identity.Hostname(), c.hostname, hostname)
|
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.identity.Hostname(), c.hostname, hostname)
|
||||||
c.hostname = hostname
|
c.hostname = hostname
|
||||||
}
|
}
|
||||||
|
@ -119,6 +129,8 @@ func (c *connection) HasCapability(name Capability) bool {
|
||||||
|
|
||||||
// Close forcibly closes the connection
|
// Close forcibly closes the connection
|
||||||
func (c *connection) Close() {
|
func (c *connection) Close() {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
c.closed = true
|
c.closed = true
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -134,7 +146,8 @@ func (c *connection) Expect() []byte {
|
||||||
c.closed = true
|
c.closed = true
|
||||||
return []byte{}
|
return []byte{}
|
||||||
}
|
}
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
if c.encrypted {
|
if c.encrypted {
|
||||||
var decryptNonce [24]byte
|
var decryptNonce [24]byte
|
||||||
copy(decryptNonce[:], buffer[:24])
|
copy(decryptNonce[:], buffer[:24])
|
||||||
|
@ -148,13 +161,18 @@ func (c *connection) Expect() []byte {
|
||||||
return []byte{}
|
return []byte{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
len, _ := binary.Uvarint(buffer[0:2])
|
length, _ := binary.Uvarint(buffer[0:2])
|
||||||
|
if length+2 >= uint64(c.MaxLength) {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
//cplog.Debugf("[%v -> %v] Wire Receive: (%d) %x", c.hostname, c.ID.Hostname(), len, buffer)
|
//cplog.Debugf("[%v -> %v] Wire Receive: (%d) %x", c.hostname, c.ID.Hostname(), len, buffer)
|
||||||
return buffer[2 : len+2]
|
return buffer[2 : length+2]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEncryptionKey turns on application-level encryption on the connection using the given key.
|
// SetEncryptionKey turns on application-level encryption on the connection using the given key.
|
||||||
func (c *connection) SetEncryptionKey(key [32]byte) {
|
func (c *connection) SetEncryptionKey(key [32]byte) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
c.key = key
|
c.key = key
|
||||||
c.encrypted = true
|
c.encrypted = true
|
||||||
}
|
}
|
||||||
|
@ -166,6 +184,8 @@ func (c *connection) Send(message []byte) {
|
||||||
binary.PutUvarint(buffer[0:2], uint64(len(message)))
|
binary.PutUvarint(buffer[0:2], uint64(len(message)))
|
||||||
copy(buffer[2:], message)
|
copy(buffer[2:], message)
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
if c.encrypted {
|
if c.encrypted {
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
pwd
|
pwd
|
||||||
go test ${1} -coverprofile=applications.cover.out -v ./applications
|
go test -race ${1} -coverprofile=applications.cover.out -v ./applications
|
||||||
go test ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard
|
go test -race ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard
|
||||||
go test ${1} -coverprofile=primitives.cover.out -v ./primitives
|
go test -race ${1} -coverprofile=primitives.cover.out -v ./primitives
|
||||||
go test ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable
|
go test -race ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable
|
||||||
go test ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core
|
go test -race ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core
|
||||||
go test ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass
|
go test -race ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass
|
||||||
go test -bench "BenchmarkAuditableStore" -benchtime 1000x primitives/auditable/*.go
|
go test -bench "BenchmarkAuditableStore" -benchtime 1000x primitives/auditable/*.go
|
||||||
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
||||||
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
||||||
|
|
Loading…
Reference in New Issue