Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
|
8369ae605b | 3 years ago |
|
1066862f58 | 3 years ago |
|
a2cfcaf8a4 | 3 years ago |
35 changed files with 1648 additions and 58 deletions
@ -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() |
|||
} |
|||
|
@ -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() |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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() |
|||
} |
@ -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 |
|||
} |
@ -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) |
|||
} |
@ -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() |
|||
} |
@ -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() |
|||
} |
@ -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)) |
|||
} |
@ -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") |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
package primitives |
|||
|
|||
import ( |
|||
"strconv" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBloomFilter_Insert(t *testing.T) { |
|||
bf := new(BloomFilter) |
|||
bf.Init(256) |
|||
|
|||
fp := 0 |
|||
for i := 0; i < 256; i++ { |
|||
input := []byte("test" + strconv.Itoa(256+i)) |
|||
if bf.Check(input) { |
|||
t.Log("False Positive!") |
|||
fp++ |
|||
} |
|||
bf.Insert(input) |
|||
} |
|||
|
|||
t.Logf("Num false positives %v %v%%", fp, (float64(fp)/256.0)*100) |
|||
|
|||
} |
@ -0,0 +1,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()) |
|||
|
|||
} |
@ -0,0 +1,71 @@ |
|||
package primitives |
|||
|
|||
import ( |
|||
"cwtch.im/tapir/primitives/core" |
|||
"github.com/bwesterb/go-ristretto" |
|||
) |
|||
|
|||
// DLProof Encapsulates a Discrete Log / Schnorr Proof
|
|||
// Note that these parameters are read-only.
|
|||
type DLProof struct { |
|||
V, A ristretto.Point |
|||
R ristretto.Scalar |
|||
} |
|||
|
|||
// DiscreteLogProof - Proof of Knowledge of Exponent
|
|||
// Given V = xG
|
|||
// Peggy: z := choose randomly from Zq
|
|||
// A := zG
|
|||
// c := H(transcript(G,V,A)) mod q
|
|||
// r := (z + cx) mod q
|
|||
//
|
|||
// Sends A,r,V to Vicky
|
|||
func DiscreteLogProof(x ristretto.Scalar, v ristretto.Point, transcript *core.Transcript) (proof DLProof) { |
|||
|
|||
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes()) |
|||
|
|||
// We bind the proof to our public V
|
|||
proof.V = v |
|||
transcript.AddToTranscript("V", proof.V.Bytes()) |
|||
|
|||
// Generate a random z
|
|||
// A := zG
|
|||
z := new(ristretto.Scalar).Rand() |
|||
proof.A = *new(ristretto.Point).ScalarMultBase(z) |
|||
transcript.AddToTranscript("A", proof.A.Bytes()) |
|||
|
|||
// Derive Challenge
|
|||
c := transcript.CommitToTranscriptScalar("c") |
|||
|
|||
// r := (z + cx) mod p
|
|||
cx := new(ristretto.Scalar).Mul(c, &x) |
|||
proof.R = *new(ristretto.Scalar).Add(z, cx) |
|||
|
|||
return |
|||
} |
|||
|
|||
// VerifyDiscreteLogProof validates a given Schnorr Proof
|
|||
// Vicky gets A,r,V from Peggy
|
|||
// Vicky computes c := H(transcript(G,V,A)) mod q
|
|||
// Vicky checks rG := A + cV
|
|||
// rG ?= zG + cV
|
|||
// (z+cx)G ?= zG + cV
|
|||
// ?= zG + cxG
|
|||
// Thus demonstrating that Peggy knows the discrete log to V
|
|||
func VerifyDiscreteLogProof(proof DLProof, transcript *core.Transcript) bool { |
|||
|
|||
transcript.AddToTranscript("G", new(ristretto.Point).SetBase().Bytes()) |
|||
transcript.AddToTranscript("V", proof.V.Bytes()) |
|||
transcript.AddToTranscript("A", proof.A.Bytes()) |
|||
c := transcript.CommitToTranscriptScalar("c") |
|||
|
|||
// Compute left hand side
|
|||
lhs := new(ristretto.Point).ScalarMultBase(&proof.R) |
|||
|
|||
// Compute right hand side
|
|||
cV := new(ristretto.Point).ScalarMult(&proof.V, c) |
|||
rhs := new(ristretto.Point).Add(&proof.A, cV) |
|||
|
|||
// Result of verification: lhs ?= rhs
|
|||
return lhs.Equals(rhs) |
|||
} |
@ -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) |
|||
|
|||
} |
@ -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" |
|||
) |
@ -0,0 +1,69 @@ |
|||
package privacypass |
|||
|
|||
import ( |
|||
"cwtch.im/tapir/primitives/core" |
|||
"github.com/bwesterb/go-ristretto" |
|||
) |
|||
|
|||
// DLEQProof encapsulates a Chaum-Pedersen DLEQ Proof
|
|||
// David Chaum and Torben P. Pedersen. Wallet databaseswith observers. In Ernest F. Brickell, editor,CRYPTO’92,volume 740 ofLNCS, pages 89–105. Springer, Heidelberg,August 1993
|
|||
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") |
|||