From 8a7ec1379f5c0fe55071d1ffad52dc7e101b15ae Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:47:38 +0200 Subject: [PATCH] split v2 and v3 verification --- go.mod | 3 +- go.sum | 4 +- pubsignals/atomicMtpV2.go | 2 +- pubsignals/atomicSigV2.go | 2 +- pubsignals/atomicV3.go | 2 +- pubsignals/common.go | 65 +++++- pubsignals/linkedMultiQuery.go | 75 +++++-- pubsignals/query.go | 286 +++---------------------- pubsignals/queryCredentialSubjectV2.go | 108 ++++++++++ pubsignals/queryCredentialSubjectV3.go | 97 +++++++++ pubsignals/query_test.go | 8 +- 11 files changed, 367 insertions(+), 285 deletions(-) create mode 100644 pubsignals/queryCredentialSubjectV2.go create mode 100644 pubsignals/queryCredentialSubjectV3.go diff --git a/go.mod b/go.mod index 277fbf8..3a82ba9 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/iden3/contracts-abi/state/go/abi v1.0.0-beta.3 - github.com/iden3/go-circuits/v2 v2.0.2-0.20240216144730-74af9e108017 - github.com/iden3/go-circuits/v2 v2.0.2-0.20240131165639-deb061a1b3e6 + github.com/iden3/go-circuits/v2 v2.0.2-0.20240222121329-c549fb9a0bde github.com/iden3/go-iden3-core/v2 v2.0.3 github.com/iden3/go-iden3-crypto v0.0.15 github.com/iden3/go-jwz/v2 v2.0.1 diff --git a/go.sum b/go.sum index 3c1652c..08250fb 100644 --- a/go.sum +++ b/go.sum @@ -109,10 +109,10 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= github.com/iden3/contracts-abi/state/go/abi v1.0.0-beta.3 h1:ZHFnK2dU3NJglY+igY48JLHWtNGN/Vhf5/L/qrFk/tM= github.com/iden3/contracts-abi/state/go/abi v1.0.0-beta.3/go.mod h1:TxgIrXCvxms3sbOdsy8kTvffUCIpEEifNy0fSXdkU4w= -github.com/iden3/go-circuits/v2 v2.0.2-0.20240131165639-deb061a1b3e6 h1:DoyzPqOZWkUBGAQc2UQbddWAD3HYF5QX1zP9b+KmSH8= -github.com/iden3/go-circuits/v2 v2.0.2-0.20240131165639-deb061a1b3e6/go.mod h1:VIFIp51+IH0hOzjnKhb84bCeyq7hq76zX/C14ua6zh4= github.com/iden3/go-circuits/v2 v2.0.2-0.20240216144730-74af9e108017 h1:FGQ58mmZWCEHgnXqmpBD5+oTlYLjaa5AkC4M5Fwf7Zw= github.com/iden3/go-circuits/v2 v2.0.2-0.20240216144730-74af9e108017/go.mod h1:VIFIp51+IH0hOzjnKhb84bCeyq7hq76zX/C14ua6zh4= +github.com/iden3/go-circuits/v2 v2.0.2-0.20240222121329-c549fb9a0bde h1:/PF8qkQSj/MvMO9xnagGmc5hFq2/+NxPEsLpb7hF0yo= +github.com/iden3/go-circuits/v2 v2.0.2-0.20240222121329-c549fb9a0bde/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= diff --git a/pubsignals/atomicMtpV2.go b/pubsignals/atomicMtpV2.go index 43d1608..a13a1fd 100644 --- a/pubsignals/atomicMtpV2.go +++ b/pubsignals/atomicMtpV2.go @@ -42,7 +42,7 @@ func (c *AtomicQueryMTPV2) VerifyQuery( IsRevocationChecked: c.IsRevocationChecked, } - err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, false, opts...) + err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, circuits.AtomicQueryMTPV2CircuitID, opts...) return CircuitVerificationResult{}, err } diff --git a/pubsignals/atomicSigV2.go b/pubsignals/atomicSigV2.go index 49c814d..4928b24 100644 --- a/pubsignals/atomicSigV2.go +++ b/pubsignals/atomicSigV2.go @@ -41,7 +41,7 @@ func (c *AtomicQuerySigV2) VerifyQuery( ValueArraySize: c.ValueArraySize, IsRevocationChecked: c.IsRevocationChecked, } - err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, false, opts...) + err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, circuits.AtomicQuerySigV2CircuitID, opts...) return CircuitVerificationResult{}, err } diff --git a/pubsignals/atomicV3.go b/pubsignals/atomicV3.go index 5149296..51b4599 100644 --- a/pubsignals/atomicV3.go +++ b/pubsignals/atomicV3.go @@ -52,7 +52,7 @@ func (c *AtomicQueryV3) VerifyQuery( Nullifier: c.Nullifier, ProofType: c.ProofType, } - err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, true, opts...) + err := query.Check(ctx, schemaLoader, &pubSig, verifiablePresentation, circuits.AtomicQueryV3CircuitID, opts...) if err != nil { return output, err } diff --git a/pubsignals/common.go b/pubsignals/common.go index e724a57..d010a8f 100644 --- a/pubsignals/common.go +++ b/pubsignals/common.go @@ -1,10 +1,12 @@ package pubsignals import ( + "bytes" "context" "encoding/json" "fmt" "math/big" + "strconv" "github.com/iden3/go-circuits/v2" "github.com/iden3/go-iden3-crypto/poseidon" @@ -173,7 +175,7 @@ func ParseQueriesMetadata(ctx context.Context, credentialType, ldContextJSON str func transformQueryValueToBigInts(_ context.Context, value any, ldType string) (out []*big.Int, err error) { if value == nil { - return out, nil + return make([]*big.Int, 0), nil } listOfValues, ok := value.([]interface{}) @@ -201,6 +203,33 @@ func transformQueryValueToBigInts(_ context.Context, value any, ldType string) ( return []*big.Int{hashValue}, err } +func isPositiveInteger(v interface{}) bool { + number, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64) + if err != nil { + // value is not a number + return true + } + return number >= 0 +} + +// IsValidOperation checks if operation and type are supported. +func IsValidOperation(typ string, op int) bool { + if op == circuits.NOOP { + return true + } + + ops, ok := availableTypesOperations[typ] + if !ok { + // by default all unknown types will be considered as string + ops = availableTypesOperations[ld.XSDString] + _, ok = ops[op] + return ok + } + + _, ok = ops[op] + return ok +} + func getKeyByValue(m map[string]int, targetValue int) (string, bool) { for key, value := range m { if value == targetValue { @@ -237,3 +266,37 @@ func CalculateQueryHash( }) } + +func fieldValueFromVerifiablePresentation(ctx context.Context, verifiablePresentation json.RawMessage, schemaLoader ld.DocumentLoader, key string) (*big.Int, error) { + if verifiablePresentation == nil { + return nil, errors.New("selective disclosure value is missed") + } + + mz, err := merklize.MerklizeJSONLD(ctx, + bytes.NewBuffer(verifiablePresentation), + merklize.WithDocumentLoader(schemaLoader)) + if err != nil { + return nil, errors.Errorf("failed to merklize doc: %v", err) + } + + merklizedPath, err := merklize.Options{DocumentLoader: schemaLoader}. + NewPathFromDocument(verifiablePresentation, + fmt.Sprintf("verifiableCredential.credentialSubject.%s", key)) + if err != nil { + return nil, errors.Errorf("failed build path to '%s' key: %v", key, err) + } + + proof, valueByPath, err := mz.Proof(ctx, merklizedPath) + if err != nil { + return nil, errors.Errorf("failed get raw value: %v", err) + } + if !proof.Existence { + return nil, errors.Errorf("path '%v' doesn't exist in document", merklizedPath.Parts()) + } + + mvBig, err := valueByPath.MtEntry() + if err != nil { + return nil, errors.Errorf("failed to hash value: %v", err) + } + return mvBig, nil +} diff --git a/pubsignals/linkedMultiQuery.go b/pubsignals/linkedMultiQuery.go index 84762c3..028ddfd 100644 --- a/pubsignals/linkedMultiQuery.go +++ b/pubsignals/linkedMultiQuery.go @@ -11,6 +11,7 @@ import ( "github.com/iden3/go-schema-processor/v2/merklize" "github.com/iden3/go-schema-processor/v2/utils" "github.com/piprate/json-gold/ld" + "github.com/pkg/errors" ) // LinkedMultiQuery is a wrapper for circuits.LinkedMultiQueryPubSignals. @@ -23,7 +24,7 @@ func (c *LinkedMultiQuery) VerifyQuery( ctx context.Context, query Query, schemaLoader ld.DocumentLoader, - _ json.RawMessage, + vp json.RawMessage, _ map[string]interface{}, _ ...VerifyOpt, ) (CircuitVerificationResult, error) { @@ -57,10 +58,21 @@ func (c *LinkedMultiQuery) VerifyQuery( return outputs, err } - queryHashes := []*big.Int{} + requests := []QueryRequest{} + querySignalsMeta := make(queryMetaPubSignals, len(c.CircuitQueryHash)) + for i, q := range c.CircuitQueryHash { + querySignalsMeta[i] = struct { + OperatorOutput *big.Int + QueryHash *big.Int + }{OperatorOutput: c.OperatorOutput[i], QueryHash: q} + } + for i := 0; i < circuits.LinkedMultiQueryLength; i++ { if i >= len(queriesMetadata) { - queryHashes = append(queryHashes, big.NewInt(0)) + requests = append(requests, struct { + QueryMetadata *QueryMetadata + QueryHash *big.Int + }{QueryMetadata: nil, QueryHash: big.NewInt(0)}) continue } @@ -81,25 +93,40 @@ func (c *LinkedMultiQuery) VerifyQuery( return outputs, err } - queryHashes = append(queryHashes, queryHash) + requests = append(requests, struct { + QueryMetadata *QueryMetadata + QueryHash *big.Int + }{QueryMetadata: &queriesMetadata[i], QueryHash: queryHash}) } - circuitQueryHashArray := make(bigIntArray, len(c.CircuitQueryHash)) - copy(circuitQueryHashArray, c.CircuitQueryHash) - sort.Sort(circuitQueryHashArray) + sortedPubsignalsMetadata := make(queryMetaPubSignals, len(c.CircuitQueryHash)) + copy(sortedPubsignalsMetadata, querySignalsMeta) + sort.Sort(sortedPubsignalsMetadata) - calcQueryHashArray := make(bigIntArray, len(queryHashes)) - copy(calcQueryHashArray, queryHashes) - sort.Sort(calcQueryHashArray) + sortedRequests := make(queryRequests, len(requests)) + copy(sortedRequests, requests) + sort.Sort(sortedRequests) - if circuitQueryHashArray.Len() != calcQueryHashArray.Len() { + if sortedPubsignalsMetadata.Len() != sortedRequests.Len() { return outputs, fmt.Errorf("query hashes do not match") } - for i := 0; i < circuitQueryHashArray.Len(); i++ { - if circuitQueryHashArray[i].Cmp(calcQueryHashArray[i]) != 0 { + for i := 0; i < sortedPubsignalsMetadata.Len(); i++ { + if sortedPubsignalsMetadata[i].QueryHash.Cmp(sortedRequests[i].QueryHash) != 0 { return outputs, fmt.Errorf("query hashes do not match") } + + if sortedRequests[i].QueryMetadata != nil && sortedRequests[i].QueryMetadata.Operator == circuits.SD { + disclosedValue, err2 := fieldValueFromVerifiablePresentation(ctx, vp, schemaLoader, sortedRequests[i].QueryMetadata.FieldName) + if err2 != nil { + return outputs, err2 + } + if disclosedValue.Cmp(sortedPubsignalsMetadata[i].OperatorOutput) != 0 { + return outputs, errors.New("disclosed value is not in the proof outputs") + + } + } + } outputs = CircuitVerificationResult{ @@ -109,11 +136,25 @@ func (c *LinkedMultiQuery) VerifyQuery( return outputs, nil } -type bigIntArray []*big.Int +type QueryRequest struct { + QueryMetadata *QueryMetadata + QueryHash *big.Int +} +type QueryMetaPubSignals struct { + OperatorOutput *big.Int + QueryHash *big.Int +} +type queryMetaPubSignals []QueryMetaPubSignals + +func (q queryMetaPubSignals) Len() int { return len(q) } +func (q queryMetaPubSignals) Swap(i, j int) { q[i], q[j] = q[j], q[i] } +func (q queryMetaPubSignals) Less(i, j int) bool { return q[i].QueryHash.Cmp(q[j].QueryHash) < 0 } + +type queryRequests []QueryRequest -func (a bigIntArray) Len() int { return len(a) } -func (a bigIntArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a bigIntArray) Less(i, j int) bool { return a[i].Cmp(a[j]) < 0 } +func (q queryRequests) Len() int { return len(q) } +func (q queryRequests) Swap(i, j int) { q[i], q[j] = q[j], q[i] } +func (q queryRequests) Less(i, j int) bool { return q[i].QueryHash.Cmp(q[j].QueryHash) < 0 } // VerifyStates verifies user state and issuer auth claim state in the smart contract. func (c *LinkedMultiQuery) VerifyStates(_ context.Context, _ map[string]StateResolver, _ ...VerifyOpt) error { diff --git a/pubsignals/query.go b/pubsignals/query.go index 3a06ab8..0b5780d 100644 --- a/pubsignals/query.go +++ b/pubsignals/query.go @@ -1,17 +1,14 @@ package pubsignals import ( - "bytes" "context" "encoding/json" "fmt" "math/big" - "strconv" "time" "github.com/iden3/go-circuits/v2" core "github.com/iden3/go-iden3-core/v2" - parser "github.com/iden3/go-schema-processor/v2/json" "github.com/iden3/go-schema-processor/v2/merklize" "github.com/iden3/go-schema-processor/v2/utils" "github.com/piprate/json-gold/ld" @@ -54,7 +51,7 @@ var ( // ErrValuesSize proof was created for different values. ErrValuesSize = errors.New("query asked proof about more values") // ErrInvalidValues proof was created for different values. - ErrInvalidValues = errors.New("proof was generated for anther values") + ErrInvalidValues = errors.New("proof was generated for another values") // ErrNegativeValue only positive integers allowed. ErrNegativeValue = errors.New("negative values not supported") ) @@ -103,11 +100,11 @@ type CircuitOutputs struct { // Would be good to use ctx for external http requests, but current interfaces // doesn't allow to do it. Left it for future. func (q Query) Check( - _ context.Context, + ctx context.Context, loader ld.DocumentLoader, pubSig *CircuitOutputs, verifiablePresentation json.RawMessage, - supportSdOperator bool, + circuitID circuits.CircuitID, opts ...VerifyOpt, ) error { if err := q.verifyIssuer(pubSig); err != nil { @@ -127,14 +124,22 @@ func (q Query) Check( if err := q.verifySchemaID(schemaBytes, pubSig, loader); err != nil { return err } + if !q.SkipClaimRevocationCheck && pubSig.IsRevocationChecked == 0 { + return errors.New("check revocation is required") + } - if err := q.verifyCredentialSubject(pubSig, verifiablePresentation, - schemaBytes, loader, supportSdOperator); err != nil { + queriesMetadata, err := ParseQueriesMetadata(ctx, q.Type, string(schemaBytes), q.CredentialSubject, merklize.Options{DocumentLoader: loader}) + if err != nil { return err } - if !q.SkipClaimRevocationCheck && pubSig.IsRevocationChecked == 0 { - return errors.New("check revocation is required") + if len(queriesMetadata) > 1 { + return errors.New("multiple requests not supported") + } + + var metadata QueryMetadata + if len(queriesMetadata) == 1 { + metadata = queriesMetadata[0] } cfg := defaultProofVerifyOpts @@ -148,50 +153,36 @@ func (q Query) Check( return ErrProofGenerationOutdated } - return q.verifyClaim(schemaBytes, pubSig, loader) -} - -func (q Query) verifyClaim(schemaBytes []byte, pubSig *CircuitOutputs, - schemaLoader ld.DocumentLoader) error { + switch circuitID { + case circuits.AtomicQueryV3CircuitID: + err = verifyCredentialSubjectV3(pubSig, verifiablePresentation, loader, metadata) + case circuits.AtomicQueryMTPV2CircuitID, circuits.AtomicQuerySigV2CircuitID: + err = verifyCredentialSubjectV2(pubSig, verifiablePresentation, loader, metadata) - if len(q.CredentialSubject) == 0 { - return nil + default: + return errors.Errorf("circuit id %s is not supported", circuitID) } - fieldName, _, err := extractQueryFields(q.CredentialSubject) if err != nil { return err } + return q.verifyClaimInclusion(pubSig, metadata) +} - if pubSig.Merklized == 1 { - path, err := merklize.Options{DocumentLoader: schemaLoader}. - FieldPathFromContext(schemaBytes, q.Type, fieldName) - if err != nil { - return err - } - - err = path.Prepend(PathToSubjectType) - if err != nil { - return err - } +func (q Query) verifyClaimInclusion(pubSig *CircuitOutputs, + metadata QueryMetadata) error { - mkPath, err := path.MtEntry() - if err != nil { - return err - } + if pubSig.Merklized == 1 { - if mkPath.Cmp(pubSig.ClaimPathKey) != 0 { + if metadata.ClaimPathKey.Cmp(pubSig.ClaimPathKey) != 0 { return errors.New("proof was generated for another path") } if pubSig.ClaimPathNotExists == 1 { return errors.New("proof doesn't contains target query key") } } else { - slotIndex, err := parser.Parser{}.GetFieldSlotIndex(fieldName, q.Type, schemaBytes) - if err != nil { - return errors.Errorf("failed to get field slot: %v", err) - } - if slotIndex != pubSig.SlotIndex { + + if metadata.SlotIndex != pubSig.SlotIndex { return errors.New("proof was generated for another slot") } } @@ -226,220 +217,3 @@ func (q Query) verifySchemaID(schemaBytes []byte, pubSig *CircuitOutputs, } return ErrSchemaID } - -func (q Query) verifyCredentialSubject( - pubSig *CircuitOutputs, - verifiablePresentation json.RawMessage, - ctxBytes []byte, - schemaLoader ld.DocumentLoader, - supportSdOperator bool, -) error { - - ctx := context.Background() - - queriesMetadata, err := ParseQueriesMetadata(ctx, q.Type, string(ctxBytes), q.CredentialSubject, merklize.Options{DocumentLoader: schemaLoader}) - if err != nil { - return err - } - - if len(queriesMetadata) > 1 { - return errors.New("multiple requests not supported") - } - - var metadata QueryMetadata - if len(queriesMetadata) == 1 { - metadata = queriesMetadata[0] - } - - // validate selectivity disclosure request - if metadata.Operator == circuits.SD { - return q.validateDisclosure(ctx, pubSig, metadata.FieldName, - verifiablePresentation, schemaLoader, supportSdOperator) - } - - // validate empty credential subject request - if q.isEmptyCredentialSubject(metadata.Operator, pubSig.Merklized) { - return q.verifyEmptyCredentialSubject(pubSig) - } - - if metadata.Operator != pubSig.Operator { - return ErrRequestOperator - } - - if metadata.Operator == circuits.NOOP { - return nil - } - - if len(metadata.Values) > len(pubSig.Value) { - return ErrValuesSize - } - - if len(metadata.Values) < pubSig.ValueArraySize { - diff := pubSig.ValueArraySize - len(metadata.Values) - for diff > 0 { - metadata.Values = append(metadata.Values, big.NewInt(0)) - diff-- - } - } - - for i := 0; i < len(metadata.Values); i++ { - if metadata.Values[i].Cmp(pubSig.Value[i]) != 0 { - return ErrInvalidValues - } - } - - return nil -} - -func (q Query) validateDisclosure(ctx context.Context, pubSig *CircuitOutputs, - key string, verifiablePresentation json.RawMessage, - schemaLoader ld.DocumentLoader, suppordSdOperator bool) error { - - if verifiablePresentation == nil { - return errors.New("selective disclosure value is missed") - } - - mz, err := merklize.MerklizeJSONLD(ctx, - bytes.NewBuffer(verifiablePresentation), - merklize.WithDocumentLoader(schemaLoader)) - if err != nil { - return errors.Errorf("failed to merklize doc: %v", err) - } - - merklizedPath, err := merklize.Options{DocumentLoader: schemaLoader}. - NewPathFromDocument(verifiablePresentation, - fmt.Sprintf("verifiableCredential.credentialSubject.%s", key)) - if err != nil { - return errors.Errorf("failed build path to '%s' key: %v", key, err) - } - - proof, valueByPath, err := mz.Proof(ctx, merklizedPath) - if err != nil { - return errors.Errorf("failed get raw value: %v", err) - } - if !proof.Existence { - return errors.Errorf("path '%v' doesn't exist in document", merklizedPath.Parts()) - } - - mvBig, err := valueByPath.MtEntry() - if err != nil { - return errors.Errorf("failed to hash value: %v", err) - } - - if !suppordSdOperator { - if pubSig.Operator != circuits.EQ { - return errors.New("selective disclosure available only for equal operation") - } - - for i := 1; i < len(pubSig.Value); i++ { - if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { - return errors.New("selective disclosure not available for array of values") - } - } - - if pubSig.Value[0].Cmp(mvBig) != 0 { - return errors.New("different value between proof and disclosure value") - } - - } else { - if pubSig.Operator != circuits.SD { - return errors.New("invalid pub signal operator for selective disclosure") - } - - if pubSig.OperatorOutput == nil || pubSig.OperatorOutput.Cmp(mvBig) != 0 { - return errors.New("operator output should be equal to disclosed value") - } - for i := 0; i < len(pubSig.Value); i++ { - if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { - return errors.New("selective disclosure values should be zero") - } - } - - } - - return nil -} - -func (q Query) verifyEmptyCredentialSubject( - pubSig *CircuitOutputs, -) error { - if pubSig.Operator != circuits.EQ { - return errors.New("empty credentialSubject request available only for equal operation") - } - - for i := 1; i < len(pubSig.Value); i++ { - if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { - return errors.New("empty credentialSubject request not available for array of values") - } - } - - p, err := merklize.NewPath("https://www.w3.org/2018/credentials#credentialSubject") - if err != nil { - return err - } - bi, err := p.MtEntry() - if err != nil { - return err - } - - if pubSig.ClaimPathKey.Cmp(bi) != 0 { - return errors.New("proof doesn't contain credentialSubject in claimPathKey") - } - - return nil -} - -func (q Query) isEmptyCredentialSubject( - operator, - isMerklized int, -) bool { - return q.CredentialSubject == nil && operator == circuits.NOOP && isMerklized == 1 -} - -func extractQueryFields(req map[string]interface{}) (fieldName string, fieldPredicate map[string]interface{}, err error) { - - if len(req) > 1 { - return "", nil, errors.New("multiple requests not supported") - } - - for field, body := range req { - fieldName = field - var ok bool - fieldPredicate, ok = body.(map[string]interface{}) - if !ok { - return "", nil, errors.New("failed cast type map[string]interface") - } - if len(fieldPredicate) > 1 { - return "", nil, errors.New("multiple predicates for one field not supported") - } - break - } - return fieldName, fieldPredicate, nil -} - -func isPositiveInteger(v interface{}) bool { - number, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64) - if err != nil { - // value is not a number - return true - } - return number >= 0 -} - -// IsValidOperation checks if operation and type are supported. -func IsValidOperation(typ string, op int) bool { - if op == circuits.NOOP { - return true - } - - ops, ok := availableTypesOperations[typ] - if !ok { - // by default all unknown types will be considered as string - ops = availableTypesOperations[ld.XSDString] - _, ok = ops[op] - return ok - } - - _, ok = ops[op] - return ok -} diff --git a/pubsignals/queryCredentialSubjectV2.go b/pubsignals/queryCredentialSubjectV2.go new file mode 100644 index 0000000..5d685c4 --- /dev/null +++ b/pubsignals/queryCredentialSubjectV2.go @@ -0,0 +1,108 @@ +package pubsignals + +import ( + "context" + "encoding/json" + "math/big" + + "github.com/iden3/go-circuits/v2" + "github.com/iden3/go-schema-processor/v2/merklize" + "github.com/piprate/json-gold/ld" + "github.com/pkg/errors" +) + +func verifyCredentialSubjectV2( + pubSig *CircuitOutputs, + verifiablePresentation json.RawMessage, + schemaLoader ld.DocumentLoader, + metadata QueryMetadata, +) error { + + ctx := context.Background() + + // validate selectivity disclosure request + if metadata.Operator == circuits.SD { + return validateDisclosureV2(ctx, pubSig, metadata.FieldName, + verifiablePresentation, schemaLoader) + } + + // validate empty credential subject request + if pubSig.Operator == circuits.NOOP && metadata.FieldName == "" && pubSig.Merklized == 1 { + return verifyEmptyCredentialSubjectV2(pubSig, metadata.Path) + } + + if metadata.Operator != pubSig.Operator { + return ErrRequestOperator + } + + if len(metadata.Values) > len(pubSig.Value) { + return ErrValuesSize + } + + if len(metadata.Values) < pubSig.ValueArraySize { + diff := pubSig.ValueArraySize - len(metadata.Values) + for diff > 0 { + metadata.Values = append(metadata.Values, big.NewInt(0)) + diff-- + } + } + + for i := 0; i < len(metadata.Values); i++ { + if metadata.Values[i].Cmp(pubSig.Value[i]) != 0 { + return ErrInvalidValues + } + } + + return nil +} + +func validateDisclosureV2(ctx context.Context, pubSig *CircuitOutputs, + key string, verifiablePresentation json.RawMessage, + schemaLoader ld.DocumentLoader) error { + + mvBig, err := fieldValueFromVerifiablePresentation(ctx, verifiablePresentation, schemaLoader, key) + if err != nil { + return err + } + + if pubSig.Operator != circuits.EQ { + return errors.New("selective disclosure available only for equal operation") + } + + for i := 1; i < len(pubSig.Value); i++ { + if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { + return errors.New("selective disclosure not available for array of values") + } + } + + if pubSig.Value[0].Cmp(mvBig) != 0 { + return errors.New("different value between proof and disclosure value") + } + return nil +} + +func verifyEmptyCredentialSubjectV2( + pubSig *CircuitOutputs, + credSubjectPath *merklize.Path, +) error { + if pubSig.Operator != circuits.EQ { + return errors.New("empty credentialSubject request available only for equal operation") + } + + for i := 1; i < len(pubSig.Value); i++ { + if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { + return errors.New("empty credentialSubject request not available for array of values") + } + } + + bi, err := credSubjectPath.MtEntry() + if err != nil { + return err + } + + if pubSig.ClaimPathKey.Cmp(bi) != 0 { + return errors.New("proof doesn't contain credentialSubject in claimPathKey") + } + + return nil +} diff --git a/pubsignals/queryCredentialSubjectV3.go b/pubsignals/queryCredentialSubjectV3.go new file mode 100644 index 0000000..4dbc2f4 --- /dev/null +++ b/pubsignals/queryCredentialSubjectV3.go @@ -0,0 +1,97 @@ +package pubsignals + +import ( + "context" + "encoding/json" + "math/big" + + "github.com/iden3/go-circuits/v2" + "github.com/piprate/json-gold/ld" + "github.com/pkg/errors" +) + +func verifyCredentialSubjectV3( + pubSig *CircuitOutputs, + verifiablePresentation json.RawMessage, + schemaLoader ld.DocumentLoader, + metadata QueryMetadata, +) error { + + ctx := context.Background() + + // validate selectivity disclosure request + if metadata.Operator == circuits.SD { + return validateDisclosureV3(ctx, pubSig, metadata.FieldName, + verifiablePresentation, schemaLoader) + } + + // validate empty credential subject request + if pubSig.Operator == circuits.NOOP && metadata.FieldName == "" { + return verifyEmptyCredentialSubjectV3(pubSig) + } + + if metadata.Operator != pubSig.Operator { + return ErrRequestOperator + } + + if len(metadata.Values) > len(pubSig.Value) { + return ErrValuesSize + } + + if pubSig.ValueArraySize != len(metadata.Values) { + return errors.Errorf("values that used are not matching with expected in query. Size of value array size is different, expected %v, got %v ", len(metadata.Values), pubSig.ValueArraySize) + } + + for i := 0; i < pubSig.ValueArraySize; i++ { + if metadata.Values[i].Cmp(pubSig.Value[i]) != 0 { + return ErrInvalidValues + } + } + + for i := pubSig.ValueArraySize; i < len(pubSig.Value); i++ { + if pubSig.Value[i].Cmp(new(big.Int)) != 0 { + return errors.New("signal values other then values queries must be set to zero.") + } + } + + return nil +} + +func validateDisclosureV3(ctx context.Context, pubSig *CircuitOutputs, + key string, verifiablePresentation json.RawMessage, + schemaLoader ld.DocumentLoader) error { + + mvBig, err2 := fieldValueFromVerifiablePresentation(ctx, verifiablePresentation, schemaLoader, key) + if err2 != nil { + return err2 + } + + if pubSig.Operator != circuits.SD { + return errors.New("invalid pub signal operator for selective disclosure") + } + + if pubSig.OperatorOutput == nil || pubSig.OperatorOutput.Cmp(mvBig) != 0 { + return errors.New("operator output should be equal to disclosed value") + } + for i := 0; i < len(pubSig.Value); i++ { + if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { + return errors.New("public signal values must be zero") + } + } + return nil +} + +func verifyEmptyCredentialSubjectV3( + pubSig *CircuitOutputs, +) error { + if pubSig.Operator != circuits.NOOP { + return errors.New("empty credentialSubject request available only for equal operation") + } + + for i := 1; i < len(pubSig.Value); i++ { + if pubSig.Value[i].Cmp(big.NewInt(0)) != 0 { + return errors.New("empty credentialSubject request not available for array of values") + } + } + return nil +} diff --git a/pubsignals/query_test.go b/pubsignals/query_test.go index 6e7fd5c..b824178 100644 --- a/pubsignals/query_test.go +++ b/pubsignals/query_test.go @@ -97,7 +97,7 @@ func TestCheckRequest_Success(t *testing.T) { loader *mockJSONLDSchemaLoader }{ { - name: "Check merkalized query", + name: "Check merklized query", query: Query{ AllowedIssuers: []string{"*"}, CredentialSubject: map[string]interface{}{ @@ -304,7 +304,7 @@ func TestCheckRequest_Success(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, tt.vp, false) + err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, tt.vp, circuits.AtomicQuerySigV2CircuitID) require.NoError(t, err) tt.loader.assert(t) }) @@ -536,7 +536,7 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, tt.vp, false) + err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, tt.vp, circuits.AtomicQuerySigV2CircuitID) require.EqualError(t, err, tt.expErr.Error()) tt.loader.assert(t) }) @@ -839,7 +839,7 @@ func TestCheckRequest_Error(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, nil, false) + err := tt.query.Check(context.Background(), tt.loader, tt.pubSig, nil, circuits.AtomicQuerySigV2CircuitID) require.EqualError(t, err, tt.expErr.Error()) tt.loader.assert(t) })