From b0a9eb46bdd5de2561ca286b008d49e5b2d451d0 Mon Sep 17 00:00:00 2001 From: volodymyr-basiuk <31999965+volodymyr-basiuk@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:10:36 +0200 Subject: [PATCH] Feature/linked circuits (#78) * add linked circuits wrappers --- circuits.go | 2 + linkedMultiQuery.go | 195 +++++ linkedMultiQuery_test.go | 123 +++ testdata/linkedMultiQuery_inputs.json | 1126 +++++++++++++++++++++++++ 4 files changed, 1446 insertions(+) create mode 100644 linkedMultiQuery.go create mode 100644 linkedMultiQuery_test.go create mode 100644 testdata/linkedMultiQuery_inputs.json diff --git a/circuits.go b/circuits.go index e1cca32..0894ac6 100644 --- a/circuits.go +++ b/circuits.go @@ -39,6 +39,8 @@ const ( SybilMTPCircuitID CircuitID = "sybilCredentialAtomicMTP" // SybilSigCircuitID is a type for sybilSig.circom SybilSigCircuitID CircuitID = "sybilCredentialAtomicSig" + // LinkedMultiQuery10CircuitID is a type for linkedMultiQuery10.circom + LinkedMultiQuery10CircuitID CircuitID = "linkedMultiQuery10-beta.0" ) // ErrorCircuitIDNotFound returns if CircuitID is not registered diff --git a/linkedMultiQuery.go b/linkedMultiQuery.go new file mode 100644 index 0000000..518d376 --- /dev/null +++ b/linkedMultiQuery.go @@ -0,0 +1,195 @@ +package circuits + +import ( + "encoding/json" + "fmt" + "math/big" + "strconv" + + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-merkletree-sql/v2" +) + +const LinkedMultiQueryLength = 10 + +// LinkedMultiQueryInputs type represent linkedMultiQuery10.circom inputs +type LinkedMultiQueryInputs struct { + BaseConfig + LinkNonce *big.Int + Claim *core.Claim + Query []*Query +} + +// linkedMultiQueryCircuitInputs type reflect linkedMultiQuery10.circom private inputs required by prover +type linkedMultiQueryCircuitInputs struct { + LinkNonce string `json:"linkNonce"` + IssuerClaim *core.Claim `json:"issuerClaim"` + Enabled []int `json:"enabled"` + ClaimSchema string `json:"claimSchema"` + ClaimPathNotExists []int `json:"claimPathNotExists"` // 0 for inclusion, 1 for non-inclusion + ClaimPathMtp [][]string `json:"claimPathMtp"` + ClaimPathMtpNoAux []string `json:"claimPathMtpNoAux"` // 1 if aux node is empty, 0 if non-empty or for inclusion proofs + ClaimPathMtpAuxHi []*merkletree.Hash `json:"claimPathMtpAuxHi"` // 0 for inclusion proof + ClaimPathMtpAuxHv []*merkletree.Hash `json:"claimPathMtpAuxHv"` // 0 for inclusion proof + ClaimPathKey []string `json:"claimPathKey"` // hash of path in merklized json-ld document + ClaimPathValue []string `json:"claimPathValue"` // value in this path in merklized json-ld document + SlotIndex []int `json:"slotIndex"` + Operator []int `json:"operator"` + Value [][]string `json:"value"` +} + +// InputsMarshal returns Circom private inputs for linkedMultiQuery10.circom +func (l LinkedMultiQueryInputs) InputsMarshal() ([]byte, error) { + s := linkedMultiQueryCircuitInputs{} + s.LinkNonce = l.LinkNonce.String() + s.IssuerClaim = l.Claim + s.ClaimSchema = l.Claim.GetSchemaHash().BigInt().String() + + s.Enabled = make([]int, LinkedMultiQueryLength) + s.ClaimPathNotExists = make([]int, LinkedMultiQueryLength) + s.ClaimPathMtp = make([][]string, LinkedMultiQueryLength) + s.ClaimPathMtpNoAux = make([]string, LinkedMultiQueryLength) + s.ClaimPathMtpAuxHi = make([]*merkletree.Hash, LinkedMultiQueryLength) + s.ClaimPathMtpAuxHv = make([]*merkletree.Hash, LinkedMultiQueryLength) + s.ClaimPathKey = make([]string, LinkedMultiQueryLength) + s.ClaimPathValue = make([]string, LinkedMultiQueryLength) + s.SlotIndex = make([]int, LinkedMultiQueryLength) + s.Operator = make([]int, LinkedMultiQueryLength) + s.Value = make([][]string, LinkedMultiQueryLength) + + for i := 0; i < LinkedMultiQueryLength; i++ { + if l.Query[i] == nil { + s.Enabled[i] = 0 + s.ClaimPathNotExists[i] = 0 + s.ClaimPathMtp[i] = PrepareSiblingsStr([]*merkletree.Hash{}, l.GetMTLevelsClaim()) + + s.ClaimPathMtpNoAux[i] = "0" + s.ClaimPathMtpAuxHi[i] = &merkletree.HashZero + s.ClaimPathMtpAuxHv[i] = &merkletree.HashZero + + s.ClaimPathKey[i] = "0" + s.ClaimPathValue[i] = "0" + + s.SlotIndex[i] = 0 + s.Operator[i] = 0 + + values, err := PrepareCircuitArrayValues(make([]*big.Int, 0), l.GetValueArrSize()) + if err != nil { + return nil, err + } + s.Value[i] = bigIntArrayToStringArray(values) + continue + } + + s.Enabled[i] = 1 + valueProof := l.Query[i].ValueProof + if valueProof == nil { + valueProof = &ValueProof{} + valueProof.Path = big.NewInt(0) + valueProof.Value = big.NewInt(0) + valueProof.MTP = &merkletree.Proof{} + } + + s.ClaimPathNotExists[i] = existenceToInt(valueProof.MTP.Existence) + s.ClaimPathMtp[i] = PrepareSiblingsStr(valueProof.MTP.AllSiblings(), + l.GetMTLevelsClaim()) + + nodAuxJSONLD := GetNodeAuxValue(valueProof.MTP) + s.ClaimPathMtpNoAux[i] = nodAuxJSONLD.noAux + s.ClaimPathMtpAuxHi[i] = nodAuxJSONLD.key + s.ClaimPathMtpAuxHv[i] = nodAuxJSONLD.value + + s.ClaimPathKey[i] = valueProof.Path.String() + s.ClaimPathValue[i] = valueProof.Value.String() + + s.SlotIndex[i] = l.Query[i].SlotIndex + s.Operator[i] = l.Query[i].Operator + values, err := PrepareCircuitArrayValues(l.Query[i].Values, l.GetValueArrSize()) + if err != nil { + return nil, err + } + s.Value[i] = bigIntArrayToStringArray(values) + } + + return json.Marshal(s) +} + +// LinkedMultiQueryPubSignals linkedMultiQuery10.circom public signals +type LinkedMultiQueryPubSignals struct { + LinkID *big.Int `json:"linkID"` + Merklized int `json:"merklized"` + OperatorOutput []*big.Int `json:"operatorOutput"` + CircuitQueryHash []*big.Int `json:"circuitQueryHash"` + Enabled []bool `json:"enabled"` +} + +// PubSignalsUnmarshal unmarshal linkedMultiQuery10.circom public inputs to LinkedMultiQueryPubSignals +func (lo *LinkedMultiQueryPubSignals) PubSignalsUnmarshal(data []byte) error { + // expected order: + // linkID + // merklized + // operatorOutput + // circuitQueryHash + // enabled + + outputsLength := LinkedMultiQueryLength*3 + 2 + var sVals []string + err := json.Unmarshal(data, &sVals) + if err != nil { + return err + } + + if len(sVals) != outputsLength { + return fmt.Errorf("invalid number of Output values expected {%d} go {%d} ", outputsLength, len(sVals)) + } + + var ok bool + fieldIdx := 0 + + // - linkID + if lo.LinkID, ok = big.NewInt(0).SetString(sVals[fieldIdx], 10); !ok { + return fmt.Errorf("invalid link ID value: '%s'", sVals[fieldIdx]) + } + fieldIdx++ + + // -- merklized + if lo.Merklized, err = strconv.Atoi(sVals[fieldIdx]); err != nil { + return err + } + fieldIdx++ + + // -- operatorOutput + lo.OperatorOutput = make([]*big.Int, LinkedMultiQueryLength) + for i := 0; i < LinkedMultiQueryLength; i++ { + if lo.OperatorOutput[i], ok = big.NewInt(0).SetString(sVals[fieldIdx], 10); !ok { + return fmt.Errorf("invalid operator output value: '%s'", sVals[fieldIdx]) + } + fieldIdx++ + } + // -- circuitQueryHash + lo.CircuitQueryHash = make([]*big.Int, LinkedMultiQueryLength) + for i := 0; i < LinkedMultiQueryLength; i++ { + if lo.CircuitQueryHash[i], ok = big.NewInt(0).SetString(sVals[fieldIdx], 10); !ok { + return fmt.Errorf("invalid query hash value: '%s'", sVals[fieldIdx]) + } + fieldIdx++ + } + + // -- enabled + lo.Enabled = make([]bool, LinkedMultiQueryLength) + for i := 0; i < LinkedMultiQueryLength; i++ { + enabledInt, err := strconv.Atoi(sVals[fieldIdx]) + if err != nil { + return err + } + lo.Enabled[i] = enabledInt == 1 + fieldIdx++ + } + + return nil +} + +// GetObjMap returns LinkedMultiQueryPubSignals as a map +func (l LinkedMultiQueryPubSignals) GetObjMap() map[string]interface{} { + return toMap(l) +} diff --git a/linkedMultiQuery_test.go b/linkedMultiQuery_test.go new file mode 100644 index 0000000..97e6320 --- /dev/null +++ b/linkedMultiQuery_test.go @@ -0,0 +1,123 @@ +package circuits + +import ( + "encoding/json" + "fmt" + "math/big" + "testing" + + it "github.com/iden3/go-circuits/v2/testing" + "github.com/stretchr/testify/require" +) + +func TestLinkedMultiQueryInputs_PrepareInputs(t *testing.T) { + user := it.NewIdentity(t, userPK) + subjectID := user.ID + claim := it.DefaultUserClaim(t, subjectID) + + queries := make([]*Query, 10) + queries[0] = &Query{ + ValueProof: nil, + Operator: EQ, + Values: it.PrepareIntArray([]*big.Int{big.NewInt(10)}, 64), + SlotIndex: 2, + } + + queries[1] = &Query{ + ValueProof: nil, + Operator: LT, + Values: it.PrepareIntArray([]*big.Int{big.NewInt(133)}, 64), + SlotIndex: 2, + } + + queries[2] = &Query{ + ValueProof: nil, + Operator: LTE, + Values: it.PrepareIntArray([]*big.Int{big.NewInt(555)}, 64), + SlotIndex: 2, + } + + in := LinkedMultiQueryInputs{ + LinkNonce: big.NewInt(35346346369657418), + Claim: claim, + Query: queries, + } + + bytesInputs, err := in.InputsMarshal() + require.Nil(t, err) + + fmt.Println(string(bytesInputs)) + + exp := it.TestData(t, "linkedMultiQuery_inputs", string(bytesInputs), *generate) + require.JSONEq(t, exp, string(bytesInputs)) +} + +func TestLinkedMultiQueryPubSignals_CircuitUnmarshal(t *testing.T) { + out := new(LinkedMultiQueryPubSignals) + err := out.PubSignalsUnmarshal([]byte( + `[ + "443", + "1", + "1", + "2", + "3", + "4", + "5", + "0", + "0", + "0", + "0", + "0", + "100", + "200", + "300", + "400", + "500", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0" + ]`)) + require.NoError(t, err) + + operatorOutput := make([]*big.Int, 10) + circuitQueryHash := make([]*big.Int, 10) + enabled := make([]bool, 10) + for i := 1; i <= 10; i++ { + indx := i - 1 + operatorOutput[indx] = big.NewInt((int64(i))) + circuitQueryHash[indx] = big.NewInt(int64(i * 100)) + enabled[indx] = true + if i > 5 { + operatorOutput[indx] = big.NewInt(0) + circuitQueryHash[indx] = big.NewInt(0) + enabled[indx] = false + } + } + + exp := LinkedMultiQueryPubSignals{ + LinkID: big.NewInt(443), + Merklized: 1, + OperatorOutput: operatorOutput, + CircuitQueryHash: circuitQueryHash, + Enabled: enabled, + } + + jsonOut, err := json.Marshal(out) + require.NoError(t, err) + jsonExp, err := json.Marshal(exp) + require.NoError(t, err) + + require.JSONEq(t, string(jsonExp), string(jsonOut)) +} diff --git a/testdata/linkedMultiQuery_inputs.json b/testdata/linkedMultiQuery_inputs.json new file mode 100644 index 0000000..f0e4e9a --- /dev/null +++ b/testdata/linkedMultiQuery_inputs.json @@ -0,0 +1,1126 @@ +{ + "linkNonce": "35346346369657418", + "issuerClaim": [ + "3583233690122716044519380227940806650830", + "26109404700696283154998654512117952420503675471097392618762221546565140481", + "10", + "0", + "30803922965249841627828060161", + "0", + "0", + "0" + ], + "enabled": [ + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "claimSchema": "180410020913331409885634153623124536270", + "claimPathNotExists": [ + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "claimPathMtp": [ + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + ], + "claimPathMtpNoAux": [ + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "claimPathMtpAuxHi": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "claimPathMtpAuxHv": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "claimPathKey": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "claimPathValue": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "slotIndex": [ + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "operator": [ + 1, + 2, + 7, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "value": [ + [ + "10", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "133", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "555", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + ] +} \ No newline at end of file