From 3b4fee2e72cbc166c22e6938412efaed18931837 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 19 Apr 2022 15:16:07 -0700 Subject: [PATCH 1/2] Reduce new allocations, propagate errors in PRNG.Next() --- applications/proof_of_work_app.go | 16 +++++++++++-- applications/token_app.go | 5 +++- primitives/core/transcript.go | 15 +++++++----- primitives/privacypass/token.go | 8 ++++++- primitives/privacypass/token_test.go | 12 ++++++++-- primitives/privacypass/tokenserver.go | 33 +++++++++++++++++++++------ service.go | 16 ++++++------- 7 files changed, 78 insertions(+), 27 deletions(-) diff --git a/applications/proof_of_work_app.go b/applications/proof_of_work_app.go index 87c8743..6156938 100644 --- a/applications/proof_of_work_app.go +++ b/applications/proof_of_work_app.go @@ -5,6 +5,7 @@ import ( "git.openprivacy.ca/cwtch.im/tapir" "git.openprivacy.ca/cwtch.im/tapir/primitives/core" "git.openprivacy.ca/openprivacy/log" + ristretto "github.com/gtank/ristretto255" ) // ProofOfWorkApplication forces the incoming connection to do proof of work before granting a capability @@ -66,9 +67,18 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core var sum [32]byte solution := []byte{} solve := make([]byte, len(challenge)+32) + // reuse our allocation + buf := make([]byte, 64) + next := new(ristretto.Scalar) + encodedSolution := make([]byte, 0, 32) for !solved { - - solution = prng.Next().Encode(nil) + err := prng.Next(buf, next) + if err != nil { + // this will cause the challenge to fail... + log.Errorf("error completing challenge: %v", err) + return nil + } + solution = next.Encode(encodedSolution) copy(solve[0:], solution[:]) copy(solve[len(solution):], challenge[:]) @@ -80,6 +90,8 @@ func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core solved = false } } + // reuse this allocated memory next time... + encodedSolution = encodedSolution[:0] } log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum) return solution[:] diff --git a/applications/token_app.go b/applications/token_app.go index 263bc6f..11da174 100644 --- a/applications/token_app.go +++ b/applications/token_app.go @@ -55,7 +55,10 @@ func (tokenapp *TokenApplication) Init(connection tapir.Connection) { var blinded []privacypass.BlindedToken err := json.Unmarshal(connection.Expect(), &blinded) if err == nil { - batchProof := tokenapp.TokenService.SignBlindedTokenBatch(blinded, tokenapp.Transcript()) + batchProof, err := tokenapp.TokenService.SignBlindedTokenBatch(blinded, tokenapp.Transcript()) + if err != nil { + return + } log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit()) data, _ := json.Marshal(batchProof) connection.Send(data) diff --git a/primitives/core/transcript.go b/primitives/core/transcript.go index b05c59e..3d5e8c9 100644 --- a/primitives/core/transcript.go +++ b/primitives/core/transcript.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "git.openprivacy.ca/openprivacy/log" "github.com/gtank/merlin" ristretto "github.com/gtank/ristretto255" "golang.org/x/crypto/sha3" @@ -67,12 +68,14 @@ type PRNG struct { } // 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 +func (prng *PRNG) Next(buf []byte, next *ristretto.Scalar) error { + n, err := io.ReadFull(prng.prng, buf) + if n != 64 || err != nil { + log.Errorf("could not read prng: %v %v", n, err) + return fmt.Errorf("error fetching complete output from prng: %v", err) + } + next.FromUniformBytes(buf) + return nil } // CommitToPRNG commits the label to the transcript and derives a PRNG from the transcript. diff --git a/primitives/privacypass/token.go b/primitives/privacypass/token.go index e7ce557..28160c8 100644 --- a/primitives/privacypass/token.go +++ b/primitives/privacypass/token.go @@ -95,8 +95,14 @@ func verifyBatchProof(dleq DLEQProof, Y *ristretto.Element, blindedTokens []Blin prng := transcript.CommitToPRNG("w") M := new(ristretto.Element).Zero() Z := new(ristretto.Element).Zero() + buf := make([]byte, 64) + c := new(ristretto.Scalar) for i := range blindedTokens { - c := prng.Next() + err := prng.Next(buf, c) + if err != nil { + log.Errorf("error verifying batch proof: %v", err) + return false + } M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M) Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z) } diff --git a/primitives/privacypass/token_test.go b/primitives/privacypass/token_test.go index 24c7848..4b4b042 100644 --- a/primitives/privacypass/token_test.go +++ b/primitives/privacypass/token_test.go @@ -56,7 +56,12 @@ func TestToken_ConstrainToToken(t *testing.T) { // Constraint forces T = kW to be part of the batch proof // And because the batch proof must prove that *all* inputs share the same key and also checks the servers public key // We get a consistency check for almost free. - signedTokens := server.SignBlindedTokenBatchWithConstraint([]BlindedToken{blindedToken2}, token.t, core.NewTranscript("")) + signedTokens, err := server.SignBlindedTokenBatchWithConstraint([]BlindedToken{blindedToken2}, token.t, core.NewTranscript("")) + + if err != nil { + t.Fatalf("error signing tokens with constraints") + } + transcript := core.NewTranscript("") // NOTE: For this to work token.t and token.W need to be obtain by the client from known source e.g. a public message board. @@ -78,7 +83,10 @@ func TestGenerateBlindedTokenBatch(t *testing.T) { serverTranscript := core.NewTranscript("privacyPass") tokens, blindedTokens := GenerateBlindedTokenBatch(10) - batchProof := server.SignBlindedTokenBatch(blindedTokens, serverTranscript) + batchProof, err := server.SignBlindedTokenBatch(blindedTokens, serverTranscript) + if err != nil { + t.Fatalf("error constructing signed/blinded token batch: %v", err) + } verified := UnblindSignedTokenBatch(tokens, blindedTokens, batchProof.SignedTokens, server.Y, batchProof.Proof, clientTranscript) diff --git a/primitives/privacypass/tokenserver.go b/primitives/privacypass/tokenserver.go index fb11038..afa5b5e 100644 --- a/primitives/privacypass/tokenserver.go +++ b/primitives/privacypass/tokenserver.go @@ -7,6 +7,7 @@ import ( "fmt" "git.openprivacy.ca/cwtch.im/tapir/persistence" "git.openprivacy.ca/cwtch.im/tapir/primitives/core" + "git.openprivacy.ca/openprivacy/log" ristretto "github.com/gtank/ristretto255" "golang.org/x/crypto/sha3" "sync" @@ -69,17 +70,23 @@ func (ts *TokenServer) SignBlindedToken(bt BlindedToken) SignedToken { } // SignBlindedTokenBatch signs a batch of blinded tokens under a given transcript -func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, transcript *core.Transcript) SignedBatchWithProof { +func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, transcript *core.Transcript) (*SignedBatchWithProof, error) { var signedTokens []SignedToken for _, bt := range blindedTokens { signedTokens = append(signedTokens, ts.SignBlindedToken(bt)) } - return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)} + + proof, err := ts.constructBatchProof(blindedTokens, signedTokens, transcript) + if err != nil { + return nil, err + } + signedProof := SignedBatchWithProof{signedTokens, *proof} + return &signedProof, nil } // SignBlindedTokenBatchWithConstraint signs a batch of blinded tokens under a given transcript given a constraint that the tokens must be signed // by the same public key as an existing token -func (ts *TokenServer) SignBlindedTokenBatchWithConstraint(blindedTokens []BlindedToken, constraintToken []byte, transcript *core.Transcript) SignedBatchWithProof { +func (ts *TokenServer) SignBlindedTokenBatchWithConstraint(blindedTokens []BlindedToken, constraintToken []byte, transcript *core.Transcript) (*SignedBatchWithProof, error) { var signedTokens []SignedToken for _, bt := range blindedTokens { signedTokens = append(signedTokens, ts.SignBlindedToken(bt)) @@ -89,11 +96,16 @@ func (ts *TokenServer) SignBlindedTokenBatchWithConstraint(blindedTokens []Blind // W == kT W := new(ristretto.Element).ScalarMult(ts.k, T) blindedTokens = append(blindedTokens, BlindedToken{P: T}) - return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, append(signedTokens, SignedToken{Q: W}), transcript)} + proof, err := ts.constructBatchProof(blindedTokens, append(signedTokens, SignedToken{Q: W}), transcript) + if err != nil { + return nil, err + } + signedProof := SignedBatchWithProof{signedTokens, *proof} + return &signedProof, nil } // constructBatchProof construct a batch proof that all the signed tokens have been signed correctly -func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof { +func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) (*DLEQProof, error) { transcript.NewProtocol(BatchProofProtocol) transcript.AddToTranscript(BatchProofX, new(ristretto.Element).Base().Encode(nil)) transcript.AddToTranscript(BatchProofY, ts.Y.Encode(nil)) @@ -104,12 +116,19 @@ func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedT M := new(ristretto.Element).Zero() Z := new(ristretto.Element).Zero() + buf := make([]byte, 64) + c := new(ristretto.Scalar) for i := range blindedTokens { - c := prng.Next() + err := prng.Next(buf, c) + if err != nil { + log.Errorf("error constructing batch proof: %v", err) + return nil, err + } M = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, blindedTokens[i].P), M) Z = new(ristretto.Element).Add(new(ristretto.Element).ScalarMult(c, signedTokens[i].Q), Z) } - return DiscreteLogEquivalenceProof(ts.k, new(ristretto.Element).Base(), ts.Y, M, Z, transcript) + proof := DiscreteLogEquivalenceProof(ts.k, new(ristretto.Element).Base(), ts.Y, M, Z, transcript) + return &proof, nil } // SpendToken returns true a SpentToken is valid and has never been spent before, false otherwise. diff --git a/service.go b/service.go index 9ec9512..e683395 100644 --- a/service.go +++ b/service.go @@ -62,6 +62,7 @@ type connection struct { MaxLength int lock sync.Mutex service Service + expectBuffer []byte } // NewConnection creates a new Connection @@ -74,7 +75,7 @@ func NewConnection(service Service, id *primitives.Identity, hostname string, ou connection.outbound = outbound connection.MaxLength = 8192 connection.service = service - + connection.expectBuffer = make([]byte, 8192) go connection.app.Init(connection) return connection } @@ -153,10 +154,9 @@ func (c *connection) closeInner() { // Expect blocks and reads a single Tapir packet , from the connection. func (c *connection) Expect() []byte { - buffer := make([]byte, c.MaxLength) // Multiple goroutines may invoke methods on a Conn simultaneously. // As such we don't need to mutex around closed. - n, err := io.ReadFull(c.conn, buffer) + n, err := io.ReadFull(c.conn, c.expectBuffer) if n != c.MaxLength || err != nil { log.Debugf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.identity.Hostname(), n, err) @@ -167,22 +167,22 @@ func (c *connection) Expect() []byte { defer c.lock.Unlock() if c.encrypted { var decryptNonce [24]byte - copy(decryptNonce[:], buffer[:24]) - decrypted, ok := secretbox.Open(nil, buffer[24:], &decryptNonce, &c.key) + copy(decryptNonce[:], c.expectBuffer[:24]) + decrypted, ok := secretbox.Open(nil, c.expectBuffer[24:], &decryptNonce, &c.key) if ok { - copy(buffer, decrypted) + copy(c.expectBuffer, decrypted) } else { log.Errorf("[%v -> %v] Error Decrypting Message On Wire", c.hostname, c.identity.Hostname()) c.closeInner() return []byte{} } } - length, _ := binary.Uvarint(buffer[0:2]) + length, _ := binary.Uvarint(c.expectBuffer[0:2]) if length+2 >= uint64(c.MaxLength) { return []byte{} } //cplog.Debugf("[%v -> %v] Wire Receive: (%d) %x", c.hostname, c.ID.Hostname(), len, buffer) - return buffer[2 : length+2] + return c.expectBuffer[2 : length+2] } // SetEncryptionKey turns on application-level encryption on the connection using the given key. From 89d12f812f77dde4372f90575debf0f61810b08c Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 20 Apr 2022 11:15:11 -0700 Subject: [PATCH 2/2] Upgrade Connectivity --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 43d0dba..48aa1cb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.openprivacy.ca/cwtch.im/tapir go 1.16 require ( - git.openprivacy.ca/openprivacy/connectivity v1.8.2 + git.openprivacy.ca/openprivacy/connectivity v1.8.3 git.openprivacy.ca/openprivacy/log v1.0.3 github.com/davecgh/go-spew v1.1.1 // indirect github.com/gtank/merlin v0.1.1 diff --git a/go.sum b/go.sum index f5f6211..29ee296 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ git.openprivacy.ca/openprivacy/connectivity v1.8.1 h1:OjWy+JTAvlrstY8PnGPBp7Ho04 git.openprivacy.ca/openprivacy/connectivity v1.8.1/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw= git.openprivacy.ca/openprivacy/connectivity v1.8.2 h1:uCFnrJXsTh3ne4GcgvamoxomQ6fMishD3C2nQGpgdMY= git.openprivacy.ca/openprivacy/connectivity v1.8.2/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw= +git.openprivacy.ca/openprivacy/connectivity v1.8.3 h1:bWM8aQHqHIpobYQcLQ9OsNPoIl+H+4JFWbYGdG0nHlg= +git.openprivacy.ca/openprivacy/connectivity v1.8.3/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0= git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=