From f4fd2e6a4194824bafe62933692f82d6641bfcd9 Mon Sep 17 00:00:00 2001 From: Dimasik Kolezhniuk Date: Tue, 12 Dec 2023 13:16:50 +0100 Subject: [PATCH] Custom registration (#457) * Custom registration --- chain.go | 69 ++++++++++++++++++++ did.go | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++-- did_test.go | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 411 insertions(+), 6 deletions(-) create mode 100644 chain.go diff --git a/chain.go b/chain.go new file mode 100644 index 0000000..4a4e61b --- /dev/null +++ b/chain.go @@ -0,0 +1,69 @@ +package core + +import ( + "fmt" + + "github.com/iden3/go-iden3-core/v2/w3c" +) + +// ChainID is alias for int32 that represents ChainID +type ChainID int32 + +// ChainIDs Object containing chain IDs for various blockchains and networks. +var chainIDs = map[string]ChainID{ + "eth:main": 1, + "eth:goerli": 5, + "eth:sepolia": 11155111, + "polygon:main": 137, + "polygon:mumbai": 80001, + "zkevm:main": 1101, + "zkevm:test": 1442, +} + +// ChainIDfromDID returns chain name from w3c.DID +func ChainIDfromDID(did w3c.DID) (ChainID, error) { + + id, err := IDFromDID(did) + if err != nil { + return 0, err + } + + blockchain, err := BlockchainFromID(id) + if err != nil { + return 0, err + } + + networkID, err := NetworkIDFromID(id) + if err != nil { + return 0, err + } + + chainID, ok := chainIDs[fmt.Sprintf("%s:%s", blockchain, networkID)] + if !ok { + return 0, fmt.Errorf("chainID not found for %s:%s", blockchain, networkID) + } + + return chainID, nil +} + +// RegisterChainID registers chainID for blockchain and network +func RegisterChainID(blockchain Blockchain, network NetworkID, chainID int) error { + k := fmt.Sprintf("%s:%s", blockchain, network) + existingChainID, ok := chainIDs[k] + if ok && existingChainID != ChainID(chainID) { + return fmt.Errorf("chainID '%s:%s' already registered with value %d", blockchain, network, existingChainID) + } + chainIDs[k] = ChainID(chainID) + + return nil +} + +// GetChainID returns chainID for blockchain and network +func GetChainID(blockchain Blockchain, network NetworkID) (ChainID, error) { + k := fmt.Sprintf("%s:%s", blockchain, network) + if _, ok := chainIDs[k]; !ok { + return 0, fmt.Errorf("chainID not registered for %s:%s", blockchain, network) + } + + return chainIDs[k], nil +} diff --git a/did.go b/did.go index 4abece0..adf3008 100644 --- a/did.go +++ b/did.go @@ -38,6 +38,22 @@ const ( DIDMethodOther DIDMethod = "" ) +var didMethods = map[DIDMethod]DIDMethod{ + DIDMethodIden3: DIDMethodIden3, + DIDMethodPolygonID: DIDMethodPolygonID, + DIDMethodOther: DIDMethodOther, +} + +// GetDIDMethod returns DID method by name +func GetDIDMethod(name string) (DIDMethod, error) { + + method, ok := didMethods[DIDMethod(name)] + if !ok { + return DIDMethodOther, fmt.Errorf("DID method '%s' not found", name) + } + return method, nil +} + // Blockchain id of the network "eth", "polygon", etc. type Blockchain string @@ -56,30 +72,75 @@ const ( NoChain Blockchain = "" ) +var blockchains = map[Blockchain]Blockchain{ + Ethereum: Ethereum, + Polygon: Polygon, + ZkEVM: ZkEVM, + UnknownChain: UnknownChain, + ReadOnly: ReadOnly, + NoChain: NoChain, +} + +// GetBlockchain returns blockchain by name +func GetBlockchain(name string) (Blockchain, error) { + blockchain, ok := blockchains[Blockchain(name)] + if !ok { + return UnknownChain, fmt.Errorf("blockchain '%s' not found", name) + } + return blockchain, nil +} + +// RegisterBlockchain registers new blockchain +func RegisterBlockchain(b Blockchain) error { + blockchains[b] = b + return nil +} + // NetworkID is method specific network identifier type NetworkID string const ( // Main is main network Main NetworkID = "main" - // Mumbai is polygon mumbai test network Mumbai NetworkID = "mumbai" - // Goerli is ethereum goerli test network Goerli NetworkID = "goerli" // goerli // Sepolia is ethereum Sepolia test network Sepolia NetworkID = "sepolia" - // Test is test network Test NetworkID = "test" - // UnknownNetwork is used when it's not possible to retrieve network from identifier UnknownNetwork NetworkID = "unknown" // NoNetwork should be used for readonly identity to build readonly flag NoNetwork NetworkID = "" ) +var networks = map[NetworkID]NetworkID{ + Main: Main, + Mumbai: Mumbai, + Goerli: Goerli, + Sepolia: Sepolia, + Test: Test, + UnknownNetwork: UnknownNetwork, + NoNetwork: NoNetwork, +} + +// GetNetwork returns network by name +func GetNetwork(name string) (NetworkID, error) { + network, ok := networks[NetworkID(name)] + if !ok { + return UnknownNetwork, fmt.Errorf("network '%s' not found", name) + } + return network, nil +} + +// RegisterNetwork registers new network +func RegisterNetwork(n NetworkID) error { + networks[n] = n + return nil +} + // DIDMethodByte did method flag representation var DIDMethodByte = map[DIDMethod]byte{ DIDMethodIden3: 0b00000001, @@ -87,6 +148,24 @@ var DIDMethodByte = map[DIDMethod]byte{ DIDMethodOther: 0b11111111, } +// RegisterDIDMethod registers new DID method with byte flag +func RegisterDIDMethod(m DIDMethod, b byte) error { + existingByte, ok := DIDMethodByte[m] + if ok && existingByte != b { + return fmt.Errorf("DID method '%s' already registered with byte %b", m, existingByte) + } + + max := DIDMethodByte[DIDMethodOther] + if b >= max { + return fmt.Errorf("Can't register DID method byte: current %b, maximum byte allowed: %b", b, max-1) + } + + didMethods[m] = m + DIDMethodByte[m] = b + + return nil +} + // DIDNetworkFlag is a structure to represent DID blockchain and network id type DIDNetworkFlag struct { Blockchain Blockchain @@ -126,6 +205,88 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ }, } +// DIDMethodNetworkParams is a structure to represent DID method network options +type DIDMethodNetworkParams struct { + Method DIDMethod + Blockchain Blockchain + Network NetworkID + NetworkFlag byte +} + +type registrationOptions struct { + chainID *int + methodByte *byte +} + +// RegistrationOptions is a type for DID method network options +type RegistrationOptions func(params *registrationOptions) + +// WithChainID registers new chain ID method with byte flag +func WithChainID(chainID int) RegistrationOptions { + return func(opts *registrationOptions) { + opts.chainID = &chainID + } +} + +// WithDIDMethodByte registers new DID method with byte flag +func WithDIDMethodByte(methodByte byte) RegistrationOptions { + return func(opts *registrationOptions) { + opts.methodByte = &methodByte + } +} + +// RegisterDIDMethodNetwork registers new DID method network +func RegisterDIDMethodNetwork(params DIDMethodNetworkParams, opts ...RegistrationOptions) error { + var err error + o := registrationOptions{} + for _, opt := range opts { + opt(&o) + } + + b := params.Blockchain + n := params.Network + m := params.Method + + err = RegisterBlockchain(b) + if err != nil { + return err + } + + err = RegisterNetwork(n) + if err != nil { + return err + } + + if o.methodByte != nil { + err = RegisterDIDMethod(m, *o.methodByte) + if err != nil { + return err + } + } + + flg := DIDNetworkFlag{Blockchain: b, NetworkID: n} + + if _, ok := DIDMethodNetwork[m]; !ok { + DIDMethodNetwork[m] = map[DIDNetworkFlag]byte{} + } + + if o.chainID != nil { + err = RegisterChainID(b, n, *o.chainID) + if err != nil { + return err + } + } + existedFlag, ok := DIDMethodNetwork[m][flg] + if ok && existedFlag != params.NetworkFlag { + return fmt.Errorf("DID method network '%s' with blockchain '%s' and network '%s' already registered with another flag '%b'", + m, b, n, existedFlag) + } + + DIDMethodNetwork[m][flg] = params.NetworkFlag + return nil + +} + // BuildDIDType builds bytes type from chain and network func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { @@ -218,8 +379,11 @@ func newIDFromUnsupportedDID(did w3c.DID) ID { } func idFromDID(did w3c.DID) (ID, error) { - method := DIDMethod(did.Method) - _, ok := DIDMethodByte[method] + method, ok := didMethods[DIDMethod(did.Method)] + if !ok { + method = DIDMethodOther + } + _, ok = DIDMethodByte[method] if !ok || method == DIDMethodOther { return ID{}, ErrMethodUnknown } diff --git a/did_test.go b/did_test.go index 8deaa2e..51e26b5 100644 --- a/did_test.go +++ b/did_test.go @@ -379,3 +379,175 @@ func ethAddrFromHex(ea string) [20]byte { copy(ethAddr[:], eaBytes) return ethAddr } + +func TestCustomDIDRegistration(t *testing.T) { + testCases := []struct { + Description string + Data DIDMethodNetworkParams + opts []RegistrationOptions + }{ + { + Description: "register new did method network", + Data: DIDMethodNetworkParams{ + Method: "test_method", + Blockchain: "test_chain", + Network: "test_net", + NetworkFlag: 0b0001_0001, + }, + opts: []RegistrationOptions{WithChainID(101), WithDIDMethodByte(0b00000011)}, + }, + { + Description: "register one more new did method network", + Data: DIDMethodNetworkParams{ + Method: "method", + Blockchain: "chain", + Network: "network", + NetworkFlag: 0b0001_0001, + }, + opts: []RegistrationOptions{WithChainID(102), WithDIDMethodByte(0b00000100)}, + }, + { + Description: "register the same new did method network", + Data: DIDMethodNetworkParams{ + Method: "method", + Blockchain: "chain", + Network: "network", + NetworkFlag: 0b0001_0001, + }, + opts: []RegistrationOptions{WithChainID(102), WithDIDMethodByte(0b00000100)}, + }, + { + Description: "register network to existing did method", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: "chain", + Network: Test, + NetworkFlag: 0b01000000 | 0b00000011, + }, + opts: []RegistrationOptions{WithChainID(103)}, + }, + { + Description: "register network to existing did method and chainId", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: ReadOnly, + Network: NoNetwork, + NetworkFlag: 0b00000000, + }, + opts: []RegistrationOptions{WithChainID(103)}, + }, + { + Description: "register one more network to existing did method", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: ReadOnly, + Network: "network", + NetworkFlag: 0b01000000 | 0b00000011, + }, + opts: []RegistrationOptions{WithChainID(104)}, + }, + { + Description: "register known chain id to new did method", + Data: DIDMethodNetworkParams{ + Method: "method2", + Blockchain: Polygon, + Network: Mumbai, + NetworkFlag: 0b0001_0001, + }, + opts: []RegistrationOptions{WithDIDMethodByte(1)}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Description, func(t *testing.T) { + err := RegisterDIDMethodNetwork(tc.Data, tc.opts...) + require.NoError(t, err) + }) + } + + d := helperBuildDIDFromType(t, "method", "chain", "network") + require.Equal(t, "4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT", d.IDStrings[2]) + + did3, err := w3c.ParseDID("did:method:chain:network:4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT") + require.NoError(t, err) + + id, err := idFromDID(*did3) + require.NoError(t, err) + + require.Equal(t, "4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT", id.String()) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethod("method"), method) + + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Blockchain("chain"), blockchain) + + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, NetworkID("network"), networkID) + +} + +func TestCustomDIDRegistration_Negative(t *testing.T) { + testCases := []struct { + Description string + Data DIDMethodNetworkParams + opts []RegistrationOptions + err string + }{ + + { + Description: "try to overwrite existing chain id", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: Polygon, + Network: Mumbai, + NetworkFlag: 0b0001_0001, + }, + opts: []RegistrationOptions{WithChainID(1)}, + err: "chainID 'polygon:mumbai' already registered with value 80001", + }, + { + Description: "try to overwrite existing DID method byte", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: Ethereum, + Network: Main, + NetworkFlag: 0b00100000 | 0b00000001, + }, + opts: []RegistrationOptions{WithChainID(1), WithDIDMethodByte(0b00000010)}, + err: "DID method 'iden3' already registered with byte 1", + }, + { + Description: "try to write max did method byte", + Data: DIDMethodNetworkParams{ + Method: "method33", + Blockchain: Ethereum, + Network: Main, + NetworkFlag: 0b00100000 | 0b00000001, + }, + opts: []RegistrationOptions{WithChainID(1), WithDIDMethodByte(0b11111111)}, + err: "Can't register DID method byte: current 11111111, maximum byte allowed: 11111110", + }, + { + Description: "try to rewrite existing DID Method Network Flag", + Data: DIDMethodNetworkParams{ + Method: DIDMethodIden3, + Blockchain: Ethereum, + Network: Main, + NetworkFlag: 0b00100000 | 0b00000011, + }, + opts: nil, + err: "DID method network 'iden3' with blockchain 'eth' and network 'main' already registered with another flag '100001'", + }, + } + + for _, tc := range testCases { + t.Run(tc.Description, func(t *testing.T) { + err := RegisterDIDMethodNetwork(tc.Data, tc.opts...) + require.EqualError(t, err, tc.err) + }) + } + +}