From 74144f207aa1eaf88f1e55286a7e4264c2090d09 Mon Sep 17 00:00:00 2001
From: Leonard Lyubich <leonard@nspcc.ru>
Date: Tue, 5 May 2020 13:16:21 +0300
Subject: [PATCH] service: implement functions for verification of signatures

---
 service/errors.go    |   8 ++-
 service/sign.go      | 103 ++++++++++++++++++++++++++--
 service/sign_test.go | 160 +++++++++++++++++++++++++++++++++++++------
 service/types.go     |  20 +++++-
 4 files changed, 264 insertions(+), 27 deletions(-)

diff --git a/service/errors.go b/service/errors.go
index 1154a281..6b0ed24d 100644
--- a/service/errors.go
+++ b/service/errors.go
@@ -17,5 +17,11 @@ const ErrCannotFindOwner = internal.Error("cannot find owner public key")
 // ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey
 const ErrWrongOwner = internal.Error("wrong owner")
 
-// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource argument, but received nil.
+// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource, but received nil.
 const ErrNilSignedDataSource = internal.Error("signed data source is nil")
+
+// ErrNilSignatureKeySource is returned by functions that expect a non-nil SignatureKeySource, but received nil.
+const ErrNilSignatureKeySource = internal.Error("empty key-signature source")
+
+// ErrEmptyDataWithSignature is returned by functions that expect a non-nil DataWithSignature, but received nil.
+const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
diff --git a/service/sign.go b/service/sign.go
index 654c4a9c..9a6eba99 100644
--- a/service/sign.go
+++ b/service/sign.go
@@ -6,12 +6,39 @@ import (
 	crypto "github.com/nspcc-dev/neofs-crypto"
 )
 
+type keySign struct {
+	key  *ecdsa.PublicKey
+	sign []byte
+}
+
+// GetSignature is a sign field getter.
+func (s keySign) GetSignature() []byte {
+	return s.sign
+}
+
+// GetPublicKey is a key field getter,
+func (s keySign) GetPublicKey() *ecdsa.PublicKey {
+	return s.key
+}
+
+// Unites passed key with signature and returns SignKeyPair interface.
+func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair {
+	return &keySign{
+		key:  key,
+		sign: sign,
+	}
+}
+
 // Returns data from DataSignatureAccumulator for signature creation/verification.
 //
 // If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained
 // using this interface for optimization. In this case, it is understood that reading into the slice D
 // that the method DataForSignature returns does not change D.
 func dataForSignature(src SignedDataSource) ([]byte, error) {
+	if src == nil {
+		return nil, ErrNilSignedDataSource
+	}
+
 	r, ok := src.(SignedDataReader)
 	if !ok {
 		return src.SignedData()
@@ -42,10 +69,8 @@ func dataForSignature(src SignedDataSource) ([]byte, error) {
 // If passed data container is nil, ErrNilSignedDataSource returns.
 // If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
 // If the data container or the signature function returns an error, it is returned directly.
-func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) {
-	if src == nil {
-		return nil, ErrNilSignedDataSource
-	} else if key == nil {
+func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) {
+	if key == nil {
 		return nil, crypto.ErrEmptyPrivateKey
 	}
 
@@ -61,7 +86,7 @@ func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error)
 //
 // Returns signing errors only.
 func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error {
-	sign, err := DataSignature(v, key)
+	sign, err := DataSignature(key, v)
 	if err != nil {
 		return err
 	}
@@ -70,3 +95,71 @@ func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error
 
 	return nil
 }
+
+// Checks passed key-signature pairs for data from the passed container.
+//
+// If passed key-signatures pair set is empty, nil returns immediately.
+func verifySignatures(src SignedDataSource, items ...SignKeyPair) error {
+	if len(items) <= 0 {
+		return nil
+	}
+
+	data, err := dataForSignature(src)
+	if err != nil {
+		return err
+	}
+
+	for _, signKey := range items {
+		if err := crypto.Verify(
+			signKey.GetPublicKey(),
+			data,
+			signKey.GetSignature(),
+		); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// VerifySignatures checks passed key-signature pairs for data from the passed container.
+//
+// If passed data source is nil, ErrNilSignedDataSource returns.
+// If check data is not ready, corresponding error returns.
+// If at least one of the pairs is invalid, an error returns.
+func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error {
+	return verifySignatures(src, items...)
+}
+
+// VerifyAccumulatedSignatures checks if accumulated key-signature pairs are valid.
+//
+// Behaves like VerifySignatures.
+// If passed key-signature source is empty, ErrNilSignatureKeySource returns.
+func VerifyAccumulatedSignatures(src SignatureKeySource) error {
+	if src == nil {
+		return ErrNilSignatureKeySource
+	}
+
+	return verifySignatures(src, src.GetSignKeyPairs()...)
+}
+
+// VerifySignatureWithKey checks data signature from the passed container with passed key.
+//
+// If passed data with signature is nil, ErrEmptyDataWithSignature returns.
+// If passed key is nil, crypto.ErrEmptyPublicKey returns.
+// A non-nil error returns if and only if the signature does not pass verification.
+func VerifySignatureWithKey(src DataWithSignature, key *ecdsa.PublicKey) error {
+	if src == nil {
+		return ErrEmptyDataWithSignature
+	} else if key == nil {
+		return crypto.ErrEmptyPublicKey
+	}
+
+	return verifySignatures(
+		src,
+		newSignatureKeyPair(
+			key,
+			src.GetSignature(),
+		),
+	)
+}
diff --git a/service/sign_test.go b/service/sign_test.go
index 5ac7c6c4..8fba9689 100644
--- a/service/sign_test.go
+++ b/service/sign_test.go
@@ -25,18 +25,28 @@ type testSignedDataReader struct {
 }
 
 type testKeySigAccum struct {
-	d []byte
-	f func([]byte, *ecdsa.PublicKey)
+	data []byte
+	sig  []byte
+	key  *ecdsa.PublicKey
+}
+
+func (s testKeySigAccum) GetSignature() []byte {
+	return s.sig
+}
+
+func (s testKeySigAccum) GetSignKeyPairs() []SignKeyPair {
+	return []SignKeyPair{
+		newSignatureKeyPair(s.key, s.sig),
+	}
 }
 
 func (s testKeySigAccum) SignedData() ([]byte, error) {
-	return s.d, nil
+	return s.data, nil
 }
 
 func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
-	if s.f != nil {
-		s.f(sig, key)
-	}
+	s.key = key
+	s.sig = sig
 }
 
 func testData(t *testing.T, sz int) []byte {
@@ -69,17 +79,17 @@ func (s testSignedDataSrc) SignedData() ([]byte, error) {
 func TestDataSignature(t *testing.T) {
 	var err error
 
-	// nil data source
-	_, err = DataSignature(nil, nil)
-	require.EqualError(t, err, ErrNilSignedDataSource.Error())
-
 	// nil private key
-	_, err = DataSignature(new(testSignedDataSrc), nil)
+	_, err = DataSignature(nil, nil)
 	require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error())
 
 	// create test private key
 	sk := test.DecodeKey(0)
 
+	// nil private key
+	_, err = DataSignature(sk, nil)
+	require.EqualError(t, err, ErrNilSignedDataSource.Error())
+
 	t.Run("common signed data source", func(t *testing.T) {
 		// create test data source
 		src := &testSignedDataSrc{
@@ -89,14 +99,14 @@ func TestDataSignature(t *testing.T) {
 		// create custom error for data source
 		src.e = errors.New("test error for data source")
 
-		_, err = DataSignature(src, sk)
+		_, err = DataSignature(sk, src)
 		require.EqualError(t, err, src.e.Error())
 
 		// reset error to nil
 		src.e = nil
 
 		// calculate data signature
-		sig, err := DataSignature(src, sk)
+		sig, err := DataSignature(sk, src)
 		require.NoError(t, err)
 
 		// ascertain that the signature passes verification
@@ -112,14 +122,14 @@ func TestDataSignature(t *testing.T) {
 		// create custom error for signed data reader
 		src.e = errors.New("test error for signed data reader")
 
-		sig, err := DataSignature(src, sk)
+		sig, err := DataSignature(sk, src)
 		require.EqualError(t, err, src.e.Error())
 
 		// reset error to nil
 		src.e = nil
 
 		// calculate data signature
-		sig, err = DataSignature(src, sk)
+		sig, err = DataSignature(sk, src)
 		require.NoError(t, err)
 
 		// ascertain that the signature passes verification
@@ -136,12 +146,122 @@ func TestAddSignatureWithKey(t *testing.T) {
 
 	// create test signature accumulator
 	var s SignatureKeyAccumulator = &testKeySigAccum{
-		d: data,
-		f: func(sig []byte, key *ecdsa.PublicKey) {
-			require.Equal(t, &sk.PublicKey, key)
-			require.NoError(t, crypto.Verify(key, data, sig))
-		},
+		data: data,
 	}
 
 	require.NoError(t, AddSignatureWithKey(s, sk))
 }
+
+func TestVerifySignatures(t *testing.T) {
+	// empty signatures
+	require.NoError(t, VerifySignatures(nil))
+
+	// create test signature source
+	src := &testSignedDataSrc{
+		d: testData(t, 10),
+	}
+
+	// create private key for test
+	sk := test.DecodeKey(0)
+
+	// calculate a signature of the data
+	sig, err := crypto.Sign(sk, src.d)
+	require.NoError(t, err)
+
+	// ascertain that verification is passed
+	require.NoError(t,
+		VerifySignatures(
+			src,
+			newSignatureKeyPair(&sk.PublicKey, sig),
+		),
+	)
+
+	// break the signature
+	sig[0]++
+
+	require.Error(t,
+		VerifySignatures(
+			src,
+			newSignatureKeyPair(&sk.PublicKey, sig),
+		),
+	)
+
+	// restore the signature
+	sig[0]--
+
+	// empty data source
+	require.EqualError(t,
+		VerifySignatures(nil, nil),
+		ErrNilSignedDataSource.Error(),
+	)
+
+}
+
+func TestVerifyAccumulatedSignatures(t *testing.T) {
+	// nil signature source
+	require.EqualError(t,
+		VerifyAccumulatedSignatures(nil),
+		ErrNilSignatureKeySource.Error(),
+	)
+
+	// create test private key
+	sk := test.DecodeKey(0)
+
+	// create signature source
+	src := &testKeySigAccum{
+		data: testData(t, 10),
+		key:  &sk.PublicKey,
+	}
+
+	var err error
+
+	// calculate a signature
+	src.sig, err = crypto.Sign(sk, src.data)
+	require.NoError(t, err)
+
+	// ascertain that verification is passed
+	require.NoError(t, VerifyAccumulatedSignatures(src))
+
+	// break the signature
+	src.sig[0]++
+
+	// ascertain that verification is failed
+	require.Error(t, VerifyAccumulatedSignatures(src))
+}
+
+func TestVerifySignatureWithKey(t *testing.T) {
+	// nil signature source
+	require.EqualError(t,
+		VerifySignatureWithKey(nil, nil),
+		ErrEmptyDataWithSignature.Error(),
+	)
+
+	// create test signature source
+	src := &testKeySigAccum{
+		data: testData(t, 10),
+	}
+
+	// nil public key
+	require.EqualError(t,
+		VerifySignatureWithKey(src, nil),
+		crypto.ErrEmptyPublicKey.Error(),
+	)
+
+	// create test private key
+	sk := test.DecodeKey(0)
+
+	var err error
+
+	// calculate a signature
+	src.sig, err = crypto.Sign(sk, src.data)
+	require.NoError(t, err)
+
+	// ascertain that verification is passed
+	require.NoError(t, VerifySignatureWithKey(src, &sk.PublicKey))
+
+	// break the signature
+	src.sig[0]++
+
+	// ascertain that verification is failed
+	require.Error(t, VerifySignatureWithKey(src, &sk.PublicKey))
+}
diff --git a/service/types.go b/service/types.go
index 80a4a493..3bf8c3a2 100644
--- a/service/types.go
+++ b/service/types.go
@@ -192,8 +192,26 @@ type SignedDataReader interface {
 	ReadSignedData([]byte) (int, error)
 }
 
-// SignatureKeyAccumulator is an interface of the accumulator of data signatures with keys.
+// SignatureKeyAccumulator is an interface of the container of a data and signatures.
 type SignatureKeyAccumulator interface {
 	SignedDataSource
 	AddSignKey([]byte, *ecdsa.PublicKey)
 }
+
+// SignKeyPair is an interface of key-signature pair with read access.
+type SignKeyPair interface {
+	SignatureSource
+	GetPublicKey() *ecdsa.PublicKey
+}
+
+// SignatureKeyAccumulator is an interface of the container of a data and signatures with read access.
+type SignatureKeySource interface {
+	SignedDataSource
+	GetSignKeyPairs() []SignKeyPair
+}
+
+// DataWithSignature is an interface of data-signature pair with read access.
+type DataWithSignature interface {
+	SignedDataSource
+	SignatureSource
+}