Skip to content

Commit

Permalink
refactor with CredentialStatusResolverRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
volodymyr-basiuk committed Jan 11, 2024
1 parent 8ae5447 commit c04426f
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 91 deletions.
20 changes: 16 additions & 4 deletions verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/iden3/go-merkletree-sql/v2"
"github.com/iden3/go-schema-processor/v2/merklize"
"github.com/iden3/iden3comm/v2"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -90,6 +89,11 @@ func (vc *W3CCredential) VerifyProof(ctx context.Context, proofType ProofType, r
}

func verifyBJJSignatureProof(proof BJJSignatureProof2021, coreClaim *core.Claim, userDID, resolverURL string, opts ...W3CProofVerificationOpt) (bool, error) {
verifyConfig := W3CProofVerificationConfig{}
for _, o := range opts {
o(&verifyConfig)
}

// issuer claim
authClaim := &core.Claim{}
err := authClaim.FromHex(proof.IssuerData.AuthCoreClaim)
Expand Down Expand Up @@ -141,7 +145,9 @@ func verifyBJJSignatureProof(proof BJJSignatureProof2021, coreClaim *core.Claim,
}

// validate credential status
_, err = ValidateCredentialStatus(context.Background(), proof.IssuerData.CredentialStatus, userDID, proof.IssuerData.ID, opts...)
verifyConfig.credentialStatusConfig.issuerDID = &proof.IssuerData.ID
verifyConfig.credentialStatusConfig.userDID = &userDID
_, err = ValidateCredentialStatus(proof.IssuerData.CredentialStatus, *verifyConfig.credentialStatusConfig)

if err != nil {
return false, err
Expand Down Expand Up @@ -369,11 +375,17 @@ type RevocationStatus struct {
MTP merkletree.Proof `json:"mtp"`
}

// WithStatusConfig return new options
func WithStatusConfig(config *CredentialStatusConfig) W3CProofVerificationOpt {
return func(opts *W3CProofVerificationConfig) {
opts.credentialStatusConfig = config
}
}

// W3CProofVerificationOpt returns configuration options for W3C proof verification
type W3CProofVerificationOpt func(opts *W3CProofVerificationConfig)

// W3CProofVerificationConfig options for W3C proof verification
type W3CProofVerificationConfig struct {
Resolver CredStatusResolver
packageManager iden3comm.PackageManager
credentialStatusConfig *CredentialStatusConfig
}
126 changes: 59 additions & 67 deletions verifiable/credential_status.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package verifiable

import (
"context"
"encoding/json"
"fmt"
"math/big"
Expand All @@ -10,7 +9,6 @@ import (

"github.com/iden3/go-circuits/v2"
core "github.com/iden3/go-iden3-core/v2"
"github.com/iden3/go-iden3-core/v2/w3c"
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/iden3/go-merkletree-sql/v2"
"github.com/iden3/iden3comm/v2"
Expand All @@ -26,36 +24,62 @@ type OnChainRevStatus struct {
genesisState *big.Int
}

type CredStatusResolver interface {
type CredStatusStateResolver interface {
GetStateInfoByID(id *big.Int) (StateInfo, error)
GetRevocationStatus(id *big.Int, nonce uint64) (RevocationStatus, error)
GetRevocationStatusByIDAndState(id *big.Int, state *big.Int, nonce uint64) (RevocationStatus, error)
}

// WithResolver return new options
func WithResolver(resolver CredStatusResolver) W3CProofVerificationOpt {
return func(opts *W3CProofVerificationConfig) {
opts.Resolver = resolver
// WithStatusResolverRegistry return new options
func WithStatusResolverRegistry(registry *CredentialStatusResolverRegistry) CredentialStatusOpt {
return func(opts *CredentialStatusConfig) {
opts.statusResolverRegistry = registry
}
}

// WithStateResolver return new options
func WithStateResolver(resolver CredStatusStateResolver) CredentialStatusOpt {
return func(opts *CredentialStatusConfig) {
opts.stateResolver = resolver
}
}

// WithPackageManager return new options
func WithPackageManager(pm iden3comm.PackageManager) W3CProofVerificationOpt {
return func(opts *W3CProofVerificationConfig) {
func WithPackageManager(pm *iden3comm.PackageManager) CredentialStatusOpt {
return func(opts *CredentialStatusConfig) {
opts.packageManager = pm
}
}

var idsInStateContract = map[core.ID]bool{}
var idsInStateContractLock sync.RWMutex
// WithUserDID return new options
func WithUserDID(userDID *string) CredentialStatusOpt {
return func(opts *CredentialStatusConfig) {
opts.userDID = userDID
}
}

var supportedCredentialStatusTypes = map[CredentialStatusType]bool{
Iden3ReverseSparseMerkleTreeProof: true,
SparseMerkleTreeProof: true,
Iden3OnchainSparseMerkleTreeProof2023: true,
Iden3commRevocationStatusV1: true,
// WithIssuerDID return new options
func WithIssuerDID(issuerDID *string) CredentialStatusOpt {
return func(opts *CredentialStatusConfig) {
opts.issuerDID = issuerDID
}
}

// CredentialStatusOpt returns configuration options for CredentialStatusConfig
type CredentialStatusOpt func(opts *CredentialStatusConfig)

// CredentialStatusConfig options for credential status verification
type CredentialStatusConfig struct {
statusResolverRegistry *CredentialStatusResolverRegistry
stateResolver CredStatusStateResolver
packageManager *iden3comm.PackageManager
userDID *string
issuerDID *string
}

var idsInStateContract = map[core.ID]bool{}
var idsInStateContractLock sync.RWMutex

var errIdentityDoesNotExist = errors.New("identity does not exist")

func isErrIdentityDoesNotExist(err error) bool {
Expand All @@ -80,15 +104,8 @@ func (e errPathNotFound) Error() string {
return fmt.Sprintf("path not found: %v", e.path)
}

func ValidateCredentialStatus(ctx context.Context, credStatus interface{},
userDID, issuerDID string, config ...W3CProofVerificationOpt) (circuits.MTProof, error) {

cfg := W3CProofVerificationConfig{}
for _, o := range config {
o(&cfg)
}

proof, err := resolveRevStatus(ctx, cfg.Resolver, credStatus, userDID, issuerDID, &cfg.packageManager)
func ValidateCredentialStatus(credStatus interface{}, config CredentialStatusConfig) (circuits.MTProof, error) {
proof, err := resolveRevStatus(credStatus, config)
if err != nil {
return proof, err
}
Expand All @@ -100,8 +117,6 @@ func ValidateCredentialStatus(ctx context.Context, credStatus interface{},
return proof, errors.New("signature proof: invalid tree state of the issuer while checking credential status of singing key")
}

// revocationNonce is float64, but if we meet valid string representation
// of Int, we will use it.
credStatusObj, ok := credStatus.(jsonObj)
if !ok {
return proof, fmt.Errorf("invali credential status")
Expand All @@ -124,64 +139,41 @@ func ValidateCredentialStatus(ctx context.Context, credStatus interface{},
return proof, nil
}

func resolveRevStatus(ctx context.Context, resolver CredStatusResolver,
credStatus interface{}, userDID, issuerDID string, packageManager *iden3comm.PackageManager) (circuits.MTProof, error) {
func resolveRevStatus(status interface{}, config CredentialStatusConfig) (circuits.MTProof, error) {
var statusType CredentialStatusType
var credentialStatusTyped CredentialStatus

parsedIssuerDID, err := w3c.ParseDID(issuerDID)
if err != nil {
return circuits.MTProof{}, err
}

issuerID, err := core.IDFromDID(*parsedIssuerDID)
if err != nil {
return circuits.MTProof{}, err
}

switch status := credStatus.(type) {
switch status := status.(type) {
case *CredentialStatus:
if status.Type == Iden3ReverseSparseMerkleTreeProof {
revNonce := new(big.Int).SetUint64(status.RevocationNonce)
return resolveRevStatusFromRHS(ctx, status.ID, resolver, &issuerID,
revNonce)
}
if status.Type == Iden3OnchainSparseMerkleTreeProof2023 {
return resolverOnChainRevocationStatus(resolver, &issuerID, status)
}
if status.Type == Iden3commRevocationStatusV1 {
return resolveRevocationStatusFromAgent(userDID, issuerDID, status, packageManager)
}
return resolveRevocationStatusFromIssuerService(ctx, status.ID)

statusType = status.Type
credentialStatusTyped = *status
case CredentialStatus:
return resolveRevStatus(ctx, resolver, &status, userDID, issuerDID, packageManager)

statusType = status.Type
credentialStatusTyped = status
case jsonObj:
credStatusType, ok := status["type"].(string)
if !ok {
return circuits.MTProof{},
errors.New("credential status doesn't contain type")
}
credentialStatusType := CredentialStatusType(credStatusType)
if !supportedCredentialStatusTypes[credentialStatusType] {
return circuits.MTProof{}, fmt.Errorf(
"credential status type %s id not supported",
credStatusType)
}

var typedCredentialStatus CredentialStatus
err := remarshalObj(&typedCredentialStatus, status)
statusType = CredentialStatusType(credStatusType)
err := remarshalObj(&credentialStatusTyped, status)
if err != nil {
return circuits.MTProof{}, err
}
return resolveRevStatus(ctx, resolver, &typedCredentialStatus, userDID, issuerDID, packageManager)

default:
return circuits.MTProof{},
errors.New("unknown credential status format")
}

resolver, err := config.statusResolverRegistry.Get(statusType)
if err != nil {
return circuits.MTProof{}, err
}
return resolver.Resolve(credentialStatusTyped, config)
}

func lastStateFromContract(resolver CredStatusResolver,
func lastStateFromContract(resolver CredStatusStateResolver,
id *core.ID) (*merkletree.Hash, error) {
var zeroID core.ID
if id == nil || *id == zeroID {
Expand Down
53 changes: 41 additions & 12 deletions verifiable/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,20 @@ func TestW3CCredential_ValidateBJJSignatureProof(t *testing.T) {
httpmock.RegisterResponder("GET", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf?state=f9dd6aa4e1abef52b6c94ab7eb92faf1a283b371d263e25ac835c9c04894741e",
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-05T08:05:13.413770024Z","pattern":"^(did:polygonid:.+)$","driverUrl":"http://driver-did-polygonid:8080/1.0/identifiers/","duration":429,"did":{"didString":"did:polygonid:polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","methodSpecificId":"polygon:mumbai:2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf","method":"polygonid"}},"didDocumentMetadata":{}}`))

// httpmock.RegisterMatcherResponder("POST", "http://my-rpc/v2/1111",
// httpmock.BodyContainsString(`{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"data":"0xb4bdea550010961e749448c0c935c85ae263d271b383a2f1fa92ebb74ac9b652efab1202","from":"0x0000000000000000000000000000000000000000","to":"0x134b1be34911e39a8397ec6289782989729807a4"},"latest"]}`),
// httpmock.NewStringResponder(200, `{"jsonrpc":"2.0","id":1,"result":"0x0010961e749448c0c935c85ae263d271b383a2f1fa92ebb74ac9b652efab120209444d55300819a07594d731c0bf7d3713f5e9324e0435f926c3ef1d8e4a823400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065846207000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029cf4ff0000000000000000000000000000000000000000000000000000000000000000"}`))

httpmock.RegisterResponder("GET", "https://rhs-staging.polygonid.me/node/34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409",
httpmock.NewStringResponder(200, `{"node":{"hash":"34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409","children":["4436ea12d352ddb84d2ac7a27bbf7c9f1bfc7d3ff69f3e6cf4348f424317fd0b","0000000000000000000000000000000000000000000000000000000000000000","37eabc712cdaa64793561b16b8143f56f149ad1b0c35297a1b125c765d1c071e"]},"status":"OK"}`))

config := []W3CProofVerificationOpt{WithResolver(credStatusResolverMock{}), WithPackageManager(*iden3comm.NewPackageManager())}
isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, config...)
resolverRegisty := CredentialStatusResolverRegistry{}
rhsResolver := RHSResolver{}
resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver)
statusResolverMock := credStatusResolverMock{}
statusConfigOpts := []CredentialStatusOpt{WithStateResolver(statusResolverMock), WithPackageManager(iden3comm.NewPackageManager()), WithStatusResolverRegistry(&resolverRegisty)}
statusConfig := CredentialStatusConfig{}
for _, o := range statusConfigOpts {
o(&statusConfig)
}
verifyConfig := []W3CProofVerificationOpt{WithStatusConfig(&statusConfig)}
isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, verifyConfig...)
require.NoError(t, err)
require.True(t, isValid)
}
Expand Down Expand Up @@ -224,8 +229,17 @@ func TestW3CCredential_ValidateBJJSignatureProofGenesis(t *testing.T) {
httpmock.RegisterResponder("GET", "https://rhs-staging.polygonid.me/node/da6184809dbad90ccc52bb4dbfe2e8ff3f516d87c74d75bcc68a67101760b817",
httpmock.NewStringResponder(200, `{"node":{"hash":"da6184809dbad90ccc52bb4dbfe2e8ff3f516d87c74d75bcc68a67101760b817","children":["aec50251fdc67959254c74ab4f2e746a7cd1c6f494c8ac028d655dfbccea430e","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]},"status":"OK"}`))

config := []W3CProofVerificationOpt{WithResolver(credStatusResolverMock{}), WithPackageManager(*iden3comm.NewPackageManager())}
isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, config...)
resolverRegisty := CredentialStatusResolverRegistry{}
rhsResolver := RHSResolver{}
resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver)
statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(iden3comm.NewPackageManager())}
statusConfig := CredentialStatusConfig{}
for _, o := range statusConfigOpts {
o(&statusConfig)
}
verifyConfig := []W3CProofVerificationOpt{WithStatusConfig(&statusConfig)}

isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, verifyConfig...)
require.NoError(t, err)
require.True(t, isValid)
}
Expand Down Expand Up @@ -330,8 +344,16 @@ 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":{}}`))

config := []W3CProofVerificationOpt{WithResolver(credStatusResolverMock{}), WithPackageManager(*iden3comm.NewPackageManager())}
isValid, err := vc.VerifyProof(context.Background(), Iden3SparseMerkleTreeProofType, resolverURL, config...)
resolverRegisty := CredentialStatusResolverRegistry{}
rhsResolver := RHSResolver{}
resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver)
statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(iden3comm.NewPackageManager())}
statusConfig := CredentialStatusConfig{}
for _, o := range statusConfigOpts {
o(&statusConfig)
}
verifyConfig := []W3CProofVerificationOpt{WithStatusConfig(&statusConfig)}
isValid, err := vc.VerifyProof(context.Background(), Iden3SparseMerkleTreeProofType, resolverURL, verifyConfig...)
require.NoError(t, err)
require.True(t, isValid)
}
Expand Down Expand Up @@ -431,8 +453,15 @@ func TestW3CCredential_ValidateBJJSignatureProofAgentStatus(t *testing.T) {
pckManager := iden3comm.NewPackageManager()
err = pckManager.RegisterPackers(&PlainMessagePacker{})
require.NoError(t, err)
config := []W3CProofVerificationOpt{WithResolver(credStatusResolverMock{}), WithPackageManager(*pckManager)}
isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, config...)
resolverRegisty := CredentialStatusResolverRegistry{}
resolverRegisty.Register(Iden3commRevocationStatusV1, AgentResolver{})
statusConfigOpts := []CredentialStatusOpt{WithStatusResolverRegistry(&resolverRegisty), WithStateResolver(credStatusResolverMock{}), WithPackageManager(pckManager)}
statusConfig := CredentialStatusConfig{}
for _, o := range statusConfigOpts {
o(&statusConfig)
}
verifyConfig := []W3CProofVerificationOpt{WithStatusConfig(&statusConfig)}
isValid, err := vc.VerifyProof(context.Background(), BJJSignatureProofType, resolverURL, verifyConfig...)
require.NoError(t, err)
require.True(t, isValid)
}
Expand Down
32 changes: 32 additions & 0 deletions verifiable/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package verifiable

import (
"fmt"

"github.com/iden3/go-circuits/v2"
)

// CredentialStatusResolver is an interface that allows to interact with deifferent types of credential status to resolve revocation status
type CredentialStatusResolver interface {
Resolve(credentialStatus CredentialStatus, cfg CredentialStatusConfig) (circuits.MTProof, error)
}

// CredentialStatusResolverRegistry is a registry of CredentialStatusResolver
type CredentialStatusResolverRegistry struct {
resolvers map[CredentialStatusType]*CredentialStatusResolver
}

func (r *CredentialStatusResolverRegistry) Register(resolverType CredentialStatusType, resolver CredentialStatusResolver) {
if len(r.resolvers) == 0 {
r.resolvers = make(map[CredentialStatusType]*CredentialStatusResolver)
}
r.resolvers[resolverType] = &resolver
}

func (r *CredentialStatusResolverRegistry) Get(resolverType CredentialStatusType) (CredentialStatusResolver, error) {
resolver, ok := r.resolvers[resolverType]
if !ok {
return nil, fmt.Errorf("credential status type %s id not registered", resolverType)
}
return *resolver, nil
}
Loading

0 comments on commit c04426f

Please sign in to comment.