Compare commits
8 Commits
master
...
bulletproo
Author | SHA1 | Date |
---|---|---|
Sarah Jamie Lewis | daa05d97c8 | |
Sarah Jamie Lewis | 5afd4a930a | |
Sarah Jamie Lewis | ec8c9352ec | |
Sarah Jamie Lewis | 9332385a6f | |
Sarah Jamie Lewis | 136b9b8192 | |
Sarah Jamie Lewis | bd3a6043be | |
Sarah Jamie Lewis | 1066862f58 | |
Sarah Jamie Lewis | a2cfcaf8a4 |
|
@ -3,3 +3,5 @@ vendor/
|
|||
/tor/
|
||||
coverage.out
|
||||
/testing/tor/
|
||||
/applications/tor/
|
||||
*.db
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package primitives
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBloomFilter_Insert(t *testing.T) {
|
||||
bf := new(BloomFilter)
|
||||
bf.Init(256)
|
||||
|
||||
fp := 0
|
||||
for i := 0; i < 256; i++ {
|
||||
input := []byte("test" + strconv.Itoa(256+i))
|
||||
if bf.Check(input) {
|
||||
t.Log("False Positive!")
|
||||
fp++
|
||||
}
|
||||
bf.Insert(input)
|
||||
}
|
||||
|
||||
t.Logf("Num false positives %v %v%%", fp, (float64(fp)/256.0)*100)
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConstraintSystem(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
cs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
|
||||
abytes := sha512.Sum512([]byte("a"))
|
||||
a := new(ristretto.Scalar)
|
||||
a.FromUniformBytes(abytes[:])
|
||||
|
||||
bbytes := sha512.Sum512([]byte("b"))
|
||||
b := new(ristretto.Scalar)
|
||||
b.FromUniformBytes(bbytes[:])
|
||||
|
||||
xbytes := sha512.Sum512([]byte("b"))
|
||||
x := new(ristretto.Scalar)
|
||||
x.FromUniformBytes(xbytes[:])
|
||||
|
||||
ybytes := sha512.Sum512([]byte("a"))
|
||||
y := new(ristretto.Scalar)
|
||||
y.FromUniformBytes(ybytes[:])
|
||||
|
||||
prng := core.NewTranscript("private").CommitToPRNG("private")
|
||||
|
||||
V1, a_lc := cs.Commit(a, prng.Next())
|
||||
V2, b_lc := cs.Commit(b, prng.Next())
|
||||
V3, x_lc := cs.Commit(x, prng.Next())
|
||||
V4, y_lc := cs.Commit(y, prng.Next())
|
||||
|
||||
vcs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
va_lc := vcs.VerifierCommit(V1)
|
||||
vb_lc := vcs.VerifierCommit(V2)
|
||||
vx_lc := vcs.VerifierCommit(V3)
|
||||
vy_lc := vcs.VerifierCommit(V4)
|
||||
|
||||
_, _, in := cs.Multiply(a_lc, b_lc)
|
||||
_, _, out := cs.Multiply(x_lc, y_lc)
|
||||
cs.Constrain(in.Sub(out))
|
||||
|
||||
_, _, vin := vcs.Multiply(va_lc, vb_lc)
|
||||
_, _, vout := vcs.Multiply(vx_lc, vy_lc)
|
||||
vcs.Constrain(vin.Sub(vout))
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(core.One())
|
||||
|
||||
lhs := new(ristretto.Scalar)
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aL, wL))
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aR, wR))
|
||||
|
||||
rhs := new(ristretto.Scalar)
|
||||
rhs.Add(rhs, core.InnerProduct(cs.aO, wO))
|
||||
rrhs := new(ristretto.Scalar)
|
||||
rrhs.Add(rrhs, core.InnerProduct(cs.v, wV))
|
||||
|
||||
t.Logf("lhs: %v\n", lhs)
|
||||
t.Logf("rhs: %v\n", rhs)
|
||||
t.Logf("rrhs: %v\n", rrhs)
|
||||
t.Logf("\nwL: %v\nwR: %v\nwO: %v\nwC: %v", wL, wR, wO, wV)
|
||||
|
||||
proof := cs.Prove(cs.params, core.NewTranscript(""))
|
||||
|
||||
t.Logf("Proof Result: %v", vcs.Verify(proof, cs.params, core.NewTranscript("")))
|
||||
}
|
||||
|
||||
func TestConstraintSystemMix(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
cs := NewConstrainSystem(Setup(2, core.NewTranscript("")))
|
||||
|
||||
one := core.One()
|
||||
two := new(ristretto.Scalar).Add(one, one)
|
||||
three := new(ristretto.Scalar).Add(two, one)
|
||||
four := new(ristretto.Scalar).Add(two, two)
|
||||
|
||||
prng := core.NewTranscript("private").CommitToPRNG("private")
|
||||
|
||||
V1, a_lc := cs.Commit(three, prng.Next())
|
||||
V2, b_lc := cs.Commit(three, prng.Next())
|
||||
V3, x_lc := cs.Commit(two, prng.Next())
|
||||
V4, y_lc := cs.Commit(four, prng.Next())
|
||||
|
||||
// todo make this an actual verifier!
|
||||
cs.VerifierCommit(V1)
|
||||
cs.VerifierCommit(V2)
|
||||
cs.VerifierCommit(V3)
|
||||
cs.VerifierCommit(V4)
|
||||
|
||||
_, _, out := cs.Multiply(a_lc.Sub(x_lc).Add(b_lc.Sub(y_lc)), x_lc.Add(y_lc).Sub(a_lc.Add(b_lc)))
|
||||
_, _, out2 := cs.Multiply(a_lc.Sub(x_lc).Add(b_lc.Sub(y_lc)), x_lc.Add(y_lc).Sub(a_lc.Add(b_lc)))
|
||||
cs.Constrain(out.Add(out2))
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(core.One())
|
||||
|
||||
lhs := new(ristretto.Scalar)
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aL, wL))
|
||||
lhs.Add(lhs, core.InnerProduct(cs.aR, wR))
|
||||
|
||||
rhs := new(ristretto.Scalar)
|
||||
rhs.Add(rhs, core.InnerProduct(cs.aO, wO))
|
||||
rrhs := new(ristretto.Scalar)
|
||||
rrhs.Add(rrhs, core.InnerProduct(cs.v, wV))
|
||||
|
||||
t.Logf("lhs: %v\n", lhs)
|
||||
t.Logf("rhs: %v\n", rhs)
|
||||
t.Logf("rrhs: %v\n", rrhs)
|
||||
t.Logf("\nwL: %v\nwR: %v\nwO: %v\nwC: %v", wL, wR, wO, wV)
|
||||
|
||||
proof := cs.Prove(cs.params, core.NewTranscript(""))
|
||||
t.Logf("Proof Result: %v", cs.Verify(proof, cs.params, core.NewTranscript("")))
|
||||
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
type Variable struct {
|
||||
Enum string
|
||||
Index int
|
||||
}
|
||||
|
||||
type Term struct {
|
||||
Variable
|
||||
Coefficient *ristretto.Scalar
|
||||
}
|
||||
|
||||
// LinearCombination represent a vector of Terms
|
||||
// We use the same structure as dalek-bulletproofs and reference the variable by an index into the canonical
|
||||
// term in the constraint system.
|
||||
type LinearCombination struct {
|
||||
Terms []Term
|
||||
}
|
||||
|
||||
// Adding together linear combinations lc+olc = lc[:]+olc[:]
|
||||
// (a * -1) + (b * -1) = (a*-1 + b * -1)
|
||||
func (lc *LinearCombination) Add(olc *LinearCombination) *LinearCombination {
|
||||
terms := lc.Terms
|
||||
for _, term := range olc.Terms {
|
||||
terms = append(terms, term)
|
||||
}
|
||||
return &LinearCombination{terms}
|
||||
}
|
||||
|
||||
// Subtracting a linear combination olc from lc results in the terms of lc being extended
|
||||
// with those of lc, but with the coefficients in old being negated.
|
||||
// (a * -1 - b * -1) = (a*-1 + b * 1)
|
||||
func (lc *LinearCombination) Sub(olc *LinearCombination) *LinearCombination {
|
||||
terms := lc.Terms
|
||||
for _, term := range olc.Terms {
|
||||
term.Coefficient = new(ristretto.Scalar).Negate(term.Coefficient)
|
||||
terms = append(terms, term)
|
||||
}
|
||||
return &LinearCombination{terms}
|
||||
}
|
||||
|
||||
// ConstraintSystem allow constructing constraint systems over committed parameters, generating a proof of that constraint
|
||||
// and allows a verifier to check that the constraint holds.
|
||||
type ConstraintSystem struct {
|
||||
aL core.ScalarVector
|
||||
aR core.ScalarVector
|
||||
aO core.ScalarVector
|
||||
|
||||
v core.ScalarVector
|
||||
vBlinding core.ScalarVector
|
||||
constraints []*LinearCombination
|
||||
|
||||
V core.GeneratorVector // todo clarify this
|
||||
|
||||
params CommitmentsParams
|
||||
}
|
||||
|
||||
func NewConstrainSystem(params CommitmentsParams) ConstraintSystem {
|
||||
cs := ConstraintSystem{}
|
||||
cs.params = params
|
||||
return cs
|
||||
}
|
||||
|
||||
// Multiply constructs a multiplication gate within our constraint system.
|
||||
// It takes in two linear combinations a and b, evaluates the result c, and outputs three linear combinations
|
||||
// one for the left input wire (a), one of the right input wire b, and one for the output wire (c)
|
||||
// a and b are constrained to (a*-1) and (b*-1)
|
||||
func (cs *ConstraintSystem) Multiply(a, b *LinearCombination) (*LinearCombination, *LinearCombination, *LinearCombination) {
|
||||
|
||||
l := cs.eval(a)
|
||||
r := cs.eval(b)
|
||||
|
||||
c := new(ristretto.Scalar).Multiply(l, r)
|
||||
|
||||
log.Debugf("%v & %v == %v", l, r, c)
|
||||
|
||||
a.Terms = append(a.Terms, Term{Variable{"left", len(cs.aL)}, new(ristretto.Scalar).Negate(core.One())})
|
||||
b.Terms = append(b.Terms, Term{Variable{"right", len(cs.aR)}, new(ristretto.Scalar).Negate(core.One())})
|
||||
|
||||
o := &LinearCombination{[]Term{{Variable{"output", len(cs.aO)}, core.One()}}}
|
||||
|
||||
cs.aL = append(cs.aL, l)
|
||||
cs.aR = append(cs.aR, r)
|
||||
cs.aO = append(cs.aO, c)
|
||||
|
||||
cs.Constrain(a)
|
||||
cs.Constrain(b)
|
||||
|
||||
return a, b, o
|
||||
|
||||
}
|
||||
|
||||
// eval evaluates a linear combination lc, by looking up each term in the relevant vector in the constrain system
|
||||
// and multiplying it by it's coefficient.
|
||||
func (cs *ConstraintSystem) eval(lc *LinearCombination) *ristretto.Scalar {
|
||||
result := new(ristretto.Scalar)
|
||||
for _, term := range lc.Terms {
|
||||
switch term.Enum {
|
||||
case "left":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aL[term.Index]))
|
||||
case "right":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aR[term.Index]))
|
||||
case "output":
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.aO[term.Index]))
|
||||
case "committed":
|
||||
if len(cs.V) > 0 {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, core.One()))
|
||||
} else {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(term.Coefficient, cs.v[term.Index]))
|
||||
}
|
||||
case "one":
|
||||
result.Add(result, term.Coefficient)
|
||||
default:
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (cs *ConstraintSystem) Commit(v *ristretto.Scalar, vBlind *ristretto.Scalar) (*ristretto.Element, *LinearCombination) {
|
||||
i := len(cs.v)
|
||||
cs.v = append(cs.v, v)
|
||||
cs.vBlinding = append(cs.vBlinding, vBlind)
|
||||
|
||||
V := core.MultiExp(core.ScalarVector{v, vBlind}, core.GeneratorVector{cs.params.g, cs.params.h})
|
||||
|
||||
return V, &LinearCombination{[]Term{{Variable{"committed", i}, core.One()}}}
|
||||
}
|
||||
|
||||
func (cs *ConstraintSystem) VerifierCommit(V *ristretto.Element) *LinearCombination {
|
||||
i := len(cs.V)
|
||||
cs.V = append(cs.V, V)
|
||||
return &LinearCombination{[]Term{{Variable{"committed", i}, core.One()}}}
|
||||
}
|
||||
|
||||
// Constrain adds the given linear combination to the constraints vector
|
||||
// when constraints are flattened, each constraint will be evaluated and determine the
|
||||
// weights to add.
|
||||
func (cs *ConstraintSystem) Constrain(c *LinearCombination) {
|
||||
cs.constraints = append(cs.constraints, c)
|
||||
}
|
||||
|
||||
// flatten constructs a set of 4 vectors of weights wL, wR, wO and wV representing the left inputs (L), right inputs (R), outputs (O) and
|
||||
// committed values (C) such that
|
||||
// <aL,wL> + <aR,wR> + <aO, wO> = <v,WL>
|
||||
func (cs *ConstraintSystem) flatten(z *ristretto.Scalar) (wL core.ScalarVector, wR core.ScalarVector, wO core.ScalarVector, wV core.ScalarVector) {
|
||||
wL = make(core.ScalarVector, len(cs.aL))
|
||||
wR = make(core.ScalarVector, len(cs.aL))
|
||||
wO = make(core.ScalarVector, len(cs.aL))
|
||||
var m int
|
||||
if len(cs.V) > 0 {
|
||||
m = len(cs.V)
|
||||
wV = make(core.ScalarVector, len(cs.V))
|
||||
} else {
|
||||
m = len(cs.v)
|
||||
wV = make(core.ScalarVector, len(cs.v))
|
||||
}
|
||||
|
||||
for i := 0; i < len(cs.aL); i++ {
|
||||
wL[i] = new(ristretto.Scalar)
|
||||
wR[i] = new(ristretto.Scalar)
|
||||
wO[i] = new(ristretto.Scalar)
|
||||
}
|
||||
for i := 0; i < m; i++ {
|
||||
wV[i] = new(ristretto.Scalar)
|
||||
}
|
||||
|
||||
expZ := new(ristretto.Scalar).Add(z, new(ristretto.Scalar).Zero())
|
||||
for _, constraint := range cs.constraints {
|
||||
for _, term := range constraint.Terms {
|
||||
log.Debugf("term: %v", term)
|
||||
switch term.Enum {
|
||||
case "left":
|
||||
wL[term.Index].Add(wL[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "right":
|
||||
wR[term.Index].Add(wR[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "output":
|
||||
wO[term.Index].Add(wO[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
case "committed":
|
||||
wV[term.Index].Subtract(wV[term.Index], new(ristretto.Scalar).Multiply(expZ, term.Coefficient))
|
||||
default:
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
expZ = expZ.Multiply(expZ, z)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ConstraintProof struct {
|
||||
Ai *ristretto.Element
|
||||
Ao *ristretto.Element
|
||||
S *ristretto.Element
|
||||
T1 *ristretto.Element
|
||||
T3 *ristretto.Element
|
||||
T4 *ristretto.Element
|
||||
T5 *ristretto.Element
|
||||
T6 *ristretto.Element
|
||||
Micro *ristretto.Scalar
|
||||
TX *ristretto.Scalar
|
||||
TX_Blinding *ristretto.Scalar
|
||||
IPP InnerProductProof
|
||||
}
|
||||
|
||||
// Prove the constraint
|
||||
func (cs *ConstraintSystem) Prove(c CommitmentsParams, transcript *core.Transcript) ConstraintProof {
|
||||
|
||||
n := len(cs.aL)
|
||||
|
||||
log.Debugf("n = %v\n", n)
|
||||
|
||||
// Generate a prng to from this transcript and some external randomness
|
||||
// We use this to generate the rest of our private scalars
|
||||
// TODO: move to transcript
|
||||
private := make([]byte, 64)
|
||||
rand.Read(private)
|
||||
prngTranscript := core.NewTranscript("private-transcript")
|
||||
prngTranscript.AddToTranscript("randomness", private)
|
||||
prng := prngTranscript.CommitToPRNG(transcript.OutputTranscriptToAudit())
|
||||
|
||||
alpha := prng.Next()
|
||||
beta := prng.Next()
|
||||
rho := prng.Next()
|
||||
|
||||
Ai := core.MultiExp(append(cs.aL.Join(cs.aR), alpha), append(c.G.Join(c.H), c.h))
|
||||
Ao := core.MultiExp(cs.aO.SafeAppend(beta), c.G.SafeAppend(c.h))
|
||||
|
||||
Sl := make(core.ScalarVector, c.max)
|
||||
Sr := make(core.ScalarVector, c.max)
|
||||
for i := 0; i < c.max; i++ {
|
||||
Sl[i] = prng.Next()
|
||||
Sr[i] = prng.Next()
|
||||
}
|
||||
|
||||
S := core.MultiExp(append(Sl.Join(Sr), rho), c.G.Join(c.H).SafeAppend(c.h))
|
||||
|
||||
transcript.AddToTranscript("Ai", []byte(Ai.String()))
|
||||
transcript.AddToTranscript("So", []byte(Ao.String()))
|
||||
transcript.AddToTranscript("S", []byte(S.String()))
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
yinv := new(ristretto.Scalar).Invert(y)
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
log.Debugf("Calculating y^%v", n)
|
||||
powY := core.PowerVector(y, n)
|
||||
powYInv := core.PowerVector(yinv, n)
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(z)
|
||||
|
||||
lhs := make([]core.ScalarVector, 4)
|
||||
//lhs[0] = 0
|
||||
lhs[1] = core.EntrywiseSum(cs.aL, core.EntryWiseProduct(powYInv, wR))
|
||||
lhs[2] = cs.aO
|
||||
lhs[3] = Sl
|
||||
|
||||
rhs := make([]core.ScalarVector, 4)
|
||||
rhs[0] = core.EntrywiseSub(wO, powY)
|
||||
rhs[1] = core.EntrywiseSum(core.EntryWiseProduct(powY, cs.aR), wL)
|
||||
// r2 = 0
|
||||
rhs[3] = core.EntryWiseProduct(powY, Sr)
|
||||
|
||||
t1 := core.InnerProduct(lhs[1], rhs[0])
|
||||
t2 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[1], rhs[1]), core.InnerProduct(lhs[2], rhs[0]))
|
||||
t3 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[2], rhs[1]), core.InnerProduct(lhs[3], rhs[0]))
|
||||
t4 := new(ristretto.Scalar).Add(core.InnerProduct(lhs[1], rhs[3]), core.InnerProduct(lhs[3], rhs[1]))
|
||||
t5 := core.InnerProduct(lhs[2], rhs[3])
|
||||
t6 := core.InnerProduct(lhs[3], rhs[3])
|
||||
|
||||
//t2 := core.InnerProduct(cs.aL,core.EntryWiseProduct(cs.aR, powY))
|
||||
//t2.Subtract(t2, core.InnerProduct(cs.aO, powY))
|
||||
//t2.Add(t2, )
|
||||
|
||||
//
|
||||
tau1 := prng.Next()
|
||||
tau3 := prng.Next()
|
||||
tau4 := prng.Next()
|
||||
tau5 := prng.Next()
|
||||
tau6 := prng.Next()
|
||||
|
||||
T1 := core.MultiExp(core.ScalarVector{t1, tau1}, core.GeneratorVector{c.g, c.h})
|
||||
T3 := core.MultiExp(core.ScalarVector{t3, tau3}, core.GeneratorVector{c.g, c.h})
|
||||
T4 := core.MultiExp(core.ScalarVector{t4, tau4}, core.GeneratorVector{c.g, c.h})
|
||||
T5 := core.MultiExp(core.ScalarVector{t5, tau5}, core.GeneratorVector{c.g, c.h})
|
||||
T6 := core.MultiExp(core.ScalarVector{t6, tau6}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
transcript.AddToTranscript("T1", []byte(T1.String()))
|
||||
transcript.AddToTranscript("T3", []byte(T3.String()))
|
||||
transcript.AddToTranscript("T4", []byte(T4.String()))
|
||||
transcript.AddToTranscript("T5", []byte(T5.String()))
|
||||
transcript.AddToTranscript("T6", []byte(T6.String()))
|
||||
|
||||
//u := transcript.CommitToTranscriptScalar("u")
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
log.Debugf("x: %v", x)
|
||||
|
||||
// T(X) = t0 + t1x + t2x
|
||||
TX := new(ristretto.Scalar).Zero()
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t1, x))
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t2, x2))
|
||||
x3 := new(ristretto.Scalar).Multiply(x2, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t3, x3))
|
||||
x4 := new(ristretto.Scalar).Multiply(x3, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t4, x4))
|
||||
x5 := new(ristretto.Scalar).Multiply(x4, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t5, x5))
|
||||
x6 := new(ristretto.Scalar).Multiply(x5, x)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t6, x6))
|
||||
|
||||
// evaluate l(X)
|
||||
// lx0 = 0
|
||||
lx1 := core.VectorMulScalar(lhs[1], x)
|
||||
lx2 := core.VectorMulScalar(lhs[2], x2)
|
||||
lx3 := core.VectorMulScalar(lhs[3], new(ristretto.Scalar).Multiply(x2, x))
|
||||
lx := core.EntrywiseSum(core.EntrywiseSum(lx1, lx2), lx3)
|
||||
|
||||
// evaluate r(X)
|
||||
rx1 := core.VectorMulScalar(rhs[1], x)
|
||||
// rx2 := core.VectorMulScalar(rhs[2],x2 ) rhs[2] == 0
|
||||
rx3 := core.VectorMulScalar(rhs[3], new(ristretto.Scalar).Multiply(x2, x))
|
||||
rx := core.EntrywiseSum(core.EntrywiseSum(rx1, rhs[0]), rx3)
|
||||
|
||||
// calculate the inner product (t̂)
|
||||
iplr := core.InnerProduct(lx, rx)
|
||||
|
||||
// T(x) ?= <l(X),r(X)>
|
||||
log.Debugf("T(X) = %v", TX)
|
||||
log.Debugf("ipp = %v", iplr)
|
||||
log.Debugf("equal: %v", TX.Equal(iplr) == 1)
|
||||
|
||||
tau_blind := new(ristretto.Scalar).Multiply(tau1, x)
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau3, x3))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau4, x4))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau5, x5))
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(tau6, x6))
|
||||
|
||||
//delta := core.InnerProduct(core.EntryWiseProduct(powYInv,wR), wL)
|
||||
|
||||
tau_blind.Add(tau_blind, new(ristretto.Scalar).Multiply(core.InnerProduct(wV, cs.vBlinding), x2))
|
||||
|
||||
micro := new(ristretto.Scalar).Multiply(alpha, x)
|
||||
micro.Add(micro, new(ristretto.Scalar).Multiply(beta, x2))
|
||||
micro.Add(micro, new(ristretto.Scalar).Multiply(rho, x3))
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(powY[i]), c.H[i])
|
||||
}
|
||||
|
||||
P := core.MultiExp(lx.Join(rx), c.G.Join(H_))
|
||||
log.Debugf("P: %v", P)
|
||||
|
||||
uP := new(ristretto.Element).Add(P, new(ristretto.Element).ScalarMult(iplr, c.u))
|
||||
log.Debugf("uP: %v", uP)
|
||||
ipp := ProveInnerProduct(lx, rx, c.u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
|
||||
return ConstraintProof{Ai, Ao, S, T1, T3, T4, T5, T6, micro, TX, tau_blind, ipp}
|
||||
}
|
||||
|
||||
// Verify the constraint
|
||||
func (cs *ConstraintSystem) Verify(proof ConstraintProof, c CommitmentsParams, transcript *core.Transcript) bool {
|
||||
|
||||
log.Debugf("Verifying: %v\n", proof)
|
||||
|
||||
n := len(cs.aL)
|
||||
transcript.AddToTranscript("Ai", []byte(proof.Ai.String()))
|
||||
transcript.AddToTranscript("So", []byte(proof.Ao.String()))
|
||||
transcript.AddToTranscript("S", []byte(proof.S.String()))
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
yinv := new(ristretto.Scalar).Invert(y)
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
log.Debugf("Calculating y^%v", n)
|
||||
powY := core.PowerVector(y, n)
|
||||
powYInv := core.PowerVector(yinv, n)
|
||||
|
||||
wL, wR, wO, wV := cs.flatten(z)
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(powYInv[i], c.H[i])
|
||||
}
|
||||
|
||||
Wl := core.MultiExp(wL, H_)
|
||||
Wr := core.MultiExp(core.EntryWiseProduct(powYInv, wR), c.G)
|
||||
Wo := core.MultiExp(wO, H_)
|
||||
|
||||
transcript.AddToTranscript("T1", []byte(proof.T1.String()))
|
||||
transcript.AddToTranscript("T3", []byte(proof.T3.String()))
|
||||
transcript.AddToTranscript("T4", []byte(proof.T4.String()))
|
||||
transcript.AddToTranscript("T5", []byte(proof.T5.String()))
|
||||
transcript.AddToTranscript("T6", []byte(proof.T6.String()))
|
||||
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
log.Debugf("x: %v", x)
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
x3 := new(ristretto.Scalar).Multiply(x2, x)
|
||||
x4 := new(ristretto.Scalar).Multiply(x3, x)
|
||||
x5 := new(ristretto.Scalar).Multiply(x4, x)
|
||||
x6 := new(ristretto.Scalar).Multiply(x5, x)
|
||||
|
||||
HY := core.MultiExp(core.VectorNegate(powY), H_)
|
||||
P := core.MultiExp(core.ScalarVector{x, x2, x3, x, x, core.One()}, core.GeneratorVector{proof.Ai, proof.Ao, proof.S, Wl, Wr, Wo})
|
||||
P.Add(P, HY)
|
||||
|
||||
P_ := P.Subtract(P, new(ristretto.Element).ScalarMult(proof.Micro, c.h))
|
||||
uP := new(ristretto.Element).Add(P_, new(ristretto.Element).ScalarMult(proof.TX, c.u))
|
||||
|
||||
log.Debugf("P: %v", P_)
|
||||
log.Debugf("uP: %v", uP)
|
||||
|
||||
lhs := core.MultiExp(core.ScalarVector{proof.TX, proof.TX_Blinding}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
delta := core.InnerProduct(core.EntryWiseProduct(powYInv, wR), wL)
|
||||
|
||||
rhs := core.MultiExp(core.VectorMulScalar(wV, x2).Join(core.ScalarVector{x, x3, x4, x5, x6, new(ristretto.Scalar).Multiply(x2, delta)}),
|
||||
cs.V.Join(core.GeneratorVector{proof.T1, proof.T3, proof.T4, proof.T5, proof.T6, c.g}))
|
||||
|
||||
log.Debugf("lhs: %v", lhs)
|
||||
log.Debugf("rhs: %v", rhs)
|
||||
//rhs := core.MultiExp(,cs.)
|
||||
|
||||
return lhs.Equal(rhs) == 1 && Verify(proof.IPP, n, c.u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package bulletproofs
|
||||
|
||||
type Generator struct {
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/binary"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
// InnerProductProof encapsulates an inner product proof
|
||||
type InnerProductProof struct {
|
||||
L core.PointVector
|
||||
R core.PointVector
|
||||
A *ristretto.Scalar
|
||||
B *ristretto.Scalar
|
||||
}
|
||||
|
||||
// ProveInnerProduct generates a proof for <a,b>, the inner product of a and b
|
||||
func ProveInnerProduct(a, b core.ScalarVector, u *ristretto.Element, P *ristretto.Element, G, H core.GeneratorVector, transcript *core.Transcript) InnerProductProof {
|
||||
n := len(a)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("ipp v1"))
|
||||
nb := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(nb, uint64(n))
|
||||
transcript.AddToTranscript("n", nb)
|
||||
|
||||
Lvec := core.PointVector{}
|
||||
Rvec := core.PointVector{}
|
||||
for n != 1 {
|
||||
np := n / 2
|
||||
aL, aR := a[:np], a[np:]
|
||||
bL, bR := b[:np], b[np:]
|
||||
GL, GR := G[:np], G[np:]
|
||||
HL, HR := H[:np], H[np:]
|
||||
|
||||
cL := core.InnerProduct(aL, bR)
|
||||
cR := core.InnerProduct(aR, bL)
|
||||
|
||||
L := core.MultiExp(append(aL.Join(bR), cL), append(GR.Join(HL), u))
|
||||
R := core.MultiExp(append(aR.Join(bL), cR), append(GL.Join(HR), u))
|
||||
|
||||
transcript.AddElementToTranscript("L", L)
|
||||
Lvec = append(Lvec, L)
|
||||
transcript.AddElementToTranscript("R", R)
|
||||
Rvec = append(Rvec, R)
|
||||
|
||||
u := transcript.CommitToTranscriptScalar("u")
|
||||
uinv := new(ristretto.Scalar)
|
||||
uinv.Invert(u)
|
||||
|
||||
for i := 0; i < len(aL); i++ {
|
||||
aL_ := new(ristretto.Scalar).Multiply(aL[i], u)
|
||||
aL[i] = new(ristretto.Scalar).Add(aL_, new(ristretto.Scalar).Multiply(aR[i], uinv))
|
||||
bL_ := new(ristretto.Scalar).Multiply(bL[i], uinv)
|
||||
bL[i] = new(ristretto.Scalar).Add(bL_, new(ristretto.Scalar).Multiply(bR[i], u))
|
||||
|
||||
GL[i] = core.MultiExp(core.ScalarVector{uinv, u}, core.GeneratorVector{GL[i], GR[i]})
|
||||
HL[i] = core.MultiExp(core.ScalarVector{u, uinv}, core.GeneratorVector{HL[i], HR[i]})
|
||||
}
|
||||
|
||||
x2 := new(ristretto.Scalar).Multiply(u, u)
|
||||
P_ := new(ristretto.Element).ScalarMult(x2, L)
|
||||
P_.Add(P_, P)
|
||||
P_.Add(P_, new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(x2), R))
|
||||
P = P_
|
||||
//ranscript.AddToTranscript("P'", []byte(P.String()))
|
||||
|
||||
a = aL
|
||||
b = bL
|
||||
G = GL
|
||||
H = HL
|
||||
n = np
|
||||
}
|
||||
|
||||
return InnerProductProof{Lvec, Rvec, a[0], b[0]}
|
||||
}
|
||||
|
||||
// Verify checks the given inner product proof
|
||||
func Verify(proof InnerProductProof, n int, u, P *ristretto.Element, G, H core.GeneratorVector, transcript *core.Transcript) bool {
|
||||
np := n / 2
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("ipp v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(n))
|
||||
transcript.AddToTranscript("n", b)
|
||||
for i := range proof.L {
|
||||
GL, GR := G[:np], G[np:]
|
||||
HL, HR := H[:np], H[np:]
|
||||
|
||||
transcript.AddElementToTranscript("L", proof.L[i])
|
||||
transcript.AddElementToTranscript("R", proof.R[i])
|
||||
x := transcript.CommitToTranscriptScalar("u")
|
||||
|
||||
log.Debugf("L: %x\n", proof.L[i].Encode([]byte{}))
|
||||
log.Debugf("R %x\n", proof.R[i].Encode([]byte{}))
|
||||
log.Debugf("u: %x\n", x.Encode([]byte{}))
|
||||
xinv := new(ristretto.Scalar)
|
||||
xinv.Invert(x)
|
||||
|
||||
for j := 0; j < np; j++ {
|
||||
GL[j] = core.MultiExp(core.ScalarVector{xinv, x}, core.GeneratorVector{GL[j], GR[j]})
|
||||
HL[j] = core.MultiExp(core.ScalarVector{x, xinv}, core.GeneratorVector{HL[j], HR[j]})
|
||||
}
|
||||
|
||||
x2 := new(ristretto.Scalar).Multiply(x, x)
|
||||
P_ := new(ristretto.Element).ScalarMult(x2, proof.L[i])
|
||||
P_.Add(P_, P)
|
||||
P_.Add(P_, new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(x2), proof.R[i]))
|
||||
P = P_
|
||||
G = GL
|
||||
H = HL
|
||||
np = np / 2
|
||||
}
|
||||
c := new(ristretto.Scalar)
|
||||
c.Multiply(proof.A, proof.B)
|
||||
P_ := core.MultiExp(core.ScalarVector{proof.A, proof.B, c}, core.GeneratorVector{G[0], H[0], u})
|
||||
log.Debugf("P:%v\nP':%v\n", P, P_)
|
||||
return P.Equal(P_) == 1
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assert(t *testing.T, expected *ristretto.Scalar, actual *ristretto.Scalar) {
|
||||
if expected.Equal(actual) == 1 {
|
||||
t.Logf("inner_product matched: %v", actual)
|
||||
} else {
|
||||
t.Fatalf("c should be %v instead: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_inner_product(t *testing.T) {
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
a := core.ScalarVector{one, zero, one, zero}
|
||||
b := core.ScalarVector{zero, one, zero, one}
|
||||
c := core.InnerProduct(a, b)
|
||||
assert(t, zero, c)
|
||||
|
||||
a = core.ScalarVector{one, one, one, zero}
|
||||
b = core.ScalarVector{one, one, zero, one}
|
||||
c = core.InnerProduct(a, b)
|
||||
|
||||
check := new(ristretto.Scalar)
|
||||
check.Add(one, one)
|
||||
|
||||
assert(t, check, c)
|
||||
|
||||
}
|
||||
|
||||
func TestProveInnerProduct(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
a := core.ScalarVector{one, zero, one, one}
|
||||
b := core.ScalarVector{zero, one, one, one}
|
||||
|
||||
proverTranscript := core.NewTranscript("test_innerproductproof")
|
||||
verifierTranscript := core.NewTranscript("test_innerproductproof")
|
||||
|
||||
G := proverTranscript.CommitToGenerators("G", 4)
|
||||
H := proverTranscript.CommitToGenerators("H", 4)
|
||||
u := proverTranscript.CommitToGenerator("u")
|
||||
|
||||
verifierTranscript.CommitToGenerators("G", 4)
|
||||
verifierTranscript.CommitToGenerators("H", 4)
|
||||
verifierTranscript.CommitToGenerator("u")
|
||||
|
||||
c := core.InnerProduct(a, b)
|
||||
|
||||
P_ := core.MultiExp(append(a.Join(b), c), append(core.GeneratorVector(G).Join(core.GeneratorVector(H)), u))
|
||||
|
||||
proof := ProveInnerProduct(a, b, u, new(ristretto.Element).Add(new(ristretto.Element).Zero(), P_), core.CopyVector(G), core.CopyVector(H), proverTranscript)
|
||||
|
||||
if Verify(proof, 4, u, P_, core.CopyVector(G), core.CopyVector(H), verifierTranscript) {
|
||||
t.Logf("Inner Product Proof Passed!")
|
||||
} else {
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
t.Fatalf("Inner Product Proof Failed!")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/binary"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// RangeProof encapsulates a proof that V = [0, max) where max is defined by the Setup function
|
||||
type RangeProof struct {
|
||||
A *ristretto.Element
|
||||
S *ristretto.Element
|
||||
T1 *ristretto.Element
|
||||
T2 *ristretto.Element
|
||||
TauX *ristretto.Scalar
|
||||
InnerProduct *ristretto.Scalar
|
||||
Mu *ristretto.Scalar
|
||||
IPP InnerProductProof
|
||||
V []*ristretto.Element
|
||||
}
|
||||
|
||||
// CommitmentsParams encapsulates the commitment parameters for a given range proof
|
||||
type CommitmentsParams struct {
|
||||
G core.GeneratorVector
|
||||
H core.GeneratorVector
|
||||
u *ristretto.Element
|
||||
g *ristretto.Element
|
||||
h *ristretto.Element
|
||||
max int
|
||||
}
|
||||
|
||||
// Setup generates multiple independent commitment parameters from the underlying transcript
|
||||
func Setup(max int, transcript *core.Transcript) (c CommitmentsParams) {
|
||||
c.G = transcript.CommitToGenerators("G", int(max))
|
||||
c.H = transcript.CommitToGenerators("H", int(max))
|
||||
|
||||
c.u = transcript.CommitToGenerator("u")
|
||||
c.g = transcript.CommitToGenerator("g")
|
||||
c.h = transcript.CommitToGenerator("h")
|
||||
|
||||
c.max = max
|
||||
return
|
||||
}
|
||||
|
||||
func Rand(seed string) *ristretto.Scalar {
|
||||
t := core.NewTranscript(seed)
|
||||
return t.CommitToTranscriptScalar("seed")
|
||||
}
|
||||
|
||||
// GenerateRangeProof creates a valid rangeproof that value is within [0,max) under the given transcript
|
||||
// It returns the rangeproof and a random scalar "gamma" that can be used to open V, the commitment to v vGgH
|
||||
func GenerateRangeProof(value int32, c CommitmentsParams, transcript *core.Transcript) (RangeProof, *ristretto.Scalar) {
|
||||
one := core.IdentityVector(1)[0]
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, c.max)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("rangeproof v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(c.max))
|
||||
transcript.AddToTranscript("n", b)
|
||||
b = make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, 1)
|
||||
transcript.AddToTranscript("m", b)
|
||||
|
||||
// Generate a prng to from this transcript and some external randomness
|
||||
// We use this to generate the rest of our private scalars
|
||||
// TODO: move to transcript
|
||||
private := make([]byte, 64)
|
||||
rand.Read(private)
|
||||
prngTranscript := core.NewTranscript("private-transcript")
|
||||
prngTranscript.AddToTranscript("randomness", private)
|
||||
prng := prngTranscript.CommitToPRNG(transcript.OutputTranscriptToAudit())
|
||||
|
||||
gamma := prng.Next()
|
||||
|
||||
aL := valueToVector(value, c.max)
|
||||
aR := core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(one))
|
||||
alpha := prng.Next()
|
||||
|
||||
vs := new(ristretto.Scalar)
|
||||
b = make([]byte, 32)
|
||||
copy(b, big.NewInt(int64(value)).Bytes())
|
||||
vs.Decode(b)
|
||||
|
||||
V := core.MultiExp(core.ScalarVector{gamma, vs}, core.GeneratorVector{c.h, c.g})
|
||||
|
||||
transcript.AddElementToTranscript("V", V)
|
||||
|
||||
A := core.MultiExp(append(aL.Join(aR), alpha), append(c.G.Join(c.H), c.h))
|
||||
log.Debugf("vs: %v", vs)
|
||||
|
||||
Sl := make(core.ScalarVector, c.max)
|
||||
Sr := make(core.ScalarVector, c.max)
|
||||
for i := 0; i < c.max; i++ {
|
||||
Sl[i] = prng.Next()
|
||||
Sr[i] = prng.Next()
|
||||
}
|
||||
p := prng.Next()
|
||||
|
||||
S := core.MultiExp(append(Sl.Join(Sr), p), append(c.G.Join(c.H), c.h))
|
||||
|
||||
transcript.AddElementToTranscript("A", A)
|
||||
transcript.AddElementToTranscript("S", S)
|
||||
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
|
||||
y_n := core.PowerVector(y, c.max)
|
||||
z2 := new(ristretto.Scalar).Multiply(z, z)
|
||||
|
||||
l0 := core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(z))
|
||||
//l1 == Sr
|
||||
r0 := core.EntrywiseSum(core.EntryWiseProduct(y_n, core.VectorAddScalar(aR, z)), core.VectorMulScalar(two_n, z2))
|
||||
r1 := core.EntryWiseProduct(Sr, y_n)
|
||||
|
||||
t0 := new(ristretto.Scalar).Add(new(ristretto.Scalar).Multiply(z2, vs), delta(y_n, z, c.max))
|
||||
t1 := new(ristretto.Scalar)
|
||||
t1.Add(core.InnerProduct(Sl, r0), core.InnerProduct(l0, r1))
|
||||
t2 := core.InnerProduct(Sl, r1)
|
||||
|
||||
tau1 := prng.Next()
|
||||
tau2 := prng.Next()
|
||||
|
||||
T1 := core.MultiExp(core.ScalarVector{t1, tau1}, core.GeneratorVector{c.g, c.h})
|
||||
T2 := core.MultiExp(core.ScalarVector{t2, tau2}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
transcript.AddElementToTranscript("T_1", T1)
|
||||
transcript.AddElementToTranscript("T_2", T2)
|
||||
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
|
||||
// T(X) = t0 + t1x + t2x
|
||||
TX := new(ristretto.Scalar)
|
||||
TX.Add(new(ristretto.Scalar).Zero(), t0)
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t1, x))
|
||||
TX.Add(TX, new(ristretto.Scalar).Multiply(t2, new(ristretto.Scalar).Multiply(x, x)))
|
||||
|
||||
l := core.EntrywiseSum(core.VectorAddScalar(aL, new(ristretto.Scalar).Negate(z)), core.VectorMulScalar(Sl, x))
|
||||
_r := core.EntrywiseSum(core.VectorAddScalar(aR, z), core.VectorMulScalar(Sr, x))
|
||||
r := core.EntrywiseSum(core.EntryWiseProduct(y_n, _r), core.VectorMulScalar(two_n, z2))
|
||||
|
||||
iplr := core.InnerProduct(l, r)
|
||||
|
||||
log.Debugf("T(X) = %v", TX)
|
||||
log.Debugf("ipp = %v", iplr)
|
||||
log.Debugf("equal: %v", TX.Equal(iplr) == 1)
|
||||
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(y_n[i]), c.H[i])
|
||||
}
|
||||
|
||||
P := core.MultiExp(l.Join(r), c.G.Join(H_))
|
||||
log.Debugf("P: %v", P)
|
||||
|
||||
mu := new(ristretto.Scalar)
|
||||
mu.Add(alpha, new(ristretto.Scalar).Multiply(p, x))
|
||||
|
||||
taux := new(ristretto.Scalar)
|
||||
taux.Multiply(tau2, new(ristretto.Scalar).Multiply(x, x))
|
||||
taux.Add(taux, new(ristretto.Scalar).Multiply(tau1, x))
|
||||
taux.Add(taux, new(ristretto.Scalar).Multiply(z2, gamma))
|
||||
|
||||
transcript.AddToTranscript("t_x", iplr.Encode([]byte{}))
|
||||
transcript.AddToTranscript("t_x_blinding", taux.Encode([]byte{}))
|
||||
transcript.AddToTranscript("e_blinding", mu.Encode([]byte{}))
|
||||
w := transcript.CommitToTranscriptScalar("w")
|
||||
|
||||
U := new(ristretto.Element).ScalarMult(w, c.g)
|
||||
|
||||
uP := new(ristretto.Element).Add(P, new(ristretto.Element).ScalarMult(iplr, U))
|
||||
log.Debugf("uP: %v", uP)
|
||||
ipp := ProveInnerProduct(l, r, U, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
|
||||
return RangeProof{A, S, T1, T2, taux, iplr, mu, ipp, []*ristretto.Element{V}}, gamma
|
||||
}
|
||||
|
||||
// VerifyRangeProof returns true if the given proof passes all the checks for a given set of commitment parameters
|
||||
// and the given transcript
|
||||
func VerifyRangeProof(proof RangeProof, c CommitmentsParams, transcript *core.Transcript) bool {
|
||||
one := core.IdentityVector(1)[0]
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, c.max)
|
||||
|
||||
transcript.AddToTranscript("dom-sep", []byte("rangeproof v1"))
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(c.max))
|
||||
transcript.AddToTranscript("n", b)
|
||||
b = make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, 1)
|
||||
transcript.AddToTranscript("m", b)
|
||||
|
||||
for _, v := range proof.V {
|
||||
transcript.AddElementToTranscript("V", v)
|
||||
}
|
||||
|
||||
transcript.AddElementToTranscript("A", proof.A)
|
||||
transcript.AddElementToTranscript("S", proof.S)
|
||||
y := transcript.CommitToTranscriptScalar("y")
|
||||
z := transcript.CommitToTranscriptScalar("z")
|
||||
transcript.AddElementToTranscript("T_1", proof.T1)
|
||||
transcript.AddElementToTranscript("T_2", proof.T2)
|
||||
x := transcript.CommitToTranscriptScalar("x")
|
||||
|
||||
transcript.AddToTranscript("t_x", proof.InnerProduct.Encode([]byte{}))
|
||||
transcript.AddToTranscript("t_x_blinding", proof.TauX.Encode([]byte{}))
|
||||
transcript.AddToTranscript("e_blinding", proof.Mu.Encode([]byte{}))
|
||||
log.Debugf("mu: %x", proof.Mu.Encode([]byte{}))
|
||||
|
||||
log.Debugf("x: %x", x.Encode([]byte{}))
|
||||
y_n := core.PowerVector(y, c.max)
|
||||
// generate h'
|
||||
H_ := make(core.GeneratorVector, c.max)
|
||||
H_[0] = c.H[0]
|
||||
for i := 1; i < c.max; i++ {
|
||||
H_[i] = new(ristretto.Element).ScalarMult(new(ristretto.Scalar).Invert(y_n[i]), c.H[i])
|
||||
}
|
||||
|
||||
// check t = t(x) = t0 + t1.x + t1.x^2
|
||||
lhs := core.MultiExp(core.ScalarVector{proof.InnerProduct, proof.TauX}, core.GeneratorVector{c.g, c.h})
|
||||
|
||||
z2 := new(ristretto.Scalar)
|
||||
z2.Multiply(z, z)
|
||||
|
||||
x2 := new(ristretto.Scalar)
|
||||
x2.Multiply(x, x)
|
||||
|
||||
rhs := core.MultiExp(core.ScalarVector{z2, delta(y_n, z, c.max), x, x2}, core.GeneratorVector(proof.V).Join(core.GeneratorVector{c.g, proof.T1, proof.T2}))
|
||||
log.Debugf("lhs: %v", lhs)
|
||||
log.Debugf("rhs: %v", rhs)
|
||||
log.Debugf("equal: %v", lhs.Equal(rhs))
|
||||
|
||||
if lhs.Equal(rhs) == 1 {
|
||||
|
||||
// compute P
|
||||
|
||||
negz := new(ristretto.Scalar).Negate(z)
|
||||
negzG := new(ristretto.Element).Zero()
|
||||
for _, gen := range c.G {
|
||||
negzG = negzG.Add(negzG, new(ristretto.Element).ScalarMult(negz, gen))
|
||||
}
|
||||
|
||||
mul := core.EntrywiseSum(core.VectorMulScalar(y_n, z), core.VectorMulScalar(two_n, new(ristretto.Scalar).Multiply(z, z)))
|
||||
|
||||
Pr := new(ristretto.Element).Add(new(ristretto.Element).Zero(), proof.A)
|
||||
Pr = Pr.Add(Pr, new(ristretto.Element).ScalarMult(x, proof.S))
|
||||
Pr = Pr.Add(Pr, negzG)
|
||||
Pr = Pr.Add(Pr, core.MultiExp(mul, H_))
|
||||
|
||||
Pl := new(ristretto.Element).Subtract(Pr, new(ristretto.Element).ScalarMult(proof.Mu, c.h))
|
||||
// check inner product
|
||||
|
||||
w := transcript.CommitToTranscriptScalar("w")
|
||||
U := new(ristretto.Element).ScalarMult(w, c.g)
|
||||
|
||||
uP := new(ristretto.Element).Add(Pl, new(ristretto.Element).ScalarMult(proof.InnerProduct, U))
|
||||
|
||||
log.Debugf("P: %v", Pl)
|
||||
log.Debugf("uP: %v", uP)
|
||||
|
||||
return Verify(proof.IPP, c.max, U, new(ristretto.Element).Add(new(ristretto.Element).Zero(), uP), core.CopyVector(c.G), core.CopyVector(H_), transcript)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func delta(y_n core.ScalarVector, z *ristretto.Scalar, max int) *ristretto.Scalar {
|
||||
one := core.IdentityVector(1)[0]
|
||||
// (z-z^2)
|
||||
z2 := new(ristretto.Scalar).Multiply(z, z)
|
||||
result := new(ristretto.Scalar)
|
||||
result.Subtract(z, z2)
|
||||
// (z-z^2) * <1^n,y^n>
|
||||
result.Multiply(result, core.InnerProduct(core.IdentityVector(max), y_n))
|
||||
two := new(ristretto.Scalar)
|
||||
two.Add(one, one)
|
||||
two_n := core.PowerVector(two, max)
|
||||
// (z-z^2) * <1^n,y^n> - z^3 *<1n,2n>
|
||||
z3 := new(ristretto.Scalar).Multiply(z2, z)
|
||||
result.Subtract(result, new(ristretto.Scalar).Multiply(z3, core.InnerProduct(core.IdentityVector(max), two_n)))
|
||||
return result
|
||||
}
|
||||
|
||||
func valueToVector(value int32, max int) core.ScalarVector {
|
||||
one := core.IdentityVector(1)[0]
|
||||
zero := new(ristretto.Scalar)
|
||||
zero.Zero()
|
||||
result := core.ScalarVector{}
|
||||
for len(result) != max {
|
||||
v := value & 0x0001
|
||||
if v == 1 {
|
||||
result = append(result, one)
|
||||
} else {
|
||||
result = append(result, zero)
|
||||
}
|
||||
value = value >> 1
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package bulletproofs
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives/core"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||
"github.com/gtank/ristretto255"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProove(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
proverTranscript := core.NewTranscript("rangeproof")
|
||||
verifierTranscript := core.NewTranscript("rangeproof")
|
||||
|
||||
rangeproof, _ := GenerateRangeProof(10, Setup(32, proverTranscript), proverTranscript)
|
||||
if VerifyRangeProof(rangeproof, Setup(32, verifierTranscript), verifierTranscript) == true {
|
||||
t.Logf("Range Proof Passed!")
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
jsonProof, _ := json.Marshal(rangeproof)
|
||||
t.Logf("RangeProof: %s", jsonProof)
|
||||
} else {
|
||||
t.Logf("%v\n\n%v\n", proverTranscript.OutputTranscriptToAudit(), verifierTranscript.OutputTranscriptToAudit())
|
||||
t.Fatalf("Failed to Verify Range Proof")
|
||||
}
|
||||
}
|
||||
|
||||
func byteToPoint(in []byte) *ristretto255.Element {
|
||||
element := ristretto255.NewElement()
|
||||
element.Decode(in)
|
||||
return element
|
||||
}
|
||||
|
||||
func byteToScalar(in []byte) *ristretto255.Scalar {
|
||||
scalar := ristretto255.NewScalar()
|
||||
scalar.Decode(in[0:32])
|
||||
return scalar
|
||||
}
|
||||
|
||||
func decodeInnerProduct(in []byte) *InnerProductProof {
|
||||
num_elements := len(in) / 32
|
||||
lg_n := (num_elements - 2) / 2
|
||||
lvec := make(core.PointVector, lg_n)
|
||||
rvec := make(core.PointVector, lg_n)
|
||||
for i := 0; i < lg_n; i++ {
|
||||
pos := 2 * i * 32
|
||||
lvec[i] = byteToPoint(in[pos : pos+32])
|
||||
rvec[i] = byteToPoint(in[pos+32 : pos+64])
|
||||
}
|
||||
pos := 2 * lg_n * 32
|
||||
a := byteToScalar(in[pos : pos+32])
|
||||
b := byteToScalar(in[pos+32 : pos+64])
|
||||
return &InnerProductProof{lvec, rvec, a, b}
|
||||
}
|
||||
|
||||
func TestDalek(t *testing.T) {
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
t.Logf("Testing dalek-cryptography bulletproofs test vector...")
|
||||
rp, _ := hex.DecodeString("46b6ea8b6a9710c41c2622d4b353dbcf5f89afe8ed66c469f192bec19dc71d23c0442827f97fc9085a89caa87d294b0a21e7b8957732ec4951f6bf7d3aa2c66e7af3b7b956c7dcb3bed1223575a217a30642b603b6bf1d4138ed95e3458c524510b42c8d82958f40b447a84242b1ba1eeea54013f80bad643048eeb0b17c292a057cb6ae1c42338837c05eaa6336a17d60fa141204e015a1df15b28c1318c709d7eb35569cde89c0bf37eace54880a151498b38da54c6d739564f46f01b73601e518355ea06c9ef58a45fcb3baadbd1ac54e0838c471a6b91845f123d569fa0c46ef94471b7b826230e8576146beec08ac3e6683998815c576581f4c0e493433480f95f6495210636eaa2e32b577e1c363e35e522db85b18a56d57eb626f9e2b50578e0d7ee7b74b328e158b366bb9d117db725820a2fec3b1508212d75823345a801c0b602bfa05919d7e3bb8e71944587072badc363f334b08ba90d13e077ad24b82bacd51fc668d2b880daabd3b87e6bdc9584af66523026a30aadfc359283891bb65cca502f47421ffeee1fb5a5237bfa965b66a8b8ca5d6954f4f8222244c6a5340dc81e8d781d092cae2a763f185dd0b89965b1dd2506807b5d3e5a305fd9a68e60b91389dcffae6f85538713aa7ed272b8174e2f0b9730ebb6c464d06")
|
||||
|
||||
t.Logf("Deserializing dalek-cryptography bulletproofs test vector...%x", rp)
|
||||
|
||||
A := byteToPoint(rp[0:32])
|
||||
S := byteToPoint(rp[32:64])
|
||||
T1 := byteToPoint(rp[64:96])
|
||||
T2 := byteToPoint(rp[96:128])
|
||||
|
||||
TX := byteToScalar(rp[128:160])
|
||||
TX_blinding := byteToScalar(rp[160:192])
|
||||
micro := byteToScalar(rp[192 : 192+32])
|
||||
|
||||
ipp := decodeInnerProduct(rp[192+32:])
|
||||
|
||||
vbytes := []string{
|
||||
"90b0c2fe57934dff9f5396e135e7d72b82b3c5393e1843178918eb2cf28a5f3c",
|
||||
"74256a3e2a7fe948210c4095195ae4db3e3498c6c5fddc2afb226c0f1e97e468",
|
||||
"7e348def6d03dc7bcbe7e03736ca2898e2efa9f6ff8ae4ed1cb5252ec1744075",
|
||||
"861859f5d4c14f5d6d7ad88dcf43c9a98064a7d8702ffc9bad9eba2ed766702a",
|
||||
"4c09b1260c833fefe25b1c3d3becc80979beca5e864d57fcb410bb15c7ba5c14",
|
||||
"08cf26bfdf2e6b731536f5e48b4c0ac7b5fc846d36aaa3fe0d28f07c207f0814",
|
||||
"a6e2d1c2770333c9a8a5ac10d9eb28e8609d5954428261335b2fd6ff0e0e8d69",
|
||||
"30beef3b58fd2c18dde771d5c77e32f8dc01361e284aef517bce54a5c74c4665",
|
||||
}
|
||||
|
||||
V := make([]*ristretto255.Element, len(vbytes))
|
||||
for i, v := range vbytes {
|
||||
V[i] = ristretto255.NewElement()
|
||||
vdec, _ := hex.DecodeString(v)
|
||||
V[i].Decode(vdec)
|
||||
}
|
||||
|
||||
rangeProof := RangeProof{A, S, T1, T2, TX_blinding, TX, micro, *ipp, V[0:1]}
|
||||
|
||||
json, _ := json.Marshal(rangeProof)
|
||||
t.Logf("RangeProof: %s", json)
|
||||
|
||||
t.Logf("Deserialized Range Proof: %s", json)
|
||||
|
||||
t.Logf("Generating dalek-cryptography pedersen generators....")
|
||||
params := CommitmentsParams{}
|
||||
params.g = ristretto255.NewElement().Base()
|
||||
params.h = ristretto255.NewElement()
|
||||
h := sha3.Sum512(params.g.Encode([]byte{}))
|
||||
|
||||
params.h = ristretto255.NewElement().FromUniformBytes(h[:])
|
||||
|
||||
params.max = 8
|
||||
params.G = make(core.GeneratorVector, params.max)
|
||||
params.H = make(core.GeneratorVector, params.max)
|
||||
|
||||
labelG := []byte{'G', 0, 0, 0, 0}
|
||||
shake := sha3.NewShake256()
|
||||
shake.Write([]byte("GeneratorsChain"))
|
||||
shake.Write(labelG[:])
|
||||
|
||||
labelH := []byte{'H', 0, 0, 0, 0}
|
||||
shakeH := sha3.NewShake256()
|
||||
shakeH.Write([]byte("GeneratorsChain"))
|
||||
shakeH.Write(labelH[:])
|
||||
|
||||
t.Logf("Generating dalek-cryptography BP generators....")
|
||||
for i := 0; i < 8; i++ {
|
||||
b := make([]byte, 64)
|
||||
shake.Read(b)
|
||||
params.G[i] = ristretto255.NewElement()
|
||||
params.G[i].FromUniformBytes(b)
|
||||
|
||||
//t.Logf("G: %x", params.G[i].Encode([]byte{}))
|
||||
|
||||
bH := make([]byte, 64)
|
||||
shakeH.Read(bH)
|
||||
params.H[i] = ristretto255.NewElement()
|
||||
params.H[i].FromUniformBytes(bH)
|
||||
|
||||
// t.Logf("H: %x", params.H[i].Encode([]byte{}))
|
||||
|
||||
}
|
||||
|
||||
//t.Logf("parmas: %v", params)
|
||||
|
||||
verifierTranscript := core.NewTranscript("Deserialize-And-Verify Test")
|
||||
t.Logf("Verification Result: %v", VerifyRangeProof(rangeProof, params, verifierTranscript))
|
||||
|
||||
t.Logf("Transcript: %s\n", verifierTranscript.OutputTranscriptToAudit())
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ristretto "github.com/gtank/ristretto255"
|
||||
)
|
||||
|
||||
// ScalarVector explicit type checking
|
||||
type ScalarVector []*ristretto.Scalar
|
||||
|
||||
// ElementVector explicit type checking
|
||||
type PointVector []*ristretto.Element
|
||||
|
||||
// GeneratorVector explicit type checking
|
||||
type GeneratorVector []*ristretto.Element
|
||||
|
||||
// CopyVector safely copies a vector
|
||||
func CopyVector(G GeneratorVector) GeneratorVector {
|
||||
H := make(GeneratorVector, len(G))
|
||||
for i, g := range G {
|
||||
H[i] = new(ristretto.Element).Add(new(ristretto.Element).Zero(), g)
|
||||
}
|
||||
return H
|
||||
}
|
||||
|
||||
// InnerProduct takes the inner product of a and b i.e. <a,b>
|
||||
func InnerProduct(a, b ScalarVector) *ristretto.Scalar {
|
||||
if len(a) != len(b) {
|
||||
panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(b)))
|
||||
}
|
||||
|
||||
result := new(ristretto.Scalar).Zero()
|
||||
for i, ai := range a {
|
||||
result.Add(result, new(ristretto.Scalar).Multiply(ai, b[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MultiExp takes in a vector of scalars = {a,b,c...} and a vector of generator = {A,B,C...} and outputs
|
||||
// {aA,bB,cC}
|
||||
func MultiExp(a ScalarVector, G GeneratorVector) *ristretto.Element {
|
||||
if len(a) > len(G) {
|
||||
panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(G)))
|
||||
}
|
||||
result := new(ristretto.Element).Zero()
|
||||
for i, ai := range a {
|
||||
aG := new(ristretto.Element).ScalarMult(ai, G[i])
|
||||
result = new(ristretto.Element).Add(result, aG)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Join is defined for a vector of Scalars
|
||||
func (a ScalarVector) SafeAppend(b *ristretto.Scalar) ScalarVector {
|
||||
list := make(ScalarVector, len(a)+1)
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
list[len(a)] = b
|
||||
return list
|
||||
}
|
||||
|
||||
// Join is defined for a vector of Scalars
|
||||
func (a ScalarVector) Join(b ScalarVector) ScalarVector {
|
||||
list := make(ScalarVector, len(a)+len(b))
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
for i := len(a); i < len(a)+len(b); i++ {
|
||||
list[i] = b[i-len(a)]
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Join as defined for a vector of Generators
|
||||
func (a GeneratorVector) SafeAppend(b *ristretto.Element) GeneratorVector {
|
||||
list := make(GeneratorVector, len(a)+1)
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
list[len(a)] = b
|
||||
return list
|
||||
}
|
||||
|
||||
// Join as defined for a vector of Generators
|
||||
func (a GeneratorVector) Join(b GeneratorVector) GeneratorVector {
|
||||
list := make(GeneratorVector, len(a)+len(b))
|
||||
for i := 0; i < len(a); i++ {
|
||||
list[i] = a[i]
|
||||
}
|
||||
for i := len(a); i < len(a)+len(b); i++ {
|
||||
list[i] = b[i-len(a)]
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// VectorAddScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {a+s,b+s,c+s....}
|
||||
func VectorAddScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Add(vector[i], scalar)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// VectorNegate takes in a vector v = {a,b,c..} and a scalar s and outputs {-a,-b,-c}
|
||||
func VectorNegate(vector ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar).Negate(vector[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// VectorMulScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {as,bs,cs....}
|
||||
func VectorMulScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(vector[i], scalar)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntrywiseSum takes the entry wise sum of two vectors
|
||||
func EntrywiseSum(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Add(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntrywiseSubtract takes the entry wise sum of two vectors
|
||||
func EntrywiseSub(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Subtract(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EntryWiseProduct takes the entry wise product of two vectors
|
||||
func EntryWiseProduct(vector ScalarVector, vector2 ScalarVector) ScalarVector {
|
||||
result := make(ScalarVector, len(vector))
|
||||
for i, v := range vector {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(v, vector2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// One returns a ristretto scalar == 1
|
||||
func One() *ristretto.Scalar {
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
return one
|
||||
}
|
||||
|
||||
// IdentityVector is a convenience function to generate a vector v = {1,1,1...1}
|
||||
func IdentityVector(n int) ScalarVector {
|
||||
result := make(ScalarVector, n)
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = one
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PowerVector creates a vector v = {1,x,x^2,x^3..x^n}
|
||||
func PowerVector(x *ristretto.Scalar, n int) ScalarVector {
|
||||
result := make(ScalarVector, n)
|
||||
one := new(ristretto.Scalar)
|
||||
one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
result[0] = one
|
||||
result[1] = x
|
||||
for i := 2; i < n; i++ {
|
||||
result[i] = new(ristretto.Scalar)
|
||||
result[i].Multiply(result[i-1], x)
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//gut In Ernest F. Brickell, editor,CRYPTO’92,volume 740 ofLNCS, pages 89–105. Springer, Heidelberg,August 1993
|
||||
type DLEQProof struct {
|
||||
C *ristretto.Scalar
|
||||
S *ristretto.Scalar
|
||||
}
|
||||
|
||||
// DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript
|
||||
// Given 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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
16
service.go
16
service.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue