Compare commits

...

8 Commits

Author SHA1 Message Date
Sarah Jamie Lewis daa05d97c8 Alignment with dalek
the build failed Details
2019-10-12 15:04:23 -07:00
Sarah Jamie Lewis 5afd4a930a Verifier-specific logic
the build failed Details
2019-10-10 16:41:10 -07:00
Sarah Jamie Lewis ec8c9352ec *VERY* rough cut of bulletproofs constraint system - do not use
the build failed Details
2019-10-10 15:36:36 -07:00
Sarah Jamie Lewis 9332385a6f Switching to a different ristretto implementation
the build failed Details
2019-10-07 23:03:33 -07:00
Sarah Jamie Lewis 136b9b8192 First cut of bulletproofs
the build failed Details
2019-10-06 21:33:41 -07:00
Sarah Jamie Lewis bd3a6043be Adding a PoW Token Service that Grants Tokens for PoW
the build was successful Details
2019-10-04 23:56:30 -07:00
Sarah Jamie Lewis 1066862f58 First cut of Token Board
the build failed Details
2019-09-15 00:49:44 -07:00
Sarah Jamie Lewis a2cfcaf8a4 Auditable Store
the build was successful Details
2019-09-14 17:43:35 -07:00
43 changed files with 3038 additions and 111 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ vendor/
/tor/
coverage.out
/testing/tor/
/applications/tor/
*.db

View File

@ -1,7 +1,24 @@
package tapir
import (
"cwtch.im/tapir/primitives/core"
)
// Capability defines a status granted to a connection, from an application. That allows the connection to access
// other Application or functions within an Application.
type Capability string
// Application defines the interface for all Tapir Applications
type Application interface {
NewInstance() Application
Init(connection Connection)
Transcript() *core.Transcript
PropagateTranscript(transcript *core.Transcript)
}
// InteractiveApplication defines the interface for interactive Tapir applications (apps that expect the user to send
// and receive messages from)
type InteractiveApplication interface {
Application
Listen()
}

View File

@ -0,0 +1,58 @@
package applications
import (
"cwtch.im/tapir"
)
// ApplicationChain is a meta-app that can be used to build complex applications from other applications
type ApplicationChain struct {
TranscriptApp
apps []tapir.Application
endapp tapir.InteractiveApplication
capabilities []tapir.Capability
}
// ChainApplication adds a new application to the chain. Returns a pointer to app so this call
// can itself be chained.
func (appchain *ApplicationChain) ChainApplication(app tapir.Application, capability tapir.Capability) *ApplicationChain {
appchain.apps = append(appchain.apps, app.NewInstance())
appchain.capabilities = append(appchain.capabilities, capability)
return appchain
}
// ChainInteractiveApplication adds an interactive application to the chain. There can only be 1 interactive application.
func (appchain *ApplicationChain) ChainInteractiveApplication(app tapir.InteractiveApplication) *ApplicationChain {
appchain.endapp = app
return appchain
}
// NewInstance should always return a new instantiation of the application.
func (appchain *ApplicationChain) NewInstance() tapir.Application {
applicationChain := new(ApplicationChain)
for _, app := range appchain.apps {
applicationChain.apps = append(applicationChain.apps, app.NewInstance())
}
applicationChain.capabilities = appchain.capabilities
return applicationChain
}
// Init is run when the connection is first started.
func (appchain *ApplicationChain) Init(connection tapir.Connection) {
appchain.TranscriptApp.Init(connection)
for i, app := range appchain.apps {
app.PropagateTranscript(appchain.transcript)
app.Init(connection)
if connection.HasCapability(appchain.capabilities[i]) == false {
connection.Close()
return
}
connection.SetApp(app)
}
}
// Listen calls listen on the Interactive application
func (appchain *ApplicationChain) Listen() {
if appchain.endapp != nil {
appchain.endapp.Listen()
}
}

View File

@ -17,10 +17,11 @@ type AuthMessage struct {
}
// AuthCapability defines the Authentication Capability granted by AuthApp
const AuthCapability = "AUTH"
const AuthCapability = tapir.Capability("AuthenticationCapability")
// AuthApp is the concrete Application type that handles Authentication
type AuthApp struct {
TranscriptApp
}
// NewInstance creates a new instance of the AuthApp
@ -30,7 +31,8 @@ func (ea AuthApp) NewInstance() tapir.Application {
// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
// or the connection is closed.
func (ea AuthApp) Init(connection tapir.Connection) {
func (ea *AuthApp) Init(connection tapir.Connection) {
ea.TranscriptApp.Init(connection)
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
ephemeralIdentity, _ := primitives.InitializeEphemeralIdentity()
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
@ -80,8 +82,10 @@ func (ea AuthApp) Init(connection tapir.Connection) {
}
// Derive a challenge from the transcript of the public parameters of this authentication protocol
var transcript *primitives.Transcript
transcript = primitives.NewTranscript("tapir-auth-" + outboundHostname + "-" + inboundHostname)
transcript := ea.Transcript()
transcript.NewProtocol("auth-app")
transcript.AddToTranscript("outbound-hostname", []byte(outboundHostname))
transcript.AddToTranscript("inbound-hostname", []byte(inboundHostname))
transcript.AddToTranscript("outbound-challenge", outboundAuthMessage)
transcript.AddToTranscript("inbound-challenge", inboundAuthMessage)
challengeBytes := transcript.CommitToTranscript("3dh-auth-challenge")
@ -92,14 +96,16 @@ func (ea AuthApp) Init(connection tapir.Connection) {
// Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
// To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
// along with our hostname
// We expect the remote to do the same, and compare the two.
// If successful we extend our auth capability to the connection and reassert the hostname.
// We note that the only successful scenario here requires that the remote peer have successfully derived the same
// encryption key and the same transcript challenge.
connection.Send(challengeBytes)
connection.Send(append(challengeBytes, []byte(connection.ID().Hostname())...))
remoteChallenge := connection.Expect()
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
connection.SetHostname(utils.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.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)

View File

@ -47,11 +47,11 @@ func (MockConnection) SetHostname(hostname string) {
panic("implement me")
}
func (MockConnection) HasCapability(name string) bool {
func (MockConnection) HasCapability(name tapir.Capability) bool {
panic("implement me")
}
func (MockConnection) SetCapability(name string) {
func (MockConnection) SetCapability(name tapir.Capability) {
panic("implement me")
}
@ -72,6 +72,10 @@ func (MockConnection) App() tapir.Application {
return nil
}
func (MockConnection) SetApp(tapir.Application) {
// no op
}
func (MockConnection) IsClosed() bool {
panic("implement me")
}

View File

@ -0,0 +1,102 @@
package applications
import (
"crypto/sha256"
"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
type ProofOfWorkApplication struct {
TranscriptApp
}
// transcript constants
const (
PoWApp = "pow-app"
PoWSeed = "pow-seed"
PoWChallenge = "pow-challenge"
PoWPRNG = "pow-prng"
PoWSolution = "pow-solution"
)
// SuccessfulProofOfWorkCapability is given when a successfully PoW Challenge has been Completed
const SuccessfulProofOfWorkCapability = tapir.Capability("SuccessfulProofOfWorkCapability")
// NewInstance should always return a new instantiation of the application.
func (powapp *ProofOfWorkApplication) NewInstance() tapir.Application {
return new(ProofOfWorkApplication)
}
// Init is run when the connection is first started.
func (powapp *ProofOfWorkApplication) Init(connection tapir.Connection) {
powapp.Transcript().NewProtocol(PoWApp)
if connection.IsOutbound() {
powapp.Transcript().AddToTranscript(PoWSeed, connection.Expect())
solution := powapp.solveChallenge(powapp.Transcript().CommitToTranscript(PoWChallenge), powapp.transcript.CommitToPRNG(PoWPRNG))
powapp.transcript.AddToTranscript(PoWSolution, solution)
connection.Send(solution)
connection.SetCapability(SuccessfulProofOfWorkCapability) // We can self grant.because the server will close the connection on failure
return
}
// We may be the first application, in which case we need to randomize the transcript challenge
// We use the random hostname of the inbound server (if we've authenticated them then the challenge will
// already be sufficiently randomized, so this doesn't hurt)
// It does sadly mean an additional round trip.
powapp.Transcript().AddToTranscript(PoWSeed, []byte(connection.Hostname()))
connection.Send([]byte(connection.Hostname()))
solution := connection.Expect()
challenge := powapp.Transcript().CommitToTranscript(PoWChallenge)
// soft-commitment to the prng, doesn't force the client to use it (but we could technically check that it did, not necessary for the security of this App)
powapp.transcript.CommitToPRNG(PoWPRNG)
powapp.transcript.AddToTranscript(PoWSolution, solution)
if powapp.validateChallenge(challenge, solution) {
connection.SetCapability(SuccessfulProofOfWorkCapability)
return
}
}
// SolveChallenge takes in a challenge and a message and returns a solution
// The solution is a 24 byte nonce which when hashed with the challenge and the message
// produces a sha256 hash with Difficulty leading 0s
func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core.PRNG) []byte {
solved := false
var sum [32]byte
solution := []byte{}
solve := make([]byte, len(challenge)+32)
for !solved {
solution = prng.Next().Bytes()
copy(solve[0:], solution[:])
copy(solve[len(solution):], challenge[:])
sum = sha256.Sum256(solve)
solved = true
for i := 0; i < 2; i++ {
if sum[i] != 0x00 {
solved = false
}
}
}
log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
return solution[:]
}
// ValidateChallenge returns true if the message and spamguard pass the challenge
func (powapp *ProofOfWorkApplication) validateChallenge(challenge []byte, solution []byte) bool {
solve := make([]byte, len(challenge)+32)
copy(solve[0:], solution[0:32])
copy(solve[32:], challenge[:])
sum := sha256.Sum256(solve)
for i := 0; i < 2; i++ {
if sum[i] != 0x00 {
return false
}
}
log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
return true
}

56
applications/token_app.go Normal file
View File

@ -0,0 +1,56 @@
package applications
import (
"cwtch.im/tapir"
"cwtch.im/tapir/primitives/privacypass"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
)
// TokenApplication provides Tokens for PoW
type TokenApplication struct {
TranscriptApp
TokenService *privacypass.TokenServer
Tokens []*privacypass.Token
}
// HasTokensCapability is granted once the client has obtained signed tokens
const HasTokensCapability = tapir.Capability("HasTokensCapability")
// NewInstance should always return a new instantiation of the application.
func (powapp *TokenApplication) NewInstance() tapir.Application {
app := new(TokenApplication)
app.TokenService = powapp.TokenService
return app
}
// Init is run when the connection is first started.
func (powapp *TokenApplication) Init(connection tapir.Connection) {
powapp.Transcript().NewProtocol("token-app")
if connection.IsOutbound() {
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, powapp.TokenService.Y, signedBatch.Proof, powapp.Transcript())
if verified {
log.Debugf("Successfully obtained signed tokens")
powapp.Tokens = tokens
connection.SetCapability(HasTokensCapability)
return
}
log.Debugf("Failed to verify signed token batch")
}
} 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
}
}
}

View File

@ -0,0 +1,112 @@
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/openprivacy/libricochet-go/log"
)
// NewTokenBoardClient generates a new Client for Token Board
func NewTokenBoardClient(store *auditable.Store, handler AppHandler, paymentHandler privacypass.TokenPaymentHandler) tapir.Application {
tba := new(Client)
tba.AuditableStore = store
tba.handler = handler
tba.paymentHandler = paymentHandler
return tba
}
// Client defines a client for the TokenBoard server
type Client struct {
applications.AuthApp
connection tapir.Connection
AuditableStore *auditable.Store
paymentHandler privacypass.TokenPaymentHandler
handler AppHandler
}
// NewInstance Client a new TokenBoardApp
func (ta *Client) NewInstance() tapir.Application {
tba := new(Client)
tba.AuditableStore = ta.AuditableStore
tba.handler = ta.handler
tba.paymentHandler = ta.paymentHandler
return tba
}
// Init initializes the cryptographic TokenBoardApp
func (ta *Client) Init(connection tapir.Connection) {
ta.AuthApp.Init(connection)
if connection.HasCapability(applications.AuthCapability) {
ta.connection = connection
go ta.Listen()
return
}
connection.Close()
}
// Listen processes the messages for this application
func (ta *Client) Listen() {
for {
log.Debugf("Client waiting...")
data := ta.connection.Expect()
if len(data) == 0 {
log.Debugf("Server closed the connection...")
return // connection is closed
}
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)
case replayResultMessage:
var state auditable.State
log.Debugf("Replaying %v Messages...", message.ReplayResult.NumMessages)
lastCommit := ta.AuditableStore.LatestCommit
for i := 0; i < message.ReplayResult.NumMessages; i++ {
message := ta.connection.Expect()
state.Messages = append(state.Messages, message)
}
data := ta.connection.Expect()
var signedProof auditable.SignedProof
json.Unmarshal(data, &signedProof)
state.SignedProof = signedProof
err := ta.AuditableStore.AppendState(state)
if err == nil {
log.Debugf("Successfully updated Auditable Store %v", ta.AuditableStore.LatestCommit)
ta.handler.HandleNewMessages(lastCommit)
} else {
log.Debugf("Error updating Auditable Store %v", err)
}
}
}
}
// Replay posts a Replay Message to the server.
func (ta *Client) Replay() {
log.Debugf("Sending replay request for %v", ta.AuditableStore.LatestCommit)
data, _ := json.Marshal(Message{MessageType: replayRequestMessage, ReplayRequest: replayRequest{LastCommit: ta.AuditableStore.LatestCommit}})
ta.connection.Send(data)
}
// PurchaseTokens purchases the given number of tokens from the server (using the provided payment handler)
func (ta *Client) PurchaseTokens() {
ta.paymentHandler.MakePayment()
}
// Post sends a Post Request to the server
func (ta *Client) Post(message auditable.Message) bool {
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)
return true
}
log.Debugf("No Valid Tokens: %v", err)
return false
}

View File

@ -0,0 +1,52 @@
package tokenboard
import (
"cwtch.im/tapir/primitives/auditable"
"cwtch.im/tapir/primitives/privacypass"
)
// AppHandler allows clients to react to specific events.
type AppHandler interface {
HandleNewMessages(previousLastCommit []byte)
}
// MessageType defines the enum for TokenBoard messages
type messageType int
const (
replayRequestMessage messageType = iota
replayResultMessage
postRequestMessage
postResultMessage
)
// Message encapsulates the application protocol
type Message struct {
MessageType messageType
PostRequest postRequest `json:",omitempty"`
PostResult postResult `json:",omitempty"`
ReplayRequest replayRequest `json:",omitempty"`
ReplayResult replayResult `json:",omitempty"`
}
// ReplayRequest requests a reply from the given Commit
type replayRequest struct {
LastCommit []byte
}
// PostRequest requests to post the message to the board with the given token
type postRequest struct {
Token privacypass.SpentToken
Message auditable.Message
}
// PostResult returns the success of a given post attempt
type postResult struct {
Success bool
Proof auditable.SignedProof
}
// ReplayResult is sent by the server before a stream of replayed messages
type replayResult struct {
NumMessages int
}

View File

@ -0,0 +1,90 @@
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/openprivacy/libricochet-go/log"
)
// NewTokenBoardServer generates new Server for Token Board
func NewTokenBoardServer(tokenService *privacypass.TokenServer, store *auditable.Store) tapir.Application {
tba := new(Server)
tba.TokenService = tokenService
tba.AuditableStore = store
return tba
}
// Server defines the token board server
type Server struct {
applications.AuthApp
connection tapir.Connection
TokenService *privacypass.TokenServer
AuditableStore *auditable.Store
}
// NewInstance creates a new TokenBoardApp
func (ta *Server) NewInstance() tapir.Application {
tba := new(Server)
tba.TokenService = ta.TokenService
tba.AuditableStore = ta.AuditableStore
return tba
}
// Init initializes the cryptographic TokenBoardApp
func (ta *Server) Init(connection tapir.Connection) {
ta.AuthApp.Init(connection)
if connection.HasCapability(applications.AuthCapability) {
ta.connection = connection
go ta.Listen()
return
}
connection.Close()
}
// Listen processes the messages for this application
func (ta *Server) Listen() {
for {
data := ta.connection.Expect()
if len(data) == 0 {
return // connection is closed
}
var message Message
json.Unmarshal(data, &message)
log.Debugf("Received a Message: %v", message)
switch message.MessageType {
case postRequestMessage:
postrequest := message.PostRequest
log.Debugf("Received a Post Message Request: %x %x", postrequest.Token, postrequest.Message)
ta.postMessageRequest(postrequest.Token, postrequest.Message)
case replayRequestMessage:
log.Debugf("Received Replay Request %v", message.ReplayRequest)
state := ta.AuditableStore.GetStateAfter(message.ReplayRequest.LastCommit)
response, _ := json.Marshal(Message{MessageType: replayResultMessage, ReplayResult: replayResult{len(state.Messages)}})
log.Debugf("Sending Replay Response %v", replayResult{len(state.Messages)})
ta.connection.Send(response)
for _, message := range state.Messages {
ta.connection.Send(message)
}
data, _ := json.Marshal(state.SignedProof)
ta.connection.Send(data)
}
}
}
func (ta *Server) postMessageRequest(token privacypass.SpentToken, message auditable.Message) {
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}})
ta.connection.Send(data)
} else {
log.Debugf("Attempt to spend an invalid token: %v", err)
data, _ := json.Marshal(Message{MessageType: postResultMessage, PostResult: postResult{false, auditable.SignedProof{}}})
ta.connection.Send(data)
}
}

View File

@ -0,0 +1,149 @@
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/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"runtime"
"sync"
"testing"
"time"
)
type Handler struct {
Store *auditable.Store
}
func (h Handler) HandleNewMessages(previousLastCommit []byte) {
log.Debugf("Handling Messages After %x", previousLastCommit)
messages := h.Store.GetMessagesAfter(previousLastCommit)
for _, message := range messages {
log.Debugf("Message %s", message)
}
}
type FreePaymentHandler struct {
tokens []*privacypass.Token
TokenService *privacypass.TokenServer
ACN connectivity.ACN
ServerHostname string
}
func (fph *FreePaymentHandler) MakePayment() {
id, sk := primitives.InitializeEphemeralIdentity()
var client tapir.Service
client = new(tor.BaseOnionService)
client.Init(fph.ACN, sk, &id)
tokenApplication := new(applications.TokenApplication)
tokenApplication.TokenService = fph.TokenService
powTokenApp := new(applications.ApplicationChain).
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
ChainApplication(tokenApplication, applications.HasTokensCapability)
client.Connect(fph.ServerHostname, powTokenApp)
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) (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(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())
// Connect to Tor
var acn connectivity.ACN
acn, _ = connectivity.StartTor("./", "")
acn.WaitTillBootstrapped()
// Generate Server Key
sid, sk := primitives.InitializeEphemeralIdentity()
tokenService := privacypass.NewTokenServer()
serverAuditableStore := new(auditable.Store)
serverAuditableStore.Init(sid)
clientAuditableStore := new(auditable.Store)
// Only initialize with public parameters
sidpubk := sid.PublicKey()
publicsid := primitives.InitializeIdentity("server", nil, &sidpubk)
clientAuditableStore.Init(publicsid)
// Init the Server running the Simple App.
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))
sg.Done()
}()
// Init the Server running the PoW Token App.
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
powTokenApp := new(applications.ApplicationChain).
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
ChainApplication(tokenApplication, applications.HasTokensCapability)
powTokenService.Listen(powTokenApp)
sg.Done()
}()
time.Sleep(time.Second * 30) // wait for server to initialize
id, sk := primitives.InitializeEphemeralIdentity()
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.WaitForCapabilityOrClose(sid.Hostname(), applications.AuthCapability)
conn, _ := client.GetConnection(sid.Hostname())
tba, _ := conn.App().(*Client)
tba.PurchaseTokens()
tba.Post([]byte("HELLO 1"))
tba.Post([]byte("HELLO 2"))
tba.Post([]byte("HELLO 3"))
tba.Post([]byte("HELLO 4"))
tba.Post([]byte("HELLO 5"))
tba.Replay()
time.Sleep(time.Second * 10) // We have to wait for the async replay request!
tba.Post([]byte("HELLO 6"))
tba.Post([]byte("HELLO 7"))
tba.Post([]byte("HELLO 8"))
tba.Post([]byte("HELLO 9"))
tba.Post([]byte("HELLO 10"))
tba.Replay()
time.Sleep(time.Second * 10) // We have to wait for the async replay request!
if tba.Post([]byte("HELLO 11")) {
t.Errorf("Post should have failed.")
}
time.Sleep(time.Second * 10)
acn.Close()
sg.Wait()
}

View File

@ -0,0 +1,32 @@
package applications
import (
"cwtch.im/tapir"
"cwtch.im/tapir/primitives/core"
)
// TranscriptApp defines a Tapir Meta-App which provides a global cryptographic transcript
type TranscriptApp struct {
transcript *core.Transcript
}
// NewInstance creates a new TranscriptApp
func (TranscriptApp) NewInstance() tapir.Application {
ta := new(TranscriptApp)
return ta
}
// Init initializes the cryptographic transcript
func (ta *TranscriptApp) Init(connection tapir.Connection) {
ta.transcript = core.NewTranscript("tapir-transcript")
}
// Transcript returns a pointer to the cryptographic transcript
func (ta *TranscriptApp) Transcript() *core.Transcript {
return ta.transcript
}
// PropagateTranscript overrides the default transcript and propagates a transcript from a previous session
func (ta *TranscriptApp) PropagateTranscript(transcript *core.Transcript) {
ta.transcript = transcript
}

View File

@ -34,7 +34,7 @@ func (s *BaseOnionService) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id
// 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 string) (tapir.Connection, error) {
func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name tapir.Capability) (tapir.Connection, error) {
conn, err := s.GetConnection(cid)
if err == nil {
for {

View File

@ -0,0 +1,76 @@
package persistence
import (
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
bolt "go.etcd.io/bbolt"
)
// BoltPersistence creates a persistence services backed by an on-disk bolt database
type BoltPersistence struct {
db *bolt.DB
}
// Open opens a database
func (bp *BoltPersistence) Open(handle string) error {
db, err := bolt.Open(handle, 0600, nil)
bp.db = db
log.Debugf("Loaded the Database")
return err
}
// Setup initializes the given buckets if they do not exist in the database
func (bp *BoltPersistence) Setup(buckets []string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
for _, bucket := range buckets {
tx.CreateBucketIfNotExists([]byte(bucket))
}
return nil
})
}
// Close closes the databases
func (bp *BoltPersistence) Close() {
bp.db.Close()
}
// Persist stores a record in the database
func (bp *BoltPersistence) Persist(bucket string, name string, value interface{}) error {
valueBytes, _ := json.Marshal(value)
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucket))
b.Put([]byte(name), valueBytes)
return nil
})
}
// Check returns true if the record exists in the given bucket.
func (bp *BoltPersistence) Check(bucket string, name string) (bool, error) {
log.Debugf("Checking database: %v %v", bucket, name)
var val []byte
err := bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucket))
val = b.Get([]byte(name))
return nil
})
if err != nil {
return false, err
} else if val != nil {
return true, nil
}
return false, nil
}
// Load reads a value from a given bucket.
func (bp *BoltPersistence) Load(bucket string, name string, value interface{}) error {
var val []byte
err := bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucket))
val = b.Get([]byte(name))
return nil
})
if err != nil {
return err
}
return json.Unmarshal(val, &value)
}

View File

@ -0,0 +1,23 @@
package persistence
import (
"testing"
)
func TestBoltPersistence_Open(t *testing.T) {
var db Service
db = new(BoltPersistence)
db.Open("test.dbgi")
db.Setup([]string{"tokens"})
db.Persist("tokens", "random_value", true)
var exists bool
db.Load("tokens", "random_value", &exists)
if exists {
t.Logf("Successfully stored: %v", exists)
} else {
t.Fatalf("Failure to store record in DB!")
}
db.Close()
}

View File

@ -0,0 +1,11 @@
package persistence
// Service provides a consistent interface for interacting with on-disk, in-memory or server-backed storage
type Service interface {
Open(handle string) error
Setup(buckets []string) error
Persist(bucket string, name string, value interface{}) error
Check(bucket string, name string) (bool, error)
Load(bucket string, name string, value interface{}) error
Close()
}

View File

@ -0,0 +1,185 @@
package auditable
import (
"cwtch.im/tapir/persistence"
"cwtch.im/tapir/primitives"
"cwtch.im/tapir/primitives/core"
"encoding/base64"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"sync"
)
// SignedProof encapsulates a signed proof
type SignedProof []byte
// Message encapsulates a message for more readable code.
type Message []byte
// State defines an array of messages.
type State struct {
SignedProof SignedProof
Messages []Message
}
//
const (
auditableDataStoreProtocol = "auditable-data-store"
newMessage = "new-message"
commit = "commit"
collapse = "collapse"
)
// Store defines a cryptographically secure & auditable transcript of messages sent from multiple
// unrelated clients to a server.
type Store struct {
state State
identity primitives.Identity
transcript *core.Transcript
LatestCommit []byte
commits map[string]int
mutex sync.Mutex
db persistence.Service
}
// Init initializes an auditable store
func (as *Store) Init(identity primitives.Identity) {
as.identity = identity
as.transcript = core.NewTranscript(auditableDataStoreProtocol)
as.commits = make(map[string]int)
}
const messageBucket = "auditable-messages"
// LoadFromStorage initializes an auditable store from a DB
func (as *Store) LoadFromStorage(db persistence.Service) {
db.Setup([]string{messageBucket})
var messages []Message
db.Load(messageBucket, "messages", &messages)
log.Debugf("Loaded from Database: %v", len(messages))
for _, message := range messages {
as.add(message)
}
log.Debugf("Loaded %v Messages from the Database", len(messages))
as.db = db
}
// Add adds a message to the auditable store
func (as *Store) Add(message Message) SignedProof {
sp := as.add(message)
if as.db != nil {
as.db.Persist(messageBucket, "messages", as.state.Messages)
}
return sp
}
// Add adds a message to the auditable store
func (as *Store) add(message Message) SignedProof {
as.mutex.Lock()
defer as.mutex.Unlock()
as.transcript.AddToTranscript(newMessage, message)
as.LatestCommit = as.transcript.CommitToTranscript(commit)
as.state.Messages = append(as.state.Messages, message)
as.state.SignedProof = as.identity.Sign(as.LatestCommit)
as.commits[base64.StdEncoding.EncodeToString(as.LatestCommit)] = len(as.state.Messages) - 1
return as.state.SignedProof
}
// GetState returns the current auditable state
func (as *Store) GetState() State {
as.mutex.Lock()
defer as.mutex.Unlock()
return as.state
}
// GetStateAfter returns the current auditable state after a given commitment
func (as *Store) GetStateAfter(commitment []byte) State {
if commitment == nil {
return as.GetState()
}
var state State
state.Messages = as.GetMessagesAfter(commitment)
state.SignedProof = as.identity.Sign(as.LatestCommit)
return state
}
// GetMessagesAfter provides access to messages after the given commit.
func (as *Store) GetMessagesAfter(latestCommit []byte) []Message {
as.mutex.Lock()
defer as.mutex.Unlock()
index, ok := as.commits[base64.StdEncoding.EncodeToString(latestCommit)]
if !ok && len(latestCommit) == 32 {
return []Message{}
} else if len(latestCommit) == 0 {
index = -1
}
return as.state.Messages[index+1:]
}
// AppendState merges a given state onto our state, first verifying that the two transcripts align
func (as *Store) AppendState(state State) error {
next := len(as.state.Messages)
for i, m := range state.Messages {
as.state.Messages = append(as.state.Messages, m)
// We reconstruct the transcript
as.transcript.AddToTranscript(newMessage, m)
as.LatestCommit = as.transcript.CommitToTranscript(commit)
log.Debugf("Adding message %d commit: %x", next+i, as.LatestCommit)
as.commits[base64.StdEncoding.EncodeToString(as.LatestCommit)] = next + i
}
// 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) == false {
return errors.New("state is not consistent, the server is malicious")
}
return nil
}
// MergeState merges a given state onto our state, first verifying that the two transcripts align
func (as *Store) MergeState(state State) error {
return as.AppendState(State{Messages: state.Messages[len(as.state.Messages):], SignedProof: state.SignedProof})
}
// VerifyFraudProof - the main idea behind this is as follows:
//
// Every update requires the server to sign, and thus commit to, a transcript
// Clients reconstruct the transcript via MergeState, as such clients can keep track of every commit.
// if a client can present a signed transcript commit from the server that other clients do not have, it is proof
// that either 1) they are out of sync with the server or 2) the server is presenting different transcripts to different people
//
// If, after syncing, the FraudProof still validates, then the server must be malicious.
// the information revealed by publicizing a fraud proof is minimal it only reveals the inconsistent transcript commit
// 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) == 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
return false, errors.New("signed proof has not been signed by the given public key")
}
_, exists := as.commits[base64.StdEncoding.EncodeToString(fraudCommit)]
if !exists {
// We have a message signed by the server which verifies that a message was inserted into the state at a given index
// However this directly contradicts our version of the state.
// There is still a possibility that we are out of sync with the server and that new messages have since been added
// We assume that the caller has first Merged the most recent state.
return true, nil
}
return false, nil
}
// Collapse constructs a verifiable proof stating that the server has collapsed the previous history into the current
// root = H(onion)
// L = H(Sign(LatestCommit))
func (as *Store) Collapse() {
as.LatestCommit = as.identity.Sign(as.transcript.CommitToTranscript(collapse))
}

View File

@ -0,0 +1,70 @@
package auditable
import (
"cwtch.im/tapir/persistence"
"cwtch.im/tapir/primitives"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"os"
"testing"
)
func BenchmarkAuditableStore(b *testing.B) {
log.SetLevel(log.LevelDebug)
os.Remove("benchmark-auditablestore.db")
as := new(Store)
serverID, _ := primitives.InitializeEphemeralIdentity()
as.Init(serverID)
db := new(persistence.BoltPersistence)
db.Open("benchmark-auditablestore.db")
as.LoadFromStorage(db)
for i := 0; i < b.N; i++ {
data := fmt.Sprintf("Message %v", i)
as.Add(Message(data))
}
db.Close()
db.Open("benchmark-auditablestore.db")
vs := new(Store)
vs.Init(serverID)
vs.LoadFromStorage(db)
db.Close()
os.Remove("benchmark-auditablestore.db")
}
func TestAuditableStore(t *testing.T) {
as := new(Store)
vs := new(Store)
serverID, _ := primitives.InitializeEphemeralIdentity()
as.Init(serverID)
vs.Init(serverID) // This doesn't do anything
as.Add([]byte("Hello World"))
state := as.GetState()
if vs.MergeState(state) != nil {
t.Fatalf("Fraud Proof Failed on Honest Proof")
}
fraudProof := as.Add([]byte("Hello World 2"))
// If you comment these out it simulates a lying server.
state = as.GetState()
if vs.MergeState(state) != nil {
t.Fatalf("Fraud Proof Failed on Honest Proof")
}
fraud, err := vs.VerifyFraudProof(as.LatestCommit, fraudProof, serverID.PublicKey())
if err != nil {
t.Fatalf("Error validated fraud proof: %v", err)
}
if fraud {
t.Fatalf("Technically a fraud, but the client hasn't updated yet")
}
}

View File

@ -2,6 +2,8 @@ package primitives
import (
"crypto/sha256"
"math"
"math/big"
"sync"
)
@ -12,32 +14,30 @@ type BloomFilter struct {
}
// Init constructs a bloom filter of size m
func (bf *BloomFilter) Init(m int16) {
func (bf *BloomFilter) Init(m int64) {
bf.B = make([]bool, m)
}
// Hash transforms a message to a set of bit flips
// Supports up to m == 65535
func (bf *BloomFilter) Hash(msg []byte) []int {
hash := sha256.Sum256(msg)
pos1a := (int(hash[0]) + int(hash[1]) + int(hash[2]) + int(hash[3])) % 0xFF
pos1b := (int(hash[4]) + int(hash[5]) + int(hash[6]) + int(hash[7])) % 0xFF
pos1 := ((pos1a << 8) + pos1b) & (0xFFFF % len(bf.B))
// 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...))
pos2a := (int(hash[8]) + int(hash[9]) + int(hash[10]) + int(hash[11])) % 0xFF
pos2b := (int(hash[12]) + int(hash[13]) + int(hash[14]) + int(hash[15])) % 0xFF
pos2 := ((pos2a << 8) + pos2b) & (0xFFFF % len(bf.B))
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))
pos3a := (int(hash[16]) + int(hash[17]) + int(hash[18]) + int(hash[19])) % 0xFF
pos3b := (int(hash[20]) + int(hash[21]) + int(hash[22]) + int(hash[23])) % 0xFF
pos3 := ((pos3a << 8) + pos3b) & (0xFFFF % len(bf.B))
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()
pos4a := (int(hash[24]) + int(hash[25]) + int(hash[26]) + int(hash[27])) % 0xFF
pos4b := (int(hash[28]) + int(hash[29]) + int(hash[30]) + int(hash[31])) % 0xFF
pos4 := ((pos4a << 8) + pos4b) & (0xFFFF % len(bf.B))
return []int{pos1, pos2, pos3, pos4}
return []int{int(p1), int(p2), int(p3), int(p4)}
}
// Insert updates the BloomFilter (suitable for concurrent use)

24
primitives/bloom_test.go Normal file
View File

@ -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)
}

View File

@ -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("")))
}

View File

@ -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)
}

View File

@ -0,0 +1,4 @@
package bulletproofs
type Generator struct {
}

View File

@ -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
}

View File

@ -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!")
}
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -0,0 +1,108 @@
package core
import (
"fmt"
"github.com/gtank/merlin"
ristretto "github.com/gtank/ristretto255"
"golang.org/x/crypto/sha3"
"io"
)
// 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.
//
// 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
}
// NewTranscript creates a new Transcript with the given Label, the label should be unique to the application
func NewTranscript(label string) *Transcript {
transcript := new(Transcript)
transcript.merlinTranscript = merlin.NewTranscript(label)
return transcript
}
// AddToTranscript appends a value to the transcript with the given label
// This binds the given data to the label.
func (t *Transcript) AddToTranscript(label string, b []byte) {
op := fmt.Sprintf("%s (%d) %x;", label, len(b), b)
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
t.merlinTranscript.AppendMessage([]byte(label), b)
}
// AddElementToTranscript appends a value to the transcript with the given label
// This binds the given data to the label.
func (t *Transcript) AddElementToTranscript(label string, element *ristretto.Element) {
t.AddToTranscript(label, element.Encode([]byte{}))
}
// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
func (t Transcript) OutputTranscriptToAudit() string {
return t.transcript
}
// NewProtocol provides explicit protocol separation in a transcript (more readable audit scripts and even more explicit
// binding of committed values to a given context)
func (t *Transcript) NewProtocol(label string) {
op := fmt.Sprintf("---- new-protcol: %s ----", label)
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
t.merlinTranscript.AppendMessage([]byte("protocol"), []byte(label))
}
// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
func (t *Transcript) CommitToTranscript(label string) []byte {
//t.AddToTranscript("commit", []byte(label))
b := t.merlinTranscript.ExtractBytes([]byte(label), 64)
return b
}
// PRNG defines a psuedorandom number generator
type PRNG struct {
prng io.Reader
}
// Next returns the next "random" scalar from the PRNG
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.
func (t *Transcript) CommitToPRNG(label string) PRNG {
b := t.merlinTranscript.ExtractBytes([]byte(label), 64)
prng := sha3.NewShake256()
prng.Write(b)
return PRNG{prng: prng}
}
// CommitToGenerator derives a verifiably random generator from the transcript
func (t *Transcript) CommitToGenerator(label string) *ristretto.Element {
c := t.CommitToTranscript(label)
return new(ristretto.Element).FromUniformBytes(c)
}
// CommitToGenerators derives a set of verifiably random generators from the transcript
func (t *Transcript) CommitToGenerators(label string, n int) (generators []*ristretto.Element) {
for i := 0; i < n; i++ {
generators = append(generators, t.CommitToGenerator(fmt.Sprintf("%v-%d", label, i)))
}
return generators
}
// CommitToTranscriptScalar is a convenience method for CommitToTranscript which returns a ristretto Scalar
func (t *Transcript) CommitToTranscriptScalar(label string) *ristretto.Scalar {
c := t.CommitToTranscript(label)
s := new(ristretto.Scalar)
s.FromUniformBytes(c[:])
return s
}

View File

@ -0,0 +1,28 @@
package core
import (
"testing"
)
func TestNewTranscript(t *testing.T) {
// Some very basic integrity checking
transcript := NewTranscript("label")
transcript.AddToTranscript("action", []byte("test data"))
if transcript.OutputTranscriptToAudit() != transcript.OutputTranscriptToAudit() {
t.Fatalf("Multiple Audit Calls should not impact underlying Transcript")
}
t.Logf("%v", transcript.OutputTranscriptToAudit())
t.Logf("%v", transcript.CommitToTranscript("first commit"))
t.Logf("%v", transcript.OutputTranscriptToAudit())
t.Logf("%v", transcript.CommitToTranscript("second commit"))
t.Logf("%v", transcript.OutputTranscriptToAudit())
transcript.AddToTranscript("action", []byte("test data"))
t.Logf("%v", transcript.CommitToTranscript("third commit"))
t.Logf("%v", transcript.OutputTranscriptToAudit())
}

View File

@ -41,7 +41,7 @@ func (i *Identity) PublicKey() ed25519.PublicKey {
return *i.edpubk
}
// EDH performs a diffie-helman operation on this identities private key with the given public key.
// EDH performs a diffie-hellman operation on this identities private key with the given public key.
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
secret := utils.EDH(*i.edpk, key)
return secret[:]
@ -51,3 +51,8 @@ func (i *Identity) EDH(key ed25519.PublicKey) []byte {
func (i *Identity) Hostname() string {
return utils.GetTorV3Hostname(*i.edpubk)
}
// Sign produces a signature for a given message attributable to the given identity
func (i *Identity) Sign(input []byte) []byte {
return ed25519.Sign(*i.edpk, input)
}

View File

@ -0,0 +1,17 @@
package primitives
import (
"testing"
)
func TestIdentity_EDH(t *testing.T) {
id1, _ := InitializeEphemeralIdentity()
id2, _ := InitializeEphemeralIdentity()
k1 := id1.EDH(id2.PublicKey())
k2 := id2.EDH(id1.PublicKey())
t.Logf("k1: %x\nk2: %x\n", k1, k2)
}

View File

@ -0,0 +1,17 @@
package privacypass
// Transcript Constants
const (
BatchProofProtocol = "privacy-pass-batch-proof"
BatchProofX = "X-batch"
BatchProofY = "Y-batch"
BatchProofPVector = "P-vector"
BatchProofQVector = "Q-vector"
DLEQX = "X"
DLEQY = "Y"
DLEQP = "P"
DLEQQ = "Q"
DLEQA = "A"
DLEQB = "B"
)

View File

@ -0,0 +1,69 @@
package privacypass
import (
"cwtch.im/tapir/primitives/core"
"github.com/bwesterb/go-ristretto"
)
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
//gut In Ernest F. Brickell, editor,CRYPTO92,volume 740 ofLNCS, pages 89105. 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 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
//
// Sends c,s to Vicky
func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) DLEQProof {
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())
transcript.AddToTranscript(DLEQP, P.Bytes())
transcript.AddToTranscript(DLEQQ, Q.Bytes())
transcript.AddToTranscript(DLEQA, A.Bytes())
transcript.AddToTranscript(DLEQB, B.Bytes())
c := transcript.CommitToTranscriptScalar("c")
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 P = kX, Q = kP, Y=kX, and Proof = (c,s)
// Vicky: X' := sX
// 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.Point, Y *ristretto.Point, P *ristretto.Point, Q *ristretto.Point, transcript *core.Transcript) bool {
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.Point).Add(Xs, Yc)
B := new(ristretto.Point).Add(Ps, Qc)
transcript.AddToTranscript(DLEQX, X.Bytes())
transcript.AddToTranscript(DLEQY, Y.Bytes())
transcript.AddToTranscript(DLEQP, P.Bytes())
transcript.AddToTranscript(DLEQQ, Q.Bytes())
transcript.AddToTranscript(DLEQA, A.Bytes())
transcript.AddToTranscript(DLEQB, B.Bytes())
return transcript.CommitToTranscriptScalar("c").Equals(dleq.C)
}

View File

@ -0,0 +1,107 @@
package privacypass
import (
"crypto/hmac"
"crypto/rand"
"cwtch.im/tapir/primitives/core"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/bwesterb/go-ristretto"
"golang.org/x/crypto/sha3"
)
// Token is an implementation of PrivacyPass
// Davidson A, Goldberg I, Sullivan N, Tankersley G, Valsorda F. Privacy pass: Bypassing internet challenges anonymously. Proceedings on Privacy Enhancing Technologies. 2018 Jun 1;2018(3):164-80.
type Token struct {
t []byte
r *ristretto.Scalar
W *ristretto.Point
}
// BlindedToken encapsulates a Blinded Token
type BlindedToken struct {
P *ristretto.Point
}
// SignedToken encapsulates a Signed (Blinded) Token
type SignedToken struct {
Q *ristretto.Point
}
// SpentToken encapsulates the parameters needed to spend a Token
type SpentToken struct {
T []byte
MAC []byte
}
// TokenPaymentHandler defines an interface with external payment processors
type TokenPaymentHandler interface {
MakePayment()
NextToken(data []byte) (SpentToken, error)
}
// GenBlindedToken initializes the Token
// GenToken() & Blind()
func (t *Token) GenBlindedToken() BlindedToken {
t.t = make([]byte, 32)
rand.Read(t.t)
t.r = new(ristretto.Scalar).Rand()
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.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[:])
return SpentToken{t.t, mac.Sum(data)}
}
// GenerateBlindedTokenBatch generates a batch of blinded tokens (and their unblinded equivalents)
func GenerateBlindedTokenBatch(num int) (tokens []*Token, blindedTokens []BlindedToken) {
for i := 0; i < num; i++ {
tokens = append(tokens, new(Token))
blindedTokens = append(blindedTokens, tokens[i].GenBlindedToken())
}
return
}
// verifyBatchProof verifies a given batch proof (see also UnblindSignedTokenBatch)
func verifyBatchProof(dleq DLEQProof, Y *ristretto.Point, blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) bool {
transcript.NewProtocol(BatchProofProtocol)
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 := new(ristretto.Point).SetZero()
Z := new(ristretto.Point).SetZero()
for i := range blindedTokens {
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, 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.Point, proof DLEQProof, transcript *core.Transcript) bool {
verified := verifyBatchProof(proof, Y, blindedTokens, signedTokens, transcript)
if !verified {
return false
}
for i, t := range tokens {
t.unblindSignedToken(signedTokens[i])
}
return true
}

View File

@ -0,0 +1,69 @@
package privacypass
import (
"cwtch.im/tapir/persistence"
"cwtch.im/tapir/primitives/core"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"testing"
)
func TestToken_SpendToken(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")
}
if err := server.SpendToken(spentToken, []byte("Hello")); err != nil {
t.Errorf("Token Should be Valid: %v", err)
}
if err := server.SpendToken(spentToken, []byte("Hello")); err == nil {
t.Errorf("Token Should be Spent")
}
}
func TestGenerateBlindedTokenBatch(t *testing.T) {
log.SetLevel(log.LevelDebug)
db := new(persistence.BoltPersistence)
db.Open("tokens.db")
server := NewTokenServerFromStore(db)
clientTranscript := core.NewTranscript("privacyPass")
serverTranscript := core.NewTranscript("privacyPass")
tokens, blindedTokens := GenerateBlindedTokenBatch(10)
batchProof := server.SignBlindedTokenBatch(blindedTokens, serverTranscript)
verified := UnblindSignedTokenBatch(tokens, blindedTokens, batchProof.SignedTokens, server.Y, batchProof.Proof, clientTranscript)
if !verified {
t.Errorf("Something went wrong, the proof did not pass")
}
// Attempt to Spend All the tokens
for _, token := range tokens {
spentToken := token.SpendToken([]byte("Hello"))
if err := server.SpendToken(spentToken, []byte("Hello")); err != nil {
t.Errorf("Token Should be Valid: %v", err)
}
}
t.Logf("Client Transcript,: %s", clientTranscript.OutputTranscriptToAudit())
t.Logf("Server Transcript,: %s", serverTranscript.OutputTranscriptToAudit())
wrongTranscript := core.NewTranscript("wrongTranscript")
verified = UnblindSignedTokenBatch(tokens, blindedTokens, batchProof.SignedTokens, server.Y, batchProof.Proof, wrongTranscript)
if verified {
t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit())
}
db.Close()
}

View File

@ -0,0 +1,109 @@
package privacypass
import (
"crypto/hmac"
"cwtch.im/tapir/persistence"
"cwtch.im/tapir/primitives/core"
"encoding/hex"
"fmt"
"github.com/bwesterb/go-ristretto"
"golang.org/x/crypto/sha3"
"sync"
)
// TokenServer implements a token server.
type TokenServer struct {
k *ristretto.Scalar
Y *ristretto.Point
seen map[string]bool
persistanceService persistence.Service
mutex sync.Mutex
}
// SignedBatchWithProof encapsulates a signed batch of blinded tokens with a batch proof for verification
type SignedBatchWithProof struct {
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).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(persistenceService persistence.Service) TokenServer {
k := new(ristretto.Scalar).Rand()
persistenceService.Setup([]string{tokenBucket})
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.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 {
var signedTokens []SignedToken
for _, bt := range blindedTokens {
signedTokens = append(signedTokens, ts.SignBlindedToken(bt))
}
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 {
transcript.NewProtocol(BatchProofProtocol)
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 := new(ristretto.Point).SetZero()
Z := new(ristretto.Point).SetZero()
for i := range blindedTokens {
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 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.
func (ts *TokenServer) SpendToken(token SpentToken, data []byte) error {
ts.mutex.Lock()
defer ts.mutex.Unlock() // We only want 1 client at a time redeeming tokens to prevent double-spends
if ts.persistanceService == nil {
if _, spent := ts.seen[hex.EncodeToString(token.T)]; spent {
return fmt.Errorf("token: %v has already been spent", token)
}
} else {
spent, err := ts.persistanceService.Check(tokenBucket, hex.EncodeToString(token.T))
if err != nil || spent == true {
return fmt.Errorf("token: %v has already been spent", token)
}
}
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[:])
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 {
ts.persistanceService.Persist(tokenBucket, hex.EncodeToString(token.T), true)
}
return nil
}
return fmt.Errorf("token: %v is invalid and/or has not been signed by this service", token)
}

View File

@ -1,21 +0,0 @@
package primitives
import (
"time"
)
// TimeProvider is an interface used by services to timestamp events. Why not just have them use time.Now()? We want
// to be able to write tests that simulate behavior over several hours, and thus having an interface to abstract away
// time details for the services is very useful.
type TimeProvider interface {
GetCurrentTime() time.Time
}
// OSTimeProvider provides a wrapper around time provider which simply provides the time as given by the operating system.
type OSTimeProvider struct {
}
// GetCurrentTime returns the time provided by the OS
func (ostp OSTimeProvider) GetCurrentTime() time.Time {
return time.Now()
}

View File

@ -1,51 +0,0 @@
package primitives
import (
"fmt"
"golang.org/x/crypto/sha3"
"hash"
)
// Transcript implements a transcript of a public coin argument.
//
// We have the following goals:
// - Provide a consisted transcript API for our zero knowledge protocols
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
// - produce an auditable human-readable transcript.
//
// 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 {
hash hash.Hash
transcript string
}
// NewTranscript creates a new Transcript with the given Label, the label should be unique to the application
func NewTranscript(label string) *Transcript {
transcript := new(Transcript)
transcript.hash = sha3.New256()
transcript.AddToTranscript("protocol", []byte(label))
return transcript
}
// AddToTranscript appends a value to the transcript with the given label
// This binds the given data to the label.
func (t *Transcript) AddToTranscript(label string, b []byte) {
op := fmt.Sprintf("%s (%d) %x;", label, len(b), b)
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)
t.hash.Write([]byte(op))
}
// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
func (t Transcript) OutputTranscriptToAudit() string {
return t.transcript
}
// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
func (t *Transcript) CommitToTranscript(label string) []byte {
t.AddToTranscript("commit", []byte(label))
b := t.hash.Sum([]byte{})
t.AddToTranscript(label, b)
return b
}

View File

@ -19,7 +19,7 @@ type Service interface {
Connect(hostname string, application Application) (bool, error)
Listen(application Application) error
GetConnection(connectionID string) (Connection, error)
WaitForCapabilityOrClose(connectionID string, capability string) (Connection, error)
WaitForCapabilityOrClose(connectionID string, capability Capability) (Connection, error)
Shutdown()
}
@ -30,12 +30,13 @@ type Connection interface {
ID() *primitives.Identity
Expect() []byte
SetHostname(hostname string)
HasCapability(name string) bool
SetCapability(name string)
HasCapability(name Capability) bool
SetCapability(name Capability)
SetEncryptionKey(key [32]byte)
Send(message []byte)
Close()
App() Application
SetApp(application Application)
IsClosed() bool
}
@ -76,6 +77,11 @@ func (c *connection) App() Application {
return c.app
}
// App returns the overarching application using this Connection.
func (c *connection) SetApp(application Application) {
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 {
@ -100,13 +106,13 @@ func (c *connection) SetHostname(hostname string) {
}
// SetCapability sets a capability on the connection
func (c *connection) SetCapability(name string) {
func (c *connection) SetCapability(name Capability) {
log.Debugf("[%v -- %v] Setting Capability %v", c.identity.Hostname(), c.hostname, name)
c.capabilities.Store(name, true)
}
// HasCapability checks if the connection has a given capability
func (c *connection) HasCapability(name string) bool {
func (c *connection) HasCapability(name Capability) bool {
_, ok := c.capabilities.Load(name)
return ok
}

View File

@ -21,12 +21,12 @@ type SimpleApp struct {
}
// NewInstance should always return a new instantiation of the application.
func (ea SimpleApp) NewInstance() tapir.Application {
func (ea *SimpleApp) NewInstance() tapir.Application {
return new(SimpleApp)
}
// Init is run when the connection is first started.
func (ea SimpleApp) Init(connection tapir.Connection) {
func (ea *SimpleApp) Init(connection tapir.Connection) {
// First run the Authentication App
ea.AuthApp.Init(connection)
@ -76,7 +76,7 @@ func TestTapir(t *testing.T) {
sg := new(sync.WaitGroup)
sg.Add(1)
go func() {
service.Listen(SimpleApp{})
service.Listen(new(SimpleApp))
sg.Done()
}()
@ -114,7 +114,7 @@ func genclient(acn connectivity.ACN) (tapir.Service, string) {
// Client will Connect and launch it's own Echo App goroutine.
func connectclient(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
client.Connect(utils.GetTorV3Hostname(key), SimpleApp{})
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.

View File

@ -40,7 +40,7 @@ func TestTapirMaliciousRemote(t *testing.T) {
sg := new(sync.WaitGroup)
sg.Add(1)
go func() {
service.Listen(applications.AuthApp{})
service.Listen(new(applications.AuthApp))
sg.Done()
}()
@ -67,7 +67,7 @@ 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(utils.GetTorV3Hostname(key), 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.

View File

@ -3,6 +3,12 @@
set -e
pwd
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