Commit 2ab730b0 authored by Angelo De Caro's avatar Angelo De Caro
Browse files

[FAB-9838] Idemix Signer/Verifier



This change-set does the following:
- implement the signer/verifier
- tests

Change-Id: I89c61314a3ce2cb997c696f81a96522500eae9db
Signed-off-by: default avatarAngelo De Caro <adc@zurich.ibm.com>
parent 32ead688
......@@ -100,3 +100,39 @@ type Revocation interface {
// is used in this epoch.
Verify(pk *ecdsa.PublicKey, cri []byte, epoch int, alg bccsp.RevocationAlgorithm) error
}
// SignatureScheme is a local interface to decouple from the idemix implementation
// the sign-related operations
type SignatureScheme interface {
// Sign creates a new idemix signature (Schnorr-type signature).
// The attributes slice steers which attributes are disclosed:
// If attributes[i].Type == bccsp.IdemixHiddenAttribute then attribute i remains hidden and otherwise it is disclosed.
// We require the revocation handle to remain undisclosed (i.e., attributes[rhIndex] == bccsp.IdemixHiddenAttribute).
// Parameters are to be understood as follow:
// cred: the serialized version of an idemix credential;
// sk: the user secret key;
// (Nym, RNym): Nym key-pair;
// ipk: issuer public key;
// attributes: as described above;
// msg: the message to be signed;
// rhIndex: revocation handle index relative to attributes;
// cri: the serialized version of the Credential Revocation Information (it contains the epoch this signature
// is created in reference to).
Sign(cred []byte, sk Big, Nym Ecp, RNym Big, ipk IssuerPublicKey, attributes []bccsp.IdemixAttribute,
msg []byte, rhIndex int, cri []byte) ([]byte, error)
// Verify verifies an idemix signature.
// The attribute slice steers which attributes it expects to be disclosed
// If attributes[i].Type == bccsp.IdemixHiddenAttribute then attribute i remains hidden and otherwise
// attributes[i].Value is expected to contain the disclosed attribute value.
// In other words, this function will check that if attribute i is disclosed, the i-th attribute equals attributes[i].Value.
// Parameters are to be understood as follow:
// ipk: issuer public key;
// signature: signature to verify;
// msg: message signed;
// attributes: as described above;
// rhIndex: revocation handle index relative to attributes;
// revocationPublicKey: revocation public key;
// epoch: revocation epoch.
Verify(ipk IssuerPublicKey, signature, msg []byte, attributes []bccsp.IdemixAttribute, rhIndex int, revocationPublicKey *ecdsa.PublicKey, epoch int) error
}
......@@ -21,6 +21,7 @@ import (
//go:generate counterfeiter -o mock/credrequest.go -fake-name CredRequest . CredRequest
//go:generate counterfeiter -o mock/credential.go -fake-name Credential . Credential
//go:generate counterfeiter -o mock/revocation.go -fake-name Revocation . Revocation
//go:generate counterfeiter -o mock/signature_scheme.go -fake-name SignatureScheme . SignatureScheme
func TestPlain(t *testing.T) {
RegisterFailHandler(Fail)
......
// Code generated by counterfeiter. DO NOT EDIT.
package mock
import (
"crypto/ecdsa"
"sync"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/idemix"
)
type SignatureScheme struct {
SignStub func(cred []byte, sk idemix.Big, Nym idemix.Ecp, RNym idemix.Big, ipk idemix.IssuerPublicKey, attributes []bccsp.IdemixAttribute, msg []byte, rhIndex int, cri []byte) ([]byte, error)
signMutex sync.RWMutex
signArgsForCall []struct {
cred []byte
sk idemix.Big
Nym idemix.Ecp
RNym idemix.Big
ipk idemix.IssuerPublicKey
attributes []bccsp.IdemixAttribute
msg []byte
rhIndex int
cri []byte
}
signReturns struct {
result1 []byte
result2 error
}
signReturnsOnCall map[int]struct {
result1 []byte
result2 error
}
VerifyStub func(pk idemix.IssuerPublicKey, signature, digest []byte, attributes []bccsp.IdemixAttribute, hIndex int, revocationPublicKey *ecdsa.PublicKey, epoch int) error
verifyMutex sync.RWMutex
verifyArgsForCall []struct {
pk idemix.IssuerPublicKey
signature []byte
digest []byte
attributes []bccsp.IdemixAttribute
hIndex int
revocationPublicKey *ecdsa.PublicKey
epoch int
}
verifyReturns struct {
result1 error
}
verifyReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *SignatureScheme) Sign(cred []byte, sk idemix.Big, Nym idemix.Ecp, RNym idemix.Big, ipk idemix.IssuerPublicKey, attributes []bccsp.IdemixAttribute, msg []byte, rhIndex int, cri []byte) ([]byte, error) {
var credCopy []byte
if cred != nil {
credCopy = make([]byte, len(cred))
copy(credCopy, cred)
}
var attributesCopy []bccsp.IdemixAttribute
if attributes != nil {
attributesCopy = make([]bccsp.IdemixAttribute, len(attributes))
copy(attributesCopy, attributes)
}
var msgCopy []byte
if msg != nil {
msgCopy = make([]byte, len(msg))
copy(msgCopy, msg)
}
var criCopy []byte
if cri != nil {
criCopy = make([]byte, len(cri))
copy(criCopy, cri)
}
fake.signMutex.Lock()
ret, specificReturn := fake.signReturnsOnCall[len(fake.signArgsForCall)]
fake.signArgsForCall = append(fake.signArgsForCall, struct {
cred []byte
sk idemix.Big
Nym idemix.Ecp
RNym idemix.Big
ipk idemix.IssuerPublicKey
attributes []bccsp.IdemixAttribute
msg []byte
rhIndex int
cri []byte
}{credCopy, sk, Nym, RNym, ipk, attributesCopy, msgCopy, rhIndex, criCopy})
fake.recordInvocation("Sign", []interface{}{credCopy, sk, Nym, RNym, ipk, attributesCopy, msgCopy, rhIndex, criCopy})
fake.signMutex.Unlock()
if fake.SignStub != nil {
return fake.SignStub(cred, sk, Nym, RNym, ipk, attributes, msg, rhIndex, cri)
}
if specificReturn {
return ret.result1, ret.result2
}
return fake.signReturns.result1, fake.signReturns.result2
}
func (fake *SignatureScheme) SignCallCount() int {
fake.signMutex.RLock()
defer fake.signMutex.RUnlock()
return len(fake.signArgsForCall)
}
func (fake *SignatureScheme) SignArgsForCall(i int) ([]byte, idemix.Big, idemix.Ecp, idemix.Big, idemix.IssuerPublicKey, []bccsp.IdemixAttribute, []byte, int, []byte) {
fake.signMutex.RLock()
defer fake.signMutex.RUnlock()
return fake.signArgsForCall[i].cred, fake.signArgsForCall[i].sk, fake.signArgsForCall[i].Nym, fake.signArgsForCall[i].RNym, fake.signArgsForCall[i].ipk, fake.signArgsForCall[i].attributes, fake.signArgsForCall[i].msg, fake.signArgsForCall[i].rhIndex, fake.signArgsForCall[i].cri
}
func (fake *SignatureScheme) SignReturns(result1 []byte, result2 error) {
fake.SignStub = nil
fake.signReturns = struct {
result1 []byte
result2 error
}{result1, result2}
}
func (fake *SignatureScheme) SignReturnsOnCall(i int, result1 []byte, result2 error) {
fake.SignStub = nil
if fake.signReturnsOnCall == nil {
fake.signReturnsOnCall = make(map[int]struct {
result1 []byte
result2 error
})
}
fake.signReturnsOnCall[i] = struct {
result1 []byte
result2 error
}{result1, result2}
}
func (fake *SignatureScheme) Verify(pk idemix.IssuerPublicKey, signature []byte, digest []byte, attributes []bccsp.IdemixAttribute, hIndex int, revocationPublicKey *ecdsa.PublicKey, epoch int) error {
var signatureCopy []byte
if signature != nil {
signatureCopy = make([]byte, len(signature))
copy(signatureCopy, signature)
}
var digestCopy []byte
if digest != nil {
digestCopy = make([]byte, len(digest))
copy(digestCopy, digest)
}
var attributesCopy []bccsp.IdemixAttribute
if attributes != nil {
attributesCopy = make([]bccsp.IdemixAttribute, len(attributes))
copy(attributesCopy, attributes)
}
fake.verifyMutex.Lock()
ret, specificReturn := fake.verifyReturnsOnCall[len(fake.verifyArgsForCall)]
fake.verifyArgsForCall = append(fake.verifyArgsForCall, struct {
pk idemix.IssuerPublicKey
signature []byte
digest []byte
attributes []bccsp.IdemixAttribute
hIndex int
revocationPublicKey *ecdsa.PublicKey
epoch int
}{pk, signatureCopy, digestCopy, attributesCopy, hIndex, revocationPublicKey, epoch})
fake.recordInvocation("Verify", []interface{}{pk, signatureCopy, digestCopy, attributesCopy, hIndex, revocationPublicKey, epoch})
fake.verifyMutex.Unlock()
if fake.VerifyStub != nil {
return fake.VerifyStub(pk, signature, digest, attributes, hIndex, revocationPublicKey, epoch)
}
if specificReturn {
return ret.result1
}
return fake.verifyReturns.result1
}
func (fake *SignatureScheme) VerifyCallCount() int {
fake.verifyMutex.RLock()
defer fake.verifyMutex.RUnlock()
return len(fake.verifyArgsForCall)
}
func (fake *SignatureScheme) VerifyArgsForCall(i int) (idemix.IssuerPublicKey, []byte, []byte, []bccsp.IdemixAttribute, int, *ecdsa.PublicKey, int) {
fake.verifyMutex.RLock()
defer fake.verifyMutex.RUnlock()
return fake.verifyArgsForCall[i].pk, fake.verifyArgsForCall[i].signature, fake.verifyArgsForCall[i].digest, fake.verifyArgsForCall[i].attributes, fake.verifyArgsForCall[i].hIndex, fake.verifyArgsForCall[i].revocationPublicKey, fake.verifyArgsForCall[i].epoch
}
func (fake *SignatureScheme) VerifyReturns(result1 error) {
fake.VerifyStub = nil
fake.verifyReturns = struct {
result1 error
}{result1}
}
func (fake *SignatureScheme) VerifyReturnsOnCall(i int, result1 error) {
fake.VerifyStub = nil
if fake.verifyReturnsOnCall == nil {
fake.verifyReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.verifyReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *SignatureScheme) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.signMutex.RLock()
defer fake.signMutex.RUnlock()
fake.verifyMutex.RLock()
defer fake.verifyMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *SignatureScheme) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ idemix.SignatureScheme = new(SignatureScheme)
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package idemix
import (
"github.com/hyperledger/fabric/bccsp"
"github.com/pkg/errors"
)
type Signer struct {
SignatureScheme SignatureScheme
}
func (s *Signer) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) ([]byte, error) {
userSecretKey, ok := k.(*userSecretKey)
if !ok {
return nil, errors.New("invalid key, expected *userSecretKey")
}
signerOpts, ok := opts.(*bccsp.IdemixSignerOpts)
if !ok {
return nil, errors.New("invalid options, expected *IdemixSignerOpts")
}
// Issuer public key
if signerOpts.IssuerPK == nil {
return nil, errors.New("invalid options, missing issuer public key")
}
ipk, ok := signerOpts.IssuerPK.(*issuerPublicKey)
if !ok {
return nil, errors.New("invalid issuer public key, expected *issuerPublicKey")
}
// Nym
if signerOpts.Nym == nil {
return nil, errors.New("invalid options, missing nym key")
}
nymSk, ok := signerOpts.Nym.(*nymSecretKey)
if !ok {
return nil, errors.New("invalid nym key, expected *nymSecretKey")
}
if len(digest) == 0 {
return nil, errors.New("invalid digest, it must not be empty")
}
sigma, err := s.SignatureScheme.Sign(
signerOpts.Credential,
userSecretKey.sk,
nymSk.pk, nymSk.sk,
ipk.pk,
signerOpts.Attributes,
digest,
signerOpts.RhIndex,
signerOpts.CRI,
)
if err != nil {
return nil, err
}
return sigma, nil
}
type Verifier struct {
SignatureScheme SignatureScheme
}
func (v *Verifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) {
issuerPublicKey, ok := k.(*issuerPublicKey)
if !ok {
return false, errors.New("invalid key, expected *issuerPublicKey")
}
signerOpts, ok := opts.(*bccsp.IdemixSignerOpts)
if !ok {
return false, errors.New("invalid options, expected *IdemixSignerOpts")
}
rpk, ok := signerOpts.RevocationPublicKey.(*revocationPublicKey)
if !ok {
return false, errors.New("invalid options, expected *revocationPublicKey")
}
if len(signature) == 0 {
return false, errors.New("invalid signature, it must not be empty")
}
err := v.SignatureScheme.Verify(
issuerPublicKey.pk,
signature,
digest,
signerOpts.Attributes,
signerOpts.RhIndex,
rpk.pubKey,
signerOpts.Epoch,
)
if err != nil {
return false, err
}
return true, nil
}
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package idemix_test
import (
"errors"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/idemix"
"github.com/hyperledger/fabric/bccsp/idemix/mock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Signature", func() {
Describe("when creating a signature", func() {
var (
Signer *idemix.Signer
fakeSignatureScheme *mock.SignatureScheme
nymSK bccsp.Key
)
BeforeEach(func() {
fakeSignatureScheme = &mock.SignatureScheme{}
Signer = &idemix.Signer{SignatureScheme: fakeSignatureScheme}
var err error
sk := &mock.Big{}
sk.BytesReturns([]byte{1, 2, 3, 4}, nil)
nymSK, err = idemix.NewNymSecretKey(sk, nil, false)
Expect(err).NotTo(HaveOccurred())
})
Context("and the underlying cryptographic algorithm succeed", func() {
var (
fakeSignature []byte
)
BeforeEach(func() {
fakeSignature = []byte("fake signature")
fakeSignatureScheme.SignReturns(fakeSignature, nil)
})
It("returns no error and a signature", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).NotTo(HaveOccurred())
Expect(signature).To(BeEquivalentTo(fakeSignature))
})
})
Context("and the underlying cryptographic algorithm fails", func() {
BeforeEach(func() {
fakeSignatureScheme.SignReturns(nil, errors.New("sign error"))
})
It("returns an error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("sign error"))
Expect(signature).To(BeNil())
})
})
Context("and the parameters are not well formed", func() {
Context("and the user secret key is nil", func() {
It("returns error", func() {
signature, err := Signer.Sign(
nil,
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("invalid key, expected *userSecretKey"))
Expect(signature).To(BeNil())
})
})
Context("and the user secret key is not of type *userSecretKey", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewIssuerPublicKey(nil),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("invalid key, expected *userSecretKey"))
Expect(signature).To(BeNil())
})
})
Context("and the digest is empty", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
nil,
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("invalid digest, it must not be empty"))
Expect(signature).To(BeNil())
})
})
Context("and the option is nil", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
nil,
)
Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts"))
Expect(signature).To(BeNil())
})
})
Context("and the option is not of type *IdemixSignerOpts", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixCRISignerOpts{},
)
Expect(err).To(MatchError("invalid options, expected *IdemixSignerOpts"))
Expect(signature).To(BeNil())
})
})
Context("and the nym is nil", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("invalid options, missing nym key"))
Expect(signature).To(BeNil())
})
})
Context("and the nym is not of type *nymSecretKey", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: idemix.NewIssuerPublicKey(nil),
IssuerPK: idemix.NewIssuerPublicKey(nil),
},
)
Expect(err).To(MatchError("invalid nym key, expected *nymSecretKey"))
Expect(signature).To(BeNil())
})
})
Context("and the IssuerPk is nil", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
},
)
Expect(err).To(MatchError("invalid options, missing issuer public key"))
Expect(signature).To(BeNil())
})
})
Context("and the IssuerPk is not of type *issuerPublicKey", func() {
It("returns error", func() {
signature, err := Signer.Sign(
idemix.NewUserSecretKey(nil, false),
[]byte("a digest"),
&bccsp.IdemixSignerOpts{
Nym: nymSK,
IssuerPK: idemix.NewUserSecretKey(nil, false),
},
)
Expect(err).To(MatchError("invalid issuer public key, expected *issuerPublicKey"))
Expect(signature).To(BeNil())
})
})
})
})
Describe("when verifying a signature", func() {
var (
Verifier *idemix.Verifier
fakeSignatureScheme *mock.SignatureScheme
)