diff --git a/README.md b/README.md index 046f9bf..c4ac79e 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,16 @@ The configuration object is a JSON document with the following structure: ```json5 { "ipfsNodeUrl": "http://localhost:5001", // IPFS Node URL + "didMethods": [ + { + "name": "ethr", // DID method name + "blockchain": "ethereum", // Blockchain name + "network": "mainnet", // Network name + "networkFlag": 6, // Network flag + "methodByte": "0b010011", // Method byte + "chainID": "10293" + } + ], "chainConfigs": { "1": { // Chain ID as decimal "rpcUrl": "http://localhost:8545", // RPC URL diff --git a/cmd/polygonid/polygonid.go b/cmd/polygonid/polygonid.go index a5d1815..8302a77 100644 --- a/cmd/polygonid/polygonid.go +++ b/cmd/polygonid/polygonid.go @@ -216,66 +216,66 @@ func PLGNAuthV2InputsMarshal(jsonResponse **C.char, in *C.char, return true } +// Deprecated: Use PLGNNewGenesisID instead. It supports environment +// configuration, giving the ability to register custom DID methods. +// //export PLGNCalculateGenesisID func PLGNCalculateGenesisID(jsonResponse **C.char, in *C.char, status **C.PLGNStatus) bool { - _, cancel := logAPITime() + ctx, cancel := logAPITime() defer cancel() - var req struct { - ClaimsTreeRoot *jsonIntStr `json:"claimsTreeRoot"` - Blockchain core.Blockchain `json:"blockchain"` - Network core.NetworkID `json:"network"` - } - if in == nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_NIL_POINTER, "pointer to request is nil") return false } - err := json.Unmarshal([]byte(C.GoString(in)), &req) + inStr := C.GoString(in) + resp, err := c_polygonid.NewGenesysID(ctx, c_polygonid.EnvConfig{}, + []byte(inStr)) if err != nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) return false } - state, err := merkletree.HashElems(req.ClaimsTreeRoot.Int(), - merkletree.HashZero.BigInt(), merkletree.HashZero.BigInt()) + respB, err := json.Marshal(resp) if err != nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) return false } - typ, err := core.BuildDIDType(core.DIDMethodPolygonID, req.Blockchain, - req.Network) - if err != nil { - maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) + *jsonResponse = C.CString(string(respB)) + return true +} + +//export PLGNNewGenesisID +func PLGNNewGenesisID(jsonResponse **C.char, in *C.char, cfg *C.char, + status **C.PLGNStatus) bool { + + ctx, cancel := logAPITime() + defer cancel() + + if in == nil { + maybeCreateStatus(status, C.PLGNSTATUSCODE_NIL_POINTER, + "pointer to request is nil") return false } - coreID, err := core.NewIDFromIdenState(typ, state.BigInt()) + envCfg, err := createEnvConfig(cfg) if err != nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) return false } - did, err := core.ParseDIDFromID(*coreID) + inStr := C.GoString(in) + resp, err := c_polygonid.NewGenesysID(ctx, envCfg, []byte(inStr)) if err != nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) return false } - resp := struct { - DID string `json:"did"` - ID string `json:"id"` - IDAsInt string `json:"idAsInt"` - }{ - DID: did.String(), - ID: coreID.String(), - IDAsInt: coreID.BigInt().String(), - } respB, err := json.Marshal(resp) if err != nil { maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) @@ -863,13 +863,11 @@ func PLGNCacheCredentials(in *C.char, cfg *C.char, status **C.PLGNStatus) bool { // createEnvConfig returns empty config if input json is nil. func createEnvConfig(cfgJson *C.char) (c_polygonid.EnvConfig, error) { - var cfg c_polygonid.EnvConfig - var err error + var cfgData []byte if cfgJson != nil { - cfgData := C.GoBytes(unsafe.Pointer(cfgJson), C.int(C.strlen(cfgJson))) - err = json.Unmarshal(cfgData, &cfg) + cfgData = C.GoBytes(unsafe.Pointer(cfgJson), C.int(C.strlen(cfgJson))) } - return cfg, err + return c_polygonid.NewEnvConfigFromJSON(cfgData) } type atomicQueryInputsFn func(ctx context.Context, cfg c_polygonid.EnvConfig, diff --git a/env_config.go b/env_config.go new file mode 100644 index 0000000..49035f4 --- /dev/null +++ b/env_config.go @@ -0,0 +1,129 @@ +package c_polygonid + +import ( + "encoding/json" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-schema-processor/v2/loaders" + "github.com/piprate/json-gold/ld" +) + +type EnvConfig struct { + DIDMethods []MethodConfig + ChainConfigs PerChainConfig + EthereumURL string // Deprecated: Use ChainConfigs instead + StateContractAddr common.Address // Deprecated: Use ChainConfigs instead + ReverseHashServiceUrl string // Deprecated + IPFSNodeURL string +} + +var globalRegistationLock sync.Mutex +var registeredDIDMethods sync.Map + +// NewEnvConfigFromJSON returns empty config if input json is nil. +func NewEnvConfigFromJSON(in []byte) (EnvConfig, error) { + var cfg EnvConfig + if in == nil { + return cfg, nil + } + + var err error + err = json.Unmarshal(in, &cfg) + if err != nil { + return cfg, fmt.Errorf("unable to parse json config: %w", err) + } + + if len(cfg.DIDMethods) == 0 { + return cfg, nil + } + + err = registerDIDMethods(cfg.DIDMethods) + if err != nil { + return cfg, err + } + + var zeroAddr common.Address + for _, didMethod := range cfg.DIDMethods { + chainIDCfg, ok := cfg.ChainConfigs[didMethod.ChainID] + if !ok { + return cfg, fmt.Errorf("no chain config found for chain ID %v", + didMethod.ChainID) + } + if chainIDCfg.RPCUrl == "" { + return cfg, fmt.Errorf("no RPC URL found for chain ID %v", + didMethod.ChainID) + } + if chainIDCfg.StateContractAddr == zeroAddr { + return cfg, fmt.Errorf( + "no state contract address found for chain ID %v", + didMethod.ChainID) + } + } + + return cfg, err +} + +func registerDIDMethods(methodConfigs []MethodConfig) error { + newMethodConfigs := make([]MethodConfig, 0, len(methodConfigs)) + + for _, methodCfg := range methodConfigs { + if _, ok := registeredDIDMethods.Load(methodCfg.Hash()); !ok { + newMethodConfigs = append(newMethodConfigs, methodCfg) + } + } + + if len(newMethodConfigs) == 0 { + return nil + } + + globalRegistationLock.Lock() + defer globalRegistationLock.Unlock() + + for _, methodCfg := range newMethodConfigs { + chainIDi := chainIDToInt(methodCfg.ChainID) + + params := core.DIDMethodNetworkParams{ + Method: methodCfg.MethodName, + Blockchain: methodCfg.Blockchain, + Network: methodCfg.NetworkID, + NetworkFlag: methodCfg.NetworkFlag, + } + err := core.RegisterDIDMethodNetwork(params, + core.WithChainID(chainIDi), + core.WithDIDMethodByte(methodCfg.MethodByte)) + if err != nil { + return fmt.Errorf( + "can't register DID method %v, blockchain %v, network ID %v, "+ + "network flag: %x, method byte %v, chain ID %v: %w", + methodCfg.MethodName, methodCfg.Blockchain, methodCfg.NetworkID, + methodCfg.NetworkFlag, methodCfg.MethodByte, + methodCfg.ChainID, err) + } + + registeredDIDMethods.Store(methodCfg.Hash(), struct{}{}) + } + + return nil +} + +func (cfg EnvConfig) documentLoader() ld.DocumentLoader { + var ipfsNode loaders.IPFSClient + if cfg.IPFSNodeURL != "" { + ipfsNode = &ipfsCli{rpcURL: cfg.IPFSNodeURL} + } + + var opts []loaders.DocumentLoaderOption + + cacheEngine, err := newBadgerCacheEngine( + withEmbeddedDocumentBytes( + "https://www.w3.org/2018/credentials/v1", + credentialsV1JsonLDBytes)) + if err == nil { + opts = append(opts, loaders.WithCacheEngine(cacheEngine)) + } + + return loaders.NewDocumentLoader(ipfsNode, "", opts...) +} diff --git a/env_config_test.go b/env_config_test.go new file mode 100644 index 0000000..c011743 --- /dev/null +++ b/env_config_test.go @@ -0,0 +1,86 @@ +package c_polygonid + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + core "github.com/iden3/go-iden3-core/v2" + "github.com/stretchr/testify/require" +) + +func TestNewEnvConfigFromJSON(t *testing.T) { + cfgJSON := `{ + "ethereumUrl": "http://localhost:8545", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655", + "reverseHashServiceUrl": "http://localhost:8003", + "ipfsNodeUrl": "http://localhost:5001", + "chainConfigs": { + "1": { + "rpcUrl": "http://localhost:8545", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + }, + "0x10": { + "rpcUrl": "http://localhost:8546", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + }, + "0X2835": { + "rpcUrl": "http://localhost:8547", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + } + }, + "didMethods": [ + { + "name": "ethr", + "blockchain": "ethereum", + "network": "mainnet", + "networkFlag": 6, + "methodByte": "0b010011", + "chainID": "10293" + } + ] +}` + cfg, err := NewEnvConfigFromJSON([]byte(cfgJSON)) + require.NoError(t, err) + + require.Equal(t, + EnvConfig{ + DIDMethods: []MethodConfig{ + { + MethodName: "ethr", + Blockchain: "ethereum", + NetworkID: "mainnet", + NetworkFlag: 6, + MethodByte: 19, + ChainID: 10293, + }, + }, + ChainConfigs: PerChainConfig{ + 1: { + RPCUrl: "http://localhost:8545", + StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), + }, + 16: { + RPCUrl: "http://localhost:8546", + StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), + }, + 10293: { + RPCUrl: "http://localhost:8547", + StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), + }, + }, + EthereumURL: "http://localhost:8545", + StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), + ReverseHashServiceUrl: "http://localhost:8003", + IPFSNodeURL: "http://localhost:5001", + }, + cfg) + + // check that custom DID methods are registered + chainID, err := core.GetChainID("ethereum", "mainnet") + require.NoError(t, err) + require.Equal(t, core.ChainID(10293), chainID) + blockchain, networkID, err := core.NetworkByChainID(core.ChainID(10293)) + require.NoError(t, err) + require.Equal(t, core.Blockchain("ethereum"), blockchain) + require.Equal(t, core.NetworkID("mainnet"), networkID) +} diff --git a/examples/json_functions_tests.c b/examples/json_functions_tests.c index c5fa404..b9dab6b 100644 --- a/examples/json_functions_tests.c +++ b/examples/json_functions_tests.c @@ -6,6 +6,9 @@ // GoUint8 is a C bool type typedef GoUint8(*FN)(char**, char*, PLGNStatus**); +// FN2 is a generic function that accept configuration compared to FN function +typedef GoUint8(*FN2)(char**, char*, char*, PLGNStatus**); + // if return is false, test is not passed typedef bool(*JSProcess)(cJSON *); @@ -33,50 +36,58 @@ remove_timestamp_field(cJSON *obj) { typedef struct _TEST { char *in; char *out; + char *cfg; FN fn; + FN2 fn2; JSProcess resultPostprocessFn; } TEST; TEST testCases[] = { { - .in = "testdata/create_claim_in.json", - .out = "testdata/create_claim_out.json", - .fn = &PLGNCreateClaim + .in = "testdata/create_claim_in.json", + .out = "testdata/create_claim_out.json", + .fn = &PLGNCreateClaim + }, + { + .in = "testdata/create_claim_all_fields_1_in.json", + .out = "testdata/create_claim_all_fields_1_out.json", + .fn = &PLGNCreateClaim }, { - .in = "testdata/create_claim_all_fields_1_in.json", - .out = "testdata/create_claim_all_fields_1_out.json", - .fn = &PLGNCreateClaim + .in = "testdata/create_claim_all_fields_2_in.json", + .out = "testdata/create_claim_all_fields_2_out.json", + .fn = &PLGNCreateClaim }, { - .in = "testdata/create_claim_all_fields_2_in.json", - .out = "testdata/create_claim_all_fields_2_out.json", - .fn = &PLGNCreateClaim + .in = "testdata/auth_v2_inputs_in.json", + .out = "testdata/auth_v2_inputs_out.json", + .fn = &PLGNAuthV2InputsMarshal }, { - .in = "testdata/auth_v2_inputs_in.json", - .out = "testdata/auth_v2_inputs_out.json", - .fn = &PLGNAuthV2InputsMarshal + .in = "testdata/calculate_genesis_id_in.json", + .out = "testdata/calculate_genesis_id_out.json", + .fn = &PLGNCalculateGenesisID }, { - .in = "testdata/calculate_genesis_id_in.json", - .out = "testdata/calculate_genesis_id_out.json", - .fn = &PLGNCalculateGenesisID + .in = "testdata/id_to_int_in.json", + .out = "testdata/id_to_int_out.json", + .fn = &PLGNIDToInt }, { - .in = "testdata/id_to_int_in.json", - .out = "testdata/id_to_int_out.json", - .fn = &PLGNIDToInt + .in = "testdata/proof_from_smart_contract_in.json", + .out = "testdata/proof_from_smart_contract_out.json", + .fn = &PLGNProofFromSmartContract }, { - .in = "testdata/proof_from_smart_contract_in.json", - .out = "testdata/proof_from_smart_contract_out.json", - .fn = &PLGNProofFromSmartContract + .in = "testdata/profile_id_in.json", + .out = "testdata/profile_id_out.json", + .fn = &PLGNProfileID }, { - .in = "testdata/profile_id_in.json", - .out = "testdata/profile_id_out.json", - .fn = &PLGNProfileID + .in = "testdata/new_genesis_id_in.json", + .cfg = "testdata/new_genesis_id_cfg.json", + .out = "testdata/new_genesis_id_out.json", + .fn2 = &PLGNNewGenesisID } // timestamp is different on each call, so we can't just compare output for equality // this test is failed because ec2-34-243-185-133.eu-west-1.compute.amazonaws.com:8888 is down @@ -90,7 +101,7 @@ TEST testCases[] = { bool json_equal(const char *want, const char *actual, - JSProcess resultPostprocessFn) { + JSProcess resultPostprocessFn) { cJSON *wantJson = NULL; cJSON *actualJson = NULL; wantJson = cJSON_Parse(want); @@ -115,31 +126,62 @@ json_equal(const char *want, const char *actual, // return 0 on success or non-0 on error int -run_test(char *in, char *out, FN fn, - JSProcess resultPostprocessFn) { +run_test(TEST tc) { int ret_val = 0; char *resp = NULL; PLGNStatus *status = NULL; + char *want_output = NULL; + char *input = NULL; + char *cfg = NULL; + + if (tc.fn == NULL && tc.fn2 == NULL) { + printf("functions are NULL\n"); + ret_val = 1; + goto cleanup; + } - char *input = read_file(in); + if (tc.fn != NULL && tc.fn2 != NULL) { + printf("functions are not NULL\n"); + ret_val = 1; + goto cleanup; + } + + input = read_file(tc.in); if (!input) { ret_val = 1; goto cleanup; } - char *want_output = read_file(out); + + want_output = read_file(tc.out); if (!want_output) { ret_val = 1; goto cleanup; } - bool ok = fn(&resp, input, &status); + bool ok = {0}; + + if (tc.fn != NULL) { + + ok = tc.fn(&resp, input, &status); + + } else { + + cfg = read_file(tc.cfg); + if (!cfg) { + ret_val = 1; + goto cleanup; + } + + ok = tc.fn2(&resp, input, cfg, &status); + } + if (!ok) { - consume_status(status, "Error marshaling input"); + consume_status(status, "Error calling function"); ret_val = 1; goto cleanup; } - ok = json_equal(want_output, resp, resultPostprocessFn); + ok = json_equal(want_output, resp, tc.resultPostprocessFn); if (!ok) { ret_val = 1; fprintf(stderr, "result is not equal to expected output\n\ngot: %s\n\nwant: %s\n\n", @@ -148,13 +190,13 @@ run_test(char *in, char *out, FN fn, cleanup: if (input) { - free(input); + free(input); } if (resp) { - free(resp); + free(resp); } if (want_output) { - free(want_output); + free(want_output); } return ret_val; } @@ -163,14 +205,13 @@ int main() { int ret_val = 0; for(int i = 0; i < sizeof(testCases)/sizeof(TEST); i++) { - int r = run_test(testCases[i].in, testCases[i].out, testCases[i].fn, - testCases[i].resultPostprocessFn); - if (r != 0) { - ret_val = r; - printf("FAILED: %s => %s\n", testCases[i].in, testCases[i].out); - } else { - printf("OK: %s => %s\n", testCases[i].in, testCases[i].out); - } + int r = run_test(testCases[i]); + if (r != 0) { + ret_val = r; + printf("FAILED: %s => %s\n", testCases[i].in, testCases[i].out); + } else { + printf("OK: %s => %s\n", testCases[i].in, testCases[i].out); + } } return ret_val; } diff --git a/examples/testdata/new_genesis_id_cfg.json b/examples/testdata/new_genesis_id_cfg.json new file mode 100644 index 0000000..6afbfad --- /dev/null +++ b/examples/testdata/new_genesis_id_cfg.json @@ -0,0 +1,18 @@ +{ + "chainConfigs": { + "59140": { + "rpcUrl": "http://localhost:8545", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + } + }, + "didMethods": [ + { + "name": "polygonid", + "blockchain": "linea", + "network": "testnet", + "networkFlag": "0b01000011", + "methodByte": "0b00000010", + "chainID": "59140" + } + ] +} \ No newline at end of file diff --git a/examples/testdata/new_genesis_id_in.json b/examples/testdata/new_genesis_id_in.json new file mode 100644 index 0000000..dd4a187 --- /dev/null +++ b/examples/testdata/new_genesis_id_in.json @@ -0,0 +1,5 @@ +{ + "claimsTreeRoot":"16306276920027997118951972513784102597349518910734830865369546877495436692483", + "blockchain":"linea", + "network":"testnet" +} \ No newline at end of file diff --git a/examples/testdata/new_genesis_id_out.json b/examples/testdata/new_genesis_id_out.json new file mode 100644 index 0000000..fae4746 --- /dev/null +++ b/examples/testdata/new_genesis_id_out.json @@ -0,0 +1,5 @@ +{ + "did": "did:polygonid:linea:testnet:31Akw5AB2xBrwqmbDUA2XoSGCfTepz52q9jmFE4mXA", + "id": "31Akw5AB2xBrwqmbDUA2XoSGCfTepz52q9jmFE4mXA", + "idAsInt": "24460059377712687587111979692736628604804094576108957842967948238113620738" +} diff --git a/go.mod b/go.mod index b977b51..637a407 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.21 require ( github.com/dgraph-io/badger/v4 v4.2.0 - github.com/ethereum/go-ethereum v1.13.10 + github.com/ethereum/go-ethereum v1.13.11 github.com/iden3/contracts-abi/onchain-credential-status-resolver/go/abi v0.0.0-20230911113809-c58b7e7a69b0 github.com/iden3/contracts-abi/state/go/abi v0.0.0-20230405152923-4a25f6f1f0f4 github.com/iden3/go-circuits/v2 v2.0.1 - github.com/iden3/go-iden3-core/v2 v2.0.3 + github.com/iden3/go-iden3-core/v2 v2.0.4-0.20240129182142-32180363999b github.com/iden3/go-iden3-crypto v0.0.15 github.com/iden3/go-merkletree-sql/v2 v2.0.6 github.com/iden3/go-schema-processor/v2 v2.1.3-0.20240116085451-1abc0e6ed115 @@ -34,7 +34,7 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -66,7 +66,7 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 69fc173..814ec8a 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= -github.com/ethereum/go-ethereum v1.13.10/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= +github.com/ethereum/go-ethereum v1.13.11 h1:b51Dsm+rEg7anFRUMGB8hODXHvNfcRKzz9vcj8wSdUs= +github.com/ethereum/go-ethereum v1.13.11/go.mod h1:gFtlVORuUcT+UUIcJ/veCNjkuOSujCi338uSHJrYAew= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -94,8 +94,9 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -159,8 +160,8 @@ github.com/iden3/contracts-abi/state/go/abi v0.0.0-20230405152923-4a25f6f1f0f4 h github.com/iden3/contracts-abi/state/go/abi v0.0.0-20230405152923-4a25f6f1f0f4/go.mod h1:TxgIrXCvxms3sbOdsy8kTvffUCIpEEifNy0fSXdkU4w= github.com/iden3/go-circuits/v2 v2.0.1 h1:tcJtBE8aLJsf9qpBoTUKE143Mne025cunQnSExMXaKo= github.com/iden3/go-circuits/v2 v2.0.1/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-core/v2 v2.0.4-0.20240129182142-32180363999b h1:9tdFuHcaCiHy7xehrY6bIWlSPlPZH8H7jYU3pFvWIP0= +github.com/iden3/go-iden3-core/v2 v2.0.4-0.20240129182142-32180363999b/go.mod h1:L9PxhWPvoS9qTb3inEkZBm1RpjHBt+VTwvxssdzbAdw= github.com/iden3/go-iden3-crypto v0.0.15 h1:4MJYlrot1l31Fzlo2sF56u7EVFeHHJkxGXXZCtESgK4= github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= github.com/iden3/go-merkletree-sql/v2 v2.0.6 h1:vsVDImnvnHf7Ggr45ptFOXJyWNA/8IwVQO1jzRLUlY8= @@ -318,11 +319,12 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/inputs_sig.go b/inputs_sig.go index 7cf2202..d008fbe 100644 --- a/inputs_sig.go +++ b/inputs_sig.go @@ -35,7 +35,6 @@ import ( "github.com/iden3/go-iden3-crypto/utils" "github.com/iden3/go-merkletree-sql/v2" json2 "github.com/iden3/go-schema-processor/v2/json" - "github.com/iden3/go-schema-processor/v2/loaders" "github.com/iden3/go-schema-processor/v2/merklize" "github.com/iden3/go-schema-processor/v2/processor" "github.com/iden3/go-schema-processor/v2/verifiable" @@ -234,7 +233,8 @@ func claimWithSigProofFromObj(ctx context.Context, cfg EnvConfig, } issuerID, err := core.IDFromDID(*issuerDID) if err != nil { - return out, err + return out, fmt.Errorf("can't get issuer ID from DID (%v): %w", + issuerDID, err) } out.IssuerID = &issuerID out.Claim, err = proof.GetCoreClaim() @@ -416,7 +416,7 @@ type onChainInputsRequest struct { type txData struct { ContractAddress common.Address `json:"contractAddress"` - ChainID ChainID `json:"chainId"` + ChainID core.ChainID `json:"chainId"` } type v3OnChainInputsRequest struct { @@ -1734,7 +1734,7 @@ func stateContractHasID(ctx context.Context, id *core.ID, cfg ChainConfig, } type onchainRevStatus struct { - chainID ChainID + chainID core.ChainID contractAddress common.Address revNonce uint64 genesisState *big.Int @@ -1966,7 +1966,7 @@ func (cc ChainConfig) validate() error { return nil } -type PerChainConfig map[ChainID]ChainConfig +type PerChainConfig map[core.ChainID]ChainConfig func (p *PerChainConfig) UnmarshalJSON(bytes []byte) error { if (*p) == nil { @@ -1978,7 +1978,7 @@ func (p *PerChainConfig) UnmarshalJSON(bytes []byte) error { return err } for k, v := range o { - var chainID ChainID + var chainID core.ChainID chainID, err = newChainIDFromString(k) if err != nil { return err @@ -1988,75 +1988,20 @@ func (p *PerChainConfig) UnmarshalJSON(bytes []byte) error { return nil } -type EnvConfig struct { - ChainConfigs PerChainConfig - EthereumURL string // Deprecated: Use ChainConfigs instead - StateContractAddr common.Address // Deprecated: Use ChainConfigs instead - ReverseHashServiceUrl string // Deprecated - IPFSNodeURL string -} - -func (cfg EnvConfig) documentLoader() ld.DocumentLoader { - var ipfsNode loaders.IPFSClient - if cfg.IPFSNodeURL != "" { - ipfsNode = &ipfsCli{rpcURL: cfg.IPFSNodeURL} - } - - var opts []loaders.DocumentLoaderOption - - cacheEngine, err := newBadgerCacheEngine( - withEmbeddedDocumentBytes( - "https://www.w3.org/2018/credentials/v1", - credentialsV1JsonLDBytes)) - if err == nil { - opts = append(opts, loaders.WithCacheEngine(cacheEngine)) +func newChainIDFromString(in string) (core.ChainID, error) { + radix := 10 + if strings.HasPrefix(in, "0x") || strings.HasPrefix(in, "0X") { + radix = 16 + in = in[2:] } - return loaders.NewDocumentLoader(ipfsNode, "", opts...) -} - -type ChainID uint64 - -func (cid ChainID) Unpack() (core.Blockchain, core.NetworkID, error) { - for k, v := range knownChainIDs { - if v == cid { - return k.blockchain, k.networkID, nil - } - } - return core.NoChain, core.NoNetwork, fmt.Errorf("unknown chain ID") -} - -func newChainIDFromString(in string) (ChainID, error) { - var chainID uint64 - var err error - if strings.HasPrefix(in, "0x") || - strings.HasPrefix(in, "0X") { - chainID, err = strconv.ParseUint(in[2:], 16, 64) - if err != nil { - return 0, err - } - } else { - chainID, err = strconv.ParseUint(in, 10, 64) - if err != nil { - return 0, err - } + var chainID core.ChainID + assertUnderlineTypeInt32(chainID) + i, err := strconv.ParseInt(in, radix, 32) + if err != nil { + return 0, fmt.Errorf("can't parse ChainID type: %w", err) } - return ChainID(chainID), nil -} - -type chainIDKey struct { - blockchain core.Blockchain - networkID core.NetworkID -} - -var knownChainIDs = map[chainIDKey]ChainID{ - {core.Ethereum, core.Main}: 1, - {core.Ethereum, core.Goerli}: 5, - {core.Polygon, core.Main}: 137, - {core.ZkEVM, core.Main}: 1101, - {core.ZkEVM, core.Test}: 1442, - {core.Polygon, core.Mumbai}: 80001, - {core.Ethereum, core.Sepolia}: 11155111, + return core.ChainID(i), nil } func (cfg EnvConfig) networkCfgByID(id *core.ID) (ChainConfig, error) { @@ -2068,7 +2013,7 @@ func (cfg EnvConfig) networkCfgByID(id *core.ID) (ChainConfig, error) { return cfg.networkCfgByChainID(chainID) } -func (cfg EnvConfig) networkCfgByChainID(chainID ChainID) (ChainConfig, error) { +func (cfg EnvConfig) networkCfgByChainID(chainID core.ChainID) (ChainConfig, error) { chainCfg, ok := cfg.ChainConfigs[chainID] if !ok { chainCfg = cfg.defaultChainCfg() @@ -2084,7 +2029,7 @@ func (cfg EnvConfig) defaultChainCfg() ChainConfig { } } -func chainIDFromID(id *core.ID) (ChainID, error) { +func chainIDFromID(id *core.ID) (core.ChainID, error) { blockchain, err := core.BlockchainFromID(*id) if err != nil { return 0, err @@ -2095,13 +2040,7 @@ func chainIDFromID(id *core.ID) (ChainID, error) { return 0, err } - key := chainIDKey{blockchain, networkID} - chainID, ok := knownChainIDs[key] - if !ok { - return 0, fmt.Errorf("unknown chain: %s", id.String()) - } - - return chainID, nil + return core.GetChainID(blockchain, networkID) } // Currently, our library does not have a Close function. As a result, we @@ -2567,7 +2506,7 @@ func fmtPath(path merklize.Path) string { func verifierIDFromTxData(txData txData) (*core.ID, error) { genState := core.GenesisFromEthAddress(txData.ContractAddress) - blockchain, networkID, err := txData.ChainID.Unpack() + blockchain, networkID, err := core.NetworkByChainID(txData.ChainID) if err != nil { return nil, err } @@ -2578,3 +2517,84 @@ func verifierIDFromTxData(txData txData) (*core.ID, error) { id := core.NewID(tp, genState) return &id, nil } + +type GenesysIDResponse struct { + DID string `json:"did"` + ID string `json:"id"` + IDAsInt string `json:"idAsInt"` +} + +func NewGenesysID(ctx context.Context, cfg EnvConfig, + in []byte) (GenesysIDResponse, error) { + + var req struct { + ClaimsTreeRoot *JsonFieldIntStr `json:"claimsTreeRoot"` + Blockchain *core.Blockchain `json:"blockchain"` + Network *core.NetworkID `json:"network"` + Method *core.DIDMethod `json:"method"` + } + + if in == nil { + return GenesysIDResponse{}, errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return GenesysIDResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.ClaimsTreeRoot == nil { + return GenesysIDResponse{}, + errors.New("claims tree root is not set in the request") + } + + if req.Blockchain == nil { + return GenesysIDResponse{}, + errors.New("blockchain is not set in the request") + } + + if req.Network == nil { + return GenesysIDResponse{}, + errors.New("network is not set in the request") + } + + if req.Method == nil { + // for backward compatibility, if method is not set, use polygon + var m = core.DIDMethodPolygonID + req.Method = &m + } + + state, err := merkletree.HashElems(req.ClaimsTreeRoot.Int(), + merkletree.HashZero.BigInt(), merkletree.HashZero.BigInt()) + if err != nil { + return GenesysIDResponse{}, + fmt.Errorf("failed to calculate state: %w", err) + } + + typ, err := core.BuildDIDType(*req.Method, *req.Blockchain, + *req.Network) + if err != nil { + return GenesysIDResponse{}, + fmt.Errorf("failed to build DID type: %w", err) + } + + coreID, err := core.NewIDFromIdenState(typ, state.BigInt()) + if err != nil { + return GenesysIDResponse{}, + fmt.Errorf("failed to create ID: %w", err) + } + + did, err := core.ParseDIDFromID(*coreID) + if err != nil { + return GenesysIDResponse{}, + fmt.Errorf("failed to make DID from ID: %w", err) + } + + return GenesysIDResponse{ + DID: did.String(), + ID: coreID.String(), + IDAsInt: coreID.BigInt().String(), + }, + nil +} diff --git a/inputs_sig_test.go b/inputs_sig_test.go index 619d9cf..2be5d0d 100644 --- a/inputs_sig_test.go +++ b/inputs_sig_test.go @@ -116,7 +116,7 @@ func TestPrepareInputs(t *testing.T) { httpmock.IgnoreUntouchedURLs(), httpmock.WithPostRequestBodyProcessor(removeIdFromEthBody))() cfg := EnvConfig{ - ChainConfigs: map[ChainID]ChainConfig{ + ChainConfigs: map[core.ChainID]ChainConfig{ 80001: { RPCUrl: "http://localhost:8545", StateContractAddr: common.HexToAddress("0x134B1BE34911E39A8397ec6289782989729807a4"), @@ -141,7 +141,7 @@ func TestPrepareInputs(t *testing.T) { httpmock.IgnoreUntouchedURLs(), httpmock.WithPostRequestBodyProcessor(removeIdFromEthBody))() cfg := EnvConfig{ - ChainConfigs: map[ChainID]ChainConfig{ + ChainConfigs: map[core.ChainID]ChainConfig{ 80001: { RPCUrl: "http://localhost:8545", StateContractAddr: common.HexToAddress("0x134B1BE34911E39A8397ec6289782989729807a4"), @@ -696,7 +696,7 @@ func TestEnvConfig_UnmarshalJSON(t *testing.T) { StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), ReverseHashServiceUrl: "http://localhost:8003", IPFSNodeURL: "http://localhost:5001", - ChainConfigs: map[ChainID]ChainConfig{ + ChainConfigs: map[core.ChainID]ChainConfig{ 1: { RPCUrl: "http://localhost:8545", StateContractAddr: common.HexToAddress("0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655"), @@ -1144,3 +1144,86 @@ func TestVerifierIDFromTxData(t *testing.T) { "17966356888215056651324659145404695842840677593163532338422715818832826881", id.BigInt().Text(10)) } + +func TestNewGenesysID(t *testing.T) { + in := `{ + "claimsTreeRoot":"16306276920027997118951972513784102597349518910734830865369546877495436692483", + "blockchain":"polygon", + "network":"mumbai" +}` + + ctx := context.Background() + + resp, err := NewGenesysID(ctx, EnvConfig{}, []byte(in)) + require.NoError(t, err) + wantResp := GenesysIDResponse{ + DID: "did:polygonid:polygon:mumbai:2qMJFBiKaPx3XCKbu1Q45QNaUfdpzk9KkcmNaiyAxc", + ID: "2qMJFBiKaPx3XCKbu1Q45QNaUfdpzk9KkcmNaiyAxc", + IDAsInt: "24121873806719949961527676655485054357633990236472608901764984551147442690", + } + require.Equal(t, wantResp, resp) +} + +func TestNewGenesysID_DIDMethod(t *testing.T) { + cfgJSON := `{ + "chainConfigs": { + "59140": { + "rpcUrl": "http://localhost:8545", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + } + }, + "didMethods": [ + { + "name": "polygonid", + "blockchain": "linea", + "network": "testnet", + "networkFlag": "0b01000011", + "methodByte": "0b00000010", + "chainID": "59140" + } + ] +}` + cfg, err := NewEnvConfigFromJSON([]byte(cfgJSON)) + require.NoError(t, err) + + in := `{ + "claimsTreeRoot":"16306276920027997118951972513784102597349518910734830865369546877495436692483", + "blockchain":"linea", + "network":"testnet" +}` + + ctx := context.Background() + + resp, err := NewGenesysID(ctx, cfg, []byte(in)) + require.NoError(t, err) + wantResp := GenesysIDResponse{ + DID: "did:polygonid:linea:testnet:31Akw5AB2xBrwqmbDUA2XoSGCfTepz52q9jmFE4mXA", + ID: "31Akw5AB2xBrwqmbDUA2XoSGCfTepz52q9jmFE4mXA", + IDAsInt: "24460059377712687587111979692736628604804094576108957842967948238113620738", + } + require.Equal(t, wantResp, resp) +} + +func TestNewGenesysID_DIDMethod_Error(t *testing.T) { + cfgJSON := `{ + "chainConfigs": { + "59140": { + "rpcUrl": "http://localhost:8545", + "stateContractAddr": "0xEA9aF2088B4a9770fC32A12fD42E61BDD317E655" + } + } +}` + cfg, err := NewEnvConfigFromJSON([]byte(cfgJSON)) + require.NoError(t, err) + + in := `{ + "claimsTreeRoot":"16306276920027997118951972513784102597349518910734830865369546877495436692483", + "blockchain":"linea2", + "network":"testnet2" +}` + + ctx := context.Background() + + _, err = NewGenesysID(ctx, cfg, []byte(in)) + require.EqualError(t, err, "failed to build DID type: not supported network") +} diff --git a/method_config.go b/method_config.go new file mode 100644 index 0000000..e20c2d0 --- /dev/null +++ b/method_config.go @@ -0,0 +1,88 @@ +package c_polygonid + +import ( + "encoding/json" + "errors" + "hash/fnv" + "math" + "math/big" + + core "github.com/iden3/go-iden3-core/v2" +) + +type MethodConfig struct { + MethodName core.DIDMethod `json:"name"` + Blockchain core.Blockchain `json:"blockchain"` + NetworkID core.NetworkID `json:"network"` + NetworkFlag byte `json:"networkFlag"` + MethodByte byte `json:"methodByte"` + ChainID core.ChainID `json:"chainId"` +} + +func (cfg *MethodConfig) UnmarshalJSON(in []byte) error { + var j struct { + MethodName *core.DIDMethod `json:"name"` + Blockchain *core.Blockchain `json:"blockchain"` + NetworkID *core.NetworkID `json:"network"` + NetworkFlag *jsonByte `json:"networkFlag"` + MethodByte *jsonByte `json:"methodByte"` + ChainID *jsonNumber `json:"chainId"` + } + err := json.Unmarshal(in, &j) + if err != nil { + return err + } + + if j.MethodName == nil { + return errors.New("method name is empty") + } + cfg.MethodName = *j.MethodName + + if j.Blockchain == nil { + return errors.New("blockchain is empty") + } + cfg.Blockchain = *j.Blockchain + + if j.NetworkID == nil { + return errors.New("network ID is empty") + } + cfg.NetworkID = *j.NetworkID + + if j.NetworkFlag == nil { + return errors.New("network flag is not set") + } + cfg.NetworkFlag = byte(*j.NetworkFlag) + + if j.MethodByte == nil { + return errors.New("method byte is not set") + } + cfg.MethodByte = byte(*j.MethodByte) + + if j.ChainID == nil { + return errors.New("chain ID is not set") + } + assertUnderlineTypeInt32(cfg.ChainID) + chainID := (*big.Int)(j.ChainID) + if !chainID.IsInt64() { + return errors.New("chain ID is not inside int32 bounds") + } + chainIDi := chainID.Int64() + if chainIDi > math.MaxInt32 || chainIDi < math.MinInt32 { + return errors.New("chain ID is not inside int32 bounds") + } + cfg.ChainID = core.ChainID(chainIDi) + + return nil +} + +// Hash generate a unique hash for the method config +func (cfg MethodConfig) Hash() uint64 { + h := fnv.New64a() + // errors are always nil + _, _ = h.Write([]byte(cfg.MethodName)) + _, _ = h.Write([]byte(cfg.Blockchain)) + _, _ = h.Write([]byte(cfg.NetworkID)) + _, _ = h.Write([]byte{cfg.NetworkFlag, cfg.MethodByte}) + _, _ = h.Write(chainIDToBytes(cfg.ChainID)) + return h.Sum64() +} diff --git a/method_config_test.go b/method_config_test.go new file mode 100644 index 0000000..735e9b2 --- /dev/null +++ b/method_config_test.go @@ -0,0 +1,52 @@ +package c_polygonid + +import ( + "encoding/json" + "testing" + + core "github.com/iden3/go-iden3-core/v2" + "github.com/stretchr/testify/require" +) + +func TestMethodConfig_UnmarshalJSON(t *testing.T) { + in := `{ + "name": "ethr", + "blockchain": "ethereum", + "network": "mainnet", + "networkFlag": 6, + "methodByte": "0b010011", + "chainID": "10293" + }` + var methodCfg MethodConfig + err := json.Unmarshal([]byte(in), &methodCfg) + require.NoError(t, err) + + require.Equal(t, core.DIDMethod("ethr"), methodCfg.MethodName) + require.Equal(t, core.Blockchain("ethereum"), methodCfg.Blockchain) + require.Equal(t, core.NetworkID("mainnet"), methodCfg.NetworkID) + require.Equal(t, byte(6), methodCfg.NetworkFlag) + require.Equal(t, byte(19), methodCfg.MethodByte) + require.Equal(t, core.ChainID(10293), methodCfg.ChainID) + + in = `{ + "blockchain": "ethereum", + "network": "mainnet", + "networkFlag": 6, + "methodByte": "0b010011", + "chainID": "10293" + }` + err = json.Unmarshal([]byte(in), &methodCfg) + require.EqualError(t, err, "method name is empty") + + in = `{ + "name": "ethr", + "blockchain": "ethereum", + "network": "mainnet", + "networkFlag": 6, + "methodByte": "0b010011", + "chainID": "0X123" + }` + err = json.Unmarshal([]byte(in), &methodCfg) + require.NoError(t, err) + require.Equal(t, core.ChainID(291), methodCfg.ChainID) +} diff --git a/types.go b/types.go index 7fac9f0..45b8539 100644 --- a/types.go +++ b/types.go @@ -1,11 +1,19 @@ package c_polygonid import ( + "encoding/binary" "encoding/hex" + "encoding/json" "errors" + "fmt" "log" + "math/big" + "strconv" + "strings" + core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/iden3/go-iden3-crypto/utils" ) // wrapper type to unmarshal signature from json @@ -33,3 +41,131 @@ func (s *hexSigJson) UnmarshalJSON(bytes []byte) error { _, err = (*babyjub.Signature)(s).Decompress(compSigBytes) return err } + +type jsonByte byte + +func (b *jsonByte) Byte() byte { + return byte(*b) +} + +func (b *jsonByte) UnmarshalJSON(in []byte) error { + if len(in) == 0 { + return errors.New("invalid byte format") + } + num := string(in) + radix := 10 + if in[0] == '"' { + if len(in) < 3 || in[len(in)-1] != '"' { + return errors.New("invalid byte format") + } + + num = string(in[1 : len(in)-1]) + prefix := "" + if len(num) > 2 { + prefix = num[:2] + } + switch prefix { + case "0x", "0X": + radix = 16 + num = num[2:] + case "0b", "0B": + radix = 2 + num = num[2:] + } + } + i, err := strconv.ParseUint(num, radix, 8) + if err != nil { + return err + } + *b = jsonByte(i) + return nil +} + +type jsonNumber big.Int + +// UnmarshalJSON implements json.Unmarshaler interface. It unmarshals any +// number-like JSON value into a big.Int. +// +// Examples: +// +// "0x123" -> 291 +// "0X123" -> 291 +// "123" -> 123 +// 123 -> 123 +func (j *jsonNumber) UnmarshalJSON(in []byte) error { + if len(in) == 0 { + return errors.New("empty input") + } + + if j == nil { + return errors.New("jsonNumber is nil") + } + + var s string + var radix = 10 + + if in[0] >= '0' && in[0] <= '9' { + s = string(in) + } else if in[0] == '"' && in[len(in)-1] == '"' { + s = string(in[1 : len(in)-1]) + if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { + radix = 16 + s = s[2:] + } + } + + _, ok := ((*big.Int)(j)).SetString(s, radix) + if !ok { + return errors.New("invalid integer number format") + } + + return nil +} + +// function to fail a compilation if underlined type is not int32 +func assertUnderlineTypeInt32[T ~int32](_ T) {} + +func chainIDToInt(chainID core.ChainID) int { + // assertion is required to correctly handle the underlined type during + // int conversion + assertUnderlineTypeInt32(chainID) + return int(chainID) +} + +func chainIDToBytes(chainID core.ChainID) []byte { + // assertion is required to correctly handle the underlined type during + // int to bytes serialization + assertUnderlineTypeInt32(chainID) + var chainIDBytes [4]byte + binary.LittleEndian.PutUint32(chainIDBytes[:], uint32(chainID)) + return chainIDBytes[:] +} + +type JsonFieldIntStr big.Int + +func (i *JsonFieldIntStr) UnmarshalJSON(bytes []byte) error { + var s *string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + if s == nil { + (*big.Int)(i).SetInt64(0) + return nil + } + + _, ok := (*big.Int)(i).SetString(*s, 10) + if !ok { + return fmt.Errorf("invalid Int string") + } + + if !utils.CheckBigIntInField((*big.Int)(i)) { + return fmt.Errorf("int is not in field") + } + + return nil +} + +func (i *JsonFieldIntStr) Int() *big.Int { + return (*big.Int)(i) +} diff --git a/types_test.go b/types_test.go new file mode 100644 index 0000000..bee35e3 --- /dev/null +++ b/types_test.go @@ -0,0 +1,87 @@ +package c_polygonid + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestJsonNumber_UnmarshalJSON(t *testing.T) { + in := `123` + var jn = new(big.Int) + err := ((*jsonNumber)(jn)).UnmarshalJSON([]byte(in)) + require.NoError(t, err) + require.Equal(t, "123", jn.String()) + + var jn2 struct { + Jn *jsonNumber + } + in2 := `{"jn":123}` + err = json.Unmarshal([]byte(in2), &jn2) + require.NoError(t, err) + require.Equal(t, "123", ((*big.Int)(jn2.Jn)).String()) + + var jn3 struct { + Jn *jsonNumber + } + in3 := `{"jn":"123"}` + err = json.Unmarshal([]byte(in3), &jn3) + require.NoError(t, err) + require.Equal(t, "123", ((*big.Int)(jn3.Jn)).String()) + + var jn4 struct { + Jn *jsonNumber + } + in4 := `{"jn":"0x123"}` + err = json.Unmarshal([]byte(in4), &jn4) + require.NoError(t, err) + require.Equal(t, "291", ((*big.Int)(jn4.Jn)).String()) + + var jn5 struct { + Jn *jsonNumber + } + in5 := `{"jn":"0X123"}` + err = json.Unmarshal([]byte(in5), &jn5) + require.NoError(t, err) + require.Equal(t, "291", ((*big.Int)(jn5.Jn)).String()) + + in6 := `123.4` + var jn6 = new(big.Int) + err = json.Unmarshal([]byte(in6), (*jsonNumber)(jn6)) + require.EqualError(t, err, "invalid integer number format") + + var jn7 = new(big.Int) + err = ((*jsonNumber)(jn7)).UnmarshalJSON(nil) + require.EqualError(t, err, "empty input") +} + +func TestJsonByte_UnmarshalJSON(t *testing.T) { + testCases := []struct { + in []byte + want byte + err string + }{ + {[]byte(`123`), 123, ""}, + {[]byte(`"123"`), 123, ""}, + {[]byte(`"0xab"`), 171, ""}, + {[]byte(`"0xa"`), 10, ""}, + {[]byte(`"0xabc"`), 172, + "strconv.ParseUint: parsing \"abc\": value out of range"}, + {[]byte(`"0B101011"`), 43, ""}, + } + + for _, tc := range testCases { + t.Run(string(tc.in), func(t *testing.T) { + var jb jsonByte + err := json.Unmarshal(tc.in, &jb) + if tc.err != "" { + require.EqualError(t, err, tc.err) + return + } + require.NoError(t, err) + require.Equal(t, jsonByte(tc.want), jb) + }) + } +}