diff --git a/go.mod b/go.mod index 3d6881e..2c06375 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,16 @@ go 1.19 require ( github.com/btcsuite/btcd v0.22.2 + github.com/cosmos/cosmos-sdk v0.45.12 + github.com/cosmos/go-bip39 v1.0.0 github.com/gogo/protobuf v1.3.3 github.com/hyperledger/aries-framework-go v0.1.9-0.20230222063211-02f80847168a github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20220322085443-50e8f9bd208b github.com/medibloc/panacea-core/v2 v2.0.5 + github.com/mr-tron/base58 v1.2.0 github.com/piprate/json-gold v0.4.2 github.com/stretchr/testify v1.8.1 + github.com/tendermint/tendermint v0.34.24 ) require github.com/google/uuid v1.3.0 // indirect @@ -27,7 +31,6 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect - github.com/cosmos/cosmos-sdk v0.45.9 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.2 // indirect @@ -62,7 +65,6 @@ require ( github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/sha256-simd v0.1.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect @@ -89,7 +91,6 @@ require ( github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tendermint/tendermint v0.34.24 // indirect github.com/tendermint/tm-db v0.6.7 // indirect github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect github.com/tidwall/gjson v1.6.7 // indirect diff --git a/go.sum b/go.sum index 403c198..ea45562 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= @@ -522,6 +523,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= @@ -625,6 +627,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/vc/command.go b/pkg/vc/command.go index f48d518..46d8b4b 100644 --- a/pkg/vc/command.go +++ b/pkg/vc/command.go @@ -128,24 +128,18 @@ func (f *Framework) VerifyPresentation(vp []byte, opts ...VerificationOption) (* // TODO: For now, check of constraints in presentation definition is not supported // https://github.com/hyperledger/aries-framework-go/issues/2108 - _, err = pd.Match(presentation, f.loader, presexch.WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(f.loader))) + _, err = pd.Match(presentation, + f.loader, + presexch.WithCredentialOptions( + verifiable.WithJSONLDDocumentLoader(f.loader), + verifiable.WithPublicKeyFetcher(f.resolver.PublicKeyFetcher()), + ), + ) if err != nil { return nil, fmt.Errorf("is not matched with presentation definition: %w", err) } } - // verify VCs - for _, cred := range presentation.Credentials() { - vc, err := json.Marshal(cred) - if err != nil { - return nil, fmt.Errorf("failed to read credentials from presentation: %w", err) - } - - if err = f.VerifyCredential(vc); err != nil { - return nil, fmt.Errorf("failed to verify credential: %w", err) - } - } - return presentation, nil } diff --git a/pkg/vc/framework.go b/pkg/vc/framework.go index 507548c..98645a5 100644 --- a/pkg/vc/framework.go +++ b/pkg/vc/framework.go @@ -2,6 +2,7 @@ package vc import ( "github.com/hyperledger/aries-framework-go/component/storageutil/mem" + "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/doc/ld" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" @@ -16,7 +17,11 @@ type Framework struct { resolver *verifiable.VDRKeyResolver } -func NewFramework(vdr vdr.Registry) (*Framework, error) { +type didResolver interface { + Resolve(did string, opts ...vdr.DIDMethodOption) (*did.DocResolution, error) +} + +func NewFramework(vdr didResolver) (*Framework, error) { storeProvider := mem.NewProvider() contextStore, err := ldstore.NewContextStore(storeProvider) if err != nil { diff --git a/pkg/vdr/panacea_vdr.go b/pkg/vdr/panacea_vdr.go index 3a90262..3f386fb 100644 --- a/pkg/vdr/panacea_vdr.go +++ b/pkg/vdr/panacea_vdr.go @@ -3,17 +3,15 @@ package vdr import ( "bytes" "context" - "errors" "fmt" - + "github.com/btcsuite/btcd/btcec" "github.com/gogo/protobuf/jsonpb" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" didtypes "github.com/medibloc/panacea-core/v2/x/did/types" + "github.com/mr-tron/base58" ) -var _ vdr.Registry = &PanaceaVDR{} - type didClient interface { GetDID(context.Context, string) (*didtypes.DIDDocumentWithSeq, error) } @@ -34,6 +32,27 @@ func (r *PanaceaVDR) Resolve(didID string, _ ...vdr.DIDMethodOption) (*did.DocRe return nil, fmt.Errorf("failed to get DID document: %w", err) } + var vms []*didtypes.VerificationMethod + for _, vm := range didDocWithSeq.Document.VerificationMethods { + pubKeyBz, err := base58.Decode(vm.PublicKeyBase58) + if err != nil { + return nil, fmt.Errorf("invalid base58 encoded public key: %w", err) + } + + if btcec.IsCompressedPubKey(pubKeyBz) { + pubKey, err := btcec.ParsePubKey(pubKeyBz, btcec.S256()) + if err != nil { + return nil, fmt.Errorf("invalid secp256k1 public key of verification method: %w", err) + } + + pubKeyStr := base58.Encode(pubKey.SerializeUncompressed()) + vm.PublicKeyBase58 = pubKeyStr + } + vms = append(vms, vm) + } + + didDocWithSeq.Document.VerificationMethods = vms + docBuf := new(bytes.Buffer) if err := new(jsonpb.Marshaler).Marshal(docBuf, didDocWithSeq.Document); err != nil { return nil, fmt.Errorf("failed to marshal DID document: %w", err) @@ -48,19 +67,3 @@ func (r *PanaceaVDR) Resolve(didID string, _ ...vdr.DIDMethodOption) (*did.DocRe DIDDocument: doc, }, nil } - -func (r *PanaceaVDR) Create(_ string, _ *did.Doc, _ ...vdr.DIDMethodOption) (*did.DocResolution, error) { - return nil, errors.New("not implemented") -} - -func (r *PanaceaVDR) Update(_ *did.Doc, _ ...vdr.DIDMethodOption) error { - return errors.New("not implemented") -} - -func (r *PanaceaVDR) Deactivate(_ string, _ ...vdr.DIDMethodOption) error { - return errors.New("not implemented") -} - -func (r *PanaceaVDR) Close() error { - return errors.New("not implemented") -} diff --git a/pkg/vdr/panacea_vdr_test.go b/pkg/vdr/panacea_vdr_test.go index f01ee99..9f9aea2 100644 --- a/pkg/vdr/panacea_vdr_test.go +++ b/pkg/vdr/panacea_vdr_test.go @@ -2,28 +2,216 @@ package vdr import ( "context" + "encoding/json" + "fmt" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/go-bip39" + "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" + "github.com/hyperledger/aries-framework-go/pkg/doc/util" + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + "github.com/medibloc/vc-sdk/pkg/vc" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/crypto/secp256k1" "testing" + "time" didtypes "github.com/medibloc/panacea-core/v2/x/did/types" - "github.com/stretchr/testify/require" ) -func TestPanaceaVDR_Resolve(t *testing.T) { - panaceaVDR := NewPanaceaVDR(mockDIDClient{}) +const ( + ecdsaSigType = "EcdsaSecp256k1Signature2019" + ecdsaVerificationType = "EcdsaSecp256k1VerificationKey2019" +) + +var ( + intFilterType = "integer" + strFilterType = "string" +) + +type panaceaVDRTestSuite struct { + suite.Suite + + issuerPrivKey secp256k1.PrivKey + holderPrivKey secp256k1.PrivKey + + issuerDID string + holderDID string + + VDR *mockDIDClient +} + +func TestPanaceaVDRTestSuite(t *testing.T) { + suite.Run(t, &panaceaVDRTestSuite{}) +} + +func (suite *panaceaVDRTestSuite) BeforeTest(_, _ string) { + issuerMnemonic, _ := newMnemonic() + holderMnemonic, _ := newMnemonic() + + suite.issuerPrivKey, _ = generatePrivateKeyFromMnemonic(issuerMnemonic) + suite.holderPrivKey, _ = generatePrivateKeyFromMnemonic(holderMnemonic) + + suite.issuerDID = didtypes.NewDID(suite.issuerPrivKey.PubKey().Bytes()) + suite.holderDID = didtypes.NewDID(suite.holderPrivKey.PubKey().Bytes()) + + issuerPubKeyBase58 := base58.Encode(suite.issuerPrivKey.PubKey().Bytes()) + holderPubKeyBase58 := base58.Encode(suite.holderPrivKey.PubKey().Bytes()) + + issuerDIDDoc := createDIDDoc(suite.issuerDID, issuerPubKeyBase58) + holderDIDDoc := createDIDDoc(suite.holderDID, holderPubKeyBase58) + + suite.VDR = newMockDIDClient(issuerDIDDoc, holderDIDDoc) +} + +func (suite *panaceaVDRTestSuite) TestPresentationExchange_WithPanaceaVDR() { + presDef := &presexch.PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*presexch.InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*presexch.Schema{{ + URI: "https://www.w3.org/2018/credentials#VerifiableCredential", + Required: false, + }, { + URI: "https://w3id.org/security/bbs/v1", + Required: false, + }}, + Constraints: &presexch.Constraints{ + Fields: []*presexch.Field{ + { + Path: []string{"$.credentialSubject.age"}, + Filter: &presexch.Filter{ + Type: &intFilterType, + Minimum: 18, + Maximum: 30, + }, + }, + { + Path: []string{"$.credentialSubject.nationality"}, + Filter: &presexch.Filter{ + Type: &strFilterType, + Enum: []presexch.StrOrInt{"Korea"}, + }, + }, + }, + }, + }}, + } + pdBz, err := json.Marshal(presDef) + suite.NoError(err) + + cred := &verifiable.Credential{ + ID: "https://my-verifiable-credential.com", + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + Issuer: verifiable.Issuer{ + ID: suite.issuerDID, + }, + Issued: &util.TimeWrapper{ + Time: time.Time{}, + }, + Subject: map[string]interface{}{ + "id": suite.holderDID, + "first_name": "Hansol", + "last_name": "Lee", + "age": 21, + "nationality": "Korea", + "hobby": "movie", + }, + } - did := "did:panacea:abcd1234" - docRes, err := panaceaVDR.Resolve(did) - require.NoError(t, err) - require.Equal(t, did, docRes.DIDDocument.ID) + vcBz, err := cred.MarshalJSON() + suite.NoError(err) + + panaceaVDR := NewPanaceaVDR(suite.VDR) + framework, err := vc.NewFramework(panaceaVDR) + suite.NoError(err) + + signedVC, err := framework.SignCredential(vcBz, suite.issuerPrivKey, &vc.ProofOptions{ + VerificationMethod: fmt.Sprintf("%s#key1", suite.issuerDID), + SignatureType: ecdsaSigType, + Domain: "https://my-domain.com", + Challenge: "this is a challenge", + Created: "2017-06-18T21:19:10Z", + }) + suite.NoError(err) + + vp, err := framework.CreatePresentationFromPD(signedVC, pdBz) + suite.NoError(err) + + vpBz, err := json.Marshal(vp) + suite.NoError(err) + + signedVP, err := framework.SignPresentation(vpBz, suite.holderPrivKey, &vc.ProofOptions{ + VerificationMethod: fmt.Sprintf("%s#key1", suite.holderDID), + SignatureType: ecdsaSigType, + Domain: "https://my-domain.com", + Challenge: "this is a challenge", + Created: "2017-06-18T21:19:10Z", + }) + suite.NoError(err) + + _, err = framework.VerifyPresentation(signedVP, vc.WithPresentationDefinition(pdBz)) + suite.NoError(err) } var _ didClient = &mockDIDClient{} -type mockDIDClient struct{} +type mockDIDClient struct { + vdr map[string]*didtypes.DIDDocumentWithSeq +} + +func newMockDIDClient(didDocs ...*didtypes.DIDDocumentWithSeq) *mockDIDClient { + vdr := make(map[string]*didtypes.DIDDocumentWithSeq) + mockDIDCli := &mockDIDClient{vdr} + + for _, doc := range didDocs { + mockDIDCli.vdr[doc.Document.Id] = doc + } + + return mockDIDCli +} + +func (m *mockDIDClient) GetDID(_ context.Context, did string) (*didtypes.DIDDocumentWithSeq, error) { + return m.vdr[did], nil +} + +func newMnemonic() (string, error) { + entropy, err := bip39.NewEntropy(256) + if err != nil { + return "", err + } + return bip39.NewMnemonic(entropy) +} + +func generatePrivateKeyFromMnemonic(mnemonic string) (secp256k1.PrivKey, error) { + if !bip39.IsMnemonicValid(mnemonic) { + return nil, fmt.Errorf("invalid mnemonic") + } + + hdPath := hd.NewFundraiserParams(0, 371, 0).String() + master, ch := hd.ComputeMastersFromSeed(bip39.NewSeed(mnemonic, "")) + + return hd.DerivePrivateKeyForPath(master, ch, hdPath) +} -func (m mockDIDClient) GetDID(_ context.Context, did string) (*didtypes.DIDDocumentWithSeq, error) { - didDoc := didtypes.NewDIDDocument(did) +func createDIDDoc(did, pubKeyBase58 string) *didtypes.DIDDocumentWithSeq { return &didtypes.DIDDocumentWithSeq{ - Document: &didDoc, - }, nil + Document: &didtypes.DIDDocument{ + Id: did, + Contexts: &didtypes.JSONStringOrStrings{"https://www.w3.org/ns/did/v1"}, + Authentications: []didtypes.VerificationRelationship{didtypes.NewVerificationRelationship(fmt.Sprintf("%s#key1", did))}, + VerificationMethods: []*didtypes.VerificationMethod{ + { + Controller: did, + Id: fmt.Sprintf("%s#key1", did), + PublicKeyBase58: pubKeyBase58, + Type: ecdsaVerificationType, + }, + }, + }, + Sequence: 0, + } }