From 72f293de6fa6058d72e2780dbcf5ea737ac67120 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:45:52 +0200 Subject: [PATCH] add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- internal/client.go | 14 -- internal/nizk.go | 2 +- oprf.go | 15 +++ tests/encoding_test.go | 16 +-- tests/errors_test.go | 295 +++++++++++++++++++++++++++++++++++++++++ tests/voprf_test.go | 20 ++- voprf/client.go | 41 ++---- voprf/server.go | 32 ++--- 8 files changed, 365 insertions(+), 70 deletions(-) create mode 100644 tests/errors_test.go diff --git a/internal/client.go b/internal/client.go index 88b851d..fc9b9bc 100644 --- a/internal/client.go +++ b/internal/client.go @@ -9,17 +9,11 @@ package internal import ( - "errors" "slices" group "github.com/bytemare/crypto" ) -var ( - errBatchNoElements = errors.New("no evaluated elements provided to Finalize()") - errBatchDifferentSize = errors.New("number of evaluations is different thant number of previously blinded inputs") -) - // A Client holds the core functionalities for all OPRF, TOPRF, VOPRF, and POPRF. type Client struct { // Core abstracts configuration dependent operations. @@ -106,14 +100,6 @@ func (c *Client) Finalize(index int, evaluated *group.Element, info ...byte) []b // FinalizeBatch unblinds the evaluated elements and returns the corresponding protocol outputs. The optional info // argument must only be provided when using the POPRF mode. func (c *Client) FinalizeBatch(evaluated []*group.Element, info ...byte) ([][]byte, error) { - if len(evaluated) == 0 { - return nil, errBatchNoElements - } - - if len(evaluated) != c.Size() { - return nil, errBatchDifferentSize - } - out := make([][]byte, len(evaluated)) for i, e := range evaluated { diff --git a/internal/nizk.go b/internal/nizk.go index 54567aa..82d514c 100644 --- a/internal/nizk.go +++ b/internal/nizk.go @@ -19,7 +19,7 @@ const ( dstChallenge = "Challenge" ) -var errProofFailed = errors.New("proof fails") +var errProofFailed = errors.New("invalid proof") // Verifiable enables VOPRF and POPRF functions over OPRF operations. type Verifiable struct { diff --git a/oprf.go b/oprf.go index 0a959e6..f9e1216 100644 --- a/oprf.go +++ b/oprf.go @@ -12,6 +12,8 @@ package voprf import ( + "errors" + group "github.com/bytemare/crypto" "github.com/bytemare/voprf/internal" @@ -40,6 +42,11 @@ const ( Secp256k1 = Ciphersuite(group.Secp256k1) ) +var ( + errBatchNoElements = errors.New("no evaluated elements provided to Finalize()") + errBatchDifferentSize = errors.New("number of evaluations is different thant number of previously blinded inputs") +) + // FromGroup returns a Ciphersuite given a Group. func FromGroup(g group.Group) Ciphersuite { return Ciphersuite(g) @@ -112,6 +119,14 @@ func (c *Client) Finalize(evaluated *group.Element) []byte { // FinalizeBatch unblinds the evaluated elements and returns the corresponding protocol outputs. func (c *Client) FinalizeBatch(evaluated []*group.Element) ([][]byte, error) { + if len(evaluated) == 0 { + return nil, errBatchNoElements + } + + if len(evaluated) != c.Size() { + return nil, errBatchDifferentSize + } + return c.Client.FinalizeBatch(evaluated) } diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 5bf1d7b..3fb2b03 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -10,7 +10,6 @@ package voprf_test import ( "bytes" - "strings" "testing" "github.com/bytemare/voprf/voprf" @@ -18,21 +17,18 @@ import ( func Test_DecodeElement(t *testing.T) { testAll(t, func(c *configuration) { - bad := getBadElement(t, c) - - if _, err := c.ciphersuite.DecodeElement(bad); err == nil || - !strings.Contains(err.Error(), "element Decode: ") { - t.Errorf("expected error, got %v", err) + element := c.group.NewElement().Base().Multiply(c.group.NewScalar().Random()).Encode() + if _, err := c.ciphersuite.DecodeElement(element); err != nil { + t.Errorf("unexpected error, got %v", err) } }) } func Test_DecodeScalar(t *testing.T) { testAll(t, func(c *configuration) { - bad := getBadScalar(t, c) - - if _, err := c.ciphersuite.DecodeScalar(bad); err == nil || !strings.Contains(err.Error(), "scalar Decode: ") { - t.Errorf("expected error, got %v", err) + scalar := c.group.NewScalar().Random().Encode() + if _, err := c.ciphersuite.DecodeScalar(scalar); err != nil { + t.Errorf("unexpected error, got %v", err) } }) } diff --git a/tests/errors_test.go b/tests/errors_test.go new file mode 100644 index 0000000..f9af086 --- /dev/null +++ b/tests/errors_test.go @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package voprf_test + +import ( + "errors" + "fmt" + "strings" + "testing" + + group "github.com/bytemare/crypto" + + oprf "github.com/bytemare/voprf" + "github.com/bytemare/voprf/internal" + "github.com/bytemare/voprf/voprf" +) + +/* +Test +- NewClient: serverPublicKey == nil || serverPublicKey.IsIdentity() +- client.checkEvaluation +- client.verifyproof: len(c.blindedInput) == 0 +- client.verifyproof +- server.checkkeys +- + +*/ + +func Test_DecodeBadElement(t *testing.T) { + testAll(t, func(c *configuration) { + bad := getBadElement(t, c) + + if _, err := c.ciphersuite.DecodeElement(bad); err == nil || + !strings.Contains(err.Error(), "element Decode: ") { + t.Errorf("expected error, got %v", err) + } + }) +} + +func Test_DecodeBadScalar(t *testing.T) { + testAll(t, func(c *configuration) { + bad := getBadScalar(t, c) + + if _, err := c.ciphersuite.DecodeScalar(bad); err == nil || !strings.Contains(err.Error(), "scalar Decode: ") { + t.Errorf("expected error, got %v", err) + } + }) +} + +func Test_VOPRF_Client_BadPubkey(t *testing.T) { + expected := errors.New("server public key is either nil or the identity element") + testAll(t, func(c *configuration) { + // Test with nil pubkey + if _, err := voprf.NewClient(c.ciphersuite, nil); err == nil || expected.Error() != err.Error() { + t.Error("expected error") + } + + // Test with identity + if _, err := voprf.NewClient(c.ciphersuite, c.group.NewElement()); err == nil || + expected.Error() != err.Error() { + t.Error("expected error") + } + }) +} + +func copyEval(e *voprf.Evaluation) *voprf.Evaluation { + cpy := &voprf.Evaluation{ + Proof: [2]*group.Scalar{ + e.Proof[0].Copy(), + e.Proof[1].Copy(), + }, + Evaluations: make([]*group.Element, len(e.Evaluations)), + } + + for i, eval := range e.Evaluations { + cpy.Evaluations[i] = eval.Copy() + } + + return cpy +} + +type Finalizer func() + +func testFinalize(t *testing.T, client *voprf.Client, expected error, badEval *voprf.Evaluation) { + if _, err := client.Finalize(badEval); err == nil || err.Error() != expected.Error() { + t.Errorf("expected error: want %q, got %q", expected, err) + } + + if _, err := client.FinalizeBatch(badEval); err == nil || err.Error() != expected.Error() { + t.Errorf("expected error: want %q, got %q", expected, err) + } +} + +func Test_VOPRF_Client_BadEvaluation(t *testing.T) { + errInputNilEval := errors.New("provided evaluation is nil") + errDifferentSize := errors.New("number of evaluations differs from number of previously blinded elements") + errInputNoEval := errors.New("provided evaluation does not contain evaluations") + errInputProofCNil := errors.New("proof c is nil") + errInputProofCZero := errors.New("proof c is zero") + errInputProofSNil := errors.New("proof s is nil") + errInputProofSZero := errors.New("proof s is zero") + + testAll(t, func(c *configuration) { + server := voprf.NewServer(c.ciphersuite) + server.GenerateKeys() + _, pk := server.KeyPair() + + client, err := voprf.NewClient(c.ciphersuite, pk) + if err != nil { + t.Error(err) + } + + blinded := client.Blind([]byte("input")) + evaluation := server.Evaluate(blinded) + + testFinalize(t, client, errInputNilEval, nil) + + badEval := copyEval(evaluation) + badEval.Evaluations = nil + testFinalize(t, client, errInputNoEval, badEval) + + badEval.Evaluations = []*group.Element{} + testFinalize(t, client, errInputNoEval, badEval) + + badEval = copyEval(evaluation) + badEval.Proof[0] = nil + testFinalize(t, client, errInputProofCNil, badEval) + + badEval = copyEval(evaluation) + badEval.Proof[0] = c.group.NewScalar() + testFinalize(t, client, errInputProofCZero, badEval) + + badEval = copyEval(evaluation) + badEval.Proof[1] = nil + testFinalize(t, client, errInputProofSNil, badEval) + + badEval = copyEval(evaluation) + badEval.Proof[1] = c.group.NewScalar() + testFinalize(t, client, errInputProofSZero, badEval) + + badEval = copyEval(evaluation) + badEval.Evaluations = append(badEval.Evaluations, c.group.NewElement()) + testFinalize(t, client, errDifferentSize, badEval) + }) +} + +func Test_VOPRF_Client_InvalidProof(t *testing.T) { + errProofFailed := errors.New("invalid proof") + + testAll(t, func(c *configuration) { + server := voprf.NewServer(c.ciphersuite) + server.GenerateKeys() + _, pk := server.KeyPair() + + client, err := voprf.NewClient(c.ciphersuite, pk) + if err != nil { + t.Error(err) + } + + blinded := client.Blind([]byte("input")) + evaluation := server.Evaluate(blinded) + cpy := copyEval(evaluation) + + // Tamper with c + evaluation.Proof[0] = c.group.NewScalar().Random() + testFinalize(t, client, errProofFailed, evaluation) + + // Tamper with s + cpy.Proof[1] = c.group.NewScalar().Random() + testFinalize(t, client, errProofFailed, cpy) + }) +} + +func Test_VOPRF_Server_CheckKeys(t *testing.T) { + errInvalidPublicKey := errors.New("server public key is either nil or the identity element") + errInvalidPrivateKey := errors.New("private key is nil or zero") + errInvalidKeyPair := errors.New("input public key doesn't belong to the private key") + + testAll(t, func(c *configuration) { + server := voprf.NewServer(c.ciphersuite) + + sk := c.group.NewScalar().Random() + pk := c.group.NewElement().Base().Multiply(sk) + + // Test private key + if err := server.SetKeyPair(nil, pk); err == nil || err.Error() != errInvalidPrivateKey.Error() { + t.Error("expected error") + } + + zero := c.group.NewScalar() + if err := server.SetKeyPair(zero, pk); err == nil || err.Error() != errInvalidPrivateKey.Error() { + t.Error("expected error") + } + + // Test public key + if err := server.SetKeyPair(sk, nil); err == nil || err.Error() != errInvalidPublicKey.Error() { + t.Error("expected error") + } + + identity := c.group.NewElement() + if err := server.SetKeyPair(sk, identity); err == nil || err.Error() != errInvalidPublicKey.Error() { + t.Error("expected error") + } + + wrongKey := c.group.NewElement().Base().Multiply(c.group.NewScalar().Random()) + if err := server.SetKeyPair(sk, wrongKey); err == nil || err.Error() != errInvalidKeyPair.Error() { + t.Errorf("expected error: want %q, got %q", errInvalidKeyPair, err) + } + }) +} + +func Test_OPRF_Client_Finalize_BadBatch(t *testing.T) { + errBatchNoElements := errors.New("no evaluated elements provided to Finalize()") + errBatchDifferentSize := errors.New("number of evaluations is different thant number of previously blinded inputs") + inputs := [][]byte{ + []byte("input1"), + []byte("input2"), + []byte("input3"), + } + + testAll(t, func(c *configuration) { + client := c.ciphersuite.Client() + blinded := client.BlindBatch(inputs) + evaluation := oprf.EvaluateBatch(c.group.NewScalar().Random(), blinded) + + if _, err := client.FinalizeBatch(nil); err == nil || err.Error() != errBatchNoElements.Error() { + t.Error("expected error") + } + + if _, err := client.FinalizeBatch(evaluation[:2]); err == nil || err.Error() != errBatchDifferentSize.Error() { + t.Error("expected error") + } + }) +} + +func getBadCiphersuite() oprf.Ciphersuite { + return oprf.Ciphersuite(group.Edwards25519Sha512) +} + +func hasPanic(f func()) (has bool, err error) { + err = nil + var report interface{} + func() { + defer func() { + if report = recover(); report != nil { + has = true + } + }() + + f() + }() + + if has { + err = fmt.Errorf("%v", report) + } + + return +} + +func expectPanic(expectedError error, f func()) (bool, string) { + hasPanic, err := hasPanic(f) + + if !hasPanic { + return false, "no panic" + } + + if expectedError == nil { + return true, "" + } + + if err == nil { + return false, "panic but no message" + } + + if err.Error() != expectedError.Error() { + return false, fmt.Sprintf("expected %q, got %q", expectedError, err) + } + + return true, "" +} + +func Test_BadCiphersuite(t *testing.T) { + expectedError := errors.New("invalid OPRF dependency - Group: edwards25519_XMD:SHA-512_ELL2_RO_") + if hasPanic, err := expectPanic(expectedError, func() { + _ = internal.LoadConfiguration(group.Edwards25519Sha512, 0) + }); !hasPanic { + t.Fatalf("expected panic with wrong group: %v", err) + } +} diff --git a/tests/voprf_test.go b/tests/voprf_test.go index 2d6a6e5..d772845 100644 --- a/tests/voprf_test.go +++ b/tests/voprf_test.go @@ -87,7 +87,25 @@ func TestPOPRF(t *testing.T) { }) } -func TestBatching(t *testing.T) { +func TestOPRFBatching(t *testing.T) { + inputs := [][]byte{ + []byte("input1"), + []byte("input2"), + []byte("input3"), + } + + testAll(t, func(c *configuration) { + client := c.ciphersuite.Client() + blinded := client.BlindBatch(inputs) + evaluation := oprf.EvaluateBatch(c.group.NewScalar().Random(), blinded) + + if _, err := client.FinalizeBatch(evaluation); err != nil { + t.Fatal(err) + } + }) +} + +func TestVPOPRFBatching(t *testing.T) { info := []byte("info") inputs := [][]byte{ []byte("input1"), diff --git a/voprf/client.go b/voprf/client.go index 03a69d5..64db62a 100644 --- a/voprf/client.go +++ b/voprf/client.go @@ -19,7 +19,6 @@ import ( var ( errInvalidPublicKey = errors.New("server public key is either nil or the identity element") - errInputNotSet = errors.New("no prior input found") errDifferentSize = errors.New("number of evaluations differs from number of previously blinded elements") errInputNilEval = errors.New("provided evaluation is nil") errInputNoEval = errors.New("provided evaluation does not contain evaluations") @@ -61,7 +60,7 @@ func NewClient(cs voprf.Ciphersuite, serverPublicKey *group.Element, poprfInfo . verifiable: internal.NewVerifiable(c.Core, poprfInfo), serverPublicKey: serverPublicKey, tweakedKey: nil, - blindedInput: nil, + blindedInput: []*group.Element{}, } if mode == internal.POPRF { @@ -97,10 +96,6 @@ func (c *Client) verifyProof(evaluation *Evaluation) error { var pk *group.Element var cs, ds []*group.Element - if len(c.blindedInput) == 0 { - return errInputNotSet - } - if c.oprf.Mode == internal.VOPRF { cs, ds = c.blindedInput, evaluation.Evaluations pk = c.serverPublicKey @@ -113,35 +108,25 @@ func (c *Client) verifyProof(evaluation *Evaluation) error { } func (c *Client) checkEvaluation(evaluation *Evaluation) error { - if evaluation == nil { + switch { + case evaluation == nil: return errInputNilEval - } - - if len(evaluation.Evaluations) == 0 { - return errInputNoEval - } - - if evaluation.Proof[0] == nil { + case evaluation.Proof[0] == nil: return errInputProofCNil - } - - if evaluation.Proof[0].IsZero() { + case evaluation.Proof[0].IsZero(): return errInputProofCZero - } - - if evaluation.Proof[1] == nil { + case evaluation.Proof[1] == nil: return errInputProofSNil - } - - if evaluation.Proof[1].IsZero() { + case evaluation.Proof[1].IsZero(): return errInputProofSZero - } - - if len(evaluation.Evaluations) != len(c.blindedInput) { + case len(evaluation.Evaluations) == 0: + return errInputNoEval + case len(evaluation.Evaluations) != len(c.blindedInput): + // combined with the previous check this check, this also implies that len(c.blindedInput) >= 1 return errDifferentSize + default: + return nil } - - return nil } // Finalize verifies the Server provided proofs, and, if they are valid, unblinds the evaluated element and returns diff --git a/voprf/server.go b/voprf/server.go index aa6554c..cb43e83 100644 --- a/voprf/server.go +++ b/voprf/server.go @@ -17,6 +17,11 @@ import ( "github.com/bytemare/voprf/internal" ) +var ( + errInvalidPrivateKey = errors.New("private key is nil or zero") + errInvalidKeyPair = errors.New("input public key doesn't belong to the private key") +) + // Server is used for VOPRF or POPRF server executions. For OPRF or TOPRF, used the oprf package (no need for a server // instance). type Server struct { @@ -53,10 +58,17 @@ func NewServer(cs voprf.Ciphersuite, poprfInfo ...byte) *Server { return s } -var ( - errInvalidPrivateKey = errors.New("private key is nil or zero") - errInvalidKeyPair = errors.New("input public key doesn't belong to the private key") -) +func (s *Server) setKeyPair(privateKey *group.Scalar, publicKey *group.Element) { + s.privateKey = privateKey + s.publicKey = publicKey + + if s.Core.Mode == internal.POPRF { + s.scalar, s.t = s.Verifiable.TweakPrivateKey(privateKey) + s.tweakedKey = s.Core.Group.Base().Multiply(s.t) + } else { + s.scalar = s.privateKey + } +} func checkKeys(g group.Group, privateKey *group.Scalar, publicKey *group.Element) error { if publicKey == nil || publicKey.IsIdentity() { @@ -74,18 +86,6 @@ func checkKeys(g group.Group, privateKey *group.Scalar, publicKey *group.Element return nil } -func (s *Server) setKeyPair(privateKey *group.Scalar, publicKey *group.Element) { - s.privateKey = privateKey - s.publicKey = publicKey - - if s.Core.Mode == internal.POPRF { - s.scalar, s.t = s.Verifiable.TweakPrivateKey(privateKey) - s.tweakedKey = s.Core.Group.Base().Multiply(s.t) - } else { - s.scalar = s.privateKey - } -} - // SetKeyPair sets the server's private and public key pair. This returns an error if either key is nil, the public key // is the identity element, or if it doesn't match as a public key to the provided private key. func (s *Server) SetKeyPair(privateKey *group.Scalar, publicKey *group.Element) error {