diff --git a/auth.go b/auth.go index 9d6dc25..f7d9f57 100644 --- a/auth.go +++ b/auth.go @@ -293,7 +293,7 @@ func (v *Verifier) VerifyAuthResponse( rawMessage = nil } - err = cv.VerifyQuery(ctx, query, v.claimSchemaLoader, rawMessage) + err = cv.VerifyQuery(ctx, query, v.claimSchemaLoader, rawMessage, opts...) if err != nil { return err } diff --git a/auth_test.go b/auth_test.go index 5635239..5db5d54 100644 --- a/auth_test.go +++ b/auth_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "math/big" "testing" + "time" "github.com/google/uuid" "github.com/iden3/go-circuits" @@ -283,6 +284,8 @@ var stateResolvers = map[string]pubsignals.StateResolver{ "polygon:mumbai": &mockStateResolver{}, } +const proofGenerationDelay = time.Hour * 100000 + type mockStateResolver struct { } @@ -613,7 +616,7 @@ func TestVerifyMessageWithMTPProof_Merkalized(t *testing.T) { schemaLoader := &mockJSONLDSchemaLoader{schema: kycV3Schema} authInstance, err := NewVerifierWithExplicitError(verificationKeyloader, schemaLoader, stateResolvers) require.NoError(t, err) - err = authInstance.VerifyAuthResponse(context.Background(), message, request) + err = authInstance.VerifyAuthResponse(context.Background(), message, request, pubsignals.WithAcceptedProofGenerationDelay(proofGenerationDelay)) require.NoError(t, err) } @@ -665,7 +668,7 @@ func TestVerifier_FullVerify(t *testing.T) { schemaLoader := &mockJSONLDSchemaLoader{schema: kycV3Schema} authInstance, err := NewVerifierWithExplicitError(verificationKeyloader, schemaLoader, stateResolvers) require.NoError(t, err) - _, err = authInstance.FullVerify(context.Background(), token, request) + _, err = authInstance.FullVerify(context.Background(), token, request, pubsignals.WithAcceptedProofGenerationDelay(proofGenerationDelay)) require.NoError(t, err) } @@ -702,7 +705,7 @@ func TestVerifier_FullVerify_JWS(t *testing.T) { err = pm.RegisterPackers(jwsPacker) require.NoError(t, err) v.SetPackageManager(pm) - _, err = v.FullVerify(context.Background(), token, request) + _, err = v.FullVerify(context.Background(), token, request, pubsignals.WithAcceptedProofGenerationDelay(proofGenerationDelay)) require.NoError(t, err) } @@ -921,7 +924,7 @@ func TestVerifier_FullVerifySelectiveDisclosure(t *testing.T) { schemaLoader := &mockJSONLDSchemaLoader{schema: kycV4Schema} authInstance, err := NewVerifierWithExplicitError(verificationKeyloader, schemaLoader, stateResolvers) require.NoError(t, err) - _, err = authInstance.FullVerify(context.Background(), token, request) + _, err = authInstance.FullVerify(context.Background(), token, request, pubsignals.WithAcceptedProofGenerationDelay(proofGenerationDelay)) require.NoError(t, err) } @@ -951,6 +954,6 @@ func TestEmptyCredentialSubject(t *testing.T) { schemaLoader := &mockJSONLDSchemaLoader{schema: kycV101Schema} authInstance, err := NewVerifierWithExplicitError(verificationKeyloader, schemaLoader, stateResolvers) require.NoError(t, err) - _, err = authInstance.FullVerify(context.Background(), token, request) + _, err = authInstance.FullVerify(context.Background(), token, request, pubsignals.WithAcceptedProofGenerationDelay(proofGenerationDelay)) require.NoError(t, err) } diff --git a/pubsignals/atomicMtpV2.go b/pubsignals/atomicMtpV2.go index 40b906f..3459697 100644 --- a/pubsignals/atomicMtpV2.go +++ b/pubsignals/atomicMtpV2.go @@ -24,6 +24,7 @@ func (c *AtomicQueryMTPV2) VerifyQuery( query Query, schemaLoader loaders.SchemaLoader, verifiablePresentation json.RawMessage, + opts ...VerifyOpt, ) error { return query.Check(ctx, schemaLoader, &CircuitOutputs{ IssuerID: c.IssuerID, @@ -37,7 +38,7 @@ func (c *AtomicQueryMTPV2) VerifyQuery( ClaimPathNotExists: c.ClaimPathNotExists, ValueArraySize: c.ValueArraySize, IsRevocationChecked: c.IsRevocationChecked, - }, verifiablePresentation) + }, verifiablePresentation, opts...) } // VerifyStates verifies user state and issuer claim issuance state in the smart contract. diff --git a/pubsignals/atomicSigV2.go b/pubsignals/atomicSigV2.go index 9cc376c..ef9519a 100644 --- a/pubsignals/atomicSigV2.go +++ b/pubsignals/atomicSigV2.go @@ -24,6 +24,7 @@ func (c *AtomicQuerySigV2) VerifyQuery( query Query, schemaLoader loaders.SchemaLoader, verifiablePresentation json.RawMessage, + opts ...VerifyOpt, ) error { err := query.Check(ctx, schemaLoader, &CircuitOutputs{ IssuerID: c.IssuerID, @@ -37,7 +38,7 @@ func (c *AtomicQuerySigV2) VerifyQuery( ClaimPathNotExists: c.ClaimPathNotExists, ValueArraySize: c.ValueArraySize, IsRevocationChecked: c.IsRevocationChecked, - }, verifiablePresentation) + }, verifiablePresentation, opts...) if err != nil { return err } diff --git a/pubsignals/authV2.go b/pubsignals/authV2.go index 4e3cb70..3d397ac 100644 --- a/pubsignals/authV2.go +++ b/pubsignals/authV2.go @@ -23,7 +23,8 @@ func (c *AuthV2) VerifyQuery( _ context.Context, _ Query, _ loaders.SchemaLoader, - _ json.RawMessage) error { + _ json.RawMessage, + _ ...VerifyOpt) error { return errors.New("authV2 circuit doesn't support queries") } diff --git a/pubsignals/circuitVerifier.go b/pubsignals/circuitVerifier.go index 67b1da6..550ada6 100644 --- a/pubsignals/circuitVerifier.go +++ b/pubsignals/circuitVerifier.go @@ -18,7 +18,7 @@ type StateResolver interface { // Verifier is interface for verification of public signals of zkp type Verifier interface { - VerifyQuery(ctx context.Context, query Query, schemaLoader loaders.SchemaLoader, verifiablePresentation json.RawMessage) error + VerifyQuery(ctx context.Context, query Query, schemaLoader loaders.SchemaLoader, verifiablePresentation json.RawMessage, opts ...VerifyOpt) error VerifyStates(ctx context.Context, resolvers map[string]StateResolver, opts ...VerifyOpt) error VerifyIDOwnership(userIdentifier string, challenge *big.Int) error diff --git a/pubsignals/query.go b/pubsignals/query.go index a17f621..f5692b6 100644 --- a/pubsignals/query.go +++ b/pubsignals/query.go @@ -8,6 +8,7 @@ import ( "fmt" "math/big" "strconv" + "time" "github.com/iden3/go-circuits" "github.com/iden3/go-iden3-auth/loaders" @@ -86,6 +87,7 @@ func (q Query) Check( loader loaders.SchemaLoader, pubSig *CircuitOutputs, verifiablePresentation json.RawMessage, + opts ...VerifyOpt, ) error { if err := q.verifyIssuer(pubSig); err != nil { return err @@ -112,6 +114,17 @@ func (q Query) Check( return errors.New("check revocation is required") } + cfg := defaultProofVerifyOpts + for _, o := range opts { + o(&cfg) + } + + if time.Since( + time.Unix(pubSig.Timestamp, 0), + ) > cfg.acceptedProofGenerationDelay { + return ErrProofGenerationOutdated + } + return q.verifyClaim(ctx, schemaBytes, pubSig) } diff --git a/pubsignals/query_test.go b/pubsignals/query_test.go index 77fa28f..318df01 100644 --- a/pubsignals/query_test.go +++ b/pubsignals/query_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "testing" + "time" core "github.com/iden3/go-iden3-core" "github.com/stretchr/testify/require" @@ -163,6 +164,7 @@ var vpEmployee = []byte(`{ }`) func TestCheckRequest_Success(t *testing.T) { + now := time.Now().Unix() tests := []struct { name string query Query @@ -192,6 +194,7 @@ func TestCheckRequest_Success(t *testing.T) { Value: []*big.Int{big.NewInt(800)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, }, { @@ -215,6 +218,7 @@ func TestCheckRequest_Success(t *testing.T) { Value: []*big.Int{big.NewInt(800)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, vp: vp, }, @@ -241,6 +245,7 @@ func TestCheckRequest_Success(t *testing.T) { Value: []*big.Int{bigIntTrueHash}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, }, { @@ -267,6 +272,7 @@ func TestCheckRequest_Success(t *testing.T) { }(), Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, vp: vpEmployee, }, @@ -296,6 +302,7 @@ func TestCheckRequest_Success(t *testing.T) { }(), Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, vp: vpEmployee, }, @@ -310,6 +317,9 @@ func TestCheckRequest_Success(t *testing.T) { } func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { + now := time.Now().Unix() + durationMin, _ := time.ParseDuration("-1m") + dayAndMinuteAgo := time.Now().AddDate(0, 0, -1).Add(durationMin).Unix() tests := []struct { name string query Query @@ -317,6 +327,33 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { vp json.RawMessage expErr error }{ + { + name: "Generated proof is outdated", + query: Query{ + AllowedIssuers: []string{"*"}, + CredentialSubject: map[string]interface{}{ + "countryCode": map[string]interface{}{ + "$nin": []interface{}{float64(800)}, + }, + }, + Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", + Type: "KYCCountryOfResidenceCredential", + }, + pubSig: &CircuitOutputs{ + IssuerID: &issuerID, + ClaimSchema: KYCCountrySchema, + ClaimPathKey: func() *big.Int { + v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) + return v + }(), + Operator: 5, + Value: []*big.Int{big.NewInt(800)}, + Merklized: 1, + IsRevocationChecked: 1, + Timestamp: dayAndMinuteAgo, + }, + expErr: errors.New("generated proof is outdated"), + }, { name: "Empty disclosure value for disclosure request", query: Query{ @@ -339,6 +376,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { Value: []*big.Int{big.NewInt(800)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("selective disclosure value is missed"), }, @@ -364,6 +402,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { Value: []*big.Int{big.NewInt(800)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("selective disclosure available only for equal operation"), }, @@ -389,6 +428,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { Value: []*big.Int{big.NewInt(800), big.NewInt(801)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("selective disclosure not available for array of values"), }, @@ -414,6 +454,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { Value: []*big.Int{big.NewInt(1)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("different value between proof and disclosure value"), }, @@ -439,6 +480,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { Value: []*big.Int{big.NewInt(800)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("path '[https://www.w3.org/2018/credentials#verifiableCredential https://www.w3.org/2018/credentials#credentialSubject https://github.com/iden3/claim-schema-vocab/blob/main/credentials/kyc.md#documentType]' doesn't exist in document"), }, @@ -453,6 +495,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { } func TestCheckRequest_Error(t *testing.T) { + now := time.Now().Unix() tests := []struct { name string query Query @@ -479,6 +522,7 @@ func TestCheckRequest_Error(t *testing.T) { pubSig: &CircuitOutputs{ IssuerID: &issuerID, ClaimSchema: KYCCountrySchema, + Timestamp: now, }, expErr: ErrSchemaID, }, @@ -496,6 +540,7 @@ func TestCheckRequest_Error(t *testing.T) { pubSig: &CircuitOutputs{ IssuerID: &issuerID, ClaimSchema: KYCCountrySchema, + Timestamp: now, }, expErr: errors.New("multiple requests not supported"), }, @@ -512,6 +557,7 @@ func TestCheckRequest_Error(t *testing.T) { pubSig: &CircuitOutputs{ IssuerID: &issuerID, ClaimSchema: KYCCountrySchema, + Timestamp: now, }, expErr: errors.New("failed cast type map[string]interface"), }, @@ -531,6 +577,7 @@ func TestCheckRequest_Error(t *testing.T) { pubSig: &CircuitOutputs{ IssuerID: &issuerID, ClaimSchema: KYCCountrySchema, + Timestamp: now, }, expErr: errors.New("multiple predicates for one field not supported"), }, @@ -550,6 +597,7 @@ func TestCheckRequest_Error(t *testing.T) { IssuerID: &issuerID, ClaimSchema: KYCCountrySchema, Operator: 3, + Timestamp: now, }, expErr: ErrRequestOperator, }, @@ -570,6 +618,7 @@ func TestCheckRequest_Error(t *testing.T) { ClaimSchema: KYCCountrySchema, Operator: 5, Value: []*big.Int{big.NewInt(40)}, + Timestamp: now, }, expErr: ErrInvalidValues, }, @@ -593,6 +642,7 @@ func TestCheckRequest_Error(t *testing.T) { Value: []*big.Int{big.NewInt(20)}, Merklized: 1, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("proof was generated for another path"), }, @@ -616,6 +666,7 @@ func TestCheckRequest_Error(t *testing.T) { Merklized: 0, SlotIndex: 0, IsRevocationChecked: 1, + Timestamp: now, }, expErr: errors.New("different slot index for claim"), }, @@ -640,6 +691,7 @@ func TestCheckRequest_Error(t *testing.T) { Merklized: 0, SlotIndex: 0, IsRevocationChecked: 0, + Timestamp: now, }, expErr: errors.New("check revocation is required"), }, @@ -664,6 +716,7 @@ func TestCheckRequest_Error(t *testing.T) { Merklized: 0, SlotIndex: 0, IsRevocationChecked: 0, + Timestamp: now, }, expErr: errors.New("invalid operation '$lt' for field type 'http://www.w3.org/2001/XMLSchema#boolean'"), }, @@ -688,6 +741,7 @@ func TestCheckRequest_Error(t *testing.T) { Merklized: 0, SlotIndex: 0, IsRevocationChecked: 0, + Timestamp: now, }, expErr: ErrNegativeValue, }, diff --git a/pubsignals/signals.go b/pubsignals/signals.go index d536736..4bf7383 100644 --- a/pubsignals/signals.go +++ b/pubsignals/signals.go @@ -18,6 +18,8 @@ var ( ErrIssuerClaimStateIsNotValid = errors.New("issuer state is not valid") // ErrIssuerNonRevocationClaimStateIsNotValid declares that issuer non-revocation state is invalid. ErrIssuerNonRevocationClaimStateIsNotValid = errors.New("issuer state for non-revocation proofs is not valid") + // ErrProofGenerationOutdated declares that generated proof is outdated. + ErrProofGenerationOutdated = errors.New("generated proof is outdated") ) // RegisterVerifier is factory for public signals init. diff --git a/pubsignals/verifyopts.go b/pubsignals/verifyopts.go index b9e50d3..fcb0cb6 100644 --- a/pubsignals/verifyopts.go +++ b/pubsignals/verifyopts.go @@ -4,7 +4,8 @@ import "time" var ( defaultAuthVerifyOpts = VerifyConfig{acceptedStateTransitionDelay: time.Minute * 5} - defaultProofVerifyOpts = VerifyConfig{acceptedStateTransitionDelay: time.Hour} + defaultProofVerifyOpts = VerifyConfig{acceptedStateTransitionDelay: time.Hour, + acceptedProofGenerationDelay: time.Hour * 24} ) // WithAcceptedStateTransitionDelay sets the delay of the revoked state. @@ -14,6 +15,13 @@ func WithAcceptedStateTransitionDelay(duration time.Duration) VerifyOpt { } } +// WithAcceptedProofGenerationDelay sets the delay of the proof generation. +func WithAcceptedProofGenerationDelay(duration time.Duration) VerifyOpt { + return func(v *VerifyConfig) { + v.acceptedProofGenerationDelay = duration + } +} + // VerifyOpt sets options. type VerifyOpt func(v *VerifyConfig) @@ -21,4 +29,5 @@ type VerifyOpt func(v *VerifyConfig) type VerifyConfig struct { // is the period of time that a revoked state remains valid. acceptedStateTransitionDelay time.Duration + acceptedProofGenerationDelay time.Duration }