diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..39a2b6e --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf73504..586d5c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,8 @@ jobs: # Codecov - name: Codecov uses: codecov/codecov-action@bbeaa140357942e4e8d8e15f1cd2f4e612f64c59 # pin@master + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: file: .github/coverage.out diff --git a/README.md b/README.md index 258a7e1..2a2105d 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,15 @@ # (V)OPRF : (Verifiable) Oblivious Pseudorandom Functions -[![VOPRF](https://github.com/bytemare/voprf/actions/workflows/ci.yml/badge.svg)](https://github.com/bytemare/voprf/actions/workflows/ci.yml) +[![VOPRF](https://github.com/bytemare/voprf/actions/workflows/ci.yml/badge.svg?branch=)](https://github.com/bytemare/voprf/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/bytemare/voprf.svg)](https://pkg.go.dev/github.com/bytemare/voprf) [![codecov](https://codecov.io/gh/bytemare/voprf/branch/main/graph/badge.svg?token=5bQfB0OctA)](https://codecov.io/gh/bytemare/voprf) -Package voprf provides abstracted access to Oblivious Pseudorandom Functions (OPRF) over elliptic curves. - -This implementation supports the OPRF, VOPRF, and POPRF protocols as specified in the latest [internet draft](https://tools.ietf.org/html/draft-irtf-cfrg-voprf). +Package voprf provides abstracted access to Oblivious Pseudorandom Functions (OPRF) over Elliptic Curves as specified in +[RFC9497](https://datatracker.ietf.org/doc/rfc9497) and fully supports the OPRF, VOPRF, and POPRF protocols. ## Versioning -[SemVer](http://semver.org/) is used for versioning. For the versions available, see the [tags on this repository](https://github.com/bytemare/voprf/tags). - -Minor v0.x versions match the corresponding CFRG draft version, the master branch implements the latest changes of [the draft development](https://github.com/cfrg/draft-irtf-cfrg-voprf). +[SemVer](http://semver.org) is used for versioning. For the versions available, see the [tags on this repository](https://github.com/bytemare/voprf/tags). ## Contributing diff --git a/SECURITY.md b/SECURITY.md index f83bfea..6c03d46 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,6 @@ ## Supported Versions -The VOPRF protocol is still in the process of specification. Therefore, this implementation evolves with the draft. Only the latest version will be benefit from security fixes. Maintainers of projects using this implementation of VOPRF are invited to update their dependency. ## Reporting a Vulnerability diff --git a/client_state.go b/client_state.go index e28adfd..4e46ae3 100644 --- a/client_state.go +++ b/client_state.go @@ -28,7 +28,7 @@ type State struct { // Export extracts the client's internal values that can be imported in another client for session resumption. func (c *Client) Export() *State { s := &State{ - Identifier: c.id, + Identifier: c.ciphersuite, TweakedKey: nil, ServerPublicKey: nil, Input: nil, diff --git a/doc.go b/doc.go index e0d24d0..d0fca86 100644 --- a/doc.go +++ b/doc.go @@ -9,7 +9,5 @@ // Package voprf provides abstracted access to Oblivious Pseudorandom Functions (OPRF) // and VOPRF Oblivious Pseudorandom Functions (VOPRF) using Elliptic Curves (EC(V)OPRF). // -// This work in progress implements https://tools.ietf.org/html/draft-irtf-cfrg-voprf -// -// Integrations can use either base or verifiable mode with additive or multiplicative operations. +// This implements RFC9497. package voprf diff --git a/evaluation.go b/evaluation.go index 1cf8329..9efe734 100644 --- a/evaluation.go +++ b/evaluation.go @@ -16,7 +16,7 @@ import ( // Evaluation holds the serialized evaluated elements and serialized proof. type Evaluation struct { - // Elements represents the unique serialization of an Elements + // Elements represents the unique serialization of Elements Elements [][]byte `json:"e"` // Proofs diff --git a/examples_test.go b/examples_test.go index 062dde1..dd7990e 100644 --- a/examples_test.go +++ b/examples_test.go @@ -10,6 +10,7 @@ package voprf_test import ( "encoding/hex" + "fmt" "github.com/bytemare/voprf" ) @@ -41,6 +42,7 @@ func exchangeWithServer(blinded []byte, verifiable bool) []byte { return ev } +// This shows you how to set up and run the base OPRF client. func Example_client() { input := []byte("input") @@ -52,13 +54,15 @@ func Example_client() { // The client blinds the initial input, and sends this to the server. blinded := client.Blind(input, nil) + fmt.Printf("Send these %d bytes to the server.\n", len(blinded)) - // Exchange with the server is not covered here. The following call is to mock an exchange with a server. - ev := exchangeWithServer(blinded, false) + // Exchange with the server is not covered in this example. Let's say the server sends the following serialized + // evaluation. + evaluation, _ := hex.DecodeString("00010020b4d261d982c6edd2fea53e8a39c1df6393f23cb9d1b4768891ec2f43b8d8e831") // The client needs to decode the evaluation to finalize the process. eval := new(voprf.Evaluation) - if err := eval.Deserialize(ev); err != nil { + if err = eval.Deserialize(evaluation); err != nil { panic(err) } @@ -67,9 +71,10 @@ func Example_client() { if output == nil || err != nil { panic(err) } - // Output: + // Output:Send these 32 bytes to the server. } +// This shows you how to set up and run the Verifiable OPRF client. func Example_verifiableClient() { ciphersuite := voprf.Ristretto255Sha512 input := []byte("input") @@ -85,11 +90,11 @@ func Example_verifiableClient() { blinded := client.Blind(input, nil) // Exchange with the server is not covered here. The following call is to mock an exchange with a server. - ev := exchangeWithServer(blinded, true) + evaluation := exchangeWithServer(blinded, true) // The client needs to decode the evaluation to finalize the process. eval := new(voprf.Evaluation) - if err := eval.Deserialize(ev); err != nil { + if err := eval.Deserialize(evaluation); err != nil { panic(err) } @@ -102,6 +107,7 @@ func Example_verifiableClient() { // Output: } +// This shows you how to set up and run the base OPRF server. func Example_server() { // We suppose the client sends this blinded element. blinded, _ := hex.DecodeString("7eaf3d7cbe43d54637274342ce53578b2aba836f297f4f07997a6e1dced1c058") @@ -123,6 +129,7 @@ func Example_server() { // Output: } +// This shows you how to set up and run the Verifiable OPRF server. func Example_verifiableServer() { privateKey, _ := hex.DecodeString("8132542d5ed08594e7522b5eac6bee38bab5868996c25a3fd2a7739be1856b04") diff --git a/oprf.go b/oprf.go index ce0c4b4..7452b21 100644 --- a/oprf.go +++ b/oprf.go @@ -75,7 +75,7 @@ func (c Ciphersuite) new(mode Mode) *oprf { return &oprf{ hash: hashes[c].Get(), contextString: contextString(mode, c), - id: c, + ciphersuite: c, mode: mode, group: groups[c], } @@ -130,30 +130,34 @@ func (c Ciphersuite) KeyGen() *KeyPair { pk := c.Group().Base().Multiply(sk) return &KeyPair{ - ID: c, - PublicKey: pk.Encode(), - SecretKey: sk.Encode(), + Ciphersuite: c, + PublicKey: pk, + SecretKey: sk, } } // DeriveKeyPair deterministically generates a private and public key pair from input seed. -func (c Ciphersuite) DeriveKeyPair(mode Mode, seed, info []byte) (*group.Scalar, *group.Element) { +func (c Ciphersuite) DeriveKeyPair(mode Mode, seed, info []byte) *KeyPair { dst := concatenate([]byte(deriveKeyPairDST), contextString(mode, c)) deriveInput := concatenate(seed, lengthPrefixEncode(info)) var counter uint8 - var s *group.Scalar + var sk *group.Scalar - for s == nil || s.IsZero() { + for sk == nil || sk.IsZero() { if counter > 255 { panic("impossible to generate non-zero scalar") } - s = c.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) + sk = c.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) counter++ } - return s, c.Group().Base().Multiply(s) + return &KeyPair{ + Ciphersuite: c, + PublicKey: c.Group().Base().Multiply(sk), + SecretKey: sk, + } } // Client returns a (P|V)OPRF client. For the OPRF mode, serverPublicKey should be nil, and non-nil otherwise. @@ -189,19 +193,19 @@ func (c Ciphersuite) Server(mode Mode, privateKey []byte) (*Server, error) { type oprf struct { hash *hash.Hash - id Ciphersuite + ciphersuite Ciphersuite contextString []byte mode Mode group group.Group } -func contextString(mode Mode, id Ciphersuite) []byte { - ctx := make([]byte, 0, len(Version)+3+len(id.String())) +func contextString(mode Mode, ciphersuite Ciphersuite) []byte { + ctx := make([]byte, 0, len(Version)+3+len(ciphersuite.String())) ctx = append(ctx, Version...) ctx = append(ctx, "-"...) ctx = append(ctx, byte(mode)) ctx = append(ctx, "-"...) - ctx = append(ctx, id.String()...) + ctx = append(ctx, ciphersuite.String()...) return ctx } diff --git a/server.go b/server.go index f6d1619..d33f7ff 100644 --- a/server.go +++ b/server.go @@ -128,48 +128,6 @@ func (s *Server) EvaluateBatchWithRandom(blindedElements [][]byte, random, info return s.innerEvaluateBatch(blindedElements, random, info) } -// FullEvaluate reproduces the full PRF but without the blinding operations, using the client's input. -// This should output the same digest as the client's Finalize() function. -func (s *Server) FullEvaluate(input, info []byte) ([]byte, error) { - p := s.HashToGroup(input) - - scalar, _, err := s.getPrivateKeys(info) - if err != nil { - return nil, err - } - - t := p.Multiply(scalar) - - if s.oprf.mode == OPRF || s.oprf.mode == VOPRF { - info = nil - } - - return s.hashTranscript(input, info, t.Encode()), nil -} - -// VerifyFinalize takes the client input (the un-blinded element) and the client's finalize() output, -// and returns whether it can match the client's output. -func (s *Server) VerifyFinalize(input, info, output []byte) bool { - digest, err := s.FullEvaluate(input, info) - if err != nil { - return false - } - - return ctEqual(digest, output) -} - -// VerifyFinalizeBatch takes the batch of client input (the un-blinded elements) and the client's finalize() outputs, -// and returns whether it can match the client's outputs. -func (s *Server) VerifyFinalizeBatch(input, output [][]byte, info []byte) bool { - res := true - - for i, in := range input { - res = s.VerifyFinalize(in, info, output[i]) - } - - return res -} - // PrivateKey returns the server's serialized private key. func (s *Server) PrivateKey() []byte { return s.privateKey.Encode() @@ -182,5 +140,5 @@ func (s *Server) PublicKey() []byte { // Ciphersuite returns the cipher suite used in the server's instance. func (s *Server) Ciphersuite() Ciphersuite { - return s.oprf.id + return s.oprf.ciphersuite } diff --git a/tests/helper_test.go b/tests/helper_test.go index 449bb3d..0eda225 100644 --- a/tests/helper_test.go +++ b/tests/helper_test.go @@ -19,6 +19,7 @@ import ( "testing" group "github.com/bytemare/crypto" + "github.com/bytemare/hash" "github.com/bytemare/voprf" ) @@ -33,29 +34,46 @@ type configuration struct { curve elliptic.Curve ciphersuite voprf.Ciphersuite name string + hash hash.Hashing + group group.Group } var configurationTable = []configuration{ { name: "Ristretto255", ciphersuite: voprf.Ristretto255Sha512, + group: group.Ristretto255Sha512, + hash: hash.SHA512, curve: nil, }, { name: "P256Sha256", ciphersuite: voprf.P256Sha256, + group: group.P256Sha256, + hash: hash.SHA256, curve: elliptic.P256(), }, { name: "P384Sha512", ciphersuite: voprf.P384Sha384, + group: group.P384Sha384, + hash: hash.SHA384, curve: elliptic.P384(), }, { name: "P521Sha512", ciphersuite: voprf.P521Sha512, + group: group.P521Sha512, + hash: hash.SHA512, curve: elliptic.P521(), }, + { + name: "Secp256k1Sha256", + ciphersuite: voprf.Secp256k1, + group: group.Secp256k1, + hash: hash.SHA256, + curve: nil, + }, } func testAll(t *testing.T, f func(*configuration)) { @@ -102,16 +120,16 @@ func randomBytes(length int) []byte { return r } -func getBadNistElement(t *testing.T, id group.Group) []byte { - size := id.ElementLength() +func getBadNistElement(t *testing.T, g group.Group) []byte { + size := g.ElementLength() element := randomBytes(size) // detag compression element[0] = 4 // test if invalid compression is detected - err := id.NewElement().Decode(element) + err := g.NewElement().Decode(element) if err == nil { - t.Errorf("detagged compressed point did not yield an error for group %s", id) + t.Errorf("detagged compressed point did not yield an error for group %s", g) } return element @@ -183,19 +201,19 @@ func lengthPrefixEncode(input []byte) []byte { return append(i2osp2(len(input)), input...) } -func contextString(mode voprf.Mode, id voprf.Ciphersuite) []byte { - ctx := make([]byte, 0, len(voprf.Version)+3+len(id.String())) +func contextString(mode voprf.Mode, g voprf.Ciphersuite) []byte { + ctx := make([]byte, 0, len(voprf.Version)+3+len(g.String())) ctx = append(ctx, voprf.Version...) ctx = append(ctx, "-"...) ctx = append(ctx, byte(mode)) ctx = append(ctx, "-"...) - ctx = append(ctx, id.String()...) + ctx = append(ctx, g.String()...) return ctx } -func deriveKeyPair(seed, info []byte, mode voprf.Mode, id voprf.Ciphersuite) (*group.Scalar, *group.Element) { - dst := concatenate([]byte(deriveKeyPairDST), contextString(mode, id)) +func deriveKeyPair(seed, info []byte, mode voprf.Mode, g voprf.Ciphersuite) (*group.Scalar, *group.Element) { + dst := concatenate([]byte(deriveKeyPairDST), contextString(mode, g)) deriveInput := concatenate(seed, lengthPrefixEncode(info)) var counter uint8 @@ -206,9 +224,9 @@ func deriveKeyPair(seed, info []byte, mode voprf.Mode, id voprf.Ciphersuite) (*g panic("impossible to generate non-zero scalar") } - s = id.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) + s = g.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) counter++ } - return s, id.Group().Base().Multiply(s) + return s, g.Group().Base().Multiply(s) } diff --git a/tests/state_test.go b/tests/state_test.go index d312f30..c71e763 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -11,6 +11,7 @@ package voprf_test import ( "bytes" "encoding/json" + "errors" "fmt" "testing" @@ -47,20 +48,38 @@ func TestEvaluationSerde(t *testing.T) { t.Fatal(err) } + errSerDeFailed := errors.New("evaluation serde failed") + if !areArraysOfArraysEqual(evaluation.Elements, deser.Elements) { - t.Fatal("evaluation serde failed") + t.Fatal(errSerDeFailed) } if bytes.Compare(evaluation.ProofC, evaluation.ProofC) != 0 { - t.Fatal("evaluation serde failed") + t.Fatal(errSerDeFailed) } if bytes.Compare(evaluation.ProofS, evaluation.ProofS) != 0 { - t.Fatal("evaluation serde failed") + t.Fatal(errSerDeFailed) + } +} + +func serdeExport(t *testing.T, client *voprf.Client) (*voprf.State, *voprf.State) { + export := client.Export() + + serialized, err := json.Marshal(export) + if err != nil { + t.Fatal(err) + } + + state := &voprf.State{} + if err := json.Unmarshal(serialized, state); err != nil { + t.Fatal(err) } + + return export, state } -func TestClient_State(t *testing.T) { +func TestClientState(t *testing.T) { suite := voprf.Ristretto255Sha512 input := []byte("input") kp := suite.KeyGen() // only used in VOPRF and POPRF @@ -68,24 +87,14 @@ func TestClient_State(t *testing.T) { for _, mode := range []voprf.Mode{voprf.OPRF, voprf.VOPRF, voprf.POPRF} { t.Run(fmt.Sprintf("State test for mode %v", mode), func(t *testing.T) { - client, err := suite.Client(mode, kp.PublicKey) + client, err := suite.Client(mode, kp.PublicKey.Encode()) if err != nil { t.Fatal(err) } client.Blind(input, info) - export := client.Export() - - serialized, err := json.Marshal(export) - if err != nil { - t.Fatal(err) - } - - state := &voprf.State{} - if err := json.Unmarshal(serialized, state); err != nil { - t.Fatal(err) - } + export, state := serdeExport(t, client) resumed, err := state.RecoverClient() if err != nil { diff --git a/tests/vectors_test.go b/tests/vectors_test.go index d77ee6f..0621886 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -226,10 +226,10 @@ func (v vector) checkParams(t *testing.T) { //} } -func testBlind(t *testing.T, id voprf.Ciphersuite, client *voprf.Client, input, blind, expected, info []byte) { - s := id.Group().NewScalar() +func testBlind(t *testing.T, ciphersuite voprf.Ciphersuite, client *voprf.Client, input, blind, expected, info []byte) { + s := ciphersuite.Group().NewScalar() if err := s.Decode(blind); err != nil { - t.Fatal(fmt.Errorf("blind decoding to scalar in suite %v errored with %q", id, err)) + t.Fatal(fmt.Errorf("blind decoding to scalar in suite %v errored with %q", ciphersuite, err)) } client.SetBlinds([]*group.Scalar{s}) @@ -254,25 +254,10 @@ func testBlindBatchWithBlinds(t *testing.T, client *voprf.Client, inputs, blinds } } -func testOPRF( - t *testing.T, - id voprf.Ciphersuite, - mode voprf.Mode, - client *voprf.Client, - server *voprf.Server, - test *test, -) { +func testOPRFServerEvaluation(t *testing.T, server *voprf.Server, test *test) *voprf.Evaluation { + var ev *voprf.Evaluation var err error - // OPRFClient Blinding - if test.Batch == 1 { - testBlind(t, id, client, test.Input[0], test.Blind[0], test.BlindedElement[0], test.Info) - } else { - testBlindBatchWithBlinds(t, client, test.Input, test.Blind, test.BlindedElement, test.Info) - } - - // OPRFServer evaluating - var ev *voprf.Evaluation if test.Batch == 1 { ev, err = server.EvaluateWithRandom(test.BlindedElement[0], test.NonceR, test.Info) if err != nil { @@ -295,6 +280,51 @@ func testOPRF( } } + return ev +} + +func testOPRFClientFinalize(t *testing.T, client *voprf.Client, ev *voprf.Evaluation, test *test) { + if test.Batch == 1 { + output, err := client.Finalize(ev, test.Info) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(test.Output[0], output) { + t.Fatal("finalize() output is not valid.") + } + } else { + output, err := client.FinalizeBatch(ev, test.Info) + if err != nil { + t.Fatal(err) + } + + for i, o := range test.Output { + if !bytes.Equal(o, output[i]) { + t.Fatal("finalizeBatch() output is not valid.") + } + } + } +} + +func testOPRF( + t *testing.T, + ciphersuite voprf.Ciphersuite, + mode voprf.Mode, + client *voprf.Client, + server *voprf.Server, + test *test, +) { + // OPRFClient Blinding + if test.Batch == 1 { + testBlind(t, ciphersuite, client, test.Input[0], test.Blind[0], test.BlindedElement[0], test.Info) + } else { + testBlindBatchWithBlinds(t, client, test.Input, test.Blind, test.BlindedElement, test.Info) + } + + // OPRFServer evaluating + ev := testOPRFServerEvaluation(t, server, test) + // Verify proofs if mode == voprf.VOPRF || mode == voprf.POPRF { if !bytes.Equal(test.ProofC, ev.ProofC) { @@ -315,35 +345,70 @@ func testOPRF( } // OPRFClient finalize - if test.Batch == 1 { - output, err := client.Finalize(ev, test.Info) - if err != nil { - t.Fatal(err) - } + testOPRFClientFinalize(t, client, ev, test) +} - if !bytes.Equal(test.Output[0], output) { - t.Fatal("finalize() output is not valid.") - } +func (v vector) testVector( + t *testing.T, + tv *testVector, + suite voprf.Ciphersuite, + mode voprf.Mode, + privKey, serverPublicKey, expectedDST []byte, +) { + test, err := tv.Decode() + if err != nil { + t.Fatal(fmt.Sprintf("batches : %v Failed %v\n", tv.Batch, err)) + } - if !server.VerifyFinalize(test.Input[0], test.Info, output) { - t.Fatal("VerifyFinalize() returned false.") - } - } else { - output, err := client.FinalizeBatch(ev, test.Info) - if err != nil { - t.Fatal(err) - } + if err := test.Verify(suite); err != nil { + t.Fatal(err) + } - for i, o := range test.Output { - if !bytes.Equal(o, output[i]) { - t.Fatal("finalizeBatch() output is not valid.") - } - } + // Test DeriveKeyPair + seed, err := hex.DecodeString(v.SksSeed) + if err != nil { + t.Fatal(err) + } - if !server.VerifyFinalizeBatch(test.Input, output, test.Info) { - t.Fatal("VerifyFinalize() returned false.") - } + keyInfo, err := hex.DecodeString(v.KeyInfo) + if err != nil { + t.Fatal(err) + } + + sks, _ := deriveKeyPair(seed, keyInfo, mode, suite) + // log.Printf("sks %v", hex.EncodeToString(serializeScalar(sks, scalarLength(o.id)))) + if !bytes.Equal(sks.Encode(), privKey) { + t.Fatalf("DeriveKeyPair yields unexpected output\n\twant: %v\n\tgot : %v", privKey, sks.Encode()) } + + // Set up a new server. + server, err := suite.Server(mode, privKey) + if err != nil { + t.Fatalf( + "failed on setting up server %q\nvector value (%d) %v\ndecoded (%d) %v\n", + err, + len(v.SkSm), + v.SkSm, + len(privKey), + privKey, + ) + } + + if string(expectedDST) != string(dst(hash2groupDSTPrefix, contextString(mode, suite))) { + t.Fatal("GroupDST output is not valid.") + } + + client, err := suite.Client(mode, serverPublicKey) + if err != nil { + t.Fatal(err) + } + + if string(expectedDST) != string(dst(hash2groupDSTPrefix, contextString(mode, suite))) { + t.Fatal("GroupDST output is not valid.") + } + + // test protocol execution + testOPRF(t, v.SuiteID, mode, client, server, test) } func (v vector) test(t *testing.T) { @@ -373,63 +438,9 @@ func (v vector) test(t *testing.T) { t.Fatalf("hex decoding errored with %q", err) } - // Test Multiplicative Mode for i, tv := range v.TestVectors { t.Run(fmt.Sprintf("Vector %d", i), func(t *testing.T) { - test, err := tv.Decode() - if err != nil { - t.Fatal(fmt.Sprintf("batches : %v Failed %v\n", tv.Batch, err)) - } - - if err := test.Verify(suite); err != nil { - t.Fatal(err) - } - - // Test DeriveKeyPair - seed, err := hex.DecodeString(v.SksSeed) - if err != nil { - t.Fatal(err) - } - - keyInfo, err := hex.DecodeString(v.KeyInfo) - if err != nil { - t.Fatal(err) - } - - sks, _ := deriveKeyPair(seed, keyInfo, mode, suite) - // log.Printf("sks %v", hex.EncodeToString(serializeScalar(sks, scalarLength(o.id)))) - if !bytes.Equal(sks.Encode(), privKey) { - t.Fatalf("DeriveKeyPair yields unexpected output\n\twant: %v\n\tgot : %v", privKey, sks.Encode()) - } - - // Set up a new server. - server, err := suite.Server(mode, privKey) - if err != nil { - t.Fatalf( - "failed on setting up server %q\nvector value (%d) %v\ndecoded (%d) %v\n", - err, - len(v.SkSm), - v.SkSm, - len(privKey), - privKey, - ) - } - - if string(expectedDST) != string(dst(hash2groupDSTPrefix, contextString(mode, suite))) { - t.Fatal("GroupDST output is not valid.") - } - - client, err := suite.Client(mode, serverPublicKey) - if err != nil { - t.Fatal(err) - } - - if string(expectedDST) != string(dst(hash2groupDSTPrefix, contextString(mode, suite))) { - t.Fatal("GroupDST output is not valid.") - } - - // test protocol execution - testOPRF(t, v.SuiteID, mode, client, server, test) + v.testVector(t, &tv, suite, mode, privKey, serverPublicKey, expectedDST) }) } } diff --git a/tests/voprf_test.go b/tests/voprf_test.go index 71b044b..d2fa8ba 100644 --- a/tests/voprf_test.go +++ b/tests/voprf_test.go @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT -// SPDX-License-Identifier: MIT // // Copyright (C) 2024 Daniel Bourdrez. All Rights Reserved. // @@ -11,11 +10,14 @@ package voprf_test import ( "encoding/hex" + "errors" "testing" "github.com/bytemare/voprf" ) +var errExpectedEquality = errors.New("expected equality") + func makeClientAndServer(t *testing.T, mode voprf.Mode, ciphersuite voprf.Ciphersuite) (*voprf.Client, *voprf.Server) { server, err := ciphersuite.Server(mode, nil) if err != nil { @@ -105,6 +107,39 @@ func TestBatching(t *testing.T) { }) } +func TestAvailability(t *testing.T) { + testAll(t, func(c *configuration) { + if !c.ciphersuite.Available() { + t.Fatal("expected availability") + } + }) +} + +func TestCiphersuiteGroup(t *testing.T) { + testAll(t, func(c *configuration) { + if c.ciphersuite.Group() != c.group { + t.Fatal(errExpectedEquality) + } + + ciphersuite, err := voprf.FromGroup(c.group) + if err != nil { + t.Fatal(err) + } + + if ciphersuite != c.ciphersuite { + t.Fatal(errExpectedEquality) + } + }) +} + +func TestCiphersuiteHashes(t *testing.T) { + testAll(t, func(c *configuration) { + if c.hash != c.ciphersuite.Hash() { + t.Fatal(errExpectedEquality) + } + }) +} + func TestServerKeys(t *testing.T) { mode := voprf.OPRF @@ -126,7 +161,7 @@ func TestServerKeys(t *testing.T) { pk := c.ciphersuite.Group().Base().Multiply(private) if pk.Equal(public) != 1 { - t.Fatal("expected equality") + t.Fatal(errExpectedEquality) } }) } @@ -149,9 +184,9 @@ func TestDeriveKeyPair(t *testing.T) { refPk := ciphersuite.Group().NewElement() _ = refPk.Decode(encodedReferencePublicKeyR255) - sk, pk := ciphersuite.DeriveKeyPair(voprf.OPRF, random, info) + keyPair := ciphersuite.DeriveKeyPair(voprf.OPRF, random, info) - if sk.Equal(refSk) != 1 || pk.Equal(refPk) != 1 { - t.Fatal("expected equality") + if keyPair.SecretKey.Equal(refSk) != 1 || keyPair.PublicKey.Equal(refPk) != 1 { + t.Fatal(errExpectedEquality) } } diff --git a/utils.go b/utils.go index 9b57cf4..a08cb6d 100644 --- a/utils.go +++ b/utils.go @@ -11,13 +11,16 @@ package voprf import ( "crypto/subtle" "encoding/binary" + + group "github.com/bytemare/crypto" ) -// KeyPair assembles a VOPRF key pair. The SecretKey can be used as the evaluation key for the group identified by ID. +// KeyPair assembles a VOPRF key pair. The SecretKey can be used as the evaluation key for +// the group identified by Ciphersuite. type KeyPair struct { - ID Ciphersuite - PublicKey []byte - SecretKey []byte + PublicKey *group.Element + SecretKey *group.Scalar + Ciphersuite Ciphersuite } func i2osp2(value int) []byte {