Skip to content

Commit

Permalink
Add didMethods config option to register custom DID methods (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
olomix authored Feb 27, 2024
1 parent c869a1f commit f4c7c42
Show file tree
Hide file tree
Showing 16 changed files with 929 additions and 169 deletions.
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

0 comments on commit f4c7c42

Please sign in to comment.