diff --git a/keys.go b/keys.go index 4d1b729..623281a 100644 --- a/keys.go +++ b/keys.go @@ -10,6 +10,7 @@ package secretsharing import ( "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -26,8 +27,12 @@ var ( errEncodingInvalidLength = errors.New("invalid encoding length") errEncodingInvalidJSONEncoding = errors.New("invalid JSON encoding") errInvalidPolynomialLength = errors.New("invalid polynomial length (exceeds uint16 limit 65535)") + errPublicKeyShareDecodePrefix = errors.New("failed to decode PublicKeyShare") + errKeyShareDecodePrefix = errors.New("failed to decode KeyShare") ) +const errFmt = "%w: %w" + // The Share interface enables to use functions in this package with compatible key shares. type Share interface { // Identifier returns the identity for this share. @@ -78,13 +83,18 @@ func (p *PublicKeyShare) Encode() []byte { return out } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (p *PublicKeyShare) Hex() string { + return hex.EncodeToString(p.Encode()) +} + func (p *PublicKeyShare) decode(g group.Group, cLen int, data []byte) error { eLen := g.ElementLength() id := binary.LittleEndian.Uint16(data[1:3]) pk := g.NewElement() if err := pk.Decode(data[7 : 7+eLen]); err != nil { - return fmt.Errorf("failed to decode public key: %w", err) + return fmt.Errorf("%w: failed to decode public key: %w", errPublicKeyShareDecodePrefix, err) } i := 0 @@ -93,7 +103,7 @@ func (p *PublicKeyShare) decode(g group.Group, cLen int, data []byte) error { for j := 7 + eLen; j < len(data); j += eLen { c := g.NewElement() if err := c.Decode(data[j : j+eLen]); err != nil { - return fmt.Errorf("failed to decode commitment %d: %w", i+1, err) + return fmt.Errorf("%w: failed to decode commitment %d: %w", errPublicKeyShareDecodePrefix, i+1, err) } commitment[i] = c @@ -112,21 +122,31 @@ func (p *PublicKeyShare) decode(g group.Group, cLen int, data []byte) error { func (p *PublicKeyShare) Decode(data []byte) error { g, expectedLength, cLen, err := decodeKeyShareHeader(data) if err != nil { - return err + return fmt.Errorf(errFmt, errPublicKeyShareDecodePrefix, err) } if len(data) != expectedLength { - return errEncodingInvalidLength + return fmt.Errorf(errFmt, errPublicKeyShareDecodePrefix, errEncodingInvalidLength) } return p.decode(g, cLen, data) } +// DecodeHex sets p to the decoding of the hex encoded representation returned by Hex(). +func (p *PublicKeyShare) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errPublicKeyShareDecodePrefix, err) + } + + return p.Decode(b) +} + // UnmarshalJSON decodes data into p, or returns an error. func (p *PublicKeyShare) UnmarshalJSON(data []byte) error { ps := new(publicKeyShareShadow) if err := unmarshalJSON(data, ps); err != nil { - return err + return fmt.Errorf(errFmt, errPublicKeyShareDecodePrefix, err) } *p = PublicKeyShare(*ps) @@ -168,31 +188,36 @@ func (k *KeyShare) Encode() []byte { return out } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (k *KeyShare) Hex() string { + return hex.EncodeToString(k.Encode()) +} + // Decode deserializes the compact encoding obtained from Encode(), or returns an error. func (k *KeyShare) Decode(data []byte) error { - g, pkLen, cLen, _err := decodeKeyShareHeader(data) - if _err != nil { - return _err + g, pkLen, cLen, err := decodeKeyShareHeader(data) + if err != nil { + return fmt.Errorf(errFmt, errKeyShareDecodePrefix, err) } expectedLength := pkLen + g.ScalarLength() + g.ElementLength() if len(data) != expectedLength { - return errEncodingInvalidLength + return fmt.Errorf(errFmt, errKeyShareDecodePrefix, errEncodingInvalidLength) } pk := new(PublicKeyShare) - if err := pk.decode(g, cLen, data[:pkLen]); err != nil { - return err + if err = pk.decode(g, cLen, data[:pkLen]); err != nil { + return fmt.Errorf(errFmt, errKeyShareDecodePrefix, err) } s := g.NewScalar() - if err := s.Decode(data[pkLen : pkLen+g.ScalarLength()]); err != nil { - return fmt.Errorf("failed to decode Secret in KeyShare: %w", err) + if err = s.Decode(data[pkLen : pkLen+g.ScalarLength()]); err != nil { + return fmt.Errorf("%w: failed to decode secret key: %w", errKeyShareDecodePrefix, err) } e := g.NewElement() - if err := e.Decode(data[pkLen+g.ScalarLength():]); err != nil { - return fmt.Errorf("failed to decode GroupPublicKey in KeyShare: %w", err) + if err = e.Decode(data[pkLen+g.ScalarLength():]); err != nil { + return fmt.Errorf("%w: failed to decode GroupPublicKey: %w", errKeyShareDecodePrefix, err) } k.populate(s, e, pk) @@ -200,6 +225,16 @@ func (k *KeyShare) Decode(data []byte) error { return nil } +// DecodeHex sets k to the decoding of the hex encoded representation returned by Hex(). +func (k *KeyShare) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errKeyShareDecodePrefix, err) + } + + return k.Decode(b) +} + func (k *KeyShare) populate(s *group.Scalar, gpk *group.Element, pks *PublicKeyShare) { k.Secret = s k.GroupPublicKey = gpk @@ -210,7 +245,7 @@ func (k *KeyShare) populate(s *group.Scalar, gpk *group.Element, pks *PublicKeyS func (k *KeyShare) UnmarshalJSON(data []byte) error { ks := new(keyShareShadow) if err := unmarshalJSON(data, ks); err != nil { - return err + return fmt.Errorf(errFmt, errKeyShareDecodePrefix, err) } k.populate(ks.Secret, ks.GroupPublicKey, (*PublicKeyShare)(ks.publicKeyShareShadow)) diff --git a/tests/ss_test.go b/tests/ss_test.go index 6c2a6aa..ceaf87a 100644 --- a/tests/ss_test.go +++ b/tests/ss_test.go @@ -704,7 +704,7 @@ func compareKeyShares(s1, s2 *secretsharing.KeyShare) error { return comparePublicKeyShare(&s1.PublicKeyShare, &s2.PublicKeyShare) } -func TestEncoding(t *testing.T) { +func TestEncoding_Bytes(t *testing.T) { threshold := uint16(3) // threshold is the minimum amount of necessary shares to recombine the secret max := uint16(7) // the max amount of key share-holders @@ -746,7 +746,49 @@ func TestEncoding(t *testing.T) { } } -func TestJSONEncoding(t *testing.T) { +func TestEncoding_Hex(t *testing.T) { + threshold := uint16(3) // threshold is the minimum amount of necessary shares to recombine the secret + max := uint16(7) // the max amount of key share-holders + + for _, g := range groups { + t.Run(g.String(), func(t *testing.T) { + // This is the global secret to be shared + secret := g.NewScalar().Random() + + // Shard the secret into shares + shares, err := secretsharing.ShardAndCommit(g, secret, threshold, max) + if err != nil { + t.Fatal(err) + } + + // PublicKeyShare + h := shares[0].Public().Hex() + + decodedPKS := new(secretsharing.PublicKeyShare) + if err = decodedPKS.DecodeHex(h); err != nil { + t.Fatal(err) + } + + if err = comparePublicKeyShare(&shares[0].PublicKeyShare, decodedPKS); err != nil { + t.Fatal(err) + } + + // KeyShare + h = shares[0].Hex() + + decodedKS := &secretsharing.KeyShare{} + if err = decodedKS.DecodeHex(h); err != nil { + t.Fatal(err) + } + + if err = compareKeyShares(shares[0], decodedKS); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestEncoding_JSON(t *testing.T) { threshold := uint16(3) // threshold is the minimum amount of necessary shares to recombine the secret max := uint16(7) // the max amount of key share-holders @@ -796,6 +838,7 @@ func TestJSONEncoding(t *testing.T) { type serde interface { Decode([]byte) error + DecodeHex(h string) error UnmarshalJSON(data []byte) error } @@ -812,6 +855,12 @@ func testDecodeErrorPrefix(t *testing.T, s serde, data []byte, expectedPrefix er } } +func testDecodeHexError(t *testing.T, s serde, data string, expectedError error) { + if err := s.DecodeHex(data); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected error %q, got %q", expectedError, err) + } +} + func testUnmarshalJSONError(t *testing.T, s serde, data []byte, expectedError error) { if err := json.Unmarshal(data, s); err == nil || err.Error() != expectedError.Error() { t.Fatalf("expected error %q, got %q", expectedError, err) @@ -883,9 +932,9 @@ func TestEncoding_PublicKeyShare_Bad(t *testing.T) { threshold := uint16(3) max := uint16(4) - errEncodingInvalidLength := errors.New("invalid encoding length") - errEncodingInvalidGroup := errors.New("invalid group identifier") - errEncodingInvalidJSONEncoding := errors.New("invalid JSON encoding") + errEncodingInvalidLength := errors.New("failed to decode PublicKeyShare: invalid encoding length") + errEncodingInvalidGroup := errors.New("failed to decode PublicKeyShare: invalid group identifier") + errEncodingInvalidJSONEncoding := errors.New("failed to decode PublicKeyShare: invalid JSON encoding") for _, g := range groups { t.Run(g.String(), func(t *testing.T) { @@ -917,16 +966,21 @@ func TestEncoding_PublicKeyShare_Bad(t *testing.T) { // Decode: Bad public key encoded = slices.Replace(encoded, 7, 7+g.ElementLength(), badElement...) - expectedErrorPrefix := errors.New("failed to decode public key") + expectedErrorPrefix := errors.New("failed to decode PublicKeyShare: failed to decode public key") testDecodeErrorPrefix(t, new(secretsharing.PublicKeyShare), encoded, expectedErrorPrefix) // Decode: bad commitment encoded = shares[0].Public().Encode() offset := 7 + 2*g.ElementLength() encoded = slices.Replace(encoded, offset, offset+g.ElementLength(), badElement...) - expectedErrorPrefix = errors.New("failed to decode commitment 2") + expectedErrorPrefix = errors.New("failed to decode PublicKeyShare: failed to decode commitment 2") testDecodeErrorPrefix(t, new(secretsharing.PublicKeyShare), encoded, expectedErrorPrefix) + // Bad Hex + h := shares[0].Public().Hex() + expectedErrorPrefix = errors.New("failed to decode PublicKeyShare: encoding/hex: odd length hex string") + testDecodeHexError(t, new(secretsharing.PublicKeyShare), h[:len(h)-1], expectedErrorPrefix) + // UnmarshallJSON: bad json data, err := json.Marshal(shares[0]) if err != nil { @@ -974,7 +1028,7 @@ func TestEncoding_PublicKeyShare_Bad(t *testing.T) { data = replaceStringInBytes(data, fmt.Sprintf("\"group\":%d", g), "\"group\":"+overflow) expectedErrorPrefix = errors.New( - "failed to read Group: strconv.Atoi: parsing \"9223372036854775808\": value out of range", + "failed to decode PublicKeyShare: failed to read Group: strconv.Atoi: parsing \"9223372036854775808\": value out of range", ) testUnmarshalJSONErrorPrefix(t, new(secretsharing.PublicKeyShare), data, expectedErrorPrefix) @@ -1026,7 +1080,9 @@ func TestEncoding_PublicKeyShare_Bad(t *testing.T) { t.Fatal(err) } - errInvalidPolynomialLength := errors.New("invalid polynomial length (exceeds uint16 limit 65535)") + errInvalidPolynomialLength := errors.New( + "failed to decode PublicKeyShare: invalid polynomial length (exceeds uint16 limit 65535)", + ) testUnmarshalJSONError(t, new(secretsharing.PublicKeyShare), data, errInvalidPolynomialLength) }) } @@ -1036,9 +1092,9 @@ func TestEncoding_KeyShare_Bad(t *testing.T) { threshold := uint16(1) max := uint16(2) - errEncodingInvalidLength := errors.New("invalid encoding length") - errEncodingInvalidGroup := errors.New("invalid group identifier") - errEncodingInvalidJSONEncoding := errors.New("invalid JSON encoding") + errEncodingInvalidLength := errors.New("failed to decode KeyShare: invalid encoding length") + errEncodingInvalidGroup := errors.New("failed to decode KeyShare: invalid group identifier") + errEncodingInvalidJSONEncoding := errors.New("failed to decode KeyShare: invalid JSON encoding") for _, g := range groups { t.Run(g.String(), func(t *testing.T) { @@ -1074,14 +1130,16 @@ func TestEncoding_KeyShare_Bad(t *testing.T) { encoded = shares[0].Encode() encoded = slices.Replace(encoded, offset, offset+g.ElementLength(), badElement...) - expectedErrorPrefix := errors.New("failed to decode public key: ") + expectedErrorPrefix := errors.New( + "failed to decode KeyShare: failed to decode PublicKeyShare: failed to decode public key: element Decode: ", + ) testDecodeErrorPrefix(t, new(secretsharing.KeyShare), encoded, expectedErrorPrefix) // Decode: Bad scalar offset += g.ElementLength() + len(shares[0].Commitment)*g.ElementLength() encoded = shares[0].Encode() encoded = slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar...) - expectedErrorPrefix = errors.New("failed to decode Secret in KeyShare") + expectedErrorPrefix = errors.New("failed to decode KeyShare: failed to decode secret key: scalar Decode: ") testDecodeErrorPrefix(t, new(secretsharing.KeyShare), encoded, expectedErrorPrefix) @@ -1089,10 +1147,17 @@ func TestEncoding_KeyShare_Bad(t *testing.T) { offset += g.ScalarLength() encoded = shares[0].Encode() encoded = slices.Replace(encoded, offset, offset+g.ElementLength(), badElement...) - expectedErrorPrefix = errors.New("failed to decode GroupPublicKey in KeyShare") + expectedErrorPrefix = errors.New( + "failed to decode KeyShare: failed to decode GroupPublicKey: element Decode: ", + ) testDecodeErrorPrefix(t, new(secretsharing.KeyShare), encoded, expectedErrorPrefix) + // Bad Hex + h := shares[0].Hex() + expectedErrorPrefix = errors.New("failed to decode KeyShare: encoding/hex: odd length hex string") + testDecodeHexError(t, new(secretsharing.KeyShare), h[:len(h)-1], expectedErrorPrefix) + // UnmarshallJSON: bad json data, err := json.Marshal(shares[0]) if err != nil {