diff --git a/pkg/doc/cl/api.go b/pkg/doc/cl/api.go new file mode 100644 index 0000000000..718bf41018 --- /dev/null +++ b/pkg/doc/cl/api.go @@ -0,0 +1,77 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cl + +import ( + "github.com/hyperledger/aries-framework-go/pkg/crypto" + "github.com/hyperledger/aries-framework-go/pkg/kms" +) + +// Issuer contains all high-level methods to process CL Anoncreds on the issuer's side. +type Issuer interface { + // GetCredentialDefinition returns a public CredDef data - public key, correctness proof and attributes + // returns: + // credDef as *CredentialDefinition + // error in case of errors + GetCredentialDefinition() (*CredentialDefinition, error) + // OfferCredential generates CredOffer containing valid nonce + // returns: + // offer as *CredentialOffer + // error in case of errors + OfferCredential() (*CredentialOffer, error) + // IssueCredential issues and signs Credential for values and CredRequest + // provided by prover and CredOffer from the previous step + // Resulting Credential will contain signature and signature's correctness proof, along with issued attributes + // returns: + // credential as *Credential + // error in case of errors + IssueCredential(values map[string]interface{}, + credentialRequest *CredentialRequest, credOffer *CredentialOffer) (*Credential, error) +} + +// Prover contains all high-level methods to process CL Anoncreds on the prover's side. +type Prover interface { + // RequestCredential generates CredRequest which contains blinded secrets with MS, using issuer's CredDef public data + // and CredOffer from the previous step + // returns: + // request as *CredentialRequest + // error in case of errors + RequestCredential(credOffer *CredentialOffer, + credDef *CredentialDefinition, proverID string) (*CredentialRequest, error) + // ProcessCredential updates issued Credential signature for CredDef, using blinding factor from a CredRequest + // returns: + // credential as *Credential + // error in case of errors + ProcessCredential(credential *Credential, credRequest *CredentialRequest, + credDef *CredentialDefinition) (*Credential, error) + // CreateProof composes Proof for the provided Credentials for CredDefs + // matching revealead attrs and predicates specified in PresentationRequest + // returns: + // proof as *Proof + // error in case of errors + CreateProof(presentationRequest *PresentationRequest, credentials []*Credential, + credDefs []*CredentialDefinition) (*Proof, error) +} + +// Verifier contains all high-level methods to process CL Anoncreds on the verifier's side. +type Verifier interface { + // RequestPresentation generates PresentationRequest with unique nonce and provided list of attrs and predicates + // returns: + // request as *PresentationRequest + // error in case of errors + RequestPresentation(items []*PresentationRequestItem) (*PresentationRequest, error) + // VerifyProof verifies given Proof according to PresentationRequest and CredDefs + // returns: + // error in case of errors or nil if proof verification was successful + VerifyProof(proof *Proof, presentationRequest *PresentationRequest, credDefs []*CredentialDefinition) error +} + +// Provider for CL services constructors. +type Provider interface { + KMS() kms.KeyManager + Crypto() crypto.Crypto +} diff --git a/pkg/doc/cl/model.go b/pkg/doc/cl/model.go new file mode 100644 index 0000000000..ce813f26dc --- /dev/null +++ b/pkg/doc/cl/model.go @@ -0,0 +1,64 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cl + +// CredentialDefinition contains public data of CL CredDef. +type CredentialDefinition struct { + CredPubKey []byte + CredDefCorrectnessProof []byte + Attrs []string +} + +// CredentialOffer contains nonce of CL CredOffer. +type CredentialOffer struct { + Nonce []byte +} + +// CredentialRequest contains nonce, proverID and blinded secrets of CL CredRequest. +type CredentialRequest struct { + BlindedCredentialSecrets *BlindedCredentialSecrets + Nonce []byte + ProverID string +} + +// BlindedCredentialSecrets contains handle, blinding factor and correctness proof of CL BlindedSecrets. +type BlindedCredentialSecrets struct { + Handle []byte + BlindingFactor []byte + CorrectnessProof []byte +} + +// Credential contains CL Credential's signature, correctness proof for it and related credential's values. +type Credential struct { + Signature []byte + Values map[string]interface{} + SigProof []byte +} + +// PresentationRequest contains items used for CL Proof generation. +type PresentationRequest struct { + Items []*PresentationRequestItem + Nonce []byte +} + +// PresentationRequestItem consists of revealed attributes and predicates upon which CL Proof is generated. +type PresentationRequestItem struct { + RevealedAttrs []string + Predicates []*Predicate +} + +// Predicate defines predicate for CL Proof. +type Predicate struct { + Attr string + PType string + Value int32 +} + +// Proof wraps CL Proof in raw bytes. +type Proof struct { + Proof []byte +} diff --git a/pkg/doc/cl/ursa/crypto_test.go b/pkg/doc/cl/ursa/crypto_test.go new file mode 100644 index 0000000000..b5d049343c --- /dev/null +++ b/pkg/doc/cl/ursa/crypto_test.go @@ -0,0 +1,228 @@ +//go:build ursa +// +build ursa + +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ursa + +import ( + "testing" + + "github.com/google/tink/go/keyset" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/pkg/cl" + "github.com/hyperledger/aries-framework-go/pkg/crypto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" + bld "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/cl/blinder" + sgn "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/cl/signer" + "github.com/hyperledger/aries-framework-go/pkg/kms" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" +) + +func TestCL(t *testing.T) { + km := &mockkms.KeyManager{} + c := &tinkcrypto.Crypto{} + provider := newProvider(km, c) + + values := map[string]interface{}{"attr1": 5, "attr2": "aaa"} + values2 := map[string]interface{}{"attr3": 5, "attr4": "aaa"} + + presentaionItems := []*cl.PresentationRequestItem{{ + RevealedAttrs: []string{"attr2"}, + Predicates: []*cl.Predicate{ + { + PType: "GE", + Attr: "attr1", + Value: 4, + }, + }, + }} + + var ( + issuer *Issuer + prover *Prover + verifier *Verifier + + credDef *cl.CredentialDefinition + offer *cl.CredentialOffer + request *cl.CredentialRequest + credential *cl.Credential + + originalSignature []byte + + presentation *cl.PresentationRequest + proof *cl.Proof + ) + + t.Run("test CL services creation", func(t *testing.T) { + var err error + + issKh, issPubKey := createAndExportSignerKey(t, []string{"attr1", "attr2"}) + km.GetKeyValue = issKh + km.ExportPubKeyBytesValue = issPubKey + km.ExportPubKeyTypeValue = kms.CLCredDefType + + issuer, err = NewIssuer(provider, "credDefKID", []string{"attr1", "attr2"}) + require.NoError(t, err) + + prvKh := createBlinderKey(t) + km.GetKeyValue = prvKh + + prover, err = NewProver(provider, "msKID") + require.NoError(t, err) + + verifier, err = NewVerifier() + require.NoError(t, err) + }) + + t.Run("test CL issue credential flow", func(t *testing.T) { + var err error + + // 0. Issuer expose CredDef + credDef, err = issuer.GetCredentialDefinition() + require.NoError(t, err) + require.NotEmpty(t, credDef.CredPubKey) + require.NotEmpty(t, credDef.CredDefCorrectnessProof) + + // 1. Issuer offers credential + offer, err = issuer.OfferCredential() + require.NoError(t, err) + require.NotEmpty(t, offer.Nonce) + + // 2. Prover requests credential + request, err = prover.RequestCredential(offer, credDef, "proverDID") + require.NoError(t, err) + require.NotEmpty(t, request.Nonce) + require.NotEmpty(t, request.ProverID) + require.NotEmpty(t, request.BlindedCredentialSecrets) + + // 3. Issuer issues credential + credential, err = issuer.IssueCredential(values, request, offer) + require.NoError(t, err) + require.NotEmpty(t, credential.Signature) + require.NotEmpty(t, credential.SigProof) + require.NotEmpty(t, credential.Values) + + // 4. Prover verifies credential + originalSignature = credential.Signature + + err = prover.ProcessCredential(credential, request, credDef) + require.NoError(t, err) + require.NotEmpty(t, credential.Signature) + require.NotEmpty(t, credential.SigProof) + require.NotEmpty(t, credential.Values) + + require.NotEqual(t, originalSignature, credential.Signature) + }) + + t.Run("test CL present proof flow", func(t *testing.T) { + var err error + + // 1. Verifier makes presentation request + presentation, err = verifier.RequestPresentation(presentaionItems) + require.NoError(t, err) + require.NotEmpty(t, presentation.Items) + require.NotEmpty(t, presentation.Nonce) + + // 2. Prover creates proof accordingly + proof, err = prover.CreateProof(presentation, []*cl.Credential{credential}, []*cl.CredentialDefinition{credDef}) + require.NoError(t, err) + require.NotEmpty(t, proof.Proof) + + // 3. Verifier verifies resulting proof + err = verifier.VerifyProof(proof, presentation, []*cl.CredentialDefinition{credDef}) + require.NoError(t, err) + }) + + t.Run("test CL failures", func(t *testing.T) { + var err error + + issKh2, issPubKey2 := createAndExportSignerKey(t, []string{"attr3", "attr4"}) + km.GetKeyValue = issKh2 + km.ExportPubKeyBytesValue = issPubKey2 + km.ExportPubKeyTypeValue = kms.CLCredDefType + + issuer2, err := NewIssuer(provider, "credDefKID2", []string{"attr3", "attr4"}) + require.NoError(t, err) + credDef2, err := issuer2.GetCredentialDefinition() + require.NoError(t, err) + + // Issuer fails to issue credential for unknown credDef + _, err = issuer2.IssueCredential(values, request, offer) + require.Error(t, err) + + // Issuer fails to issue credential for invalid values + _, err = issuer.IssueCredential(values2, request, offer) + require.Error(t, err) + + // Prover fails to process credential with unmatched credDef + err = prover.ProcessCredential(credential, request, credDef2) + require.Error(t, err) + + // Prover fails to create proof with unmatched credDefs + _, err = prover.CreateProof(presentation, []*cl.Credential{credential}, []*cl.CredentialDefinition{credDef2}) + require.Error(t, err) + + // Verifier fails to verify proof for unprocessed credential + unprocessed := &cl.Credential{ + Signature: originalSignature, + SigProof: credential.SigProof, + Values: credential.Values, + } + + invalid, err := prover.CreateProof(presentation, []*cl.Credential{unprocessed}, []*cl.CredentialDefinition{credDef}) + require.NoError(t, err) + + err = verifier.VerifyProof(invalid, presentation, []*cl.CredentialDefinition{credDef}) + require.Error(t, err) + + // Verifier fails to verify proof for other credDef + err = verifier.VerifyProof(proof, presentation, []*cl.CredentialDefinition{credDef2}) + require.Error(t, err) + }) +} + +func createAndExportSignerKey(t *testing.T, attrs []string) (*keyset.Handle, []byte) { + kh, err := keyset.NewHandle(sgn.CredDefKeyTemplate(attrs)) + require.NoError(t, err) + + pKh, err := kh.Public() + require.NoError(t, err) + + pubKey, err := sgn.ExportCredDefPubKey(pKh) + require.NoError(t, err) + + return kh, pubKey +} + +func createBlinderKey(t *testing.T) *keyset.Handle { + kh, err := keyset.NewHandle(bld.MasterSecretKeyTemplate()) + require.NoError(t, err) + + return kh +} + +type mockProvider struct { + km kms.KeyManager + cr crypto.Crypto +} + +func (p *mockProvider) KMS() kms.KeyManager { + return p.km +} + +func (p *mockProvider) Crypto() crypto.Crypto { + return p.cr +} + +func newProvider(km kms.KeyManager, cr crypto.Crypto) *mockProvider { + return &mockProvider{ + km: km, + cr: cr, + } +} diff --git a/pkg/doc/cl/ursa/helpers.go b/pkg/doc/cl/ursa/helpers.go new file mode 100644 index 0000000000..4f35b937ea --- /dev/null +++ b/pkg/doc/cl/ursa/helpers.go @@ -0,0 +1,373 @@ +//go:build ursa +// +build ursa + +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ursa + +import ( + "github.com/hyperledger/ursa-wrapper-go/pkg/libursa/ursa" + + "github.com/hyperledger/aries-framework-go/pkg/cl" + "github.com/hyperledger/aries-framework-go/pkg/internal/ursautil" +) + +// subProofItem is a auxiliary struct for processing proofs. +type subProofItem struct { + BlindedVals []byte + Credential *cl.Credential + CredentialDefinition *cl.CredentialDefinition + Item *cl.PresentationRequestItem +} + +func newNonce() ([]byte, error) { + _nonce, err := ursa.NewNonce() + if err != nil { + return nil, err + } + + defer _nonce.Free() // nolint: errcheck + + nonce, err := _nonce.ToJSON() + if err != nil { + return nil, err + } + + return nonce, nil +} + +func blindCredentialSecrets( + pubKey []byte, + correctnessProof []byte, + nonce []byte, + blindedMs []byte, +) (*cl.BlindedCredentialSecrets, error) { + _nonce, err := ursa.NonceFromJSON(string(nonce)) + if err != nil { + return nil, err + } + + defer _nonce.Free() // nolint: errcheck + + _pubKey, err := ursa.CredentialPublicKeyFromJSON(pubKey) + if err != nil { + return nil, err + } + + defer _pubKey.Free() // nolint: errcheck + + _correctnessProof, err := ursa.CredentialKeyCorrectnessProofFromJSON(correctnessProof) + if err != nil { + return nil, err + } + + defer _correctnessProof.Free() // nolint: errcheck + + _blindedMs, err := ursa.CredentialValuesFromJSON(blindedMs) + if err != nil { + return nil, err + } + + defer _blindedMs.Free() // nolint: errcheck + + _blindedSecrets, err := ursa.BlindCredentialSecrets(_pubKey, _correctnessProof, _nonce, _blindedMs) + if err != nil { + return nil, err + } + + defer _blindedSecrets.Handle.Free() // nolint: errcheck + defer _blindedSecrets.CorrectnessProof.Free() // nolint: errcheck + defer _blindedSecrets.BlindingFactor.Free() // nolint: errcheck + + secrets, err := _blindedSecrets.Handle.ToJSON() + if err != nil { + return nil, err + } + + proof, err := _blindedSecrets.CorrectnessProof.ToJSON() + if err != nil { + return nil, err + } + + blindingFactor, err := _blindedSecrets.BlindingFactor.ToJSON() + if err != nil { + return nil, err + } + + return &cl.BlindedCredentialSecrets{Handle: secrets, CorrectnessProof: proof, BlindingFactor: blindingFactor}, nil +} + +// nolint: funlen +func processCredentialSignature( + credential *cl.Credential, + credReq *cl.CredentialRequest, + credDef *cl.CredentialDefinition, + blindedVals []byte, +) error { + _signature, err := ursa.CredentialSignatureFromJSON(credential.Signature) + if err != nil { + return err + } + + defer _signature.Free() // nolint: errcheck + + _correctnessProof, err := ursa.CredentialSignatureCorrectnessProofFromJSON(credential.SigProof) + if err != nil { + return err + } + + defer _correctnessProof.Free() // nolint: errcheck + + _blindingFactor, err := ursa.CredentialSecretsBlindingFactorsFromJSON(credReq.BlindedCredentialSecrets.BlindingFactor) + if err != nil { + return err + } + + defer _blindingFactor.Free() // nolint: errcheck + + _nonce, err := ursa.NonceFromJSON(string(credReq.Nonce)) + if err != nil { + return err + } + + defer _nonce.Free() // nolint: errcheck + + _pubKey, err := ursa.CredentialPublicKeyFromJSON(credDef.CredPubKey) + if err != nil { + return err + } + + defer _pubKey.Free() // nolint: errcheck + + _blindedVals, err := ursa.CredentialValuesFromJSON(blindedVals) + if err != nil { + return err + } + + defer _blindedVals.Free() // nolint: errcheck + + err = _signature.ProcessCredentialSignature( + _blindedVals, + _correctnessProof, + _blindingFactor, + _pubKey, + _nonce, + ) + if err != nil { + return err + } + + updatedSignature, err := _signature.ToJSON() + if err != nil { + return err + } + + updatedCorrectnessProof, err := _correctnessProof.ToJSON() + if err != nil { + return err + } + + credential.Signature = updatedSignature + credential.SigProof = updatedCorrectnessProof + + return nil +} + +func createProof( + items []*subProofItem, + nonce []byte, +) ([]byte, error) { + proofBuilder, err := ursa.NewProofBuilder() + if err != nil { + return nil, err + } + + err = proofBuilder.AddCommonAttribute("master_secret") + if err != nil { + return nil, err + } + + _reqNonce, err := ursa.NonceFromJSON(string(nonce)) + if err != nil { + return nil, err + } + + defer _reqNonce.Free() // nolint: errcheck + + for _, item := range items { + err = processSubProof(proofBuilder, item) + if err != nil { + return nil, err + } + } + + _proof, err := proofBuilder.Finalize(_reqNonce) + if err != nil { + return nil, err + } + + defer _proof.Free() // nolint: errcheck + + proofJSON, err := _proof.ToJSON() + if err != nil { + return nil, err + } + + return proofJSON, nil +} + +func processSubProof( + proofBuilder *ursa.ProofBuilder, + item *subProofItem, +) error { + _request, err := buildSubProofRequest(item.Item.RevealedAttrs, item.Item.Predicates) + if err != nil { + return err + } + + defer _request.Free() // nolint: errcheck + + _blindedValues, err := ursa.CredentialValuesFromJSON(item.BlindedVals) + if err != nil { + return err + } + + defer _blindedValues.Free() // nolint: errcheck + + _signature, err := ursa.CredentialSignatureFromJSON(item.Credential.Signature) + if err != nil { + return err + } + + defer _signature.Free() // nolint: errcheck + + _pubKey, err := ursa.CredentialPublicKeyFromJSON(item.CredentialDefinition.CredPubKey) + if err != nil { + return err + } + + defer _pubKey.Free() // nolint: errcheck + + _schema, _nonSchema, err := ursautil.BuildSchema(item.CredentialDefinition.Attrs) + if err != nil { + return err + } + + defer _schema.Free() // nolint: errcheck + defer _nonSchema.Free() // nolint: errcheck + + err = proofBuilder.AddSubProofRequest( + _request, + _schema, + _nonSchema, + _signature, + _blindedValues, + _pubKey, + ) + + return err +} + +func verifyProof( + proof *cl.Proof, + items []*subProofItem, + nonce []byte, +) error { + verifier, err := ursa.NewProofVerifier() + if err != nil { + return err + } + + _reqNonce, err := ursa.NonceFromJSON(string(nonce)) + if err != nil { + return err + } + + defer _reqNonce.Free() // nolint: errcheck + + _proof, err := ursa.ProofFromJSON(proof.Proof) + if err != nil { + return err + } + + defer _proof.Free() // nolint: errcheck + + for _, item := range items { + err = processSubProofVerifier(verifier, item) + if err != nil { + return err + } + } + + err = verifier.Verify(_proof, _reqNonce) + + return err +} + +func processSubProofVerifier( + verifier *ursa.ProofVerifier, + item *subProofItem, +) error { + _request, err := buildSubProofRequest(item.Item.RevealedAttrs, item.Item.Predicates) + if err != nil { + return err + } + + defer _request.Free() // nolint: errcheck + + _pubKey, err := ursa.CredentialPublicKeyFromJSON(item.CredentialDefinition.CredPubKey) + if err != nil { + return err + } + + defer _pubKey.Free() // nolint: errcheck + + _schema, _nonSchema, err := ursautil.BuildSchema(item.CredentialDefinition.Attrs) + if err != nil { + return err + } + + defer _schema.Free() // nolint: errcheck + defer _nonSchema.Free() // nolint: errcheck + + err = verifier.AddSubProofRequest( + _request, + _schema, + _nonSchema, + _pubKey, + ) + + return err +} + +func buildSubProofRequest(revealedAttrs []string, predicates []*cl.Predicate) (*ursa.SubProofRequestHandle, error) { + subProofBuilder, err := ursa.NewSubProofRequestBuilder() + if err != nil { + return nil, err + } + + for _, revealedAttr := range revealedAttrs { + err = subProofBuilder.AddRevealedAttr(revealedAttr) + if err != nil { + return nil, err + } + } + + for _, predicate := range predicates { + err = subProofBuilder.AddPredicate(predicate.Attr, predicate.PType, predicate.Value) + if err != nil { + return nil, err + } + } + + subProofRequest, err := subProofBuilder.Finalize() + if err != nil { + return nil, err + } + + return subProofRequest, nil +} diff --git a/pkg/doc/cl/ursa/issuer.go b/pkg/doc/cl/ursa/issuer.go new file mode 100644 index 0000000000..72511de650 --- /dev/null +++ b/pkg/doc/cl/ursa/issuer.go @@ -0,0 +1,100 @@ +//go:build ursa +// +build ursa + +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ursa + +import ( + "errors" + "fmt" + + "github.com/hyperledger/aries-framework-go/pkg/cl" + "github.com/hyperledger/aries-framework-go/pkg/crypto" + "github.com/hyperledger/aries-framework-go/pkg/kms" +) + +// Issuer is an ursa implementation of the CL Issuer API. +type Issuer struct { + crypto crypto.Crypto + kh interface{} + pubKey []byte + attrs []string +} + +// NewIssuer insaniates a service for the provided keyID and attributes. +func NewIssuer(provider cl.Provider, keyID string, attrs []string) (*Issuer, error) { + km := provider.KMS() + + kh, err := km.Get(keyID) + if err != nil { + return nil, fmt.Errorf("failed to get KeyHandle for %s: %w", keyID, err) + } + + pubKey, kt, err := km.ExportPubKeyBytes(keyID) + if err != nil { + return nil, err + } + + if kt != kms.CLCredDefType { + return nil, errors.New("not a CredDef key") + } + + return &Issuer{kh: kh, crypto: provider.Crypto(), pubKey: pubKey, attrs: attrs}, nil +} + +// GetCredentialDefinition returns a public CredDef data - public key, correctness proof and attributes +// returns: +// credDef as *CredentialDefinition +// error in case of errors +func (s *Issuer) GetCredentialDefinition() (*cl.CredentialDefinition, error) { + correctnessProof, err := s.crypto.GetCorrectnessProof(s.kh) + if err != nil { + return nil, err + } + + return &cl.CredentialDefinition{CredDefCorrectnessProof: correctnessProof, CredPubKey: s.pubKey, Attrs: s.attrs}, nil +} + +// OfferCredential generates CredOffer containing valid nonce +// returns: +// offer as *CredentialOffer +// error in case of errors +func (s *Issuer) OfferCredential() (*cl.CredentialOffer, error) { + nonce, err := newNonce() + if err != nil { + return nil, err + } + + return &cl.CredentialOffer{Nonce: nonce}, nil +} + +// IssueCredential issues and signs Credential for values and CredRequest +// provided by prover and CredOffer from the previous step +// Resulting Credential will contain signature and signature's correctness proof, along with issued attributes +// returns: +// credential as *Credential +// error in case of errors +func (s *Issuer) IssueCredential( + values map[string]interface{}, + credRequest *cl.CredentialRequest, + credOffer *cl.CredentialOffer, +) (*cl.Credential, error) { + sig, sigProof, err := s.crypto.SignWithSecrets( + s.kh, + values, + credRequest.BlindedCredentialSecrets.Handle, + credRequest.BlindedCredentialSecrets.CorrectnessProof, + [][]byte{credOffer.Nonce, credRequest.Nonce}, + credRequest.ProverID, + ) + if err != nil { + return nil, err + } + + return &cl.Credential{Signature: sig, SigProof: sigProof, Values: values}, nil +} diff --git a/pkg/doc/cl/ursa/prover.go b/pkg/doc/cl/ursa/prover.go new file mode 100644 index 0000000000..1a1f0a34d4 --- /dev/null +++ b/pkg/doc/cl/ursa/prover.go @@ -0,0 +1,141 @@ +//go:build ursa +// +build ursa + +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ursa + +import ( + "fmt" + + "github.com/hyperledger/aries-framework-go/pkg/cl" + "github.com/hyperledger/aries-framework-go/pkg/crypto" +) + +// Prover is an ursa implementation of the CL Prover API. +type Prover struct { + crypto crypto.Crypto + kh interface{} +} + +// NewProver insaniates a Prover service for the provided keyID. +func NewProver(provider cl.Provider, keyID string) (*Prover, error) { + km := provider.KMS() + + kh, err := km.Get(keyID) + if err != nil { + return nil, fmt.Errorf("failed to get KeyHandle for %s: %w", keyID, err) + } + + return &Prover{kh: kh, crypto: provider.Crypto()}, nil +} + +// RequestCredential generates CredRequest which contains blinded secrets with MS, using issuer's CredDef public data +// and CredOffer from the previous step +// returns: +// request as *CredentialRequest +// error in case of errors +func (s *Prover) RequestCredential( + credOffer *cl.CredentialOffer, + credDef *cl.CredentialDefinition, + proverID string, +) (*cl.CredentialRequest, error) { + blindedMs, err := s.crypto.Blind(s.kh) + if err != nil { + return nil, err + } + + nonce, err := newNonce() + if err != nil { + return nil, err + } + + secrets, err := blindCredentialSecrets( + credDef.CredPubKey, + credDef.CredDefCorrectnessProof, + credOffer.Nonce, + blindedMs[0], + ) + if err != nil { + return nil, err + } + + return &cl.CredentialRequest{BlindedCredentialSecrets: secrets, Nonce: nonce, ProverID: proverID}, nil +} + +// ProcessCredential updates issued Credential signature for CredDef, using blinding factor from a CredRequest +// returns: +// credential as *Credential +// error in case of errors +func (s *Prover) ProcessCredential( + credential *cl.Credential, + credRequest *cl.CredentialRequest, + credDef *cl.CredentialDefinition, +) error { + blindedVals, err := s.crypto.Blind(s.kh, credential.Values) + if err != nil { + return err + } + + err = processCredentialSignature( + credential, + credRequest, + credDef, + blindedVals[0], + ) + + return err +} + +// CreateProof composes Proof for the provided Credentials for CredDefs +// matching revealead attrs and predicates specified in PresentationRequest +// returns: +// proof as *Proof +// error in case of errors +func (s *Prover) CreateProof( + presentationRequest *cl.PresentationRequest, + credentials []*cl.Credential, + credDefs []*cl.CredentialDefinition, +) (*cl.Proof, error) { + if len(presentationRequest.Items) != len(credentials) { + return nil, fmt.Errorf("not enough credentials provided to fulfill the presentsation request") + } + + if len(presentationRequest.Items) != len(credDefs) { + return nil, fmt.Errorf("not enough credential definitions provided to fulfill the presentsation request") + } + + var multivals []map[string]interface{} + for _, cred := range credentials { + multivals = append(multivals, cred.Values) + } + + blindedMultiVals, err := s.crypto.Blind(s.kh, multivals...) + if err != nil { + return nil, err + } + + var subProofItems []*subProofItem + + for i, item := range presentationRequest.Items { + subProofItem := &subProofItem{ + BlindedVals: blindedMultiVals[i], + Credential: credentials[i], + CredentialDefinition: credDefs[i], + Item: item, + } + + subProofItems = append(subProofItems, subProofItem) + } + + proof, err := createProof(subProofItems, presentationRequest.Nonce) + if err != nil { + return nil, err + } + + return &cl.Proof{Proof: proof}, nil +} diff --git a/pkg/doc/cl/ursa/verifier.go b/pkg/doc/cl/ursa/verifier.go new file mode 100644 index 0000000000..46f8d482db --- /dev/null +++ b/pkg/doc/cl/ursa/verifier.go @@ -0,0 +1,64 @@ +//go:build ursa +// +build ursa + +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ursa + +import ( + "fmt" + + "github.com/hyperledger/aries-framework-go/pkg/cl" +) + +// Verifier is an ursa implementation of the CL Verifier API. +type Verifier struct{} + +// NewVerifier insaniates Verifier service. +func NewVerifier() (*Verifier, error) { + return &Verifier{}, nil +} + +// RequestPresentation generates PresentationRequest with unique nonce and provided list of attrs and predicates +// returns: +// request as *PresentationRequest +// error in case of errors +func (s *Verifier) RequestPresentation(items []*cl.PresentationRequestItem) (*cl.PresentationRequest, error) { + nonce, err := newNonce() + if err != nil { + return nil, err + } + + return &cl.PresentationRequest{Items: items, Nonce: nonce}, nil +} + +// VerifyProof verifies given Proof according to PresentationRequest and CredDefs +// returns: +// error in case of errors or nil if proof verification was successful +func (s *Verifier) VerifyProof(proof *cl.Proof, + presentationRequest *cl.PresentationRequest, + credDefs []*cl.CredentialDefinition, +) error { + if len(presentationRequest.Items) != len(credDefs) { + return fmt.Errorf("not enough credential definitions provided to fulfill the presentsation request") + } + + var subProofItems []*subProofItem + + for i, item := range presentationRequest.Items { + subProofItem := &subProofItem{ + CredentialDefinition: credDefs[i], + Item: item, + } + + subProofItems = append(subProofItems, subProofItem) + } + + err := verifyProof(proof, subProofItems, presentationRequest.Nonce) + + return err +}