Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add didMethods config option to register custom DID methods #52

Merged
merged 8 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 28 additions & 30 deletions cmd/polygonid/polygonid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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,
Expand Down
129 changes: 129 additions & 0 deletions env_config.go
Original file line number Diff line number Diff line change
@@ -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...)
}
86 changes: 86 additions & 0 deletions env_config_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Loading