Compare commits
8 Commits
master
...
bulletproo
Author | SHA1 | Date |
---|---|---|
Sarah Jamie Lewis | daa05d97c8 | |
Sarah Jamie Lewis | 5afd4a930a | |
Sarah Jamie Lewis | ec8c9352ec | |
Sarah Jamie Lewis | 9332385a6f | |
Sarah Jamie Lewis | 136b9b8192 | |
Sarah Jamie Lewis | bd3a6043be | |
Sarah Jamie Lewis | 1066862f58 | |
Sarah Jamie Lewis | a2cfcaf8a4 |
76
.drone.yml
76
.drone.yml
|
@ -1,45 +1,34 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: linux-test
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/cwtch.im/tapir
|
||||
|
||||
steps:
|
||||
- name: fetch
|
||||
image: golang:1.17.5
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
pipeline:
|
||||
fetch:
|
||||
image: golang
|
||||
commands:
|
||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
|
||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
|
||||
- chmod a+x tor
|
||||
- export GO111MODULE=on
|
||||
- go mod download
|
||||
- go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- name: quality
|
||||
image: golang:1.17.5
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
- go mod vendor
|
||||
- go get -u golang.org/x/lint/golint
|
||||
quality:
|
||||
image: golang
|
||||
commands:
|
||||
- staticcheck ./...
|
||||
- name: units-tests
|
||||
image: golang:1.17.5
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
- go list ./... | xargs go vet
|
||||
- go list ./... | xargs golint -set_exit_status
|
||||
units-tests:
|
||||
image: golang
|
||||
commands:
|
||||
- export PATH=`pwd`:$PATH
|
||||
- export PATH=$PATH:/go/src/cwtch.im/tapir
|
||||
- sh testing/tests.sh
|
||||
- name: integ-test
|
||||
image: golang:1.17.5
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
integ-test:
|
||||
image: golang
|
||||
commands:
|
||||
- export PATH=`pwd`:$PATH
|
||||
- go test -race -v git.openprivacy.ca/cwtch.im/tapir/testing
|
||||
- name: notify-email
|
||||
- ./tor -f ./torrc
|
||||
- sleep 15
|
||||
- go test -v cwtch.im/tapir/testing
|
||||
notify-email:
|
||||
image: drillster/drone-email
|
||||
host: build.openprivacy.ca
|
||||
port: 25
|
||||
|
@ -47,27 +36,10 @@ steps:
|
|||
from: drone@openprivacy.ca
|
||||
when:
|
||||
status: [ failure ]
|
||||
- name: notify-gogs
|
||||
notify-gogs:
|
||||
image: openpriv/drone-gogs
|
||||
pull: if-not-exists
|
||||
when:
|
||||
event: pull_request
|
||||
status: [ success, changed, failure ]
|
||||
environment:
|
||||
GOGS_ACCOUNT_TOKEN:
|
||||
from_secret: gogs_account_token
|
||||
settings:
|
||||
gogs_url: https://git.openprivacy.ca
|
||||
|
||||
volumes:
|
||||
# gopath where bin and pkg lives to persist across steps
|
||||
- name: deps
|
||||
temp: {}
|
||||
|
||||
trigger:
|
||||
repo: cwtch.im/tapir
|
||||
branch: master
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
secrets: [gogs_account_token]
|
||||
gogs_url: https://git.openprivacy.ca
|
||||
|
|
|
@ -5,6 +5,3 @@ coverage.out
|
|||
/testing/tor/
|
||||
/applications/tor/
|
||||
*.db
|
||||
/applications/tokenboard/tor/
|
||||
fuzzing/
|
||||
*.cover.out
|
||||
|
|
8
LICENSE
8
LICENSE
|
@ -1,8 +0,0 @@
|
|||
MIT License
|
||||
Copyright (c) 2019 Open Privacy Research Society
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,7 +1,7 @@
|
|||
package tapir
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
)
|
||||
|
||||
// Capability defines a status granted to a connection, from an application. That allows the connection to access
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package applications
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"cwtch.im/tapir"
|
||||
)
|
||||
|
||||
// ApplicationChain is a meta-app that can be used to build complex applications from other applications
|
||||
|
@ -40,17 +40,13 @@ func (appchain *ApplicationChain) NewInstance() tapir.Application {
|
|||
func (appchain *ApplicationChain) Init(connection tapir.Connection) {
|
||||
appchain.TranscriptApp.Init(connection)
|
||||
for i, app := range appchain.apps {
|
||||
// propagate the transcript to the app
|
||||
app.PropagateTranscript(appchain.transcript)
|
||||
// apply the app to the connection
|
||||
connection.SetApp(app)
|
||||
// initialize the application given the connection
|
||||
app.Init(connection)
|
||||
// if we hit our guard then carry on, otherwise close...
|
||||
if !connection.HasCapability(appchain.capabilities[i]) {
|
||||
if connection.HasCapability(appchain.capabilities[i]) == false {
|
||||
connection.Close()
|
||||
return
|
||||
}
|
||||
connection.SetApp(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ package applications
|
|||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
|
@ -49,26 +49,21 @@ func (ea *AuthApp) Init(connection tapir.Connection) {
|
|||
|
||||
// If we are an outbound connection we can perform an additional check to ensure that the server sent us back the correct long term
|
||||
// public key
|
||||
if connection.IsOutbound() && torProvider.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey) != connection.Hostname() {
|
||||
log.Errorf("The remote server (%v) has attempted to authenticate with a different public key %v", connection.Hostname(), torProvider.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
||||
if connection.IsOutbound() && utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey) != connection.Hostname() {
|
||||
log.Errorf("The remote server (%v) has attempted to authenticate with a different public key %v", connection.Hostname(), utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
|
||||
connection.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Perform the triple-diffie-hellman exchange.
|
||||
key, err := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
|
||||
if err != nil {
|
||||
log.Errorf("Failed Auth Challenge %v", err)
|
||||
connection.Close()
|
||||
return
|
||||
}
|
||||
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
|
||||
connection.SetEncryptionKey(key)
|
||||
|
||||
// We just successfully unmarshaled both of these, so we can safely ignore the err return from these functions.
|
||||
challengeRemote, _ := json.Marshal(remoteAuthMessage)
|
||||
challengeLocal, _ := json.Marshal(authMessage)
|
||||
|
||||
// Define canonical labels so both sides of the connection can generate the same key
|
||||
// Define canonical labels so both sides of the
|
||||
var outboundAuthMessage []byte
|
||||
var outboundHostname string
|
||||
var inboundAuthMessage []byte
|
||||
|
@ -76,11 +71,11 @@ func (ea *AuthApp) Init(connection tapir.Connection) {
|
|||
|
||||
if connection.IsOutbound() {
|
||||
outboundHostname = connection.ID().Hostname()
|
||||
inboundHostname = torProvider.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
inboundHostname = utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
outboundAuthMessage = challengeLocal
|
||||
inboundAuthMessage = challengeRemote
|
||||
} else {
|
||||
outboundHostname = torProvider.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
outboundHostname = utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
inboundHostname = connection.ID().Hostname()
|
||||
outboundAuthMessage = challengeRemote
|
||||
inboundAuthMessage = challengeLocal
|
||||
|
@ -108,12 +103,12 @@ func (ea *AuthApp) Init(connection tapir.Connection) {
|
|||
// encryption key and the same transcript challenge.
|
||||
connection.Send(append(challengeBytes, []byte(connection.ID().Hostname())...))
|
||||
remoteChallenge := connection.Expect()
|
||||
assertedHostname := torProvider.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
assertedHostname := utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
|
||||
if subtle.ConstantTimeCompare(append(challengeBytes, []byte(assertedHostname)...), remoteChallenge) == 1 {
|
||||
connection.SetHostname(assertedHostname)
|
||||
connection.SetCapability(AuthCapability)
|
||||
} else {
|
||||
log.Debugf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
||||
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
|
||||
connection.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ package applications
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"testing"
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ type MockConnection struct {
|
|||
func (mc *MockConnection) Init(outbound bool) {
|
||||
mc.id, _ = primitives.InitializeEphemeralIdentity()
|
||||
mc.outbound = outbound
|
||||
return
|
||||
}
|
||||
|
||||
func (mc MockConnection) Hostname() string {
|
||||
|
@ -58,9 +59,8 @@ func (MockConnection) SetEncryptionKey(key [32]byte) {
|
|||
// no op
|
||||
}
|
||||
|
||||
func (MockConnection) Send(message []byte) error {
|
||||
func (MockConnection) Send(message []byte) {
|
||||
// no op
|
||||
return nil
|
||||
}
|
||||
|
||||
func (MockConnection) Close() {
|
||||
|
@ -80,10 +80,6 @@ func (MockConnection) IsClosed() bool {
|
|||
panic("implement me")
|
||||
}
|
||||
|
||||
func (MockConnection) Broadcast(message []byte, capability tapir.Capability) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestAuthApp_Failed(t *testing.T) {
|
||||
var authApp AuthApp
|
||||
ai := authApp.NewInstance()
|
||||
|
|
|
@ -2,10 +2,9 @@ package applications
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
)
|
||||
|
||||
// ProofOfWorkApplication forces the incoming connection to do proof of work before granting a capability
|
||||
|
@ -67,20 +66,9 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core
|
|||
var sum [32]byte
|
||||
solution := []byte{}
|
||||
solve := make([]byte, len(challenge)+32)
|
||||
// reuse our allocation
|
||||
buf := make([]byte, 64)
|
||||
next := new(ristretto.Scalar)
|
||||
encodedSolution := make([]byte, 0, 32)
|
||||
for !solved {
|
||||
err := prng.Next(buf, next)
|
||||
if err != nil {
|
||||
// this will cause the challenge to fail...
|
||||
log.Errorf("error completing challenge: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//lint:ignore SA1019 API this is "deprecated", but without it it will cause an allocation on every single check
|
||||
solution = next.Encode(encodedSolution)
|
||||
solution = prng.Next().Bytes()
|
||||
|
||||
copy(solve[0:], solution[:])
|
||||
copy(solve[len(solution):], challenge[:])
|
||||
|
@ -92,8 +80,6 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core
|
|||
solved = false
|
||||
}
|
||||
}
|
||||
// reuse this allocated memory next time...
|
||||
encodedSolution = encodedSolution[:0]
|
||||
}
|
||||
log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
|
||||
return solution[:]
|
||||
|
@ -101,9 +87,6 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core
|
|||
|
||||
// ValidateChallenge returns true if the message and spamguard pass the challenge
|
||||
func (powapp *ProofOfWorkApplication) validateChallenge(challenge []byte, solution []byte) bool {
|
||||
if len(solution) != 32 {
|
||||
return false
|
||||
}
|
||||
solve := make([]byte, len(challenge)+32)
|
||||
copy(solve[0:], solution[0:32])
|
||||
copy(solve[32:], challenge[:])
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package applications
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives/privacypass"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
)
|
||||
|
||||
// TokenApplication provides Tokens for PoW
|
||||
|
@ -16,52 +16,41 @@ type TokenApplication struct {
|
|||
|
||||
// HasTokensCapability is granted once the client has obtained signed tokens
|
||||
const HasTokensCapability = tapir.Capability("HasTokensCapability")
|
||||
const numTokens = 10
|
||||
|
||||
// NewInstance should always return a new instantiation of the application.
|
||||
func (tokenapp *TokenApplication) NewInstance() tapir.Application {
|
||||
func (powapp *TokenApplication) NewInstance() tapir.Application {
|
||||
app := new(TokenApplication)
|
||||
app.TokenService = tokenapp.TokenService
|
||||
app.TokenService = powapp.TokenService
|
||||
return app
|
||||
}
|
||||
|
||||
// Init is run when the connection is first started.
|
||||
func (tokenapp *TokenApplication) Init(connection tapir.Connection) {
|
||||
tokenapp.Transcript().NewProtocol("token-app")
|
||||
log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit())
|
||||
func (powapp *TokenApplication) Init(connection tapir.Connection) {
|
||||
powapp.Transcript().NewProtocol("token-app")
|
||||
if connection.IsOutbound() {
|
||||
tokens, blinded := privacypass.GenerateBlindedTokenBatch(numTokens)
|
||||
tokens, blinded := privacypass.GenerateBlindedTokenBatch(10)
|
||||
data, _ := json.Marshal(blinded)
|
||||
connection.Send(data)
|
||||
var signedBatch privacypass.SignedBatchWithProof
|
||||
err := json.Unmarshal(connection.Expect(), &signedBatch)
|
||||
if err == nil {
|
||||
verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, tokenapp.TokenService.Y, signedBatch.Proof, tokenapp.Transcript())
|
||||
verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, powapp.TokenService.Y, signedBatch.Proof, powapp.Transcript())
|
||||
if verified {
|
||||
log.Debugf("Successfully obtained signed tokens")
|
||||
tokenapp.Tokens = tokens
|
||||
powapp.Tokens = tokens
|
||||
connection.SetCapability(HasTokensCapability)
|
||||
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")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// We are the server
|
||||
var blinded []privacypass.BlindedToken
|
||||
err := json.Unmarshal(connection.Expect(), &blinded)
|
||||
if err == nil {
|
||||
batchProof, err := tokenapp.TokenService.SignBlindedTokenBatch(blinded, tokenapp.Transcript())
|
||||
if err != nil {
|
||||
} else {
|
||||
var blinded []privacypass.BlindedToken
|
||||
err := json.Unmarshal(connection.Expect(), &blinded)
|
||||
if err == nil {
|
||||
batchProof := powapp.TokenService.SignBlindedTokenBatch(blinded, powapp.Transcript())
|
||||
data, _ := json.Marshal(batchProof)
|
||||
connection.Send(data)
|
||||
return
|
||||
}
|
||||
log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit())
|
||||
data, _ := json.Marshal(batchProof)
|
||||
connection.Send(data)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package tokenboard
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/applications"
|
||||
"cwtch.im/tapir/primitives/auditable"
|
||||
"cwtch.im/tapir/primitives/privacypass"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/auditable"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
)
|
||||
|
||||
// NewTokenBoardClient generates a new Client for Token Board
|
||||
|
@ -60,6 +60,7 @@ func (ta *Client) Listen() {
|
|||
|
||||
var message Message
|
||||
json.Unmarshal(data, &message)
|
||||
log.Debugf("Received a Message: %v", message)
|
||||
switch message.MessageType {
|
||||
case postResultMessage:
|
||||
log.Debugf("Post result: %x", message.PostResult.Proof)
|
||||
|
@ -100,7 +101,7 @@ func (ta *Client) PurchaseTokens() {
|
|||
|
||||
// Post sends a Post Request to the server
|
||||
func (ta *Client) Post(message auditable.Message) bool {
|
||||
token, err := ta.paymentHandler.NextToken(message, ta.connection.Hostname())
|
||||
token, err := ta.paymentHandler.NextToken(message)
|
||||
if err == nil {
|
||||
data, _ := json.Marshal(Message{MessageType: postRequestMessage, PostRequest: postRequest{Token: token, Message: message}})
|
||||
ta.connection.Send(data)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package tokenboard
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/auditable"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
||||
"cwtch.im/tapir/primitives/auditable"
|
||||
"cwtch.im/tapir/primitives/privacypass"
|
||||
)
|
||||
|
||||
// AppHandler allows clients to react to specific events.
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package tokenboard
|
||||
|
||||
// NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed.
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/applications"
|
||||
"cwtch.im/tapir/primitives/auditable"
|
||||
"cwtch.im/tapir/primitives/privacypass"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/auditable"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
)
|
||||
|
||||
// NewTokenBoardServer generates new Server for Token Board
|
||||
|
@ -57,7 +55,7 @@ func (ta *Server) Listen() {
|
|||
|
||||
var message Message
|
||||
json.Unmarshal(data, &message)
|
||||
|
||||
log.Debugf("Received a Message: %v", message)
|
||||
switch message.MessageType {
|
||||
case postRequestMessage:
|
||||
postrequest := message.PostRequest
|
||||
|
@ -79,7 +77,7 @@ func (ta *Server) Listen() {
|
|||
}
|
||||
|
||||
func (ta *Server) postMessageRequest(token privacypass.SpentToken, message auditable.Message) {
|
||||
if err := ta.TokenService.SpendToken(token, append(message, ta.connection.ID().Hostname()...)); err == nil {
|
||||
if err := ta.TokenService.SpendToken(token, message); err == nil {
|
||||
log.Debugf("Token is valid")
|
||||
signedproof := ta.AuditableStore.Add(message)
|
||||
data, _ := json.Marshal(Message{MessageType: postResultMessage, PostResult: postResult{true, signedproof}})
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package tokenboard
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/applications"
|
||||
"cwtch.im/tapir/networks/tor"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"cwtch.im/tapir/primitives/auditable"
|
||||
"cwtch.im/tapir/primitives/privacypass"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/networks/tor"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/auditable"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -38,7 +37,8 @@ type FreePaymentHandler struct {
|
|||
|
||||
func (fph *FreePaymentHandler) MakePayment() {
|
||||
id, sk := primitives.InitializeEphemeralIdentity()
|
||||
client := new(tor.BaseOnionService)
|
||||
var client tapir.Service
|
||||
client = new(tor.BaseOnionService)
|
||||
client.Init(fph.ACN, sk, &id)
|
||||
|
||||
tokenApplication := new(applications.TokenApplication)
|
||||
|
@ -47,46 +47,30 @@ func (fph *FreePaymentHandler) MakePayment() {
|
|||
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||
client.Connect(fph.ServerHostname, powTokenApp)
|
||||
conn, err := client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability)
|
||||
if err == nil {
|
||||
powtapp, _ := conn.App().(*applications.TokenApplication)
|
||||
fph.tokens = append(fph.tokens, powtapp.Tokens...)
|
||||
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
log.Debugf("Error making payment: %v", err)
|
||||
client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability)
|
||||
conn, _ := client.GetConnection(fph.ServerHostname)
|
||||
powtapp, _ := conn.App().(*applications.TokenApplication)
|
||||
fph.tokens = append(fph.tokens, powtapp.Tokens...)
|
||||
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func (fph *FreePaymentHandler) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) {
|
||||
func (fph *FreePaymentHandler) NextToken(data []byte) (privacypass.SpentToken, error) {
|
||||
if len(fph.tokens) == 0 {
|
||||
return privacypass.SpentToken{}, errors.New("No more tokens")
|
||||
}
|
||||
token := fph.tokens[0]
|
||||
fph.tokens = fph.tokens[1:]
|
||||
return token.SpendToken(append(data, hostname...)), nil
|
||||
return token.SpendToken(data), nil
|
||||
}
|
||||
|
||||
func TestTokenBoardApp(t *testing.T) {
|
||||
// numRoutinesStart := runtime.NumGoroutine()
|
||||
log.SetLevel(log.LevelDebug)
|
||||
log.Infof("Number of goroutines open at start: %d", runtime.NumGoroutine())
|
||||
|
||||
os.MkdirAll("./tor/", 0700)
|
||||
builder := new(torProvider.TorrcBuilder)
|
||||
builder.WithSocksPort(9059).WithControlPort(9060).WithHashedPassword("tapir-integration-test").Build("./tor/torrc")
|
||||
torDataDir := ""
|
||||
var err error
|
||||
if torDataDir, err = os.MkdirTemp("./tor/", "data-dir-"); err != nil {
|
||||
t.Fatalf("could not create data dir")
|
||||
}
|
||||
|
||||
// Connect to Tor
|
||||
acn, err := torProvider.NewTorACNWithAuth("./", "", torDataDir, 9060, torProvider.HashedPasswordAuthenticator{Password: "tapir-integration-test"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("could not launch ACN %v", err)
|
||||
}
|
||||
var acn connectivity.ACN
|
||||
acn, _ = connectivity.StartTor("./", "")
|
||||
acn.WaitTillBootstrapped()
|
||||
|
||||
// Generate Server Key
|
||||
|
@ -102,25 +86,27 @@ func TestTokenBoardApp(t *testing.T) {
|
|||
clientAuditableStore.Init(publicsid)
|
||||
|
||||
// Init the Server running the Simple App.
|
||||
service := new(tor.BaseOnionService)
|
||||
var service tapir.Service
|
||||
service = new(tor.BaseOnionService)
|
||||
service.Init(acn, sk, &sid)
|
||||
|
||||
// Goroutine Management
|
||||
sg := new(sync.WaitGroup)
|
||||
sg.Add(1)
|
||||
go func() {
|
||||
service.Listen(NewTokenBoardServer(tokenService, serverAuditableStore))
|
||||
service.Listen(NewTokenBoardServer(&tokenService, serverAuditableStore))
|
||||
sg.Done()
|
||||
}()
|
||||
|
||||
// Init the Server running the PoW Token App.
|
||||
powTokenService := new(tor.BaseOnionService)
|
||||
var powTokenService tapir.Service
|
||||
powTokenService = new(tor.BaseOnionService)
|
||||
spowid, spowk := primitives.InitializeEphemeralIdentity()
|
||||
powTokenService.Init(acn, spowk, &spowid)
|
||||
sg.Add(1)
|
||||
go func() {
|
||||
tokenApplication := new(applications.TokenApplication)
|
||||
tokenApplication.TokenService = tokenService
|
||||
tokenApplication.TokenService = &tokenService
|
||||
powTokenApp := new(applications.ApplicationChain).
|
||||
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
|
||||
ChainApplication(tokenApplication, applications.HasTokensCapability)
|
||||
|
@ -128,11 +114,12 @@ func TestTokenBoardApp(t *testing.T) {
|
|||
sg.Done()
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second * 60) // wait for server to initialize
|
||||
time.Sleep(time.Second * 30) // wait for server to initialize
|
||||
id, sk := primitives.InitializeEphemeralIdentity()
|
||||
client := new(tor.BaseOnionService)
|
||||
var client tapir.Service
|
||||
client = new(tor.BaseOnionService)
|
||||
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)
|
||||
conn, _ := client.GetConnection(sid.Hostname())
|
||||
tba, _ := conn.App().(*Client)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package applications
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
)
|
||||
|
||||
// TranscriptApp defines a Tapir Meta-App which provides a global cryptographic transcript
|
||||
|
@ -19,9 +18,6 @@ func (TranscriptApp) NewInstance() tapir.Application {
|
|||
|
||||
// Init initializes the cryptographic transcript
|
||||
func (ta *TranscriptApp) Init(connection tapir.Connection) {
|
||||
if ta.transcript != nil {
|
||||
ta.panic()
|
||||
}
|
||||
ta.transcript = core.NewTranscript("tapir-transcript")
|
||||
}
|
||||
|
||||
|
@ -32,19 +28,5 @@ func (ta *TranscriptApp) Transcript() *core.Transcript {
|
|||
|
||||
// PropagateTranscript overrides the default transcript and propagates a transcript from a previous session
|
||||
func (ta *TranscriptApp) PropagateTranscript(transcript *core.Transcript) {
|
||||
if ta.transcript != nil {
|
||||
ta.panic()
|
||||
}
|
||||
ta.transcript = transcript
|
||||
}
|
||||
|
||||
func (ta *TranscriptApp) panic() {
|
||||
// Note: if this is ever happens it is a critical application bug
|
||||
// This will prevent a misuse of application chains that cause an earlier
|
||||
// transcript to be overwritten. Since we expect the security of many higher level applications
|
||||
// to be reliant on the randomness provided by the transcript we want to be actively hostile to any potential
|
||||
// misuse.
|
||||
log.Errorf("apps should not attempt to intitalize or overwrite a transcript once one has been initialized - this is a CRITICAL bug and so we have safely crashed")
|
||||
// We could silently fail to do anything here, but that is likely more dangerous in the long run...
|
||||
panic("apps should not attempt to intitalize or overwrite a transcript a transcript once one has been initialized - this is a CRITICAL bug and so we have safely crashed")
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
package applications
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTranscriptApp(t *testing.T) {
|
||||
ta := new(TranscriptApp)
|
||||
ta.Init(MockConnection{})
|
||||
ta.Transcript().NewProtocol("test")
|
||||
ta.transcript.CommitToTranscript("test-commit")
|
||||
t.Logf(ta.Transcript().OutputTranscriptToAudit())
|
||||
|
||||
// Now we test panic'ing....
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("The code did not panic - it definitely should have")
|
||||
}
|
||||
}()
|
||||
|
||||
// Attempt to reinitialized the transcript, apps should *never* do this and we want to be hostile to that
|
||||
// behaviour
|
||||
ta.Init(MockConnection{})
|
||||
}
|
21
go.mod
21
go.mod
|
@ -1,21 +1,6 @@
|
|||
module git.openprivacy.ca/cwtch.im/tapir
|
||||
|
||||
go 1.17
|
||||
module cwtch.im/tapir
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3
|
||||
github.com/gtank/merlin v0.1.1
|
||||
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d
|
||||
)
|
||||
|
||||
require (
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
||||
)
|
||||
|
|
66
go.sum
66
go.sum
|
@ -1,56 +1,28 @@
|
|||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6 h1:g74PyDGvpMZ3+K0dXy3mlTJh+e0rcwNk0XF8owzkmOA=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6/go.mod h1:Hn1gpOx/bRZp5wvCtPQVJPXrfeUH0EGiG/Aoa0vjGLg=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||
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=
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||
github.com/cretz/bine v0.1.0 h1:1/fvhLE+fk0bPzjdO5Ci+0ComYxEMuB1JhM4X5skT3g=
|
||||
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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.3-0.20210930101514-6bb39798585c h1:gkfmnY4Rlt3VINCo4uKdpvngiibQyoENVj5Q88sxXhE=
|
||||
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c/go.mod h1:tDPFhGdt3hJWqtKwx57i9baiB1Cj0yAg22VOPUqm5vY=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
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/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-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package tor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -16,34 +15,11 @@ import (
|
|||
|
||||
// BaseOnionService is a concrete implementation of the service interface over Tor onion services.
|
||||
type BaseOnionService struct {
|
||||
connections sync.Map
|
||||
acn connectivity.ACN
|
||||
id *primitives.Identity
|
||||
privateKey ed25519.PrivateKey
|
||||
ls connectivity.ListenService
|
||||
lock sync.Mutex
|
||||
port int
|
||||
shutdownChannel chan bool
|
||||
}
|
||||
|
||||
// Metrics provides a report of useful information about the status of the service e.g. the number of active
|
||||
// connections
|
||||
func (s *BaseOnionService) Metrics() tapir.ServiceMetrics {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
count := 0
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if !connection.IsClosed() {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return tapir.ServiceMetrics{
|
||||
ConnectionCount: count,
|
||||
}
|
||||
connections sync.Map
|
||||
acn connectivity.ACN
|
||||
id *primitives.Identity
|
||||
privateKey ed25519.PrivateKey
|
||||
ls connectivity.ListenService
|
||||
}
|
||||
|
||||
// Init initializes a BaseOnionService with a given private key and identity
|
||||
|
@ -54,176 +30,50 @@ func (s *BaseOnionService) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id
|
|||
s.acn = acn
|
||||
s.id = id
|
||||
s.privateKey = sk
|
||||
s.port = 9878
|
||||
// blocking so we can wait on shutdown for closing of this goroutine
|
||||
s.shutdownChannel = make(chan bool)
|
||||
go func() {
|
||||
for s.waitOrTimeout() {
|
||||
s.GarbageCollect()
|
||||
}
|
||||
log.Debugf("closing down garbage collection goroutine")
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *BaseOnionService) waitOrTimeout() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
select {
|
||||
case <-s.shutdownChannel:
|
||||
return false
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// SetPort configures the port that the service uses.
|
||||
func (s *BaseOnionService) SetPort(port int) {
|
||||
s.port = port
|
||||
}
|
||||
|
||||
// WaitForCapabilityOrClose blocks until the connection has the given capability or the underlying connection is closed
|
||||
// (through error or user action)
|
||||
func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name tapir.Capability) (tapir.Connection, error) {
|
||||
attempts := 0
|
||||
for {
|
||||
if attempts > 4 {
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.Hostname() == cid {
|
||||
if !connection.IsClosed() {
|
||||
connection.Close()
|
||||
s.connections.Delete(key)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
log.Debugf("WaitForCapabilityOrClose attempts exceeded for %v, all connections closed", cid)
|
||||
return nil, errors.New("failed to acquire capability after multiple attempts, forcibly closing all connections with the peer")
|
||||
}
|
||||
|
||||
if attempts > 0 {
|
||||
// Allow connections to be torn down / closed before checking again
|
||||
// There is no point in busy looping...
|
||||
time.Sleep(time.Second * time.Duration(attempts))
|
||||
}
|
||||
|
||||
// Increment Attempts
|
||||
attempts++
|
||||
|
||||
log.Debugf("Lookup up a connection %v...", cid)
|
||||
// Lookup the connection...
|
||||
conn, err := s.GetConnection(cid)
|
||||
|
||||
// If there are no active connections then return an error...
|
||||
if conn == nil {
|
||||
log.Debugf("no active connection found for %v", cid)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// if we have only one connection and it has the desired capability then return the connection with
|
||||
// no error...
|
||||
conn, err := s.GetConnection(cid)
|
||||
if err == nil {
|
||||
for {
|
||||
if conn.HasCapability(name) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
log.Debugf("Found 1 connections for %v, but it lacks the desired capability %v", cid, name)
|
||||
continue
|
||||
}
|
||||
|
||||
// If We have 2 connections for the same hostname...
|
||||
if err != nil {
|
||||
log.Debugf("found duplicate connections for %v <-> %v %v", s.id.Hostname(), cid, err)
|
||||
|
||||
inboundCount := 0
|
||||
|
||||
// By convention the lowest lexicographical hostname purges all their outbounds to the higher
|
||||
// hostname
|
||||
// Which should only leave a single connection remaining (as we dedupe on connect too)
|
||||
// This does allow people to attempt to guarantee being the outbound connection but due to the bidirectional
|
||||
// authentication this should never result in an advantage in the protocol.
|
||||
// Close all outbound connections to connection
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.Hostname() == cid {
|
||||
if !connection.IsClosed() && connection.IsOutbound() && s.id.Hostname() < cid {
|
||||
connection.Close()
|
||||
s.connections.Delete(key)
|
||||
}
|
||||
|
||||
if !connection.IsClosed() && !connection.IsOutbound() {
|
||||
inboundCount++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// If we have more than 1 inbound count then forcibly close all connections...
|
||||
// This shouldn't happen honestly, but if it does then it can cause an infinite check here
|
||||
if inboundCount > 1 {
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.Hostname() == cid {
|
||||
if !connection.IsClosed() {
|
||||
connection.Close()
|
||||
s.connections.Delete(key)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil, errors.New("multiple inbound connections found and closed; the only resolution to this is to close them all and try connecting again")
|
||||
if conn.IsClosed() {
|
||||
return nil, errors.New("connection is closed")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetConnection returns a connection for a given hostname.
|
||||
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
|
||||
conn := make([]tapir.Connection, 0)
|
||||
var conn tapir.Connection
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
log.Debugf("Checking %v", key)
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.Hostname() == hostname {
|
||||
if !connection.IsClosed() {
|
||||
conn = append(conn, connection)
|
||||
} else {
|
||||
// Delete this Closed Connection
|
||||
s.connections.Delete(key)
|
||||
conn = connection
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(conn) == 0 {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection found")
|
||||
}
|
||||
if len(conn) > 1 {
|
||||
// If there are multiple connections we return the first one but also notify the user (this should be a
|
||||
// temporary edgecase (see Connect))
|
||||
return conn[0], errors.New("multiple connections found")
|
||||
}
|
||||
return conn[0], nil
|
||||
}
|
||||
|
||||
// GarbageCollect iterates through the connection pool and cleans up any connections that are closed
|
||||
// that haven't been removed from the map.
|
||||
func (s *BaseOnionService) GarbageCollect() {
|
||||
log.Debugf("running garbage collection...")
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.IsClosed() {
|
||||
// Delete this Closed Connection
|
||||
s.connections.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Connect initializes a new outbound connection to the given peer, using the defined Application
|
||||
func (s *BaseOnionService) Connect(hostname string, app tapir.Application) (bool, error) {
|
||||
currconn, _ := s.GetConnection(hostname)
|
||||
|
||||
// We already have a connection
|
||||
if currconn != nil {
|
||||
_, err := s.GetConnection(hostname)
|
||||
if err == nil {
|
||||
// Note: This check is not 100% reliable. And we may end up with two connections between peers
|
||||
// This can happen when a client connects to a server as the server is connecting to the client
|
||||
// Because at the start of the connection the server cannot derive the true hostname of the client until it
|
||||
|
@ -231,7 +81,6 @@ func (s *BaseOnionService) Connect(hostname string, app tapir.Application) (bool
|
|||
// We mitigate this by performing multiple checks when Connect'ing
|
||||
return true, errors.New("already connected to " + hostname)
|
||||
}
|
||||
|
||||
// connects to a remote server
|
||||
// spins off to a connection struct
|
||||
log.Debugf("Connecting to %v", hostname)
|
||||
|
@ -242,14 +91,14 @@ func (s *BaseOnionService) Connect(hostname string, app tapir.Application) (bool
|
|||
// Second check. If we didn't catch a double connection attempt before the Open we *should* catch it now because
|
||||
// the auth protocol is quick and Open over onion connections can take some time.
|
||||
// Again this isn't 100% reliable.
|
||||
tconn, _ := s.GetConnection(hostname)
|
||||
if tconn != nil {
|
||||
_, err := s.GetConnection(hostname)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return true, errors.New("already connected to " + hostname)
|
||||
}
|
||||
|
||||
log.Debugf("Connected to %v [%v]", hostname, connectionID)
|
||||
s.connections.Store(connectionID, tapir.NewConnection(s, s.id, hostname, true, conn, app.NewInstance()))
|
||||
s.connections.Store(connectionID, tapir.NewConnection(s.id, hostname, true, conn, app.NewInstance()))
|
||||
return true, nil
|
||||
}
|
||||
log.Debugf("Error connecting to %v %v", hostname, err)
|
||||
|
@ -268,19 +117,16 @@ func (s *BaseOnionService) getNewConnectionID() string {
|
|||
func (s *BaseOnionService) Listen(app tapir.Application) error {
|
||||
// accepts a new connection
|
||||
// spins off to a connection struct
|
||||
s.lock.Lock()
|
||||
ls, err := s.acn.Listen(s.privateKey, s.port)
|
||||
ls, err := s.acn.Listen(s.privateKey, 9878)
|
||||
s.ls = ls
|
||||
s.lock.Unlock()
|
||||
|
||||
log.Debugf("Starting a service on %v ", ls.AddressFull())
|
||||
if err == nil {
|
||||
log.Debugf("Starting a service on %v ", s.ls.AddressFull())
|
||||
for {
|
||||
conn, err := s.ls.Accept()
|
||||
if err == nil {
|
||||
tempHostname := s.getNewConnectionID()
|
||||
log.Debugf("Accepted connection from %v", tempHostname)
|
||||
s.connections.Store(tempHostname, tapir.NewConnection(s, s.id, tempHostname, false, conn, app.NewInstance()))
|
||||
s.connections.Store(tempHostname, tapir.NewConnection(s.id, tempHostname, false, conn, app.NewInstance()))
|
||||
} else {
|
||||
log.Debugf("Error accepting connection %v", err)
|
||||
return err
|
||||
|
@ -293,33 +139,10 @@ func (s *BaseOnionService) Listen(app tapir.Application) error {
|
|||
|
||||
// Shutdown closes the service and ensures that any connections are closed.
|
||||
func (s *BaseOnionService) Shutdown() {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if s.ls != nil {
|
||||
s.ls.Close()
|
||||
}
|
||||
|
||||
// close all existing connections manually
|
||||
s.ls.Close()
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
connection.Close()
|
||||
return true
|
||||
})
|
||||
|
||||
// wait for the return of our garbage collection goroutine
|
||||
s.shutdownChannel <- true
|
||||
}
|
||||
|
||||
// Broadcast sends a message to all connections who possess the given capability
|
||||
func (s *BaseOnionService) Broadcast(message []byte, capability tapir.Capability) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.connections.Range(func(key, value interface{}) bool {
|
||||
connection := value.(tapir.Connection)
|
||||
if connection.HasCapability(capability) {
|
||||
connection.Send(message)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package persistence
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
|
@ -53,7 +53,6 @@ func (bp *BoltPersistence) Check(bucket string, name string) (bool, error) {
|
|||
val = b.Get([]byte(name))
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if val != nil {
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
package persistence
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoltPersistence_Open(t *testing.T) {
|
||||
os.Remove("test.dbgi")
|
||||
db := new(BoltPersistence)
|
||||
var db Service
|
||||
db = new(BoltPersistence)
|
||||
db.Open("test.dbgi")
|
||||
db.Setup([]string{"tokens"})
|
||||
// 2020.02: Fails in WSL1 because of a mmap issue.
|
||||
// https://github.com/microsoft/WSL/issues/4873
|
||||
// Scheduled to be fixed in the 20h1 Win10 release
|
||||
db.Persist("tokens", "random_value", true)
|
||||
|
||||
var exists bool
|
||||
|
|
Binary file not shown.
|
@ -14,15 +14,11 @@ import (
|
|||
// Alice Ephemeral <-> Bob Ephemeral
|
||||
//
|
||||
// Through this, a unique session key is derived. The exchange is offline-deniable (in the context of Tapir and Onion Service)
|
||||
func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteLongTermPublicKey ed25519.PublicKey, remoteEphemeralPublicKey ed25519.PublicKey, outbound bool) ([32]byte, error) {
|
||||
func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteLongTermPublicKey ed25519.PublicKey, remoteEphemeralPublicKey ed25519.PublicKey, outbound bool) [32]byte {
|
||||
// 3DH Handshake
|
||||
l2e, err1 := longtermIdentity.EDH(remoteEphemeralPublicKey)
|
||||
e2l, err2 := ephemeralIdentity.EDH(remoteLongTermPublicKey)
|
||||
e2e, err3 := ephemeralIdentity.EDH(remoteEphemeralPublicKey)
|
||||
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
return [32]byte{}, err1
|
||||
}
|
||||
l2e := longtermIdentity.EDH(remoteEphemeralPublicKey)
|
||||
e2l := ephemeralIdentity.EDH(remoteLongTermPublicKey)
|
||||
e2e := ephemeralIdentity.EDH(remoteEphemeralPublicKey)
|
||||
|
||||
// We need to define an order for the result concatenation so that both sides derive the same key.
|
||||
var result [96]byte
|
||||
|
@ -35,5 +31,5 @@ func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteL
|
|||
copy(result[32:64], l2e)
|
||||
copy(result[64:96], e2e)
|
||||
}
|
||||
return sha3.Sum256(result[:]), nil
|
||||
return sha3.Sum256(result[:])
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package auditable
|
||||
|
||||
// WARNING NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed.
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/persistence"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/persistence"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"sync"
|
||||
)
|
||||
|
@ -25,6 +23,7 @@ type State struct {
|
|||
Messages []Message
|
||||
}
|
||||
|
||||
//
|
||||
const (
|
||||
auditableDataStoreProtocol = "auditable-data-store"
|
||||
newMessage = "new-message"
|
||||
|
@ -136,7 +135,7 @@ func (as *Store) AppendState(state State) error {
|
|||
// verify that our state matches the servers signed state
|
||||
// this is *not* a security check, as a rogue server can simply sign any state
|
||||
// however committing to a state allows us to build fraud proofs for malicious servers later on.
|
||||
if !ed25519.Verify(as.identity.PublicKey(), as.LatestCommit, state.SignedProof) {
|
||||
if ed25519.Verify(as.identity.PublicKey(), as.LatestCommit, state.SignedProof) == false {
|
||||
return errors.New("state is not consistent, the server is malicious")
|
||||
}
|
||||
return nil
|
||||
|
@ -159,7 +158,7 @@ func (as *Store) MergeState(state State) error {
|
|||
// and not the cause (which could be reordered messages, dropped messages, additional messages or any combination)
|
||||
func (as *Store) VerifyFraudProof(fraudCommit []byte, signedFraudProof SignedProof, key ed25519.PublicKey) (bool, error) {
|
||||
|
||||
if !ed25519.Verify(key, fraudCommit, signedFraudProof) {
|
||||
if ed25519.Verify(key, fraudCommit, signedFraudProof) == false {
|
||||
// This could happen due to misuse of this function (trying to verify a proof with the wrong public key)
|
||||
// This could happen if the server lies to us and submits a fake state proof, however we cannot use this to
|
||||
// prove that the server is acting maliciously
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package auditable
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/persistence"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/persistence"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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)
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConstraintSystem(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
cs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
|
||||
abytes := sha512.Sum512([]byte("a"))
|
||||
a := new(ristretto.Scalar)
|
||||
a.FromUniformBytes(abytes[:])
|
||||
|
||||
bbytes := sha512.Sum512([]byte("b"))
|
||||
b := new(ristretto.Scalar)
|
||||
b.FromUniformBytes(bbytes[:])
|
||||
|
||||
xbytes := sha512.Sum512([]byte("b"))
|
||||
x := new(ristretto.Scalar)
|
||||
x.FromUniformBytes(xbytes[:])
|
||||
|
||||
ybytes := sha512.Sum512([]byte("a"))
|
||||
y := new(ristretto.Scalar)
|
||||
y.FromUniformBytes(ybytes[:])
|
||||
|
||||
prng := core.NewTranscript("private").CommitToPRNG("private")
|
||||
|
||||
V1, a_lc := cs.Commit(a, prng.Next())
|
||||
V2, b_lc := cs.Commit(b, prng.Next())
|
||||
V3, x_lc := cs.Commit(x, prng.Next())
|
||||
V4, y_lc := cs.Commit(y, prng.Next())
|
||||
|
||||
vcs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
va_lc := vcs.VerifierCommit(V1)
|
||||
vb_lc := vcs.VerifierCommit(V2)
|
||||
vx_lc := vcs.VerifierCommit(V3)
|
||||
vy_lc := vcs.VerifierCommit(V4)
|
||||
|
||||
_, _, in := cs.Multiply(a_lc, b_lc)
|
||||
_, _, out := cs.Multiply(x_lc, y_lc)
|
||||
cs.Constrain(in.Sub(out))
|
||||
|
||||
_, _, vin := vcs.Multiply(va_lc, vb_lc)
|
||||
_, _, vout := vcs.Multiply(vx_lc, vy_lc)
|
||||
vcs.Constrain(vin.Sub(vout))
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(core.One())
|
||||
|
||||
lhs := new(ristretto.Scalar)
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aL, wL))
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aR, wR))
|
||||
|
||||
rhs := new(ristretto.Scalar)
|
||||
rhs.Add(rhs, core.InnerProduct(cs.aO, wO))
|
||||
rrhs := new(ristretto.Scalar)
|
||||
rrhs.Add(rrhs, core.InnerProduct(cs.v, wV))
|
||||
|
||||
t.Logf("lhs: %v\n", lhs)
|
||||
t.Logf("rhs: %v\n", rhs)
|
||||
t.Logf("rrhs: %v\n", rrhs)
|
||||
t.Logf("\nwL: %v\nwR: %v\nwO: %v\nwC: %v", wL, wR, wO, wV)
|
||||
|
||||
proof := cs.Prove(cs.params, core.NewTranscript(""))
|
||||
|
||||
t.Logf("Proof Result: %v", vcs.Verify(proof, cs.params, core.NewTranscript("")))
|
||||
}
|
||||
|
||||
func TestConstraintSystemMix(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
cs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
|
||||
one := core.One()
|
||||
two := new(ristretto.Scalar).Add(one, one)
|
||||
three := new(ristretto.Scalar).Add(two, one)
|
||||
four := new(ristretto.Scalar).Add(two, two)
|
||||
|
||||
prng := core.NewTranscript("private").CommitToPRNG("private")
|
||||
|
||||
V1, a_lc := cs.Commit(three, prng.Next())
|
||||
V2, b_lc := cs.Commit(three, prng.Next())
|
||||
V3, x_lc := cs.Commit(two, prng.Next())
|
||||
V4, y_lc := cs.Commit(four, prng.Next())
|
||||
|
||||
// todo make this an actual verifier!
|
||||
cs.VerifierCommit(V1)
|
||||
cs.VerifierCommit(V2)
|
||||
cs.VerifierCommit(V3)
|
||||
cs.VerifierCommit(V4)
|
||||
|
||||
_, _, out := cs.Multiply(a_lc.Sub(x_lc).Add(b_lc.Sub(y_lc)), x_lc.Add(y_lc).Sub(a_lc.Add(b_lc)))
|
||||
_, _, out2 := cs.Multiply(a_lc.Sub(x_lc).Add(b_lc.Sub(y_lc)), x_lc.Add(y_lc).Sub(a_lc.Add(b_lc)))
|
||||
cs.Constrain(out.Add(out2))
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(core.One())
|
||||
|
||||
lhs := new(ristretto.Scalar)
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aL, wL))
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aR, wR))
|
||||
|
||||
rhs := new(ristretto.Scalar)
|
||||
rhs.Add(rhs, core.InnerProduct(cs.aO, wO))
|
||||
rrhs := new(ristretto.Scalar)
|
||||
rrhs.Add(rrhs, core.InnerProduct(cs.v, wV))
|
||||
|
||||
t.Logf("lhs: %v\n", lhs)
|
||||
t.Logf("rhs: %v\n", rhs)
|
||||
t.Logf("rrhs: %v\n", rrhs)
|
||||
t.Logf("\nwL: %v\nwR: %v\nwO: %v\nwC: %v", wL, wR, wO, wV)
|
||||
|
||||
proof := cs.Prove(cs.params, core.NewTranscript(""))
|
||||
t.Logf("Proof Result: %v", cs.Verify(proof, cs.params, core.NewTranscript("")))
|
||||
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
type Variable struct {
|
||||
Enum string
|
||||
Index int
|
||||
}
|
||||
|
||||
type Term struct {
|
||||
Variable
|
||||
Coefficient *ristretto.Scalar
|
||||
}
|
||||
|
||||
// LinearCombination represent a vector of Terms
|
||||
// We use the same structure as dalek-bulletproofs and reference the variable by an index into the canonical
|
||||
// term in the constraint system.
|
||||
type LinearCombination struct {
|
||||
Terms []Term
|
||||
}
|
||||
|
||||
// Adding together linear combinations lc+olc = lc[:]+olc[:]
|
||||
// (a * -1) + (b * -1) = (a*-1 + b * -1)
|
||||
func (lc *LinearCombination) Add(olc *LinearCombination) *LinearCombination {
|
||||
terms := lc.Terms
|
||||
for _, term := range olc.Terms {
|
||||
terms = append(terms, term)
|
||||
}
|
||||
return &LinearCombination{terms}
|
||||
}
|
||||
|
||||
// Subtracting a linear combination olc from lc results in the terms of lc being extended
|
||||
// with those of lc, but with the coefficients in old being negated.
|
||||
// (a * -1 - b * -1) = (a*-1 + b * 1)
|
||||
func (lc *LinearCombination) Sub(olc *LinearCombination) *LinearCombination {
|
||||
terms := lc.Terms
|
||||
for _, term := range olc.Terms {
|
||||
term.Coefficient = new(ristretto.Scalar).Negate(term.Coefficient)
|
||||
terms = append(terms, term)
|
||||
}
|
||||
return &LinearCombination{terms}
|
||||
}
|
||||
|
||||
// ConstraintSystem allow constructing constraint systems over committed parameters, generating a proof of that constraint
|
||||
// and allows a verifier to check that the constraint holds.
|
||||
type ConstraintSystem struct {
|
||||
aL core.ScalarVector
|
||||
aR core.ScalarVector
|
||||
aO core.ScalarVector
|
||||
|
||||
v core.ScalarVector
|
||||
vBlinding core.ScalarVector
|
||||
constraints []*LinearCombination
|
||||
|
||||
V core.GeneratorVector // todo clarify this
|
||||
|
||||
params CommitmentsParams
|
||||
}
|
||||
|
||||
func NewConstrainSystem(params CommitmentsParams) ConstraintSystem {
|
||||
cs := ConstraintSystem{}
|
||||
cs.params = params
|
||||
return cs
|
||||
}
|
||||
|
||||
// Multiply constructs a multiplication gate within our constraint system.
|
||||
// It takes in two linear combinations a and b, evaluates the result c, and outputs three linear combinations
|
||||
// one for the left input wire (a), one of the right input wire b, and one for the output wire (c)
|
||||
// a and b are constrained to (a*-1) and (b*-1)
|
||||
func (cs *ConstraintSystem) Multiply(a, b *LinearCombination) (*LinearCombination, *LinearCombination, *LinearCombination) {
|
||||
|
||||
l := cs.eval(a)
|
||||
r := cs.eval(b)
|
||||
|
||||
c := new(ristretto.Scalar).Multiply(l, r)
|
||||
|
||||
log.Debugf("%v & %v == %v", l, r, c)
|
||||
|
||||
a.Terms = append(a.Terms, Term{Variable{"left", len(cs.aL)}, new(ristretto.Scalar).Negate(core.One())})
|
||||
b.Terms = append(b.Terms, Term{Variable{"right", len(cs.aR)}, new(ristretto.Scalar).Negate(core.One())})
|
||||
|
||||
o := &LinearCombination{[]Term{{Variable{"output", len(cs.aO)}, core.One()}}}
|
||||
|
||||
cs.aL = append(cs.aL, l)
|
||||
cs.aR = append(cs.aR, r)
|
||||
cs.aO = append(cs.aO, c)
|
||||
|
||||
cs.Constrain(a)
|
||||
cs.Constrain(b)
|
||||
|
||||
return a, b, o
|
||||
|
||||
}
|
||||
|
||||
// eval evaluates a linear combination lc, by looking up each term in the relevant vector in the constrain system
|
||||
// and multiplying it by it's coefficient.
|
||||
func (cs *ConstraintSystem) eval(lc *LinearCombination) *ristretto.Scalar {
|
||||
result := new(ristretto.Scalar)
|
||||
for _, term := range lc.Terms {
|
||||
switch term.Enum {
|
||||
case "left":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aL[term.Index]))
|
||||
case "right":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aR[term.Index]))
|
||||
case "output":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aO[term.Index]))
|
||||
case "committed":
|
||||
if len(cs.V) > 0 {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, core.One()))
|
||||
} else {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.v[term.Index]))
|
||||
}
|
||||
case "one":
|
||||
result.Add(result, term.Coefficient)
|
||||
default:
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (cs *ConstraintSystem) Commit(v *ristretto.Scalar, vBlind *ristretto.Scalar) (*ristretto.Element, *LinearCombination) {
|
||||
i := len(cs.v)
|
||||
cs.v = append(cs.v, v)
|
||||
cs.vBlinding = append(cs.vBlinding, vBlind)
|
||||
|
||||
V := core.MultiExp(core.ScalarVector{v, vBlind}, core.GeneratorVector{cs.params.g, cs.params.h})
|
||||
|
||||
return V, &LinearCombination{[]Term{{Variable{"committed", i}, core.One()}}}
|
||||
}
|
||||
|
||||
func (cs *ConstraintSystem) VerifierCommit(V *ristretto.Element) *LinearCombination {
|
||||
i := len(cs.V)
|
||||
cs.V = append(cs.V, V)
|
||||
return &LinearCombination{[]Term{{Variable{"committed", i}, core.One()}}}
|
||||
}
|
||||
|
||||
// Constrain adds the given linear combination to the constraints vector
|
||||
// when constraints are flattened, each constraint will be evaluated and determine the
|
||||
// weights to add.
|
||||
func (cs *ConstraintSystem) Constrain(c *LinearCombination) {
|
||||
cs.constraints = append(cs.constraints, c)
|
||||
}
|
||||
|
||||
// flatten constructs a set of 4 vectors of weights wL, wR, wO and wV representing the left inputs (L), right inputs (R), outputs (O) and
|
||||
// committed values (C) such that
|
||||
// <aL,wL> + <aR,wR> + <aO, wO> = <v,WL>
|
||||
func (cs *ConstraintSystem) flatten(z *ristretto.Scalar) (wL core.ScalarVector, wR core.ScalarVector, wO core.ScalarVector, wV core.ScalarVector) {
|
||||
wL = make(core.ScalarVector, len(cs.aL))
|
||||
wR = make(core.ScalarVector, len(cs.aL))
|
||||
wO = make(core.ScalarVector, len(cs.aL))
|
||||
var m int
|
||||
if len(cs.V) > 0 {
|
||||
m = len(cs.V)
|
||||
wV = make(core.ScalarVector, len(cs.V))
|
||||
} else {
|
||||
m = len(cs.v)
|
||||
wV = make(core.ScalarVector, len(cs.v))
|
||||
}
|
||||
|
||||
for i := 0; i < len(cs.aL); i++ {
|
||||
wL[i] = new(ristretto.Scalar)
|
||||
wR[i] = new(ristretto.Scalar)
|
||||
wO[i] = new(ristretto.Scalar)
|
||||
}
|
||||
for i := 0; i < m; i++ {
|
||||
wV[i] = new(ristretto.Scalar)
|
||||
}
|
||||
|
||||
expZ := new(ristretto.Scalar).Add(z, new(ristretto.Scalar).Zero())
|
||||
for _, constraint := range cs.constraints {
|
||||
for _, term := range constraint.Terms {
|
||||
log.Debugf("term: %v", term)
|
||||
switch term.Enum {
|
||||
case "left":
|
||||
wL[term.Index].Add(wL[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "right":
|
||||
wR[term.Index].Add(wR[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "output":
|
||||
wO[term.Index].Add(wO[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "committed":
|
||||
wV[term.Index].Subtract(wV[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
default:
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
expZ = expZ.Multiply(expZ, z)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ConstraintProof struct {
|
||||
Ai *ristretto.Element
|
||||
Ao *ristretto.Element
|
||||
S *ristretto.Element
|
||||
T1 *ristretto.Element
|
||||
T3 *ristretto.Element
|
||||
T4 *ristretto.Element
|
||||
T5 *ristretto.Element
|
||||
T6 *ristretto.Element
|
||||
Micro *ristretto.Scalar
|
||||
TX *ristretto.Scalar
|
||||
TX_Blinding *ristretto.Scalar
|
||||
IPP InnerProductProof
|
||||
}
|
||||
|
||||
// Prove the constraint
|
||||
func (cs *ConstraintSystem) Prove(c CommitmentsParams, transcript *core.Transcript) ConstraintProof {
|
||||
|
||||
n := len(cs.aL)
|
||||
|
||||
log.Debugf("n = %v\n", n)
|
||||
|
||||
// Generate a prng to from this transcript and some external randomness
|
||||
// We use this to generate the rest of our private scalars
|
||||
// TODO: move to transcript
|
||||
private := make([]byte, 64)
|
||||
rand.Read(private)
|
||||
prngTranscript := core.NewTranscript("private-transcript")
|
||||
prngTranscript.AddToTranscript("randomness", private)
|
||||
prng := prngTranscript.CommitToPRNG(transcript.OutputTranscriptToAudit())
|
||||
|
||||
alpha := prng.Next()
|
||||
beta := prng.Next()
|
||||
rho := prng.Next()
|
||||
|
||||
Ai := core.MultiExp(append(cs.aL.Join(cs.aR), alpha), append(c.G.Join(c.H), c.h))
|
||||
Ao := core.MultiExp(cs.aO.SafeAppend(beta), c.G.SafeAppend(c.h))
|
||||
|
||||
Sl := make(core.ScalarVector, c.max)
|
||||
Sr := make(core.ScalarVector, c.max)
|
||||
for i := 0; i < c.max; i++ {
|
||||
Sl[i] = prng.Next()
|
||||
Sr[i] = prng.Next()
|
||||
}
|
||||
|
||||
S := core.MultiExp(append(Sl.Join(Sr), rho), c.G.Join(c.H).SafeAppend(c.h))
|
||||
|
||||
transcript.AddToTranscript("Ai", []byte(Ai.String()))
|
||||
transcript.AddToTranscript("So", []byte(Ao.String()))
|
||||
transcript.AddToTranscript("S", []byte(S.String()))
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
yinv := new(ristretto.Scalar).Invert(y)
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
log.Debugf("Calculating y^%v", n)
|
||||
powY := core.PowerVector(y, n)
|
||||
powYInv := core.PowerVector(yinv, n)
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(z)
|
||||
|
||||
lhs := make([]core.ScalarVector, 4)
|
||||
//lhs[0] = 0
|
||||
lhs[1] = core.EntrywiseSum(cs.aL, core.EntryWiseProduct(powYInv, wR))
|
||||
lhs[2] = cs.aO
|
||||
lhs[3] = Sl
|
||||
|
||||
rhs := make([]core.ScalarVector, 4)
|
||||
rhs[0] = core.EntrywiseSub(wO, powY)
|
||||
rhs[1] = core.EntrywiseSum(core.EntryWiseProduct(powY, cs.aR), wL)
|
||||
// r2 = 0
|
||||
rhs[3] = core.EntryWiseProduct(powY, Sr)
|
||||
|
||||
t1 := core.InnerProduct(lhs[1], rhs[0])
|
||||
t2 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[1], rhs[1]), core.InnerProduct(lhs[2], rhs[0]))
|
||||
t3 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[2], rhs[1]), core.InnerProduct(lhs[3], rhs[0]))
|
||||
t4 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[1], rhs[3]), core.InnerProduct(lhs[3], rhs[1]))
|
||||
t5 := core.InnerProduct(lhs[2], rhs[3])
|
||||
t6 := core.InnerProduct(lhs[3], rhs[3])
|
||||
|
||||
//t2 := core.InnerProduct(cs.aL,core.EntryWiseProduct(cs.aR, powY))
|
||||
//t2.Subtract(t2, core.InnerProduct(cs.aO, powY))
|
||||
//t2.Add(t2, )
|
||||
|
||||
//
|
||||
tau1 := prng.Next()
|
||||
tau3 := prng.Next()
|
||||
tau4 := prng.Next()
|
||||
tau5 := prng.Next()
|
||||
tau6 := prng.Next()
|
||||
|
||||
T1 := core.MultiExp(core.ScalarVector{t1, tau1}, core.GeneratorVector{c.g, c.h})
|
||||
T3 := core.MultiExp(core.ScalarVector{t3, tau3}, core.GeneratorVector{c.g, c.h})
|
||||
T4 := core.MultiExp(core.ScalarVector{t4, tau4}, core.GeneratorVector{c.g, c.h})
|
||||
T5 := core.MultiExp(core.ScalarVector{t5, tau5}, core.GeneratorVector{c.g, c.h})
|
||||
T6 := core.MultiExp(core.ScalarVector{t6, tau6}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
transcript.AddToTranscript("T1", []byte(T1.String()))
|
||||
transcript.AddToTranscript("T3", []byte(T3.String()))
|
||||
transcript.AddToTranscript("T4", []byte(T4.String()))
|
||||
transcript.AddToTranscript("T5", []byte(T5.String()))
|
||||
transcript.AddToTranscript("T6", []byte(T6.String()))
|
||||
|
||||
//u := transcript.CommitToTranscriptScalar("u")
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
log.Debugf("x: %v", x)
|
||||
|
||||
// T(X) = t0 + t1x + t2x
|
||||
TX := new(ristretto.Scalar).Zero()
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t1, x))
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t2, x2))
|
||||
x3 := new(ristretto.Scalar).Multiply(x2, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t3, x3))
|
||||
x4 := new(ristretto.Scalar).Multiply(x3, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t4, x4))
|
||||
x5 := new(ristretto.Scalar).Multiply(x4, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t5, x5))
|
||||
x6 := new(ristretto.Scalar).Multiply(x5, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t6, x6))
|
||||
|
||||
// evaluate l(X)
|
||||
// lx0 = 0
|
||||
lx1 := core.VectorMulScalar(lhs[1], x)
|
||||
lx2 := core.VectorMulScalar(lhs[2], x2)
|
||||
lx3 := core.VectorMulScalar(lhs[3], new(ristretto.Scalar).Multiply(x2, x))
|
||||
lx := core.EntrywiseSum(core.EntrywiseSum(lx1, lx2), lx3)
|
||||
|
||||
// evaluate r(X)
|
||||
rx1 := core.VectorMulScalar(rhs[1], x)
|
||||
// rx2 := core.VectorMulScalar(rhs[2],x2 ) rhs[2] == 0
|
||||
rx3 := core.VectorMulScalar(rhs[3], new(ristretto.Scalar).Multiply(x2, x))
|
||||
rx := core.EntrywiseSum(core.EntrywiseSum(rx1, rhs[0]), rx3)
|
||||
|
||||
// calculate the inner product (t̂)
|
||||
iplr := core.InnerProduct(lx, rx)
|
||||
|
||||
// T(x) ?= <l(X),r(X)>
|
||||
log.Debugf("T(X) = %v", TX)
|
||||
log.Debugf("ipp = %v", iplr)
|
||||
log.Debugf("equal: %v", TX.Equal(iplr) == 1)
|
||||
|
||||
tau_blind := new(ristretto.Scalar).Multiply(tau1, x)
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau3, x3))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau4, x4))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau5, x5))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau6, x6))
|
||||
|
||||
//delta := core.InnerProduct(core.EntryWiseProduct(powYInv,wR), wL)
|
||||
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(core.InnerProduct(wV, cs.vBlinding), x2))
|
||||
|
||||
micro := new(ristretto.Scalar).Multiply(alpha, x)
|
||||
micro.Add(micro, new(ristretto.Scalar).Multiply(beta, x2))
|
||||
micro.Add(micro, new(ristretto.Scalar).Multiply(rho, x3))
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(powY[i]), c.H[i])
|
||||
}
|
||||
|
||||
P := core.MultiExp(lx.Join(rx), c.G.Join(H_))
|
||||
log.Debugf("P: %v", P)
|
||||
|
||||
uP := new(ristretto.Element).Add(P, new(ristretto.Element).ScalarMult(iplr, c.u))
|
||||
log.Debugf("uP: %v", uP)
|
||||
ipp := ProveInnerProduct(lx, rx, c.u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
|
||||
return ConstraintProof{Ai, Ao, S, T1, T3, T4, T5, T6, micro, TX, tau_blind, ipp}
|
||||
}
|
||||
|
||||
// Verify the constraint
|
||||
func (cs *ConstraintSystem) Verify(proof ConstraintProof, c CommitmentsParams, transcript *core.Transcript) bool {
|
||||
|
||||
log.Debugf("Verifying: %v\n", proof)
|
||||
|
||||
n := len(cs.aL)
|
||||
transcript.AddToTranscript("Ai", []byte(proof.Ai.String()))
|
||||
transcript.AddToTranscript("So", []byte(proof.Ao.String()))
|
||||
transcript.AddToTranscript("S", []byte(proof.S.String()))
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
yinv := new(ristretto.Scalar).Invert(y)
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
log.Debugf("Calculating y^%v", n)
|
||||
powY := core.PowerVector(y, n)
|
||||
powYInv := core.PowerVector(yinv, n)
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(z)
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(powYInv[i], c.H[i])
|
||||
}
|
||||
|
||||
Wl := core.MultiExp(wL, H_)
|
||||
Wr := core.MultiExp(core.EntryWiseProduct(powYInv, wR), c.G)
|
||||
Wo := core.MultiExp(wO, H_)
|
||||
|
||||
transcript.AddToTranscript("T1", []byte(proof.T1.String()))
|
||||
transcript.AddToTranscript("T3", []byte(proof.T3.String()))
|
||||
transcript.AddToTranscript("T4", []byte(proof.T4.String()))
|
||||
transcript.AddToTranscript("T5", []byte(proof.T5.String()))
|
||||
transcript.AddToTranscript("T6", []byte(proof.T6.String()))
|
||||
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
log.Debugf("x: %v", x)
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
x3 := new(ristretto.Scalar).Multiply(x2, x)
|
||||
x4 := new(ristretto.Scalar).Multiply(x3, x)
|
||||
x5 := new(ristretto.Scalar).Multiply(x4, x)
|
||||
x6 := new(ristretto.Scalar).Multiply(x5, x)
|
||||
|
||||
HY := core.MultiExp(core.VectorNegate(powY), H_)
|
||||
P := core.MultiExp(core.ScalarVector{x, x2, x3, x, x, core.One()}, core.GeneratorVector{proof.Ai, proof.Ao, proof.S, Wl, Wr, Wo})
|
||||
P.Add(P, HY)
|
||||
|
||||
P_ := P.Subtract(P, new(ristretto.Element).ScalarMult(proof.Micro, c.h))
|
||||
uP := new(ristretto.Element).Add(P_, new(ristretto.Element).ScalarMult(proof.TX, c.u))
|
||||
|
||||
log.Debugf("P: %v", P_)
|
||||
log.Debugf("uP: %v", uP)
|
||||
|
||||
lhs := core.MultiExp(core.ScalarVector{proof.TX, proof.TX_Blinding}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
delta := core.InnerProduct(core.EntryWiseProduct(powYInv, wR), wL)
|
||||
|
||||
rhs := core.MultiExp(core.VectorMulScalar(wV, x2).Join(core.ScalarVector{x, x3, x4, x5, x6, new(ristretto.Scalar).Multiply(x2, delta)}),
|
||||
cs.V.Join(core.GeneratorVector{proof.T1, proof.T3, proof.T4, proof.T5, proof.T6, c.g}))
|
||||
|
||||
log.Debugf("lhs: %v", lhs)
|
||||
log.Debugf("rhs: %v", rhs)
|
||||
//rhs := core.MultiExp(,cs.)
|
||||
|
||||
return lhs.Equal(rhs) == 1 && Verify(proof.IPP, n, c.u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package bulletproofs
|
||||
|
||||
type Generator struct {
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/binary"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
// InnerProductProof encapsulates an inner product proof
|
||||
type InnerProductProof struct {
|
||||
L core.PointVector
|
||||
R core.PointVector
|
||||
A *ristretto.Scalar
|
||||
B *ristretto.Scalar
|
||||
}
|
||||
|
||||
// ProveInnerProduct generates a proof for <a,b>, the inner product of a and b
|
||||
func ProveInnerProduct(a, b core.ScalarVector, u *ristretto.Element, P *ristretto.Element, G, H core.GeneratorVector, transcript *core.Transcript) InnerProductProof {
|
||||
n := len(a)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("ipp v1"))
|
||||
nb := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(nb, uint64(n))
|
||||
transcript.AddToTranscript("n", nb)
|
||||
|
||||
Lvec := core.PointVector{}
|
||||
Rvec := core.PointVector{}
|
||||
for n != 1 {
|
||||
np := n / 2
|
||||
aL, aR := a[:np], a[np:]
|
||||
bL, bR := b[:np], b[np:]
|
||||
GL, GR := G[:np], G[np:]
|
||||
HL, HR := H[:np], H[np:]
|
||||
|
||||
cL := core.InnerProduct(aL, bR)
|
||||
cR := core.InnerProduct(aR, bL)
|
||||
|
||||
L := core.MultiExp(append(aL.Join(bR), cL), append(GR.Join(HL), u))
|
||||
R := core.MultiExp(append(aR.Join(bL), cR), append(GL.Join(HR), u))
|
||||
|
||||
transcript.AddElementToTranscript("L", L)
|
||||
Lvec = append(Lvec, L)
|
||||
transcript.AddElementToTranscript("R", R)
|
||||
Rvec = append(Rvec, R)
|
||||
|
||||
u := transcript.CommitToTranscriptScalar("u")
|
||||
uinv := new(ristretto.Scalar)
|
||||
uinv.Invert(u)
|
||||
|
||||
for i := 0; i < len(aL); i++ {
|
||||
aL_ := new(ristretto.Scalar).Multiply(aL[i], u)
|
||||
aL[i] = new(ristretto.Scalar).Add(aL_, new(ristretto.Scalar).Multiply(aR[i], uinv))
|
||||
bL_ := new(ristretto.Scalar).Multiply(bL[i], uinv)
|
||||
bL[i] = new(ristretto.Scalar).Add(bL_, new(ristretto.Scalar).Multiply(bR[i], u))
|
||||
|
||||
GL[i] = core.MultiExp(core.ScalarVector{uinv, u}, core.GeneratorVector{GL[i], GR[i]})
|
||||
HL[i] = core.MultiExp(core.ScalarVector{u, uinv}, core.GeneratorVector{HL[i], HR[i]})
|
||||
}
|
||||
|
||||
x2 := new(ristretto.Scalar).Multiply(u, u)
|
||||
P_ := new(ristretto.Element).ScalarMult(x2, L)
|
||||
P_.Add(P_, P)
|
||||
P_.Add(P_, new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(x2), R))
|
||||
P = P_
|
||||
//ranscript.AddToTranscript("P'", []byte(P.String()))
|
||||
|
||||
a = aL
|
||||
b = bL
|
||||
G = GL
|
||||
H = HL
|
||||
n = np
|
||||
}
|
||||
|
||||
return InnerProductProof{Lvec, Rvec, a[0], b[0]}
|
||||
}
|
||||
|
||||
// Verify checks the given inner product proof
|
||||
func Verify(proof InnerProductProof, n int, u, P *ristretto.Element, G, H core.GeneratorVector, transcript *core.Transcript) bool {
|
||||
np := n / 2
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("ipp v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(n))
|
||||
transcript.AddToTranscript("n", b)
|
||||
for i := range proof.L {
|
||||
GL, GR := G[:np], G[np:]
|
||||
HL, HR := H[:np], H[np:]
|
||||
|
||||
transcript.AddElementToTranscript("L", proof.L[i])
|
||||
transcript.AddElementToTranscript("R", proof.R[i])
|
||||
x := transcript.CommitToTranscriptScalar("u")
|
||||
|
||||
log.Debugf("L: %x\n", proof.L[i].Encode([]byte{}))
|
||||
log.Debugf("R %x\n", proof.R[i].Encode([]byte{}))
|
||||
log.Debugf("u: %x\n", x.Encode([]byte{}))
|
||||
xinv := new(ristretto.Scalar)
|
||||
xinv.Invert(x)
|
||||
|
||||
for j := 0; j < np; j++ {
|
||||
GL[j] = core.MultiExp(core.ScalarVector{xinv, x}, core.GeneratorVector{GL[j], GR[j]})
|
||||
HL[j] = core.MultiExp(core.ScalarVector{x, xinv}, core.GeneratorVector{HL[j], HR[j]})
|
||||
}
|
||||
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
P_ := new(ristretto.Element).ScalarMult(x2, proof.L[i])
|
||||
P_.Add(P_, P)
|
||||
P_.Add(P_, new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(x2), proof.R[i]))
|
||||
P = P_
|
||||
G = GL
|
||||
H = HL
|
||||
np = np / 2
|
||||
}
|
||||
c := new(ristretto.Scalar)
|
||||
c.Multiply(proof.A, proof.B)
|
||||
P_ := core.MultiExp(core.ScalarVector{proof.A, proof.B, c}, core.GeneratorVector{G[0], H[0], u})
|
||||
log.Debugf("P:%v\nP':%v\n", P, P_)
|
||||
return P.Equal(P_) == 1
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assert(t *testing.T, expected *ristretto.Scalar, actual *ristretto.Scalar) {
|
||||
if expected.Equal(actual) == 1 {
|
||||
t.Logf("inner_product matched: %v", actual)
|
||||
} else {
|
||||
t.Fatalf("c should be %v instead: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_inner_product(t *testing.T) {
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
a := core.ScalarVector{one, zero, one, zero}
|
||||
b := core.ScalarVector{zero, one, zero, one}
|
||||
c := core.InnerProduct(a, b)
|
||||
assert(t, zero, c)
|
||||
|
||||
a = core.ScalarVector{one, one, one, zero}
|
||||
b = core.ScalarVector{one, one, zero, one}
|
||||
c = core.InnerProduct(a, b)
|
||||
|
||||
check := new(ristretto.Scalar)
|
||||
check.Add(one, one)
|
||||
|
||||
assert(t, check, c)
|
||||
|
||||
}
|
||||
|
||||
func TestProveInnerProduct(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
a := core.ScalarVector{one, zero, one, one}
|
||||
b := core.ScalarVector{zero, one, one, one}
|
||||
|
||||
proverTranscript := core.NewTranscript("test_innerproductproof")
|
||||
verifierTranscript := core.NewTranscript("test_innerproductproof")
|
||||
|
||||
G := proverTranscript.CommitToGenerators("G", 4)
|
||||
H := proverTranscript.CommitToGenerators("H", 4)
|
||||
u := proverTranscript.CommitToGenerator("u")
|
||||
|
||||
verifierTranscript.CommitToGenerators("G", 4)
|
||||
verifierTranscript.CommitToGenerators("H", 4)
|
||||
verifierTranscript.CommitToGenerator("u")
|
||||
|
||||
c := core.InnerProduct(a, b)
|
||||
|
||||
P_ := core.MultiExp(append(a.Join(b), c), append(core.GeneratorVector(G).Join(core.GeneratorVector(H)), u))
|
||||
|
||||
proof := ProveInnerProduct(a, b, u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), P_), core.CopyVector(G), core.CopyVector(H), proverTranscript)
|
||||
|
||||
if Verify(proof, 4, u, P_, core.CopyVector(G), core.CopyVector(H), verifierTranscript) {
|
||||
t.Logf("Inner Product Proof Passed!")
|
||||
} else {
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
t.Fatalf("Inner Product Proof Failed!")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/binary"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// RangeProof encapsulates a proof that V = [0, max) where max is defined by the Setup function
|
||||
type RangeProof struct {
|
||||
A *ristretto.Element
|
||||
S *ristretto.Element
|
||||
T1 *ristretto.Element
|
||||
T2 *ristretto.Element
|
||||
TauX *ristretto.Scalar
|
||||
InnerProduct *ristretto.Scalar
|
||||
Mu *ristretto.Scalar
|
||||
IPP InnerProductProof
|
||||
V []*ristretto.Element
|
||||
}
|
||||
|
||||
// CommitmentsParams encapsulates the commitment parameters for a given range proof
|
||||
type CommitmentsParams struct {
|
||||
G core.GeneratorVector
|
||||
H core.GeneratorVector
|
||||
u *ristretto.Element
|
||||
g *ristretto.Element
|
||||
h *ristretto.Element
|
||||
max int
|
||||
}
|
||||
|
||||
// Setup generates multiple independent commitment parameters from the underlying transcript
|
||||
func Setup(max int, transcript *core.Transcript) (c CommitmentsParams) {
|
||||
c.G = transcript.CommitToGenerators("G", int(max))
|
||||
c.H = transcript.CommitToGenerators("H", int(max))
|
||||
|
||||
c.u = transcript.CommitToGenerator("u")
|
||||
c.g = transcript.CommitToGenerator("g")
|
||||
c.h = transcript.CommitToGenerator("h")
|
||||
|
||||
c.max = max
|
||||
return
|
||||
}
|
||||
|
||||
func Rand(seed string) *ristretto.Scalar {
|
||||
t := core.NewTranscript(seed)
|
||||
return t.CommitToTranscriptScalar("seed")
|
||||
}
|
||||
|
||||
// GenerateRangeProof creates a valid rangeproof that value is within [0,max) under the given transcript
|
||||
// It returns the rangeproof and a random scalar "gamma" that can be used to open V, the commitment to v vGgH
|
||||
func GenerateRangeProof(value int32, c CommitmentsParams, transcript *core.Transcript) (RangeProof, *ristretto.Scalar) {
|
||||
one := core.IdentityVector(1)[0]
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, c.max)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("rangeproof v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(c.max))
|
||||
transcript.AddToTranscript("n", b)
|
||||
b = make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, 1)
|
||||
transcript.AddToTranscript("m", b)
|
||||
|
||||
// Generate a prng to from this transcript and some external randomness
|
||||
// We use this to generate the rest of our private scalars
|
||||
// TODO: move to transcript
|
||||
private := make([]byte, 64)
|
||||
rand.Read(private)
|
||||
prngTranscript := core.NewTranscript("private-transcript")
|
||||
prngTranscript.AddToTranscript("randomness", private)
|
||||
prng := prngTranscript.CommitToPRNG(transcript.OutputTranscriptToAudit())
|
||||
|
||||
gamma := prng.Next()
|
||||
|
||||
aL := valueToVector(value, c.max)
|
||||
aR := core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(one))
|
||||
alpha := prng.Next()
|
||||
|
||||
vs := new(ristretto.Scalar)
|
||||
b = make([]byte, 32)
|
||||
copy(b, big.NewInt(int64(value)).Bytes())
|
||||
vs.Decode(b)
|
||||
|
||||
V := core.MultiExp(core.ScalarVector{gamma, vs}, core.GeneratorVector{c.h, c.g})
|
||||
|
||||
transcript.AddElementToTranscript("V", V)
|
||||
|
||||
A := core.MultiExp(append(aL.Join(aR), alpha), append(c.G.Join(c.H), c.h))
|
||||
log.Debugf("vs: %v", vs)
|
||||
|
||||
Sl := make(core.ScalarVector, c.max)
|
||||
Sr := make(core.ScalarVector, c.max)
|
||||
for i := 0; i < c.max; i++ {
|
||||
Sl[i] = prng.Next()
|
||||
Sr[i] = prng.Next()
|
||||
}
|
||||
p := prng.Next()
|
||||
|
||||
S := core.MultiExp(append(Sl.Join(Sr), p), append(c.G.Join(c.H), c.h))
|
||||
|
||||
transcript.AddElementToTranscript("A", A)
|
||||
transcript.AddElementToTranscript("S", S)
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
y_n := core.PowerVector(y, c.max)
|
||||
z2 := new(ristretto.Scalar).Multiply(z, z)
|
||||
|
||||
l0 := core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(z))
|
||||
//l1 == Sr
|
||||
r0 := core.EntrywiseSum(core.EntryWiseProduct(y_n, core.VectorAddScalar(aR, z)), core.VectorMulScalar(two_n, z2))
|
||||
r1 := core.EntryWiseProduct(Sr, y_n)
|
||||
|
||||
t0 := new(ristretto.Scalar).Add(new(ristretto.Scalar).Multiply(z2, vs), delta(y_n, z, c.max))
|
||||
t1 := new(ristretto.Scalar)
|
||||
t1.Add(core.InnerProduct(Sl, r0), core.InnerProduct(l0, r1))
|
||||
t2 := core.InnerProduct(Sl, r1)
|
||||
|
||||
tau1 := prng.Next()
|
||||
tau2 := prng.Next()
|
||||
|
||||
T1 := core.MultiExp(core.ScalarVector{t1, tau1}, core.GeneratorVector{c.g, c.h})
|
||||
T2 := core.MultiExp(core.ScalarVector{t2, tau2}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
transcript.AddElementToTranscript("T_1", T1)
|
||||
transcript.AddElementToTranscript("T_2", T2)
|
||||
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
|
||||
// T(X) = t0 + t1x + t2x
|
||||
TX := new(ristretto.Scalar)
|
||||
TX.Add(new(ristretto.Scalar).Zero(), t0)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t1, x))
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t2, new(ristretto.Scalar).Multiply(x, x)))
|
||||
|
||||
l := core.EntrywiseSum(core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(z)), core.VectorMulScalar(Sl, x))
|
||||
_r := core.EntrywiseSum(core.VectorAddScalar(aR, z), core.VectorMulScalar(Sr, x))
|
||||
r := core.EntrywiseSum(core.EntryWiseProduct(y_n, _r), core.VectorMulScalar(two_n, z2))
|
||||
|
||||
iplr := core.InnerProduct(l, r)
|
||||
|
||||
log.Debugf("T(X) = %v", TX)
|
||||
log.Debugf("ipp = %v", iplr)
|
||||
log.Debugf("equal: %v", TX.Equal(iplr) == 1)
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(y_n[i]), c.H[i])
|
||||
}
|
||||
|
||||
P := core.MultiExp(l.Join(r), c.G.Join(H_))
|
||||
log.Debugf("P: %v", P)
|
||||
|
||||
mu := new(ristretto.Scalar)
|
||||
mu.Add(alpha, new(ristretto.Scalar).Multiply(p, x))
|
||||
|
||||
taux := new(ristretto.Scalar)
|
||||
taux.Multiply(tau2, new(ristretto.Scalar).Multiply(x, x))
|
||||
taux.Add(taux, new(ristretto.Scalar).Multiply(tau1, x))
|
||||
taux.Add(taux, new(ristretto.Scalar).Multiply(z2, gamma))
|
||||
|
||||
transcript.AddToTranscript("t_x", iplr.Encode([]byte{}))
|
||||
transcript.AddToTranscript("t_x_blinding", taux.Encode([]byte{}))
|
||||
transcript.AddToTranscript("e_blinding", mu.Encode([]byte{}))
|
||||
w := transcript.CommitToTranscriptScalar("w")
|
||||
|
||||
U := new(ristretto.Element).ScalarMult(w, c.g)
|
||||
|
||||
uP := new(ristretto.Element).Add(P, new(ristretto.Element).ScalarMult(iplr, U))
|
||||
log.Debugf("uP: %v", uP)
|
||||
ipp := ProveInnerProduct(l, r, U, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
|
||||
return RangeProof{A, S, T1, T2, taux, iplr, mu, ipp, []*ristretto.Element{V}}, gamma
|
||||
}
|
||||
|
||||
// VerifyRangeProof returns true if the given proof passes all the checks for a given set of commitment parameters
|
||||
// and the given transcript
|
||||
func VerifyRangeProof(proof RangeProof, c CommitmentsParams, transcript *core.Transcript) bool {
|
||||
one := core.IdentityVector(1)[0]
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, c.max)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("rangeproof v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(c.max))
|
||||
transcript.AddToTranscript("n", b)
|
||||
b = make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, 1)
|
||||
transcript.AddToTranscript("m", b)
|
||||
|
||||
for _, v := range proof.V {
|
||||
transcript.AddElementToTranscript("V", v)
|
||||
}
|
||||
|
||||
transcript.AddElementToTranscript("A", proof.A)
|
||||
transcript.AddElementToTranscript("S", proof.S)
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
transcript.AddElementToTranscript("T_1", proof.T1)
|
||||
transcript.AddElementToTranscript("T_2", proof.T2)
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
|
||||
transcript.AddToTranscript("t_x", proof.InnerProduct.Encode([]byte{}))
|
||||
transcript.AddToTranscript("t_x_blinding", proof.TauX.Encode([]byte{}))
|
||||
transcript.AddToTranscript("e_blinding", proof.Mu.Encode([]byte{}))
|
||||
log.Debugf("mu: %x", proof.Mu.Encode([]byte{}))
|
||||
|
||||
log.Debugf("x: %x", x.Encode([]byte{}))
|
||||
y_n := core.PowerVector(y, c.max)
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(y_n[i]), c.H[i])
|
||||
}
|
||||
|
||||
// check t = t(x) = t0 + t1.x + t1.x^2
|
||||
lhs := core.MultiExp(core.ScalarVector{proof.InnerProduct, proof.TauX}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
z2 := new(ristretto.Scalar)
|
||||
z2.Multiply(z, z)
|
||||
|
||||
x2 := new(ristretto.Scalar)
|
||||
x2.Multiply(x, x)
|
||||
|
||||
rhs := core.MultiExp(core.ScalarVector{z2, delta(y_n, z, c.max), x, x2}, core.GeneratorVector(proof.V).Join(core.GeneratorVector{c.g, proof.T1, proof.T2}))
|
||||
log.Debugf("lhs: %v", lhs)
|
||||
log.Debugf("rhs: %v", rhs)
|
||||
log.Debugf("equal: %v", lhs.Equal(rhs))
|
||||
|
||||
if lhs.Equal(rhs) == 1 {
|
||||
|
||||
// compute P
|
||||
|
||||
negz := new(ristretto.Scalar).Negate(z)
|
||||
negzG := new(ristretto.Element).Zero()
|
||||
for _, gen := range c.G {
|
||||
negzG = negzG.Add(negzG, new(ristretto.Element).ScalarMult(negz, gen))
|
||||
}
|
||||
|
||||
mul := core.EntrywiseSum(core.VectorMulScalar(y_n, z), core.VectorMulScalar(two_n, new(ristretto.Scalar).Multiply(z, z)))
|
||||
|
||||
Pr := new(ristretto.Element).Add(new(ristretto.Element).Zero(), proof.A)
|
||||
Pr = Pr.Add(Pr, new(ristretto.Element).ScalarMult(x, proof.S))
|
||||
Pr = Pr.Add(Pr, negzG)
|
||||
Pr = Pr.Add(Pr, core.MultiExp(mul, H_))
|
||||
|
||||
Pl := new(ristretto.Element).Subtract(Pr, new(ristretto.Element).ScalarMult(proof.Mu, c.h))
|
||||
// check inner product
|
||||
|
||||
w := transcript.CommitToTranscriptScalar("w")
|
||||
U := new(ristretto.Element).ScalarMult(w, c.g)
|
||||
|
||||
uP := new(ristretto.Element).Add(Pl, new(ristretto.Element).ScalarMult(proof.InnerProduct, U))
|
||||
|
||||
log.Debugf("P: %v", Pl)
|
||||
log.Debugf("uP: %v", uP)
|
||||
|
||||
return Verify(proof.IPP, c.max, U, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func delta(y_n core.ScalarVector, z *ristretto.Scalar, max int) *ristretto.Scalar {
|
||||
one := core.IdentityVector(1)[0]
|
||||
// (z-z^2)
|
||||
z2 := new(ristretto.Scalar).Multiply(z, z)
|
||||
result := new(ristretto.Scalar)
|
||||
result.Subtract(z, z2)
|
||||
// (z-z^2) * <1^n,y^n>
|
||||
result.Multiply(result, core.InnerProduct(core.IdentityVector(max), y_n))
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, max)
|
||||
// (z-z^2) * <1^n,y^n> - z^3 *<1n,2n>
|
||||
z3 := new(ristretto.Scalar).Multiply(z2, z)
|
||||
result.Subtract(result, new(ristretto.Scalar).Multiply(z3, core.InnerProduct(core.IdentityVector(max), two_n)))
|
||||
return result
|
||||
}
|
||||
|
||||
func valueToVector(value int32, max int) core.ScalarVector {
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
result := core.ScalarVector{}
|
||||
for len(result) != max {
|
||||
v := value & 0x0001
|
||||
if v == 1 {
|
||||
result = append(result, one)
|
||||
} else {
|
||||
result = append(result, zero)
|
||||
}
|
||||
value = value >> 1
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"github.com/gtank/ristretto255"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProove(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
proverTranscript := core.NewTranscript("rangeproof")
|
||||
verifierTranscript := core.NewTranscript("rangeproof")
|
||||
|
||||
rangeproof, _ := GenerateRangeProof(10, Setup(32, proverTranscript), proverTranscript)
|
||||
if VerifyRangeProof(rangeproof, Setup(32, verifierTranscript), verifierTranscript) == true {
|
||||
t.Logf("Range Proof Passed!")
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
jsonProof, _ := json.Marshal(rangeproof)
|
||||
t.Logf("RangeProof: %s", jsonProof)
|
||||
} else {
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
t.Fatalf("Failed to Verify Range Proof")
|
||||
}
|
||||
}
|
||||
|
||||
func byteToPoint(in []byte) *ristretto255.Element {
|
||||
element := ristretto255.NewElement()
|
||||
element.Decode(in)
|
||||
return element
|
||||
}
|
||||
|
||||
func byteToScalar(in []byte) *ristretto255.Scalar {
|
||||
scalar := ristretto255.NewScalar()
|
||||
scalar.Decode(in[0:32])
|
||||
return scalar
|
||||
}
|
||||
|
||||
func decodeInnerProduct(in []byte) *InnerProductProof {
|
||||
num_elements := len(in) / 32
|
||||
lg_n := (num_elements - 2) / 2
|
||||
lvec := make(core.PointVector, lg_n)
|
||||
rvec := make(core.PointVector, lg_n)
|
||||
for i := 0; i < lg_n; i++ {
|
||||
pos := 2 * i * 32
|
||||
lvec[i] = byteToPoint(in[pos : pos+32])
|
||||
rvec[i] = byteToPoint(in[pos+32 : pos+64])
|
||||
}
|
||||
pos := 2 * lg_n * 32
|
||||
a := byteToScalar(in[pos : pos+32])
|
||||
b := byteToScalar(in[pos+32 : pos+64])
|
||||
return &InnerProductProof{lvec, rvec, a, b}
|
||||
}
|
||||
|
||||
func TestDalek(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
t.Logf("Testing dalek-cryptography bulletproofs test vector...")
|
||||
rp, _ := hex.DecodeString("46b6ea8b6a9710c41c2622d4b353dbcf5f89afe8ed66c469f192bec19dc71d23c0442827f97fc9085a89caa87d294b0a21e7b8957732ec4951f6bf7d3aa2c66e7af3b7b956c7dcb3bed1223575a217a30642b603b6bf1d4138ed95e3458c524510b42c8d82958f40b447a84242b1ba1eeea54013f80bad643048eeb0b17c292a057cb6ae1c42338837c05eaa6336a17d60fa141204e015a1df15b28c1318c709d7eb35569cde89c0bf37eace54880a151498b38da54c6d739564f46f01b73601e518355ea06c9ef58a45fcb3baadbd1ac54e0838c471a6b91845f123d569fa0c46ef94471b7b826230e8576146beec08ac3e6683998815c576581f4c0e493433480f95f6495210636eaa2e32b577e1c363e35e522db85b18a56d57eb626f9e2b50578e0d7ee7b74b328e158b366bb9d117db725820a2fec3b1508212d75823345a801c0b602bfa05919d7e3bb8e71944587072badc363f334b08ba90d13e077ad24b82bacd51fc668d2b880daabd3b87e6bdc9584af66523026a30aadfc359283891bb65cca502f47421ffeee1fb5a5237bfa965b66a8b8ca5d6954f4f8222244c6a5340dc81e8d781d092cae2a763f185dd0b89965b1dd2506807b5d3e5a305fd9a68e60b91389dcffae6f85538713aa7ed272b8174e2f0b9730ebb6c464d06")
|
||||
|
||||
t.Logf("Deserializing dalek-cryptography bulletproofs test vector...%x", rp)
|
||||
|
||||
A := byteToPoint(rp[0:32])
|
||||
S := byteToPoint(rp[32:64])
|
||||
T1 := byteToPoint(rp[64:96])
|
||||
T2 := byteToPoint(rp[96:128])
|
||||
|
||||
TX := byteToScalar(rp[128:160])
|
||||
TX_blinding := byteToScalar(rp[160:192])
|
||||
micro := byteToScalar(rp[192 : 192+32])
|
||||
|
||||
ipp := decodeInnerProduct(rp[192+32:])
|
||||
|
||||
vbytes := []string{
|
||||
"90b0c2fe57934dff9f5396e135e7d72b82b3c5393e1843178918eb2cf28a5f3c",
|
||||
"74256a3e2a7fe948210c4095195ae4db3e3498c6c5fddc2afb226c0f1e97e468",
|
||||
"7e348def6d03dc7bcbe7e03736ca2898e2efa9f6ff8ae4ed1cb5252ec1744075",
|
||||
"861859f5d4c14f5d6d7ad88dcf43c9a98064a7d8702ffc9bad9eba2ed766702a",
|
||||
"4c09b1260c833fefe25b1c3d3becc80979beca5e864d57fcb410bb15c7ba5c14",
|
||||
"08cf26bfdf2e6b731536f5e48b4c0ac7b5fc846d36aaa3fe0d28f07c207f0814",
|
||||
"a6e2d1c2770333c9a8a5ac10d9eb28e8609d5954428261335b2fd6ff0e0e8d69",
|
||||
"30beef3b58fd2c18dde771d5c77e32f8dc01361e284aef517bce54a5c74c4665",
|
||||
}
|
||||
|
||||
V := make([]*ristretto255.Element, len(vbytes))
|
||||
for i, v := range vbytes {
|
||||
V[i] = ristretto255.NewElement()
|
||||
vdec, _ := hex.DecodeString(v)
|
||||
V[i].Decode(vdec)
|
||||
}
|
||||
|
||||
rangeProof := RangeProof{A, S, T1, T2, TX_blinding, TX, micro, *ipp, V[0:1]}
|
||||
|
||||
json, _ := json.Marshal(rangeProof)
|
||||
t.Logf("RangeProof: %s", json)
|
||||
|
||||
t.Logf("Deserialized Range Proof: %s", json)
|
||||
|
||||
t.Logf("Generating dalek-cryptography pedersen generators....")
|
||||
params := CommitmentsParams{}
|
||||
params.g = ristretto255.NewElement().Base()
|
||||
params.h = ristretto255.NewElement()
|
||||
h := sha3.Sum512(params.g.Encode([]byte{}))
|
||||
|
||||
params.h = ristretto255.NewElement().FromUniformBytes(h[:])
|
||||
|
||||
params.max = 8
|
||||
params.G = make(core.GeneratorVector, params.max)
|
||||
params.H = make(core.GeneratorVector, params.max)
|
||||
|
||||
labelG := []byte{'G', 0, 0, 0, 0}
|
||||
shake := sha3.NewShake256()
|
||||
shake.Write([]byte("GeneratorsChain"))
|
||||
shake.Write(labelG[:])
|
||||
|
||||
labelH := []byte{'H', 0, 0, 0, 0}
|
||||
shakeH := sha3.NewShake256()
|
||||
shakeH.Write([]byte("GeneratorsChain"))
|
||||
shakeH.Write(labelH[:])
|
||||
|
||||
t.Logf("Generating dalek-cryptography BP generators....")
|
||||
for i := 0; i < 8; i++ {
|
||||
b := make([]byte, 64)
|
||||
shake.Read(b)
|
||||
params.G[i] = ristretto255.NewElement()
|
||||
params.G[i].FromUniformBytes(b)
|
||||
|
||||
//t.Logf("G: %x", params.G[i].Encode([]byte{}))
|
||||
|
||||
bH := make([]byte, 64)
|
||||
shakeH.Read(bH)
|
||||
params.H[i] = ristretto255.NewElement()
|
||||
params.H[i].FromUniformBytes(bH)
|
||||
|
||||
// t.Logf("H: %x", params.H[i].Encode([]byte{}))
|
||||
|
||||
}
|
||||
|
||||
//t.Logf("parmas: %v", params)
|
||||
|
||||
verifierTranscript := core.NewTranscript("Deserialize-And-Verify Test")
|
||||
t.Logf("Verification Result: %v", VerifyRangeProof(rangeProof, params, verifierTranscript))
|
||||
|
||||
t.Logf("Transcript: %s\n", verifierTranscript.OutputTranscriptToAudit())
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
// ScalarVector explicit type checking
|
||||
type ScalarVector []*ristretto.Scalar
|
||||
|
||||
// ElementVector explicit type checking
|
||||
type PointVector []*ristretto.Element
|
||||
|
||||
// GeneratorVector explicit type checking
|
||||
type GeneratorVector []*ristretto.Element
|
||||
|
||||
// CopyVector safely copies a vector
|
||||
func CopyVector(G GeneratorVector) GeneratorVector {
|
||||
H := make(GeneratorVector, len(G))
|
||||
for i, g := range G {
|
||||
H[i] = new(ristretto.Element).Add(new(ristretto.Element).Zero(), g)
|
||||
}
|
||||
return H
|
||||
}
|
||||
|
||||
// InnerProduct takes the inner product of a and b i.e. <a,b>
|
||||
func InnerProduct(a, b ScalarVector) *ristretto.Scalar {
|
||||
if len(a) != len(b) {
|
||||
panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(b)))
|
||||
}
|
||||
|
||||
result := new(ristretto.Scalar).Zero()
|
||||
for i, ai := range a {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(ai, b[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MultiExp takes in a vector of scalars = {a,b,c...} and a vector of generator = {A,B,C...} and outputs
|
||||
// {aA,bB,cC}
|
||||
func MultiExp(a ScalarVector, G GeneratorVector) *ristretto.Element {
|
||||
if len(a) > len(G) {
|
||||
panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(G)))
|
||||
}
|
||||
result := new(ristretto.Element).Zero()
|
||||
for i, ai := range a {
|
||||
aG := new(ristretto.Element).ScalarMult(ai, G[i])
|
||||
result = new(ristretto.Element).Add(result, aG)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Join is defined for a vector of Scalars
|
||||
func (a ScalarVector) SafeAppend(b *ristretto.Scalar) ScalarVector {
|
||||
list := make(ScalarVector, len(a)+1)
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
list[len(a)] = b
|
||||
return list
|
||||
}
|
||||
|
||||
// Join is defined for a vector of Scalars
|
||||
func (a ScalarVector) Join(b ScalarVector) ScalarVector {
|
||||
list := make(ScalarVector, len(a)+len(b))
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
for i := len(a); i < len(a)+len(b); i++ {
|
||||
list[i] = b[i-len(a)]
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Join as defined for a vector of Generators
|
||||
func (a GeneratorVector) SafeAppend(b *ristretto.Element) GeneratorVector {
|
||||
list := make(GeneratorVector, len(a)+1)
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
list[len(a)] = b
|
||||
return list
|
||||
}
|
||||
|
||||
// Join as defined for a vector of Generators
|
||||
func (a GeneratorVector) Join(b GeneratorVector) GeneratorVector {
|
||||
list := make(GeneratorVector, len(a)+len(b))
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
for i := len(a); i < len(a)+len(b); i++ {
|
||||
list[i] = b[i-len(a)]
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// VectorAddScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {a+s,b+s,c+s....}
|
||||
func VectorAddScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Add(vector[i], scalar)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// VectorNegate takes in a vector v = {a,b,c..} and a scalar s and outputs {-a,-b,-c}
|
||||
func VectorNegate(vector ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar).Negate(vector[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// VectorMulScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {as,bs,cs....}
|
||||
func VectorMulScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(vector[i], scalar)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntrywiseSum takes the entry wise sum of two vectors
|
||||
func EntrywiseSum(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Add(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntrywiseSubtract takes the entry wise sum of two vectors
|
||||
func EntrywiseSub(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Subtract(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntryWiseProduct takes the entry wise product of two vectors
|
||||
func EntryWiseProduct(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// One returns a ristretto scalar == 1
|
||||
func One() *ristretto.Scalar {
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
return one
|
||||
}
|
||||
|
||||
// IdentityVector is a convenience function to generate a vector v = {1,1,1...1}
|
||||
func IdentityVector(n int) ScalarVector {
|
||||
result := make(ScalarVector, n)
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = one
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PowerVector creates a vector v = {1,x,x^2,x^3..x^n}
|
||||
func PowerVector(x *ristretto.Scalar, n int) ScalarVector {
|
||||
result := make(ScalarVector, n)
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
result[0] = one
|
||||
result[1] = x
|
||||
for i := 2; i < n; i++ {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(result[i-1], x)
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -2,7 +2,6 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"github.com/gtank/merlin"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
@ -12,10 +11,12 @@ import (
|
|||
// Transcript provides a consistent transcript primitive for our protocols
|
||||
//
|
||||
// We have the following goals:
|
||||
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
|
||||
// - be able to produce a human-readable transcript for auditing.
|
||||
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
|
||||
// - be able to produce a human-readable transcript for auditing.
|
||||
//
|
||||
// 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 {
|
||||
merlinTranscript *merlin.Transcript
|
||||
transcript string
|
||||
|
@ -39,7 +40,7 @@ func (t *Transcript) AddToTranscript(label string, b []byte) {
|
|||
// 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.Bytes())
|
||||
t.AddToTranscript(label, element.Encode([]byte{}))
|
||||
}
|
||||
|
||||
// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
|
||||
|
@ -57,8 +58,8 @@ func (t *Transcript) NewProtocol(label string) {
|
|||
|
||||
// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
|
||||
func (t *Transcript) CommitToTranscript(label string) []byte {
|
||||
//t.AddToTranscript("commit", []byte(label))
|
||||
b := t.merlinTranscript.ExtractBytes([]byte(label), 64)
|
||||
t.transcript = fmt.Sprintf("%v\nextract %v: %v", t.transcript, label, b)
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -68,14 +69,12 @@ type PRNG struct {
|
|||
}
|
||||
|
||||
// Next returns the next "random" scalar from the PRNG
|
||||
func (prng *PRNG) Next(buf []byte, next *ristretto.Scalar) error {
|
||||
n, err := io.ReadFull(prng.prng, buf)
|
||||
if n != 64 || err != nil {
|
||||
log.Errorf("could not read prng: %v %v", n, err)
|
||||
return fmt.Errorf("error fetching complete output from prng: %v", err)
|
||||
}
|
||||
next.SetUniformBytes(buf)
|
||||
return nil
|
||||
func (prng *PRNG) Next() *ristretto.Scalar {
|
||||
buf := [64]byte{}
|
||||
io.ReadFull(prng.prng, buf[:])
|
||||
next := new(ristretto.Scalar)
|
||||
next.FromUniformBytes(buf[:])
|
||||
return next
|
||||
}
|
||||
|
||||
// CommitToPRNG commits the label to the transcript and derives a PRNG from the transcript.
|
||||
|
@ -89,8 +88,7 @@ func (t *Transcript) CommitToPRNG(label string) PRNG {
|
|||
// CommitToGenerator derives a verifiably random generator from the transcript
|
||||
func (t *Transcript) CommitToGenerator(label string) *ristretto.Element {
|
||||
c := t.CommitToTranscript(label)
|
||||
result, _ := new(ristretto.Element).SetUniformBytes(c)
|
||||
return result
|
||||
return new(ristretto.Element).FromUniformBytes(c)
|
||||
}
|
||||
|
||||
// CommitToGenerators derives a set of verifiably random generators from the transcript
|
||||
|
@ -105,6 +103,6 @@ func (t *Transcript) CommitToGenerators(label string, n int) (generators []*rist
|
|||
func (t *Transcript) CommitToTranscriptScalar(label string) *ristretto.Scalar {
|
||||
c := t.CommitToTranscript(label)
|
||||
s := new(ristretto.Scalar)
|
||||
s.SetUniformBytes(c[:])
|
||||
s.FromUniformBytes(c[:])
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -11,9 +11,7 @@ func TestNewTranscript(t *testing.T) {
|
|||
|
||||
transcript.AddToTranscript("action", []byte("test data"))
|
||||
|
||||
firstAudit := transcript.OutputTranscriptToAudit()
|
||||
secondAudit := transcript.OutputTranscriptToAudit()
|
||||
if firstAudit != secondAudit {
|
||||
if transcript.OutputTranscriptToAudit() != transcript.OutputTranscriptToAudit() {
|
||||
t.Fatalf("Multiple Audit Calls should not impact underlying Transcript")
|
||||
}
|
||||
t.Logf("%v", transcript.OutputTranscriptToAudit())
|
||||
|
|
|
@ -2,8 +2,7 @@ package primitives
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/utils"
|
||||
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
|
@ -43,14 +42,14 @@ func (i *Identity) PublicKey() ed25519.PublicKey {
|
|||
}
|
||||
|
||||
// EDH performs a diffie-hellman operation on this identities private key with the given public key.
|
||||
func (i *Identity) EDH(key ed25519.PublicKey) ([]byte, error) {
|
||||
secret, err := utils.EDH(*i.edpk, key)
|
||||
return secret[:], err
|
||||
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
||||
secret := utils.EDH(*i.edpk, key)
|
||||
return secret[:]
|
||||
}
|
||||
|
||||
// Hostname provides the onion address associated with this Identity.
|
||||
func (i *Identity) Hostname() string {
|
||||
return torProvider.GetTorV3Hostname(*i.edpubk)
|
||||
return utils.GetTorV3Hostname(*i.edpubk)
|
||||
}
|
||||
|
||||
// Sign produces a signature for a given message attributable to the given identity
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package primitives
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -10,28 +9,9 @@ func TestIdentity_EDH(t *testing.T) {
|
|||
id1, _ := InitializeEphemeralIdentity()
|
||||
id2, _ := InitializeEphemeralIdentity()
|
||||
|
||||
k1, err1 := id1.EDH(id2.PublicKey())
|
||||
k2, err2 := id2.EDH(id1.PublicKey())
|
||||
k1 := id1.EDH(id2.PublicKey())
|
||||
k2 := id2.EDH(id1.PublicKey())
|
||||
|
||||
if err1 == nil && err2 == nil && subtle.ConstantTimeCompare(k1, k2) == 1 {
|
||||
t.Logf("k1: %x\nk2: %x\n", k1, k2)
|
||||
} else {
|
||||
t.Fatalf("The derived keys should be identical")
|
||||
}
|
||||
t.Logf("k1: %x\nk2: %x\n", k1, k2)
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkEDH(b *testing.B) {
|
||||
id1, _ := InitializeEphemeralIdentity()
|
||||
id2, _ := InitializeEphemeralIdentity()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
k1, err1 := id1.EDH(id2.PublicKey())
|
||||
k2, err2 := id2.EDH(id1.PublicKey())
|
||||
if err1 == nil && err2 == nil && subtle.ConstantTimeCompare(k1, k2) == 1 {
|
||||
//b.Logf("k1: %x\nk2: %x\n", k1, k2)
|
||||
} else {
|
||||
b.Fatalf("The derived keys should be identical")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
package privacypass
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"github.com/bwesterb/go-ristretto"
|
||||
)
|
||||
|
||||
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
|
||||
// gut 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 {
|
||||
C *ristretto.Scalar
|
||||
S *ristretto.Scalar
|
||||
}
|
||||
|
||||
// DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript
|
||||
// Given Y = kX & Q = kP
|
||||
// Given P = kX, Q = kP, Y=kX
|
||||
// Peggy: t := choose randomly from Zq
|
||||
//
|
||||
// A := tX
|
||||
// B := tP
|
||||
// c := H(transcript(X,Y,P,Q,A,B))
|
||||
// s := (t + ck) mod q
|
||||
// A := tX
|
||||
// B := tP
|
||||
// c := H(transcript(X,Y,P,Q,A,B))
|
||||
// s := (t + ck) mod q
|
||||
//
|
||||
// Sends c,s to Vicky
|
||||
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Element, Y *ristretto.Element, P *ristretto.Element, Q *ristretto.Element, transcript *core.Transcript) DLEQProof {
|
||||
private := make([]byte, 64)
|
||||
rand.Read(private)
|
||||
t, err := new(ristretto.Scalar).SetUniformBytes(private)
|
||||
if err != nil {
|
||||
return DLEQProof{ristretto.NewScalar(), ristretto.NewScalar()}
|
||||
}
|
||||
A := new(ristretto.Element).ScalarMult(t, X)
|
||||
B := new(ristretto.Element).ScalarMult(t, P)
|
||||
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) DLEQProof {
|
||||
t := new(ristretto.Scalar).Rand()
|
||||
A := new(ristretto.Point).ScalarMult(X, t)
|
||||
B := new(ristretto.Point).ScalarMult(P, t)
|
||||
|
||||
transcript.AddToTranscript(DLEQX, X.Bytes())
|
||||
transcript.AddToTranscript(DLEQY, Y.Bytes())
|
||||
|
@ -41,31 +34,29 @@ func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Element, Y *r
|
|||
transcript.AddToTranscript(DLEQB, B.Bytes())
|
||||
|
||||
c := transcript.CommitToTranscriptScalar("c")
|
||||
s := new(ristretto.Scalar).Subtract(t, new(ristretto.Scalar).Multiply(c, k))
|
||||
s := new(ristretto.Scalar).Sub(t, new(ristretto.Scalar).Mul(c, k))
|
||||
return DLEQProof{c, s}
|
||||
}
|
||||
|
||||
// VerifyDiscreteLogEquivalenceProof verifies the DLEQ for the given parameters and transcript
|
||||
// Given Y = kX & Q = kP and Proof = (c,s)
|
||||
// Given P = kX, Q = kP, Y=kX, and Proof = (c,s)
|
||||
// Vicky: X' := sX
|
||||
//
|
||||
// Y' := cY
|
||||
// P' := sP
|
||||
// Q' := cQ
|
||||
// A' = X'+Y' == sX + cY ?= sG + ckG == (s+ck)X == tX == A
|
||||
// B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B
|
||||
// c' := H(transcript(X,Y,P,Q,A',B'))
|
||||
//
|
||||
// Y' := cY
|
||||
// P' := sP
|
||||
// Q' := cQ
|
||||
// A' = X'+Y' == sX + cY ?= sX + ckX == (s+ck)X == tX == A
|
||||
// B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B
|
||||
// c' := H(transcript(X,Y,P,Q,A',B'))
|
||||
// Tests c ?= c
|
||||
func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Element, Y *ristretto.Element, P *ristretto.Element, Q *ristretto.Element, transcript *core.Transcript) bool {
|
||||
func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) bool {
|
||||
|
||||
Xs := new(ristretto.Element).ScalarMult(dleq.S, X)
|
||||
Yc := new(ristretto.Element).ScalarMult(dleq.C, Y)
|
||||
Ps := new(ristretto.Element).ScalarMult(dleq.S, P)
|
||||
Qc := new(ristretto.Element).ScalarMult(dleq.C, Q)
|
||||
Xs := new(ristretto.Point).ScalarMult(X, dleq.S)
|
||||
Yc := new(ristretto.Point).ScalarMult(Y, dleq.C)
|
||||
Ps := new(ristretto.Point).ScalarMult(P, dleq.S)
|
||||
Qc := new(ristretto.Point).ScalarMult(Q, dleq.C)
|
||||
|
||||
A := new(ristretto.Element).Add(Xs, Yc)
|
||||
B := new(ristretto.Element).Add(Ps, Qc)
|
||||
A := new(ristretto.Point).Add(Xs, Yc)
|
||||
B := new(ristretto.Point).Add(Ps, Qc)
|
||||
|
||||
transcript.AddToTranscript(DLEQX, X.Bytes())
|
||||
transcript.AddToTranscript(DLEQY, Y.Bytes())
|
||||
|
@ -74,5 +65,5 @@ func VerifyDiscreteLogEquivalenceProof(dleq DLEQProof, X *ristretto.Element, Y *
|
|||
transcript.AddToTranscript(DLEQA, A.Bytes())
|
||||
transcript.AddToTranscript(DLEQB, B.Bytes())
|
||||
|
||||
return transcript.CommitToTranscriptScalar("c").Equal(dleq.C) == 1
|
||||
return transcript.CommitToTranscriptScalar("c").Equals(dleq.C)
|
||||
}
|
||||
|
|
|
@ -3,12 +3,10 @@ package privacypass
|
|||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"github.com/bwesterb/go-ristretto"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
|
@ -17,22 +15,17 @@ import (
|
|||
type Token struct {
|
||||
t []byte
|
||||
r *ristretto.Scalar
|
||||
W *ristretto.Element
|
||||
}
|
||||
|
||||
// GetT returns the underlying bytes for token for use in constraint proofs.
|
||||
func (t Token) GetT() []byte {
|
||||
return t.t
|
||||
W *ristretto.Point
|
||||
}
|
||||
|
||||
// BlindedToken encapsulates a Blinded Token
|
||||
type BlindedToken struct {
|
||||
P *ristretto.Element
|
||||
P *ristretto.Point
|
||||
}
|
||||
|
||||
// SignedToken encapsulates a Signed (Blinded) Token
|
||||
type SignedToken struct {
|
||||
Q *ristretto.Element
|
||||
Q *ristretto.Point
|
||||
}
|
||||
|
||||
// SpentToken encapsulates the parameters needed to spend a Token
|
||||
|
@ -44,8 +37,7 @@ type SpentToken struct {
|
|||
// TokenPaymentHandler defines an interface with external payment processors
|
||||
type TokenPaymentHandler interface {
|
||||
MakePayment()
|
||||
// Next Token
|
||||
NextToken(data []byte, hostname string) (SpentToken, error)
|
||||
NextToken(data []byte) (SpentToken, error)
|
||||
}
|
||||
|
||||
// GenBlindedToken initializes the Token
|
||||
|
@ -53,28 +45,25 @@ type TokenPaymentHandler interface {
|
|||
func (t *Token) GenBlindedToken() BlindedToken {
|
||||
t.t = make([]byte, 32)
|
||||
rand.Read(t.t)
|
||||
t.r = new(ristretto.Scalar)
|
||||
b := make([]byte, 64)
|
||||
rand.Read(b)
|
||||
t.r.SetUniformBytes(b)
|
||||
t.r = new(ristretto.Scalar).Rand()
|
||||
|
||||
Ht := sha3.Sum512(t.t)
|
||||
T, _ := new(ristretto.Element).SetUniformBytes(Ht[:])
|
||||
P := new(ristretto.Element).ScalarMult(t.r, T)
|
||||
Ht := sha3.Sum256(t.t)
|
||||
log.Debugf("token: %x", Ht)
|
||||
T := new(ristretto.Point).SetElligator(&Ht)
|
||||
P := new(ristretto.Point).ScalarMult(T, t.r)
|
||||
return BlindedToken{P}
|
||||
}
|
||||
|
||||
// unblindSignedToken unblinds a token that has been signed by a server
|
||||
func (t *Token) unblindSignedToken(token SignedToken) {
|
||||
t.W = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(t.r), token.Q)
|
||||
t.W = new(ristretto.Point).ScalarMult(token.Q, new(ristretto.Scalar).Inverse(t.r))
|
||||
}
|
||||
|
||||
// SpendToken binds the token with data and then redeems the token
|
||||
func (t *Token) SpendToken(data []byte) SpentToken {
|
||||
key := sha3.Sum256(append(t.t, t.W.Bytes()...))
|
||||
mac := hmac.New(sha3.New512, key[:])
|
||||
mac.Write(data)
|
||||
return SpentToken{t.t, mac.Sum(nil)}
|
||||
return SpentToken{t.t, mac.Sum(data)}
|
||||
}
|
||||
|
||||
// GenerateBlindedTokenBatch generates a batch of blinded tokens (and their unblinded equivalents)
|
||||
|
@ -87,35 +76,28 @@ func GenerateBlindedTokenBatch(num int) (tokens []*Token, blindedTokens []Blinde
|
|||
}
|
||||
|
||||
// verifyBatchProof verifies a given batch proof (see also UnblindSignedTokenBatch)
|
||||
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Element, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
|
||||
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Point, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
|
||||
transcript.NewProtocol(BatchProofProtocol)
|
||||
transcript.AddToTranscript(BatchProofX, ristretto.NewGeneratorElement().Bytes())
|
||||
transcript.AddToTranscript(BatchProofX, new(ristretto.Point).SetBase().Bytes())
|
||||
transcript.AddToTranscript(BatchProofY, Y.Bytes())
|
||||
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
||||
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
||||
prng := transcript.CommitToPRNG("w")
|
||||
M := ristretto.NewIdentityElement()
|
||||
Z := ristretto.NewIdentityElement()
|
||||
buf := make([]byte, 64)
|
||||
c := new(ristretto.Scalar)
|
||||
M := new(ristretto.Point).SetZero()
|
||||
Z := new(ristretto.Point).SetZero()
|
||||
for i := range blindedTokens {
|
||||
err := prng.Next(buf, c)
|
||||
if err != nil {
|
||||
log.Errorf("error verifying batch proof: %v", err)
|
||||
return false
|
||||
}
|
||||
M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M)
|
||||
Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z)
|
||||
c := prng.Next()
|
||||
M = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(blindedTokens[i].P, c), M)
|
||||
Z = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(signedTokens[i].Q, c), Z)
|
||||
}
|
||||
return VerifyDiscreteLogEquivalenceProof(dleq, ristretto.NewGeneratorElement(), Y, M, Z, transcript)
|
||||
return VerifyDiscreteLogEquivalenceProof(dleq, new(ristretto.Point).SetBase(), 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
|
||||
// verifies that the signing procedure has taken place correctly and unblinds the tokens.
|
||||
func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, signedTokens []SignedToken, Y *ristretto.Element, proof DLEQProof, transcript *core.Transcript) bool {
|
||||
func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, signedTokens []SignedToken, Y *ristretto.Point, proof DLEQProof, transcript *core.Transcript) bool {
|
||||
verified := verifyBatchProof(proof, Y, blindedTokens, signedTokens, transcript)
|
||||
if !verified {
|
||||
log.Debugf("Failed to unblind tokens: %v", transcript.OutputTranscriptToAudit())
|
||||
return false
|
||||
}
|
||||
for i, t := range tokens {
|
||||
|
@ -123,16 +105,3 @@ func UnblindSignedTokenBatch(tokens []*Token, blindedTokens []BlindedToken, sign
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalJSON - in order to store tokens in a serialized form we need to expose the private, unexported value
|
||||
// `t`. Note that `r` is not needed to spend the token, and as such we effectively destroy it when we serialize.
|
||||
// Ideally, go would let us do this with an annotation, alas.
|
||||
func (t Token) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
T []byte `json:"t"`
|
||||
W *ristretto.Element
|
||||
}{
|
||||
T: t.t,
|
||||
W: t.W,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package privacypass
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/persistence"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"github.com/gtank/ristretto255"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"cwtch.im/tapir/persistence"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -34,59 +31,17 @@ 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).SetUniformBytes(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, err := server.SignBlindedTokenBatchWithConstraint([]BlindedToken{blindedToken2}, token.t, core.NewTranscript(""))
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error signing tokens with constraints")
|
||||
}
|
||||
|
||||
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) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
db := new(persistence.BoltPersistence)
|
||||
db.Open("tokens.db")
|
||||
|
||||
fakeRand := sha512.Sum512([]byte{})
|
||||
k, _ := ristretto255.NewScalar().SetUniformBytes(fakeRand[:])
|
||||
server := NewTokenServerFromStore(k, db)
|
||||
defer server.Close()
|
||||
server := NewTokenServerFromStore(db)
|
||||
|
||||
clientTranscript := core.NewTranscript("privacyPass")
|
||||
serverTranscript := core.NewTranscript("privacyPass")
|
||||
|
||||
tokens, blindedTokens := GenerateBlindedTokenBatch(10)
|
||||
batchProof, err := server.SignBlindedTokenBatch(blindedTokens, serverTranscript)
|
||||
if err != nil {
|
||||
t.Fatalf("error constructing signed/blinded token batch: %v", err)
|
||||
}
|
||||
batchProof := server.SignBlindedTokenBatch(blindedTokens, serverTranscript)
|
||||
|
||||
verified := UnblindSignedTokenBatch(tokens, blindedTokens, batchProof.SignedTokens, server.Y, batchProof.Proof, clientTranscript)
|
||||
|
||||
|
@ -110,5 +65,5 @@ func TestGenerateBlindedTokenBatch(t *testing.T) {
|
|||
if verified {
|
||||
t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit())
|
||||
}
|
||||
|
||||
db.Close()
|
||||
}
|
||||
|
|
|
@ -2,13 +2,11 @@ package privacypass
|
|||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/persistence"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/persistence"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"github.com/bwesterb/go-ristretto"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"sync"
|
||||
)
|
||||
|
@ -16,7 +14,7 @@ import (
|
|||
// TokenServer implements a token server.
|
||||
type TokenServer struct {
|
||||
k *ristretto.Scalar
|
||||
Y *ristretto.Element
|
||||
Y *ristretto.Point
|
||||
seen map[string]bool
|
||||
persistanceService persistence.Service
|
||||
mutex sync.Mutex
|
||||
|
@ -24,114 +22,58 @@ type TokenServer struct {
|
|||
|
||||
// SignedBatchWithProof encapsulates a signed batch of blinded tokens with a batch proof for verification
|
||||
type SignedBatchWithProof struct {
|
||||
SignedTokens []SignedToken `json:"st"`
|
||||
Proof DLEQProof `json:"dp"`
|
||||
SignedTokens []SignedToken
|
||||
Proof DLEQProof
|
||||
}
|
||||
|
||||
const tokenBucket = "tokens"
|
||||
|
||||
// NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances)
|
||||
func NewTokenServer() *TokenServer {
|
||||
k := new(ristretto.Scalar)
|
||||
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.SetUniformBytes(b)
|
||||
return &TokenServer{k, new(ristretto.Element).ScalarBaseMult(k), make(map[string]bool), nil, sync.Mutex{}}
|
||||
func NewTokenServer() TokenServer {
|
||||
k := new(ristretto.Scalar).Rand()
|
||||
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool), nil, sync.Mutex{}}
|
||||
}
|
||||
|
||||
// NewTokenServerFromStore generates a new TokenServer backed by a persistence service.
|
||||
func NewTokenServerFromStore(k *ristretto.Scalar, persistenceService persistence.Service) *TokenServer {
|
||||
tokenServer := NewTokenServer()
|
||||
func NewTokenServerFromStore(persistenceService persistence.Service) TokenServer {
|
||||
k := new(ristretto.Scalar).Rand()
|
||||
persistenceService.Setup([]string{tokenBucket})
|
||||
|
||||
// recalculate public key from k
|
||||
tokenServer.k = k
|
||||
tokenServer.Y = new(ristretto.Element).ScalarBaseMult(tokenServer.k)
|
||||
|
||||
tokenServer.persistanceService = persistenceService
|
||||
return tokenServer
|
||||
}
|
||||
|
||||
// Close ensures that the database is properly closed...
|
||||
func (ts *TokenServer) Close() {
|
||||
ts.mutex.Lock()
|
||||
defer ts.mutex.Unlock()
|
||||
ts.persistanceService.Close()
|
||||
return TokenServer{k, new(ristretto.Point).ScalarMultBase(k), make(map[string]bool), persistenceService, sync.Mutex{}}
|
||||
}
|
||||
|
||||
// SignBlindedToken calculates kP for the given BlindedToken P
|
||||
func (ts *TokenServer) SignBlindedToken(bt BlindedToken) SignedToken {
|
||||
Q := new(ristretto.Element).ScalarMult(ts.k, bt.P)
|
||||
Q := new(ristretto.Point).ScalarMult(bt.P, ts.k)
|
||||
return SignedToken{Q}
|
||||
}
|
||||
|
||||
// SignBlindedTokenBatch signs a batch of blinded tokens under a given transcript
|
||||
func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, transcript *core.Transcript) (*SignedBatchWithProof, error) {
|
||||
func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, transcript *core.Transcript) SignedBatchWithProof {
|
||||
var signedTokens []SignedToken
|
||||
for _, bt := range blindedTokens {
|
||||
signedTokens = append(signedTokens, ts.SignBlindedToken(bt))
|
||||
}
|
||||
|
||||
proof, err := ts.constructBatchProof(blindedTokens, signedTokens, transcript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedProof := SignedBatchWithProof{signedTokens, *proof}
|
||||
return &signedProof, nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
var signedTokens []SignedToken
|
||||
for _, bt := range blindedTokens {
|
||||
signedTokens = append(signedTokens, ts.SignBlindedToken(bt))
|
||||
}
|
||||
Ht := sha3.Sum512(constraintToken)
|
||||
T, err := new(ristretto.Element).SetUniformBytes(Ht[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// W == kT
|
||||
W := new(ristretto.Element).ScalarMult(ts.k, T)
|
||||
blindedTokens = append(blindedTokens, BlindedToken{P: T})
|
||||
proof, err := ts.constructBatchProof(blindedTokens, append(signedTokens, SignedToken{Q: W}), transcript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedProof := SignedBatchWithProof{signedTokens, *proof}
|
||||
return &signedProof, nil
|
||||
return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)}
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof {
|
||||
transcript.NewProtocol(BatchProofProtocol)
|
||||
transcript.AddToTranscript(BatchProofX, ristretto.NewGeneratorElement().Bytes())
|
||||
transcript.AddToTranscript(BatchProofX, new(ristretto.Point).SetBase().Bytes())
|
||||
transcript.AddToTranscript(BatchProofY, ts.Y.Bytes())
|
||||
transcript.AddToTranscript(BatchProofPVector, []byte(fmt.Sprintf("%v", blindedTokens)))
|
||||
transcript.AddToTranscript(BatchProofQVector, []byte(fmt.Sprintf("%v", signedTokens)))
|
||||
prng := transcript.CommitToPRNG("w")
|
||||
|
||||
M := ristretto.NewIdentityElement()
|
||||
Z := ristretto.NewIdentityElement()
|
||||
M := new(ristretto.Point).SetZero()
|
||||
Z := new(ristretto.Point).SetZero()
|
||||
|
||||
buf := make([]byte, 64)
|
||||
c := new(ristretto.Scalar)
|
||||
for i := range blindedTokens {
|
||||
err := prng.Next(buf, c)
|
||||
if err != nil {
|
||||
log.Errorf("error constructing batch proof: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M)
|
||||
Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z)
|
||||
c := prng.Next()
|
||||
M = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(blindedTokens[i].P, c), M)
|
||||
Z = new(ristretto.Point).Add(new(ristretto.Point).ScalarMult(signedTokens[i].Q, c), Z)
|
||||
}
|
||||
proof := DiscreteLogEquivalenceProof(ts.k, ristretto.NewGeneratorElement(), ts.Y, M, Z, transcript)
|
||||
return &proof, nil
|
||||
return DiscreteLogEquivalenceProof(ts.k, new(ristretto.Point).SetBase(), ts.Y, M, Z, transcript)
|
||||
}
|
||||
|
||||
// SpendToken returns true a SpentToken is valid and has never been spent before, false otherwise.
|
||||
|
@ -144,22 +86,18 @@ func (ts *TokenServer) SpendToken(token SpentToken, data []byte) error {
|
|||
}
|
||||
} else {
|
||||
spent, err := ts.persistanceService.Check(tokenBucket, hex.EncodeToString(token.T))
|
||||
if err != nil || spent {
|
||||
if err != nil || spent == true {
|
||||
return fmt.Errorf("token: %v has already been spent", token)
|
||||
}
|
||||
}
|
||||
Ht := sha3.Sum512(token.T)
|
||||
T, err := new(ristretto.Element).SetUniformBytes(Ht[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
W := new(ristretto.Element).ScalarMult(ts.k, T)
|
||||
Ht := sha3.Sum256(token.T)
|
||||
T := new(ristretto.Point).SetElligator(&Ht)
|
||||
W := new(ristretto.Point).ScalarMult(T, ts.k)
|
||||
key := sha3.Sum256(append(token.T, W.Bytes()...))
|
||||
mac := hmac.New(sha3.New512, key[:])
|
||||
mac.Write(data)
|
||||
computedMAC := mac.Sum(nil)
|
||||
result := hmac.Equal(token.MAC, computedMAC)
|
||||
if result {
|
||||
K := mac.Sum(data)
|
||||
result := hmac.Equal(token.MAC, K)
|
||||
if result == true {
|
||||
if ts.persistanceService == nil {
|
||||
ts.seen[hex.EncodeToString(token.T)] = true
|
||||
} else {
|
||||
|
|
99
service.go
99
service.go
|
@ -2,30 +2,23 @@ package tapir
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ServiceMetrics outlines higher level information about the service e.g. counts of connections
|
||||
type ServiceMetrics struct {
|
||||
ConnectionCount int
|
||||
}
|
||||
|
||||
// Service defines the interface for a Tapir Service
|
||||
type Service interface {
|
||||
Init(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity *primitives.Identity)
|
||||
Connect(hostname string, application Application) (bool, error)
|
||||
Listen(application Application) error
|
||||
GetConnection(connectionID string) (Connection, error)
|
||||
Metrics() ServiceMetrics
|
||||
Broadcast(message []byte, capability Capability) error
|
||||
WaitForCapabilityOrClose(connectionID string, capability Capability) (Connection, error)
|
||||
Shutdown()
|
||||
}
|
||||
|
@ -40,18 +33,17 @@ type Connection interface {
|
|||
HasCapability(name Capability) bool
|
||||
SetCapability(name Capability)
|
||||
SetEncryptionKey(key [32]byte)
|
||||
Send(message []byte) error
|
||||
Send(message []byte)
|
||||
Close()
|
||||
App() Application
|
||||
SetApp(application Application)
|
||||
IsClosed() bool
|
||||
Broadcast(message []byte, capability Capability) error
|
||||
}
|
||||
|
||||
// Connection defines a Tapir Connection
|
||||
type connection struct {
|
||||
hostname string
|
||||
conn io.ReadWriteCloser
|
||||
conn net.Conn
|
||||
capabilities sync.Map
|
||||
encrypted bool
|
||||
key [32]byte
|
||||
|
@ -60,22 +52,17 @@ type connection struct {
|
|||
outbound bool
|
||||
closed bool
|
||||
MaxLength int
|
||||
lock sync.Mutex
|
||||
service Service
|
||||
expectBuffer []byte
|
||||
}
|
||||
|
||||
// NewConnection creates a new Connection
|
||||
func NewConnection(service Service, id *primitives.Identity, hostname string, outbound bool, conn io.ReadWriteCloser, app Application) Connection {
|
||||
func NewConnection(id *primitives.Identity, hostname string, outbound bool, conn net.Conn, app Application) Connection {
|
||||
connection := new(connection)
|
||||
connection.hostname = hostname
|
||||
connection.conn = conn
|
||||
connection.app = app
|
||||
connection.identity = id
|
||||
connection.outbound = outbound
|
||||
connection.MaxLength = 8192
|
||||
connection.service = service
|
||||
connection.expectBuffer = make([]byte, 8192)
|
||||
connection.MaxLength = 1024
|
||||
go connection.app.Init(connection)
|
||||
return connection
|
||||
}
|
||||
|
@ -87,23 +74,17 @@ func (c *connection) ID() *primitives.Identity {
|
|||
|
||||
// App returns the overarching application using this Connection.
|
||||
func (c *connection) App() Application {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.app
|
||||
}
|
||||
|
||||
// App returns the overarching application using this Connection.
|
||||
func (c *connection) SetApp(application Application) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.app = application
|
||||
}
|
||||
|
||||
// Hostname returns the hostname of the connection (if the connection has not been authorized it will return the
|
||||
// temporary hostname identifier)
|
||||
func (c *connection) Hostname() string {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.hostname
|
||||
}
|
||||
|
||||
|
@ -115,15 +96,11 @@ func (c *connection) IsOutbound() bool {
|
|||
|
||||
// IsClosed returns true if the connection is closed (connections cannot be reopened)
|
||||
func (c *connection) IsClosed() bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.closed
|
||||
}
|
||||
|
||||
// SetHostname sets the hostname on the connection
|
||||
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)
|
||||
c.hostname = hostname
|
||||
}
|
||||
|
@ -142,78 +119,59 @@ func (c *connection) HasCapability(name Capability) bool {
|
|||
|
||||
// Close forcibly closes the connection
|
||||
func (c *connection) Close() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.closeInner()
|
||||
}
|
||||
|
||||
func (c *connection) closeInner() {
|
||||
c.closed = true
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
// Expect blocks and reads a single Tapir packet , from the connection.
|
||||
func (c *connection) Expect() []byte {
|
||||
// Multiple goroutines may invoke methods on a Conn simultaneously.
|
||||
// As such we don't need to mutex around closed.
|
||||
n, err := io.ReadFull(c.conn, c.expectBuffer)
|
||||
buffer := make([]byte, c.MaxLength)
|
||||
n, err := io.ReadFull(c.conn, buffer)
|
||||
|
||||
if n != c.MaxLength || err != nil {
|
||||
log.Debugf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.identity.Hostname(), n, err)
|
||||
c.Close() // use the full close function which acquires a lock for the connection state...
|
||||
log.Errorf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.identity.Hostname(), n, err)
|
||||
c.conn.Close()
|
||||
c.closed = true
|
||||
return []byte{}
|
||||
}
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.encrypted {
|
||||
var decryptNonce [24]byte
|
||||
copy(decryptNonce[:], c.expectBuffer[:24])
|
||||
decrypted, ok := secretbox.Open(nil, c.expectBuffer[24:], &decryptNonce, &c.key)
|
||||
copy(decryptNonce[:], buffer[:24])
|
||||
decrypted, ok := secretbox.Open(nil, buffer[24:], &decryptNonce, &c.key)
|
||||
if ok {
|
||||
copy(c.expectBuffer, decrypted)
|
||||
copy(buffer, decrypted)
|
||||
} else {
|
||||
log.Errorf("[%v -> %v] Error Decrypting Message On Wire", c.hostname, c.identity.Hostname())
|
||||
c.closeInner()
|
||||
c.conn.Close()
|
||||
c.closed = true
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
length, _ := binary.Uvarint(c.expectBuffer[0:2])
|
||||
if length+2 >= uint64(c.MaxLength) {
|
||||
return []byte{}
|
||||
}
|
||||
len, _ := binary.Uvarint(buffer[0:2])
|
||||
//cplog.Debugf("[%v -> %v] Wire Receive: (%d) %x", c.hostname, c.ID.Hostname(), len, buffer)
|
||||
return c.expectBuffer[2 : length+2]
|
||||
return buffer[2 : len+2]
|
||||
}
|
||||
|
||||
// SetEncryptionKey turns on application-level encryption on the connection using the given key.
|
||||
func (c *connection) SetEncryptionKey(key [32]byte) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.key = key
|
||||
c.encrypted = true
|
||||
}
|
||||
|
||||
// Send writes a given message to a Tapir packet (of 1024 bytes in length).
|
||||
func (c *connection) Send(message []byte) error {
|
||||
|
||||
// We can only encode messages up to maxLength
|
||||
if len(message) >= c.MaxLength {
|
||||
log.Errorf("attempting to send a message that is too big")
|
||||
return errors.New("message too long")
|
||||
}
|
||||
func (c *connection) Send(message []byte) {
|
||||
|
||||
buffer := make([]byte, c.MaxLength)
|
||||
binary.PutUvarint(buffer[0:2], uint64(len(message)))
|
||||
copy(buffer[2:], message)
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.encrypted {
|
||||
var nonce [24]byte
|
||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
||||
log.Errorf("Could not read sufficient randomness %v. Closing connection", err)
|
||||
c.closeInner()
|
||||
return errors.New("could not read random")
|
||||
c.conn.Close()
|
||||
c.closed = true
|
||||
}
|
||||
// MaxLength - 40 = MaxLength - 24 nonce bytes and 16 auth tag.
|
||||
encrypted := secretbox.Seal(nonce[:], buffer[0:c.MaxLength-40], &nonce, &c.key)
|
||||
|
@ -222,12 +180,7 @@ func (c *connection) Send(message []byte) error {
|
|||
log.Debugf("[%v -> %v] Wire Send %x", c.identity.Hostname(), c.hostname, buffer)
|
||||
_, err := c.conn.Write(buffer)
|
||||
if err != nil {
|
||||
c.closeInner()
|
||||
c.conn.Close()
|
||||
c.closed = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Broadcast sends a message to all active service connections with a given capability
|
||||
func (c *connection) Broadcast(message []byte, capability Capability) error {
|
||||
return c.service.Broadcast(message, capability)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ go list ./... | xargs go vet
|
|||
echo ""
|
||||
echo "Linting:"
|
||||
|
||||
staticcheck ./...
|
||||
go list ./... | xargs golint
|
||||
|
||||
|
||||
echo "Time to format"
|
||||
|
@ -21,4 +21,4 @@ ineffassign .
|
|||
|
||||
# misspell (https://github.com/client9/misspell/cmd/misspell)
|
||||
echo "Checking for misspelled words..."
|
||||
misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
misspell . | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package testing
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/networks/tor"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/applications"
|
||||
"cwtch.im/tapir/networks/tor"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -62,28 +60,16 @@ func TestTapir(t *testing.T) {
|
|||
log.SetLevel(log.LevelDebug)
|
||||
log.Infof("Number of goroutines open at start: %d", runtime.NumGoroutine())
|
||||
// Connect to Tor
|
||||
os.MkdirAll("./tor/", 0700)
|
||||
builder := new(torProvider.TorrcBuilder)
|
||||
builder.WithSocksPort(9059).WithControlPort(9060).WithHashedPassword("tapir-integration-test").Build("./tor/torrc")
|
||||
|
||||
torDataDir := ""
|
||||
var err error
|
||||
if torDataDir, err = os.MkdirTemp("./tor/", "data-dir-"); err != nil {
|
||||
t.Fatalf("could not create data dir")
|
||||
}
|
||||
|
||||
// Connect to Tor
|
||||
acn, err := torProvider.NewTorACNWithAuth("./", "", torDataDir, 9060, torProvider.HashedPasswordAuthenticator{Password: "tapir-integration-test"})
|
||||
if err != nil {
|
||||
t.Fatalf("could not launch ACN %v", err)
|
||||
}
|
||||
var acn connectivity.ACN
|
||||
acn, _ = connectivity.StartTor("./", "")
|
||||
acn.WaitTillBootstrapped()
|
||||
|
||||
// Generate Server Keys
|
||||
id, sk := primitives.InitializeEphemeralIdentity()
|
||||
|
||||
// Init the Server running the Simple App.
|
||||
service := new(tor.BaseOnionService)
|
||||
var service tapir.Service
|
||||
service = new(tor.BaseOnionService)
|
||||
service.Init(acn, sk, &id)
|
||||
|
||||
// Goroutine Management
|
||||
|
@ -100,59 +86,42 @@ func TestTapir(t *testing.T) {
|
|||
wg.Add(2)
|
||||
// Init a Client to Connect to the Server
|
||||
client, clienthostname := genclient(acn)
|
||||
go connectclient(t, client, id.PublicKey(), wg)
|
||||
go connectclient(client, id.PublicKey(), wg)
|
||||
CheckConnection(service, clienthostname, wg)
|
||||
wg.Wait()
|
||||
// Wait for Garbage Collection...
|
||||
time.Sleep(time.Second * 60)
|
||||
// Wait for Server to Sync
|
||||
time.Sleep(time.Second * 2)
|
||||
log.Infof("Closing ACN...")
|
||||
client.Shutdown()
|
||||
service.Shutdown()
|
||||
acn.Close()
|
||||
sg.Wait()
|
||||
time.Sleep(time.Second * 5)
|
||||
time.Sleep(time.Second * 2)
|
||||
log.Infof("Number of goroutines open at close: %d", runtime.NumGoroutine())
|
||||
if numRoutinesStart != runtime.NumGoroutine() {
|
||||
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||
|
||||
t.Errorf("Potential goroutine leak: Num Start:%v NumEnd: %v", numRoutinesStart, runtime.NumGoroutine())
|
||||
}
|
||||
if !AuthSuccess {
|
||||
t.Fatalf("Integration Test FAILED, client did not auth with server")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func genclient(acn connectivity.ACN) (tapir.Service, string) {
|
||||
id, sk := primitives.InitializeEphemeralIdentity()
|
||||
client := new(tor.BaseOnionService)
|
||||
var client tapir.Service
|
||||
client = new(tor.BaseOnionService)
|
||||
client.Init(acn, sk, &id)
|
||||
return client, id.Hostname()
|
||||
}
|
||||
|
||||
// Client will Connect and launch it's own Echo App goroutine.
|
||||
func connectclient(t *testing.T, client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
|
||||
client.Connect(torProvider.GetTorV3Hostname(key), new(SimpleApp))
|
||||
func connectclient(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
|
||||
client.Connect(utils.GetTorV3Hostname(key), new(SimpleApp))
|
||||
|
||||
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
||||
// we will wait a little while then exit.
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
conn, _ := client.GetConnection(torProvider.GetTorV3Hostname(key))
|
||||
conn, _ := client.GetConnection(utils.GetTorV3Hostname(key))
|
||||
log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability))
|
||||
|
||||
if conn.HasCapability(applications.AuthCapability) == false {
|
||||
t.Errorf("tapir auth failed")
|
||||
}
|
||||
|
||||
// attempt to send a message that is too long
|
||||
var long [8195]byte
|
||||
err := conn.Send(long[:])
|
||||
if err == nil {
|
||||
t.Errorf("should have errored on message being too long...")
|
||||
}
|
||||
|
||||
AuthSuccess = true
|
||||
group.Done()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package testing
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/tapir"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/networks/tor"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
torProvider "git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"cwtch.im/tapir"
|
||||
"cwtch.im/tapir/applications"
|
||||
"cwtch.im/tapir/networks/tor"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -21,23 +21,8 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
|||
log.SetLevel(log.LevelDebug)
|
||||
log.Infof("Number of goroutines open at start: %d", runtime.NumGoroutine())
|
||||
// Connect to Tor
|
||||
os.MkdirAll("./tor/", 0700)
|
||||
builder := new(torProvider.TorrcBuilder)
|
||||
builder.WithHashedPassword("tapir-integration-test").Build("./tor/torrc")
|
||||
|
||||
// Connect to Tor
|
||||
|
||||
torDataDir := ""
|
||||
var err error
|
||||
if torDataDir, err = os.MkdirTemp("./tor/", "data-dir-"); err != nil {
|
||||
t.Fatalf("could not create data dir")
|
||||
}
|
||||
|
||||
// Connect to Tor
|
||||
acn, err := torProvider.NewTorACNWithAuth("./", "", torDataDir, 9051, torProvider.HashedPasswordAuthenticator{Password: "tapir-integration-test"})
|
||||
if err != nil {
|
||||
t.Fatalf("could not launch ACN %v", err)
|
||||
}
|
||||
var acn connectivity.ACN
|
||||
acn, _ = connectivity.StartTor("./", "")
|
||||
acn.WaitTillBootstrapped()
|
||||
|
||||
// Generate Server Keys, not we generate two sets
|
||||
|
@ -45,7 +30,8 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
|||
id2, sk2 := primitives.InitializeEphemeralIdentity()
|
||||
|
||||
// Init the Server running the Simple App.
|
||||
service := new(tor.BaseOnionService)
|
||||
var service tapir.Service
|
||||
service = new(tor.BaseOnionService)
|
||||
// Initialize an onion service with one identity, but the auth app with another, this should
|
||||
// trigger a failure in authentication protocol
|
||||
service.Init(acn, sk2, &id)
|
||||
|
@ -70,11 +56,9 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
|||
// Wait for Server to Sync
|
||||
time.Sleep(time.Second * 2)
|
||||
log.Infof("closing ACN...")
|
||||
client.Shutdown()
|
||||
service.Shutdown()
|
||||
acn.Close()
|
||||
sg.Wait()
|
||||
time.Sleep(time.Second * 5) // wait for goroutines to finish...
|
||||
time.Sleep(time.Second * 2)
|
||||
log.Infof("Number of goroutines open at close: %d", runtime.NumGoroutine())
|
||||
if numRoutinesStart != runtime.NumGoroutine() {
|
||||
t.Errorf("Potential goroutine leak: Num Start:%v NumEnd: %v", numRoutinesStart, runtime.NumGoroutine())
|
||||
|
@ -83,17 +67,17 @@ func TestTapirMaliciousRemote(t *testing.T) {
|
|||
|
||||
// Client will Connect and launch it's own Echo App goroutine.
|
||||
func connectclientandfail(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup, t *testing.T) {
|
||||
client.Connect(torProvider.GetTorV3Hostname(key), new(applications.AuthApp))
|
||||
client.Connect(utils.GetTorV3Hostname(key), new(applications.AuthApp))
|
||||
|
||||
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
|
||||
// we will wait a little while then exit.
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
log.Infof("Checking connection status...")
|
||||
conn, err := client.GetConnection(torProvider.GetTorV3Hostname(key))
|
||||
conn, err := client.GetConnection(utils.GetTorV3Hostname(key))
|
||||
if err == nil {
|
||||
group.Done()
|
||||
t.Errorf("Connection should have failed! %v %v", conn, err)
|
||||
t.Fatalf("Connection should have failed! %v %v", conn, err)
|
||||
}
|
||||
log.Infof("Successfully failed to authenticate...")
|
||||
group.Done()
|
||||
|
|
|
@ -2,20 +2,13 @@
|
|||
|
||||
set -e
|
||||
pwd
|
||||
go test -race ${1} -coverprofile=applications.cover.out -v ./applications
|
||||
go test -race ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard
|
||||
# persistence is broken in WSL
|
||||
if grep -q -v Microsoft /proc/version; then
|
||||
go test -race ${1} -coverprofile=persistence.cover.out -v ./persistence
|
||||
fi
|
||||
go test -race ${1} -coverprofile=primitives.cover.out -v ./primitives
|
||||
go test -race ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable
|
||||
go test -race ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core
|
||||
# persistence is broken in WSL
|
||||
if grep -q -v Microsoft /proc/version; then
|
||||
go test -race ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass
|
||||
go test -bench "BenchmarkAuditableStore" -benchtime 1000x primitives/auditable/*.go
|
||||
fi
|
||||
go test ${1} -coverprofile=applications.cover.out -v ./applications
|
||||
go test ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard
|
||||
go test ${1} -coverprofile=primitives.cover.out -v ./primitives
|
||||
go test ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable
|
||||
go test ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core
|
||||
go test ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass
|
||||
go test -bench "BenchmarkAuditableStore" -benchtime 1000x primitives/auditable/*.go
|
||||
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
||||
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
||||
rm -rf *.cover.out
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"filippo.io/edwards25519"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// EDH implements diffie hellman using curve25519 keys derived from ed25519 keys
|
||||
func EDH(privateKey ed25519.PrivateKey, remotePublicKey ed25519.PublicKey) ([]byte, error) {
|
||||
var privKeyBytes [64]byte
|
||||
var remotePubKeyBytes [32]byte
|
||||
copy(privKeyBytes[:], privateKey[:])
|
||||
copy(remotePubKeyBytes[:], remotePublicKey[:])
|
||||
var curve25519priv [32]byte
|
||||
|
||||
PrivateKeyToCurve25519(&curve25519priv, &privKeyBytes)
|
||||
remoteCurve25519pub, err := ed25519PublicKeyToCurve25519New(remotePublicKey)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
secret, err := curve25519.X25519(curve25519priv[:], remoteCurve25519pub[:])
|
||||
return secret, err
|
||||
}
|
||||
|
||||
// reproduced from https://github.com/FiloSottile/age/blob/main/agessh/agessh.go#L190
|
||||
func ed25519PublicKeyToCurve25519New(pk ed25519.PublicKey) ([]byte, error) {
|
||||
// See https://blog.filippo.io/using-ed25519-keys-for-encryption and
|
||||
// https://pkg.go.dev/filippo.io/edwards25519#Point.BytesMontgomery.
|
||||
p, err := new(edwards25519.Point).SetBytes(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.BytesMontgomery(), nil
|
||||
}
|
||||
|
||||
// PrivateKeyToCurve25519 converts an ed25519 private key into a corresponding
|
||||
// curve25519 private key
|
||||
func PrivateKeyToCurve25519(curve25519Private *[32]byte, privateKey *[64]byte) {
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
digest := h.Sum(nil)
|
||||
|
||||
digest[0] &= 248
|
||||
digest[31] &= 127
|
||||
digest[31] |= 64
|
||||
|
||||
copy(curve25519Private[:], digest)
|
||||
}
|
Loading…
Reference in New Issue