diff --git a/.github/licence-header.tmpl b/.github/licence-header.tmpl index 8eca910..4d3a906 100644 --- a/.github/licence-header.tmpl +++ b/.github/licence-header.tmpl @@ -1,6 +1,6 @@ SPDX-License-Identifier: MIT -Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +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 diff --git a/client.go b/client.go index 01353cb..8963d45 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 diff --git a/client_state.go b/client_state.go index f0e8e0b..e28adfd 100644 --- a/client_state.go +++ b/client_state.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -16,13 +16,13 @@ import ( // State represents a client's state, allowing internal values to be exported and imported to resume a session. type State struct { - Identifier Identifier `json:"s"` - TweakedKey []byte `json:"t,omitempty"` - ServerPublicKey []byte `json:"p,omitempty"` - Input [][]byte `json:"i"` - Blind [][]byte `json:"r"` - Blinded [][]byte `json:"d"` - Mode Mode `json:"m"` + Identifier Ciphersuite `json:"s"` + TweakedKey []byte `json:"t,omitempty"` + ServerPublicKey []byte `json:"p,omitempty"` + Input [][]byte `json:"i"` + Blind [][]byte `json:"r"` + Blinded [][]byte `json:"d"` + Mode Mode `json:"m"` } // Export extracts the client's internal values that can be imported in another client for session resumption. diff --git a/doc.go b/doc.go index 04a1901..e0d24d0 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 diff --git a/errors.go b/errors.go index ffd47b0..ad506ba 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -12,7 +12,7 @@ import "errors" var ( errParamInvalidMode = errors.New("invalid OPRF mode") - errParamInvalidID = errors.New("invalid Identifier") + errParamInvalidID = errors.New("invalid Ciphersuite") errParamFinalizeLen = errors.New("invalid number of elements in evaluation") errParamInputEqualLen = errors.New("input lengths are not equal") errParamNoPubKey = errors.New("missing public key") diff --git a/evaluation.go b/evaluation.go index 23df90c..1cf8329 100644 --- a/evaluation.go +++ b/evaluation.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 diff --git a/examples_test.go b/examples_test.go index 0646c81..062dde1 100644 --- a/examples_test.go +++ b/examples_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 diff --git a/oprf.go b/oprf.go index b5ca57d..ce0c4b4 100644 --- a/oprf.go +++ b/oprf.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -29,29 +29,32 @@ const ( POPRF ) -// Identifier of the OPRF compatible cipher suite to be used. -type Identifier string +// Ciphersuite of the OPRF compatible cipher suite to be used. +type Ciphersuite string const ( // Ristretto255Sha512 is the OPRF cipher suite of the Ristretto255 group and SHA-512. - Ristretto255Sha512 Identifier = "ristretto255-SHA512" + Ristretto255Sha512 Ciphersuite = "ristretto255-SHA512" // Decaf448Sha512 is the OPRF cipher suite of the Decaf448 group and SHA-512. - // decaf448Sha512 Identifier = "decaf448-SHAKE256". + // decaf448Sha512 Ciphersuite = "decaf448-SHAKE256". // P256Sha256 is the OPRF cipher suite of the NIST P-256 group and SHA-256. - P256Sha256 Identifier = "P256-SHA256" + P256Sha256 Ciphersuite = "P256-SHA256" // P384Sha384 is the OPRF cipher suite of the NIST P-384 group and SHA-384. - P384Sha384 Identifier = "P384-SHA384" + P384Sha384 Ciphersuite = "P384-SHA384" // P521Sha512 is the OPRF cipher suite of the NIST P-512 group and SHA-512. - P521Sha512 Identifier = "P521-SHA512" + P521Sha512 Ciphersuite = "P521-SHA512" - nbIDs = 4 + // Secp256k1 is the OPRF cipher suite of the SECp256k1 group and SHA-256. + Secp256k1 Ciphersuite = "secp256k1-SHA256" - // version is a string explicitly stating the version name. - version = "OPRFV1" + nbIDs = 5 + + // Version is a string explicitly stating the Version name. + Version = "OPRFV1" // deriveKeyPairDST is the DST prefix for the DeriveKeyPair function. deriveKeyPairDST = "DeriveKeyPair" @@ -64,36 +67,36 @@ const ( ) var ( - groups = make(map[Identifier]group.Group, nbIDs) - hashes = make(map[Identifier]hash.Hashing, nbIDs) + groups = make(map[Ciphersuite]group.Group, nbIDs) + hashes = make(map[Ciphersuite]hash.Hashing, nbIDs) ) -func (i Identifier) new(mode Mode) *oprf { +func (c Ciphersuite) new(mode Mode) *oprf { return &oprf{ - hash: hashes[i].Get(), - contextString: contextString(mode, i), - id: i, + hash: hashes[c].Get(), + contextString: contextString(mode, c), + id: c, mode: mode, - group: groups[i], + group: groups[c], } } -// Available returns whether the Identifier is registered and available for usage. -func (i Identifier) Available() bool { +// Available returns whether the Ciphersuite is registered and available for usage. +func (c Ciphersuite) Available() bool { // Check for invalid identifiers - switch i { - case Ristretto255Sha512, P256Sha256, P384Sha384, P521Sha512: + switch c { + case Ristretto255Sha512, P256Sha256, P384Sha384, P521Sha512, Secp256k1: break default: return false } // Check for unregistered groups and hashes - if _, ok := groups[i]; !ok { + if _, ok := groups[c]; !ok { return false } - if _, ok := hashes[i]; !ok { + if _, ok := hashes[c]; !ok { return false } @@ -101,17 +104,17 @@ func (i Identifier) Available() bool { } // Group returns the group identifier used in the cipher suite. -func (i Identifier) Group() group.Group { - return groups[i] +func (c Ciphersuite) Group() group.Group { + return groups[c] } // Hash returns the hash function identifier used in the cipher suite. -func (i Identifier) Hash() hash.Hashing { - return hashes[i] +func (c Ciphersuite) Hash() hash.Hashing { + return hashes[c] } -// FromGroup returns a (V)OPRF Identifier given a Group Identifier. -func FromGroup(g group.Group) (Identifier, error) { +// FromGroup returns a (V)OPRF Ciphersuite given a Group Ciphersuite. +func FromGroup(g group.Group) (Ciphersuite, error) { for k, v := range groups { if v == g { return k, nil @@ -122,24 +125,44 @@ func FromGroup(g group.Group) (Identifier, error) { } // KeyGen returns a fresh KeyPair for the given cipher suite. -func (i Identifier) KeyGen() *KeyPair { - sk := i.Group().NewScalar().Random() - pk := i.Group().Base().Multiply(sk) +func (c Ciphersuite) KeyGen() *KeyPair { + sk := c.Group().NewScalar().Random() + pk := c.Group().Base().Multiply(sk) return &KeyPair{ - ID: i, + ID: c, PublicKey: pk.Encode(), SecretKey: sk.Encode(), } } +// 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) { + dst := concatenate([]byte(deriveKeyPairDST), contextString(mode, c)) + deriveInput := concatenate(seed, lengthPrefixEncode(info)) + + var counter uint8 + var s *group.Scalar + + for s == nil || s.IsZero() { + if counter > 255 { + panic("impossible to generate non-zero scalar") + } + + s = c.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) + counter++ + } + + return s, c.Group().Base().Multiply(s) +} + // Client returns a (P|V)OPRF client. For the OPRF mode, serverPublicKey should be nil, and non-nil otherwise. -func (i Identifier) Client(mode Mode, serverPublicKey []byte) (*Client, error) { +func (c Ciphersuite) Client(mode Mode, serverPublicKey []byte) (*Client, error) { if mode != OPRF && mode != VOPRF && mode != POPRF { return nil, errParamInvalidMode } - client := i.client(mode) + client := c.client(mode) if mode == VOPRF || mode == POPRF { if serverPublicKey == nil { @@ -156,25 +179,25 @@ func (i Identifier) Client(mode Mode, serverPublicKey []byte) (*Client, error) { // Server returns a (P|V)OPRF server instantiated with the given encoded private key. // If privateKey is nil, a new private/public key pair is created. -func (i Identifier) Server(mode Mode, privateKey []byte) (*Server, error) { +func (c Ciphersuite) Server(mode Mode, privateKey []byte) (*Server, error) { if mode != OPRF && mode != VOPRF && mode != POPRF { return nil, errParamInvalidMode } - return i.server(mode, privateKey) + return c.server(mode, privateKey) } type oprf struct { hash *hash.Hash - id Identifier + id Ciphersuite contextString []byte mode Mode group group.Group } -func contextString(mode Mode, id Identifier) []byte { - ctx := make([]byte, 0, len(version)+3+len(id.String())) - ctx = append(ctx, version...) +func contextString(mode Mode, id Ciphersuite) []byte { + ctx := make([]byte, 0, len(Version)+3+len(id.String())) + ctx = append(ctx, Version...) ctx = append(ctx, "-"...) ctx = append(ctx, byte(mode)) ctx = append(ctx, "-"...) @@ -183,26 +206,6 @@ func contextString(mode Mode, id Identifier) []byte { return ctx } -// DeriveKeyPair deterministically generates a private and public key pair from input seed. -func (o *oprf) DeriveKeyPair(seed, info []byte) (*group.Scalar, *group.Element) { - dst := concatenate([]byte(deriveKeyPairDST), o.contextString) - deriveInput := concatenate(seed, lengthPrefixEncode(info)) - - var counter uint8 - var s *group.Scalar - - for s == nil || s.IsZero() { - if counter > 255 { - panic("impossible to generate non-zero scalar") - } - - s = o.group.HashToScalar(concatenate(deriveInput, []byte{counter}), dst) - counter++ - } - - return s, o.group.Base().Multiply(s) -} - // HashToGroup maps the input data to an element of the group. func (o *oprf) HashToGroup(data []byte) *group.Element { return o.group.HashToGroup(data, dst(hash2groupDSTPrefix, o.contextString)) @@ -213,11 +216,11 @@ func (o *oprf) HashToScalar(data []byte) *group.Scalar { return o.group.HashToScalar(data, dst(hash2scalarDSTPrefix, o.contextString)) } -func (i Identifier) client(mode Mode) *Client { +func (c Ciphersuite) client(mode Mode) *Client { return &Client{ tweakedKey: nil, serverPublicKey: nil, - oprf: i.new(mode), + oprf: c.new(mode), input: nil, blind: nil, blindedElement: nil, @@ -239,11 +242,11 @@ func (c *Client) setServerPublicKey(serverPublicKey []byte) error { return nil } -func (i Identifier) server(mode Mode, privateKey []byte) (*Server, error) { +func (c Ciphersuite) server(mode Mode, privateKey []byte) (*Server, error) { s := &Server{ privateKey: nil, publicKey: nil, - oprf: i.new(mode), + oprf: c.new(mode), } if privateKey == nil { @@ -286,15 +289,15 @@ func (o *oprf) hashTranscript(input, info, unblinded []byte) []byte { return h } -// String implements the Stringer() interface for the Identifier. -func (i Identifier) String() string { - return string(i) +// String implements the Stringer() interface for the Ciphersuite. +func (c Ciphersuite) String() string { + return string(c) } -func (i Identifier) register(g group.Group, h hash.Hashing) { +func (c Ciphersuite) register(g group.Group, h hash.Hashing) { if g.Available() && h.Available() { - groups[i] = g - hashes[i] = h + groups[c] = g + hashes[c] = h } else { panic(fmt.Sprintf("OPRF dependencies not available - Group: %v, Hash: %v", g.Available(), h.Available())) } @@ -306,4 +309,5 @@ func init() { P256Sha256.register(group.P256Sha256, hash.SHA256) P384Sha384.register(group.P384Sha384, hash.SHA384) P521Sha512.register(group.P521Sha512, hash.SHA512) + Secp256k1.register(group.Secp256k1, hash.SHA256) } diff --git a/server.go b/server.go index 9669ea4..f6d1619 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -180,7 +180,7 @@ func (s *Server) PublicKey() []byte { return s.publicKey.Encode() } -// Identifier returns the cipher suite used in s' instance. -func (s *Server) Identifier() Identifier { +// Ciphersuite returns the cipher suite used in the server's instance. +func (s *Server) Ciphersuite() Ciphersuite { return s.oprf.id } diff --git a/tests/helper_test.go b/tests/helper_test.go new file mode 100644 index 0000000..449bb3d --- /dev/null +++ b/tests/helper_test.go @@ -0,0 +1,214 @@ +// 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 ( + "crypto/elliptic" + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + "log" + "math/big" + "testing" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/voprf" +) + +func init() { + log.SetFlags(log.LstdFlags | log.Lshortfile) +} + +// helper functions + +type configuration struct { + curve elliptic.Curve + ciphersuite voprf.Ciphersuite + name string +} + +var configurationTable = []configuration{ + { + name: "Ristretto255", + ciphersuite: voprf.Ristretto255Sha512, + curve: nil, + }, + { + name: "P256Sha256", + ciphersuite: voprf.P256Sha256, + curve: elliptic.P256(), + }, + { + name: "P384Sha512", + ciphersuite: voprf.P384Sha384, + curve: elliptic.P384(), + }, + { + name: "P521Sha512", + ciphersuite: voprf.P521Sha512, + curve: elliptic.P521(), + }, +} + +func testAll(t *testing.T, f func(*configuration)) { + for _, test := range configurationTable { + t.Run(test.name, func(t *testing.T) { + f(&test) + }) + } +} + +func getBadRistrettoScalar() []byte { + a := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func getBadRistrettoElement() []byte { + a := "2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func badScalar(t *testing.T, g group.Group, curve elliptic.Curve) []byte { + order := curve.Params().P + exceeded := new(big.Int).Add(order, big.NewInt(2)).Bytes() + + err := g.NewScalar().Decode(exceeded) + if err == nil { + t.Errorf("Exceeding order did not yield an error for group %s", g) + } + + return exceeded +} + +func randomBytes(length int) []byte { + r := make([]byte, length) + if _, err := rand.Read(r); err != nil { + // We can as well not panic and try again in a loop and a counter to stop. + panic(fmt.Errorf("unexpected error in generating random bytes : %w", err)) + } + + return r +} + +func getBadNistElement(t *testing.T, id group.Group) []byte { + size := id.ElementLength() + element := randomBytes(size) + // detag compression + element[0] = 4 + + // test if invalid compression is detected + err := id.NewElement().Decode(element) + if err == nil { + t.Errorf("detagged compressed point did not yield an error for group %s", id) + } + + return element +} + +func getBadElement(t *testing.T, c *configuration) []byte { + switch c.ciphersuite { + case voprf.Ristretto255Sha512: + return getBadRistrettoElement() + default: + return getBadNistElement(t, c.ciphersuite.Group()) + } +} + +func getBadScalar(t *testing.T, c *configuration) []byte { + switch c.ciphersuite { + case voprf.Ristretto255Sha512: + return getBadRistrettoScalar() + default: + return badScalar(t, c.ciphersuite.Group(), c.curve) + } +} + +const ( + deriveKeyPairDST = "DeriveKeyPair" + hash2groupDSTPrefix = "HashToGroup-" +) + +func concatenate(input ...[]byte) []byte { + if len(input) == 1 { + if len(input[0]) == 0 { + return nil + } + + return input[0] + } + + length := 0 + for _, in := range input { + length += len(in) + } + + buf := make([]byte, 0, length) + + for _, in := range input { + buf = append(buf, in...) + } + + return buf +} + +func dst(prefix string, contextString []byte) []byte { + p := []byte(prefix) + t := make([]byte, 0, len(p)+len(contextString)) + t = append(t, p...) + t = append(t, contextString...) + + return t +} + +func i2osp2(value int) []byte { + out := make([]byte, 2) + binary.BigEndian.PutUint16(out, uint16(value)) + + return out +} + +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())) + ctx = append(ctx, voprf.Version...) + ctx = append(ctx, "-"...) + ctx = append(ctx, byte(mode)) + ctx = append(ctx, "-"...) + ctx = append(ctx, id.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)) + deriveInput := concatenate(seed, lengthPrefixEncode(info)) + + var counter uint8 + var s *group.Scalar + + for s == nil || s.IsZero() { + if counter > 255 { + panic("impossible to generate non-zero scalar") + } + + s = id.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) + counter++ + } + + return s, id.Group().Base().Multiply(s) +} diff --git a/tests/state_test.go b/tests/state_test.go index ec015bc..d312f30 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 diff --git a/tests/utils_test.go b/tests/utils_test.go index af6ef6e..f09fa0e 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -1,96 +1,9 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 ( - "encoding/binary" - - group "github.com/bytemare/crypto" - - "github.com/bytemare/voprf" -) - -const ( - version = "OPRFV1" - deriveKeyPairDST = "DeriveKeyPair" - hash2groupDSTPrefix = "HashToGroup-" -) - -func concatenate(input ...[]byte) []byte { - if len(input) == 1 { - if len(input[0]) == 0 { - return nil - } - - return input[0] - } - - length := 0 - for _, in := range input { - length += len(in) - } - - buf := make([]byte, 0, length) - - for _, in := range input { - buf = append(buf, in...) - } - - return buf -} - -func dst(prefix string, contextString []byte) []byte { - p := []byte(prefix) - t := make([]byte, 0, len(p)+len(contextString)) - t = append(t, p...) - t = append(t, contextString...) - - return t -} - -func i2osp2(value int) []byte { - out := make([]byte, 2) - binary.BigEndian.PutUint16(out, uint16(value)) - - return out -} - -func lengthPrefixEncode(input []byte) []byte { - return append(i2osp2(len(input)), input...) -} - -func contextString(mode voprf.Mode, id voprf.Identifier) []byte { - ctx := make([]byte, 0, len(version)+3+len(id.String())) - ctx = append(ctx, version...) - ctx = append(ctx, "-"...) - ctx = append(ctx, byte(mode)) - ctx = append(ctx, "-"...) - ctx = append(ctx, id.String()...) - - return ctx -} - -func deriveKeyPair(seed, info []byte, mode voprf.Mode, id voprf.Identifier) (*group.Scalar, *group.Element) { - dst := concatenate([]byte(deriveKeyPairDST), contextString(mode, id)) - deriveInput := concatenate(seed, lengthPrefixEncode(info)) - - var counter uint8 - var s *group.Scalar - - for s == nil || s.IsZero() { - if counter > 255 { - panic("impossible to generate non-zero scalar") - } - - s = id.Group().HashToScalar(concatenate(deriveInput, []byte{counter}), dst) - counter++ - } - - return s, id.Group().Base().Multiply(s) -} diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 97a893a..d77ee6f 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -37,7 +37,7 @@ type test struct { } type testVector struct { - ID voprf.Identifier `json:"proof,omitempty"` + ID voprf.Ciphersuite `json:"proof,omitempty"` EvaluationProof struct { Proof string `json:"proof,omitempty"` Random string `json:"r,omitempty"` @@ -70,7 +70,7 @@ func decodeBatch(nb int, in string) ([][]byte, error) { return out, nil } -func (t *test) Verify(suite voprf.Identifier) error { +func (t *test) Verify(suite voprf.Ciphersuite) error { g := suite.Group() for i, b := range t.Blind { @@ -168,15 +168,15 @@ func (tv *testVector) Decode() (*test, error) { type vectors []vector type vector struct { - DST string `json:"groupDST"` - Hash string `json:"hash"` - KeyInfo string `json:"keyInfo"` - SksSeed string `json:"seed"` - PkSm string `json:"pkSm,omitempty"` - SkSm string `json:"skSm"` - SuiteID voprf.Identifier `json:"identifier"` - TestVectors []testVector `json:"vectors,omitempty"` - Mode voprf.Mode `json:"mode"` + DST string `json:"groupDST"` + Hash string `json:"hash"` + KeyInfo string `json:"keyInfo"` + SksSeed string `json:"seed"` + PkSm string `json:"pkSm,omitempty"` + SkSm string `json:"skSm"` + SuiteID voprf.Ciphersuite `json:"identifier"` + TestVectors []testVector `json:"vectors,omitempty"` + Mode voprf.Mode `json:"mode"` } func hashToHash(h string) hash.Identifier { @@ -226,7 +226,7 @@ func (v vector) checkParams(t *testing.T) { //} } -func testBlind(t *testing.T, id voprf.Identifier, client *voprf.Client, input, blind, expected, info []byte) { +func testBlind(t *testing.T, id voprf.Ciphersuite, client *voprf.Client, input, blind, expected, info []byte) { s := id.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)) @@ -256,7 +256,7 @@ func testBlindBatchWithBlinds(t *testing.T, client *voprf.Client, inputs, blinds func testOPRF( t *testing.T, - id voprf.Identifier, + id voprf.Ciphersuite, mode voprf.Mode, client *voprf.Client, server *voprf.Server, diff --git a/tests/voprf_test.go b/tests/voprf_test.go new file mode 100644 index 0000000..71b044b --- /dev/null +++ b/tests/voprf_test.go @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +// 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 ( + "encoding/hex" + "testing" + + "github.com/bytemare/voprf" +) + +func makeClientAndServer(t *testing.T, mode voprf.Mode, ciphersuite voprf.Ciphersuite) (*voprf.Client, *voprf.Server) { + server, err := ciphersuite.Server(mode, nil) + if err != nil { + t.Fatal(err) + } + + spk := server.PublicKey() + + client, err := ciphersuite.Client(mode, spk) + if err != nil { + t.Fatal(err) + } + + return client, server +} + +func runOPRF(t *testing.T, c *configuration, mode voprf.Mode, input, info []byte) *voprf.Evaluation { + client, server := makeClientAndServer(t, mode, c.ciphersuite) + + blinded := client.Blind(input, info) + + evaluation, err := server.Evaluate(blinded, info) + if err != nil { + t.Fatal(err) + } + + if _, err = client.Finalize(evaluation, info); err != nil { + t.Fatal(err) + } + + return evaluation +} + +func TestOPRF(t *testing.T) { + mode := voprf.OPRF + input := []byte("input") + + testAll(t, func(c *configuration) { + _ = runOPRF(t, c, mode, input, nil) + }) +} + +func TestVOPRF(t *testing.T) { + mode := voprf.VOPRF + input := []byte("input") + + testAll(t, func(c *configuration) { + _ = runOPRF(t, c, mode, input, nil) + }) +} + +func TestPOPRF(t *testing.T) { + mode := voprf.POPRF + info := []byte("info") + input := []byte("input") + + testAll(t, func(c *configuration) { + _ = runOPRF(t, c, mode, input, info) + }) +} + +func TestBatching(t *testing.T) { + mode := voprf.POPRF + info := []byte("info") + inputs := [][]byte{ + []byte("input1"), + []byte("input2"), + []byte("input3"), + } + + testAll(t, func(c *configuration) { + client, server := makeClientAndServer(t, mode, c.ciphersuite) + + _, blinded, err := client.BlindBatch(inputs, info) + if err != nil { + t.Fatal(err) + } + + evaluation, err := server.EvaluateBatch(blinded, info) + if err != nil { + t.Fatal(err) + } + + if _, err = client.FinalizeBatch(evaluation, info); err != nil { + t.Fatal(err) + } + }) +} + +func TestServerKeys(t *testing.T) { + mode := voprf.OPRF + + testAll(t, func(c *configuration) { + server, err := c.ciphersuite.Server(mode, nil) + if err != nil { + t.Fatal(err) + } + + private := c.ciphersuite.Group().NewScalar() + if err = private.Decode(server.PrivateKey()); err != nil { + t.Fatal(err) + } + + public := c.ciphersuite.Group().NewElement() + if err = public.Decode(server.PublicKey()); err != nil { + t.Fatal(err) + } + + pk := c.ciphersuite.Group().Base().Multiply(private) + if pk.Equal(public) != 1 { + t.Fatal("expected equality") + } + }) +} + +func TestDeriveKeyPair(t *testing.T) { + info := []byte("some instance") + ciphersuite := voprf.Ristretto255Sha512 + + random, _ := hex.DecodeString("c332260baab120459e7ad1d47ce5a43f980abe9c19ecc0550bbd0dde58a548bf") + encodedReferenceSecretKeyR255, _ := hex.DecodeString( + "78e4560c5779791f87f6493fff0ac0476d64ebdecb9ae26a0565f673b10be906", + ) + encodedReferencePublicKeyR255, _ := hex.DecodeString( + "7c45e2a6748414358f597874d4afa951cbc39cb3300c5cfde9ac86348062560f", + ) + + refSk := ciphersuite.Group().NewScalar() + _ = refSk.Decode(encodedReferenceSecretKeyR255) + + refPk := ciphersuite.Group().NewElement() + _ = refPk.Decode(encodedReferencePublicKeyR255) + + sk, pk := ciphersuite.DeriveKeyPair(voprf.OPRF, random, info) + + if sk.Equal(refSk) != 1 || pk.Equal(refPk) != 1 { + t.Fatal("expected equality") + } +} diff --git a/utils.go b/utils.go index d0feb40..9b57cf4 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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 @@ -15,7 +15,7 @@ import ( // KeyPair assembles a VOPRF key pair. The SecretKey can be used as the evaluation key for the group identified by ID. type KeyPair struct { - ID Identifier + ID Ciphersuite PublicKey []byte SecretKey []byte } diff --git a/verifiable.go b/verifiable.go index b096a10..29094c3 100644 --- a/verifiable.go +++ b/verifiable.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// 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