From 898b55d2849a42c6008fa75315d0403e7ffccccd Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 12 Jan 2024 15:44:42 +0200 Subject: [PATCH] cleanup resolvers & fix IssuerResolver --- go.mod | 3 - go.sum | 7 -- verifiable/credential.go | 3 - verifiable/credential_status.go | 38 +----- verifiable/credential_test.go | 117 ++++++++++++++++-- verifiable/status_agent.go | 99 --------------- verifiable/status_direct.go | 24 +--- verifiable/status_on_chain.go | 211 -------------------------------- verifiable/status_rhs.go | 176 -------------------------- 9 files changed, 116 insertions(+), 562 deletions(-) delete mode 100644 verifiable/status_agent.go delete mode 100644 verifiable/status_on_chain.go delete mode 100644 verifiable/status_rhs.go diff --git a/go.mod b/go.mod index 9c7599f..61b776a 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,7 @@ require ( ) require ( - github.com/google/uuid v1.3.0 - github.com/iden3/go-circuits/v2 v2.0.0 github.com/iden3/iden3comm/v2 v2.0.1-0.20231030214854-7a0511d0e7cc - github.com/iden3/merkletree-proof v0.0.4 github.com/jarcoal/httpmock v1.3.1 ) diff --git a/go.sum b/go.sum index 5d32237..f927fe1 100644 --- a/go.sum +++ b/go.sum @@ -18,10 +18,7 @@ github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/iden3/go-circuits/v2 v2.0.0 h1:Bw0mpsqeip06d6I2ktgfhTVB7Jk9mSHi8myHZWkoc6w= -github.com/iden3/go-circuits/v2 v2.0.0/go.mod h1:VIFIp51+IH0hOzjnKhb84bCeyq7hq76zX/C14ua6zh4= github.com/iden3/go-iden3-core/v2 v2.0.3 h1:ce9Jbw10zDsinWXFc05SiK2Hof/wu4zV4/ai5gQy29k= github.com/iden3/go-iden3-core/v2 v2.0.3/go.mod h1:L9PxhWPvoS9qTb3inEkZBm1RpjHBt+VTwvxssdzbAdw= github.com/iden3/go-iden3-crypto v0.0.15 h1:4MJYlrot1l31Fzlo2sF56u7EVFeHHJkxGXXZCtESgK4= @@ -36,10 +33,6 @@ github.com/iden3/go-rapidsnark/witness/v2 v2.0.0 h1:mkY6VDfwKVJc83QGKmwVXY2LYepi github.com/iden3/go-rapidsnark/witness/wazero v0.0.0-20230524142950-0986cf057d4e h1:WeiFCrpj5pLRtSA4Mg03yTrSZhHHqN/k5b6bwxd9/tY= github.com/iden3/iden3comm/v2 v2.0.1-0.20231030214854-7a0511d0e7cc h1:VciWdH3N9hufuIn6w/SOys8+Bzjy8LD0l1f5aN2sghE= github.com/iden3/iden3comm/v2 v2.0.1-0.20231030214854-7a0511d0e7cc/go.mod h1:wrXoxi8eoQSLopatRW5+hYF9lDRvzGL2As9ZE88q/kA= -github.com/iden3/merkletree-proof v0.0.3 h1:01fSpjv2JIwVBYqV7ugLm0gjF+yo0aw41zDTWP5+81s= -github.com/iden3/merkletree-proof v0.0.3/go.mod h1:cNPG00p4ALlIXnRFmM1T2zy4547jV8cgyOZsObDrORg= -github.com/iden3/merkletree-proof v0.0.4 h1:o1XXws6zb7+BBDQYXlo0GPUH+jUY3+GxkT88U1C4Sb8= -github.com/iden3/merkletree-proof v0.0.4/go.mod h1:D49CVDG/tshMiZuDCxWjGJoNplRg9y9XIlg9/eMSYOc= github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ= github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/verifiable/credential.go b/verifiable/credential.go index f631271..8633a1c 100644 --- a/verifiable/credential.go +++ b/verifiable/credential.go @@ -278,9 +278,6 @@ func resolveDIDDocumentAuth(did, resolverURL string, state *string) (*CommonVeri return nil, err } - // bs, err := json.Marshal(res) - // fmt.Println(string(bs)) - var iden3StateInfo2023 *CommonVerificationMethod for _, a := range res.DIDDocument.VerificationMethod { if a.Type == "Iden3StateInfo2023" { diff --git a/verifiable/credential_status.go b/verifiable/credential_status.go index 62ed886..5c34639 100644 --- a/verifiable/credential_status.go +++ b/verifiable/credential_status.go @@ -6,15 +6,12 @@ import ( "math/big" "strings" - core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-crypto/poseidon" "github.com/iden3/go-merkletree-sql/v2" "github.com/iden3/iden3comm/v2" "github.com/pkg/errors" ) -type hexHash merkletree.Hash - type CredStatusStateResolver interface { GetStateInfoByID(id *big.Int) (StateInfo, error) GetRevocationStatus(id *big.Int, nonce uint64) (RevocationStatus, error) @@ -68,15 +65,6 @@ type CredentialStatusConfig struct { IssuerDID *string } -var errIdentityDoesNotExist = errors.New("identity does not exist") - -func isErrIdentityDoesNotExist(err error) bool { - if err == nil { - return false - } - return err.Error() == "execution reverted: Identity does not exist" -} - type errPathNotFound struct { path string } @@ -111,7 +99,10 @@ func ValidateCredentialStatus(credStatus interface{}, opts ...CredentialStatusOp return revocationStatus, err } - revocationRootHash, err := merkletree.NewHashFromHex(*revocationStatus.Issuer.RevocationTreeRoot) + revocationRootHash := &merkletree.HashZero + if revocationStatus.Issuer.RevocationTreeRoot != nil { + revocationRootHash, err = merkletree.NewHashFromHex(*revocationStatus.Issuer.RevocationTreeRoot) + } if err != nil { return revocationStatus, err } @@ -163,27 +154,6 @@ func resolveRevStatus(status interface{}, config CredentialStatusConfig) (out Re return resolver.Resolve(credentialStatusTyped, config) } -func lastStateFromContract(resolver CredStatusStateResolver, - id *core.ID) (*merkletree.Hash, error) { - var zeroID core.ID - if id == nil || *id == zeroID { - return nil, errors.New("ID is empty") - } - - resp, err := resolver.GetStateInfoByID(id.BigInt()) - if isErrIdentityDoesNotExist(err) { - return nil, errIdentityDoesNotExist - } else if err != nil { - return nil, err - } - - if resp.State == "" { - return nil, errors.New("got empty state") - } - - return merkletree.NewHashFromString(resp.State) -} - // marshal/unmarshal object from one type to other func remarshalObj(dst, src any) error { objBytes, err := json.Marshal(src) diff --git a/verifiable/credential_test.go b/verifiable/credential_test.go index cee9514..bcec8cd 100644 --- a/verifiable/credential_test.go +++ b/verifiable/credential_test.go @@ -50,6 +50,14 @@ func (m credStatusResolverMock) GetRevocationStatusByIDAndState(id *big.Int, sta return RevocationStatus{}, nil } +type test1Resolver struct{} + +func (test1Resolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { + statusJSON := `{"issuer":{"state":"34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409","rootOfRoots":"37eabc712cdaa64793561b16b8143f56f149ad1b0c35297a1b125c765d1c071e","claimsTreeRoot":"4436ea12d352ddb84d2ac7a27bbf7c9f1bfc7d3ff69f3e6cf4348f424317fd0b","revocationTreeRoot":"0000000000000000000000000000000000000000000000000000000000000000"},"mtp":{"existence":false,"siblings":[]}}` + var rs RevocationStatus + _ = json.Unmarshal([]byte(statusJSON), &rs) + return rs, nil +} func TestW3CCredential_ValidateBJJSignatureProof(t *testing.T) { in := `{ "id": "urn:uuid:3a8d1822-a00e-11ee-8f57-a27b3ddbdc29", @@ -131,7 +139,7 @@ func TestW3CCredential_ValidateBJJSignatureProof(t *testing.T) { httpmock.NewStringResponder(200, `{"node":{"hash":"34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409","children":["4436ea12d352ddb84d2ac7a27bbf7c9f1bfc7d3ff69f3e6cf4348f424317fd0b","0000000000000000000000000000000000000000000000000000000000000000","37eabc712cdaa64793561b16b8143f56f149ad1b0c35297a1b125c765d1c071e"]},"status":"OK"}`)) resolverRegisty := CredentialStatusResolverRegistry{} - rhsResolver := RHSResolver{} + rhsResolver := test1Resolver{} resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver) statusResolverMock := credStatusResolverMock{} statusConfigOpts := []CredentialStatusOpt{WithStateResolver(statusResolverMock), WithPackageManager(iden3comm.NewPackageManager()), WithStatusResolverRegistry(&resolverRegisty)} @@ -141,6 +149,14 @@ func TestW3CCredential_ValidateBJJSignatureProof(t *testing.T) { require.True(t, isValid) } +type test2Resolver struct{} + +func (test2Resolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { + statusJSON := `{"issuer":{"state":"da6184809dbad90ccc52bb4dbfe2e8ff3f516d87c74d75bcc68a67101760b817","rootOfRoots":"0000000000000000000000000000000000000000000000000000000000000000","claimsTreeRoot":"aec50251fdc67959254c74ab4f2e746a7cd1c6f494c8ac028d655dfbccea430e","revocationTreeRoot":"0000000000000000000000000000000000000000000000000000000000000000"},"mtp":{"existence":false,"siblings":[]}}` + var rs RevocationStatus + _ = json.Unmarshal([]byte(statusJSON), &rs) + return rs, nil +} func TestW3CCredential_ValidateBJJSignatureProofGenesis(t *testing.T) { in := `{ "id": "urn:uuid:b7a1e232-a0d3-11ee-bc8a-a27b3ddbdc29", @@ -226,7 +242,7 @@ func TestW3CCredential_ValidateBJJSignatureProofGenesis(t *testing.T) { httpmock.NewStringResponder(200, `{"node":{"hash":"da6184809dbad90ccc52bb4dbfe2e8ff3f516d87c74d75bcc68a67101760b817","children":["aec50251fdc67959254c74ab4f2e746a7cd1c6f494c8ac028d655dfbccea430e","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]},"status":"OK"}`)) resolverRegisty := CredentialStatusResolverRegistry{} - rhsResolver := RHSResolver{} + rhsResolver := test2Resolver{} resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver) statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(iden3comm.NewPackageManager())} verifyConfig := []W3CProofVerificationOpt{WithStatusOpts(statusConfigOpts), WithResolverURL(resolverURL)} @@ -336,16 +352,21 @@ func TestW3CCredential_ValidateIden3SparseMerkleTreeProof(t *testing.T) { httpmock.RegisterResponder("GET", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf?state=34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409", httpmock.NewStringResponder(200, `{"@context":"https://w3id.org/did-resolution/v1","didDocument":{"@context":["https://www.w3.org/ns/did/v1","https://schema.iden3.io/core/jsonld/auth.jsonld"],"id":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","verificationMethod":[{"id":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf#stateInfo","type":"Iden3StateInfo2023","controller":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","stateContractAddress":"80001:0x134B1BE34911E39A8397ec6289782989729807a4","published":true,"info":{"id":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","state":"34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409","replacedByState":"0000000000000000000000000000000000000000000000000000000000000000","createdAtTimestamp":"1703174663","replacedAtTimestamp":"0","createdAtBlock":"43840767","replacedAtBlock":"0"},"global":{"root":"92c4610a24247a4013ce6de4903452d164134a232a94fd1fe37178bce4937006","replacedByRoot":"0000000000000000000000000000000000000000000000000000000000000000","createdAtTimestamp":"1704439557","replacedAtTimestamp":"0","createdAtBlock":"44415346","replacedAtBlock":"0"}}]},"didResolutionMetadata":{"contentType":"application/did+ld+json","retrieved":"2024-01-05T07:53:42.67771172Z","pattern":"^(did:polygonid:.+)$","driverUrl":"http://driver-did-polygonid:8080/1.0/identifiers/","duration":442,"did":{"didString":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","methodSpecificId":"polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","method":"polygonid"}},"didDocumentMetadata":{}}`)) - resolverRegisty := CredentialStatusResolverRegistry{} - rhsResolver := RHSResolver{} - resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver) - statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(iden3comm.NewPackageManager())} + statusConfigOpts := []CredentialStatusOpt{WithStateResolver(credStatusResolverMock{}), WithPackageManager(iden3comm.NewPackageManager())} verifyConfig := []W3CProofVerificationOpt{WithStatusOpts(statusConfigOpts), WithResolverURL(resolverURL)} isValid, err := vc.VerifyProof(Iden3SparseMerkleTreeProofType, verifyConfig...) require.NoError(t, err) require.True(t, isValid) } +type test3Resolver struct{} + +func (test3Resolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { + statusJSON := `{"issuer":{"state":"96161f3fbbdd68c72bc430dae474e27b157586b33b9fbf4a3f07d75ce275570f","rootOfRoots":"eaa48e4a7d3fe2fabbd939c7df1048c3f647a9a7c9dfadaae836ec78ba673229","claimsTreeRoot":"d9597e2fef206c9821f2425e513a68c8c793bc93c9216fb883fedaaf72abf51c","revocationTreeRoot":"0000000000000000000000000000000000000000000000000000000000000000"},"mtp":{"existence":false,"siblings":[]}}` + var rs RevocationStatus + _ = json.Unmarshal([]byte(statusJSON), &rs) + return rs, nil +} func TestW3CCredential_ValidateBJJSignatureProofAgentStatus(t *testing.T) { in := `{ "id": "urn:uuid:79d93584-ae2c-11ee-8050-a27b3ddbdc28", @@ -442,7 +463,89 @@ func TestW3CCredential_ValidateBJJSignatureProofAgentStatus(t *testing.T) { err = pckManager.RegisterPackers(&PlainMessagePacker{}) require.NoError(t, err) resolverRegisty := CredentialStatusResolverRegistry{} - resolverRegisty.Register(Iden3commRevocationStatusV1, AgentResolver{}) + resolverRegisty.Register(Iden3commRevocationStatusV1, test3Resolver{}) + statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(pckManager)} + verifyConfig := []W3CProofVerificationOpt{WithStatusOpts(statusConfigOpts), WithResolverURL(resolverURL)} + isValid, err := vc.VerifyProof(BJJSignatureProofType, verifyConfig...) + require.NoError(t, err) + require.True(t, isValid) +} + +func TestW3CCredential_ValidateBJJSignatureProofIssuerStatus(t *testing.T) { + in := `{ + "id": "urn:uuid:c784e54c-b14e-11ee-94df-a27b3ddbdc28", + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld", + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld" + ], + "type": [ + "VerifiableCredential", + "KYCAgeCredential" + ], + "expirationDate": "2361-03-21T21:14:48+02:00", + "issuanceDate": "2024-01-12T15:30:40.800436+02:00", + "credentialSubject": { + "birthday": 19960424, + "documentType": 2, + "id": "did:polygonid:polygon:mumbai:2qDwkysfn58urGGatGYsHKqzYPsy5p3mc9yxZZTeqh", + "type": "KYCAgeCredential" + }, + "credentialStatus": { + "id": "http://localhost:8001/api/v1/identities/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn/claims/revocation/status/1737529009", + "revocationNonce": 1737529009, + "type": "SparseMerkleTreeProof" + }, + "issuer": "did:polygonid:polygon:mumbai:2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn", + "credentialSchema": { + "id": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v3.json", + "type": "JsonSchema2023" + }, + "proof": [ + { + "type": "BJJSignature2021", + "issuerData": { + "id": "did:polygonid:polygon:mumbai:2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn", + "state": { + "claimsTreeRoot": "9af7b27d7176f465dc9acfd7dc937bae5df1d1cd34d682692f1ea6bf7cedf514", + "value": "95e4f8437be5d50a569bb532713110e4f5d2ac97765fae54041dddae9638a119" + }, + "authCoreClaim": "cca3371a6cb1b715004407e325bd993c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d95ae65475a9b380ca6118927f741c06466e951c25bb7b03a1505d597fc078222fe8db4747e2bf9c847308b283a5c17eeba4e50ced3283d24cce665b35f701050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mtp": { + "existence": true, + "siblings": [] + }, + "credentialStatus": { + "id": "http://localhost:8001/api/v1/identities/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn/claims/revocation/status/0", + "revocationNonce": 0, + "type": "SparseMerkleTreeProof" + } + }, + "coreClaim": "c9b2370371b7fa8b3dab2a5ba81b68382a0000000000000000000000000000000212208b10849a2f9bbacd2a583d4177ec460ac4f599d8355cfc39d820d90c00c7f1c984807cf958a96b0850ee8e9f495902a87c3a8f11a2fbcabe10fdea702c0000000000000000000000000000000000000000000000000000000000000000b196906700000000281cdcdf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "signature": "16a3e5cf7638bf843dbff803aeafa9c8735dde795cc9b8638c6b1963f290f890cad183481dee4f6376ed3496296f30170d1558f929486ec8ada00aa1d1104005" + } + ] + }` + var vc W3CCredential + err := json.Unmarshal([]byte(in), &vc) + require.NoError(t, err) + + resolverURL := "http://my-universal-resolver/1.0/identifiers" + + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn?state=95e4f8437be5d50a569bb532713110e4f5d2ac97765fae54041dddae9638a119", + httpmock.NewStringResponder(200, `{"didDocument":{"@context":["https://www.w3.org/ns/did/v1","https://schema.iden3.io/core/jsonld/auth.jsonld"],"id":"did:polygonid:polygon:mumbai:2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn","verificationMethod":[{"id":"did:polygonid:polygon:mumbai:2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn#stateInfo","type":"Iden3StateInfo2023","controller":"did:polygonid:polygon:mumbai:2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn","stateContractAddress":"80001:0x134B1BE34911E39A8397ec6289782989729807a4","published":false,"global":{"root":"40c30e53dc6649842d8f1297f8b4267e7097b6941c413ac032ce53726f826229","replacedByRoot":"0000000000000000000000000000000000000000000000000000000000000000","createdAtTimestamp":"1705061221","replacedAtTimestamp":"0","createdAtBlock":"44690848","replacedAtBlock":"0"}}]}}`)) + + httpmock.RegisterResponder("GET", "http://localhost:8001/api/v1/identities/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn/claims/revocation/status/0", + httpmock.NewStringResponder(200, `{"issuer":{"state":"95e4f8437be5d50a569bb532713110e4f5d2ac97765fae54041dddae9638a119","claimsTreeRoot":"9af7b27d7176f465dc9acfd7dc937bae5df1d1cd34d682692f1ea6bf7cedf514"},"mtp":{"existence":false,"siblings":[]}}`)) + + pckManager := iden3comm.NewPackageManager() + err = pckManager.RegisterPackers(&PlainMessagePacker{}) + require.NoError(t, err) + resolverRegisty := CredentialStatusResolverRegistry{} + resolverRegisty.Register(SparseMerkleTreeProof, IssuerResolver{}) statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(pckManager)} verifyConfig := []W3CProofVerificationOpt{WithStatusOpts(statusConfigOpts), WithResolverURL(resolverURL)} isValid, err := vc.VerifyProof(BJJSignatureProofType, verifyConfig...) diff --git a/verifiable/status_agent.go b/verifiable/status_agent.go deleted file mode 100644 index 9a8d397..0000000 --- a/verifiable/status_agent.go +++ /dev/null @@ -1,99 +0,0 @@ -package verifiable - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/google/uuid" - "github.com/iden3/iden3comm/v2" - "github.com/pkg/errors" -) - -// revocationStatusRequestMessageBody is struct the represents request for revocation status -type revocationStatusRequestMessageBody struct { - RevocationNonce uint64 `json:"revocation_nonce"` -} - -const ( - // RevocationStatusRequestMessageType is type for request of revocation status - revocationStatusRequestMessageType iden3comm.ProtocolMessage = iden3comm.Iden3Protocol + "revocation/1.0/request-status" - // RevocationStatusResponseMessageType is type for response with a revocation status - revocationStatusResponseMessageType iden3comm.ProtocolMessage = iden3comm.Iden3Protocol + "revocation/1.0/status" -) - -// MediaTypePlainMessage is media type for plain message -const mediaTypePlainMessage iden3comm.MediaType = "application/iden3comm-plain-json" - -// RevocationStatusResponseMessageBody is struct the represents request for revocation status -type revocationStatusResponseMessageBody struct { - RevocationStatus -} - -type AgentResolver struct { -} - -func (AgentResolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { - revocationBody := revocationStatusRequestMessageBody{ - RevocationNonce: status.RevocationNonce, - } - rawBody, err := json.Marshal(revocationBody) - if err != nil { - return out, errors.WithStack(err) - } - - msg := iden3comm.BasicMessage{ - ID: uuid.New().String(), - ThreadID: uuid.New().String(), - From: *cfg.UserDID, - To: *cfg.IssuerDID, - Type: revocationStatusRequestMessageType, - Body: rawBody, - } - bytesMsg, err := json.Marshal(msg) - if err != nil { - return out, errors.WithStack(err) - } - - iden3commMsg, err := cfg.PackageManager.Pack(mediaTypePlainMessage, bytesMsg, nil) - if err != nil { - return out, errors.WithStack(err) - } - - resp, err := http.DefaultClient.Post(status.ID, "application/json", bytes.NewBuffer(iden3commMsg)) - if err != nil { - return out, errors.WithStack(err) - } - defer func() { - err2 := resp.Body.Close() - if err != nil { - err = errors.WithStack(err2) - } - }() - - if resp.StatusCode != http.StatusOK { - return out, errors.Errorf("bad status code: %d", resp.StatusCode) - } - - b, err := io.ReadAll(resp.Body) - if err != nil { - return out, errors.WithStack(err) - } - - basicMessage, _, err := cfg.PackageManager.Unpack(b) - if err != nil { - return out, errors.WithStack(err) - } - - if basicMessage.Type != revocationStatusResponseMessageType { - return out, errors.Errorf("unexpected message type: %s", basicMessage.Type) - } - - var revocationStatus revocationStatusResponseMessageBody - if err := json.Unmarshal(basicMessage.Body, &revocationStatus); err != nil { - return out, errors.WithStack(err) - } - - return revocationStatus.RevocationStatus, nil -} diff --git a/verifiable/status_direct.go b/verifiable/status_direct.go index 4d82515..9f6c9e0 100644 --- a/verifiable/status_direct.go +++ b/verifiable/status_direct.go @@ -7,14 +7,13 @@ import ( "io" "net/http" - "github.com/iden3/go-merkletree-sql/v2" "github.com/pkg/errors" ) type IssuerResolver struct { } -func (*IssuerResolver) Resolve(credentialStatus CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { +func (IssuerResolver) Resolve(credentialStatus CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, credentialStatus.ID, http.NoBody) if err != nil { @@ -38,28 +37,9 @@ func (*IssuerResolver) Resolve(credentialStatus CredentialStatus, cfg Credential if err != nil { return out, err } - var obj struct { - TreeState struct { - State *hexHash `json:"state"` // identity state - ClaimsRoot *hexHash `json:"claimsTreeRoot"` // claims tree root - RevocationRoot *hexHash `json:"revocationTreeRoot"` // revocation tree root - RootOfRoots *hexHash `json:"rootOfRoots"` // root of roots tree root - - } `json:"issuer"` - Proof *merkletree.Proof `json:"mtp"` - } - err = json.Unmarshal(respData, &obj) + err = json.Unmarshal(respData, &out) if err != nil { return out, err } - out.MTP = *obj.Proof - stateHashHex := (*merkletree.Hash)(obj.TreeState.State).Hex() - out.Issuer.State = &stateHashHex - claimsRootHashHex := (*merkletree.Hash)(obj.TreeState.ClaimsRoot).Hex() - out.Issuer.ClaimsTreeRoot = &claimsRootHashHex - revocationRootHashHex := (*merkletree.Hash)(obj.TreeState.RevocationRoot).Hex() - out.Issuer.RevocationTreeRoot = &revocationRootHashHex - rootOfRootsHashHex := (*merkletree.Hash)(obj.TreeState.RootOfRoots).Hex() - out.Issuer.RootOfRoots = &rootOfRootsHashHex return out, nil } diff --git a/verifiable/status_on_chain.go b/verifiable/status_on_chain.go deleted file mode 100644 index 4e20639..0000000 --- a/verifiable/status_on_chain.go +++ /dev/null @@ -1,211 +0,0 @@ -package verifiable - -import ( - "encoding/hex" - "fmt" - "math/big" - "net/url" - "strconv" - "strings" - "sync" - - core "github.com/iden3/go-iden3-core/v2" - "github.com/iden3/go-iden3-core/v2/w3c" - "github.com/iden3/go-schema-processor/v2/utils" - "github.com/pkg/errors" -) - -type onChainRevStatus struct { - chainID core.ChainID - contractAddress string - revNonce uint64 - genesisState *big.Int -} - -func isErrInvalidRootsLength(err error) bool { - if err == nil { - return false - } - return err.Error() == "execution reverted: Invalid roots length" -} - -var idsInStateContract = map[core.ID]bool{} -var idsInStateContractLock sync.RWMutex - -type OnChainResolver struct { -} - -func (*OnChainResolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { - parsedIssuerDID, err := w3c.ParseDID(*cfg.IssuerDID) - if err != nil { - return out, err - } - - issuerID, err := core.IDFromDID(*parsedIssuerDID) - if err != nil { - return out, err - } - - var zeroID core.ID - if issuerID == zeroID { - return out, errors.New("issuer ID is empty") - } - - onchainRevStatus, err := newOnchainRevStatusFromURI(status.ID) - if err != nil { - return out, err - } - - if onchainRevStatus.revNonce != status.RevocationNonce { - return out, fmt.Errorf( - "revocationNonce is not equal to the one "+ - "in OnChainCredentialStatus ID {%d} {%d}", - onchainRevStatus.revNonce, status.RevocationNonce) - } - - isStateContractHasID, err := stateContractHasID(&issuerID, cfg.StateResolver) - if err != nil { - return out, err - } - - var resp RevocationStatus - if isStateContractHasID { - resp, err = cfg.StateResolver.GetRevocationStatus(issuerID.BigInt(), - onchainRevStatus.revNonce) - if err != nil { - msg := err.Error() - if isErrInvalidRootsLength(err) { - msg = "roots were not saved to identity tree store" - } - return out, fmt.Errorf( - "GetRevocationProof smart contract call [GetRevocationStatus]: %s", - msg) - } - } else { - if onchainRevStatus.genesisState == nil { - return out, errors.New( - "genesis state is not specified in OnChainCredentialStatus ID") - } - resp, err = cfg.StateResolver.GetRevocationStatusByIDAndState( - issuerID.BigInt(), onchainRevStatus.genesisState, - onchainRevStatus.revNonce) - if err != nil { - return out, fmt.Errorf( - "GetRevocationProof smart contract call [GetRevocationStatusByIdAndState]: %s", - err.Error()) - } - } - - return resp, nil -} - -func newOnchainRevStatusFromURI(stateID string) (onChainRevStatus, error) { - var s onChainRevStatus - - uri, err := url.Parse(stateID) - if err != nil { - return s, errors.New("OnChainCredentialStatus ID is not a valid URI") - } - - contract := uri.Query().Get("contractAddress") - if contract == "" { - return s, errors.New("OnChainCredentialStatus contract address is empty") - } - - contractParts := strings.Split(contract, ":") - if len(contractParts) != 2 { - return s, errors.New( - "OnChainCredentialStatus contract address is not valid") - } - - s.chainID, err = newChainIDFromString(contractParts[0]) - if err != nil { - return s, err - } - s.contractAddress = contractParts[1] - - revocationNonce := uri.Query().Get("revocationNonce") - if revocationNonce == "" { - return s, errors.New("revocationNonce is empty in OnChainCredentialStatus ID") - } - - s.revNonce, err = strconv.ParseUint(revocationNonce, 10, 64) - if err != nil { - return s, errors.New("revocationNonce is not a number in OnChainCredentialStatus ID") - } - - // state may be nil if params is absent in query - s.genesisState, err = newIntFromHexQueryParam(uri, "state") - if err != nil { - return s, err - } - - return s, nil -} - -func newChainIDFromString(in string) (core.ChainID, error) { - var chainID uint64 - var err error - if strings.HasPrefix(in, "0x") || - strings.HasPrefix(in, "0X") { - chainID, err = strconv.ParseUint(in[2:], 16, 64) - if err != nil { - return 0, err - } - } else { - chainID, err = strconv.ParseUint(in, 10, 64) - if err != nil { - return 0, err - } - } - return core.ChainID(chainID), nil -} - -// newIntFromHexQueryParam search for query param `paramName`, parse it -// as hex string of LE bytes of *big.Int. Return nil if param is not found. -func newIntFromHexQueryParam(uri *url.URL, paramName string) (*big.Int, error) { - stateParam := uri.Query().Get(paramName) - if stateParam == "" { - return nil, nil - } - - stateParam = strings.TrimSuffix(stateParam, "0x") - stateBytes, err := hex.DecodeString(stateParam) - if err != nil { - return nil, err - } - - return newIntFromBytesLE(stateBytes), nil -} - -func newIntFromBytesLE(bs []byte) *big.Int { - return new(big.Int).SetBytes(utils.SwapEndianness(bs)) -} - -func stateContractHasID(id *core.ID, resolver CredStatusStateResolver) (bool, error) { - - idsInStateContractLock.RLock() - ok := idsInStateContract[*id] - idsInStateContractLock.RUnlock() - if ok { - return ok, nil - } - - idsInStateContractLock.Lock() - defer idsInStateContractLock.Unlock() - - ok = idsInStateContract[*id] - if ok { - return ok, nil - } - - _, err := lastStateFromContract(resolver, id) - if errors.Is(err, errIdentityDoesNotExist) { - return false, nil - } else if err != nil { - return false, err - } - - idsInStateContract[*id] = true - return true, err -} diff --git a/verifiable/status_rhs.go b/verifiable/status_rhs.go deleted file mode 100644 index a2f28ec..0000000 --- a/verifiable/status_rhs.go +++ /dev/null @@ -1,176 +0,0 @@ -package verifiable - -import ( - "bytes" - "context" - "math/big" - "net/url" - "strings" - "time" - - core "github.com/iden3/go-iden3-core/v2" - "github.com/iden3/go-iden3-core/v2/w3c" - "github.com/iden3/go-merkletree-sql/v2" - mp "github.com/iden3/merkletree-proof/http" - "github.com/pkg/errors" -) - -type RHSResolver struct { -} - -func (RHSResolver) Resolve(status CredentialStatus, cfg CredentialStatusConfig) (out RevocationStatus, err error) { - parsedIssuerDID, err := w3c.ParseDID(*cfg.IssuerDID) - if err != nil { - return out, err - } - - issuerID, err := core.IDFromDID(*parsedIssuerDID) - if err != nil { - return out, err - } - - revNonce := new(big.Int).SetUint64(status.RevocationNonce) - - baseRHSURL, genesisState, err := rhsBaseURL(status.ID) - if err != nil { - return out, err - } - - state, err := identityStateForRHS(cfg.StateResolver, &issuerID, genesisState) - if err != nil { - return out, err - } - - rhsCli, err := newRhsCli(baseRHSURL) - if err != nil { - return out, err - } - - out.Issuer, err = issuerFromRHS(context.Background(), rhsCli, state) - if errors.Is(err, mp.ErrNodeNotFound) { - if genesisState != nil && state.Equals(genesisState) { - return out, errors.New("genesis state is not found in RHS") - } else { - return out, errors.New("current state is not found in RHS") - } - } else if err != nil { - return out, err - } - - revNonceHash, err := merkletree.NewHashFromBigInt(revNonce) - if err != nil { - return out, err - } - - revTreeRootHash, err := merkletree.NewHashFromHex(*out.Issuer.RevocationTreeRoot) - if err != nil { - return out, err - } - proof, err := rhsCli.GenerateProof(context.Background(), revTreeRootHash, - revNonceHash) - if err != nil { - return out, err - } - - out.MTP = *proof - - return out, nil -} - -func identityStateForRHS(resolver CredStatusStateResolver, issuerID *core.ID, - genesisState *merkletree.Hash) (*merkletree.Hash, error) { - - state, err := lastStateFromContract(resolver, issuerID) - if !errors.Is(err, errIdentityDoesNotExist) { - return state, err - } - - if genesisState == nil { - return nil, errors.New("current state is not found for the identity") - } - - stateIsGenesis, err := genesisStateMatch(genesisState, *issuerID) - if err != nil { - return nil, err - } - - if !stateIsGenesis { - return nil, errors.New("state is not genesis for the identity") - } - - return genesisState, nil -} - -// check if genesis state matches the state from the ID -func genesisStateMatch(state *merkletree.Hash, id core.ID) (bool, error) { - var tp [2]byte - copy(tp[:], id[:2]) - otherID, err := core.NewIDFromIdenState(tp, state.BigInt()) - if err != nil { - return false, err - } - return bytes.Equal(otherID[:], id[:]), nil -} - -func issuerFromRHS(ctx context.Context, rhsCli *mp.ReverseHashCli, - state *merkletree.Hash) (Issuer, error) { - - var issuer Issuer - - stateNode, err := rhsCli.GetNode(ctx, state) - if err != nil { - return issuer, err - } - - if len(stateNode.Children) != 3 { - return issuer, errors.New( - "invalid state node, should have 3 children") - } - - stateHex := state.Hex() - issuer.State = &stateHex - claimsTreeRootHex := stateNode.Children[0].Hex() - issuer.ClaimsTreeRoot = &claimsTreeRootHex - revocationTreeRootHex := stateNode.Children[1].Hex() - issuer.RevocationTreeRoot = &revocationTreeRootHex - rootOfRootsHex := stateNode.Children[2].Hex() - issuer.RootOfRoots = &rootOfRootsHex - - return issuer, err -} - -func newRhsCli(rhsURL string) (*mp.ReverseHashCli, error) { - if rhsURL == "" { - return nil, errors.New("reverse hash service url is empty") - } - - return &mp.ReverseHashCli{ - URL: rhsURL, - HTTPTimeout: 10 * time.Second, - }, nil -} - -func rhsBaseURL(rhsURL string) (string, *merkletree.Hash, error) { - u, err := url.Parse(rhsURL) - if err != nil { - return "", nil, err - } - var state *merkletree.Hash - stateStr := u.Query().Get("state") - if stateStr != "" { - state, err = merkletree.NewHashFromHex(stateStr) - if err != nil { - return "", nil, err - } - } - - if strings.HasSuffix(u.Path, "/node") { - u.Path = strings.TrimSuffix(u.Path, "node") - } - if strings.HasSuffix(u.Path, "/node/") { - u.Path = strings.TrimSuffix(u.Path, "node/") - } - - u.RawQuery = "" - return u.String(), state, nil -}