diff --git a/pubsignals/common.go b/pubsignals/common.go index b453548..427df1a 100644 --- a/pubsignals/common.go +++ b/pubsignals/common.go @@ -45,7 +45,7 @@ func ParseCredentialSubject(_ context.Context, credentialSubject any) (out []Pro if credentialSubject == nil { return []PropertyQuery{ { - Operator: circuits.EQ, + Operator: circuits.NOOP, FieldName: "", }, }, nil @@ -58,16 +58,11 @@ func ParseCredentialSubject(_ context.Context, credentialSubject any) (out []Pro return nil, errors.New("Failed to convert credential subject to JSONObject") } - if len(jsonObject) == 0 { - return []PropertyQuery{ - { - Operator: circuits.EQ, - FieldName: "", - }, - }, nil - } for fieldName, fieldReq := range jsonObject { - fieldReqEntries := fieldReq.(map[string]interface{}) + fieldReqEntries, ok := fieldReq.(map[string]interface{}) + if !ok { + return nil, errors.New("failed cast type map[string]interface") + } isSelectiveDisclosure := len(fieldReqEntries) == 0 if isSelectiveDisclosure { @@ -150,7 +145,8 @@ func ParseQueryMetadata(ctx context.Context, propertyQuery PropertyQuery, ldCont if propertyQuery.OperatorValue != nil { if !IsValidOperation(datatype, propertyQuery.Operator) { - return nil, fmt.Errorf("operator %d is not supported for datatype %s", propertyQuery.Operator, datatype) + operatorName, _ := getKeyByValue(circuits.QueryOperators, propertyQuery.Operator) + return nil, fmt.Errorf("invalid operation '%s' for field type '%s'", operatorName, datatype) } } @@ -186,15 +182,24 @@ func transformQueryValueToBigInts(_ context.Context, value any, ldType string) ( out[i] = big.NewInt(0) } - if reflect.TypeOf(value).Kind() == reflect.Array { + if value == nil { + return out, nil + } + if reflect.TypeOf(value).Kind() == reflect.Array || reflect.TypeOf(value).Kind() == reflect.Slice { v := reflect.ValueOf(value) for i := 0; i < v.Len(); i++ { + if !isPositiveInteger(v) { + return nil, ErrNegativeValue + } out[i], err = merklize.HashValue(ldType, v.Index(i).Interface()) if err != nil { return nil, err } } } else { + if !isPositiveInteger(value) { + return nil, ErrNegativeValue + } out[0], err = merklize.HashValue(ldType, value) if err != nil { return nil, err @@ -243,3 +248,12 @@ func getSerializationAttrFromParsedContext(ldCtx *ld.Context, return "", nil } + +func getKeyByValue(m map[string]int, targetValue int) (string, bool) { + for key, value := range m { + if value == targetValue { + return key, true + } + } + return "", false +} diff --git a/pubsignals/query.go b/pubsignals/query.go index 1cc38c4..b64b6d6 100644 --- a/pubsignals/query.go +++ b/pubsignals/query.go @@ -229,59 +229,48 @@ func (q Query) verifyCredentialSubject( schemaLoader ld.DocumentLoader, supportSdOperator bool, ) error { - fieldName, predicate, err := extractQueryFields(q.CredentialSubject) + + ctx := context.Background() + + queriesMetadata, err := ParseQueriesMetadata(ctx, q.Type, string(ctxBytes), q.CredentialSubject, merklize.Options{DocumentLoader: schemaLoader}) if err != nil { return err } - var fieldType string - if fieldName != "" { - fieldType, err = merklize.Options{DocumentLoader: schemaLoader}. - TypeFromContext(ctxBytes, fmt.Sprintf("%s.%s", q.Type, fieldName)) - if err != nil { - return err + if len(queriesMetadata) > 1 { + if queriesMetadata[0].FieldName == queriesMetadata[1].FieldName { + return errors.New("multiple predicates for one field not supported") } + return errors.New("multiple requests not supported") + } + + var metadata QueryMetadata + + if len(queriesMetadata) == 1 { + metadata = queriesMetadata[0] } // validate selectivity disclosure request - if q.isSelectivityDisclosure(predicate) { - ctx := context.Background() - return q.validateDisclosure(ctx, pubSig, fieldName, + if metadata.Operator == circuits.SD { + return q.validateDisclosure(ctx, pubSig, metadata.FieldName, verifiablePresentation, schemaLoader, supportSdOperator) } // validate empty credential subject request - if q.isEmptyCredentialSubject(predicate, pubSig.Merklized) { + if q.isEmptyCredentialSubject(metadata.Operator, pubSig.Merklized) { return q.verifyEmptyCredentialSubject(pubSig) } - values, operator, err := parseFieldPredicate(fieldType, predicate) - if err != nil { - return err - } - - if operator != pubSig.Operator { + if metadata.Operator != pubSig.Operator { return ErrRequestOperator } - if operator == circuits.NOOP { + if metadata.Operator == circuits.NOOP { return nil } - if len(values) > len(pubSig.Value) { - return ErrValuesSize - } - - if len(values) < pubSig.ValueArraySize { - diff := pubSig.ValueArraySize - len(values) - for diff > 0 { - values = append(values, big.NewInt(0)) - diff-- - } - } - - for i := 0; i < len(values); i++ { - if values[i].Cmp(pubSig.Value[i]) != 0 { + for i := 0; i < len(metadata.Values); i++ { + if metadata.Values[i].Cmp(pubSig.Value[i]) != 0 { return ErrInvalidValues } } @@ -387,46 +376,11 @@ func (q Query) verifyEmptyCredentialSubject( return nil } -func (q Query) isSelectivityDisclosure( - predicate map[string]interface{}) bool { - return q.CredentialSubject != nil && len(predicate) == 0 -} - func (q Query) isEmptyCredentialSubject( - predicate map[string]interface{}, + operator, isMerklized int, ) bool { - return q.CredentialSubject == nil && len(predicate) == 0 && isMerklized == 1 -} - -func parseFieldPredicate( - fieldType string, - fieldPredicate map[string]interface{}, -) ( - values []*big.Int, - operator int, - err error, -) { - for op, v := range fieldPredicate { - var ok bool - operator, ok = circuits.QueryOperators[op] - if !ok { - return nil, 0, errors.New("query operator is not supported") - } - - if !IsValidOperation(fieldType, operator) { - return nil, 0, errors.Errorf("invalid operation '%s' for field type '%s'", op, fieldType) - } - - values, err = getValuesAsArray(v, fieldType) - if err != nil { - return nil, 0, err - } - - // only one predicate for field is supported - break - } - return values, operator, nil + return q.CredentialSubject == nil && operator == circuits.NOOP && isMerklized == 1 } func extractQueryFields(req map[string]interface{}) (fieldName string, fieldPredicate map[string]interface{}, err error) { @@ -450,37 +404,6 @@ func extractQueryFields(req map[string]interface{}) (fieldName string, fieldPred return fieldName, fieldPredicate, nil } -func getValuesAsArray(v interface{}, valueType string) ([]*big.Int, error) { - var values []*big.Int - - listOfValues, ok := v.([]interface{}) - if ok { - values = make([]*big.Int, len(listOfValues)) - for i, item := range listOfValues { - if !isPositiveInteger(item) { - return nil, ErrNegativeValue - } - hashedValue, err := merklize.HashValue(valueType, item) - if err != nil { - return nil, err - } - values[i] = hashedValue - } - return values, nil - } - - if !isPositiveInteger(v) { - return nil, ErrNegativeValue - } - hashedValue, err := merklize.HashValue(valueType, v) - if err != nil { - return nil, err - } - values = append(values, hashedValue) - - return values, nil -} - func isPositiveInteger(v interface{}) bool { number, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64) if err != nil { diff --git a/pubsignals/query_test.go b/pubsignals/query_test.go index 94bf65a..4cbbdd3 100644 --- a/pubsignals/query_test.go +++ b/pubsignals/query_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/iden3/go-circuits/v2" core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-schema-processor/v2/utils" "github.com/piprate/json-gold/ld" @@ -114,8 +115,11 @@ func TestCheckRequest_Success(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 5, - Value: []*big.Int{big.NewInt(800)}, + Operator: 5, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -143,8 +147,11 @@ func TestCheckRequest_Success(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 1, - Value: []*big.Int{big.NewInt(800)}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -176,8 +183,11 @@ func TestCheckRequest_Success(t *testing.T) { v, _ := big.NewInt(0).SetString("1944808975288007371356450257872165609440470546066507760733183342797918372827", 10) return v }(), - Operator: 1, - Value: []*big.Int{bigIntTrueHash}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{bigIntTrueHash}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -208,7 +218,8 @@ func TestCheckRequest_Success(t *testing.T) { Operator: 1, Value: func() []*big.Int { v, _ := big.NewInt(0).SetString("957410455271905675920624030785024750144198809104092676617070098470852489834", 10) - return []*big.Int{v} + r, _ := circuits.PrepareCircuitArrayValues([]*big.Int{v}, 64) + return r }(), Merklized: 1, IsRevocationChecked: 1, @@ -244,7 +255,8 @@ func TestCheckRequest_Success(t *testing.T) { Operator: 1, Value: func() []*big.Int { v, _ := big.NewInt(0).SetString("7481731651336040098616464366227645531920423822088928207225802836605991806542", 10) - return []*big.Int{v} + r, _ := circuits.PrepareCircuitArrayValues([]*big.Int{v}, 64) + return r }(), Merklized: 1, IsRevocationChecked: 1, @@ -270,10 +282,13 @@ func TestCheckRequest_Success(t *testing.T) { Type: "KYCAgeCredential", }, pubSig: &CircuitOutputs{ - IssuerID: &issuerID, - ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld#KYCAgeCredential")), - Operator: 1, - Value: []*big.Int{big.NewInt(19960424)}, + IssuerID: &issuerID, + ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld#KYCAgeCredential")), + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(19960424)}, 64) + return v + }(), Merklized: 0, SlotIndex: 2, IsRevocationChecked: 1, @@ -327,8 +342,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 5, - Value: []*big.Int{big.NewInt(800)}, + Operator: 5, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: dayAndMinuteAgo, @@ -358,8 +376,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 1, - Value: []*big.Int{big.NewInt(800)}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -389,8 +410,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 5, - Value: []*big.Int{big.NewInt(800)}, + Operator: 5, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -421,8 +445,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 1, - Value: []*big.Int{big.NewInt(800), big.NewInt(801)}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800), big.NewInt(801)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -453,8 +480,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 1, - Value: []*big.Int{big.NewInt(1)}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(1)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -485,8 +515,11 @@ func TestCheckRequest_SelectiveDisclosure_Error(t *testing.T) { v, _ := big.NewInt(0).SetString("17002437119434618783545694633038537380726339994244684348913844923422470806844", 10) return v }(), - Operator: 1, - Value: []*big.Int{big.NewInt(800)}, + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(800)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -556,8 +589,12 @@ func TestCheckRequest_Error(t *testing.T) { Context: "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld", Type: "KYCCountryOfResidenceCredential", CredentialSubject: map[string]interface{}{ - "req1": struct{}{}, - "req2": struct{}{}, + "countryCode": map[string]interface{}{ + "$nin": []interface{}{float64(800)}, + }, + "documentType": map[string]interface{}{ + "$eq": []interface{}{float64(1)}, + }, }, }, pubSig: &CircuitOutputs{ @@ -683,11 +720,14 @@ func TestCheckRequest_Error(t *testing.T) { }, }, pubSig: &CircuitOutputs{ - IssuerID: &issuerID, - ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld#KYCCountryOfResidenceCredential")), - ClaimPathKey: big.NewInt(0), - Operator: 5, - Value: []*big.Int{big.NewInt(20)}, + IssuerID: &issuerID, + ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld#KYCCountryOfResidenceCredential")), + ClaimPathKey: big.NewInt(0), + Operator: 5, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(20)}, 64) + return v + }(), Merklized: 1, IsRevocationChecked: 1, Timestamp: now, @@ -713,10 +753,13 @@ func TestCheckRequest_Error(t *testing.T) { SkipClaimRevocationCheck: false, }, pubSig: &CircuitOutputs{ - IssuerID: &issuerID, - ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld#KYCCountryOfResidenceCredential")), - Operator: 5, - Value: []*big.Int{big.NewInt(20)}, + IssuerID: &issuerID, + ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld#KYCCountryOfResidenceCredential")), + Operator: 5, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(20)}, 64) + return v + }(), Merklized: 0, SlotIndex: 0, IsRevocationChecked: 0, @@ -773,10 +816,13 @@ func TestCheckRequest_Error(t *testing.T) { SkipClaimRevocationCheck: false, }, pubSig: &CircuitOutputs{ - IssuerID: &issuerID, - ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld#KYCEmployee")), - Operator: 1, - Value: []*big.Int{big.NewInt(-1)}, + IssuerID: &issuerID, + ClaimSchema: utils.CreateSchemaHash([]byte("https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld#KYCEmployee")), + Operator: 1, + Value: func() []*big.Int { + v, _ := circuits.PrepareCircuitArrayValues([]*big.Int{big.NewInt(-1)}, 64) + return v + }(), Merklized: 0, SlotIndex: 0, IsRevocationChecked: 0,