From c9b9fe9f6df9e1bf48e4631d1e730fc379180c27 Mon Sep 17 00:00:00 2001 From: Oleksandr Brezhniev Date: Sat, 15 Apr 2023 03:07:46 +0100 Subject: [PATCH 01/37] Add support for readonly and on-chain identities. Initial work on parsing of DIDs with other DID methods --- did.go | 141 ++++++++++++++++++++++++++++++++++++++++++---------- did_test.go | 96 +++++++++++++++++++++++++++++++---- id.go | 54 ++++++++++++++++---- id_test.go | 5 +- 4 files changed, 249 insertions(+), 47 deletions(-) diff --git a/did.go b/did.go index 4aa2ac1..2edce39 100644 --- a/did.go +++ b/did.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "encoding/json" "errors" "fmt" @@ -12,9 +13,11 @@ var ( // ErrInvalidDID invalid did format. ErrInvalidDID = errors.New("invalid did format") // ErrDIDMethodNotSupported unsupported did method. - ErrDIDMethodNotSupported = errors.New("did method is not supported") + ErrDIDMethodNotSupported = errors.New("not supported did method") + // ErrBlockchainNotSupportedForDID unsupported network for did. + ErrBlockchainNotSupportedForDID = errors.New("not supported blockchain") // ErrNetworkNotSupportedForDID unsupported network for did. - ErrNetworkNotSupportedForDID = errors.New("network in not supported for did") + ErrNetworkNotSupportedForDID = errors.New("not supported network") ) // DIDSchema DID Schema @@ -40,7 +43,9 @@ const ( Polygon Blockchain = "polygon" // UnknownChain is used when it's not possible to retrieve blockchain type from identifier UnknownChain Blockchain = "unknown" - // NoChain should be used for readonly identity to build readonly flag + // ReadOnly should be used for readonly identity to build readonly flag + ReadOnly Blockchain = "readonly" + // NoChain can be used for identity to build readonly flag NoChain Blockchain = "" ) @@ -77,7 +82,7 @@ type DIDNetworkFlag struct { // DIDMethodNetwork is map for did methods and their blockchain networks var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ DIDMethodIden3: { - {Blockchain: NoChain, NetworkID: NoNetwork}: 0b00000000, + {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, @@ -86,10 +91,13 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, }, DIDMethodPolygonID: { - {Blockchain: NoChain, NetworkID: NoNetwork}: 0b00000000, + {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, + + {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, + {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, }, } @@ -101,6 +109,10 @@ func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([ return [2]byte{}, ErrDIDMethodNotSupported } + if blockchain == NoChain { + blockchain = ReadOnly + } + sb, ok := DIDMethodNetwork[method][DIDNetworkFlag{Blockchain: blockchain, NetworkID: network}] if !ok { return [2]byte{}, ErrNetworkNotSupportedForDID @@ -108,6 +120,19 @@ func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([ return [2]byte{fb, sb}, nil } +// BuildDIDTypeOnChain builds bytes type from chain and network +func BuildDIDTypeOnChain(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { + typ, err := BuildDIDType(method, blockchain, network) + if err != nil { + return [2]byte{}, err + } + + // set on-chain flag (first bit of first byte) to 1 + typ[0] |= MethodOnChainFlag + + return typ, nil +} + // FindNetworkIDForDIDMethodByValue finds network by byte value func FindNetworkIDForDIDMethodByValue(method DIDMethod, _v byte) (NetworkID, error) { _, ok := DIDMethodNetwork[method] @@ -149,45 +174,89 @@ func FindDIDMethodByValue(_v byte) (DIDMethod, error) { // DID Decentralized Identifiers (DIDs) // https://w3c.github.io/did-core/#did-syntax type DID struct { - ID ID // ID did specific id - Method DIDMethod // DIDMethod did method - Blockchain Blockchain // Blockchain network identifier eth / polygon,... - NetworkID NetworkID // NetworkID specific network identifier eth {main, ropsten, rinkeby, kovan} + ID ID // ID did specific id + Method DIDMethod // DIDMethod did method + Blockchain Blockchain // Blockchain network identifier eth / polygon,... + NetworkID NetworkID // NetworkID specific network identifier eth {main, mumbai, goerli} + isUnknownMethod bool // isUnknownMethod specifies if DID is of unsupported method + didString string // didString full DID string identifier } func (did *DID) SetString(didStr string) error { + // Parse method, networks and id arg := strings.Split(didStr, ":") - if len(arg) <= 1 { + if len(arg) < 3 { + return ErrInvalidDID + } + + if arg[0] != DIDSchema { return ErrInvalidDID } + did.didString = didStr did.Method = DIDMethod(arg[1]) + // check did method defined in core lib + _, ok := DIDMethodByte[did.Method] + if !ok { + did.isUnknownMethod = true + + // TODO: algo how to encode unknown did to ID + // keccac256 and take 27 bytes from it + + return nil + } + switch len(arg) { case 5: var err error - // validate id + + // we have both blockchain and network id specified + did.Blockchain = Blockchain(arg[2]) + did.NetworkID = NetworkID(arg[3]) + + // parse id did.ID, err = IDFromString(arg[4]) if err != nil { return fmt.Errorf("%w: %v", ErrInvalidDID, err) } + case 4: + var err error + + // we have only blockchain specified did.Blockchain = Blockchain(arg[2]) - did.NetworkID = NetworkID(arg[3]) + + // set default network id + switch did.Blockchain { + case ReadOnly: + did.NetworkID = NoNetwork + case Polygon: + did.NetworkID = Main + case Ethereum: + did.NetworkID = Main + default: + return ErrNetworkNotSupportedForDID + } + + did.ID, err = IDFromString(arg[3]) + if err != nil { + return fmt.Errorf("%w: %v", ErrInvalidDID, err) + } case 3: var err error - // validate readonly id + + // we do not have blockchain & network id, set default ones + did.Blockchain = Polygon + did.NetworkID = Main + did.ID, err = IDFromString(arg[2]) if err != nil { return fmt.Errorf("%w: %v", ErrInvalidDID, err) } - } - - // check did method defined in core lib - _, ok := DIDMethodByte[did.Method] - if !ok { - return ErrDIDMethodNotSupported + default: + return ErrInvalidDID } // check did network defined in core lib for did method @@ -198,6 +267,10 @@ func (did *DID) SetString(didStr string) error { return ErrNetworkNotSupportedForDID } + if !CheckChecksum(did.ID) { + return fmt.Errorf("%w: %s", ErrInvalidDID, "invalid checksum") + } + // check id contains did network and method return did.validate() } @@ -227,6 +300,12 @@ func (did *DID) validate() error { ErrInvalidDID, d.Blockchain, did.Blockchain) } + if !bytes.Equal(d.ID[:], did.ID[:]) { + return fmt.Errorf( + "%w: ID of core identity %s differs from given did ID %s", + ErrInvalidDID, d.ID.String(), did.ID.String()) + } + return nil } @@ -255,14 +334,22 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { // String did as a string func (did *DID) String() string { - if did.Blockchain == "" { - return fmt.Sprintf("%s:%s:%s", DIDSchema, did.Method, did.ID.String()) + if did.isUnknownMethod { + return did.didString + } + + if did.Blockchain == NoChain || did.Blockchain == ReadOnly { + return fmt.Sprintf("%s:%s:%s:%s", DIDSchema, did.Method, ReadOnly, did.ID.String()) } return fmt.Sprintf("%s:%s:%s:%s:%s", DIDSchema, did.Method, did.Blockchain, did.NetworkID, did.ID.String()) } +func (did *DID) CoreID() ID { + return did.ID +} + // ParseDID method parse string and extract DID if string is valid Iden3 identifier func ParseDID(didStr string) (*DID, error) { var did DID @@ -275,19 +362,23 @@ func ParseDIDFromID(id ID) (*DID, error) { var err error did := DID{} did.ID = id - typ := id.Type() + method := id.MethodByte() + net := id.BlockchainNetworkByte() - did.Method, err = FindDIDMethodByValue(typ[0]) + did.Method, err = FindDIDMethodByValue(method) if err != nil { return nil, err } - did.Blockchain, err = FindBlockchainForDIDMethodByValue(did.Method, typ[1]) + + did.Blockchain, err = FindBlockchainForDIDMethodByValue(did.Method, net) if err != nil { return nil, err } - did.NetworkID, err = FindNetworkIDForDIDMethodByValue(did.Method, typ[1]) + + did.NetworkID, err = FindNetworkIDForDIDMethodByValue(did.Method, net) if err != nil { return nil, err } + return &did, nil } diff --git a/did_test.go b/did_test.go index 8571305..74a6ec8 100644 --- a/did_test.go +++ b/did_test.go @@ -1,10 +1,13 @@ package core import ( + "encoding/hex" "encoding/json" + "fmt" "math/big" "testing" + "github.com/iden3/go-iden3-crypto/utils" "github.com/stretchr/testify/require" ) @@ -18,19 +21,19 @@ func TestParseDID(t *testing.T) { require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", did.ID.String()) - require.Equal(t, Mumbai, did.NetworkID) require.Equal(t, Polygon, did.Blockchain) + require.Equal(t, Mumbai, did.NetworkID) // readonly did - didStr = "did:iden3:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" + didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" did, err = ParseDID(didStr) require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", did.ID.String()) - require.Equal(t, NetworkID(""), did.NetworkID) - require.Equal(t, Blockchain(""), did.Blockchain) + require.Equal(t, ReadOnly, did.Blockchain) + require.Equal(t, NoNetwork, did.NetworkID) require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, did.ID.Type()) } @@ -80,7 +83,7 @@ func TestDID_UnmarshalJSON_Error(t *testing.T) { func TestDIDGenesisFromState(t *testing.T) { - typ0, err := BuildDIDType(DIDMethodIden3, NoChain, NoNetwork) + typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) require.NoError(t, err) genesisState := big.NewInt(1) @@ -88,20 +91,20 @@ func TestDIDGenesisFromState(t *testing.T) { require.NoError(t, err) require.Equal(t, DIDMethodIden3, did.Method) - require.Equal(t, NoChain, did.Blockchain) + require.Equal(t, ReadOnly, did.Blockchain) require.Equal(t, NoNetwork, did.NetworkID) - require.Equal(t, "did:iden3:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did.String()) + require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did.String()) } func TestDID_PolygonID_Types(t *testing.T) { // Polygon no chain, no network - did := helperBuildDIDFromType(t, DIDMethodPolygonID, NoChain, NoNetwork) + did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) require.Equal(t, DIDMethodPolygonID, did.Method) - require.Equal(t, NoChain, did.Blockchain) + require.Equal(t, ReadOnly, did.Blockchain) require.Equal(t, NoNetwork, did.NetworkID) - require.Equal(t, "did:polygonid:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", did.String()) + require.Equal(t, "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", did.String()) // Polygon | Polygon chain, Main did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) @@ -121,6 +124,57 @@ func TestDID_PolygonID_Types(t *testing.T) { } +func TestDID_PolygonID_Types_OnChain(t *testing.T) { + // Polygon | Polygon chain, Mumbai + did1 := helperBuildDIDFromTypeOnchain(t, DIDMethodPolygonID, Polygon, Mumbai) + + idInt, _ := big.NewInt(0).SetString("20318741244951419790279970260471061329920784847526059589266894371380597378", 10) + idBytes := utils.SwapEndianness(idInt.Bytes()) + fmt.Printf("\ndid hex from solidity: %x\n\n", idBytes) + + idExp, err := IDFromInt(idInt) + require.NoError(t, err) + require.Equal(t, "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", idExp.String()) + + var addressBytesExp [20]byte + _, err = hex.Decode(addressBytesExp[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + require.NoError(t, err) + + require.Equal(t, DIDMethodPolygonID, did1.Method) + require.Equal(t, Polygon, did1.Blockchain) + require.Equal(t, Mumbai, did1.NetworkID) + require.Equal(t, true, did1.ID.IsOnChain()) + + addressBytes, err := did1.ID.EthAddress() + require.NoError(t, err) + require.Equal(t, addressBytesExp, addressBytes) + + require.Equal(t, "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", did1.String()) +} + +func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { + id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + require.NoError(t, err) + + did1, err := ParseDIDFromID(id1) + require.NoError(t, err) + + var addressBytesExp [20]byte + _, err = hex.Decode(addressBytesExp[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + require.NoError(t, err) + + require.Equal(t, DIDMethodPolygonID, did1.Method) + require.Equal(t, Polygon, did1.Blockchain) + require.Equal(t, Mumbai, did1.NetworkID) + require.Equal(t, true, did1.ID.IsOnChain()) + + addressBytes, err := did1.ID.EthAddress() + require.NoError(t, err) + require.Equal(t, addressBytesExp, addressBytes) + + require.Equal(t, "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", did1.String()) +} + func helperBuildDIDFromType(t testing.TB, method DIDMethod, blockchain Blockchain, network NetworkID) *DID { t.Helper() @@ -133,3 +187,25 @@ func helperBuildDIDFromType(t testing.TB, method DIDMethod, blockchain Blockchai return did } + +func helperBuildDIDFromTypeOnchain(t testing.TB, method DIDMethod, blockchain Blockchain, network NetworkID) *DID { + t.Helper() + + typ, err := BuildDIDTypeOnChain(method, blockchain, network) + require.NoError(t, err) + + var addressBytes [20]byte + _, err = hex.Decode(addressBytes[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + require.NoError(t, err) + fmt.Printf("eth address: 0x%x\n", addressBytes) + + genesisState := GenesisFromEthAddress(addressBytes) + + did, err := DIDGenesisFromIdenState(typ, genesisState) + require.NoError(t, err) + + fmt.Printf("did: %s\n", did.String()) + fmt.Printf("did hex: %x\n", did.ID.Bytes()) + + return did +} diff --git a/id.go b/id.go index bc68910..fc1dac7 100644 --- a/id.go +++ b/id.go @@ -15,10 +15,6 @@ var ( // - first 2 bytes: `00000000 00000000` TypeDefault = [2]byte{0x00, 0x00} - // TypeReadOnly specifies the readonly identity, this type of identity MUST not be published on chain - // - first 2 bytes: `00000000 00000001` - TypeReadOnly = [2]byte{0b00000000, 0b00000001} - // TypeDID specifies the identity with iden3 method in specific networks // - first byte: did method e.g. 00000001 - iden3 did method // - second byte - blockchain network @@ -26,8 +22,14 @@ var ( // - 4-7 bits of 2nd byte: network id e.g. 0010 - mumbai // example of 2nd byte: 00010010 - polygon mumbai, 00000000 - readonly identities. // valid iden3 method {0b00000001,0b00010010}, readonly {0b00000001, 0b00000000} + + // TypeUnknown specifies that ID represents did of unsupported method + TypeUnknown = [2]byte{0xff, 0xff} ) +// MethodOnChainFlag is a flag showing that identity is on-chain +const MethodOnChainFlag = 0b10000000 + const idLength = 31 // ID is a byte array with @@ -39,11 +41,11 @@ type ID [idLength]byte // NewID creates a new ID from a type and genesis func NewID(typ [2]byte, genesis [27]byte) ID { checksum := CalculateChecksum(typ, genesis) - var b [31]byte + var b ID copy(b[:2], typ[:]) copy(b[2:], genesis[:]) copy(b[29:], checksum[:]) - return ID(b) + return b } // ProfileID calculates the Profile ID from the Identity and profile nonce. If nonce is empty or zero ID is returned @@ -122,6 +124,30 @@ func (id *ID) Type() [2]byte { return typ } +func (id *ID) MethodByte() byte { + // remove on-chain flag + return id[0] & ^byte(MethodOnChainFlag) +} + +func (id *ID) BlockchainNetworkByte() byte { + return id[1] +} + +func (id *ID) IsOnChain() bool { + return !id.IsUnknown() && (id[0]&MethodOnChainFlag == MethodOnChainFlag) +} + +func (id *ID) EthAddress() ([20]byte, error) { + if !id.IsOnChain() { + return [20]byte{}, errors.New("can't get EthAddress of not on-chain identity") + } + return EthAddressFromID(*id), nil +} + +func (id *ID) IsUnknown() bool { + return bytes.Equal(id[0:2], TypeUnknown[:]) +} + // IDFromString returns the ID from a given string func IDFromString(s string) (ID, error) { b, err := base58.Decode(s) @@ -163,10 +189,7 @@ func IDFromInt(i *big.Int) (ID, error) { } // DecomposeID returns type, genesis and checksum from an ID -func DecomposeID(id ID) ([2]byte, [27]byte, [2]byte, error) { - var typ [2]byte - var genesis [27]byte - var checksum [2]byte +func DecomposeID(id ID) (typ [2]byte, genesis [27]byte, checksum [2]byte, err error) { copy(typ[:], id[:2]) copy(genesis[:], id[2:len(id)-2]) copy(checksum[:], id[len(id)-2:]) @@ -240,3 +263,14 @@ func CheckGenesisStateID(id, state *big.Int) (bool, error) { return id.Cmp(identifier.BigInt()) == 0, nil } + +func EthAddressFromID(id ID) (address [20]byte) { + copy(address[:], id[2:22]) + return +} + +func GenesisFromEthAddress(address [20]byte) *big.Int { + var genesis [32]byte + copy(genesis[5:], address[:]) + return bytesToInt(genesis[:]) +} diff --git a/id_test.go b/id_test.go index 25490ad..b22f45c 100644 --- a/id_test.go +++ b/id_test.go @@ -78,7 +78,7 @@ func TestIDparsers(t *testing.T) { } func TestIDAsDID(t *testing.T) { - typ, err := BuildDIDType(DIDMethodIden3, Polygon, Mumbai) + typ, err := BuildDIDType(DIDMethodPolygonID, Polygon, Mumbai) require.NoError(t, err) var genesis1 [27]byte genesisbytes := hashBytes([]byte("genesistes1t2")) @@ -86,6 +86,7 @@ func TestIDAsDID(t *testing.T) { id := NewID(typ, genesis1) fmt.Println(id.String()) + fmt.Printf("%x\n", id.Bytes()) } func TestIDjsonParser(t *testing.T) { @@ -266,7 +267,7 @@ func TestID_Type(t *testing.T) { id, err := IDFromString("1MWtoAdZESeiphxp3bXupZcfS9DhMTdWNSjRwVYc2") assert.Nil(t, err) - assert.Equal(t, id.Type(), TypeReadOnly) + assert.Equal(t, id.Type(), [2]byte{0x00, 0x01}) } func TestCheckGenesisStateID(t *testing.T) { From 8874616dc972720251d3bdb27b797c34e720b7bb Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Fri, 21 Apr 2023 03:24:05 -0400 Subject: [PATCH 02/37] Create new type DID2 wich is a wrapper around github.com/build-trust/did compatible with old DID type --- did.go | 191 ++++++++++++++++++++++++++++++++++++++++++ did2_test.go | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++ did_test.go | 8 +- go.mod | 2 + go.sum | 4 + 5 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 did2_test.go diff --git a/did.go b/did.go index 2edce39..9978e04 100644 --- a/did.go +++ b/did.go @@ -7,6 +7,8 @@ import ( "fmt" "math/big" "strings" + + "github.com/build-trust/did" ) var ( @@ -382,3 +384,192 @@ func ParseDIDFromID(id ID) (*DID, error) { return &did, nil } + +type DID2 did.DID + +func NewDID2(method DIDMethod, blockchain Blockchain, networkID NetworkID, + id ID) DID2 { + return DID2{ + Method: string(method), + ID: fmt.Sprintf("%s:%s:%s", blockchain, networkID, id.String()), + IDStrings: []string{ + string(blockchain), string(networkID), id.String()}, + } +} + +func (did2 *DID2) UnmarshalJSON(bytes []byte) error { + var didStr string + err := json.Unmarshal(bytes, &didStr) + if err != nil { + return err + } + + return did2.SetString(didStr) +} + +func (did2 DID2) MarshalJSON() ([]byte, error) { + return json.Marshal(did2.String()) +} + +// DID2GenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID +func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID2, error) { + id, err := IdGenesisFromIdenState(typ, state) + if err != nil { + return nil, err + } + return ParseDID2FromID(*id) +} + +func (did2 *DID2) SetString(didStr string) error { + parsedDID, err := did.Parse(didStr) + if err != nil { + return err + } + *did2 = DID2(*parsedDID) + return did2.validate() +} + +// Return nil on success or error if fields are inconsistent. +func (did2 *DID2) validate() error { + blockchain, networkID, id, err := did2.decompose() + if err != nil { + return err + } + + if !CheckChecksum(id) { + return fmt.Errorf("%w: %s", ErrInvalidDID, "invalid checksum") + } + + d, err := ParseDIDFromID(id) + if err != nil { + return err + } + + if string(d.Method) != did2.Method { + return fmt.Errorf( + "%w: did method of core identity %s differs from given did method %s", + ErrInvalidDID, d.Method, did2.Method) + } + + if d.NetworkID != networkID { + return fmt.Errorf( + "%w: network method of core identity %s differs from given did network specific id %s", + ErrInvalidDID, d.NetworkID, networkID) + } + + if d.Blockchain != blockchain { + return fmt.Errorf( + "%w: blockchain network of core identity %s differs from given did blockhain network %s", + ErrInvalidDID, d.Blockchain, blockchain) + } + + if !bytes.Equal(d.ID[:], id[:]) { + return fmt.Errorf( + "%w: ID of core identity %s differs from given did ID %s", + ErrInvalidDID, d.ID.String(), id.String()) + } + + return nil +} + +func (did2 DID2) String() string { + return ((*did.DID)(&did2)).String() +} + +func (did2 DID2) decompose() (Blockchain, NetworkID, ID, error) { + var blockchain Blockchain + var networkID NetworkID + var idStr string + var id ID + switch len(did2.IDStrings) { + case 3: + blockchain = Blockchain(did2.IDStrings[0]) + networkID = NetworkID(did2.IDStrings[1]) + idStr = did2.IDStrings[2] + case 2: + blockchain = Blockchain(did2.IDStrings[0]) + switch blockchain { + case ReadOnly: + networkID = NoNetwork + case Polygon: + networkID = Main + case Ethereum: + networkID = Main + default: + return UnknownChain, UnknownNetwork, id, + ErrNetworkNotSupportedForDID + } + idStr = did2.IDStrings[1] + case 1: + blockchain = Polygon + networkID = Main + idStr = did2.IDStrings[0] + } + var err error + id, err = IDFromString(idStr) + if err != nil { + return UnknownChain, UnknownNetwork, id, + fmt.Errorf("%w: %v", ErrInvalidDID, err) + } + return blockchain, networkID, id, nil +} + +func (did2 DID2) CoreID() (ID, error) { + _, _, id, err := did2.decompose() + return id, err +} + +func (did2 DID2) NetworkID() (NetworkID, error) { + _, nID, _, err := did2.decompose() + return nID, err +} + +func (did2 DID2) Blockchain() (Blockchain, error) { + bc, _, _, err := did2.decompose() + return bc, err +} + +// ParseDID2FromID returns DID2 from ID +func ParseDID2FromID(id ID) (*DID2, error) { + method := id.MethodByte() + net := id.BlockchainNetworkByte() + + didMethod, err := FindDIDMethodByValue(method) + if err != nil { + return nil, err + } + + didBlockchain, err := FindBlockchainForDIDMethodByValue(didMethod, net) + if err != nil { + return nil, err + } + + didNetworkID, err := FindNetworkIDForDIDMethodByValue(didMethod, net) + if err != nil { + return nil, err + } + + didParts := []string{DIDSchema, string(didMethod), string(didBlockchain)} + if string(didNetworkID) != "" { + didParts = append(didParts, string(didNetworkID)) + } + + didParts = append(didParts, id.String()) + + didString := strings.Join(didParts, ":") + + var did2 DID2 + err = did2.SetString(didString) + if err != nil { + return nil, err + } + + return &did2, nil +} + +// ParseDID2 method parse string and extract DID2 if string is valid Iden3 identifier +func ParseDID2(didStr string) (*DID2, error) { + var did2 DID2 + err := did2.SetString(didStr) + return &did2, err +} diff --git a/did2_test.go b/did2_test.go new file mode 100644 index 0000000..766d914 --- /dev/null +++ b/did2_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/hex" + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseDID2(t *testing.T) { + + // did + didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" + + did, err := ParseDID2(didStr) + require.NoError(t, err) + + id, err := did.CoreID() + require.NoError(t, err) + require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) + blockchain, err := did.Blockchain() + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := did.NetworkID() + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) + + // readonly did + didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" + + did, err = ParseDID2(didStr) + require.NoError(t, err) + + id, err = did.CoreID() + require.NoError(t, err) + + require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) + blockchain, err = did.Blockchain() + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err = did.NetworkID() + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) + + require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) +} + +func TestDID2_MarshalJSON(t *testing.T) { + id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") + require.NoError(t, err) + did := NewDID2(DIDMethodIden3, Polygon, Mumbai, id) + + b, err := did.MarshalJSON() + require.NoError(t, err) + require.Equal(t, + `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, + string(b)) +} + +func TestDID2_UnmarshalJSON(t *testing.T) { + inBytes := `{"obj": "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` + id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") + require.NoError(t, err) + var obj struct { + Obj *DID2 `json:"obj"` + } + err = json.Unmarshal([]byte(inBytes), &obj) + require.NoError(t, err) + require.NotNil(t, obj.Obj) + id2, err := obj.Obj.CoreID() + require.NoError(t, err) + require.Equal(t, id, id2) + require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) + blockchain, err := obj.Obj.Blockchain() + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := obj.Obj.NetworkID() + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) +} + +func TestDID2_UnmarshalJSON_Error(t *testing.T) { + inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` + var obj struct { + Obj *DID2 `json:"obj"` + } + err := json.Unmarshal([]byte(inBytes), &obj) + require.EqualError(t, err, + "invalid did format: network method of core identity mumbai differs from given did network specific id goerli") +} + +func TestDID2GenesisFromState(t *testing.T) { + + typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) + require.NoError(t, err) + + genesisState := big.NewInt(1) + did, err := DID2GenesisFromIdenState(typ0, genesisState) + require.NoError(t, err) + + require.Equal(t, string(DIDMethodIden3), did.Method) + blockchain, err := did.Blockchain() + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err := did.NetworkID() + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) + require.Equal(t, + "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", + did.String()) +} + +func TestDID2_PolygonID_Types(t *testing.T) { + + // Polygon no chain, no network + did := helperBuildDID2FromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) + + require.Equal(t, string(DIDMethodPolygonID), did.Method) + blockchain, err := did.Blockchain() + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err := did.NetworkID() + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) + require.Equal(t, + "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", + did.String()) + + // Polygon | Polygon chain, Main + did2 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Main) + + require.Equal(t, string(DIDMethodPolygonID), did2.Method) + blockchain2, err := did2.Blockchain() + require.NoError(t, err) + require.Equal(t, Polygon, blockchain2) + networkID2, err := did2.NetworkID() + require.NoError(t, err) + require.Equal(t, Main, networkID2) + require.Equal(t, + "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", + did2.String()) + + // Polygon | Polygon chain, Mumbai + did3 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Mumbai) + + require.Equal(t, string(DIDMethodPolygonID), did3.Method) + blockchain3, err := did3.Blockchain() + require.NoError(t, err) + require.Equal(t, Polygon, blockchain3) + networkID3, err := did3.NetworkID() + require.NoError(t, err) + require.Equal(t, Mumbai, networkID3) + require.Equal(t, + "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", + did3.String()) + +} + +func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { + id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + require.NoError(t, err) + + did1, err := ParseDID2FromID(id1) + require.NoError(t, err) + + var addressBytesExp [20]byte + _, err = hex.Decode(addressBytesExp[:], + []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + require.NoError(t, err) + + require.Equal(t, string(DIDMethodPolygonID), did1.Method) + wantIDs := []string{"polygon", "mumbai", + "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} + require.Equal(t, wantIDs, did1.IDStrings) + id, err := did1.CoreID() + require.NoError(t, err) + bc, err := did1.Blockchain() + require.NoError(t, err) + require.Equal(t, Polygon, bc) + nID, err := did1.NetworkID() + require.NoError(t, err) + require.Equal(t, Polygon, bc) + require.Equal(t, Mumbai, nID) + require.Equal(t, true, id.IsOnChain()) + + addressBytes, err := id.EthAddress() + require.NoError(t, err) + require.Equal(t, addressBytesExp, addressBytes) + + require.Equal(t, + "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", + did1.String()) +} + +func TestDecompose(t *testing.T) { + s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" + + var d2 DID2 + err := d2.SetString(s) + require.NoError(t, err) + + wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + require.NoError(t, err) + + bch, nt, id, err := d2.decompose() + require.NoError(t, err) + require.Equal(t, Polygon, bch) + require.Equal(t, Mumbai, nt) + require.Equal(t, wantID, id) + + // TODO test other DID cases without network, blockchain and ID +} + +func helperBuildDID2FromType(t testing.TB, method DIDMethod, + blockchain Blockchain, network NetworkID) *DID2 { + t.Helper() + + typ, err := BuildDIDType(method, blockchain, network) + require.NoError(t, err) + + genesisState := big.NewInt(1) + did, err := DID2GenesisFromIdenState(typ, genesisState) + require.NoError(t, err) + + return did +} diff --git a/did_test.go b/did_test.go index 74a6ec8..70188e2 100644 --- a/did_test.go +++ b/did_test.go @@ -188,14 +188,18 @@ func helperBuildDIDFromType(t testing.TB, method DIDMethod, blockchain Blockchai return did } -func helperBuildDIDFromTypeOnchain(t testing.TB, method DIDMethod, blockchain Blockchain, network NetworkID) *DID { +func helperBuildDIDFromTypeOnchain(t testing.TB, + method DIDMethod, + blockchain Blockchain, + network NetworkID) *DID { t.Helper() typ, err := BuildDIDTypeOnChain(method, blockchain, network) require.NoError(t, err) var addressBytes [20]byte - _, err = hex.Decode(addressBytes[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + _, err = hex.Decode(addressBytes[:], + []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) fmt.Printf("eth address: 0x%x\n", addressBytes) diff --git a/go.mod b/go.mod index dd04066..8b5ee42 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/iden3/go-iden3-core go 1.18 require ( + github.com/build-trust/did v0.1.3 github.com/iden3/go-iden3-crypto v0.0.14 github.com/mr-tron/base58 v1.2.0 github.com/stretchr/testify v1.8.2 @@ -10,6 +11,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ockam-network/did v0.1.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 75df68c..d4bd4fa 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/build-trust/did v0.1.3 h1:FexuxTCwKHNBicj8X9CaDuPg+uYBrg5J1TZwSHQnFoM= +github.com/build-trust/did v0.1.3/go.mod h1:rGMmaVyPuUNuXPnpXEH9tOYpOL7t+2eh5DvtYGINBiU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -6,6 +8,8 @@ github.com/iden3/go-iden3-crypto v0.0.14/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBe github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/ockam-network/did v0.1.3 h1:qJGdccOV4bLfsS/eFM+Aj+CdCRJKNMxbmJevQclw44k= +github.com/ockam-network/did v0.1.3/go.mod h1:ZsbTIuVGt8OrQEbqWrSztUISN4joeMabdsinbLubbzw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 8ef98d9dffa28c670b81517ba7341d1c05f2a16d Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 02:45:44 -0400 Subject: [PATCH 03/37] Make Decompose method a separate function --- did.go | 82 ++++++++++++++++++++++++++++++++-------------------- did2_test.go | 6 ++-- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/did.go b/did.go index 9978e04..32f0dad 100644 --- a/did.go +++ b/did.go @@ -431,7 +431,7 @@ func (did2 *DID2) SetString(didStr string) error { // Return nil on success or error if fields are inconsistent. func (did2 *DID2) validate() error { - blockchain, networkID, id, err := did2.decompose() + blockchain, networkID, id, err := Decompose(*did2) if err != nil { return err } @@ -476,56 +476,74 @@ func (did2 DID2) String() string { return ((*did.DID)(&did2)).String() } -func (did2 DID2) decompose() (Blockchain, NetworkID, ID, error) { - var blockchain Blockchain - var networkID NetworkID - var idStr string +func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { var id ID - switch len(did2.IDStrings) { - case 3: - blockchain = Blockchain(did2.IDStrings[0]) - networkID = NetworkID(did2.IDStrings[1]) - idStr = did2.IDStrings[2] - case 2: - blockchain = Blockchain(did2.IDStrings[0]) - switch blockchain { - case ReadOnly: - networkID = NoNetwork - case Polygon: - networkID = Main - case Ethereum: - networkID = Main - default: - return UnknownChain, UnknownNetwork, id, - ErrNetworkNotSupportedForDID - } - idStr = did2.IDStrings[1] - case 1: - blockchain = Polygon - networkID = Main - idStr = did2.IDStrings[0] + + if len(did2.IDStrings) > 3 { + return UnknownChain, UnknownNetwork, id, + fmt.Errorf("%w: %s", ErrInvalidDID, "too many fields") + } + + idStr := did2.IDStrings[len(did2.IDStrings)-1] var err error id, err = IDFromString(idStr) if err != nil { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: %v", ErrInvalidDID, err) } - return blockchain, networkID, id, nil + + method := id.MethodByte() + net := id.BlockchainNetworkByte() + + didMethod, err := FindDIDMethodByValue(method) + if err != nil { + return UnknownChain, UnknownNetwork, id, err + } + + if string(didMethod) != did2.Method { + return UnknownChain, UnknownNetwork, id, + fmt.Errorf("%w: method mismatch: found %v in ID but %v in DID", + ErrInvalidDID, didMethod, did2.Method) + } + + blockchain, err := FindBlockchainForDIDMethodByValue(didMethod, net) + if err != nil { + return UnknownChain, UnknownNetwork, id, err + } + + if len(did2.IDStrings) > 1 && string(blockchain) != did2.IDStrings[0] { + return UnknownChain, UnknownNetwork, id, + fmt.Errorf("%w: blockchain mismatch: found %v in ID but %v in DID", + ErrInvalidDID, blockchain, did2.IDStrings[0]) + } + + didNetworkID, err := FindNetworkIDForDIDMethodByValue(didMethod, net) + if err != nil { + return UnknownChain, UnknownNetwork, id, err + } + + if len(did2.IDStrings) > 2 && string(didNetworkID) != did2.IDStrings[1] { + return UnknownChain, UnknownNetwork, id, + fmt.Errorf("%w: network ID mismatch: found %v in ID but %v in DID", + ErrInvalidDID, didNetworkID, did2.IDStrings[1]) + } + + return blockchain, didNetworkID, id, nil } func (did2 DID2) CoreID() (ID, error) { - _, _, id, err := did2.decompose() + _, _, id, err := Decompose(did2) return id, err } func (did2 DID2) NetworkID() (NetworkID, error) { - _, nID, _, err := did2.decompose() + _, nID, _, err := Decompose(did2) return nID, err } func (did2 DID2) Blockchain() (Blockchain, error) { - bc, _, _, err := did2.decompose() + bc, _, _, err := Decompose(did2) return bc, err } diff --git a/did2_test.go b/did2_test.go index 766d914..d443519 100644 --- a/did2_test.go +++ b/did2_test.go @@ -87,8 +87,8 @@ func TestDID2_UnmarshalJSON_Error(t *testing.T) { Obj *DID2 `json:"obj"` } err := json.Unmarshal([]byte(inBytes), &obj) - require.EqualError(t, err, - "invalid did format: network method of core identity mumbai differs from given did network specific id goerli") + require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ + "found polygon in ID but eth in DID") } func TestDID2GenesisFromState(t *testing.T) { @@ -204,7 +204,7 @@ func TestDecompose(t *testing.T) { wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - bch, nt, id, err := d2.decompose() + bch, nt, id, err := Decompose(d2) require.NoError(t, err) require.Equal(t, Polygon, bch) require.Equal(t, Mumbai, nt) From e44c464bb43645f5c5229bb43e0d6a78a2dc25e9 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 02:58:56 -0400 Subject: [PATCH 04/37] Removed CoreID method from DID and replace id with helper function CoreIDFromDID --- did.go | 36 ++++++++++++++++++++---------------- did2_test.go | 8 ++++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/did.go b/did.go index 32f0dad..b2e41cb 100644 --- a/did.go +++ b/did.go @@ -477,20 +477,9 @@ func (did2 DID2) String() string { } func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { - var id ID - - if len(did2.IDStrings) > 3 { - return UnknownChain, UnknownNetwork, id, - fmt.Errorf("%w: %s", ErrInvalidDID, "too many fields") - - } - - idStr := did2.IDStrings[len(did2.IDStrings)-1] - var err error - id, err = IDFromString(idStr) + id, err := CoreIDFromDID(did2) if err != nil { - return UnknownChain, UnknownNetwork, id, - fmt.Errorf("%w: %v", ErrInvalidDID, err) + return UnknownChain, UnknownNetwork, id, err } method := id.MethodByte() @@ -532,9 +521,24 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { return blockchain, didNetworkID, id, nil } -func (did2 DID2) CoreID() (ID, error) { - _, _, id, err := Decompose(did2) - return id, err +func CoreIDFromDID(did2 DID2) (ID, error) { + var id ID + + if len(did2.IDStrings) > 3 { + return id, fmt.Errorf("%w: %s", ErrInvalidDID, "too many fields") + } + + if len(did2.IDStrings) < 1 { + return id, fmt.Errorf("%w: no ID field in DID", ErrInvalidDID) + } + + var err error + id, err = IDFromString(did2.IDStrings[len(did2.IDStrings)-1]) + if err != nil { + return id, fmt.Errorf("%w: %v", ErrInvalidDID, err) + } + + return id, nil } func (did2 DID2) NetworkID() (NetworkID, error) { diff --git a/did2_test.go b/did2_test.go index d443519..e51c48d 100644 --- a/did2_test.go +++ b/did2_test.go @@ -17,7 +17,7 @@ func TestParseDID2(t *testing.T) { did, err := ParseDID2(didStr) require.NoError(t, err) - id, err := did.CoreID() + id, err := CoreIDFromDID(*did) require.NoError(t, err) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) blockchain, err := did.Blockchain() @@ -33,7 +33,7 @@ func TestParseDID2(t *testing.T) { did, err = ParseDID2(didStr) require.NoError(t, err) - id, err = did.CoreID() + id, err = CoreIDFromDID(*did) require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) @@ -69,7 +69,7 @@ func TestDID2_UnmarshalJSON(t *testing.T) { err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) require.NotNil(t, obj.Obj) - id2, err := obj.Obj.CoreID() + id2, err := CoreIDFromDID(*obj.Obj) require.NoError(t, err) require.Equal(t, id, id2) require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) @@ -174,7 +174,7 @@ func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) - id, err := did1.CoreID() + id, err := CoreIDFromDID(*did1) require.NoError(t, err) bc, err := did1.Blockchain() require.NoError(t, err) From 4a75c8f1d6109f49f4d4e68dc3536dd805804ea6 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 03:12:54 -0400 Subject: [PATCH 05/37] Get rid of ParseDID2 function --- did.go | 17 +++++++---------- did2_test.go | 9 +++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/did.go b/did.go index b2e41cb..9dfa33d 100644 --- a/did.go +++ b/did.go @@ -426,11 +426,11 @@ func (did2 *DID2) SetString(didStr string) error { return err } *did2 = DID2(*parsedDID) - return did2.validate() + return did2.Validate() } // Return nil on success or error if fields are inconsistent. -func (did2 *DID2) validate() error { +func (did2 *DID2) Validate() error { blockchain, networkID, id, err := Decompose(*did2) if err != nil { return err @@ -525,7 +525,7 @@ func CoreIDFromDID(did2 DID2) (ID, error) { var id ID if len(did2.IDStrings) > 3 { - return id, fmt.Errorf("%w: %s", ErrInvalidDID, "too many fields") + return id, fmt.Errorf("%w: too many fields", ErrInvalidDID) } if len(did2.IDStrings) < 1 { @@ -538,6 +538,10 @@ func CoreIDFromDID(did2 DID2) (ID, error) { return id, fmt.Errorf("%w: %v", ErrInvalidDID, err) } + if !CheckChecksum(id) { + return id, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) + } + return id, nil } @@ -588,10 +592,3 @@ func ParseDID2FromID(id ID) (*DID2, error) { return &did2, nil } - -// ParseDID2 method parse string and extract DID2 if string is valid Iden3 identifier -func ParseDID2(didStr string) (*DID2, error) { - var did2 DID2 - err := did2.SetString(didStr) - return &did2, err -} diff --git a/did2_test.go b/did2_test.go index e51c48d..63dd87c 100644 --- a/did2_test.go +++ b/did2_test.go @@ -6,6 +6,7 @@ import ( "math/big" "testing" + did2 "github.com/build-trust/did" "github.com/stretchr/testify/require" ) @@ -14,7 +15,10 @@ func TestParseDID2(t *testing.T) { // did didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - did, err := ParseDID2(didStr) + did3, err := did2.Parse(didStr) + require.NoError(t, err) + + did := (*DID2)(did3) require.NoError(t, err) id, err := CoreIDFromDID(*did) @@ -30,8 +34,9 @@ func TestParseDID2(t *testing.T) { // readonly did didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - did, err = ParseDID2(didStr) + did3, err = did2.Parse(didStr) require.NoError(t, err) + did = (*DID2)(did3) id, err = CoreIDFromDID(*did) require.NoError(t, err) From bdb5bd4e8dd204a5f7369f35f00c3ba1defd8158 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 03:29:05 -0400 Subject: [PATCH 06/37] Remove SetString method for DID2 --- did.go | 72 ++++++++++------------------------------------------ did2_test.go | 9 ++++--- 2 files changed, 20 insertions(+), 61 deletions(-) diff --git a/did.go b/did.go index 9dfa33d..4a96885 100644 --- a/did.go +++ b/did.go @@ -404,7 +404,12 @@ func (did2 *DID2) UnmarshalJSON(bytes []byte) error { return err } - return did2.SetString(didStr) + did3, err := did.Parse(didStr) + if err != nil { + return err + } + *did2 = DID2(*did3) + return nil } func (did2 DID2) MarshalJSON() ([]byte, error) { @@ -420,64 +425,12 @@ func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID2, error) { return ParseDID2FromID(*id) } -func (did2 *DID2) SetString(didStr string) error { - parsedDID, err := did.Parse(didStr) - if err != nil { - return err - } - *did2 = DID2(*parsedDID) - return did2.Validate() -} - -// Return nil on success or error if fields are inconsistent. -func (did2 *DID2) Validate() error { - blockchain, networkID, id, err := Decompose(*did2) - if err != nil { - return err - } - - if !CheckChecksum(id) { - return fmt.Errorf("%w: %s", ErrInvalidDID, "invalid checksum") - } - - d, err := ParseDIDFromID(id) - if err != nil { - return err - } - - if string(d.Method) != did2.Method { - return fmt.Errorf( - "%w: did method of core identity %s differs from given did method %s", - ErrInvalidDID, d.Method, did2.Method) - } - - if d.NetworkID != networkID { - return fmt.Errorf( - "%w: network method of core identity %s differs from given did network specific id %s", - ErrInvalidDID, d.NetworkID, networkID) - } - - if d.Blockchain != blockchain { - return fmt.Errorf( - "%w: blockchain network of core identity %s differs from given did blockhain network %s", - ErrInvalidDID, d.Blockchain, blockchain) - } - - if !bytes.Equal(d.ID[:], id[:]) { - return fmt.Errorf( - "%w: ID of core identity %s differs from given did ID %s", - ErrInvalidDID, d.ID.String(), id.String()) - } - - return nil -} - func (did2 DID2) String() string { return ((*did.DID)(&did2)).String() } func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { - id, err := CoreIDFromDID(did2) + id, err := decodeIDFromDID(did2) if err != nil { return UnknownChain, UnknownNetwork, id, err } @@ -522,6 +475,11 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { } func CoreIDFromDID(did2 DID2) (ID, error) { + _, _, id, err := Decompose(did2) + return id, err +} + +func decodeIDFromDID(did2 DID2) (ID, error) { var id ID if len(did2.IDStrings) > 3 { @@ -584,11 +542,9 @@ func ParseDID2FromID(id ID) (*DID2, error) { didString := strings.Join(didParts, ":") - var did2 DID2 - err = did2.SetString(didString) + did2, err := did.Parse(didString) if err != nil { return nil, err } - - return &did2, nil + return (*DID2)(did2), nil } diff --git a/did2_test.go b/did2_test.go index 63dd87c..d9ea800 100644 --- a/did2_test.go +++ b/did2_test.go @@ -92,6 +92,9 @@ func TestDID2_UnmarshalJSON_Error(t *testing.T) { Obj *DID2 `json:"obj"` } err := json.Unmarshal([]byte(inBytes), &obj) + require.NoError(t, err) + + _, err = CoreIDFromDID(*obj.Obj) require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ "found polygon in ID but eth in DID") } @@ -202,14 +205,14 @@ func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { func TestDecompose(t *testing.T) { s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" - var d2 DID2 - err := d2.SetString(s) + did3, err := did2.Parse(s) require.NoError(t, err) + d2 := (*DID2)(did3) wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - bch, nt, id, err := Decompose(d2) + bch, nt, id, err := Decompose(*d2) require.NoError(t, err) require.Equal(t, Polygon, bch) require.Equal(t, Mumbai, nt) From d6b11aabdb67e5397f574f46ce2062d41e7bcc03 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 03:36:46 -0400 Subject: [PATCH 07/37] Remove NetworkID & Blockchain DID2 methods. Use Decompose method instead. --- did.go | 10 ---------- did2_test.go | 43 +++++++++---------------------------------- 2 files changed, 9 insertions(+), 44 deletions(-) diff --git a/did.go b/did.go index 4a96885..79d019b 100644 --- a/did.go +++ b/did.go @@ -503,16 +503,6 @@ func decodeIDFromDID(did2 DID2) (ID, error) { return id, nil } -func (did2 DID2) NetworkID() (NetworkID, error) { - _, nID, _, err := Decompose(did2) - return nID, err -} - -func (did2 DID2) Blockchain() (Blockchain, error) { - bc, _, _, err := Decompose(did2) - return bc, err -} - // ParseDID2FromID returns DID2 from ID func ParseDID2FromID(id ID) (*DID2, error) { method := id.MethodByte() diff --git a/did2_test.go b/did2_test.go index d9ea800..ef93bc6 100644 --- a/did2_test.go +++ b/did2_test.go @@ -21,14 +21,10 @@ func TestParseDID2(t *testing.T) { did := (*DID2)(did3) require.NoError(t, err) - id, err := CoreIDFromDID(*did) + blockchain, networkID, id, err := Decompose(*did) require.NoError(t, err) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) - blockchain, err := did.Blockchain() - require.NoError(t, err) require.Equal(t, Polygon, blockchain) - networkID, err := did.NetworkID() - require.NoError(t, err) require.Equal(t, Mumbai, networkID) // readonly did @@ -38,15 +34,11 @@ func TestParseDID2(t *testing.T) { require.NoError(t, err) did = (*DID2)(did3) - id, err = CoreIDFromDID(*did) + blockchain, networkID, id, err = Decompose(*did) require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) - blockchain, err = did.Blockchain() - require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) - networkID, err = did.NetworkID() - require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) @@ -74,15 +66,11 @@ func TestDID2_UnmarshalJSON(t *testing.T) { err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) require.NotNil(t, obj.Obj) - id2, err := CoreIDFromDID(*obj.Obj) - require.NoError(t, err) - require.Equal(t, id, id2) require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) - blockchain, err := obj.Obj.Blockchain() + blockchain, networkID, id2, err := Decompose(*obj.Obj) require.NoError(t, err) + require.Equal(t, id, id2) require.Equal(t, Polygon, blockchain) - networkID, err := obj.Obj.NetworkID() - require.NoError(t, err) require.Equal(t, Mumbai, networkID) } @@ -109,11 +97,9 @@ func TestDID2GenesisFromState(t *testing.T) { require.NoError(t, err) require.Equal(t, string(DIDMethodIden3), did.Method) - blockchain, err := did.Blockchain() + blockchain, networkID, _, err := Decompose(*did) require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) - networkID, err := did.NetworkID() - require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", @@ -126,11 +112,9 @@ func TestDID2_PolygonID_Types(t *testing.T) { did := helperBuildDID2FromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) require.Equal(t, string(DIDMethodPolygonID), did.Method) - blockchain, err := did.Blockchain() + blockchain, networkID, _, err := Decompose(*did) require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) - networkID, err := did.NetworkID() - require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", @@ -140,11 +124,9 @@ func TestDID2_PolygonID_Types(t *testing.T) { did2 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Main) require.Equal(t, string(DIDMethodPolygonID), did2.Method) - blockchain2, err := did2.Blockchain() + blockchain2, networkID2, _, err := Decompose(*did2) require.NoError(t, err) require.Equal(t, Polygon, blockchain2) - networkID2, err := did2.NetworkID() - require.NoError(t, err) require.Equal(t, Main, networkID2) require.Equal(t, "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", @@ -154,11 +136,9 @@ func TestDID2_PolygonID_Types(t *testing.T) { did3 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Mumbai) require.Equal(t, string(DIDMethodPolygonID), did3.Method) - blockchain3, err := did3.Blockchain() + blockchain3, networkID3, _, err := Decompose(*did3) require.NoError(t, err) require.Equal(t, Polygon, blockchain3) - networkID3, err := did3.NetworkID() - require.NoError(t, err) require.Equal(t, Mumbai, networkID3) require.Equal(t, "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", @@ -182,12 +162,7 @@ func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) - id, err := CoreIDFromDID(*did1) - require.NoError(t, err) - bc, err := did1.Blockchain() - require.NoError(t, err) - require.Equal(t, Polygon, bc) - nID, err := did1.NetworkID() + bc, nID, id, err := Decompose(*did1) require.NoError(t, err) require.Equal(t, Polygon, bc) require.Equal(t, Mumbai, nID) From d674dd3d4c7959870b91624883c9265911a26240 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 03:47:03 -0400 Subject: [PATCH 08/37] Refactor similar code --- did.go | 73 +++++++++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/did.go b/did.go index 79d019b..4018b9d 100644 --- a/did.go +++ b/did.go @@ -435,23 +435,12 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { return UnknownChain, UnknownNetwork, id, err } - method := id.MethodByte() - net := id.BlockchainNetworkByte() - - didMethod, err := FindDIDMethodByValue(method) - if err != nil { - return UnknownChain, UnknownNetwork, id, err - } + method, blockchain, networkID, err := decodeDIDPartsFromID(id) - if string(didMethod) != did2.Method { + if string(method) != did2.Method { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: method mismatch: found %v in ID but %v in DID", - ErrInvalidDID, didMethod, did2.Method) - } - - blockchain, err := FindBlockchainForDIDMethodByValue(didMethod, net) - if err != nil { - return UnknownChain, UnknownNetwork, id, err + ErrInvalidDID, method, did2.Method) } if len(did2.IDStrings) > 1 && string(blockchain) != did2.IDStrings[0] { @@ -460,18 +449,13 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { ErrInvalidDID, blockchain, did2.IDStrings[0]) } - didNetworkID, err := FindNetworkIDForDIDMethodByValue(didMethod, net) - if err != nil { - return UnknownChain, UnknownNetwork, id, err - } - - if len(did2.IDStrings) > 2 && string(didNetworkID) != did2.IDStrings[1] { + if len(did2.IDStrings) > 2 && string(networkID) != did2.IDStrings[1] { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: network ID mismatch: found %v in ID but %v in DID", - ErrInvalidDID, didNetworkID, did2.IDStrings[1]) + ErrInvalidDID, networkID, did2.IDStrings[1]) } - return blockchain, didNetworkID, id, nil + return blockchain, networkID, id, nil } func CoreIDFromDID(did2 DID2) (ID, error) { @@ -505,27 +489,16 @@ func decodeIDFromDID(did2 DID2) (ID, error) { // ParseDID2FromID returns DID2 from ID func ParseDID2FromID(id ID) (*DID2, error) { - method := id.MethodByte() - net := id.BlockchainNetworkByte() - - didMethod, err := FindDIDMethodByValue(method) - if err != nil { - return nil, err - } - didBlockchain, err := FindBlockchainForDIDMethodByValue(didMethod, net) - if err != nil { - return nil, err + if !CheckChecksum(id) { + return nil, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) } - didNetworkID, err := FindNetworkIDForDIDMethodByValue(didMethod, net) - if err != nil { - return nil, err - } + method, blockchain, networkID, err := decodeDIDPartsFromID(id) - didParts := []string{DIDSchema, string(didMethod), string(didBlockchain)} - if string(didNetworkID) != "" { - didParts = append(didParts, string(didNetworkID)) + didParts := []string{DIDSchema, string(method), string(blockchain)} + if string(networkID) != "" { + didParts = append(didParts, string(networkID)) } didParts = append(didParts, id.String()) @@ -538,3 +511,25 @@ func ParseDID2FromID(id ID) (*DID2, error) { } return (*DID2)(did2), nil } + +func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { + methodByte := id.MethodByte() + networkByte := id.BlockchainNetworkByte() + + method, err := FindDIDMethodByValue(methodByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err + } + + blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err + } + + networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err + } + + return method, blockchain, networkID, nil +} From 991dbedb59dcca4efa69f5fa17b453d6acb4ba51 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:04:20 -0400 Subject: [PATCH 09/37] Remove NewDID2 function --- did.go | 10 ---------- did2_test.go | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/did.go b/did.go index 4018b9d..6cbd794 100644 --- a/did.go +++ b/did.go @@ -387,16 +387,6 @@ func ParseDIDFromID(id ID) (*DID, error) { type DID2 did.DID -func NewDID2(method DIDMethod, blockchain Blockchain, networkID NetworkID, - id ID) DID2 { - return DID2{ - Method: string(method), - ID: fmt.Sprintf("%s:%s:%s", blockchain, networkID, id.String()), - IDStrings: []string{ - string(blockchain), string(networkID), id.String()}, - } -} - func (did2 *DID2) UnmarshalJSON(bytes []byte) error { var didStr string err := json.Unmarshal(bytes, &didStr) diff --git a/did2_test.go b/did2_test.go index ef93bc6..25cecff 100644 --- a/did2_test.go +++ b/did2_test.go @@ -47,7 +47,8 @@ func TestParseDID2(t *testing.T) { func TestDID2_MarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) - did := NewDID2(DIDMethodIden3, Polygon, Mumbai, id) + did, err := ParseDID2FromID(id) + require.NoError(t, err) b, err := did.MarshalJSON() require.NoError(t, err) From c182c85b012cee0d29658d5eb96fcb8499b54f8b Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:09:54 -0400 Subject: [PATCH 10/37] Add missing error handling --- did.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/did.go b/did.go index 6cbd794..726b454 100644 --- a/did.go +++ b/did.go @@ -485,6 +485,9 @@ func ParseDID2FromID(id ID) (*DID2, error) { } method, blockchain, networkID, err := decodeDIDPartsFromID(id) + if err != nil { + return nil, err + } didParts := []string{DIDSchema, string(method), string(blockchain)} if string(networkID) != "" { From e5cdaaed90c2ee26aee32d038db687be0ad2eacf Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:10:42 -0400 Subject: [PATCH 11/37] Add missing error handling --- did.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/did.go b/did.go index 726b454..babf3bc 100644 --- a/did.go +++ b/did.go @@ -426,6 +426,9 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { } method, blockchain, networkID, err := decodeDIDPartsFromID(id) + if err != nil { + return UnknownChain, UnknownNetwork, id, err + } if string(method) != did2.Method { return UnknownChain, UnknownNetwork, id, From 4cab3e212bf7eeeaf8b293292585ac1ea3f4ae4b Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:11:28 -0400 Subject: [PATCH 12/37] Make linters happy --- did.go | 2 +- did2_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/did.go b/did.go index babf3bc..7fbc55f 100644 --- a/did.go +++ b/did.go @@ -451,7 +451,7 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { return blockchain, networkID, id, nil } -func CoreIDFromDID(did2 DID2) (ID, error) { +func IDFromDID(did2 DID2) (ID, error) { _, _, id, err := Decompose(did2) return id, err } diff --git a/did2_test.go b/did2_test.go index 25cecff..77d2960 100644 --- a/did2_test.go +++ b/did2_test.go @@ -83,7 +83,7 @@ func TestDID2_UnmarshalJSON_Error(t *testing.T) { err := json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) - _, err = CoreIDFromDID(*obj.Obj) + _, err = IDFromDID(*obj.Obj) require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ "found polygon in ID but eth in DID") } From 008bab700160cd4834b6b4f26b65023dcae4ecff Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:43:04 -0400 Subject: [PATCH 13/37] Remove custom DID type --- did.go | 213 --------------------------------------------------- did_test.go | 215 ---------------------------------------------------- id_test.go | 45 ----------- 3 files changed, 473 deletions(-) delete mode 100644 did_test.go diff --git a/did.go b/did.go index 7fbc55f..9c0e83b 100644 --- a/did.go +++ b/did.go @@ -1,7 +1,6 @@ package core import ( - "bytes" "encoding/json" "errors" "fmt" @@ -173,218 +172,6 @@ func FindDIDMethodByValue(_v byte) (DIDMethod, error) { return "", ErrDIDMethodNotSupported } -// DID Decentralized Identifiers (DIDs) -// https://w3c.github.io/did-core/#did-syntax -type DID struct { - ID ID // ID did specific id - Method DIDMethod // DIDMethod did method - Blockchain Blockchain // Blockchain network identifier eth / polygon,... - NetworkID NetworkID // NetworkID specific network identifier eth {main, mumbai, goerli} - isUnknownMethod bool // isUnknownMethod specifies if DID is of unsupported method - didString string // didString full DID string identifier -} - -func (did *DID) SetString(didStr string) error { - // Parse method, networks and id - arg := strings.Split(didStr, ":") - if len(arg) < 3 { - return ErrInvalidDID - } - - if arg[0] != DIDSchema { - return ErrInvalidDID - } - - did.didString = didStr - did.Method = DIDMethod(arg[1]) - - // check did method defined in core lib - _, ok := DIDMethodByte[did.Method] - if !ok { - did.isUnknownMethod = true - - // TODO: algo how to encode unknown did to ID - // keccac256 and take 27 bytes from it - - return nil - } - - switch len(arg) { - case 5: - var err error - - // we have both blockchain and network id specified - did.Blockchain = Blockchain(arg[2]) - did.NetworkID = NetworkID(arg[3]) - - // parse id - did.ID, err = IDFromString(arg[4]) - if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidDID, err) - } - - case 4: - var err error - - // we have only blockchain specified - did.Blockchain = Blockchain(arg[2]) - - // set default network id - switch did.Blockchain { - case ReadOnly: - did.NetworkID = NoNetwork - case Polygon: - did.NetworkID = Main - case Ethereum: - did.NetworkID = Main - default: - return ErrNetworkNotSupportedForDID - } - - did.ID, err = IDFromString(arg[3]) - if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidDID, err) - } - - case 3: - var err error - - // we do not have blockchain & network id, set default ones - did.Blockchain = Polygon - did.NetworkID = Main - - did.ID, err = IDFromString(arg[2]) - if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidDID, err) - } - default: - return ErrInvalidDID - } - - // check did network defined in core lib for did method - _, ok = DIDMethodNetwork[did.Method][DIDNetworkFlag{ - Blockchain: did.Blockchain, - NetworkID: did.NetworkID}] - if !ok { - return ErrNetworkNotSupportedForDID - } - - if !CheckChecksum(did.ID) { - return fmt.Errorf("%w: %s", ErrInvalidDID, "invalid checksum") - } - - // check id contains did network and method - return did.validate() -} - -// Return nil on success or error if fields are inconsistent. -func (did *DID) validate() error { - d, err := ParseDIDFromID(did.ID) - if err != nil { - return err - } - - if d.Method != did.Method { - return fmt.Errorf( - "%w: did method of core identity %s differs from given did method %s", - ErrInvalidDID, d.Method, did.Method) - } - - if d.NetworkID != did.NetworkID { - return fmt.Errorf( - "%w: network method of core identity %s differs from given did network specific id %s", - ErrInvalidDID, d.NetworkID, did.NetworkID) - } - - if d.Blockchain != did.Blockchain { - return fmt.Errorf( - "%w: blockchain network of core identity %s differs from given did blockhain network %s", - ErrInvalidDID, d.Blockchain, did.Blockchain) - } - - if !bytes.Equal(d.ID[:], did.ID[:]) { - return fmt.Errorf( - "%w: ID of core identity %s differs from given did ID %s", - ErrInvalidDID, d.ID.String(), did.ID.String()) - } - - return nil -} - -func (did *DID) UnmarshalJSON(bytes []byte) error { - var didStr string - err := json.Unmarshal(bytes, &didStr) - if err != nil { - return err - } - - return did.SetString(didStr) -} - -func (did *DID) MarshalJSON() ([]byte, error) { - return json.Marshal(did.String()) -} - -// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { - id, err := IdGenesisFromIdenState(typ, state) - if err != nil { - return nil, err - } - return ParseDIDFromID(*id) -} - -// String did as a string -func (did *DID) String() string { - if did.isUnknownMethod { - return did.didString - } - - if did.Blockchain == NoChain || did.Blockchain == ReadOnly { - return fmt.Sprintf("%s:%s:%s:%s", DIDSchema, did.Method, ReadOnly, did.ID.String()) - } - - return fmt.Sprintf("%s:%s:%s:%s:%s", DIDSchema, did.Method, did.Blockchain, - did.NetworkID, did.ID.String()) -} - -func (did *DID) CoreID() ID { - return did.ID -} - -// ParseDID method parse string and extract DID if string is valid Iden3 identifier -func ParseDID(didStr string) (*DID, error) { - var did DID - err := did.SetString(didStr) - return &did, err -} - -// ParseDIDFromID returns did from ID -func ParseDIDFromID(id ID) (*DID, error) { - var err error - did := DID{} - did.ID = id - method := id.MethodByte() - net := id.BlockchainNetworkByte() - - did.Method, err = FindDIDMethodByValue(method) - if err != nil { - return nil, err - } - - did.Blockchain, err = FindBlockchainForDIDMethodByValue(did.Method, net) - if err != nil { - return nil, err - } - - did.NetworkID, err = FindNetworkIDForDIDMethodByValue(did.Method, net) - if err != nil { - return nil, err - } - - return &did, nil -} - type DID2 did.DID func (did2 *DID2) UnmarshalJSON(bytes []byte) error { diff --git a/did_test.go b/did_test.go deleted file mode 100644 index 70188e2..0000000 --- a/did_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package core - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "math/big" - "testing" - - "github.com/iden3/go-iden3-crypto/utils" - "github.com/stretchr/testify/require" -) - -func TestParseDID(t *testing.T) { - - // did - didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - - did, err := ParseDID(didStr) - require.NoError(t, err) - - require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", - did.ID.String()) - require.Equal(t, Polygon, did.Blockchain) - require.Equal(t, Mumbai, did.NetworkID) - - // readonly did - didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - - did, err = ParseDID(didStr) - require.NoError(t, err) - - require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", - did.ID.String()) - require.Equal(t, ReadOnly, did.Blockchain) - require.Equal(t, NoNetwork, did.NetworkID) - - require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, did.ID.Type()) -} - -func TestDID_MarshalJSON(t *testing.T) { - id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") - require.NoError(t, err) - did := DID{ - ID: id, - Method: DIDMethodIden3, - Blockchain: Polygon, - NetworkID: Mumbai, - } - - b, err := did.MarshalJSON() - require.NoError(t, err) - require.Equal(t, - `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, - string(b)) -} - -func TestDID_UnmarshalJSON(t *testing.T) { - inBytes := `{"obj": "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` - id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") - require.NoError(t, err) - var obj struct { - Obj *DID `json:"obj"` - } - err = json.Unmarshal([]byte(inBytes), &obj) - require.NoError(t, err) - require.NotNil(t, obj.Obj) - require.Equal(t, id, obj.Obj.ID) - require.Equal(t, DIDMethodIden3, obj.Obj.Method) - require.Equal(t, Polygon, obj.Obj.Blockchain) - require.Equal(t, Mumbai, obj.Obj.NetworkID) -} - -func TestDID_UnmarshalJSON_Error(t *testing.T) { - inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` - var obj struct { - Obj *DID `json:"obj"` - } - err := json.Unmarshal([]byte(inBytes), &obj) - require.EqualError(t, err, - "invalid did format: network method of core identity mumbai differs from given did network specific id goerli") -} - -func TestDIDGenesisFromState(t *testing.T) { - - typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) - require.NoError(t, err) - - genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ0, genesisState) - require.NoError(t, err) - - require.Equal(t, DIDMethodIden3, did.Method) - require.Equal(t, ReadOnly, did.Blockchain) - require.Equal(t, NoNetwork, did.NetworkID) - require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did.String()) -} - -func TestDID_PolygonID_Types(t *testing.T) { - - // Polygon no chain, no network - did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - - require.Equal(t, DIDMethodPolygonID, did.Method) - require.Equal(t, ReadOnly, did.Blockchain) - require.Equal(t, NoNetwork, did.NetworkID) - require.Equal(t, "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", did.String()) - - // Polygon | Polygon chain, Main - did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) - - require.Equal(t, DIDMethodPolygonID, did2.Method) - require.Equal(t, Polygon, did2.Blockchain) - require.Equal(t, Main, did2.NetworkID) - require.Equal(t, "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", did2.String()) - - // Polygon | Polygon chain, Mumbai - did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) - - require.Equal(t, DIDMethodPolygonID, did3.Method) - require.Equal(t, Polygon, did3.Blockchain) - require.Equal(t, Mumbai, did3.NetworkID) - require.Equal(t, "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", did3.String()) - -} - -func TestDID_PolygonID_Types_OnChain(t *testing.T) { - // Polygon | Polygon chain, Mumbai - did1 := helperBuildDIDFromTypeOnchain(t, DIDMethodPolygonID, Polygon, Mumbai) - - idInt, _ := big.NewInt(0).SetString("20318741244951419790279970260471061329920784847526059589266894371380597378", 10) - idBytes := utils.SwapEndianness(idInt.Bytes()) - fmt.Printf("\ndid hex from solidity: %x\n\n", idBytes) - - idExp, err := IDFromInt(idInt) - require.NoError(t, err) - require.Equal(t, "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", idExp.String()) - - var addressBytesExp [20]byte - _, err = hex.Decode(addressBytesExp[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) - require.NoError(t, err) - - require.Equal(t, DIDMethodPolygonID, did1.Method) - require.Equal(t, Polygon, did1.Blockchain) - require.Equal(t, Mumbai, did1.NetworkID) - require.Equal(t, true, did1.ID.IsOnChain()) - - addressBytes, err := did1.ID.EthAddress() - require.NoError(t, err) - require.Equal(t, addressBytesExp, addressBytes) - - require.Equal(t, "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", did1.String()) -} - -func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { - id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") - require.NoError(t, err) - - did1, err := ParseDIDFromID(id1) - require.NoError(t, err) - - var addressBytesExp [20]byte - _, err = hex.Decode(addressBytesExp[:], []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) - require.NoError(t, err) - - require.Equal(t, DIDMethodPolygonID, did1.Method) - require.Equal(t, Polygon, did1.Blockchain) - require.Equal(t, Mumbai, did1.NetworkID) - require.Equal(t, true, did1.ID.IsOnChain()) - - addressBytes, err := did1.ID.EthAddress() - require.NoError(t, err) - require.Equal(t, addressBytesExp, addressBytes) - - require.Equal(t, "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", did1.String()) -} - -func helperBuildDIDFromType(t testing.TB, method DIDMethod, blockchain Blockchain, network NetworkID) *DID { - t.Helper() - - typ, err := BuildDIDType(method, blockchain, network) - require.NoError(t, err) - - genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ, genesisState) - require.NoError(t, err) - - return did -} - -func helperBuildDIDFromTypeOnchain(t testing.TB, - method DIDMethod, - blockchain Blockchain, - network NetworkID) *DID { - t.Helper() - - typ, err := BuildDIDTypeOnChain(method, blockchain, network) - require.NoError(t, err) - - var addressBytes [20]byte - _, err = hex.Decode(addressBytes[:], - []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) - require.NoError(t, err) - fmt.Printf("eth address: 0x%x\n", addressBytes) - - genesisState := GenesisFromEthAddress(addressBytes) - - did, err := DIDGenesisFromIdenState(typ, genesisState) - require.NoError(t, err) - - fmt.Printf("did: %s\n", did.String()) - fmt.Printf("did hex: %x\n", did.ID.Bytes()) - - return did -} diff --git a/id_test.go b/id_test.go index b22f45c..b941052 100644 --- a/id_test.go +++ b/id_test.go @@ -234,33 +234,6 @@ func TestIDinDIDFormat(t *testing.T) { var checksum [2]byte copy(checksum[:], id[len(id)-2:]) assert.Equal(t, CalculateChecksum(typ, genesis), checksum) - - fmt.Println(id.String()) - did := DID{ - ID: id, - Blockchain: Polygon, - NetworkID: Mumbai, - } - fmt.Println(did.String()) -} -func TestIDFromDIDString(t *testing.T) { - - didFromStr, err := ParseDID("did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") - require.NoError(t, err) - typ, err := BuildDIDType(didFromStr.Method, didFromStr.Blockchain, didFromStr.NetworkID) - require.NoError(t, err) - - var genesis [27]byte - genesis32bytes := hashBytes([]byte("genesistest")) - copy(genesis[:], genesis32bytes[:]) - - id := NewID(typ, genesis) - - var checksum [2]byte - copy(checksum[:], id[len(id)-2:]) - assert.Equal(t, CalculateChecksum(typ, genesis), checksum) - assert.Equal(t, didFromStr.ID.String(), id.String()) - } func TestID_Type(t *testing.T) { @@ -269,21 +242,3 @@ func TestID_Type(t *testing.T) { assert.Equal(t, id.Type(), [2]byte{0x00, 0x01}) } - -func TestCheckGenesisStateID(t *testing.T) { - userDID, err := ParseDID("did:iden3:polygon:mumbai:x6suHR8HkEYczV9yVeAKKiXCZAd25P8WS6QvNhszk") - require.NoError(t, err) - genesisID, ok := big.NewInt(0).SetString("7521024223205616003431860562270429547098131848980857190502964780628723574810", 10) - require.True(t, ok) - - isGenesis, err := CheckGenesisStateID(userDID.ID.BigInt(), genesisID) - require.NoError(t, err) - require.True(t, isGenesis) - - notGenesisState, ok := big.NewInt(0).SetString("6017654403209798611575982337826892532952335378376369712724079246845524041042", 10) - require.True(t, ok) - - isGenesis, err = CheckGenesisStateID(userDID.ID.BigInt(), notGenesisState) - require.NoError(t, err) - require.False(t, isGenesis) -} From 829f06b0a533a02a11b7e9c55885c5a7c888ef50 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:44:01 -0400 Subject: [PATCH 14/37] Rename did.go to did2.go: prepare to include third-party DID implementation --- did.go => did2.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename did.go => did2.go (100%) diff --git a/did.go b/did2.go similarity index 100% rename from did.go rename to did2.go From fa7adeb191c5138361088cdcad8cc90637064ee3 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:53:38 -0400 Subject: [PATCH 15/37] Copy DID implementation from https://github.com/build-trust/did --- claim_test.go | 7 +- did.go | 818 ++++++++++++++++++++++++++++++++++++++++++++++++++ did_test.go | 765 ++++++++++++++++++++++++++++++++++++++++++++++ id_test.go | 71 +++-- 4 files changed, 1620 insertions(+), 41 deletions(-) create mode 100644 did.go create mode 100644 did_test.go diff --git a/claim_test.go b/claim_test.go index ed737af..a01c75f 100644 --- a/claim_test.go +++ b/claim_test.go @@ -14,7 +14,6 @@ import ( "github.com/iden3/go-iden3-crypto/poseidon" "github.com/iden3/go-iden3-crypto/utils" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -389,8 +388,7 @@ func TestNewSchemaHashFromHex(t *testing.T) { exp, err := hex.DecodeString(hash) require.NoError(t, err) - assert.Equal(t, exp[:], got[:]) - + require.Equal(t, exp[:], got[:]) } func TestSchemaHash_BigInt(t *testing.T) { @@ -402,8 +400,7 @@ func TestSchemaHash_BigInt(t *testing.T) { got := schema.BigInt() - assert.Equal(t, exp, got) - + require.Equal(t, exp, got) } func TestGetIDPosition(t *testing.T) { diff --git a/did.go b/did.go new file mode 100644 index 0000000..cda2441 --- /dev/null +++ b/did.go @@ -0,0 +1,818 @@ +// Package core is a set of tools to work with Decentralized Identifiers (DIDs) as described +// in the DID spec https://w3c.github.io/did-core/ +// Got from https://github.com/build-trust/did +package core + +import ( + "fmt" + "strings" +) + +// Param represents a parsed DID param, +// which contains a name and value. A generic param is defined +// as a param name and value separated by a colon. +// generic-param-name:param-value +// A param may also be method specific, which +// requires the method name to prefix the param name separated by a colon +// method-name:param-name. +// param = param-name [ "=" param-value ] +// https://w3c.github.io/did-core/#generic-did-parameter-names +// https://w3c.github.io/did-core/#method-specific-did-parameter-names +type Param struct { + // param-name = 1*param-char + // Name may include a method name and param name separated by a colon + Name string + // param-value = *param-char + Value string +} + +// String encodes a Param struct into a valid Param string. +// Name is required by the grammar. Value is optional +func (p *Param) String() string { + if p.Name == "" { + return "" + } + + if 0 < len(p.Value) { + return p.Name + "=" + p.Value + } + + return p.Name +} + +// A DID represents a parsed DID or a DID URL +type DID struct { + // DID Method + // https://w3c.github.io/did-core/#method-specific-syntax + Method string + + // The method-specific-id component of a DID + // method-specific-id = *idchar *( ":" *idchar ) + ID string + + // method-specific-id may be composed of multiple `:` separated idstrings + IDStrings []string + + // DID URL + // did-url = did *( ";" param ) path-abempty [ "?" query ] [ "#" fragment ] + // did-url may contain multiple params, a path, query, and fragment + Params []Param + + // DID Path, the portion of a DID reference that follows the first forward slash character. + // https://w3c.github.io/did-core/#path + Path string + + // Path may be composed of multiple `/` separated segments + // path-abempty = *( "/" segment ) + PathSegments []string + + // DID Query + // https://w3c.github.io/did-core/#query + // query = *( pchar / "/" / "?" ) + Query string + + // DID Fragment, the portion of a DID reference that follows the first hash sign character ("#") + // https://w3c.github.io/did-core/#fragment + Fragment string +} + +// the parsers internal state +type parser struct { + input string // input to the parser + currentIndex int // index in the input which the parser is currently processing + out *DID // the output DID that the parser will assemble as it steps through its state machine + err error // an error in the parser state machine +} + +// a step in the parser state machine that returns the next step +type parserStep func() parserStep + +// IsURL returns true if a DID has a Path, a Query or a Fragment +// https://w3c-ccg.github.io/did-spec/#dfn-did-reference +func (d *DID) IsURL() bool { + return (len(d.Params) > 0 || d.Path != "" || len(d.PathSegments) > 0 || d.Query != "" || d.Fragment != "") +} + +// String encodes a DID struct into a valid DID string. +// nolint: gocyclo +func (d *DID) String() string { + var buf strings.Builder + + // write the did: prefix + buf.WriteString("did:") // nolint, returned error is always nil + + if d.Method != "" { + // write method followed by a `:` + buf.WriteString(d.Method) // nolint, returned error is always nil + buf.WriteByte(':') // nolint, returned error is always nil + } else { + // if there is no Method, return an empty string + return "" + } + + if d.ID != "" { + buf.WriteString(d.ID) // nolint, returned error is always nil + } else if len(d.IDStrings) > 0 { + // join IDStrings with a colon to make the ID + buf.WriteString(strings.Join(d.IDStrings[:], ":")) // nolint, returned error is always nil + } else { + // if there is no ID, return an empty string + return "" + } + + if len(d.Params) > 0 { + // write a leading ; for each param + for _, p := range d.Params { + // get a string that represents the param + param := p.String() + if param != "" { + // params must start with a ; + buf.WriteByte(';') // nolint, returned error is always nil + buf.WriteString(param) // nolint, returned error is always nil + } else { + // if a param exists but is empty, return an empty string + return "" + } + } + } + + if d.Path != "" { + // write a leading / and then Path + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(d.Path) // nolint, returned error is always nil + } else if len(d.PathSegments) > 0 { + // write a leading / and then PathSegments joined with / between them + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(strings.Join(d.PathSegments[:], "/")) // nolint, returned error is always nil + } + + if d.Query != "" { + // write a leading ? and then Query + buf.WriteByte('?') // nolint, returned error is always nil + buf.WriteString(d.Query) // nolint, returned error is always nil + } + + if d.Fragment != "" { + // add fragment only when there is no path + buf.WriteByte('#') // nolint, returned error is always nil + buf.WriteString(d.Fragment) // nolint, returned error is always nil + } + + return buf.String() +} + +// Parse parses the input string into a DID structure. +func Parse(input string) (*DID, error) { + // intialize the parser state + p := &parser{input: input, out: &DID{}} + + // the parser state machine is implemented as a loop over parser steps + // steps increment p.currentIndex as they consume the input, each step returns the next step to run + // the state machine halts when one of the steps returns nil + // + // This design is based on this talk from Rob Pike, although the talk focuses on lexical scanning, + // the DID grammar is simple enough for us to combine lexing and parsing into one lexerless parse + // http://www.youtube.com/watch?v=HxaD_trXwRE + parserState := p.checkLength + for parserState != nil { + parserState = parserState() + } + + // If one of the steps added an err to the parser state, exit. Return nil and the error. + err := p.err + if err != nil { + return nil, err + } + + // join IDStrings with : to make up ID + p.out.ID = strings.Join(p.out.IDStrings[:], ":") + + // join PathSegments with / to make up Path + p.out.Path = strings.Join(p.out.PathSegments[:], "/") + + return p.out, nil +} + +// checkLength is a parserStep that checks if the input length is atleast 7 +// the grammar requires +// +// `did:` prefix (4 chars) +// + atleast one methodchar (1 char) +// + `:` (1 char) +// + atleast one idchar (1 char) +// +// i.e atleast 7 chars +// The current specification does not take a position on maximum length of a DID. +// https://w3c-ccg.github.io/did-spec/#upper-limits-on-did-character-length +func (p *parser) checkLength() parserStep { + inputLength := len(p.input) + + if inputLength < 7 { + return p.errorf(inputLength, "input length is less than 7") + } + + return p.parseScheme +} + +// parseScheme is a parserStep that validates that the input begins with 'did:' +func (p *parser) parseScheme() parserStep { + + currentIndex := 3 // 4 bytes in 'did:', i.e index 3 + + // the grammar requires `did:` prefix + if p.input[:currentIndex+1] != "did:" { + return p.errorf(currentIndex, "input does not begin with 'did:' prefix") + } + + p.currentIndex = currentIndex + return p.parseMethod +} + +// parseMethod is a parserStep that extracts the DID Method +// from the grammar: +// +// did = "did:" method ":" specific-idstring +// method = 1*methodchar +// methodchar = %x61-7A / DIGIT ; 61-7A is a-z in US-ASCII +func (p *parser) parseMethod() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + // parse method name + // loop over every byte following the ':' in 'did:' unlil the second ':' + // method is the string between the two ':'s + for { + if currentIndex == inputLength { + // we got to the end of the input and didn't find a second ':' + return p.errorf(currentIndex, "input does not have a second `:` marking end of method name") + } + + // read the input character at currentIndex + char := input[currentIndex] + + if char == ':' { + // we've found the second : in the input that marks the end of the method + if currentIndex == startIndex { + // return error is method is empty, ex- did::1234 + return p.errorf(currentIndex, "method is empty") + } + break + } + + // as per the grammar method can only be made of digits 0-9 or small letters a-z + if isNotDigit(char) && isNotSmallLetter(char) { + return p.errorf(currentIndex, "character is not a-z OR 0-9") + } + + // move to the next char + currentIndex = currentIndex + 1 + } + + // set parser state + p.currentIndex = currentIndex + p.out.Method = input[startIndex:currentIndex] + + // method is followed by specific-idstring, parse that next + return p.parseID +} + +// parseID is a parserStep that extracts : separated idstrings that are part of a specific-idstring +// and adds them to p.out.IDStrings +// from the grammar: +// +// specific-idstring = idstring *( ":" idstring ) +// idstring = 1*idchar +// idchar = ALPHA / DIGIT / "." / "-" +// +// p.out.IDStrings is later concatented by the Parse function before it returns. +func (p *parser) parseID() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var next parserStep + + for { + if currentIndex == inputLength { + // we've reached end of input, no next state + next = nil + break + } + + char := input[currentIndex] + + if char == ':' { + // encountered : input may have another idstring, parse ID again + next = p.parseID + break + } + + if char == ';' { + // encountered ; input may have a parameter, parse that next + next = p.parseParamName + break + } + + if char == '/' { + // encountered / input may have a path following specific-idstring, parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following specific-idstring, parse that next + next = p.parseQuery + break + } + + if char == '#' { + // encountered # input may have a fragment following specific-idstring, parse that next + next = p.parseFragment + break + } + + // make sure current char is a valid idchar + // idchar = ALPHA / DIGIT / "." / "-" + if isNotValidIDChar(char) { + return p.errorf(currentIndex, "byte is not ALPHA OR DIGIT OR '.' OR '-'") + } + + // move to the next char + currentIndex = currentIndex + 1 + } + + if currentIndex == startIndex { + // idstring length is zero + // from the grammar: + // idstring = 1*idchar + // return error because idstring is empty, ex- did:a::123:456 + return p.errorf(currentIndex, "idstring must be atleast one char long") + } + + // set parser state + p.currentIndex = currentIndex + p.out.IDStrings = append(p.out.IDStrings, input[startIndex:currentIndex]) + + // return the next parser step + return next +} + +// parseParamName is a parserStep that extracts a did-url param-name. +// A Param struct is created for each param name that is encountered. +// from the grammar: +// +// param = param-name [ "=" param-value ] +// param-name = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamName() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + if currentIndex == startIndex { + // param-name length is zero + // from the grammar: + // 1*param-char + // return error because param-name is empty, ex- did:a::123:456;param-name + return p.errorf(currentIndex, "Param name must be at least one char long") + } + + // Create a new param with the name + p.out.Params = append(p.out.Params, Param{Name: input[startIndex:currentIndex], Value: ""}) + + // return the next parser step + return next +} + +// parseParamValue is a parserStep that extracts a did-url param-value. +// A parsed Param value requires that a Param was previously created when parsing a param-name. +// from the grammar: +// +// param = param-name [ "=" param-value ] +// param-value = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamValue() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + // Get the last Param in the DID and append the value + // values may be empty according to the grammar- *param-char + p.out.Params[len(p.out.Params)-1].Value = input[startIndex:currentIndex] + + // return the next parser step + return next +} + +// paramTransition is a parserStep that extracts and transitions a param-name or +// param-value. +// nolint: gocyclo +func (p *parser) paramTransition() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached end of input, no next state + next = nil + break + } + + char := input[currentIndex] + + if char == ';' { + // encountered : input may have another param, parse paramName again + next = p.parseParamName + break + } + + // Separate steps for name and value? + if char == '=' { + // parse param value + next = p.parseParamValue + break + } + + if char == '/' { + // encountered / input may have a path following current param, parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following current param, parse that next + next = p.parseQuery + break + } + + if char == '#' { + // encountered # input may have a fragment following current param, parse that next + next = p.parseFragment + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not percent encoded + percentEncoded = false + indexIncrement = 1 + } + + // make sure current char is a valid param-char + // idchar = ALPHA / DIGIT / "." / "-" + if !percentEncoded && isNotValidParamChar(char) { + return p.errorf(currentIndex, "character is not allowed in param - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // set parser state + p.currentIndex = currentIndex + + return next +} + +// parsePath is a parserStep that extracts a DID Path from a DID Reference +// from the grammar: +// +// did-path = segment-nz *( "/" segment ) +// segment = *pchar +// segment-nz = 1*pchar +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +// +// nolint: gocyclo +func (p *parser) parsePath() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + next = nil + break + } + + char := input[currentIndex] + + if char == '/' { + // encountered / input may have another path segment, try to parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following path, parse that next + next = p.parseQuery + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + if !percentEncoded && isNotValidPathChar(char) { + return p.errorf(currentIndex, "character is not allowed in path") + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + if currentIndex == startIndex && len(p.out.PathSegments) == 0 { + // path segment length is zero + // first path segment must have atleast one character + // from the grammar + // did-path = segment-nz *( "/" segment ) + return p.errorf(currentIndex, "first path segment must have atleast one character") + } + + // update parser state + p.currentIndex = currentIndex + p.out.PathSegments = append(p.out.PathSegments, input[startIndex:currentIndex]) + + return next +} + +// parseQuery is a parserStep that extracts a DID Query from a DID Reference +// from the grammar: +// +// did-query = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +func (p *parser) parseQuery() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached the end of input + // it's ok for query to be empty, so we don't need a check for that + // did-query = *( pchar / "/" / "?" ) + break + } + + char := input[currentIndex] + + if char == '#' { + // encountered # input may have a fragment following the query, parse that next + next = p.parseFragment + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // did-query = *( pchar / "/" / "?" ) + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded + if !percentEncoded && isNotValidQueryOrFragmentChar(char) { + return p.errorf(currentIndex, "character is not allowed in query - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // update parser state + p.currentIndex = currentIndex + p.out.Query = input[startIndex:currentIndex] + + return next +} + +// parseFragment is a parserStep that extracts a DID Fragment from a DID Reference +// from the grammar: +// +// did-fragment = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +func (p *parser) parseFragment() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached the end of input + // it's ok for reference to be empty, so we don't need a check for that + // did-fragment = *( pchar / "/" / "?" ) + break + } + + char := input[currentIndex] + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // did-fragment = *( pchar / "/" / "?" ) + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded + if !percentEncoded && isNotValidQueryOrFragmentChar(char) { + return p.errorf(currentIndex, "character is not allowed in fragment - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // update parser state + p.currentIndex = currentIndex + p.out.Fragment = input[startIndex:currentIndex] + + // no more parsing needed after a fragment, + // cause the state machine to exit by returning nil + return nil +} + +// errorf is a parserStep that returns nil to cause the state machine to exit +// before returning it sets the currentIndex and err field in parser state +// other parser steps use this function to exit the state machine with an error +func (p *parser) errorf(index int, format string, args ...interface{}) parserStep { + p.currentIndex = index + p.err = fmt.Errorf(format, args...) + return nil +} + +// INLINABLE +// Calls to all functions below this point should be inlined by the go compiler +// See output of `go build -gcflags -m` to confirm + +// isNotValidIDChar returns true if a byte is not allowed in a ID +// from the grammar: +// +// idchar = ALPHA / DIGIT / "." / "-" +func isNotValidIDChar(char byte) bool { + return isNotAlpha(char) && isNotDigit(char) && char != '.' && char != '-' +} + +// isNotValidParamChar returns true if a byte is not allowed in a param-name +// or param-value from the grammar: +// +// idchar = ALPHA / DIGIT / "." / "-" / "_" / ":" +func isNotValidParamChar(char byte) bool { + return isNotAlpha(char) && isNotDigit(char) && + char != '.' && char != '-' && char != '_' && char != ':' +} + +// isNotValidQueryOrFragmentChar returns true if a byte is not allowed in a Fragment +// from the grammar: +// +// did-fragment = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// +// pct-encoded is not checked in this function +func isNotValidQueryOrFragmentChar(char byte) bool { + return isNotValidPathChar(char) && char != '/' && char != '?' +} + +// isNotValidPathChar returns true if a byte is not allowed in Path +// +// did-path = segment-nz *( "/" segment ) +// segment = *pchar +// segment-nz = 1*pchar +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// +// pct-encoded is not checked in this function +func isNotValidPathChar(char byte) bool { + return isNotUnreservedOrSubdelim(char) && char != ':' && char != '@' +} + +// isNotUnreservedOrSubdelim returns true if a byte is not unreserved or sub-delims +// from the grammar: +// +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +// +// https://tools.ietf.org/html/rfc3986#appendix-A +func isNotUnreservedOrSubdelim(char byte) bool { + switch char { + case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': + return false + default: + if isNotAlpha(char) && isNotDigit(char) { + return true + } + return false + } +} + +// isNotHexDigit returns true if a byte is not a digit between 0-9 or A-F or a-f +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotHexDigit(char byte) bool { + // '\x41' is A, '\x46' is F + // '\x61' is a, '\x66' is f + return isNotDigit(char) && (char < '\x41' || char > '\x46') && (char < '\x61' || char > '\x66') +} + +// isNotDigit returns true if a byte is not a digit between 0-9 +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotDigit(char byte) bool { + // '\x30' is digit 0, '\x39' is digit 9 + return (char < '\x30' || char > '\x39') +} + +// isNotAlpha returns true if a byte is not a big letter between A-Z or small letter between a-z +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotAlpha(char byte) bool { + return isNotSmallLetter(char) && isNotBigLetter(char) +} + +// isNotBigLetter returns true if a byte is not a big letter between A-Z +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotBigLetter(char byte) bool { + // '\x41' is big letter A, '\x5A' small letter Z + return (char < '\x41' || char > '\x5A') +} + +// isNotSmallLetter returns true if a byte is not a small letter between a-z +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotSmallLetter(char byte) bool { + // '\x61' is small letter a, '\x7A' small letter z + return (char < '\x61' || char > '\x7A') +} diff --git a/did_test.go b/did_test.go new file mode 100644 index 0000000..a48e701 --- /dev/null +++ b/did_test.go @@ -0,0 +1,765 @@ +// Got from https://github.com/build-trust/did +package core + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +func TestIsURL(t *testing.T) { + t.Run("returns false if no Path or Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123"} + assert(t, false, d.IsURL()) + }) + + t.Run("returns true if Params", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo", Value: "bar"}}} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if PathSegements", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Query", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Path and Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b", Fragment: "00000"} + assert(t, true, d.IsURL()) + }) +} + +func TestString(t *testing.T) { + t.Run("assembles a DID", func(t *testing.T) { + d := &DID{Method: "example", ID: "123"} + assert(t, "did:example:123", d.String()) + }) + + t.Run("assembles a DID from IDStrings", func(t *testing.T) { + d := &DID{Method: "example", IDStrings: []string{"123", "456"}} + assert(t, "did:example:123:456", d.String()) + }) + + t.Run("returns empty string if no method", func(t *testing.T) { + d := &DID{ID: "123"} + assert(t, "", d.String()) + }) + + t.Run("returns empty string in no ID or IDStrings", func(t *testing.T) { + d := &DID{Method: "example"} + assert(t, "", d.String()) + }) + + t.Run("returns empty string if Param Name does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "", Value: "agent"}}} + assert(t, "", d.String()) + }) + + t.Run("returns name string if Param Value does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: ""}}} + assert(t, "did:example:123;service", d.String()) + }) + + t.Run("returns param string with name and value", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param generic", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;foo:bar=high", d.String()) + }) + + t.Run("includes Param generic and method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}, {Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;service=agent;foo:bar=high", d.String()) + }) + + t.Run("includes Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b"} + assert(t, "did:example:123/a/b", d.String()) + }) + + t.Run("includes Path assembled from PathSegements", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} + assert(t, "did:example:123/a/b", d.String()) + }) + + t.Run("includes Path after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}}, Path: "a/b"} + assert(t, "did:example:123;service=agent/a/b", d.String()) + }) + + t.Run("includes Query after IDString", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, "did:example:123?abc", d.String()) + }) + + t.Run("include Query after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent?abc", d.String()) + }) + + t.Run("includes Query after Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc"} + assert(t, "did:example:123/x/y?abc", d.String()) + }) + + t.Run("includes Query after Param and Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent/x/y?abc", d.String()) + }) + + t.Run("includes Query after before Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "zyx", Query: "abc"} + assert(t, "did:example:123?abc#zyx", d.String()) + }) + + t.Run("includes Query", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, "did:example:123?abc", d.String()) + }) + + t.Run("includes Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, "did:example:123#00000", d.String()) + }) + + t.Run("includes Fragment after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, "did:example:123#00000", d.String()) + }) +} + +func TestParse(t *testing.T) { + + t.Run("returns error if input is empty", func(t *testing.T) { + _, err := Parse("") + assert(t, false, err == nil) + }) + + t.Run("returns error if input length is less than length 7", func(t *testing.T) { + _, err := Parse("did:") + assert(t, false, err == nil) + + _, err = Parse("did:a") + assert(t, false, err == nil) + + _, err = Parse("did:a:") + assert(t, false, err == nil) + }) + + t.Run("returns error if input does not have a second : to mark end of method", func(t *testing.T) { + _, err := Parse("did:aaaaaaaaaaa") + assert(t, false, err == nil) + }) + + t.Run("returns error if method is empty", func(t *testing.T) { + _, err := Parse("did::aaaaaaaaaaa") + assert(t, false, err == nil) + }) + + t.Run("returns error if idstring is empty", func(t *testing.T) { + dids := []string{ + "did:a::123:456", + "did:a:123::456", + "did:a:123:456:", + "did:a:123:/abc", + "did:a:123:#abc", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if input does not begin with did: scheme", func(t *testing.T) { + _, err := Parse("a:12345") + assert(t, false, err == nil) + }) + + t.Run("returned value is nil if input does not begin with did: scheme", func(t *testing.T) { + d, _ := Parse("a:12345") + assert(t, true, d == nil) + }) + + t.Run("succeeds if it has did prefix and length is greater than 7", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, true, d != nil) + }) + + t.Run("succeeds to extract method", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, "a", d.Method) + + d, err = Parse("did:abcdef:11111") + assert(t, nil, err) + assert(t, "abcdef", d.Method) + }) + + t.Run("returns error if method has any other char than 0-9 or a-z", func(t *testing.T) { + _, err := Parse("did:aA:1") + assert(t, false, err == nil) + + _, err = Parse("did:aa-aa:1") + assert(t, false, err == nil) + }) + + t.Run("succeeds to extract id", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, "1", d.ID) + }) + + t.Run("succeeds to extract id parts", func(t *testing.T) { + d, err := Parse("did:a:123:456") + assert(t, nil, err) + + parts := d.IDStrings + assert(t, "123", parts[0]) + assert(t, "456", parts[1]) + }) + + t.Run("returns error if ID has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:1&&111") + assert(t, false, err == nil) + }) + + t.Run("returns error if param name is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456;") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;serv&ce") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param value has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;service=ag&nt") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid percent encoded", func(t *testing.T) { + _, err := Parse("did:a:123:456;ser%2ge") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param does not exist for value", func(t *testing.T) { + _, err := Parse("did:a:123:456;=value") + assert(t, false, err == nil) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==agent") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=agent", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;service") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only and empty param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar=baz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar=baz", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "baz", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds with percent encoded chars in param name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;serv%20ice=val%20ue") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "serv%20ice=val%20ue", d.Params[0].String()) + assert(t, "serv%20ice", d.Params[0].Name) + assert(t, "val%20ue", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple generic params with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo;bar") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "foo", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + assert(t, "bar", d.Params[1].Name) + assert(t, "", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple params with names and values", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=agent;foo:bar=baz") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + assert(t, "foo:bar", d.Params[1].Name) + assert(t, "baz", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==value/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param name and no value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract query after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value?abc") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "abc", d.Query) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract fragment after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value#xyz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "xyz", d.Fragment) + }) + + t.Run("succeeds to extract path", func(t *testing.T) { + d, err := Parse("did:a:123:456/someService") + assert(t, nil, err) + assert(t, "someService", d.Path) + }) + + t.Run("succeeds to extract path segements", func(t *testing.T) { + d, err := Parse("did:a:123:456/a/b") + assert(t, nil, err) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + t.Run("succeeds with percent encoded chars in path", func(t *testing.T) { + d, err := Parse("did:a:123:456/a/%20a") + assert(t, nil, err) + assert(t, "a/%20a", d.Path) + }) + + t.Run("returns error if % in path is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:a:123:456/%", + "did:a:123:456/%a", + "did:a:123:456/%!*", + "did:a:123:456/%A!", + "did:xyz:pqr#%A!", + "did:a:123:456/%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if path is empty but there is a slash", func(t *testing.T) { + _, err := Parse("did:a:123:456/") + assert(t, false, err == nil) + }) + + t.Run("returns error if first path segment is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456//abc") + assert(t, false, err == nil) + }) + + t.Run("does not fail if second path segment is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456/abc//pqr") + assert(t, nil, err) + }) + + t.Run("returns error if path has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456/ssss^sss") + assert(t, false, err == nil) + }) + + t.Run("does not fail if path has atleast one segment and a trailing slash", func(t *testing.T) { + _, err := Parse("did:a:123:456/a/b/") + assert(t, nil, err) + }) + + t.Run("succeeds to extract query after idstring", func(t *testing.T) { + d, err := Parse("did:a:123?abc") + assert(t, nil, err) + assert(t, "a", d.Method) + assert(t, "123", d.ID) + assert(t, "abc", d.Query) + }) + + t.Run("succeeds to extract query after path", func(t *testing.T) { + d, err := Parse("did:a:123/a/b/c?abc") + assert(t, nil, err) + assert(t, "a", d.Method) + assert(t, "123", d.ID) + assert(t, "a/b/c", d.Path) + assert(t, "abc", d.Query) + }) + + t.Run("succeeds to extract fragment after query", func(t *testing.T) { + d, err := Parse("did:a:123?abc#xyz") + assert(t, nil, err) + assert(t, "abc", d.Query) + assert(t, "xyz", d.Fragment) + }) + + t.Run("succeeds with percent encoded chars in query", func(t *testing.T) { + d, err := Parse("did:a:123?ab%20c") + assert(t, nil, err) + assert(t, "ab%20c", d.Query) + }) + + t.Run("returns error if % in query is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:a:123:456?%", + "did:a:123:456?%a", + "did:a:123:456?%!*", + "did:a:123:456?%A!", + "did:xyz:pqr?%A!", + "did:a:123:456?%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if query has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456?ssss^sss") + assert(t, false, err == nil) + }) + + t.Run("succeeds to extract fragment", func(t *testing.T) { + d, err := Parse("did:a:123:456#keys-1") + assert(t, nil, err) + assert(t, "keys-1", d.Fragment) + }) + + t.Run("succeeds with percent encoded chars in fragment", func(t *testing.T) { + d, err := Parse("did:a:123:456#aaaaaa%20a") + assert(t, nil, err) + assert(t, "aaaaaa%20a", d.Fragment) + }) + + t.Run("returns error if % in fragment is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:xyz:pqr#%", + "did:xyz:pqr#%a", + "did:xyz:pqr#%!*", + "did:xyz:pqr#%!A", + "did:xyz:pqr#%A!", + "did:xyz:pqr#%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("fails if fragment has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456#ssss^sss") + assert(t, false, err == nil) + }) +} + +func Test_errorf(t *testing.T) { + p := &parser{} + p.errorf(10, "%s,%s", "a", "b") + + if p.currentIndex != 10 { + t.Errorf("did not set currentIndex") + } + + e := p.err.Error() + if e != "a,b" { + t.Errorf("err message is: '%s' expected: 'a,b'", e) + } +} + +func Test_isNotValidParamChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', '-', '_', ':'} + for _, c := range a { + assert(t, false, isNotValidParamChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' ', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidParamChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidIDChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', '-'} + for _, c := range a { + assert(t, false, isNotValidIDChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' ', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidIDChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidQueryOrFragmentChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + ':', '@', + '/', '?'} + for _, c := range a { + assert(t, false, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' '} + for _, c := range a { + assert(t, true, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidPathChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + ':', '@'} + for _, c := range a { + assert(t, false, isNotValidPathChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidPathChar(c), "Input: '%c'", c) + } +} + +func Test_isNotUnreservedOrSubdelim(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='} + for _, c := range a { + assert(t, false, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) + } + + a = []byte{'%', ':', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) + } +} + +func Test_isNotHexDigit(t *testing.T) { + a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'} + for _, c := range a { + assert(t, false, isNotHexDigit(c), "Input: '%c'", c) + } + + a = []byte{'G', 'g', '%', '\x40', '\x47', '\x60', '\x67'} + for _, c := range a { + assert(t, true, isNotHexDigit(c), "Input: '%c'", c) + } +} + +func Test_isNotDigit(t *testing.T) { + a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} + for _, c := range a { + assert(t, false, isNotDigit(c), "Input: '%c'", c) + } + + a = []byte{'A', 'a', '\x29', '\x40', '/'} + for _, c := range a { + assert(t, true, isNotDigit(c), "Input: '%c'", c) + } +} + +func Test_isNotAlpha(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} + for _, c := range a { + assert(t, false, isNotAlpha(c), "Input: '%c'", c) + } + + a = []byte{'\x40', '\x5B', '\x60', '\x7B', '0', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotAlpha(c), "Input: '%c'", c) + } +} + +// nolint: dupl +// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it +func Test_isNotBigLetter(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} + for _, c := range a { + assert(t, false, isNotBigLetter(c), "Input: '%c'", c) + } + + a = []byte{'\x40', '\x5B', 'a', 'z', '1', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotBigLetter(c), "Input: '%c'", c) + } +} + +// nolint: dupl +// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it +func Test_isNotSmallLetter(t *testing.T) { + a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} + for _, c := range a { + assert(t, false, isNotSmallLetter(c), "Input: '%c'", c) + } + + a = []byte{'\x60', '\x7B', 'A', 'Z', '1', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotSmallLetter(c), "Input: '%c'", c) + } +} + +func assert(t *testing.T, expected interface{}, actual interface{}, args ...interface{}) { + if !reflect.DeepEqual(expected, actual) { + argsLength := len(args) + var message string + + // if only one arg is present, treat it as the message + if argsLength == 1 { + message = args[0].(string) + } + + // if more than one arg is present, treat it as format, args (like Printf) + if argsLength > 1 { + message = fmt.Sprintf(args[0].(string), args[1:]...) + } + + // is message is not empty add some spacing + if message != "" { + message = "\t" + message + "\n\n" + } + + _, file, line, _ := runtime.Caller(1) + fmt.Printf("%s:%d:\n\tExpected: %#v\n\tActual: %#v\n%s", filepath.Base(file), line, expected, actual, message) + t.FailNow() + } +} diff --git a/id_test.go b/id_test.go index b941052..f282851 100644 --- a/id_test.go +++ b/id_test.go @@ -11,7 +11,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,7 +38,7 @@ func TestIDparsers(t *testing.T) { copy(genesis0[:], genesis032bytes[:]) id0 := NewID(typ0, genesis0) // Check ID0 - assert.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", id0.String()) + require.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", id0.String()) // Generate ID1 var typ1 [2]byte typ1Hex, _ := hex.DecodeString("0001") @@ -49,31 +48,31 @@ func TestIDparsers(t *testing.T) { copy(genesis1[:], genesis132bytes[:]) id1 := NewID(typ1, genesis1) // Check ID1 - assert.Equal(t, "1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4", id1.String()) + require.Equal(t, "1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4", id1.String()) emptyChecksum := []byte{0x00, 0x00} - assert.True(t, !bytes.Equal(emptyChecksum, id0[29:])) - assert.True(t, !bytes.Equal(emptyChecksum, id1[29:])) + require.True(t, !bytes.Equal(emptyChecksum, id0[29:])) + require.True(t, !bytes.Equal(emptyChecksum, id1[29:])) id0FromBytes, err := IDFromBytes(id0.Bytes()) - assert.Nil(t, err) - assert.Equal(t, id0.Bytes(), id0FromBytes.Bytes()) - assert.Equal(t, id0.String(), id0FromBytes.String()) - assert.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", + require.NoError(t, err) + require.Equal(t, id0.Bytes(), id0FromBytes.Bytes()) + require.Equal(t, id0.String(), id0FromBytes.String()) + require.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", id0FromBytes.String()) id1FromBytes, err := IDFromBytes(id1.Bytes()) - assert.Nil(t, err) - assert.Equal(t, id1.Bytes(), id1FromBytes.Bytes()) - assert.Equal(t, id1.String(), id1FromBytes.String()) - assert.Equal(t, "1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4", + require.NoError(t, err) + require.Equal(t, id1.Bytes(), id1FromBytes.Bytes()) + require.Equal(t, id1.String(), id1FromBytes.String()) + require.Equal(t, "1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4", id1FromBytes.String()) id0FromString, err := IDFromString(id0.String()) - assert.Nil(t, err) - assert.Equal(t, id0.Bytes(), id0FromString.Bytes()) - assert.Equal(t, id0.String(), id0FromString.String()) - assert.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", + require.NoError(t, err) + require.Equal(t, id0.Bytes(), id0FromString.Bytes()) + require.Equal(t, id0.String(), id0FromString.String()) + require.Equal(t, "114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E", id0FromString.String()) } @@ -91,25 +90,25 @@ func TestIDAsDID(t *testing.T) { func TestIDjsonParser(t *testing.T) { id, err := IDFromString("11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv") - assert.Nil(t, err) + require.NoError(t, err) idj, err := json.Marshal(&id) - assert.Nil(t, err) - assert.Equal(t, "11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv", + require.NoError(t, err) + require.Equal(t, "11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv", strings.Replace(string(idj), "\"", "", 2)) var idp ID err = json.Unmarshal(idj, &idp) - assert.Nil(t, err) + require.NoError(t, err) - assert.Equal(t, id, idp) + require.Equal(t, id, idp) idsMap := make(map[ID]string) idsMap[id] = "first" idsMapJSON, err := json.Marshal(idsMap) - assert.Nil(t, err) + require.NoError(t, err) var idsMapUnmarshaled map[ID]string err = json.Unmarshal(idsMapJSON, &idsMapUnmarshaled) - assert.Nil(t, err) + require.NoError(t, err) } func TestCheckChecksum(t *testing.T) { @@ -122,19 +121,19 @@ func TestCheckChecksum(t *testing.T) { var checksum [2]byte copy(checksum[:], id[len(id)-2:]) - assert.Equal(t, CalculateChecksum(typ, genesis), checksum) + require.Equal(t, CalculateChecksum(typ, genesis), checksum) - assert.True(t, CheckChecksum(id)) + require.True(t, CheckChecksum(id)) // check that if we change the checksum, returns false on CheckChecksum id = NewID(typ, genesis) copy(id[29:], []byte{0x00, 0x01}) - assert.True(t, !CheckChecksum(id)) + require.True(t, !CheckChecksum(id)) // check that if we change the type, returns false on CheckChecksum id = NewID(typ, genesis) copy(id[:2], []byte{0x00, 0x01}) - assert.True(t, !CheckChecksum(id)) + require.True(t, !CheckChecksum(id)) // check that if we change the genesis, returns false on CheckChecksum id = NewID(typ, genesis) @@ -144,24 +143,24 @@ func TestCheckChecksum(t *testing.T) { copy(changedGenesis[:], changedGenesis32bytes[:27]) copy(id[2:27], changedGenesis[:]) - assert.True(t, !CheckChecksum(id)) + require.True(t, !CheckChecksum(id)) // test with a empty id var empty [31]byte _, err := IDFromBytes(empty[:]) - assert.Equal(t, errors.New("IDFromBytes error: byte array empty"), err) + require.Equal(t, errors.New("IDFromBytes error: byte array empty"), err) } func TestIDFromInt(t *testing.T) { id, err := IDFromString("11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv") - assert.Nil(t, err) + require.NoError(t, err) intID := id.BigInt() got, err := IDFromInt(intID) - assert.Nil(t, err) + require.NoError(t, err) - assert.Equal(t, id, got) + require.Equal(t, id, got) } func TestIDFromIntStr(t *testing.T) { @@ -233,12 +232,12 @@ func TestIDinDIDFormat(t *testing.T) { var checksum [2]byte copy(checksum[:], id[len(id)-2:]) - assert.Equal(t, CalculateChecksum(typ, genesis), checksum) + require.Equal(t, CalculateChecksum(typ, genesis), checksum) } func TestID_Type(t *testing.T) { id, err := IDFromString("1MWtoAdZESeiphxp3bXupZcfS9DhMTdWNSjRwVYc2") - assert.Nil(t, err) + require.NoError(t, err) - assert.Equal(t, id.Type(), [2]byte{0x00, 0x01}) + require.Equal(t, id.Type(), [2]byte{0x00, 0x01}) } From b67e0180f4a1f50ad9e7c62e9bea71f6ea568d77 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 04:55:53 -0400 Subject: [PATCH 16/37] Get rid of github.com/build-trust/did package. Copy implementation locally. --- did2.go | 10 ++++------ did2_test.go | 7 +++---- go.mod | 2 -- go.sum | 4 ---- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/did2.go b/did2.go index 9c0e83b..48bf8a8 100644 --- a/did2.go +++ b/did2.go @@ -6,8 +6,6 @@ import ( "fmt" "math/big" "strings" - - "github.com/build-trust/did" ) var ( @@ -172,7 +170,7 @@ func FindDIDMethodByValue(_v byte) (DIDMethod, error) { return "", ErrDIDMethodNotSupported } -type DID2 did.DID +type DID2 DID func (did2 *DID2) UnmarshalJSON(bytes []byte) error { var didStr string @@ -181,7 +179,7 @@ func (did2 *DID2) UnmarshalJSON(bytes []byte) error { return err } - did3, err := did.Parse(didStr) + did3, err := Parse(didStr) if err != nil { return err } @@ -203,7 +201,7 @@ func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID2, error) { } func (did2 DID2) String() string { - return ((*did.DID)(&did2)).String() + return ((*DID)(&did2)).String() } func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { @@ -288,7 +286,7 @@ func ParseDID2FromID(id ID) (*DID2, error) { didString := strings.Join(didParts, ":") - did2, err := did.Parse(didString) + did2, err := Parse(didString) if err != nil { return nil, err } diff --git a/did2_test.go b/did2_test.go index 77d2960..15e9fa4 100644 --- a/did2_test.go +++ b/did2_test.go @@ -6,7 +6,6 @@ import ( "math/big" "testing" - did2 "github.com/build-trust/did" "github.com/stretchr/testify/require" ) @@ -15,7 +14,7 @@ func TestParseDID2(t *testing.T) { // did didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - did3, err := did2.Parse(didStr) + did3, err := Parse(didStr) require.NoError(t, err) did := (*DID2)(did3) @@ -30,7 +29,7 @@ func TestParseDID2(t *testing.T) { // readonly did didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - did3, err = did2.Parse(didStr) + did3, err = Parse(didStr) require.NoError(t, err) did = (*DID2)(did3) @@ -181,7 +180,7 @@ func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { func TestDecompose(t *testing.T) { s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" - did3, err := did2.Parse(s) + did3, err := Parse(s) require.NoError(t, err) d2 := (*DID2)(did3) diff --git a/go.mod b/go.mod index 8b5ee42..dd04066 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/iden3/go-iden3-core go 1.18 require ( - github.com/build-trust/did v0.1.3 github.com/iden3/go-iden3-crypto v0.0.14 github.com/mr-tron/base58 v1.2.0 github.com/stretchr/testify v1.8.2 @@ -11,7 +10,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ockam-network/did v0.1.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index d4bd4fa..75df68c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/build-trust/did v0.1.3 h1:FexuxTCwKHNBicj8X9CaDuPg+uYBrg5J1TZwSHQnFoM= -github.com/build-trust/did v0.1.3/go.mod h1:rGMmaVyPuUNuXPnpXEH9tOYpOL7t+2eh5DvtYGINBiU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -8,8 +6,6 @@ github.com/iden3/go-iden3-crypto v0.0.14/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBe github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/ockam-network/did v0.1.3 h1:qJGdccOV4bLfsS/eFM+Aj+CdCRJKNMxbmJevQclw44k= -github.com/ockam-network/did v0.1.3/go.mod h1:ZsbTIuVGt8OrQEbqWrSztUISN4joeMabdsinbLubbzw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 42f18ae90e7516494469456d8ae2d361711a9a62 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 05:06:00 -0400 Subject: [PATCH 17/37] Remove unneeded type DID2 --- did2.go | 50 ++++++++++++++++++++++---------------------------- did2_test.go | 17 ++++++----------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/did2.go b/did2.go index 48bf8a8..3cb24d4 100644 --- a/did2.go +++ b/did2.go @@ -170,9 +170,7 @@ func FindDIDMethodByValue(_v byte) (DIDMethod, error) { return "", ErrDIDMethodNotSupported } -type DID2 DID - -func (did2 *DID2) UnmarshalJSON(bytes []byte) error { +func (did *DID) UnmarshalJSON(bytes []byte) error { var didStr string err := json.Unmarshal(bytes, &didStr) if err != nil { @@ -183,16 +181,16 @@ func (did2 *DID2) UnmarshalJSON(bytes []byte) error { if err != nil { return err } - *did2 = DID2(*did3) + *did = *did3 return nil } -func (did2 DID2) MarshalJSON() ([]byte, error) { - return json.Marshal(did2.String()) +func (did DID) MarshalJSON() ([]byte, error) { + return json.Marshal(did.String()) } // DID2GenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID2, error) { +func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { id, err := IdGenesisFromIdenState(typ, state) if err != nil { return nil, err @@ -200,12 +198,8 @@ func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID2, error) { return ParseDID2FromID(*id) } -func (did2 DID2) String() string { - return ((*DID)(&did2)).String() -} - -func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { - id, err := decodeIDFromDID(did2) +func Decompose(did DID) (Blockchain, NetworkID, ID, error) { + id, err := decodeIDFromDID(did) if err != nil { return UnknownChain, UnknownNetwork, id, err } @@ -215,45 +209,45 @@ func Decompose(did2 DID2) (Blockchain, NetworkID, ID, error) { return UnknownChain, UnknownNetwork, id, err } - if string(method) != did2.Method { + if string(method) != did.Method { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: method mismatch: found %v in ID but %v in DID", - ErrInvalidDID, method, did2.Method) + ErrInvalidDID, method, did.Method) } - if len(did2.IDStrings) > 1 && string(blockchain) != did2.IDStrings[0] { + if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: blockchain mismatch: found %v in ID but %v in DID", - ErrInvalidDID, blockchain, did2.IDStrings[0]) + ErrInvalidDID, blockchain, did.IDStrings[0]) } - if len(did2.IDStrings) > 2 && string(networkID) != did2.IDStrings[1] { + if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { return UnknownChain, UnknownNetwork, id, fmt.Errorf("%w: network ID mismatch: found %v in ID but %v in DID", - ErrInvalidDID, networkID, did2.IDStrings[1]) + ErrInvalidDID, networkID, did.IDStrings[1]) } return blockchain, networkID, id, nil } -func IDFromDID(did2 DID2) (ID, error) { - _, _, id, err := Decompose(did2) +func IDFromDID(did DID) (ID, error) { + _, _, id, err := Decompose(did) return id, err } -func decodeIDFromDID(did2 DID2) (ID, error) { +func decodeIDFromDID(did DID) (ID, error) { var id ID - if len(did2.IDStrings) > 3 { + if len(did.IDStrings) > 3 { return id, fmt.Errorf("%w: too many fields", ErrInvalidDID) } - if len(did2.IDStrings) < 1 { + if len(did.IDStrings) < 1 { return id, fmt.Errorf("%w: no ID field in DID", ErrInvalidDID) } var err error - id, err = IDFromString(did2.IDStrings[len(did2.IDStrings)-1]) + id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) if err != nil { return id, fmt.Errorf("%w: %v", ErrInvalidDID, err) } @@ -265,8 +259,8 @@ func decodeIDFromDID(did2 DID2) (ID, error) { return id, nil } -// ParseDID2FromID returns DID2 from ID -func ParseDID2FromID(id ID) (*DID2, error) { +// ParseDID2FromID returns DID from ID +func ParseDID2FromID(id ID) (*DID, error) { if !CheckChecksum(id) { return nil, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) @@ -290,7 +284,7 @@ func ParseDID2FromID(id ID) (*DID2, error) { if err != nil { return nil, err } - return (*DID2)(did2), nil + return did2, nil } func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { diff --git a/did2_test.go b/did2_test.go index 15e9fa4..c992199 100644 --- a/did2_test.go +++ b/did2_test.go @@ -17,10 +17,7 @@ func TestParseDID2(t *testing.T) { did3, err := Parse(didStr) require.NoError(t, err) - did := (*DID2)(did3) - require.NoError(t, err) - - blockchain, networkID, id, err := Decompose(*did) + blockchain, networkID, id, err := Decompose(*did3) require.NoError(t, err) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) require.Equal(t, Polygon, blockchain) @@ -31,9 +28,8 @@ func TestParseDID2(t *testing.T) { did3, err = Parse(didStr) require.NoError(t, err) - did = (*DID2)(did3) - blockchain, networkID, id, err = Decompose(*did) + blockchain, networkID, id, err = Decompose(*did3) require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) @@ -61,7 +57,7 @@ func TestDID2_UnmarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) var obj struct { - Obj *DID2 `json:"obj"` + Obj *DID `json:"obj"` } err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -77,7 +73,7 @@ func TestDID2_UnmarshalJSON(t *testing.T) { func TestDID2_UnmarshalJSON_Error(t *testing.T) { inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` var obj struct { - Obj *DID2 `json:"obj"` + Obj *DID `json:"obj"` } err := json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -182,12 +178,11 @@ func TestDecompose(t *testing.T) { did3, err := Parse(s) require.NoError(t, err) - d2 := (*DID2)(did3) wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - bch, nt, id, err := Decompose(*d2) + bch, nt, id, err := Decompose(*did3) require.NoError(t, err) require.Equal(t, Polygon, bch) require.Equal(t, Mumbai, nt) @@ -197,7 +192,7 @@ func TestDecompose(t *testing.T) { } func helperBuildDID2FromType(t testing.TB, method DIDMethod, - blockchain Blockchain, network NetworkID) *DID2 { + blockchain Blockchain, network NetworkID) *DID { t.Helper() typ, err := BuildDIDType(method, blockchain, network) From 820a84841887e51ce910289768f696015abb14e0 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 24 Apr 2023 05:10:10 -0400 Subject: [PATCH 18/37] Rename leftovers DID2 to DID --- did2.go | 14 +++++++------- did2_test.go | 32 +++++++++++++++----------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/did2.go b/did2.go index 3cb24d4..5439db6 100644 --- a/did2.go +++ b/did2.go @@ -189,13 +189,13 @@ func (did DID) MarshalJSON() ([]byte, error) { return json.Marshal(did.String()) } -// DID2GenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DID2GenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { +// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID +func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { id, err := IdGenesisFromIdenState(typ, state) if err != nil { return nil, err } - return ParseDID2FromID(*id) + return ParseDIDFromID(*id) } func Decompose(did DID) (Blockchain, NetworkID, ID, error) { @@ -259,8 +259,8 @@ func decodeIDFromDID(did DID) (ID, error) { return id, nil } -// ParseDID2FromID returns DID from ID -func ParseDID2FromID(id ID) (*DID, error) { +// ParseDIDFromID returns DID from ID +func ParseDIDFromID(id ID) (*DID, error) { if !CheckChecksum(id) { return nil, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) @@ -280,11 +280,11 @@ func ParseDID2FromID(id ID) (*DID, error) { didString := strings.Join(didParts, ":") - did2, err := Parse(didString) + did, err := Parse(didString) if err != nil { return nil, err } - return did2, nil + return did, nil } func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { diff --git a/did2_test.go b/did2_test.go index c992199..e0183a1 100644 --- a/did2_test.go +++ b/did2_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseDID2(t *testing.T) { +func TestParseDID(t *testing.T) { // did didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" @@ -39,10 +39,10 @@ func TestParseDID2(t *testing.T) { require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) } -func TestDID2_MarshalJSON(t *testing.T) { +func TestDID_MarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) - did, err := ParseDID2FromID(id) + did, err := ParseDIDFromID(id) require.NoError(t, err) b, err := did.MarshalJSON() @@ -52,7 +52,7 @@ func TestDID2_MarshalJSON(t *testing.T) { string(b)) } -func TestDID2_UnmarshalJSON(t *testing.T) { +func TestDID_UnmarshalJSON(t *testing.T) { inBytes := `{"obj": "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) @@ -70,7 +70,7 @@ func TestDID2_UnmarshalJSON(t *testing.T) { require.Equal(t, Mumbai, networkID) } -func TestDID2_UnmarshalJSON_Error(t *testing.T) { +func TestDID_UnmarshalJSON_Error(t *testing.T) { inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` var obj struct { Obj *DID `json:"obj"` @@ -83,13 +83,13 @@ func TestDID2_UnmarshalJSON_Error(t *testing.T) { "found polygon in ID but eth in DID") } -func TestDID2GenesisFromState(t *testing.T) { +func TestDIDGenesisFromState(t *testing.T) { typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) require.NoError(t, err) genesisState := big.NewInt(1) - did, err := DID2GenesisFromIdenState(typ0, genesisState) + did, err := DIDGenesisFromIdenState(typ0, genesisState) require.NoError(t, err) require.Equal(t, string(DIDMethodIden3), did.Method) @@ -102,10 +102,10 @@ func TestDID2GenesisFromState(t *testing.T) { did.String()) } -func TestDID2_PolygonID_Types(t *testing.T) { +func TestDID_PolygonID_Types(t *testing.T) { // Polygon no chain, no network - did := helperBuildDID2FromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) + did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) require.Equal(t, string(DIDMethodPolygonID), did.Method) blockchain, networkID, _, err := Decompose(*did) @@ -117,7 +117,7 @@ func TestDID2_PolygonID_Types(t *testing.T) { did.String()) // Polygon | Polygon chain, Main - did2 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Main) + did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) require.Equal(t, string(DIDMethodPolygonID), did2.Method) blockchain2, networkID2, _, err := Decompose(*did2) @@ -129,7 +129,7 @@ func TestDID2_PolygonID_Types(t *testing.T) { did2.String()) // Polygon | Polygon chain, Mumbai - did3 := helperBuildDID2FromType(t, DIDMethodPolygonID, Polygon, Mumbai) + did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) require.Equal(t, string(DIDMethodPolygonID), did3.Method) blockchain3, networkID3, _, err := Decompose(*did3) @@ -142,11 +142,11 @@ func TestDID2_PolygonID_Types(t *testing.T) { } -func TestDID2_PolygonID_ParseDID2FromID_OnChain(t *testing.T) { +func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - did1, err := ParseDID2FromID(id1) + did1, err := ParseDIDFromID(id1) require.NoError(t, err) var addressBytesExp [20]byte @@ -187,11 +187,9 @@ func TestDecompose(t *testing.T) { require.Equal(t, Polygon, bch) require.Equal(t, Mumbai, nt) require.Equal(t, wantID, id) - - // TODO test other DID cases without network, blockchain and ID } -func helperBuildDID2FromType(t testing.TB, method DIDMethod, +func helperBuildDIDFromType(t testing.TB, method DIDMethod, blockchain Blockchain, network NetworkID) *DID { t.Helper() @@ -199,7 +197,7 @@ func helperBuildDID2FromType(t testing.TB, method DIDMethod, require.NoError(t, err) genesisState := big.NewInt(1) - did, err := DID2GenesisFromIdenState(typ, genesisState) + did, err := DIDGenesisFromIdenState(typ, genesisState) require.NoError(t, err) return did From a3e9ee66ed6f9269ff0b66d513151e35a52201ae Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Wed, 26 Apr 2023 15:30:24 -0400 Subject: [PATCH 19/37] Create new ID for unrecognized DID --- did2.go | 132 +++++++++++++++++++++++++++++++++++---------------- did2_test.go | 115 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 184 insertions(+), 63 deletions(-) diff --git a/did2.go b/did2.go index 5439db6..18a6f69 100644 --- a/did2.go +++ b/did2.go @@ -1,6 +1,7 @@ package core import ( + "crypto/sha256" "encoding/json" "errors" "fmt" @@ -9,8 +10,8 @@ import ( ) var ( - // ErrInvalidDID invalid did format. - ErrInvalidDID = errors.New("invalid did format") + // ErrUnsupportedID ID with unsupported type. + ErrUnsupportedID = errors.New("unsupported ID") // ErrDIDMethodNotSupported unsupported did method. ErrDIDMethodNotSupported = errors.New("not supported did method") // ErrBlockchainNotSupportedForDID unsupported network for did. @@ -198,62 +199,57 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { return ParseDIDFromID(*id) } -func Decompose(did DID) (Blockchain, NetworkID, ID, error) { - id, err := decodeIDFromDID(did) +func IDFromDID(did DID) ID { + id, err := idFromDID(did) if err != nil { - return UnknownChain, UnknownNetwork, id, err + return newIDFromDID(did) } - - method, blockchain, networkID, err := decodeDIDPartsFromID(id) - if err != nil { - return UnknownChain, UnknownNetwork, id, err - } - - if string(method) != did.Method { - return UnknownChain, UnknownNetwork, id, - fmt.Errorf("%w: method mismatch: found %v in ID but %v in DID", - ErrInvalidDID, method, did.Method) - } - - if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { - return UnknownChain, UnknownNetwork, id, - fmt.Errorf("%w: blockchain mismatch: found %v in ID but %v in DID", - ErrInvalidDID, blockchain, did.IDStrings[0]) - } - - if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { - return UnknownChain, UnknownNetwork, id, - fmt.Errorf("%w: network ID mismatch: found %v in ID but %v in DID", - ErrInvalidDID, networkID, did.IDStrings[1]) - } - - return blockchain, networkID, id, nil + return id } -func IDFromDID(did DID) (ID, error) { - _, _, id, err := Decompose(did) - return id, err +func newIDFromDID(did DID) ID { + checkSum := sha256.Sum256([]byte(did.String())) + var genesis [27]byte + copy(genesis[:], checkSum[len(checkSum)-27:]) + return NewID(TypeUnknown, genesis) } -func decodeIDFromDID(did DID) (ID, error) { +func idFromDID(did DID) (ID, error) { var id ID - - if len(did.IDStrings) > 3 { - return id, fmt.Errorf("%w: too many fields", ErrInvalidDID) + _, ok := DIDMethodNetwork[DIDMethod(did.Method)] + if !ok { + return id, ErrUnsupportedID } - if len(did.IDStrings) < 1 { - return id, fmt.Errorf("%w: no ID field in DID", ErrInvalidDID) + if len(did.IDStrings) > 3 || len(did.IDStrings) < 1 { + return id, ErrUnsupportedID } var err error id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) if err != nil { - return id, fmt.Errorf("%w: %v", ErrInvalidDID, err) + return id, ErrUnsupportedID } if !CheckChecksum(id) { - return id, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) + return id, ErrUnsupportedID + } + + method, blockchain, networkID, err := decodeDIDPartsFromID(id) + if err != nil { + return id, ErrUnsupportedID + } + + if string(method) != did.Method { + return id, ErrUnsupportedID + } + + if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { + return id, ErrUnsupportedID + } + + if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { + return id, ErrUnsupportedID } return id, nil @@ -262,8 +258,12 @@ func decodeIDFromDID(did DID) (ID, error) { // ParseDIDFromID returns DID from ID func ParseDIDFromID(id ID) (*DID, error) { + if id.IsUnknown() { + return nil, fmt.Errorf("%w: unknown type", ErrUnsupportedID) + } + if !CheckChecksum(id) { - return nil, fmt.Errorf("%w: invalid checksum", ErrInvalidDID) + return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) } method, blockchain, networkID, err := decodeDIDPartsFromID(id) @@ -308,3 +308,51 @@ func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { return method, blockchain, networkID, nil } + +func MethodFromID(id ID) (DIDMethod, error) { + if id.IsUnknown() { + return "", ErrUnsupportedID + } + methodByte := id.MethodByte() + return FindDIDMethodByValue(methodByte) +} + +func BlockchainFromID(id ID) (Blockchain, error) { + if id.IsUnknown() { + return UnknownChain, ErrUnsupportedID + } + + method, err := MethodFromID(id) + if err != nil { + return UnknownChain, err + } + + networkByte := id.BlockchainNetworkByte() + + blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) + if err != nil { + return UnknownChain, err + } + + return blockchain, nil +} + +func NetworkIDFromID(id ID) (NetworkID, error) { + if id.IsUnknown() { + return UnknownNetwork, ErrUnsupportedID + } + + method, err := MethodFromID(id) + if err != nil { + return UnknownNetwork, err + } + + networkByte := id.BlockchainNetworkByte() + + networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) + if err != nil { + return UnknownNetwork, err + } + + return networkID, nil +} diff --git a/did2_test.go b/did2_test.go index e0183a1..611cec5 100644 --- a/did2_test.go +++ b/did2_test.go @@ -17,10 +17,16 @@ func TestParseDID(t *testing.T) { did3, err := Parse(didStr) require.NoError(t, err) - blockchain, networkID, id, err := Decompose(*did3) - require.NoError(t, err) + id := IDFromDID(*did3) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) require.Equal(t, Mumbai, networkID) // readonly did @@ -29,11 +35,16 @@ func TestParseDID(t *testing.T) { did3, err = Parse(didStr) require.NoError(t, err) - blockchain, networkID, id, err = Decompose(*did3) - require.NoError(t, err) - + id = IDFromDID(*did3) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) + method, err = MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err = BlockchainFromID(id) + require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) + networkID, err = NetworkIDFromID(id) + require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) @@ -63,11 +74,19 @@ func TestDID_UnmarshalJSON(t *testing.T) { require.NoError(t, err) require.NotNil(t, obj.Obj) require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) - blockchain, networkID, id2, err := Decompose(*obj.Obj) + + id2 := IDFromDID(*obj.Obj) + method, err := MethodFromID(id2) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id2) require.NoError(t, err) - require.Equal(t, id, id2) require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id2) + require.NoError(t, err) require.Equal(t, Mumbai, networkID) + + require.Equal(t, id, id2) } func TestDID_UnmarshalJSON_Error(t *testing.T) { @@ -78,9 +97,9 @@ func TestDID_UnmarshalJSON_Error(t *testing.T) { err := json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) - _, err = IDFromDID(*obj.Obj) - require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ - "found polygon in ID but eth in DID") + //_, err = IDFromDID(*obj.Obj) + //require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ + // "found polygon in ID but eth in DID") } func TestDIDGenesisFromState(t *testing.T) { @@ -93,10 +112,18 @@ func TestDIDGenesisFromState(t *testing.T) { require.NoError(t, err) require.Equal(t, string(DIDMethodIden3), did.Method) - blockchain, networkID, _, err := Decompose(*did) + + id := IDFromDID(*did) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id) require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) require.Equal(t, NoNetwork, networkID) + require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did.String()) @@ -108,9 +135,15 @@ func TestDID_PolygonID_Types(t *testing.T) { did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) require.Equal(t, string(DIDMethodPolygonID), did.Method) - blockchain, networkID, _, err := Decompose(*did) + id := IDFromDID(*did) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) + blockchain, err := BlockchainFromID(id) require.NoError(t, err) require.Equal(t, ReadOnly, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", @@ -120,9 +153,15 @@ func TestDID_PolygonID_Types(t *testing.T) { did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) require.Equal(t, string(DIDMethodPolygonID), did2.Method) - blockchain2, networkID2, _, err := Decompose(*did2) + id2 := IDFromDID(*did2) + method2, err := MethodFromID(id2) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method2) + blockchain2, err := BlockchainFromID(id2) require.NoError(t, err) require.Equal(t, Polygon, blockchain2) + networkID2, err := NetworkIDFromID(id2) + require.NoError(t, err) require.Equal(t, Main, networkID2) require.Equal(t, "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", @@ -132,9 +171,15 @@ func TestDID_PolygonID_Types(t *testing.T) { did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) require.Equal(t, string(DIDMethodPolygonID), did3.Method) - blockchain3, networkID3, _, err := Decompose(*did3) + id3 := IDFromDID(*did3) + method3, err := MethodFromID(id3) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method3) + blockchain3, err := BlockchainFromID(id3) require.NoError(t, err) require.Equal(t, Polygon, blockchain3) + networkID3, err := NetworkIDFromID(id3) + require.NoError(t, err) require.Equal(t, Mumbai, networkID3) require.Equal(t, "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", @@ -158,10 +203,16 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) - bc, nID, id, err := Decompose(*did1) + id := IDFromDID(*did1) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id) require.NoError(t, err) - require.Equal(t, Polygon, bc) - require.Equal(t, Mumbai, nID) + require.Equal(t, Mumbai, networkID) require.Equal(t, true, id.IsOnChain()) addressBytes, err := id.EthAddress() @@ -182,11 +233,20 @@ func TestDecompose(t *testing.T) { wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - bch, nt, id, err := Decompose(*did3) - require.NoError(t, err) - require.Equal(t, Polygon, bch) - require.Equal(t, Mumbai, nt) + id := IDFromDID(*did3) require.Equal(t, wantID, id) + + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) + + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) } func helperBuildDIDFromType(t testing.TB, method DIDMethod, @@ -202,3 +262,16 @@ func helperBuildDIDFromType(t testing.TB, method DIDMethod, return did } + +func TestNewIDFromDID(t *testing.T) { + did, err := Parse("did:something:x") + require.NoError(t, err) + id := newIDFromDID(*did) + wantID, err := hex.DecodeString( + "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") + require.NoError(t, err) + require.Equal(t, wantID, id[:]) + + id2 := IDFromDID(*did) + require.Equal(t, id, id2) +} From 7993e5dcc61e3ffdb71003def086c863250f2610 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 27 Apr 2023 04:49:53 -0400 Subject: [PATCH 20/37] Wrap errors in more meaningful test --- did2.go | 12 ++++++------ go.mod | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/did2.go b/did2.go index 18a6f69..e6b75b6 100644 --- a/did2.go +++ b/did2.go @@ -158,7 +158,7 @@ func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, e return k.Blockchain, nil } } - return UnknownChain, ErrNetworkNotSupportedForDID + return UnknownChain, ErrBlockchainNotSupportedForDID } // FindDIDMethodByValue finds did method by its byte value @@ -208,9 +208,9 @@ func IDFromDID(did DID) ID { } func newIDFromDID(did DID) ID { - checkSum := sha256.Sum256([]byte(did.String())) + hash := sha256.Sum256([]byte(did.String())) var genesis [27]byte - copy(genesis[:], checkSum[len(checkSum)-27:]) + copy(genesis[:], hash[len(hash)-27:]) return NewID(TypeUnknown, genesis) } @@ -311,7 +311,7 @@ func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { func MethodFromID(id ID) (DIDMethod, error) { if id.IsUnknown() { - return "", ErrUnsupportedID + return "", fmt.Errorf("%w: unknown type", ErrUnsupportedID) } methodByte := id.MethodByte() return FindDIDMethodByValue(methodByte) @@ -319,7 +319,7 @@ func MethodFromID(id ID) (DIDMethod, error) { func BlockchainFromID(id ID) (Blockchain, error) { if id.IsUnknown() { - return UnknownChain, ErrUnsupportedID + return UnknownChain, fmt.Errorf("%w: unknown type", ErrUnsupportedID) } method, err := MethodFromID(id) @@ -339,7 +339,7 @@ func BlockchainFromID(id ID) (Blockchain, error) { func NetworkIDFromID(id ID) (NetworkID, error) { if id.IsUnknown() { - return UnknownNetwork, ErrUnsupportedID + return UnknownNetwork, fmt.Errorf("%w: unknown type", ErrUnsupportedID) } method, err := MethodFromID(id) diff --git a/go.mod b/go.mod index dd04066..1aed2e6 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/iden3/go-iden3-core +module github.com/iden3/go-iden3-core/v2 go 1.18 From a89f52b9cccc008b53489e9b1677ccbefebe3ea0 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 27 Apr 2023 04:51:18 -0400 Subject: [PATCH 21/37] Renamed files --- did.go | 974 +++++++++++++----------------------------------- did2.go | 358 ------------------ did2_test.go | 277 -------------- did_test.go | 960 ++++++++++++----------------------------------- did_w3c.go | 818 ++++++++++++++++++++++++++++++++++++++++ did_w3c_test.go | 765 +++++++++++++++++++++++++++++++++++++ 6 files changed, 2076 insertions(+), 2076 deletions(-) delete mode 100644 did2.go delete mode 100644 did2_test.go create mode 100644 did_w3c.go create mode 100644 did_w3c_test.go diff --git a/did.go b/did.go index cda2441..e6b75b6 100644 --- a/did.go +++ b/did.go @@ -1,818 +1,358 @@ -// Package core is a set of tools to work with Decentralized Identifiers (DIDs) as described -// in the DID spec https://w3c.github.io/did-core/ -// Got from https://github.com/build-trust/did package core import ( + "crypto/sha256" + "encoding/json" + "errors" "fmt" + "math/big" "strings" ) -// Param represents a parsed DID param, -// which contains a name and value. A generic param is defined -// as a param name and value separated by a colon. -// generic-param-name:param-value -// A param may also be method specific, which -// requires the method name to prefix the param name separated by a colon -// method-name:param-name. -// param = param-name [ "=" param-value ] -// https://w3c.github.io/did-core/#generic-did-parameter-names -// https://w3c.github.io/did-core/#method-specific-did-parameter-names -type Param struct { - // param-name = 1*param-char - // Name may include a method name and param name separated by a colon - Name string - // param-value = *param-char - Value string -} - -// String encodes a Param struct into a valid Param string. -// Name is required by the grammar. Value is optional -func (p *Param) String() string { - if p.Name == "" { - return "" - } - - if 0 < len(p.Value) { - return p.Name + "=" + p.Value - } +var ( + // ErrUnsupportedID ID with unsupported type. + ErrUnsupportedID = errors.New("unsupported ID") + // ErrDIDMethodNotSupported unsupported did method. + ErrDIDMethodNotSupported = errors.New("not supported did method") + // ErrBlockchainNotSupportedForDID unsupported network for did. + ErrBlockchainNotSupportedForDID = errors.New("not supported blockchain") + // ErrNetworkNotSupportedForDID unsupported network for did. + ErrNetworkNotSupportedForDID = errors.New("not supported network") +) - return p.Name -} +// DIDSchema DID Schema +const DIDSchema = "did" -// A DID represents a parsed DID or a DID URL -type DID struct { - // DID Method - // https://w3c.github.io/did-core/#method-specific-syntax - Method string +// DIDMethod represents did methods +type DIDMethod string - // The method-specific-id component of a DID - // method-specific-id = *idchar *( ":" *idchar ) - ID string +const ( + // DIDMethodIden3 DID method-name + DIDMethodIden3 DIDMethod = "iden3" + // DIDMethodPolygonID DID method-name + DIDMethodPolygonID DIDMethod = "polygonid" +) - // method-specific-id may be composed of multiple `:` separated idstrings - IDStrings []string +// Blockchain id of the network "eth", "polygon", etc. +type Blockchain string + +const ( + // Ethereum is ethereum blockchain network + Ethereum Blockchain = "eth" + // Polygon is polygon blockchain network + Polygon Blockchain = "polygon" + // UnknownChain is used when it's not possible to retrieve blockchain type from identifier + UnknownChain Blockchain = "unknown" + // ReadOnly should be used for readonly identity to build readonly flag + ReadOnly Blockchain = "readonly" + // NoChain can be used for identity to build readonly flag + NoChain Blockchain = "" +) - // DID URL - // did-url = did *( ";" param ) path-abempty [ "?" query ] [ "#" fragment ] - // did-url may contain multiple params, a path, query, and fragment - Params []Param +// NetworkID is method specific network identifier +type NetworkID string - // DID Path, the portion of a DID reference that follows the first forward slash character. - // https://w3c.github.io/did-core/#path - Path string +const ( + // Main is ethereum main network + Main NetworkID = "main" + // Mumbai is polygon mumbai test network + Mumbai NetworkID = "mumbai" - // Path may be composed of multiple `/` separated segments - // path-abempty = *( "/" segment ) - PathSegments []string + // Goerli is ethereum goerli test network + Goerli NetworkID = "goerli" // goerli + // UnknownNetwork is used when it's not possible to retrieve network from identifier + UnknownNetwork NetworkID = "unknown" - // DID Query - // https://w3c.github.io/did-core/#query - // query = *( pchar / "/" / "?" ) - Query string + // NoNetwork should be used for readonly identity to build readonly flag + NoNetwork NetworkID = "" +) - // DID Fragment, the portion of a DID reference that follows the first hash sign character ("#") - // https://w3c.github.io/did-core/#fragment - Fragment string +// DIDMethodByte did method flag representation +var DIDMethodByte = map[DIDMethod]byte{ + DIDMethodIden3: 0b00000001, + DIDMethodPolygonID: 0b00000010, } -// the parsers internal state -type parser struct { - input string // input to the parser - currentIndex int // index in the input which the parser is currently processing - out *DID // the output DID that the parser will assemble as it steps through its state machine - err error // an error in the parser state machine +// DIDNetworkFlag is a structure to represent DID blockchain and network id +type DIDNetworkFlag struct { + Blockchain Blockchain + NetworkID NetworkID } -// a step in the parser state machine that returns the next step -type parserStep func() parserStep +// DIDMethodNetwork is map for did methods and their blockchain networks +var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ + DIDMethodIden3: { + {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, + + {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, + {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, + + {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, + {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + }, + DIDMethodPolygonID: { + {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, + + {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, + {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, -// IsURL returns true if a DID has a Path, a Query or a Fragment -// https://w3c-ccg.github.io/did-spec/#dfn-did-reference -func (d *DID) IsURL() bool { - return (len(d.Params) > 0 || d.Path != "" || len(d.PathSegments) > 0 || d.Query != "" || d.Fragment != "") + {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, + {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + }, } -// String encodes a DID struct into a valid DID string. -// nolint: gocyclo -func (d *DID) String() string { - var buf strings.Builder - - // write the did: prefix - buf.WriteString("did:") // nolint, returned error is always nil - - if d.Method != "" { - // write method followed by a `:` - buf.WriteString(d.Method) // nolint, returned error is always nil - buf.WriteByte(':') // nolint, returned error is always nil - } else { - // if there is no Method, return an empty string - return "" - } - - if d.ID != "" { - buf.WriteString(d.ID) // nolint, returned error is always nil - } else if len(d.IDStrings) > 0 { - // join IDStrings with a colon to make the ID - buf.WriteString(strings.Join(d.IDStrings[:], ":")) // nolint, returned error is always nil - } else { - // if there is no ID, return an empty string - return "" - } - - if len(d.Params) > 0 { - // write a leading ; for each param - for _, p := range d.Params { - // get a string that represents the param - param := p.String() - if param != "" { - // params must start with a ; - buf.WriteByte(';') // nolint, returned error is always nil - buf.WriteString(param) // nolint, returned error is always nil - } else { - // if a param exists but is empty, return an empty string - return "" - } - } - } +// BuildDIDType builds bytes type from chain and network +func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { - if d.Path != "" { - // write a leading / and then Path - buf.WriteByte('/') // nolint, returned error is always nil - buf.WriteString(d.Path) // nolint, returned error is always nil - } else if len(d.PathSegments) > 0 { - // write a leading / and then PathSegments joined with / between them - buf.WriteByte('/') // nolint, returned error is always nil - buf.WriteString(strings.Join(d.PathSegments[:], "/")) // nolint, returned error is always nil + fb, ok := DIDMethodByte[method] + if !ok { + return [2]byte{}, ErrDIDMethodNotSupported } - if d.Query != "" { - // write a leading ? and then Query - buf.WriteByte('?') // nolint, returned error is always nil - buf.WriteString(d.Query) // nolint, returned error is always nil + if blockchain == NoChain { + blockchain = ReadOnly } - if d.Fragment != "" { - // add fragment only when there is no path - buf.WriteByte('#') // nolint, returned error is always nil - buf.WriteString(d.Fragment) // nolint, returned error is always nil + sb, ok := DIDMethodNetwork[method][DIDNetworkFlag{Blockchain: blockchain, NetworkID: network}] + if !ok { + return [2]byte{}, ErrNetworkNotSupportedForDID } - - return buf.String() + return [2]byte{fb, sb}, nil } -// Parse parses the input string into a DID structure. -func Parse(input string) (*DID, error) { - // intialize the parser state - p := &parser{input: input, out: &DID{}} - - // the parser state machine is implemented as a loop over parser steps - // steps increment p.currentIndex as they consume the input, each step returns the next step to run - // the state machine halts when one of the steps returns nil - // - // This design is based on this talk from Rob Pike, although the talk focuses on lexical scanning, - // the DID grammar is simple enough for us to combine lexing and parsing into one lexerless parse - // http://www.youtube.com/watch?v=HxaD_trXwRE - parserState := p.checkLength - for parserState != nil { - parserState = parserState() - } - - // If one of the steps added an err to the parser state, exit. Return nil and the error. - err := p.err +// BuildDIDTypeOnChain builds bytes type from chain and network +func BuildDIDTypeOnChain(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { + typ, err := BuildDIDType(method, blockchain, network) if err != nil { - return nil, err + return [2]byte{}, err } - // join IDStrings with : to make up ID - p.out.ID = strings.Join(p.out.IDStrings[:], ":") + // set on-chain flag (first bit of first byte) to 1 + typ[0] |= MethodOnChainFlag - // join PathSegments with / to make up Path - p.out.Path = strings.Join(p.out.PathSegments[:], "/") - - return p.out, nil -} - -// checkLength is a parserStep that checks if the input length is atleast 7 -// the grammar requires -// -// `did:` prefix (4 chars) -// + atleast one methodchar (1 char) -// + `:` (1 char) -// + atleast one idchar (1 char) -// -// i.e atleast 7 chars -// The current specification does not take a position on maximum length of a DID. -// https://w3c-ccg.github.io/did-spec/#upper-limits-on-did-character-length -func (p *parser) checkLength() parserStep { - inputLength := len(p.input) - - if inputLength < 7 { - return p.errorf(inputLength, "input length is less than 7") - } - - return p.parseScheme + return typ, nil } -// parseScheme is a parserStep that validates that the input begins with 'did:' -func (p *parser) parseScheme() parserStep { - - currentIndex := 3 // 4 bytes in 'did:', i.e index 3 - - // the grammar requires `did:` prefix - if p.input[:currentIndex+1] != "did:" { - return p.errorf(currentIndex, "input does not begin with 'did:' prefix") +// FindNetworkIDForDIDMethodByValue finds network by byte value +func FindNetworkIDForDIDMethodByValue(method DIDMethod, _v byte) (NetworkID, error) { + _, ok := DIDMethodNetwork[method] + if !ok { + return UnknownNetwork, ErrDIDMethodNotSupported } - - p.currentIndex = currentIndex - return p.parseMethod -} - -// parseMethod is a parserStep that extracts the DID Method -// from the grammar: -// -// did = "did:" method ":" specific-idstring -// method = 1*methodchar -// methodchar = %x61-7A / DIGIT ; 61-7A is a-z in US-ASCII -func (p *parser) parseMethod() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - startIndex := currentIndex - - // parse method name - // loop over every byte following the ':' in 'did:' unlil the second ':' - // method is the string between the two ':'s - for { - if currentIndex == inputLength { - // we got to the end of the input and didn't find a second ':' - return p.errorf(currentIndex, "input does not have a second `:` marking end of method name") + for k, v := range DIDMethodNetwork[method] { + if v == _v { + return k.NetworkID, nil } - - // read the input character at currentIndex - char := input[currentIndex] - - if char == ':' { - // we've found the second : in the input that marks the end of the method - if currentIndex == startIndex { - // return error is method is empty, ex- did::1234 - return p.errorf(currentIndex, "method is empty") - } - break - } - - // as per the grammar method can only be made of digits 0-9 or small letters a-z - if isNotDigit(char) && isNotSmallLetter(char) { - return p.errorf(currentIndex, "character is not a-z OR 0-9") - } - - // move to the next char - currentIndex = currentIndex + 1 } - - // set parser state - p.currentIndex = currentIndex - p.out.Method = input[startIndex:currentIndex] - - // method is followed by specific-idstring, parse that next - return p.parseID + return UnknownNetwork, ErrNetworkNotSupportedForDID } -// parseID is a parserStep that extracts : separated idstrings that are part of a specific-idstring -// and adds them to p.out.IDStrings -// from the grammar: -// -// specific-idstring = idstring *( ":" idstring ) -// idstring = 1*idchar -// idchar = ALPHA / DIGIT / "." / "-" -// -// p.out.IDStrings is later concatented by the Parse function before it returns. -func (p *parser) parseID() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - startIndex := currentIndex - - var next parserStep - - for { - if currentIndex == inputLength { - // we've reached end of input, no next state - next = nil - break - } - - char := input[currentIndex] - - if char == ':' { - // encountered : input may have another idstring, parse ID again - next = p.parseID - break - } - - if char == ';' { - // encountered ; input may have a parameter, parse that next - next = p.parseParamName - break - } - - if char == '/' { - // encountered / input may have a path following specific-idstring, parse that next - next = p.parsePath - break - } - - if char == '?' { - // encountered ? input may have a query following specific-idstring, parse that next - next = p.parseQuery - break - } - - if char == '#' { - // encountered # input may have a fragment following specific-idstring, parse that next - next = p.parseFragment - break +// FindBlockchainForDIDMethodByValue finds blockchain type by byte value +func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, error) { + _, ok := DIDMethodNetwork[method] + if !ok { + return UnknownChain, ErrDIDMethodNotSupported + } + for k, v := range DIDMethodNetwork[method] { + if v == _v { + return k.Blockchain, nil } + } + return UnknownChain, ErrBlockchainNotSupportedForDID +} - // make sure current char is a valid idchar - // idchar = ALPHA / DIGIT / "." / "-" - if isNotValidIDChar(char) { - return p.errorf(currentIndex, "byte is not ALPHA OR DIGIT OR '.' OR '-'") +// FindDIDMethodByValue finds did method by its byte value +func FindDIDMethodByValue(_v byte) (DIDMethod, error) { + for k, v := range DIDMethodByte { + if v == _v { + return k, nil } - - // move to the next char - currentIndex = currentIndex + 1 } + return "", ErrDIDMethodNotSupported +} - if currentIndex == startIndex { - // idstring length is zero - // from the grammar: - // idstring = 1*idchar - // return error because idstring is empty, ex- did:a::123:456 - return p.errorf(currentIndex, "idstring must be atleast one char long") +func (did *DID) UnmarshalJSON(bytes []byte) error { + var didStr string + err := json.Unmarshal(bytes, &didStr) + if err != nil { + return err } - // set parser state - p.currentIndex = currentIndex - p.out.IDStrings = append(p.out.IDStrings, input[startIndex:currentIndex]) - - // return the next parser step - return next + did3, err := Parse(didStr) + if err != nil { + return err + } + *did = *did3 + return nil } -// parseParamName is a parserStep that extracts a did-url param-name. -// A Param struct is created for each param name that is encountered. -// from the grammar: -// -// param = param-name [ "=" param-value ] -// param-name = 1*param-char -// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded -func (p *parser) parseParamName() parserStep { - input := p.input - startIndex := p.currentIndex + 1 - next := p.paramTransition() - currentIndex := p.currentIndex - - if currentIndex == startIndex { - // param-name length is zero - // from the grammar: - // 1*param-char - // return error because param-name is empty, ex- did:a::123:456;param-name - return p.errorf(currentIndex, "Param name must be at least one char long") - } - - // Create a new param with the name - p.out.Params = append(p.out.Params, Param{Name: input[startIndex:currentIndex], Value: ""}) - - // return the next parser step - return next +func (did DID) MarshalJSON() ([]byte, error) { + return json.Marshal(did.String()) } -// parseParamValue is a parserStep that extracts a did-url param-value. -// A parsed Param value requires that a Param was previously created when parsing a param-name. -// from the grammar: -// -// param = param-name [ "=" param-value ] -// param-value = 1*param-char -// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded -func (p *parser) parseParamValue() parserStep { - input := p.input - startIndex := p.currentIndex + 1 - next := p.paramTransition() - currentIndex := p.currentIndex - - // Get the last Param in the DID and append the value - // values may be empty according to the grammar- *param-char - p.out.Params[len(p.out.Params)-1].Value = input[startIndex:currentIndex] - - // return the next parser step - return next +// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID +func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { + id, err := IdGenesisFromIdenState(typ, state) + if err != nil { + return nil, err + } + return ParseDIDFromID(*id) } -// paramTransition is a parserStep that extracts and transitions a param-name or -// param-value. -// nolint: gocyclo -func (p *parser) paramTransition() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - - var indexIncrement int - var next parserStep - var percentEncoded bool - - for { - if currentIndex == inputLength { - // we've reached end of input, no next state - next = nil - break - } - - char := input[currentIndex] - - if char == ';' { - // encountered : input may have another param, parse paramName again - next = p.parseParamName - break - } - - // Separate steps for name and value? - if char == '=' { - // parse param value - next = p.parseParamValue - break - } - - if char == '/' { - // encountered / input may have a path following current param, parse that next - next = p.parsePath - break - } - - if char == '?' { - // encountered ? input may have a query following current param, parse that next - next = p.parseQuery - break - } - - if char == '#' { - // encountered # input may have a fragment following current param, parse that next - next = p.parseFragment - break - } - - if char == '%' { - // a % must be followed by 2 hex digits - if (currentIndex+2 >= inputLength) || - isNotHexDigit(input[currentIndex+1]) || - isNotHexDigit(input[currentIndex+2]) { - return p.errorf(currentIndex, "%% is not followed by 2 hex digits") - } - // if we got here, we're dealing with percent encoded char, jump three chars - percentEncoded = true - indexIncrement = 3 - } else { - // not percent encoded - percentEncoded = false - indexIncrement = 1 - } - - // make sure current char is a valid param-char - // idchar = ALPHA / DIGIT / "." / "-" - if !percentEncoded && isNotValidParamChar(char) { - return p.errorf(currentIndex, "character is not allowed in param - %c", char) - } - - // move to the next char - currentIndex = currentIndex + indexIncrement +func IDFromDID(did DID) ID { + id, err := idFromDID(did) + if err != nil { + return newIDFromDID(did) } - - // set parser state - p.currentIndex = currentIndex - - return next + return id } -// parsePath is a parserStep that extracts a DID Path from a DID Reference -// from the grammar: -// -// did-path = segment-nz *( "/" segment ) -// segment = *pchar -// segment-nz = 1*pchar -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// pct-encoded = "%" HEXDIG HEXDIG -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -// -// nolint: gocyclo -func (p *parser) parsePath() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - startIndex := currentIndex - - var indexIncrement int - var next parserStep - var percentEncoded bool - - for { - if currentIndex == inputLength { - next = nil - break - } +func newIDFromDID(did DID) ID { + hash := sha256.Sum256([]byte(did.String())) + var genesis [27]byte + copy(genesis[:], hash[len(hash)-27:]) + return NewID(TypeUnknown, genesis) +} - char := input[currentIndex] +func idFromDID(did DID) (ID, error) { + var id ID + _, ok := DIDMethodNetwork[DIDMethod(did.Method)] + if !ok { + return id, ErrUnsupportedID + } - if char == '/' { - // encountered / input may have another path segment, try to parse that next - next = p.parsePath - break - } + if len(did.IDStrings) > 3 || len(did.IDStrings) < 1 { + return id, ErrUnsupportedID + } - if char == '?' { - // encountered ? input may have a query following path, parse that next - next = p.parseQuery - break - } + var err error + id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) + if err != nil { + return id, ErrUnsupportedID + } - if char == '%' { - // a % must be followed by 2 hex digits - if (currentIndex+2 >= inputLength) || - isNotHexDigit(input[currentIndex+1]) || - isNotHexDigit(input[currentIndex+2]) { - return p.errorf(currentIndex, "%% is not followed by 2 hex digits") - } - // if we got here, we're dealing with percent encoded char, jump three chars - percentEncoded = true - indexIncrement = 3 - } else { - // not pecent encoded - percentEncoded = false - indexIncrement = 1 - } + if !CheckChecksum(id) { + return id, ErrUnsupportedID + } - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - if !percentEncoded && isNotValidPathChar(char) { - return p.errorf(currentIndex, "character is not allowed in path") - } + method, blockchain, networkID, err := decodeDIDPartsFromID(id) + if err != nil { + return id, ErrUnsupportedID + } - // move to the next char - currentIndex = currentIndex + indexIncrement + if string(method) != did.Method { + return id, ErrUnsupportedID } - if currentIndex == startIndex && len(p.out.PathSegments) == 0 { - // path segment length is zero - // first path segment must have atleast one character - // from the grammar - // did-path = segment-nz *( "/" segment ) - return p.errorf(currentIndex, "first path segment must have atleast one character") + if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { + return id, ErrUnsupportedID } - // update parser state - p.currentIndex = currentIndex - p.out.PathSegments = append(p.out.PathSegments, input[startIndex:currentIndex]) + if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { + return id, ErrUnsupportedID + } - return next + return id, nil } -// parseQuery is a parserStep that extracts a DID Query from a DID Reference -// from the grammar: -// -// did-query = *( pchar / "/" / "?" ) -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// pct-encoded = "%" HEXDIG HEXDIG -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -func (p *parser) parseQuery() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - startIndex := currentIndex - - var indexIncrement int - var next parserStep - var percentEncoded bool - - for { - if currentIndex == inputLength { - // we've reached the end of input - // it's ok for query to be empty, so we don't need a check for that - // did-query = *( pchar / "/" / "?" ) - break - } - - char := input[currentIndex] +// ParseDIDFromID returns DID from ID +func ParseDIDFromID(id ID) (*DID, error) { - if char == '#' { - // encountered # input may have a fragment following the query, parse that next - next = p.parseFragment - break - } + if id.IsUnknown() { + return nil, fmt.Errorf("%w: unknown type", ErrUnsupportedID) + } - if char == '%' { - // a % must be followed by 2 hex digits - if (currentIndex+2 >= inputLength) || - isNotHexDigit(input[currentIndex+1]) || - isNotHexDigit(input[currentIndex+2]) { - return p.errorf(currentIndex, "%% is not followed by 2 hex digits") - } - // if we got here, we're dealing with percent encoded char, jump three chars - percentEncoded = true - indexIncrement = 3 - } else { - // not pecent encoded - percentEncoded = false - indexIncrement = 1 - } + if !CheckChecksum(id) { + return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) + } - // did-query = *( pchar / "/" / "?" ) - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded - if !percentEncoded && isNotValidQueryOrFragmentChar(char) { - return p.errorf(currentIndex, "character is not allowed in query - %c", char) - } + method, blockchain, networkID, err := decodeDIDPartsFromID(id) + if err != nil { + return nil, err + } - // move to the next char - currentIndex = currentIndex + indexIncrement + didParts := []string{DIDSchema, string(method), string(blockchain)} + if string(networkID) != "" { + didParts = append(didParts, string(networkID)) } - // update parser state - p.currentIndex = currentIndex - p.out.Query = input[startIndex:currentIndex] + didParts = append(didParts, id.String()) - return next -} + didString := strings.Join(didParts, ":") -// parseFragment is a parserStep that extracts a DID Fragment from a DID Reference -// from the grammar: -// -// did-fragment = *( pchar / "/" / "?" ) -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// pct-encoded = "%" HEXDIG HEXDIG -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -func (p *parser) parseFragment() parserStep { - input := p.input - inputLength := len(input) - currentIndex := p.currentIndex + 1 - startIndex := currentIndex - - var indexIncrement int - var percentEncoded bool - - for { - if currentIndex == inputLength { - // we've reached the end of input - // it's ok for reference to be empty, so we don't need a check for that - // did-fragment = *( pchar / "/" / "?" ) - break - } + did, err := Parse(didString) + if err != nil { + return nil, err + } + return did, nil +} - char := input[currentIndex] - - if char == '%' { - // a % must be followed by 2 hex digits - if (currentIndex+2 >= inputLength) || - isNotHexDigit(input[currentIndex+1]) || - isNotHexDigit(input[currentIndex+2]) { - return p.errorf(currentIndex, "%% is not followed by 2 hex digits") - } - // if we got here, we're dealing with percent encoded char, jump three chars - percentEncoded = true - indexIncrement = 3 - } else { - // not pecent encoded - percentEncoded = false - indexIncrement = 1 - } +func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { + methodByte := id.MethodByte() + networkByte := id.BlockchainNetworkByte() - // did-fragment = *( pchar / "/" / "?" ) - // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded - if !percentEncoded && isNotValidQueryOrFragmentChar(char) { - return p.errorf(currentIndex, "character is not allowed in fragment - %c", char) - } + method, err := FindDIDMethodByValue(methodByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err + } - // move to the next char - currentIndex = currentIndex + indexIncrement + blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err } - // update parser state - p.currentIndex = currentIndex - p.out.Fragment = input[startIndex:currentIndex] + networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) + if err != nil { + return "", UnknownChain, UnknownNetwork, err + } - // no more parsing needed after a fragment, - // cause the state machine to exit by returning nil - return nil + return method, blockchain, networkID, nil } -// errorf is a parserStep that returns nil to cause the state machine to exit -// before returning it sets the currentIndex and err field in parser state -// other parser steps use this function to exit the state machine with an error -func (p *parser) errorf(index int, format string, args ...interface{}) parserStep { - p.currentIndex = index - p.err = fmt.Errorf(format, args...) - return nil +func MethodFromID(id ID) (DIDMethod, error) { + if id.IsUnknown() { + return "", fmt.Errorf("%w: unknown type", ErrUnsupportedID) + } + methodByte := id.MethodByte() + return FindDIDMethodByValue(methodByte) } -// INLINABLE -// Calls to all functions below this point should be inlined by the go compiler -// See output of `go build -gcflags -m` to confirm +func BlockchainFromID(id ID) (Blockchain, error) { + if id.IsUnknown() { + return UnknownChain, fmt.Errorf("%w: unknown type", ErrUnsupportedID) + } -// isNotValidIDChar returns true if a byte is not allowed in a ID -// from the grammar: -// -// idchar = ALPHA / DIGIT / "." / "-" -func isNotValidIDChar(char byte) bool { - return isNotAlpha(char) && isNotDigit(char) && char != '.' && char != '-' -} + method, err := MethodFromID(id) + if err != nil { + return UnknownChain, err + } -// isNotValidParamChar returns true if a byte is not allowed in a param-name -// or param-value from the grammar: -// -// idchar = ALPHA / DIGIT / "." / "-" / "_" / ":" -func isNotValidParamChar(char byte) bool { - return isNotAlpha(char) && isNotDigit(char) && - char != '.' && char != '-' && char != '_' && char != ':' -} + networkByte := id.BlockchainNetworkByte() -// isNotValidQueryOrFragmentChar returns true if a byte is not allowed in a Fragment -// from the grammar: -// -// did-fragment = *( pchar / "/" / "?" ) -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// -// pct-encoded is not checked in this function -func isNotValidQueryOrFragmentChar(char byte) bool { - return isNotValidPathChar(char) && char != '/' && char != '?' -} + blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) + if err != nil { + return UnknownChain, err + } -// isNotValidPathChar returns true if a byte is not allowed in Path -// -// did-path = segment-nz *( "/" segment ) -// segment = *pchar -// segment-nz = 1*pchar -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// -// pct-encoded is not checked in this function -func isNotValidPathChar(char byte) bool { - return isNotUnreservedOrSubdelim(char) && char != ':' && char != '@' + return blockchain, nil } -// isNotUnreservedOrSubdelim returns true if a byte is not unreserved or sub-delims -// from the grammar: -// -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" -// -// https://tools.ietf.org/html/rfc3986#appendix-A -func isNotUnreservedOrSubdelim(char byte) bool { - switch char { - case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': - return false - default: - if isNotAlpha(char) && isNotDigit(char) { - return true - } - return false +func NetworkIDFromID(id ID) (NetworkID, error) { + if id.IsUnknown() { + return UnknownNetwork, fmt.Errorf("%w: unknown type", ErrUnsupportedID) } -} -// isNotHexDigit returns true if a byte is not a digit between 0-9 or A-F or a-f -// in US-ASCII http://www.columbia.edu/kermit/ascii.html -// https://tools.ietf.org/html/rfc5234#appendix-B.1 -func isNotHexDigit(char byte) bool { - // '\x41' is A, '\x46' is F - // '\x61' is a, '\x66' is f - return isNotDigit(char) && (char < '\x41' || char > '\x46') && (char < '\x61' || char > '\x66') -} - -// isNotDigit returns true if a byte is not a digit between 0-9 -// in US-ASCII http://www.columbia.edu/kermit/ascii.html -// https://tools.ietf.org/html/rfc5234#appendix-B.1 -func isNotDigit(char byte) bool { - // '\x30' is digit 0, '\x39' is digit 9 - return (char < '\x30' || char > '\x39') -} + method, err := MethodFromID(id) + if err != nil { + return UnknownNetwork, err + } -// isNotAlpha returns true if a byte is not a big letter between A-Z or small letter between a-z -// https://tools.ietf.org/html/rfc5234#appendix-B.1 -func isNotAlpha(char byte) bool { - return isNotSmallLetter(char) && isNotBigLetter(char) -} + networkByte := id.BlockchainNetworkByte() -// isNotBigLetter returns true if a byte is not a big letter between A-Z -// in US-ASCII http://www.columbia.edu/kermit/ascii.html -// https://tools.ietf.org/html/rfc5234#appendix-B.1 -func isNotBigLetter(char byte) bool { - // '\x41' is big letter A, '\x5A' small letter Z - return (char < '\x41' || char > '\x5A') -} + networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) + if err != nil { + return UnknownNetwork, err + } -// isNotSmallLetter returns true if a byte is not a small letter between a-z -// in US-ASCII http://www.columbia.edu/kermit/ascii.html -// https://tools.ietf.org/html/rfc5234#appendix-B.1 -func isNotSmallLetter(char byte) bool { - // '\x61' is small letter a, '\x7A' small letter z - return (char < '\x61' || char > '\x7A') + return networkID, nil } diff --git a/did2.go b/did2.go deleted file mode 100644 index e6b75b6..0000000 --- a/did2.go +++ /dev/null @@ -1,358 +0,0 @@ -package core - -import ( - "crypto/sha256" - "encoding/json" - "errors" - "fmt" - "math/big" - "strings" -) - -var ( - // ErrUnsupportedID ID with unsupported type. - ErrUnsupportedID = errors.New("unsupported ID") - // ErrDIDMethodNotSupported unsupported did method. - ErrDIDMethodNotSupported = errors.New("not supported did method") - // ErrBlockchainNotSupportedForDID unsupported network for did. - ErrBlockchainNotSupportedForDID = errors.New("not supported blockchain") - // ErrNetworkNotSupportedForDID unsupported network for did. - ErrNetworkNotSupportedForDID = errors.New("not supported network") -) - -// DIDSchema DID Schema -const DIDSchema = "did" - -// DIDMethod represents did methods -type DIDMethod string - -const ( - // DIDMethodIden3 DID method-name - DIDMethodIden3 DIDMethod = "iden3" - // DIDMethodPolygonID DID method-name - DIDMethodPolygonID DIDMethod = "polygonid" -) - -// Blockchain id of the network "eth", "polygon", etc. -type Blockchain string - -const ( - // Ethereum is ethereum blockchain network - Ethereum Blockchain = "eth" - // Polygon is polygon blockchain network - Polygon Blockchain = "polygon" - // UnknownChain is used when it's not possible to retrieve blockchain type from identifier - UnknownChain Blockchain = "unknown" - // ReadOnly should be used for readonly identity to build readonly flag - ReadOnly Blockchain = "readonly" - // NoChain can be used for identity to build readonly flag - NoChain Blockchain = "" -) - -// NetworkID is method specific network identifier -type NetworkID string - -const ( - // Main is ethereum main network - Main NetworkID = "main" - // Mumbai is polygon mumbai test network - Mumbai NetworkID = "mumbai" - - // Goerli is ethereum goerli test network - Goerli NetworkID = "goerli" // goerli - // 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 = "" -) - -// DIDMethodByte did method flag representation -var DIDMethodByte = map[DIDMethod]byte{ - DIDMethodIden3: 0b00000001, - DIDMethodPolygonID: 0b00000010, -} - -// DIDNetworkFlag is a structure to represent DID blockchain and network id -type DIDNetworkFlag struct { - Blockchain Blockchain - NetworkID NetworkID -} - -// DIDMethodNetwork is map for did methods and their blockchain networks -var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ - DIDMethodIden3: { - {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, - - {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, - {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, - - {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, - {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, - }, - DIDMethodPolygonID: { - {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, - - {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, - {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, - - {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, - {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, - }, -} - -// BuildDIDType builds bytes type from chain and network -func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { - - fb, ok := DIDMethodByte[method] - if !ok { - return [2]byte{}, ErrDIDMethodNotSupported - } - - if blockchain == NoChain { - blockchain = ReadOnly - } - - sb, ok := DIDMethodNetwork[method][DIDNetworkFlag{Blockchain: blockchain, NetworkID: network}] - if !ok { - return [2]byte{}, ErrNetworkNotSupportedForDID - } - return [2]byte{fb, sb}, nil -} - -// BuildDIDTypeOnChain builds bytes type from chain and network -func BuildDIDTypeOnChain(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { - typ, err := BuildDIDType(method, blockchain, network) - if err != nil { - return [2]byte{}, err - } - - // set on-chain flag (first bit of first byte) to 1 - typ[0] |= MethodOnChainFlag - - return typ, nil -} - -// FindNetworkIDForDIDMethodByValue finds network by byte value -func FindNetworkIDForDIDMethodByValue(method DIDMethod, _v byte) (NetworkID, error) { - _, ok := DIDMethodNetwork[method] - if !ok { - return UnknownNetwork, ErrDIDMethodNotSupported - } - for k, v := range DIDMethodNetwork[method] { - if v == _v { - return k.NetworkID, nil - } - } - return UnknownNetwork, ErrNetworkNotSupportedForDID -} - -// FindBlockchainForDIDMethodByValue finds blockchain type by byte value -func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, error) { - _, ok := DIDMethodNetwork[method] - if !ok { - return UnknownChain, ErrDIDMethodNotSupported - } - for k, v := range DIDMethodNetwork[method] { - if v == _v { - return k.Blockchain, nil - } - } - return UnknownChain, ErrBlockchainNotSupportedForDID -} - -// FindDIDMethodByValue finds did method by its byte value -func FindDIDMethodByValue(_v byte) (DIDMethod, error) { - for k, v := range DIDMethodByte { - if v == _v { - return k, nil - } - } - return "", ErrDIDMethodNotSupported -} - -func (did *DID) UnmarshalJSON(bytes []byte) error { - var didStr string - err := json.Unmarshal(bytes, &didStr) - if err != nil { - return err - } - - did3, err := Parse(didStr) - if err != nil { - return err - } - *did = *did3 - return nil -} - -func (did DID) MarshalJSON() ([]byte, error) { - return json.Marshal(did.String()) -} - -// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { - id, err := IdGenesisFromIdenState(typ, state) - if err != nil { - return nil, err - } - return ParseDIDFromID(*id) -} - -func IDFromDID(did DID) ID { - id, err := idFromDID(did) - if err != nil { - return newIDFromDID(did) - } - return id -} - -func newIDFromDID(did DID) ID { - hash := sha256.Sum256([]byte(did.String())) - var genesis [27]byte - copy(genesis[:], hash[len(hash)-27:]) - return NewID(TypeUnknown, genesis) -} - -func idFromDID(did DID) (ID, error) { - var id ID - _, ok := DIDMethodNetwork[DIDMethod(did.Method)] - if !ok { - return id, ErrUnsupportedID - } - - if len(did.IDStrings) > 3 || len(did.IDStrings) < 1 { - return id, ErrUnsupportedID - } - - var err error - id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) - if err != nil { - return id, ErrUnsupportedID - } - - if !CheckChecksum(id) { - return id, ErrUnsupportedID - } - - method, blockchain, networkID, err := decodeDIDPartsFromID(id) - if err != nil { - return id, ErrUnsupportedID - } - - if string(method) != did.Method { - return id, ErrUnsupportedID - } - - if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { - return id, ErrUnsupportedID - } - - if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { - return id, ErrUnsupportedID - } - - return id, nil -} - -// ParseDIDFromID returns DID from ID -func ParseDIDFromID(id ID) (*DID, error) { - - if id.IsUnknown() { - return nil, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - - if !CheckChecksum(id) { - return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) - } - - method, blockchain, networkID, err := decodeDIDPartsFromID(id) - if err != nil { - return nil, err - } - - didParts := []string{DIDSchema, string(method), string(blockchain)} - if string(networkID) != "" { - didParts = append(didParts, string(networkID)) - } - - didParts = append(didParts, id.String()) - - didString := strings.Join(didParts, ":") - - did, err := Parse(didString) - if err != nil { - return nil, err - } - return did, nil -} - -func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { - methodByte := id.MethodByte() - networkByte := id.BlockchainNetworkByte() - - method, err := FindDIDMethodByValue(methodByte) - if err != nil { - return "", UnknownChain, UnknownNetwork, err - } - - blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) - if err != nil { - return "", UnknownChain, UnknownNetwork, err - } - - networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) - if err != nil { - return "", UnknownChain, UnknownNetwork, err - } - - return method, blockchain, networkID, nil -} - -func MethodFromID(id ID) (DIDMethod, error) { - if id.IsUnknown() { - return "", fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - methodByte := id.MethodByte() - return FindDIDMethodByValue(methodByte) -} - -func BlockchainFromID(id ID) (Blockchain, error) { - if id.IsUnknown() { - return UnknownChain, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - - method, err := MethodFromID(id) - if err != nil { - return UnknownChain, err - } - - networkByte := id.BlockchainNetworkByte() - - blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) - if err != nil { - return UnknownChain, err - } - - return blockchain, nil -} - -func NetworkIDFromID(id ID) (NetworkID, error) { - if id.IsUnknown() { - return UnknownNetwork, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - - method, err := MethodFromID(id) - if err != nil { - return UnknownNetwork, err - } - - networkByte := id.BlockchainNetworkByte() - - networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) - if err != nil { - return UnknownNetwork, err - } - - return networkID, nil -} diff --git a/did2_test.go b/did2_test.go deleted file mode 100644 index 611cec5..0000000 --- a/did2_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package core - -import ( - "encoding/hex" - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParseDID(t *testing.T) { - - // did - didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - - did3, err := Parse(didStr) - require.NoError(t, err) - - id := IDFromDID(*did3) - require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodIden3, method) - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain) - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID) - - // readonly did - didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - - did3, err = Parse(didStr) - require.NoError(t, err) - - id = IDFromDID(*did3) - require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) - method, err = MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodIden3, method) - blockchain, err = BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, ReadOnly, blockchain) - networkID, err = NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, NoNetwork, networkID) - - require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) -} - -func TestDID_MarshalJSON(t *testing.T) { - id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") - require.NoError(t, err) - did, err := ParseDIDFromID(id) - require.NoError(t, err) - - b, err := did.MarshalJSON() - require.NoError(t, err) - require.Equal(t, - `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, - string(b)) -} - -func TestDID_UnmarshalJSON(t *testing.T) { - inBytes := `{"obj": "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` - id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") - require.NoError(t, err) - var obj struct { - Obj *DID `json:"obj"` - } - err = json.Unmarshal([]byte(inBytes), &obj) - require.NoError(t, err) - require.NotNil(t, obj.Obj) - require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) - - id2 := IDFromDID(*obj.Obj) - method, err := MethodFromID(id2) - require.NoError(t, err) - require.Equal(t, DIDMethodIden3, method) - blockchain, err := BlockchainFromID(id2) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain) - networkID, err := NetworkIDFromID(id2) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID) - - require.Equal(t, id, id2) -} - -func TestDID_UnmarshalJSON_Error(t *testing.T) { - inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` - var obj struct { - Obj *DID `json:"obj"` - } - err := json.Unmarshal([]byte(inBytes), &obj) - require.NoError(t, err) - - //_, err = IDFromDID(*obj.Obj) - //require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ - // "found polygon in ID but eth in DID") -} - -func TestDIDGenesisFromState(t *testing.T) { - - typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) - require.NoError(t, err) - - genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ0, genesisState) - require.NoError(t, err) - - require.Equal(t, string(DIDMethodIden3), did.Method) - - id := IDFromDID(*did) - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodIden3, method) - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, ReadOnly, blockchain) - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, NoNetwork, networkID) - - require.Equal(t, - "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", - did.String()) -} - -func TestDID_PolygonID_Types(t *testing.T) { - - // Polygon no chain, no network - did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - - require.Equal(t, string(DIDMethodPolygonID), did.Method) - id := IDFromDID(*did) - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, ReadOnly, blockchain) - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, NoNetwork, networkID) - require.Equal(t, - "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", - did.String()) - - // Polygon | Polygon chain, Main - did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) - - require.Equal(t, string(DIDMethodPolygonID), did2.Method) - id2 := IDFromDID(*did2) - method2, err := MethodFromID(id2) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method2) - blockchain2, err := BlockchainFromID(id2) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain2) - networkID2, err := NetworkIDFromID(id2) - require.NoError(t, err) - require.Equal(t, Main, networkID2) - require.Equal(t, - "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", - did2.String()) - - // Polygon | Polygon chain, Mumbai - did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) - - require.Equal(t, string(DIDMethodPolygonID), did3.Method) - id3 := IDFromDID(*did3) - method3, err := MethodFromID(id3) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method3) - blockchain3, err := BlockchainFromID(id3) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain3) - networkID3, err := NetworkIDFromID(id3) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID3) - require.Equal(t, - "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", - did3.String()) - -} - -func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { - id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") - require.NoError(t, err) - - did1, err := ParseDIDFromID(id1) - require.NoError(t, err) - - var addressBytesExp [20]byte - _, err = hex.Decode(addressBytesExp[:], - []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) - require.NoError(t, err) - - require.Equal(t, string(DIDMethodPolygonID), did1.Method) - wantIDs := []string{"polygon", "mumbai", - "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} - require.Equal(t, wantIDs, did1.IDStrings) - id := IDFromDID(*did1) - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain) - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID) - require.Equal(t, true, id.IsOnChain()) - - addressBytes, err := id.EthAddress() - require.NoError(t, err) - require.Equal(t, addressBytesExp, addressBytes) - - require.Equal(t, - "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", - did1.String()) -} - -func TestDecompose(t *testing.T) { - s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" - - did3, err := Parse(s) - require.NoError(t, err) - - wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") - require.NoError(t, err) - - id := IDFromDID(*did3) - require.Equal(t, wantID, id) - - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) - - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain) - - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID) -} - -func helperBuildDIDFromType(t testing.TB, method DIDMethod, - blockchain Blockchain, network NetworkID) *DID { - t.Helper() - - typ, err := BuildDIDType(method, blockchain, network) - require.NoError(t, err) - - genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ, genesisState) - require.NoError(t, err) - - return did -} - -func TestNewIDFromDID(t *testing.T) { - did, err := Parse("did:something:x") - require.NoError(t, err) - id := newIDFromDID(*did) - wantID, err := hex.DecodeString( - "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") - require.NoError(t, err) - require.Equal(t, wantID, id[:]) - - id2 := IDFromDID(*did) - require.Equal(t, id, id2) -} diff --git a/did_test.go b/did_test.go index a48e701..611cec5 100644 --- a/did_test.go +++ b/did_test.go @@ -1,765 +1,277 @@ -// Got from https://github.com/build-trust/did package core import ( - "fmt" - "path/filepath" - "reflect" - "runtime" + "encoding/hex" + "encoding/json" + "math/big" "testing" + + "github.com/stretchr/testify/require" ) -func TestIsURL(t *testing.T) { - t.Run("returns false if no Path or Fragment", func(t *testing.T) { - d := &DID{Method: "example", ID: "123"} - assert(t, false, d.IsURL()) - }) - - t.Run("returns true if Params", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo", Value: "bar"}}} - assert(t, true, d.IsURL()) - }) - - t.Run("returns true if Path", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Path: "a/b"} - assert(t, true, d.IsURL()) - }) - - t.Run("returns true if PathSegements", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} - assert(t, true, d.IsURL()) - }) - - t.Run("returns true if Query", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Query: "abc"} - assert(t, true, d.IsURL()) - }) - - t.Run("returns true if Fragment", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Fragment: "00000"} - assert(t, true, d.IsURL()) - }) - - t.Run("returns true if Path and Fragment", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Path: "a/b", Fragment: "00000"} - assert(t, true, d.IsURL()) - }) +func TestParseDID(t *testing.T) { + + // did + didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" + + did3, err := Parse(didStr) + require.NoError(t, err) + + id := IDFromDID(*did3) + require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) + + // readonly did + didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" + + did3, err = Parse(didStr) + require.NoError(t, err) + + id = IDFromDID(*did3) + require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) + method, err = MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err = BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err = NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) + + require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) } -func TestString(t *testing.T) { - t.Run("assembles a DID", func(t *testing.T) { - d := &DID{Method: "example", ID: "123"} - assert(t, "did:example:123", d.String()) - }) - - t.Run("assembles a DID from IDStrings", func(t *testing.T) { - d := &DID{Method: "example", IDStrings: []string{"123", "456"}} - assert(t, "did:example:123:456", d.String()) - }) - - t.Run("returns empty string if no method", func(t *testing.T) { - d := &DID{ID: "123"} - assert(t, "", d.String()) - }) - - t.Run("returns empty string in no ID or IDStrings", func(t *testing.T) { - d := &DID{Method: "example"} - assert(t, "", d.String()) - }) - - t.Run("returns empty string if Param Name does not exist", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "", Value: "agent"}}} - assert(t, "", d.String()) - }) - - t.Run("returns name string if Param Value does not exist", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: ""}}} - assert(t, "did:example:123;service", d.String()) - }) - - t.Run("returns param string with name and value", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} - assert(t, "did:example:123;service=agent", d.String()) - }) - - t.Run("includes Param generic", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} - assert(t, "did:example:123;service=agent", d.String()) - }) - - t.Run("includes Param method", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo:bar", Value: "high"}}} - assert(t, "did:example:123;foo:bar=high", d.String()) - }) - - t.Run("includes Param generic and method", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", - Params: []Param{{Name: "service", Value: "agent"}, {Name: "foo:bar", Value: "high"}}} - assert(t, "did:example:123;service=agent;foo:bar=high", d.String()) - }) - - t.Run("includes Path", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Path: "a/b"} - assert(t, "did:example:123/a/b", d.String()) - }) - - t.Run("includes Path assembled from PathSegements", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} - assert(t, "did:example:123/a/b", d.String()) - }) - - t.Run("includes Path after Param", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", - Params: []Param{{Name: "service", Value: "agent"}}, Path: "a/b"} - assert(t, "did:example:123;service=agent/a/b", d.String()) - }) - - t.Run("includes Query after IDString", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Query: "abc"} - assert(t, "did:example:123?abc", d.String()) - }) - - t.Run("include Query after Param", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Query: "abc", - Params: []Param{{Name: "service", Value: "agent"}}} - assert(t, "did:example:123;service=agent?abc", d.String()) - }) - - t.Run("includes Query after Path", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc"} - assert(t, "did:example:123/x/y?abc", d.String()) - }) - - t.Run("includes Query after Param and Path", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc", - Params: []Param{{Name: "service", Value: "agent"}}} - assert(t, "did:example:123;service=agent/x/y?abc", d.String()) - }) - - t.Run("includes Query after before Fragment", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Fragment: "zyx", Query: "abc"} - assert(t, "did:example:123?abc#zyx", d.String()) - }) - - t.Run("includes Query", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Query: "abc"} - assert(t, "did:example:123?abc", d.String()) - }) - - t.Run("includes Fragment", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Fragment: "00000"} - assert(t, "did:example:123#00000", d.String()) - }) - - t.Run("includes Fragment after Param", func(t *testing.T) { - d := &DID{Method: "example", ID: "123", Fragment: "00000"} - assert(t, "did:example:123#00000", d.String()) - }) +func TestDID_MarshalJSON(t *testing.T) { + id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") + require.NoError(t, err) + did, err := ParseDIDFromID(id) + require.NoError(t, err) + + b, err := did.MarshalJSON() + require.NoError(t, err) + require.Equal(t, + `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, + string(b)) } -func TestParse(t *testing.T) { - - t.Run("returns error if input is empty", func(t *testing.T) { - _, err := Parse("") - assert(t, false, err == nil) - }) - - t.Run("returns error if input length is less than length 7", func(t *testing.T) { - _, err := Parse("did:") - assert(t, false, err == nil) - - _, err = Parse("did:a") - assert(t, false, err == nil) - - _, err = Parse("did:a:") - assert(t, false, err == nil) - }) - - t.Run("returns error if input does not have a second : to mark end of method", func(t *testing.T) { - _, err := Parse("did:aaaaaaaaaaa") - assert(t, false, err == nil) - }) - - t.Run("returns error if method is empty", func(t *testing.T) { - _, err := Parse("did::aaaaaaaaaaa") - assert(t, false, err == nil) - }) - - t.Run("returns error if idstring is empty", func(t *testing.T) { - dids := []string{ - "did:a::123:456", - "did:a:123::456", - "did:a:123:456:", - "did:a:123:/abc", - "did:a:123:#abc", - } - for _, did := range dids { - _, err := Parse(did) - assert(t, false, err == nil, "Input: %s", did) - } - }) - - t.Run("returns error if input does not begin with did: scheme", func(t *testing.T) { - _, err := Parse("a:12345") - assert(t, false, err == nil) - }) - - t.Run("returned value is nil if input does not begin with did: scheme", func(t *testing.T) { - d, _ := Parse("a:12345") - assert(t, true, d == nil) - }) - - t.Run("succeeds if it has did prefix and length is greater than 7", func(t *testing.T) { - d, err := Parse("did:a:1") - assert(t, nil, err) - assert(t, true, d != nil) - }) - - t.Run("succeeds to extract method", func(t *testing.T) { - d, err := Parse("did:a:1") - assert(t, nil, err) - assert(t, "a", d.Method) - - d, err = Parse("did:abcdef:11111") - assert(t, nil, err) - assert(t, "abcdef", d.Method) - }) - - t.Run("returns error if method has any other char than 0-9 or a-z", func(t *testing.T) { - _, err := Parse("did:aA:1") - assert(t, false, err == nil) - - _, err = Parse("did:aa-aa:1") - assert(t, false, err == nil) - }) - - t.Run("succeeds to extract id", func(t *testing.T) { - d, err := Parse("did:a:1") - assert(t, nil, err) - assert(t, "1", d.ID) - }) - - t.Run("succeeds to extract id parts", func(t *testing.T) { - d, err := Parse("did:a:123:456") - assert(t, nil, err) - - parts := d.IDStrings - assert(t, "123", parts[0]) - assert(t, "456", parts[1]) - }) - - t.Run("returns error if ID has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:1&&111") - assert(t, false, err == nil) - }) - - t.Run("returns error if param name is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456;") - assert(t, false, err == nil) - }) - - t.Run("returns error if Param name has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456;serv&ce") - assert(t, false, err == nil) - }) - - t.Run("returns error if Param value has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456;service=ag&nt") - assert(t, false, err == nil) - }) - - t.Run("returns error if Param name has an invalid percent encoded", func(t *testing.T) { - _, err := Parse("did:a:123:456;ser%2ge") - assert(t, false, err == nil) - }) - - t.Run("returns error if Param does not exist for value", func(t *testing.T) { - _, err := Parse("did:a:123:456;=value") - assert(t, false, err == nil) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract generic param with name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;service==agent") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service=agent", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "agent", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract generic param with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;service") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract generic param with name only and empty param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract method param with name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo:bar=baz") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "foo:bar=baz", d.Params[0].String()) - assert(t, "foo:bar", d.Params[0].Name) - assert(t, "baz", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract method param with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo:bar") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "foo:bar", d.Params[0].String()) - assert(t, "foo:bar", d.Params[0].Name) - assert(t, "", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds with percent encoded chars in param name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;serv%20ice=val%20ue") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "serv%20ice=val%20ue", d.Params[0].String()) - assert(t, "serv%20ice", d.Params[0].Name) - assert(t, "val%20ue", d.Params[0].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract multiple generic params with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo;bar") - assert(t, nil, err) - assert(t, 2, len(d.Params)) - assert(t, "foo", d.Params[0].Name) - assert(t, "", d.Params[0].Value) - assert(t, "bar", d.Params[1].Name) - assert(t, "", d.Params[1].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract multiple params with names and values", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=agent;foo:bar=baz") - assert(t, nil, err) - assert(t, 2, len(d.Params)) - assert(t, "service", d.Params[0].Name) - assert(t, "agent", d.Params[0].Value) - assert(t, "foo:bar", d.Params[1].Name) - assert(t, "baz", d.Params[1].Value) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract path after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service==value/a/b") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service=value", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "value", d.Params[0].Value) - - segments := d.PathSegments - assert(t, "a", segments[0]) - assert(t, "b", segments[1]) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract path after generic param name and no value", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=/a/b") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "", d.Params[0].Value) - - segments := d.PathSegments - assert(t, "a", segments[0]) - assert(t, "b", segments[1]) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract query after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=value?abc") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service=value", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "value", d.Params[0].Value) - assert(t, "abc", d.Query) - }) - - // nolint: dupl - // test for params look similar to linter - t.Run("succeeds to extract fragment after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=value#xyz") - assert(t, nil, err) - assert(t, 1, len(d.Params)) - assert(t, "service=value", d.Params[0].String()) - assert(t, "service", d.Params[0].Name) - assert(t, "value", d.Params[0].Value) - assert(t, "xyz", d.Fragment) - }) - - t.Run("succeeds to extract path", func(t *testing.T) { - d, err := Parse("did:a:123:456/someService") - assert(t, nil, err) - assert(t, "someService", d.Path) - }) - - t.Run("succeeds to extract path segements", func(t *testing.T) { - d, err := Parse("did:a:123:456/a/b") - assert(t, nil, err) - - segments := d.PathSegments - assert(t, "a", segments[0]) - assert(t, "b", segments[1]) - }) - - t.Run("succeeds with percent encoded chars in path", func(t *testing.T) { - d, err := Parse("did:a:123:456/a/%20a") - assert(t, nil, err) - assert(t, "a/%20a", d.Path) - }) - - t.Run("returns error if % in path is not followed by 2 hex chars", func(t *testing.T) { - dids := []string{ - "did:a:123:456/%", - "did:a:123:456/%a", - "did:a:123:456/%!*", - "did:a:123:456/%A!", - "did:xyz:pqr#%A!", - "did:a:123:456/%A%", - } - for _, did := range dids { - _, err := Parse(did) - assert(t, false, err == nil, "Input: %s", did) - } - }) - - t.Run("returns error if path is empty but there is a slash", func(t *testing.T) { - _, err := Parse("did:a:123:456/") - assert(t, false, err == nil) - }) - - t.Run("returns error if first path segment is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456//abc") - assert(t, false, err == nil) - }) - - t.Run("does not fail if second path segment is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456/abc//pqr") - assert(t, nil, err) - }) - - t.Run("returns error if path has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456/ssss^sss") - assert(t, false, err == nil) - }) - - t.Run("does not fail if path has atleast one segment and a trailing slash", func(t *testing.T) { - _, err := Parse("did:a:123:456/a/b/") - assert(t, nil, err) - }) - - t.Run("succeeds to extract query after idstring", func(t *testing.T) { - d, err := Parse("did:a:123?abc") - assert(t, nil, err) - assert(t, "a", d.Method) - assert(t, "123", d.ID) - assert(t, "abc", d.Query) - }) - - t.Run("succeeds to extract query after path", func(t *testing.T) { - d, err := Parse("did:a:123/a/b/c?abc") - assert(t, nil, err) - assert(t, "a", d.Method) - assert(t, "123", d.ID) - assert(t, "a/b/c", d.Path) - assert(t, "abc", d.Query) - }) - - t.Run("succeeds to extract fragment after query", func(t *testing.T) { - d, err := Parse("did:a:123?abc#xyz") - assert(t, nil, err) - assert(t, "abc", d.Query) - assert(t, "xyz", d.Fragment) - }) - - t.Run("succeeds with percent encoded chars in query", func(t *testing.T) { - d, err := Parse("did:a:123?ab%20c") - assert(t, nil, err) - assert(t, "ab%20c", d.Query) - }) - - t.Run("returns error if % in query is not followed by 2 hex chars", func(t *testing.T) { - dids := []string{ - "did:a:123:456?%", - "did:a:123:456?%a", - "did:a:123:456?%!*", - "did:a:123:456?%A!", - "did:xyz:pqr?%A!", - "did:a:123:456?%A%", - } - for _, did := range dids { - _, err := Parse(did) - assert(t, false, err == nil, "Input: %s", did) - } - }) - - t.Run("returns error if query has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456?ssss^sss") - assert(t, false, err == nil) - }) - - t.Run("succeeds to extract fragment", func(t *testing.T) { - d, err := Parse("did:a:123:456#keys-1") - assert(t, nil, err) - assert(t, "keys-1", d.Fragment) - }) - - t.Run("succeeds with percent encoded chars in fragment", func(t *testing.T) { - d, err := Parse("did:a:123:456#aaaaaa%20a") - assert(t, nil, err) - assert(t, "aaaaaa%20a", d.Fragment) - }) - - t.Run("returns error if % in fragment is not followed by 2 hex chars", func(t *testing.T) { - dids := []string{ - "did:xyz:pqr#%", - "did:xyz:pqr#%a", - "did:xyz:pqr#%!*", - "did:xyz:pqr#%!A", - "did:xyz:pqr#%A!", - "did:xyz:pqr#%A%", - } - for _, did := range dids { - _, err := Parse(did) - assert(t, false, err == nil, "Input: %s", did) - } - }) - - t.Run("fails if fragment has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456#ssss^sss") - assert(t, false, err == nil) - }) +func TestDID_UnmarshalJSON(t *testing.T) { + inBytes := `{"obj": "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` + id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") + require.NoError(t, err) + var obj struct { + Obj *DID `json:"obj"` + } + err = json.Unmarshal([]byte(inBytes), &obj) + require.NoError(t, err) + require.NotNil(t, obj.Obj) + require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) + + id2 := IDFromDID(*obj.Obj) + method, err := MethodFromID(id2) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id2) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id2) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) + + require.Equal(t, id, id2) } -func Test_errorf(t *testing.T) { - p := &parser{} - p.errorf(10, "%s,%s", "a", "b") - - if p.currentIndex != 10 { - t.Errorf("did not set currentIndex") +func TestDID_UnmarshalJSON_Error(t *testing.T) { + inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` + var obj struct { + Obj *DID `json:"obj"` } + err := json.Unmarshal([]byte(inBytes), &obj) + require.NoError(t, err) - e := p.err.Error() - if e != "a,b" { - t.Errorf("err message is: '%s' expected: 'a,b'", e) - } + //_, err = IDFromDID(*obj.Obj) + //require.EqualError(t, err, "invalid did format: blockchain mismatch: "+ + // "found polygon in ID but eth in DID") } -func Test_isNotValidParamChar(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '.', '-', '_', ':'} - for _, c := range a { - assert(t, false, isNotValidParamChar(c), "Input: '%c'", c) - } +func TestDIDGenesisFromState(t *testing.T) { - a = []byte{'%', '^', '#', ' ', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '@', '/', '?'} - for _, c := range a { - assert(t, true, isNotValidParamChar(c), "Input: '%c'", c) - } -} + typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) + require.NoError(t, err) -func Test_isNotValidIDChar(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '.', '-'} - for _, c := range a { - assert(t, false, isNotValidIDChar(c), "Input: '%c'", c) - } + genesisState := big.NewInt(1) + did, err := DIDGenesisFromIdenState(typ0, genesisState) + require.NoError(t, err) - a = []byte{'%', '^', '#', ' ', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@', '/', '?'} - for _, c := range a { - assert(t, true, isNotValidIDChar(c), "Input: '%c'", c) - } -} + require.Equal(t, string(DIDMethodIden3), did.Method) -func Test_isNotValidQueryOrFragmentChar(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', - ':', '@', - '/', '?'} - for _, c := range a { - assert(t, false, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) - } + id := IDFromDID(*did) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodIden3, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) - a = []byte{'%', '^', '#', ' '} - for _, c := range a { - assert(t, true, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) - } + require.Equal(t, + "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", + did.String()) } -func Test_isNotValidPathChar(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', - ':', '@'} - for _, c := range a { - assert(t, false, isNotValidPathChar(c), "Input: '%c'", c) - } +func TestDID_PolygonID_Types(t *testing.T) { + + // Polygon no chain, no network + did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) + + require.Equal(t, string(DIDMethodPolygonID), did.Method) + id := IDFromDID(*did) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, ReadOnly, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, NoNetwork, networkID) + require.Equal(t, + "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", + did.String()) + + // Polygon | Polygon chain, Main + did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) + + require.Equal(t, string(DIDMethodPolygonID), did2.Method) + id2 := IDFromDID(*did2) + method2, err := MethodFromID(id2) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method2) + blockchain2, err := BlockchainFromID(id2) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain2) + networkID2, err := NetworkIDFromID(id2) + require.NoError(t, err) + require.Equal(t, Main, networkID2) + require.Equal(t, + "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", + did2.String()) + + // Polygon | Polygon chain, Mumbai + did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) + + require.Equal(t, string(DIDMethodPolygonID), did3.Method) + id3 := IDFromDID(*did3) + method3, err := MethodFromID(id3) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method3) + blockchain3, err := BlockchainFromID(id3) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain3) + networkID3, err := NetworkIDFromID(id3) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID3) + require.Equal(t, + "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", + did3.String()) - a = []byte{'%', '/', '?'} - for _, c := range a { - assert(t, true, isNotValidPathChar(c), "Input: '%c'", c) - } } -func Test_isNotUnreservedOrSubdelim(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='} - for _, c := range a { - assert(t, false, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) - } - - a = []byte{'%', ':', '@', '/', '?'} - for _, c := range a { - assert(t, true, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) - } +func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { + id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + require.NoError(t, err) + + did1, err := ParseDIDFromID(id1) + require.NoError(t, err) + + var addressBytesExp [20]byte + _, err = hex.Decode(addressBytesExp[:], + []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) + require.NoError(t, err) + + require.Equal(t, string(DIDMethodPolygonID), did1.Method) + wantIDs := []string{"polygon", "mumbai", + "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} + require.Equal(t, wantIDs, did1.IDStrings) + id := IDFromDID(*did1) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) + require.Equal(t, true, id.IsOnChain()) + + addressBytes, err := id.EthAddress() + require.NoError(t, err) + require.Equal(t, addressBytesExp, addressBytes) + + require.Equal(t, + "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", + did1.String()) } -func Test_isNotHexDigit(t *testing.T) { - a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'} - for _, c := range a { - assert(t, false, isNotHexDigit(c), "Input: '%c'", c) - } +func TestDecompose(t *testing.T) { + s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" - a = []byte{'G', 'g', '%', '\x40', '\x47', '\x60', '\x67'} - for _, c := range a { - assert(t, true, isNotHexDigit(c), "Input: '%c'", c) - } -} + did3, err := Parse(s) + require.NoError(t, err) -func Test_isNotDigit(t *testing.T) { - a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} - for _, c := range a { - assert(t, false, isNotDigit(c), "Input: '%c'", c) - } + wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + require.NoError(t, err) - a = []byte{'A', 'a', '\x29', '\x40', '/'} - for _, c := range a { - assert(t, true, isNotDigit(c), "Input: '%c'", c) - } -} + id := IDFromDID(*did3) + require.Equal(t, wantID, id) -func Test_isNotAlpha(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} - for _, c := range a { - assert(t, false, isNotAlpha(c), "Input: '%c'", c) - } + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, DIDMethodPolygonID, method) - a = []byte{'\x40', '\x5B', '\x60', '\x7B', '0', '9', '-', '%'} - for _, c := range a { - assert(t, true, isNotAlpha(c), "Input: '%c'", c) - } + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, Polygon, blockchain) + + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, Mumbai, networkID) } -// nolint: dupl -// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it -func Test_isNotBigLetter(t *testing.T) { - a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} - for _, c := range a { - assert(t, false, isNotBigLetter(c), "Input: '%c'", c) - } +func helperBuildDIDFromType(t testing.TB, method DIDMethod, + blockchain Blockchain, network NetworkID) *DID { + t.Helper() - a = []byte{'\x40', '\x5B', 'a', 'z', '1', '9', '-', '%'} - for _, c := range a { - assert(t, true, isNotBigLetter(c), "Input: '%c'", c) - } -} + typ, err := BuildDIDType(method, blockchain, network) + require.NoError(t, err) -// nolint: dupl -// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it -func Test_isNotSmallLetter(t *testing.T) { - a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} - for _, c := range a { - assert(t, false, isNotSmallLetter(c), "Input: '%c'", c) - } + genesisState := big.NewInt(1) + did, err := DIDGenesisFromIdenState(typ, genesisState) + require.NoError(t, err) - a = []byte{'\x60', '\x7B', 'A', 'Z', '1', '9', '-', '%'} - for _, c := range a { - assert(t, true, isNotSmallLetter(c), "Input: '%c'", c) - } + return did } -func assert(t *testing.T, expected interface{}, actual interface{}, args ...interface{}) { - if !reflect.DeepEqual(expected, actual) { - argsLength := len(args) - var message string - - // if only one arg is present, treat it as the message - if argsLength == 1 { - message = args[0].(string) - } - - // if more than one arg is present, treat it as format, args (like Printf) - if argsLength > 1 { - message = fmt.Sprintf(args[0].(string), args[1:]...) - } - - // is message is not empty add some spacing - if message != "" { - message = "\t" + message + "\n\n" - } - - _, file, line, _ := runtime.Caller(1) - fmt.Printf("%s:%d:\n\tExpected: %#v\n\tActual: %#v\n%s", filepath.Base(file), line, expected, actual, message) - t.FailNow() - } +func TestNewIDFromDID(t *testing.T) { + did, err := Parse("did:something:x") + require.NoError(t, err) + id := newIDFromDID(*did) + wantID, err := hex.DecodeString( + "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") + require.NoError(t, err) + require.Equal(t, wantID, id[:]) + + id2 := IDFromDID(*did) + require.Equal(t, id, id2) } diff --git a/did_w3c.go b/did_w3c.go new file mode 100644 index 0000000..cda2441 --- /dev/null +++ b/did_w3c.go @@ -0,0 +1,818 @@ +// Package core is a set of tools to work with Decentralized Identifiers (DIDs) as described +// in the DID spec https://w3c.github.io/did-core/ +// Got from https://github.com/build-trust/did +package core + +import ( + "fmt" + "strings" +) + +// Param represents a parsed DID param, +// which contains a name and value. A generic param is defined +// as a param name and value separated by a colon. +// generic-param-name:param-value +// A param may also be method specific, which +// requires the method name to prefix the param name separated by a colon +// method-name:param-name. +// param = param-name [ "=" param-value ] +// https://w3c.github.io/did-core/#generic-did-parameter-names +// https://w3c.github.io/did-core/#method-specific-did-parameter-names +type Param struct { + // param-name = 1*param-char + // Name may include a method name and param name separated by a colon + Name string + // param-value = *param-char + Value string +} + +// String encodes a Param struct into a valid Param string. +// Name is required by the grammar. Value is optional +func (p *Param) String() string { + if p.Name == "" { + return "" + } + + if 0 < len(p.Value) { + return p.Name + "=" + p.Value + } + + return p.Name +} + +// A DID represents a parsed DID or a DID URL +type DID struct { + // DID Method + // https://w3c.github.io/did-core/#method-specific-syntax + Method string + + // The method-specific-id component of a DID + // method-specific-id = *idchar *( ":" *idchar ) + ID string + + // method-specific-id may be composed of multiple `:` separated idstrings + IDStrings []string + + // DID URL + // did-url = did *( ";" param ) path-abempty [ "?" query ] [ "#" fragment ] + // did-url may contain multiple params, a path, query, and fragment + Params []Param + + // DID Path, the portion of a DID reference that follows the first forward slash character. + // https://w3c.github.io/did-core/#path + Path string + + // Path may be composed of multiple `/` separated segments + // path-abempty = *( "/" segment ) + PathSegments []string + + // DID Query + // https://w3c.github.io/did-core/#query + // query = *( pchar / "/" / "?" ) + Query string + + // DID Fragment, the portion of a DID reference that follows the first hash sign character ("#") + // https://w3c.github.io/did-core/#fragment + Fragment string +} + +// the parsers internal state +type parser struct { + input string // input to the parser + currentIndex int // index in the input which the parser is currently processing + out *DID // the output DID that the parser will assemble as it steps through its state machine + err error // an error in the parser state machine +} + +// a step in the parser state machine that returns the next step +type parserStep func() parserStep + +// IsURL returns true if a DID has a Path, a Query or a Fragment +// https://w3c-ccg.github.io/did-spec/#dfn-did-reference +func (d *DID) IsURL() bool { + return (len(d.Params) > 0 || d.Path != "" || len(d.PathSegments) > 0 || d.Query != "" || d.Fragment != "") +} + +// String encodes a DID struct into a valid DID string. +// nolint: gocyclo +func (d *DID) String() string { + var buf strings.Builder + + // write the did: prefix + buf.WriteString("did:") // nolint, returned error is always nil + + if d.Method != "" { + // write method followed by a `:` + buf.WriteString(d.Method) // nolint, returned error is always nil + buf.WriteByte(':') // nolint, returned error is always nil + } else { + // if there is no Method, return an empty string + return "" + } + + if d.ID != "" { + buf.WriteString(d.ID) // nolint, returned error is always nil + } else if len(d.IDStrings) > 0 { + // join IDStrings with a colon to make the ID + buf.WriteString(strings.Join(d.IDStrings[:], ":")) // nolint, returned error is always nil + } else { + // if there is no ID, return an empty string + return "" + } + + if len(d.Params) > 0 { + // write a leading ; for each param + for _, p := range d.Params { + // get a string that represents the param + param := p.String() + if param != "" { + // params must start with a ; + buf.WriteByte(';') // nolint, returned error is always nil + buf.WriteString(param) // nolint, returned error is always nil + } else { + // if a param exists but is empty, return an empty string + return "" + } + } + } + + if d.Path != "" { + // write a leading / and then Path + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(d.Path) // nolint, returned error is always nil + } else if len(d.PathSegments) > 0 { + // write a leading / and then PathSegments joined with / between them + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(strings.Join(d.PathSegments[:], "/")) // nolint, returned error is always nil + } + + if d.Query != "" { + // write a leading ? and then Query + buf.WriteByte('?') // nolint, returned error is always nil + buf.WriteString(d.Query) // nolint, returned error is always nil + } + + if d.Fragment != "" { + // add fragment only when there is no path + buf.WriteByte('#') // nolint, returned error is always nil + buf.WriteString(d.Fragment) // nolint, returned error is always nil + } + + return buf.String() +} + +// Parse parses the input string into a DID structure. +func Parse(input string) (*DID, error) { + // intialize the parser state + p := &parser{input: input, out: &DID{}} + + // the parser state machine is implemented as a loop over parser steps + // steps increment p.currentIndex as they consume the input, each step returns the next step to run + // the state machine halts when one of the steps returns nil + // + // This design is based on this talk from Rob Pike, although the talk focuses on lexical scanning, + // the DID grammar is simple enough for us to combine lexing and parsing into one lexerless parse + // http://www.youtube.com/watch?v=HxaD_trXwRE + parserState := p.checkLength + for parserState != nil { + parserState = parserState() + } + + // If one of the steps added an err to the parser state, exit. Return nil and the error. + err := p.err + if err != nil { + return nil, err + } + + // join IDStrings with : to make up ID + p.out.ID = strings.Join(p.out.IDStrings[:], ":") + + // join PathSegments with / to make up Path + p.out.Path = strings.Join(p.out.PathSegments[:], "/") + + return p.out, nil +} + +// checkLength is a parserStep that checks if the input length is atleast 7 +// the grammar requires +// +// `did:` prefix (4 chars) +// + atleast one methodchar (1 char) +// + `:` (1 char) +// + atleast one idchar (1 char) +// +// i.e atleast 7 chars +// The current specification does not take a position on maximum length of a DID. +// https://w3c-ccg.github.io/did-spec/#upper-limits-on-did-character-length +func (p *parser) checkLength() parserStep { + inputLength := len(p.input) + + if inputLength < 7 { + return p.errorf(inputLength, "input length is less than 7") + } + + return p.parseScheme +} + +// parseScheme is a parserStep that validates that the input begins with 'did:' +func (p *parser) parseScheme() parserStep { + + currentIndex := 3 // 4 bytes in 'did:', i.e index 3 + + // the grammar requires `did:` prefix + if p.input[:currentIndex+1] != "did:" { + return p.errorf(currentIndex, "input does not begin with 'did:' prefix") + } + + p.currentIndex = currentIndex + return p.parseMethod +} + +// parseMethod is a parserStep that extracts the DID Method +// from the grammar: +// +// did = "did:" method ":" specific-idstring +// method = 1*methodchar +// methodchar = %x61-7A / DIGIT ; 61-7A is a-z in US-ASCII +func (p *parser) parseMethod() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + // parse method name + // loop over every byte following the ':' in 'did:' unlil the second ':' + // method is the string between the two ':'s + for { + if currentIndex == inputLength { + // we got to the end of the input and didn't find a second ':' + return p.errorf(currentIndex, "input does not have a second `:` marking end of method name") + } + + // read the input character at currentIndex + char := input[currentIndex] + + if char == ':' { + // we've found the second : in the input that marks the end of the method + if currentIndex == startIndex { + // return error is method is empty, ex- did::1234 + return p.errorf(currentIndex, "method is empty") + } + break + } + + // as per the grammar method can only be made of digits 0-9 or small letters a-z + if isNotDigit(char) && isNotSmallLetter(char) { + return p.errorf(currentIndex, "character is not a-z OR 0-9") + } + + // move to the next char + currentIndex = currentIndex + 1 + } + + // set parser state + p.currentIndex = currentIndex + p.out.Method = input[startIndex:currentIndex] + + // method is followed by specific-idstring, parse that next + return p.parseID +} + +// parseID is a parserStep that extracts : separated idstrings that are part of a specific-idstring +// and adds them to p.out.IDStrings +// from the grammar: +// +// specific-idstring = idstring *( ":" idstring ) +// idstring = 1*idchar +// idchar = ALPHA / DIGIT / "." / "-" +// +// p.out.IDStrings is later concatented by the Parse function before it returns. +func (p *parser) parseID() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var next parserStep + + for { + if currentIndex == inputLength { + // we've reached end of input, no next state + next = nil + break + } + + char := input[currentIndex] + + if char == ':' { + // encountered : input may have another idstring, parse ID again + next = p.parseID + break + } + + if char == ';' { + // encountered ; input may have a parameter, parse that next + next = p.parseParamName + break + } + + if char == '/' { + // encountered / input may have a path following specific-idstring, parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following specific-idstring, parse that next + next = p.parseQuery + break + } + + if char == '#' { + // encountered # input may have a fragment following specific-idstring, parse that next + next = p.parseFragment + break + } + + // make sure current char is a valid idchar + // idchar = ALPHA / DIGIT / "." / "-" + if isNotValidIDChar(char) { + return p.errorf(currentIndex, "byte is not ALPHA OR DIGIT OR '.' OR '-'") + } + + // move to the next char + currentIndex = currentIndex + 1 + } + + if currentIndex == startIndex { + // idstring length is zero + // from the grammar: + // idstring = 1*idchar + // return error because idstring is empty, ex- did:a::123:456 + return p.errorf(currentIndex, "idstring must be atleast one char long") + } + + // set parser state + p.currentIndex = currentIndex + p.out.IDStrings = append(p.out.IDStrings, input[startIndex:currentIndex]) + + // return the next parser step + return next +} + +// parseParamName is a parserStep that extracts a did-url param-name. +// A Param struct is created for each param name that is encountered. +// from the grammar: +// +// param = param-name [ "=" param-value ] +// param-name = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamName() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + if currentIndex == startIndex { + // param-name length is zero + // from the grammar: + // 1*param-char + // return error because param-name is empty, ex- did:a::123:456;param-name + return p.errorf(currentIndex, "Param name must be at least one char long") + } + + // Create a new param with the name + p.out.Params = append(p.out.Params, Param{Name: input[startIndex:currentIndex], Value: ""}) + + // return the next parser step + return next +} + +// parseParamValue is a parserStep that extracts a did-url param-value. +// A parsed Param value requires that a Param was previously created when parsing a param-name. +// from the grammar: +// +// param = param-name [ "=" param-value ] +// param-value = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamValue() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + // Get the last Param in the DID and append the value + // values may be empty according to the grammar- *param-char + p.out.Params[len(p.out.Params)-1].Value = input[startIndex:currentIndex] + + // return the next parser step + return next +} + +// paramTransition is a parserStep that extracts and transitions a param-name or +// param-value. +// nolint: gocyclo +func (p *parser) paramTransition() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached end of input, no next state + next = nil + break + } + + char := input[currentIndex] + + if char == ';' { + // encountered : input may have another param, parse paramName again + next = p.parseParamName + break + } + + // Separate steps for name and value? + if char == '=' { + // parse param value + next = p.parseParamValue + break + } + + if char == '/' { + // encountered / input may have a path following current param, parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following current param, parse that next + next = p.parseQuery + break + } + + if char == '#' { + // encountered # input may have a fragment following current param, parse that next + next = p.parseFragment + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not percent encoded + percentEncoded = false + indexIncrement = 1 + } + + // make sure current char is a valid param-char + // idchar = ALPHA / DIGIT / "." / "-" + if !percentEncoded && isNotValidParamChar(char) { + return p.errorf(currentIndex, "character is not allowed in param - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // set parser state + p.currentIndex = currentIndex + + return next +} + +// parsePath is a parserStep that extracts a DID Path from a DID Reference +// from the grammar: +// +// did-path = segment-nz *( "/" segment ) +// segment = *pchar +// segment-nz = 1*pchar +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +// +// nolint: gocyclo +func (p *parser) parsePath() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + next = nil + break + } + + char := input[currentIndex] + + if char == '/' { + // encountered / input may have another path segment, try to parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following path, parse that next + next = p.parseQuery + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + if !percentEncoded && isNotValidPathChar(char) { + return p.errorf(currentIndex, "character is not allowed in path") + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + if currentIndex == startIndex && len(p.out.PathSegments) == 0 { + // path segment length is zero + // first path segment must have atleast one character + // from the grammar + // did-path = segment-nz *( "/" segment ) + return p.errorf(currentIndex, "first path segment must have atleast one character") + } + + // update parser state + p.currentIndex = currentIndex + p.out.PathSegments = append(p.out.PathSegments, input[startIndex:currentIndex]) + + return next +} + +// parseQuery is a parserStep that extracts a DID Query from a DID Reference +// from the grammar: +// +// did-query = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +func (p *parser) parseQuery() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached the end of input + // it's ok for query to be empty, so we don't need a check for that + // did-query = *( pchar / "/" / "?" ) + break + } + + char := input[currentIndex] + + if char == '#' { + // encountered # input may have a fragment following the query, parse that next + next = p.parseFragment + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // did-query = *( pchar / "/" / "?" ) + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded + if !percentEncoded && isNotValidQueryOrFragmentChar(char) { + return p.errorf(currentIndex, "character is not allowed in query - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // update parser state + p.currentIndex = currentIndex + p.out.Query = input[startIndex:currentIndex] + + return next +} + +// parseFragment is a parserStep that extracts a DID Fragment from a DID Reference +// from the grammar: +// +// did-fragment = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// pct-encoded = "%" HEXDIG HEXDIG +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +func (p *parser) parseFragment() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + startIndex := currentIndex + + var indexIncrement int + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached the end of input + // it's ok for reference to be empty, so we don't need a check for that + // did-fragment = *( pchar / "/" / "?" ) + break + } + + char := input[currentIndex] + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not pecent encoded + percentEncoded = false + indexIncrement = 1 + } + + // did-fragment = *( pchar / "/" / "?" ) + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + // isNotValidQueryOrFragmentChar checks for all the valid chars except pct-encoded + if !percentEncoded && isNotValidQueryOrFragmentChar(char) { + return p.errorf(currentIndex, "character is not allowed in fragment - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // update parser state + p.currentIndex = currentIndex + p.out.Fragment = input[startIndex:currentIndex] + + // no more parsing needed after a fragment, + // cause the state machine to exit by returning nil + return nil +} + +// errorf is a parserStep that returns nil to cause the state machine to exit +// before returning it sets the currentIndex and err field in parser state +// other parser steps use this function to exit the state machine with an error +func (p *parser) errorf(index int, format string, args ...interface{}) parserStep { + p.currentIndex = index + p.err = fmt.Errorf(format, args...) + return nil +} + +// INLINABLE +// Calls to all functions below this point should be inlined by the go compiler +// See output of `go build -gcflags -m` to confirm + +// isNotValidIDChar returns true if a byte is not allowed in a ID +// from the grammar: +// +// idchar = ALPHA / DIGIT / "." / "-" +func isNotValidIDChar(char byte) bool { + return isNotAlpha(char) && isNotDigit(char) && char != '.' && char != '-' +} + +// isNotValidParamChar returns true if a byte is not allowed in a param-name +// or param-value from the grammar: +// +// idchar = ALPHA / DIGIT / "." / "-" / "_" / ":" +func isNotValidParamChar(char byte) bool { + return isNotAlpha(char) && isNotDigit(char) && + char != '.' && char != '-' && char != '_' && char != ':' +} + +// isNotValidQueryOrFragmentChar returns true if a byte is not allowed in a Fragment +// from the grammar: +// +// did-fragment = *( pchar / "/" / "?" ) +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// +// pct-encoded is not checked in this function +func isNotValidQueryOrFragmentChar(char byte) bool { + return isNotValidPathChar(char) && char != '/' && char != '?' +} + +// isNotValidPathChar returns true if a byte is not allowed in Path +// +// did-path = segment-nz *( "/" segment ) +// segment = *pchar +// segment-nz = 1*pchar +// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +// +// pct-encoded is not checked in this function +func isNotValidPathChar(char byte) bool { + return isNotUnreservedOrSubdelim(char) && char != ':' && char != '@' +} + +// isNotUnreservedOrSubdelim returns true if a byte is not unreserved or sub-delims +// from the grammar: +// +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +// +// https://tools.ietf.org/html/rfc3986#appendix-A +func isNotUnreservedOrSubdelim(char byte) bool { + switch char { + case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': + return false + default: + if isNotAlpha(char) && isNotDigit(char) { + return true + } + return false + } +} + +// isNotHexDigit returns true if a byte is not a digit between 0-9 or A-F or a-f +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotHexDigit(char byte) bool { + // '\x41' is A, '\x46' is F + // '\x61' is a, '\x66' is f + return isNotDigit(char) && (char < '\x41' || char > '\x46') && (char < '\x61' || char > '\x66') +} + +// isNotDigit returns true if a byte is not a digit between 0-9 +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotDigit(char byte) bool { + // '\x30' is digit 0, '\x39' is digit 9 + return (char < '\x30' || char > '\x39') +} + +// isNotAlpha returns true if a byte is not a big letter between A-Z or small letter between a-z +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotAlpha(char byte) bool { + return isNotSmallLetter(char) && isNotBigLetter(char) +} + +// isNotBigLetter returns true if a byte is not a big letter between A-Z +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotBigLetter(char byte) bool { + // '\x41' is big letter A, '\x5A' small letter Z + return (char < '\x41' || char > '\x5A') +} + +// isNotSmallLetter returns true if a byte is not a small letter between a-z +// in US-ASCII http://www.columbia.edu/kermit/ascii.html +// https://tools.ietf.org/html/rfc5234#appendix-B.1 +func isNotSmallLetter(char byte) bool { + // '\x61' is small letter a, '\x7A' small letter z + return (char < '\x61' || char > '\x7A') +} diff --git a/did_w3c_test.go b/did_w3c_test.go new file mode 100644 index 0000000..a48e701 --- /dev/null +++ b/did_w3c_test.go @@ -0,0 +1,765 @@ +// Got from https://github.com/build-trust/did +package core + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +func TestIsURL(t *testing.T) { + t.Run("returns false if no Path or Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123"} + assert(t, false, d.IsURL()) + }) + + t.Run("returns true if Params", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo", Value: "bar"}}} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if PathSegements", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Query", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, true, d.IsURL()) + }) + + t.Run("returns true if Path and Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b", Fragment: "00000"} + assert(t, true, d.IsURL()) + }) +} + +func TestString(t *testing.T) { + t.Run("assembles a DID", func(t *testing.T) { + d := &DID{Method: "example", ID: "123"} + assert(t, "did:example:123", d.String()) + }) + + t.Run("assembles a DID from IDStrings", func(t *testing.T) { + d := &DID{Method: "example", IDStrings: []string{"123", "456"}} + assert(t, "did:example:123:456", d.String()) + }) + + t.Run("returns empty string if no method", func(t *testing.T) { + d := &DID{ID: "123"} + assert(t, "", d.String()) + }) + + t.Run("returns empty string in no ID or IDStrings", func(t *testing.T) { + d := &DID{Method: "example"} + assert(t, "", d.String()) + }) + + t.Run("returns empty string if Param Name does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "", Value: "agent"}}} + assert(t, "", d.String()) + }) + + t.Run("returns name string if Param Value does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: ""}}} + assert(t, "did:example:123;service", d.String()) + }) + + t.Run("returns param string with name and value", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param generic", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;foo:bar=high", d.String()) + }) + + t.Run("includes Param generic and method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}, {Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;service=agent;foo:bar=high", d.String()) + }) + + t.Run("includes Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b"} + assert(t, "did:example:123/a/b", d.String()) + }) + + t.Run("includes Path assembled from PathSegements", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} + assert(t, "did:example:123/a/b", d.String()) + }) + + t.Run("includes Path after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}}, Path: "a/b"} + assert(t, "did:example:123;service=agent/a/b", d.String()) + }) + + t.Run("includes Query after IDString", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, "did:example:123?abc", d.String()) + }) + + t.Run("include Query after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent?abc", d.String()) + }) + + t.Run("includes Query after Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc"} + assert(t, "did:example:123/x/y?abc", d.String()) + }) + + t.Run("includes Query after Param and Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent/x/y?abc", d.String()) + }) + + t.Run("includes Query after before Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "zyx", Query: "abc"} + assert(t, "did:example:123?abc#zyx", d.String()) + }) + + t.Run("includes Query", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc"} + assert(t, "did:example:123?abc", d.String()) + }) + + t.Run("includes Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, "did:example:123#00000", d.String()) + }) + + t.Run("includes Fragment after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, "did:example:123#00000", d.String()) + }) +} + +func TestParse(t *testing.T) { + + t.Run("returns error if input is empty", func(t *testing.T) { + _, err := Parse("") + assert(t, false, err == nil) + }) + + t.Run("returns error if input length is less than length 7", func(t *testing.T) { + _, err := Parse("did:") + assert(t, false, err == nil) + + _, err = Parse("did:a") + assert(t, false, err == nil) + + _, err = Parse("did:a:") + assert(t, false, err == nil) + }) + + t.Run("returns error if input does not have a second : to mark end of method", func(t *testing.T) { + _, err := Parse("did:aaaaaaaaaaa") + assert(t, false, err == nil) + }) + + t.Run("returns error if method is empty", func(t *testing.T) { + _, err := Parse("did::aaaaaaaaaaa") + assert(t, false, err == nil) + }) + + t.Run("returns error if idstring is empty", func(t *testing.T) { + dids := []string{ + "did:a::123:456", + "did:a:123::456", + "did:a:123:456:", + "did:a:123:/abc", + "did:a:123:#abc", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if input does not begin with did: scheme", func(t *testing.T) { + _, err := Parse("a:12345") + assert(t, false, err == nil) + }) + + t.Run("returned value is nil if input does not begin with did: scheme", func(t *testing.T) { + d, _ := Parse("a:12345") + assert(t, true, d == nil) + }) + + t.Run("succeeds if it has did prefix and length is greater than 7", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, true, d != nil) + }) + + t.Run("succeeds to extract method", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, "a", d.Method) + + d, err = Parse("did:abcdef:11111") + assert(t, nil, err) + assert(t, "abcdef", d.Method) + }) + + t.Run("returns error if method has any other char than 0-9 or a-z", func(t *testing.T) { + _, err := Parse("did:aA:1") + assert(t, false, err == nil) + + _, err = Parse("did:aa-aa:1") + assert(t, false, err == nil) + }) + + t.Run("succeeds to extract id", func(t *testing.T) { + d, err := Parse("did:a:1") + assert(t, nil, err) + assert(t, "1", d.ID) + }) + + t.Run("succeeds to extract id parts", func(t *testing.T) { + d, err := Parse("did:a:123:456") + assert(t, nil, err) + + parts := d.IDStrings + assert(t, "123", parts[0]) + assert(t, "456", parts[1]) + }) + + t.Run("returns error if ID has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:1&&111") + assert(t, false, err == nil) + }) + + t.Run("returns error if param name is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456;") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;serv&ce") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param value has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;service=ag&nt") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid percent encoded", func(t *testing.T) { + _, err := Parse("did:a:123:456;ser%2ge") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param does not exist for value", func(t *testing.T) { + _, err := Parse("did:a:123:456;=value") + assert(t, false, err == nil) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==agent") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=agent", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;service") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only and empty param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar=baz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar=baz", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "baz", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds with percent encoded chars in param name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;serv%20ice=val%20ue") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "serv%20ice=val%20ue", d.Params[0].String()) + assert(t, "serv%20ice", d.Params[0].Name) + assert(t, "val%20ue", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple generic params with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo;bar") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "foo", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + assert(t, "bar", d.Params[1].Name) + assert(t, "", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple params with names and values", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=agent;foo:bar=baz") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + assert(t, "foo:bar", d.Params[1].Name) + assert(t, "baz", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==value/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param name and no value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract query after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value?abc") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "abc", d.Query) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract fragment after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value#xyz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "xyz", d.Fragment) + }) + + t.Run("succeeds to extract path", func(t *testing.T) { + d, err := Parse("did:a:123:456/someService") + assert(t, nil, err) + assert(t, "someService", d.Path) + }) + + t.Run("succeeds to extract path segements", func(t *testing.T) { + d, err := Parse("did:a:123:456/a/b") + assert(t, nil, err) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + t.Run("succeeds with percent encoded chars in path", func(t *testing.T) { + d, err := Parse("did:a:123:456/a/%20a") + assert(t, nil, err) + assert(t, "a/%20a", d.Path) + }) + + t.Run("returns error if % in path is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:a:123:456/%", + "did:a:123:456/%a", + "did:a:123:456/%!*", + "did:a:123:456/%A!", + "did:xyz:pqr#%A!", + "did:a:123:456/%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if path is empty but there is a slash", func(t *testing.T) { + _, err := Parse("did:a:123:456/") + assert(t, false, err == nil) + }) + + t.Run("returns error if first path segment is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456//abc") + assert(t, false, err == nil) + }) + + t.Run("does not fail if second path segment is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456/abc//pqr") + assert(t, nil, err) + }) + + t.Run("returns error if path has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456/ssss^sss") + assert(t, false, err == nil) + }) + + t.Run("does not fail if path has atleast one segment and a trailing slash", func(t *testing.T) { + _, err := Parse("did:a:123:456/a/b/") + assert(t, nil, err) + }) + + t.Run("succeeds to extract query after idstring", func(t *testing.T) { + d, err := Parse("did:a:123?abc") + assert(t, nil, err) + assert(t, "a", d.Method) + assert(t, "123", d.ID) + assert(t, "abc", d.Query) + }) + + t.Run("succeeds to extract query after path", func(t *testing.T) { + d, err := Parse("did:a:123/a/b/c?abc") + assert(t, nil, err) + assert(t, "a", d.Method) + assert(t, "123", d.ID) + assert(t, "a/b/c", d.Path) + assert(t, "abc", d.Query) + }) + + t.Run("succeeds to extract fragment after query", func(t *testing.T) { + d, err := Parse("did:a:123?abc#xyz") + assert(t, nil, err) + assert(t, "abc", d.Query) + assert(t, "xyz", d.Fragment) + }) + + t.Run("succeeds with percent encoded chars in query", func(t *testing.T) { + d, err := Parse("did:a:123?ab%20c") + assert(t, nil, err) + assert(t, "ab%20c", d.Query) + }) + + t.Run("returns error if % in query is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:a:123:456?%", + "did:a:123:456?%a", + "did:a:123:456?%!*", + "did:a:123:456?%A!", + "did:xyz:pqr?%A!", + "did:a:123:456?%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("returns error if query has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456?ssss^sss") + assert(t, false, err == nil) + }) + + t.Run("succeeds to extract fragment", func(t *testing.T) { + d, err := Parse("did:a:123:456#keys-1") + assert(t, nil, err) + assert(t, "keys-1", d.Fragment) + }) + + t.Run("succeeds with percent encoded chars in fragment", func(t *testing.T) { + d, err := Parse("did:a:123:456#aaaaaa%20a") + assert(t, nil, err) + assert(t, "aaaaaa%20a", d.Fragment) + }) + + t.Run("returns error if % in fragment is not followed by 2 hex chars", func(t *testing.T) { + dids := []string{ + "did:xyz:pqr#%", + "did:xyz:pqr#%a", + "did:xyz:pqr#%!*", + "did:xyz:pqr#%!A", + "did:xyz:pqr#%A!", + "did:xyz:pqr#%A%", + } + for _, did := range dids { + _, err := Parse(did) + assert(t, false, err == nil, "Input: %s", did) + } + }) + + t.Run("fails if fragment has invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456#ssss^sss") + assert(t, false, err == nil) + }) +} + +func Test_errorf(t *testing.T) { + p := &parser{} + p.errorf(10, "%s,%s", "a", "b") + + if p.currentIndex != 10 { + t.Errorf("did not set currentIndex") + } + + e := p.err.Error() + if e != "a,b" { + t.Errorf("err message is: '%s' expected: 'a,b'", e) + } +} + +func Test_isNotValidParamChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', '-', '_', ':'} + for _, c := range a { + assert(t, false, isNotValidParamChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' ', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidParamChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidIDChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', '-'} + for _, c := range a { + assert(t, false, isNotValidIDChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' ', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidIDChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidQueryOrFragmentChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + ':', '@', + '/', '?'} + for _, c := range a { + assert(t, false, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' '} + for _, c := range a { + assert(t, true, isNotValidQueryOrFragmentChar(c), "Input: '%c'", c) + } +} + +func Test_isNotValidPathChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + ':', '@'} + for _, c := range a { + assert(t, false, isNotValidPathChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidPathChar(c), "Input: '%c'", c) + } +} + +func Test_isNotUnreservedOrSubdelim(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='} + for _, c := range a { + assert(t, false, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) + } + + a = []byte{'%', ':', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotUnreservedOrSubdelim(c), "Input: '%c'", c) + } +} + +func Test_isNotHexDigit(t *testing.T) { + a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'} + for _, c := range a { + assert(t, false, isNotHexDigit(c), "Input: '%c'", c) + } + + a = []byte{'G', 'g', '%', '\x40', '\x47', '\x60', '\x67'} + for _, c := range a { + assert(t, true, isNotHexDigit(c), "Input: '%c'", c) + } +} + +func Test_isNotDigit(t *testing.T) { + a := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} + for _, c := range a { + assert(t, false, isNotDigit(c), "Input: '%c'", c) + } + + a = []byte{'A', 'a', '\x29', '\x40', '/'} + for _, c := range a { + assert(t, true, isNotDigit(c), "Input: '%c'", c) + } +} + +func Test_isNotAlpha(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} + for _, c := range a { + assert(t, false, isNotAlpha(c), "Input: '%c'", c) + } + + a = []byte{'\x40', '\x5B', '\x60', '\x7B', '0', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotAlpha(c), "Input: '%c'", c) + } +} + +// nolint: dupl +// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it +func Test_isNotBigLetter(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} + for _, c := range a { + assert(t, false, isNotBigLetter(c), "Input: '%c'", c) + } + + a = []byte{'\x40', '\x5B', 'a', 'z', '1', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotBigLetter(c), "Input: '%c'", c) + } +} + +// nolint: dupl +// Test_isNotSmallLetter and Test_isNotBigLetter look too similar to the dupl linter, ignore it +func Test_isNotSmallLetter(t *testing.T) { + a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} + for _, c := range a { + assert(t, false, isNotSmallLetter(c), "Input: '%c'", c) + } + + a = []byte{'\x60', '\x7B', 'A', 'Z', '1', '9', '-', '%'} + for _, c := range a { + assert(t, true, isNotSmallLetter(c), "Input: '%c'", c) + } +} + +func assert(t *testing.T, expected interface{}, actual interface{}, args ...interface{}) { + if !reflect.DeepEqual(expected, actual) { + argsLength := len(args) + var message string + + // if only one arg is present, treat it as the message + if argsLength == 1 { + message = args[0].(string) + } + + // if more than one arg is present, treat it as format, args (like Printf) + if argsLength > 1 { + message = fmt.Sprintf(args[0].(string), args[1:]...) + } + + // is message is not empty add some spacing + if message != "" { + message = "\t" + message + "\n\n" + } + + _, file, line, _ := runtime.Caller(1) + fmt.Printf("%s:%d:\n\tExpected: %#v\n\tActual: %#v\n%s", filepath.Base(file), line, expected, actual, message) + t.FailNow() + } +} From 7eb3c1bf5ff609341d4ad2106561efd4a0d9cf2c Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 1 May 2023 03:55:53 -0400 Subject: [PATCH 22/37] Make type DIDMethod uint instead of string. We need several types (DIDMethodPolygonID and DIDMethodPolygonIDOnChain) to resolve into same string. --- did.go | 84 +++++++++++++++++++++++++++++++++++++++-------------- did_test.go | 12 ++++---- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/did.go b/did.go index e6b75b6..0df5675 100644 --- a/did.go +++ b/did.go @@ -12,6 +12,8 @@ import ( var ( // ErrUnsupportedID ID with unsupported type. ErrUnsupportedID = errors.New("unsupported ID") + // ErrIncorrectDID return if DID method is known, but format of DID is incorrect. + ErrIncorrectDID = errors.New("incorrect DID") // ErrDIDMethodNotSupported unsupported did method. ErrDIDMethodNotSupported = errors.New("not supported did method") // ErrBlockchainNotSupportedForDID unsupported network for did. @@ -24,15 +26,40 @@ var ( const DIDSchema = "did" // DIDMethod represents did methods -type DIDMethod string +type DIDMethod uint8 const ( - // DIDMethodIden3 DID method-name - DIDMethodIden3 DIDMethod = "iden3" - // DIDMethodPolygonID DID method-name - DIDMethodPolygonID DIDMethod = "polygonid" + // DIDMethodIden3 + DIDMethodIden3 DIDMethod = iota + // DIDMethodPolygonID + DIDMethodPolygonID DIDMethod = iota + // DIDMethodPolygonIDOnChain + DIDMethodPolygonIDOnChain DIDMethod = iota + // DIDMethodOther any other method not listed before + DIDMethodOther DIDMethod = iota ) +var knownMethods = map[DIDMethod]struct{}{ + DIDMethodIden3: {}, + DIDMethodPolygonID: {}, + DIDMethodPolygonIDOnChain: {}, +} + +func (m DIDMethod) String() string { + switch m { + case DIDMethodIden3: + return "iden3" + case DIDMethodPolygonID: + return "polygonid" + case DIDMethodPolygonIDOnChain: + return "polygonid" + case DIDMethodOther: + return "" + default: + return fmt.Sprintf("unknown<%v>", uint8(m)) + } +} + // Blockchain id of the network "eth", "polygon", etc. type Blockchain string @@ -69,8 +96,10 @@ const ( // DIDMethodByte did method flag representation var DIDMethodByte = map[DIDMethod]byte{ - DIDMethodIden3: 0b00000001, - DIDMethodPolygonID: 0b00000010, + DIDMethodIden3: 0b00000001, + DIDMethodPolygonID: 0b00000010, + DIDMethodPolygonIDOnChain: 0b00000011, + DIDMethodOther: 0b11111111, } // DIDNetworkFlag is a structure to represent DID blockchain and network id @@ -99,6 +128,11 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, }, + DIDMethodPolygonIDOnChain: { + {Blockchain: Polygon, NetworkID: Main}: 0b10010000 | 0b00000001, + {Blockchain: Polygon, NetworkID: Mumbai}: 0b10010000 | 0b00000010, + }, + DIDMethodOther: {}, } // BuildDIDType builds bytes type from chain and network @@ -168,7 +202,7 @@ func FindDIDMethodByValue(_v byte) (DIDMethod, error) { return k, nil } } - return "", ErrDIDMethodNotSupported + return DIDMethodOther, ErrDIDMethodNotSupported } func (did *DID) UnmarshalJSON(bytes []byte) error { @@ -215,24 +249,32 @@ func newIDFromDID(did DID) ID { } func idFromDID(did DID) (ID, error) { - var id ID - _, ok := DIDMethodNetwork[DIDMethod(did.Method)] - if !ok { - return id, ErrUnsupportedID + found := false + for method := range knownMethods { + if method.String() == did.Method { + found = true + break + } } + if !found { + return ID{}, ErrUnsupportedID + } + + var id ID if len(did.IDStrings) > 3 || len(did.IDStrings) < 1 { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: unexpected number of ID strings", + ErrIncorrectDID) } var err error id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) if err != nil { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: can't parse ID string", ErrIncorrectDID) } if !CheckChecksum(id) { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: incorrect ID checksum", ErrIncorrectDID) } method, blockchain, networkID, err := decodeDIDPartsFromID(id) @@ -240,7 +282,7 @@ func idFromDID(did DID) (ID, error) { return id, ErrUnsupportedID } - if string(method) != did.Method { + if method.String() != did.Method { return id, ErrUnsupportedID } @@ -271,7 +313,7 @@ func ParseDIDFromID(id ID) (*DID, error) { return nil, err } - didParts := []string{DIDSchema, string(method), string(blockchain)} + didParts := []string{DIDSchema, method.String(), string(blockchain)} if string(networkID) != "" { didParts = append(didParts, string(networkID)) } @@ -293,17 +335,17 @@ func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { method, err := FindDIDMethodByValue(methodByte) if err != nil { - return "", UnknownChain, UnknownNetwork, err + return DIDMethodOther, UnknownChain, UnknownNetwork, err } blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) if err != nil { - return "", UnknownChain, UnknownNetwork, err + return DIDMethodOther, UnknownChain, UnknownNetwork, err } networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) if err != nil { - return "", UnknownChain, UnknownNetwork, err + return DIDMethodOther, UnknownChain, UnknownNetwork, err } return method, blockchain, networkID, nil @@ -311,7 +353,7 @@ func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { func MethodFromID(id ID) (DIDMethod, error) { if id.IsUnknown() { - return "", fmt.Errorf("%w: unknown type", ErrUnsupportedID) + return DIDMethodOther, fmt.Errorf("%w: unknown type", ErrUnsupportedID) } methodByte := id.MethodByte() return FindDIDMethodByValue(methodByte) diff --git a/did_test.go b/did_test.go index 611cec5..f854e95 100644 --- a/did_test.go +++ b/did_test.go @@ -73,7 +73,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) require.NotNil(t, obj.Obj) - require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) + require.Equal(t, DIDMethodIden3.String(), obj.Obj.Method) id2 := IDFromDID(*obj.Obj) method, err := MethodFromID(id2) @@ -111,7 +111,7 @@ func TestDIDGenesisFromState(t *testing.T) { did, err := DIDGenesisFromIdenState(typ0, genesisState) require.NoError(t, err) - require.Equal(t, string(DIDMethodIden3), did.Method) + require.Equal(t, DIDMethodIden3.String(), did.Method) id := IDFromDID(*did) method, err := MethodFromID(id) @@ -134,7 +134,7 @@ func TestDID_PolygonID_Types(t *testing.T) { // Polygon no chain, no network did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - require.Equal(t, string(DIDMethodPolygonID), did.Method) + require.Equal(t, DIDMethodPolygonID.String(), did.Method) id := IDFromDID(*did) method, err := MethodFromID(id) require.NoError(t, err) @@ -152,7 +152,7 @@ func TestDID_PolygonID_Types(t *testing.T) { // Polygon | Polygon chain, Main did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) - require.Equal(t, string(DIDMethodPolygonID), did2.Method) + require.Equal(t, DIDMethodPolygonID.String(), did2.Method) id2 := IDFromDID(*did2) method2, err := MethodFromID(id2) require.NoError(t, err) @@ -170,7 +170,7 @@ func TestDID_PolygonID_Types(t *testing.T) { // Polygon | Polygon chain, Mumbai did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) - require.Equal(t, string(DIDMethodPolygonID), did3.Method) + require.Equal(t, DIDMethodPolygonID.String(), did3.Method) id3 := IDFromDID(*did3) method3, err := MethodFromID(id3) require.NoError(t, err) @@ -199,7 +199,7 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) - require.Equal(t, string(DIDMethodPolygonID), did1.Method) + require.Equal(t, DIDMethodPolygonID.String(), did1.Method) wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) From 3e3ad4829f8024d422ea5e6d6b20eceef151dd40 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 1 May 2023 04:35:51 -0400 Subject: [PATCH 23/37] Refactor working with method byte, remove some redundant ID methods. --- did.go | 88 +++++++++++++++++++++++------------------------------ did_test.go | 13 ++++---- id.go | 24 ++------------- 3 files changed, 46 insertions(+), 79 deletions(-) diff --git a/did.go b/did.go index 0df5675..c021e25 100644 --- a/did.go +++ b/did.go @@ -26,17 +26,17 @@ var ( const DIDSchema = "did" // DIDMethod represents did methods -type DIDMethod uint8 +type DIDMethod byte const ( // DIDMethodIden3 - DIDMethodIden3 DIDMethod = iota + DIDMethodIden3 DIDMethod = 0b00000001 // DIDMethodPolygonID - DIDMethodPolygonID DIDMethod = iota + DIDMethodPolygonID DIDMethod = 0b00000010 // DIDMethodPolygonIDOnChain - DIDMethodPolygonIDOnChain DIDMethod = iota + DIDMethodPolygonIDOnChain DIDMethod = 0b10000010 // DIDMethodOther any other method not listed before - DIDMethodOther DIDMethod = iota + DIDMethodOther DIDMethod = 0b11111111 ) var knownMethods = map[DIDMethod]struct{}{ @@ -45,6 +45,10 @@ var knownMethods = map[DIDMethod]struct{}{ DIDMethodPolygonIDOnChain: {}, } +var onChainMethods = map[DIDMethod]struct{}{ + DIDMethodPolygonIDOnChain: {}, +} + func (m DIDMethod) String() string { switch m { case DIDMethodIden3: @@ -60,6 +64,10 @@ func (m DIDMethod) String() string { } } +func (m DIDMethod) Byte() byte { + return byte(m) +} + // Blockchain id of the network "eth", "polygon", etc. type Blockchain string @@ -94,14 +102,6 @@ const ( NoNetwork NetworkID = "" ) -// DIDMethodByte did method flag representation -var DIDMethodByte = map[DIDMethod]byte{ - DIDMethodIden3: 0b00000001, - DIDMethodPolygonID: 0b00000010, - DIDMethodPolygonIDOnChain: 0b00000011, - DIDMethodOther: 0b11111111, -} - // DIDNetworkFlag is a structure to represent DID blockchain and network id type DIDNetworkFlag struct { Blockchain Blockchain @@ -129,17 +129,17 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, }, DIDMethodPolygonIDOnChain: { - {Blockchain: Polygon, NetworkID: Main}: 0b10010000 | 0b00000001, - {Blockchain: Polygon, NetworkID: Mumbai}: 0b10010000 | 0b00000010, + {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, + {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, }, DIDMethodOther: {}, } // BuildDIDType builds bytes type from chain and network -func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { +func BuildDIDType(method DIDMethod, blockchain Blockchain, + network NetworkID) ([2]byte, error) { - fb, ok := DIDMethodByte[method] - if !ok { + if _, ok := knownMethods[method]; !ok { return [2]byte{}, ErrDIDMethodNotSupported } @@ -147,24 +147,13 @@ func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([ blockchain = ReadOnly } - sb, ok := DIDMethodNetwork[method][DIDNetworkFlag{Blockchain: blockchain, NetworkID: network}] + netFlag := DIDNetworkFlag{Blockchain: blockchain, NetworkID: network} + sb, ok := DIDMethodNetwork[method][netFlag] if !ok { return [2]byte{}, ErrNetworkNotSupportedForDID } - return [2]byte{fb, sb}, nil -} - -// BuildDIDTypeOnChain builds bytes type from chain and network -func BuildDIDTypeOnChain(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { - typ, err := BuildDIDType(method, blockchain, network) - if err != nil { - return [2]byte{}, err - } - // set on-chain flag (first bit of first byte) to 1 - typ[0] |= MethodOnChainFlag - - return typ, nil + return [2]byte{method.Byte(), sb}, nil } // FindNetworkIDForDIDMethodByValue finds network by byte value @@ -195,16 +184,6 @@ func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, e return UnknownChain, ErrBlockchainNotSupportedForDID } -// FindDIDMethodByValue finds did method by its byte value -func FindDIDMethodByValue(_v byte) (DIDMethod, error) { - for k, v := range DIDMethodByte { - if v == _v { - return k, nil - } - } - return DIDMethodOther, ErrDIDMethodNotSupported -} - func (did *DID) UnmarshalJSON(bytes []byte) error { var didStr string err := json.Unmarshal(bytes, &didStr) @@ -330,14 +309,9 @@ func ParseDIDFromID(id ID) (*DID, error) { } func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { - methodByte := id.MethodByte() + method := id.Method() networkByte := id.BlockchainNetworkByte() - method, err := FindDIDMethodByValue(methodByte) - if err != nil { - return DIDMethodOther, UnknownChain, UnknownNetwork, err - } - blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) if err != nil { return DIDMethodOther, UnknownChain, UnknownNetwork, err @@ -355,8 +329,12 @@ func MethodFromID(id ID) (DIDMethod, error) { if id.IsUnknown() { return DIDMethodOther, fmt.Errorf("%w: unknown type", ErrUnsupportedID) } - methodByte := id.MethodByte() - return FindDIDMethodByValue(methodByte) + method := id.Method() + if _, ok := knownMethods[method]; !ok && method != DIDMethodOther { + return DIDMethodOther, fmt.Errorf("%w: unknown method", + ErrUnsupportedID) + } + return method, nil } func BlockchainFromID(id ID) (Blockchain, error) { @@ -398,3 +376,13 @@ func NetworkIDFromID(id ID) (NetworkID, error) { return networkID, nil } + +func EthAddressFromID(id ID) ([20]byte, error) { + if _, ok := onChainMethods[id.Method()]; !ok { + return [20]byte{}, errors.New( + "can't get Ethereum address of not on-chain identity") + } + var address [20]byte + copy(address[:], id[2:22]) + return address, nil +} diff --git a/did_test.go b/did_test.go index f854e95..ef8a92b 100644 --- a/did_test.go +++ b/did_test.go @@ -47,7 +47,7 @@ func TestParseDID(t *testing.T) { require.NoError(t, err) require.Equal(t, NoNetwork, networkID) - require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) + require.Equal(t, [2]byte{DIDMethodIden3.Byte(), 0b0}, id.Type()) } func TestDID_MarshalJSON(t *testing.T) { @@ -199,25 +199,24 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID.String(), did1.Method) + require.Equal(t, DIDMethodPolygonIDOnChain.String(), did1.Method) wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) id := IDFromDID(*did1) method, err := MethodFromID(id) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) + require.Equal(t, DIDMethodPolygonIDOnChain, method) blockchain, err := BlockchainFromID(id) require.NoError(t, err) require.Equal(t, Polygon, blockchain) networkID, err := NetworkIDFromID(id) require.NoError(t, err) require.Equal(t, Mumbai, networkID) - require.Equal(t, true, id.IsOnChain()) - addressBytes, err := id.EthAddress() + ethAddr, err := EthAddressFromID(id) require.NoError(t, err) - require.Equal(t, addressBytesExp, addressBytes) + require.Equal(t, addressBytesExp, ethAddr) require.Equal(t, "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", @@ -238,7 +237,7 @@ func TestDecompose(t *testing.T) { method, err := MethodFromID(id) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) + require.Equal(t, DIDMethodPolygonIDOnChain, method) blockchain, err := BlockchainFromID(id) require.NoError(t, err) diff --git a/id.go b/id.go index fc1dac7..dfc130c 100644 --- a/id.go +++ b/id.go @@ -27,9 +27,6 @@ var ( TypeUnknown = [2]byte{0xff, 0xff} ) -// MethodOnChainFlag is a flag showing that identity is on-chain -const MethodOnChainFlag = 0b10000000 - const idLength = 31 // ID is a byte array with @@ -124,26 +121,14 @@ func (id *ID) Type() [2]byte { return typ } -func (id *ID) MethodByte() byte { - // remove on-chain flag - return id[0] & ^byte(MethodOnChainFlag) +func (id *ID) Method() DIDMethod { + return DIDMethod(id[0]) } func (id *ID) BlockchainNetworkByte() byte { return id[1] } -func (id *ID) IsOnChain() bool { - return !id.IsUnknown() && (id[0]&MethodOnChainFlag == MethodOnChainFlag) -} - -func (id *ID) EthAddress() ([20]byte, error) { - if !id.IsOnChain() { - return [20]byte{}, errors.New("can't get EthAddress of not on-chain identity") - } - return EthAddressFromID(*id), nil -} - func (id *ID) IsUnknown() bool { return bytes.Equal(id[0:2], TypeUnknown[:]) } @@ -264,11 +249,6 @@ func CheckGenesisStateID(id, state *big.Int) (bool, error) { return id.Cmp(identifier.BigInt()) == 0, nil } -func EthAddressFromID(id ID) (address [20]byte) { - copy(address[:], id[2:22]) - return -} - func GenesisFromEthAddress(address [20]byte) *big.Int { var genesis [32]byte copy(genesis[5:], address[:]) From 062cadc54cade5659cc47ea0a803d2d878f76ca1 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Mon, 1 May 2023 04:53:23 -0400 Subject: [PATCH 24/37] return error from IDFromDID if we know the DID method, but still can't parse ID --- did.go | 23 ++++++++++++++--------- did_test.go | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/did.go b/did.go index c021e25..b5937b9 100644 --- a/did.go +++ b/did.go @@ -14,6 +14,8 @@ var ( ErrUnsupportedID = errors.New("unsupported ID") // ErrIncorrectDID return if DID method is known, but format of DID is incorrect. ErrIncorrectDID = errors.New("incorrect DID") + // ErrMethodUnknown return if DID method is unknown. + ErrMethodUnknown = errors.New("unknown DID method") // ErrDIDMethodNotSupported unsupported did method. ErrDIDMethodNotSupported = errors.New("not supported did method") // ErrBlockchainNotSupportedForDID unsupported network for did. @@ -212,12 +214,12 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { return ParseDIDFromID(*id) } -func IDFromDID(did DID) ID { +func IDFromDID(did DID) (ID, error) { id, err := idFromDID(did) - if err != nil { - return newIDFromDID(did) + if errors.Is(err, ErrMethodUnknown) { + return newIDFromDID(did), nil } - return id + return id, err } func newIDFromDID(did DID) ID { @@ -236,7 +238,7 @@ func idFromDID(did DID) (ID, error) { } } if !found { - return ID{}, ErrUnsupportedID + return ID{}, ErrMethodUnknown } var id ID @@ -258,19 +260,22 @@ func idFromDID(did DID) (ID, error) { method, blockchain, networkID, err := decodeDIDPartsFromID(id) if err != nil { - return id, ErrUnsupportedID + return id, err } if method.String() != did.Method { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: methods in ID and DID are different", + ErrIncorrectDID) } if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: blockchains in ID and DID are different", + ErrIncorrectDID) } if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { - return id, ErrUnsupportedID + return id, fmt.Errorf("%w: networkIDs in ID and DID are different", + ErrIncorrectDID) } return id, nil diff --git a/did_test.go b/did_test.go index ef8a92b..2a4fda8 100644 --- a/did_test.go +++ b/did_test.go @@ -17,7 +17,8 @@ func TestParseDID(t *testing.T) { did3, err := Parse(didStr) require.NoError(t, err) - id := IDFromDID(*did3) + id, err := IDFromDID(*did3) + require.NoError(t, err) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) method, err := MethodFromID(id) require.NoError(t, err) @@ -35,7 +36,8 @@ func TestParseDID(t *testing.T) { did3, err = Parse(didStr) require.NoError(t, err) - id = IDFromDID(*did3) + id, err = IDFromDID(*did3) + require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) method, err = MethodFromID(id) require.NoError(t, err) @@ -75,7 +77,8 @@ func TestDID_UnmarshalJSON(t *testing.T) { require.NotNil(t, obj.Obj) require.Equal(t, DIDMethodIden3.String(), obj.Obj.Method) - id2 := IDFromDID(*obj.Obj) + id2, err := IDFromDID(*obj.Obj) + require.NoError(t, err) method, err := MethodFromID(id2) require.NoError(t, err) require.Equal(t, DIDMethodIden3, method) @@ -113,7 +116,8 @@ func TestDIDGenesisFromState(t *testing.T) { require.Equal(t, DIDMethodIden3.String(), did.Method) - id := IDFromDID(*did) + id, err := IDFromDID(*did) + require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) require.Equal(t, DIDMethodIden3, method) @@ -135,7 +139,8 @@ func TestDID_PolygonID_Types(t *testing.T) { did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) require.Equal(t, DIDMethodPolygonID.String(), did.Method) - id := IDFromDID(*did) + id, err := IDFromDID(*did) + require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) require.Equal(t, DIDMethodPolygonID, method) @@ -153,7 +158,8 @@ func TestDID_PolygonID_Types(t *testing.T) { did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) require.Equal(t, DIDMethodPolygonID.String(), did2.Method) - id2 := IDFromDID(*did2) + id2, err := IDFromDID(*did2) + require.NoError(t, err) method2, err := MethodFromID(id2) require.NoError(t, err) require.Equal(t, DIDMethodPolygonID, method2) @@ -171,7 +177,8 @@ func TestDID_PolygonID_Types(t *testing.T) { did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) require.Equal(t, DIDMethodPolygonID.String(), did3.Method) - id3 := IDFromDID(*did3) + id3, err := IDFromDID(*did3) + require.NoError(t, err) method3, err := MethodFromID(id3) require.NoError(t, err) require.Equal(t, DIDMethodPolygonID, method3) @@ -203,7 +210,8 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { wantIDs := []string{"polygon", "mumbai", "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} require.Equal(t, wantIDs, did1.IDStrings) - id := IDFromDID(*did1) + id, err := IDFromDID(*did1) + require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) require.Equal(t, DIDMethodPolygonIDOnChain, method) @@ -232,7 +240,8 @@ func TestDecompose(t *testing.T) { wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") require.NoError(t, err) - id := IDFromDID(*did3) + id, err := IDFromDID(*did3) + require.NoError(t, err) require.Equal(t, wantID, id) method, err := MethodFromID(id) @@ -271,6 +280,7 @@ func TestNewIDFromDID(t *testing.T) { require.NoError(t, err) require.Equal(t, wantID, id[:]) - id2 := IDFromDID(*did) + id2, err := IDFromDID(*did) + require.NoError(t, err) require.Equal(t, id, id2) } From 08d238a62d518468ca8189eaecef0ce2a9d249ac Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Tue, 2 May 2023 15:09:01 -0400 Subject: [PATCH 25/37] Getting rid of unknown ID type --- did.go | 86 ++++++++++++++++++++++++++++++----------------------- did_test.go | 3 +- id.go | 15 ---------- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/did.go b/did.go index b5937b9..ca66830 100644 --- a/did.go +++ b/did.go @@ -134,7 +134,9 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, }, - DIDMethodOther: {}, + DIDMethodOther: { + {Blockchain: UnknownChain, NetworkID: UnknownNetwork}: 0b11111111, + }, } // BuildDIDType builds bytes type from chain and network @@ -217,16 +219,21 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { func IDFromDID(did DID) (ID, error) { id, err := idFromDID(did) if errors.Is(err, ErrMethodUnknown) { - return newIDFromDID(did), nil + return newIDFromUnsupportedDID(did), nil } return id, err } -func newIDFromDID(did DID) ID { +func newIDFromUnsupportedDID(did DID) ID { hash := sha256.Sum256([]byte(did.String())) var genesis [27]byte copy(genesis[:], hash[len(hash)-27:]) - return NewID(TypeUnknown, genesis) + flg := DIDNetworkFlag{Blockchain: UnknownChain, NetworkID: UnknownNetwork} + var tp = [2]byte{ + DIDMethodOther.Byte(), + DIDMethodNetwork[DIDMethodOther][flg], + } + return NewID(tp, genesis) } func idFromDID(did DID) (ID, error) { @@ -284,10 +291,6 @@ func idFromDID(did DID) (ID, error) { // ParseDIDFromID returns DID from ID func ParseDIDFromID(id ID) (*DID, error) { - if id.IsUnknown() { - return nil, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - if !CheckChecksum(id) { return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) } @@ -297,6 +300,11 @@ func ParseDIDFromID(id ID) (*DID, error) { return nil, err } + if isUnsupportedDID(method, blockchain, networkID) { + return nil, fmt.Errorf("%w: unsupported DID", + ErrMethodUnknown) + } + didParts := []string{DIDSchema, method.String(), string(blockchain)} if string(networkID) != "" { didParts = append(didParts, string(networkID)) @@ -314,8 +322,8 @@ func ParseDIDFromID(id ID) (*DID, error) { } func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { - method := id.Method() - networkByte := id.BlockchainNetworkByte() + method := DIDMethod(id[0]) + networkByte := id[1] blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) if err != nil { @@ -331,59 +339,54 @@ func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { } func MethodFromID(id ID) (DIDMethod, error) { - if id.IsUnknown() { - return DIDMethodOther, fmt.Errorf("%w: unknown type", ErrUnsupportedID) + method, blockchain, netID, err := decodeDIDPartsFromID(id) + if err != nil { + return DIDMethodOther, err } - method := id.Method() - if _, ok := knownMethods[method]; !ok && method != DIDMethodOther { - return DIDMethodOther, fmt.Errorf("%w: unknown method", - ErrUnsupportedID) + + if isUnsupportedDID(method, blockchain, netID) { + return DIDMethodOther, fmt.Errorf("%w: unsupported DID", + ErrMethodUnknown) } + return method, nil } func BlockchainFromID(id ID) (Blockchain, error) { - if id.IsUnknown() { - return UnknownChain, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - - method, err := MethodFromID(id) + method, blockchain, netID, err := decodeDIDPartsFromID(id) if err != nil { return UnknownChain, err } - networkByte := id.BlockchainNetworkByte() - - blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) - if err != nil { - return UnknownChain, err + if isUnsupportedDID(method, blockchain, netID) { + return UnknownChain, fmt.Errorf("%w: unsupported DID", + ErrMethodUnknown) } return blockchain, nil } func NetworkIDFromID(id ID) (NetworkID, error) { - if id.IsUnknown() { - return UnknownNetwork, fmt.Errorf("%w: unknown type", ErrUnsupportedID) - } - - method, err := MethodFromID(id) + method, blockchain, netID, err := decodeDIDPartsFromID(id) if err != nil { return UnknownNetwork, err } - networkByte := id.BlockchainNetworkByte() - - networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) - if err != nil { - return UnknownNetwork, err + if isUnsupportedDID(method, blockchain, netID) { + return UnknownNetwork, fmt.Errorf("%w: unsupported DID", + ErrMethodUnknown) } - return networkID, nil + return netID, nil } func EthAddressFromID(id ID) ([20]byte, error) { - if _, ok := onChainMethods[id.Method()]; !ok { + method, err := MethodFromID(id) + if err != nil { + return [20]byte{}, err + } + + if _, ok := onChainMethods[method]; !ok { return [20]byte{}, errors.New( "can't get Ethereum address of not on-chain identity") } @@ -391,3 +394,10 @@ func EthAddressFromID(id ID) ([20]byte, error) { copy(address[:], id[2:22]) return address, nil } + +func isUnsupportedDID(method DIDMethod, blockchain Blockchain, + networkID NetworkID) bool { + + return method == DIDMethodOther && blockchain == UnknownChain && + networkID == UnknownNetwork +} diff --git a/did_test.go b/did_test.go index 2a4fda8..9283741 100644 --- a/did_test.go +++ b/did_test.go @@ -274,7 +274,8 @@ func helperBuildDIDFromType(t testing.TB, method DIDMethod, func TestNewIDFromDID(t *testing.T) { did, err := Parse("did:something:x") require.NoError(t, err) - id := newIDFromDID(*did) + id := newIDFromUnsupportedDID(*did) + require.Equal(t, []byte{0xff, 0xff}, id[:2]) wantID, err := hex.DecodeString( "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") require.NoError(t, err) diff --git a/id.go b/id.go index dfc130c..8e4c354 100644 --- a/id.go +++ b/id.go @@ -22,9 +22,6 @@ var ( // - 4-7 bits of 2nd byte: network id e.g. 0010 - mumbai // example of 2nd byte: 00010010 - polygon mumbai, 00000000 - readonly identities. // valid iden3 method {0b00000001,0b00010010}, readonly {0b00000001, 0b00000000} - - // TypeUnknown specifies that ID represents did of unsupported method - TypeUnknown = [2]byte{0xff, 0xff} ) const idLength = 31 @@ -121,18 +118,6 @@ func (id *ID) Type() [2]byte { return typ } -func (id *ID) Method() DIDMethod { - return DIDMethod(id[0]) -} - -func (id *ID) BlockchainNetworkByte() byte { - return id[1] -} - -func (id *ID) IsUnknown() bool { - return bytes.Equal(id[0:2], TypeUnknown[:]) -} - // IDFromString returns the ID from a given string func IDFromString(s string) (ID, error) { b, err := base58.Decode(s) From 9187aa1daf474904c93b3aed28659f5863ba7bb8 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Wed, 3 May 2023 13:41:17 -0400 Subject: [PATCH 26/37] Fix encoding ethereum address to/from genesis --- did.go | 16 +++++++++++++--- did_test.go | 34 ++++++++++++++++++++++++++++++++++ id.go | 23 ++++++++++------------- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/did.go b/did.go index ca66830..8aa0f2c 100644 --- a/did.go +++ b/did.go @@ -216,6 +216,10 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { return ParseDIDFromID(*id) } +func DIDFromGenesis(typ [2]byte, genesis [genesisLn]byte) (*DID, error) { + return ParseDIDFromID(NewID(typ, genesis)) +} + func IDFromDID(did DID) (ID, error) { id, err := idFromDID(did) if errors.Is(err, ErrMethodUnknown) { @@ -226,8 +230,8 @@ func IDFromDID(did DID) (ID, error) { func newIDFromUnsupportedDID(did DID) ID { hash := sha256.Sum256([]byte(did.String())) - var genesis [27]byte - copy(genesis[:], hash[len(hash)-27:]) + var genesis [genesisLn]byte + copy(genesis[:], hash[len(hash)-genesisLn:]) flg := DIDNetworkFlag{Blockchain: UnknownChain, NetworkID: UnknownNetwork} var tp = [2]byte{ DIDMethodOther.Byte(), @@ -391,10 +395,16 @@ func EthAddressFromID(id ID) ([20]byte, error) { "can't get Ethereum address of not on-chain identity") } var address [20]byte - copy(address[:], id[2:22]) + copy(address[:], id[2+7:]) return address, nil } +func GenesisFromEthAddress(addr [20]byte) [genesisLn]byte { + var genesis [genesisLn]byte + copy(genesis[7:], addr[:]) + return genesis +} + func isUnsupportedDID(method DIDMethod, blockchain Blockchain, networkID NetworkID) bool { diff --git a/did_test.go b/did_test.go index 9283741..b255edb 100644 --- a/did_test.go +++ b/did_test.go @@ -285,3 +285,37 @@ func TestNewIDFromDID(t *testing.T) { require.NoError(t, err) require.Equal(t, id, id2) } + +func TestGenesisFromEthAddress(t *testing.T) { + + ethAddrHex := "accb91a7d1d9ad0d33b83f2546ed30285c836c6e" + wantGenesisHex := "00000000000000accb91a7d1d9ad0d33b83f2546ed30285c836c6e" + require.Len(t, ethAddrHex, 20*2) + require.Len(t, wantGenesisHex, 27*2) + + ethAddrBytes, err := hex.DecodeString(ethAddrHex) + require.NoError(t, err) + var ethAddr [20]byte + copy(ethAddr[:], ethAddrBytes) + + genesis := GenesisFromEthAddress(ethAddr) + wantGenesis, err := hex.DecodeString(wantGenesisHex) + require.NoError(t, err) + require.Equal(t, wantGenesis, genesis[:]) + + tp2, err := BuildDIDType(DIDMethodPolygonIDOnChain, Polygon, Mumbai) + require.NoError(t, err) + + id := NewID(tp2, genesis) + ethAddr2, err := EthAddressFromID(id) + require.NoError(t, err) + require.Equal(t, ethAddr, ethAddr2) + t.Log(hex.EncodeToString(ethAddr2[:])) + + var wantID ID + copy(wantID[:], tp2[:]) + copy(wantID[len(tp2):], genesis[:]) + ch := CalculateChecksum(tp2, genesis) + copy(wantID[len(tp2)+len(genesis):], ch[:]) + require.Equal(t, wantID, id) +} diff --git a/id.go b/id.go index 8e4c354..53affb9 100644 --- a/id.go +++ b/id.go @@ -25,6 +25,7 @@ var ( ) const idLength = 31 +const genesisLn = 27 // ID is a byte array with // [ type | root_genesis | checksum ] @@ -33,7 +34,7 @@ const idLength = 31 type ID [idLength]byte // NewID creates a new ID from a type and genesis -func NewID(typ [2]byte, genesis [27]byte) ID { +func NewID(typ [2]byte, genesis [genesisLn]byte) ID { checksum := CalculateChecksum(typ, genesis) var b ID copy(b[:2], typ[:]) @@ -59,8 +60,8 @@ func ProfileID(id ID, nonce *big.Int) (ID, error) { return ID{}, err } - var genesis [27]byte - copy(genesis[:], firstNBytes(hash, 27)) + var genesis [genesisLn]byte + copy(genesis[:], firstNBytes(hash, genesisLn)) return NewID(typ, genesis), nil } @@ -159,7 +160,9 @@ func IDFromInt(i *big.Int) (ID, error) { } // DecomposeID returns type, genesis and checksum from an ID -func DecomposeID(id ID) (typ [2]byte, genesis [27]byte, checksum [2]byte, err error) { +func DecomposeID(id ID) (typ [2]byte, genesis [genesisLn]byte, checksum [2]byte, + err error) { + copy(typ[:], id[:2]) copy(genesis[:], id[2:len(id)-2]) copy(checksum[:], id[len(id)-2:]) @@ -170,7 +173,7 @@ func DecomposeID(id ID) (typ [2]byte, genesis [27]byte, checksum [2]byte, err er // where checksum: // // hash( [type | root_genesis ] ) -func CalculateChecksum(typ [2]byte, genesis [27]byte) [2]byte { +func CalculateChecksum(typ [2]byte, genesis [genesisLn]byte) [2]byte { var toChecksum [29]byte copy(toChecksum[:], typ[:]) copy(toChecksum[2:], genesis[:]) @@ -201,7 +204,7 @@ func CheckChecksum(id ID) bool { func IdGenesisFromIdenState(typ [2]byte, //nolint:revive state *big.Int) (*ID, error) { - var idGenesisBytes [27]byte + var idGenesisBytes [genesisLn]byte idenStateData, err := NewElemBytesFromInt(state) if err != nil { @@ -209,7 +212,7 @@ func IdGenesisFromIdenState(typ [2]byte, //nolint:revive } // we take last 27 bytes, because of swapped endianness - copy(idGenesisBytes[:], idenStateData[len(idenStateData)-27:]) + copy(idGenesisBytes[:], idenStateData[len(idenStateData)-genesisLn:]) id := NewID(typ, idGenesisBytes) return &id, nil } @@ -233,9 +236,3 @@ func CheckGenesisStateID(id, state *big.Int) (bool, error) { return id.Cmp(identifier.BigInt()) == 0, nil } - -func GenesisFromEthAddress(address [20]byte) *big.Int { - var genesis [32]byte - copy(genesis[5:], address[:]) - return bytesToInt(genesis[:]) -} From 9f5944c75cef16478d952996cee7d657aaf85460 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Wed, 3 May 2023 14:35:11 -0400 Subject: [PATCH 27/37] move W3C did to deparate package --- did.go | 36 +++++-------------- did_w3c.go => did/did_w3c.go | 2 +- did_w3c_test.go => did/did_w3c_test.go | 2 +- did/json.go | 22 ++++++++++++ did_test.go | 49 +++++++++++++------------- 5 files changed, 58 insertions(+), 53 deletions(-) rename did_w3c.go => did/did_w3c.go (99%) rename did_w3c_test.go => did/did_w3c_test.go (99%) create mode 100644 did/json.go diff --git a/did.go b/did.go index 8aa0f2c..31d3ce0 100644 --- a/did.go +++ b/did.go @@ -2,11 +2,12 @@ package core import ( "crypto/sha256" - "encoding/json" "errors" "fmt" "math/big" "strings" + + didw3c "github.com/iden3/go-iden3-core/v2/did" ) var ( @@ -188,27 +189,8 @@ func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, e return UnknownChain, ErrBlockchainNotSupportedForDID } -func (did *DID) UnmarshalJSON(bytes []byte) error { - var didStr string - err := json.Unmarshal(bytes, &didStr) - if err != nil { - return err - } - - did3, err := Parse(didStr) - if err != nil { - return err - } - *did = *did3 - return nil -} - -func (did DID) MarshalJSON() ([]byte, error) { - return json.Marshal(did.String()) -} - // DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { +func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*didw3c.DID, error) { id, err := IdGenesisFromIdenState(typ, state) if err != nil { return nil, err @@ -216,11 +198,11 @@ func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*DID, error) { return ParseDIDFromID(*id) } -func DIDFromGenesis(typ [2]byte, genesis [genesisLn]byte) (*DID, error) { +func DIDFromGenesis(typ [2]byte, genesis [genesisLn]byte) (*didw3c.DID, error) { return ParseDIDFromID(NewID(typ, genesis)) } -func IDFromDID(did DID) (ID, error) { +func IDFromDID(did didw3c.DID) (ID, error) { id, err := idFromDID(did) if errors.Is(err, ErrMethodUnknown) { return newIDFromUnsupportedDID(did), nil @@ -228,7 +210,7 @@ func IDFromDID(did DID) (ID, error) { return id, err } -func newIDFromUnsupportedDID(did DID) ID { +func newIDFromUnsupportedDID(did didw3c.DID) ID { hash := sha256.Sum256([]byte(did.String())) var genesis [genesisLn]byte copy(genesis[:], hash[len(hash)-genesisLn:]) @@ -240,7 +222,7 @@ func newIDFromUnsupportedDID(did DID) ID { return NewID(tp, genesis) } -func idFromDID(did DID) (ID, error) { +func idFromDID(did didw3c.DID) (ID, error) { found := false for method := range knownMethods { if method.String() == did.Method { @@ -293,7 +275,7 @@ func idFromDID(did DID) (ID, error) { } // ParseDIDFromID returns DID from ID -func ParseDIDFromID(id ID) (*DID, error) { +func ParseDIDFromID(id ID) (*didw3c.DID, error) { if !CheckChecksum(id) { return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) @@ -318,7 +300,7 @@ func ParseDIDFromID(id ID) (*DID, error) { didString := strings.Join(didParts, ":") - did, err := Parse(didString) + did, err := didw3c.Parse(didString) if err != nil { return nil, err } diff --git a/did_w3c.go b/did/did_w3c.go similarity index 99% rename from did_w3c.go rename to did/did_w3c.go index cda2441..17ebb76 100644 --- a/did_w3c.go +++ b/did/did_w3c.go @@ -1,7 +1,7 @@ // Package core is a set of tools to work with Decentralized Identifiers (DIDs) as described // in the DID spec https://w3c.github.io/did-core/ // Got from https://github.com/build-trust/did -package core +package did import ( "fmt" diff --git a/did_w3c_test.go b/did/did_w3c_test.go similarity index 99% rename from did_w3c_test.go rename to did/did_w3c_test.go index a48e701..9c17a5b 100644 --- a/did_w3c_test.go +++ b/did/did_w3c_test.go @@ -1,5 +1,5 @@ // Got from https://github.com/build-trust/did -package core +package did import ( "fmt" diff --git a/did/json.go b/did/json.go new file mode 100644 index 0000000..1c6c011 --- /dev/null +++ b/did/json.go @@ -0,0 +1,22 @@ +package did + +import "encoding/json" + +func (did *DID) UnmarshalJSON(bytes []byte) error { + var didStr string + err := json.Unmarshal(bytes, &didStr) + if err != nil { + return err + } + + did3, err := Parse(didStr) + if err != nil { + return err + } + *did = *did3 + return nil +} + +func (did DID) MarshalJSON() ([]byte, error) { + return json.Marshal(did.String()) +} diff --git a/did_test.go b/did_test.go index b255edb..d0ce7ff 100644 --- a/did_test.go +++ b/did_test.go @@ -6,6 +6,7 @@ import ( "math/big" "testing" + "github.com/iden3/go-iden3-core/v2/did" "github.com/stretchr/testify/require" ) @@ -14,7 +15,7 @@ func TestParseDID(t *testing.T) { // did didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - did3, err := Parse(didStr) + did3, err := did.Parse(didStr) require.NoError(t, err) id, err := IDFromDID(*did3) @@ -33,7 +34,7 @@ func TestParseDID(t *testing.T) { // readonly did didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - did3, err = Parse(didStr) + did3, err = did.Parse(didStr) require.NoError(t, err) id, err = IDFromDID(*did3) @@ -55,10 +56,10 @@ func TestParseDID(t *testing.T) { func TestDID_MarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) - did, err := ParseDIDFromID(id) + did2, err := ParseDIDFromID(id) require.NoError(t, err) - b, err := did.MarshalJSON() + b, err := did2.MarshalJSON() require.NoError(t, err) require.Equal(t, `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, @@ -70,7 +71,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) var obj struct { - Obj *DID `json:"obj"` + Obj *did.DID `json:"obj"` } err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -95,7 +96,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { func TestDID_UnmarshalJSON_Error(t *testing.T) { inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` var obj struct { - Obj *DID `json:"obj"` + Obj *did.DID `json:"obj"` } err := json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -111,12 +112,12 @@ func TestDIDGenesisFromState(t *testing.T) { require.NoError(t, err) genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ0, genesisState) + did2, err := DIDGenesisFromIdenState(typ0, genesisState) require.NoError(t, err) - require.Equal(t, DIDMethodIden3.String(), did.Method) + require.Equal(t, DIDMethodIden3.String(), did2.Method) - id, err := IDFromDID(*did) + id, err := IDFromDID(*did2) require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) @@ -130,16 +131,16 @@ func TestDIDGenesisFromState(t *testing.T) { require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", - did.String()) + did2.String()) } func TestDID_PolygonID_Types(t *testing.T) { // Polygon no chain, no network - did := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) + did1 := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - require.Equal(t, DIDMethodPolygonID.String(), did.Method) - id, err := IDFromDID(*did) + require.Equal(t, DIDMethodPolygonID.String(), did1.Method) + id, err := IDFromDID(*did1) require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) @@ -152,7 +153,7 @@ func TestDID_PolygonID_Types(t *testing.T) { require.Equal(t, NoNetwork, networkID) require.Equal(t, "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", - did.String()) + did1.String()) // Polygon | Polygon chain, Main did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) @@ -195,7 +196,7 @@ func TestDID_PolygonID_Types(t *testing.T) { } func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { - id1, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + id1, err := IDFromString("2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86") require.NoError(t, err) did1, err := ParseDIDFromID(id1) @@ -208,7 +209,7 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { require.Equal(t, DIDMethodPolygonIDOnChain.String(), did1.Method) wantIDs := []string{"polygon", "mumbai", - "2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6"} + "2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86"} require.Equal(t, wantIDs, did1.IDStrings) id, err := IDFromDID(*did1) require.NoError(t, err) @@ -227,14 +228,14 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { require.Equal(t, addressBytesExp, ethAddr) require.Equal(t, - "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6", + "did:polygonid:polygon:mumbai:2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86", did1.String()) } func TestDecompose(t *testing.T) { s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" - did3, err := Parse(s) + did3, err := did.Parse(s) require.NoError(t, err) wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") @@ -258,30 +259,30 @@ func TestDecompose(t *testing.T) { } func helperBuildDIDFromType(t testing.TB, method DIDMethod, - blockchain Blockchain, network NetworkID) *DID { + blockchain Blockchain, network NetworkID) *did.DID { t.Helper() typ, err := BuildDIDType(method, blockchain, network) require.NoError(t, err) genesisState := big.NewInt(1) - did, err := DIDGenesisFromIdenState(typ, genesisState) + did1, err := DIDGenesisFromIdenState(typ, genesisState) require.NoError(t, err) - return did + return did1 } func TestNewIDFromDID(t *testing.T) { - did, err := Parse("did:something:x") + did1, err := did.Parse("did:something:x") require.NoError(t, err) - id := newIDFromUnsupportedDID(*did) + id := newIDFromUnsupportedDID(*did1) require.Equal(t, []byte{0xff, 0xff}, id[:2]) wantID, err := hex.DecodeString( "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") require.NoError(t, err) require.Equal(t, wantID, id[:]) - id2, err := IDFromDID(*did) + id2, err := IDFromDID(*did1) require.NoError(t, err) require.Equal(t, id, id2) } From c53679381c8dc310fe6b8a7356d3dd3e282ec997 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 4 May 2023 15:04:36 -0400 Subject: [PATCH 28/37] remove dependency on onChainMethods dict --- did.go | 16 +++++----------- did_test.go | 7 +++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/did.go b/did.go index 31d3ce0..b157203 100644 --- a/did.go +++ b/did.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "crypto/sha256" "errors" "fmt" @@ -48,10 +49,6 @@ var knownMethods = map[DIDMethod]struct{}{ DIDMethodPolygonIDOnChain: {}, } -var onChainMethods = map[DIDMethod]struct{}{ - DIDMethodPolygonIDOnChain: {}, -} - func (m DIDMethod) String() string { switch m { case DIDMethodIden3: @@ -367,15 +364,12 @@ func NetworkIDFromID(id ID) (NetworkID, error) { } func EthAddressFromID(id ID) ([20]byte, error) { - method, err := MethodFromID(id) - if err != nil { - return [20]byte{}, err - } - - if _, ok := onChainMethods[method]; !ok { + var z [7]byte + if !bytes.Equal(z[:], id[2:2+len(z)]) { return [20]byte{}, errors.New( - "can't get Ethereum address of not on-chain identity") + "can't get Ethereum address: high bytes of genesis are not zero") } + var address [20]byte copy(address[:], id[2+7:]) return address, nil diff --git a/did_test.go b/did_test.go index d0ce7ff..05d2b5b 100644 --- a/did_test.go +++ b/did_test.go @@ -319,4 +319,11 @@ func TestGenesisFromEthAddress(t *testing.T) { ch := CalculateChecksum(tp2, genesis) copy(wantID[len(tp2)+len(genesis):], ch[:]) require.Equal(t, wantID, id) + + // make genesis not look like an address + genesis[0] = 1 + id = NewID(tp2, genesis) + _, err = EthAddressFromID(id) + require.EqualError(t, err, + "can't get Ethereum address: high bytes of genesis are not zero") } From edd24a91ea9be7c90725542ba14d80f85cf472a4 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 4 May 2023 15:23:18 -0400 Subject: [PATCH 29/37] Remove method DIDMethodPolygonIDOnChain --- did.go | 11 ++--------- did_test.go | 51 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/did.go b/did.go index b157203..1ed43c7 100644 --- a/did.go +++ b/did.go @@ -44,9 +44,8 @@ const ( ) var knownMethods = map[DIDMethod]struct{}{ - DIDMethodIden3: {}, - DIDMethodPolygonID: {}, - DIDMethodPolygonIDOnChain: {}, + DIDMethodIden3: {}, + DIDMethodPolygonID: {}, } func (m DIDMethod) String() string { @@ -55,8 +54,6 @@ func (m DIDMethod) String() string { return "iden3" case DIDMethodPolygonID: return "polygonid" - case DIDMethodPolygonIDOnChain: - return "polygonid" case DIDMethodOther: return "" default: @@ -128,10 +125,6 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, }, - DIDMethodPolygonIDOnChain: { - {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, - {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, - }, DIDMethodOther: { {Blockchain: UnknownChain, NetworkID: UnknownNetwork}: 0b11111111, }, diff --git a/did_test.go b/did_test.go index 05d2b5b..583ea52 100644 --- a/did_test.go +++ b/did_test.go @@ -3,6 +3,7 @@ package core import ( "encoding/hex" "encoding/json" + "fmt" "math/big" "testing" @@ -196,7 +197,7 @@ func TestDID_PolygonID_Types(t *testing.T) { } func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { - id1, err := IDFromString("2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86") + id1, err := IDFromString("2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x") require.NoError(t, err) did1, err := ParseDIDFromID(id1) @@ -207,15 +208,15 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonIDOnChain.String(), did1.Method) + require.Equal(t, DIDMethodPolygonID.String(), did1.Method) wantIDs := []string{"polygon", "mumbai", - "2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86"} + "2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x"} require.Equal(t, wantIDs, did1.IDStrings) id, err := IDFromDID(*did1) require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonIDOnChain, method) + require.Equal(t, DIDMethodPolygonID, method) blockchain, err := BlockchainFromID(id) require.NoError(t, err) require.Equal(t, Polygon, blockchain) @@ -228,17 +229,24 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { require.Equal(t, addressBytesExp, ethAddr) require.Equal(t, - "did:polygonid:polygon:mumbai:2z32DSAgDwCwCCrafvUsdwyt1m6esEKLHCHXoW8zo86", + "did:polygonid:polygon:mumbai:2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x", did1.String()) } func TestDecompose(t *testing.T) { - s := "did:polygonid:polygon:mumbai:2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6" + wantIDHex := "2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x" + ethAddrHex := "a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0" + genesis := genFromHex("00000000000000" + ethAddrHex) + tp, err := BuildDIDType(DIDMethodPolygonID, Polygon, Mumbai) + require.NoError(t, err) + id0 := NewID(tp, genesis) + + s := fmt.Sprintf("did:polygonid:polygon:mumbai:%v", id0.String()) did3, err := did.Parse(s) require.NoError(t, err) - wantID, err := IDFromString("2z39iB1bPjY2STTFSwbzvK8gqJQMsv5PLpvoSg3opa6") + wantID, err := IDFromString(wantIDHex) require.NoError(t, err) id, err := IDFromDID(*did3) @@ -247,7 +255,7 @@ func TestDecompose(t *testing.T) { method, err := MethodFromID(id) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonIDOnChain, method) + require.Equal(t, DIDMethodPolygonID, method) blockchain, err := BlockchainFromID(id) require.NoError(t, err) @@ -256,6 +264,10 @@ func TestDecompose(t *testing.T) { networkID, err := NetworkIDFromID(id) require.NoError(t, err) require.Equal(t, Mumbai, networkID) + + ethAddr, err := EthAddressFromID(id) + require.NoError(t, err) + require.Equal(t, ethAddrFromHex(ethAddrHex), ethAddr) } func helperBuildDIDFromType(t testing.TB, method DIDMethod, @@ -304,14 +316,13 @@ func TestGenesisFromEthAddress(t *testing.T) { require.NoError(t, err) require.Equal(t, wantGenesis, genesis[:]) - tp2, err := BuildDIDType(DIDMethodPolygonIDOnChain, Polygon, Mumbai) + tp2, err := BuildDIDType(DIDMethodPolygonID, Polygon, Mumbai) require.NoError(t, err) id := NewID(tp2, genesis) ethAddr2, err := EthAddressFromID(id) require.NoError(t, err) require.Equal(t, ethAddr, ethAddr2) - t.Log(hex.EncodeToString(ethAddr2[:])) var wantID ID copy(wantID[:], tp2[:]) @@ -327,3 +338,23 @@ func TestGenesisFromEthAddress(t *testing.T) { require.EqualError(t, err, "can't get Ethereum address: high bytes of genesis are not zero") } + +func genFromHex(gh string) [genesisLn]byte { + genBytes, err := hex.DecodeString(gh) + if err != nil { + panic(err) + } + var gen [genesisLn]byte + copy(gen[:], genBytes) + return gen +} + +func ethAddrFromHex(ea string) [20]byte { + eaBytes, err := hex.DecodeString(ea) + if err != nil { + panic(err) + } + var ethAddr [20]byte + copy(ethAddr[:], eaBytes) + return ethAddr +} From 37cdb4fb8644f701aed1564166a9744690e5ed81 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 4 May 2023 15:23:36 -0400 Subject: [PATCH 30/37] Remove method DIDMethodPolygonIDOnChain --- did.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/did.go b/did.go index 1ed43c7..0e7196f 100644 --- a/did.go +++ b/did.go @@ -37,8 +37,6 @@ const ( DIDMethodIden3 DIDMethod = 0b00000001 // DIDMethodPolygonID DIDMethodPolygonID DIDMethod = 0b00000010 - // DIDMethodPolygonIDOnChain - DIDMethodPolygonIDOnChain DIDMethod = 0b10000010 // DIDMethodOther any other method not listed before DIDMethodOther DIDMethod = 0b11111111 ) From 953bf751811042c04ef9795c28ccf39414c30e03 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 4 May 2023 15:55:49 -0400 Subject: [PATCH 31/37] make DIDMethod be strings again --- did.go | 98 +++++++++++++++++++++++++---------------------------- did_test.go | 22 ++++++------ 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/did.go b/did.go index 0e7196f..07e32f9 100644 --- a/did.go +++ b/did.go @@ -30,39 +30,17 @@ var ( const DIDSchema = "did" // DIDMethod represents did methods -type DIDMethod byte +type DIDMethod string const ( // DIDMethodIden3 - DIDMethodIden3 DIDMethod = 0b00000001 + DIDMethodIden3 DIDMethod = "iden3" // DIDMethodPolygonID - DIDMethodPolygonID DIDMethod = 0b00000010 + DIDMethodPolygonID DIDMethod = "polygonid" // DIDMethodOther any other method not listed before - DIDMethodOther DIDMethod = 0b11111111 + DIDMethodOther DIDMethod = "" ) -var knownMethods = map[DIDMethod]struct{}{ - DIDMethodIden3: {}, - DIDMethodPolygonID: {}, -} - -func (m DIDMethod) String() string { - switch m { - case DIDMethodIden3: - return "iden3" - case DIDMethodPolygonID: - return "polygonid" - case DIDMethodOther: - return "" - default: - return fmt.Sprintf("unknown<%v>", uint8(m)) - } -} - -func (m DIDMethod) Byte() byte { - return byte(m) -} - // Blockchain id of the network "eth", "polygon", etc. type Blockchain string @@ -94,9 +72,16 @@ const ( UnknownNetwork NetworkID = "unknown" // NoNetwork should be used for readonly identity to build readonly flag - NoNetwork NetworkID = "" + NoNetwork NetworkID = "null" ) +// DIDMethodByte did method flag representation +var DIDMethodByte = map[DIDMethod]byte{ + DIDMethodIden3: 0b00000001, + DIDMethodPolygonID: 0b00000010, + DIDMethodOther: 0b11111111, +} + // DIDNetworkFlag is a structure to represent DID blockchain and network id type DIDNetworkFlag struct { Blockchain Blockchain @@ -132,13 +117,15 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ func BuildDIDType(method DIDMethod, blockchain Blockchain, network NetworkID) ([2]byte, error) { - if _, ok := knownMethods[method]; !ok { + fb, ok := DIDMethodByte[method] + if !ok { return [2]byte{}, ErrDIDMethodNotSupported } - if blockchain == NoChain { - blockchain = ReadOnly - } + // If we uncomment this, there would be no way to create type 0xffff + //if blockchain == NoChain { + // blockchain = ReadOnly + //} netFlag := DIDNetworkFlag{Blockchain: blockchain, NetworkID: network} sb, ok := DIDMethodNetwork[method][netFlag] @@ -146,7 +133,7 @@ func BuildDIDType(method DIDMethod, blockchain Blockchain, return [2]byte{}, ErrNetworkNotSupportedForDID } - return [2]byte{method.Byte(), sb}, nil + return [2]byte{fb, sb}, nil } // FindNetworkIDForDIDMethodByValue finds network by byte value @@ -177,6 +164,16 @@ func FindBlockchainForDIDMethodByValue(method DIDMethod, _v byte) (Blockchain, e return UnknownChain, ErrBlockchainNotSupportedForDID } +// FindDIDMethodByValue finds did method by its byte value +func FindDIDMethodByValue(b byte) (DIDMethod, error) { + for k, v := range DIDMethodByte { + if v == b { + return k, nil + } + } + return DIDMethodOther, ErrDIDMethodNotSupported +} + // DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*didw3c.DID, error) { id, err := IdGenesisFromIdenState(typ, state) @@ -204,33 +201,28 @@ func newIDFromUnsupportedDID(did didw3c.DID) ID { copy(genesis[:], hash[len(hash)-genesisLn:]) flg := DIDNetworkFlag{Blockchain: UnknownChain, NetworkID: UnknownNetwork} var tp = [2]byte{ - DIDMethodOther.Byte(), + DIDMethodByte[DIDMethodOther], DIDMethodNetwork[DIDMethodOther][flg], } return NewID(tp, genesis) } func idFromDID(did didw3c.DID) (ID, error) { - found := false - for method := range knownMethods { - if method.String() == did.Method { - found = true - break - } - } - if !found { + method := DIDMethod(did.Method) + _, ok := DIDMethodByte[method] + if !ok || method == DIDMethodOther { return ID{}, ErrMethodUnknown } var id ID - if len(did.IDStrings) > 3 || len(did.IDStrings) < 1 { + if len(did.IDStrings) != 3 { return id, fmt.Errorf("%w: unexpected number of ID strings", ErrIncorrectDID) } var err error - id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) + id, err = IDFromString(did.IDStrings[2]) if err != nil { return id, fmt.Errorf("%w: can't parse ID string", ErrIncorrectDID) } @@ -239,22 +231,22 @@ func idFromDID(did didw3c.DID) (ID, error) { return id, fmt.Errorf("%w: incorrect ID checksum", ErrIncorrectDID) } - method, blockchain, networkID, err := decodeDIDPartsFromID(id) + method2, blockchain, networkID, err := decodeDIDPartsFromID(id) if err != nil { return id, err } - if method.String() != did.Method { + if method2 != method { return id, fmt.Errorf("%w: methods in ID and DID are different", ErrIncorrectDID) } - if len(did.IDStrings) > 1 && string(blockchain) != did.IDStrings[0] { + if string(blockchain) != did.IDStrings[0] { return id, fmt.Errorf("%w: blockchains in ID and DID are different", ErrIncorrectDID) } - if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { + if string(networkID) != did.IDStrings[1] { return id, fmt.Errorf("%w: networkIDs in ID and DID are different", ErrIncorrectDID) } @@ -279,7 +271,7 @@ func ParseDIDFromID(id ID) (*didw3c.DID, error) { ErrMethodUnknown) } - didParts := []string{DIDSchema, method.String(), string(blockchain)} + didParts := []string{DIDSchema, string(method), string(blockchain)} if string(networkID) != "" { didParts = append(didParts, string(networkID)) } @@ -296,15 +288,17 @@ func ParseDIDFromID(id ID) (*didw3c.DID, error) { } func decodeDIDPartsFromID(id ID) (DIDMethod, Blockchain, NetworkID, error) { - method := DIDMethod(id[0]) - networkByte := id[1] + method, err := FindDIDMethodByValue(id[0]) + if err != nil { + return DIDMethodOther, UnknownChain, UnknownNetwork, err + } - blockchain, err := FindBlockchainForDIDMethodByValue(method, networkByte) + blockchain, err := FindBlockchainForDIDMethodByValue(method, id[1]) if err != nil { return DIDMethodOther, UnknownChain, UnknownNetwork, err } - networkID, err := FindNetworkIDForDIDMethodByValue(method, networkByte) + networkID, err := FindNetworkIDForDIDMethodByValue(method, id[1]) if err != nil { return DIDMethodOther, UnknownChain, UnknownNetwork, err } diff --git a/did_test.go b/did_test.go index 583ea52..df48290 100644 --- a/did_test.go +++ b/did_test.go @@ -33,7 +33,7 @@ func TestParseDID(t *testing.T) { require.Equal(t, Mumbai, networkID) // readonly did - didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" + didStr = "did:iden3:readonly:null:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" did3, err = did.Parse(didStr) require.NoError(t, err) @@ -51,7 +51,7 @@ func TestParseDID(t *testing.T) { require.NoError(t, err) require.Equal(t, NoNetwork, networkID) - require.Equal(t, [2]byte{DIDMethodIden3.Byte(), 0b0}, id.Type()) + require.Equal(t, [2]byte{DIDMethodByte[DIDMethodIden3], 0b0}, id.Type()) } func TestDID_MarshalJSON(t *testing.T) { @@ -77,7 +77,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) require.NotNil(t, obj.Obj) - require.Equal(t, DIDMethodIden3.String(), obj.Obj.Method) + require.Equal(t, string(DIDMethodIden3), obj.Obj.Method) id2, err := IDFromDID(*obj.Obj) require.NoError(t, err) @@ -116,7 +116,7 @@ func TestDIDGenesisFromState(t *testing.T) { did2, err := DIDGenesisFromIdenState(typ0, genesisState) require.NoError(t, err) - require.Equal(t, DIDMethodIden3.String(), did2.Method) + require.Equal(t, string(DIDMethodIden3), did2.Method) id, err := IDFromDID(*did2) require.NoError(t, err) @@ -131,7 +131,7 @@ func TestDIDGenesisFromState(t *testing.T) { require.Equal(t, NoNetwork, networkID) require.Equal(t, - "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", + "did:iden3:readonly:null:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did2.String()) } @@ -140,7 +140,7 @@ func TestDID_PolygonID_Types(t *testing.T) { // Polygon no chain, no network did1 := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - require.Equal(t, DIDMethodPolygonID.String(), did1.Method) + require.Equal(t, string(DIDMethodPolygonID), did1.Method) id, err := IDFromDID(*did1) require.NoError(t, err) method, err := MethodFromID(id) @@ -153,13 +153,13 @@ func TestDID_PolygonID_Types(t *testing.T) { require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, - "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", + "did:polygonid:readonly:null:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", did1.String()) // Polygon | Polygon chain, Main did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) - require.Equal(t, DIDMethodPolygonID.String(), did2.Method) + require.Equal(t, string(DIDMethodPolygonID), did2.Method) id2, err := IDFromDID(*did2) require.NoError(t, err) method2, err := MethodFromID(id2) @@ -178,7 +178,7 @@ func TestDID_PolygonID_Types(t *testing.T) { // Polygon | Polygon chain, Mumbai did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) - require.Equal(t, DIDMethodPolygonID.String(), did3.Method) + require.Equal(t, string(DIDMethodPolygonID), did3.Method) id3, err := IDFromDID(*did3) require.NoError(t, err) method3, err := MethodFromID(id3) @@ -196,7 +196,7 @@ func TestDID_PolygonID_Types(t *testing.T) { } -func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { +func TestDID_PolygonID_ParseDIDFromID(t *testing.T) { id1, err := IDFromString("2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x") require.NoError(t, err) @@ -208,7 +208,7 @@ func TestDID_PolygonID_ParseDIDFromID_OnChain(t *testing.T) { []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID.String(), did1.Method) + require.Equal(t, string(DIDMethodPolygonID), did1.Method) wantIDs := []string{"polygon", "mumbai", "2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x"} require.Equal(t, wantIDs, did1.IDStrings) From 5cced07b3b4fddb77b1dc11b66df61f78b492400 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Fri, 5 May 2023 06:53:22 -0400 Subject: [PATCH 32/37] make readonly networkID empty string --- did.go | 13 ++++--------- did_test.go | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/did.go b/did.go index 07e32f9..492e83d 100644 --- a/did.go +++ b/did.go @@ -72,7 +72,7 @@ const ( UnknownNetwork NetworkID = "unknown" // NoNetwork should be used for readonly identity to build readonly flag - NoNetwork NetworkID = "null" + NoNetwork NetworkID = "" ) // DIDMethodByte did method flag representation @@ -122,11 +122,6 @@ func BuildDIDType(method DIDMethod, blockchain Blockchain, return [2]byte{}, ErrDIDMethodNotSupported } - // If we uncomment this, there would be no way to create type 0xffff - //if blockchain == NoChain { - // blockchain = ReadOnly - //} - netFlag := DIDNetworkFlag{Blockchain: blockchain, NetworkID: network} sb, ok := DIDMethodNetwork[method][netFlag] if !ok { @@ -216,13 +211,13 @@ func idFromDID(did didw3c.DID) (ID, error) { var id ID - if len(did.IDStrings) != 3 { + if len(did.IDStrings) > 3 || len(did.IDStrings) < 2 { return id, fmt.Errorf("%w: unexpected number of ID strings", ErrIncorrectDID) } var err error - id, err = IDFromString(did.IDStrings[2]) + id, err = IDFromString(did.IDStrings[len(did.IDStrings)-1]) if err != nil { return id, fmt.Errorf("%w: can't parse ID string", ErrIncorrectDID) } @@ -246,7 +241,7 @@ func idFromDID(did didw3c.DID) (ID, error) { ErrIncorrectDID) } - if string(networkID) != did.IDStrings[1] { + if len(did.IDStrings) > 2 && string(networkID) != did.IDStrings[1] { return id, fmt.Errorf("%w: networkIDs in ID and DID are different", ErrIncorrectDID) } diff --git a/did_test.go b/did_test.go index df48290..720fe52 100644 --- a/did_test.go +++ b/did_test.go @@ -33,7 +33,7 @@ func TestParseDID(t *testing.T) { require.Equal(t, Mumbai, networkID) // readonly did - didStr = "did:iden3:readonly:null:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" + didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" did3, err = did.Parse(didStr) require.NoError(t, err) @@ -131,7 +131,23 @@ func TestDIDGenesisFromState(t *testing.T) { require.Equal(t, NoNetwork, networkID) require.Equal(t, - "did:iden3:readonly:null:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", + "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", + did2.String()) +} + +func TestDIDFromID(t *testing.T) { + typ0, err := BuildDIDType(DIDMethodIden3, ReadOnly, NoNetwork) + require.NoError(t, err) + + genesisState := big.NewInt(1) + id, err := IdGenesisFromIdenState(typ0, genesisState) + require.NoError(t, err) + + did2, err := ParseDIDFromID(*id) + require.NoError(t, err) + + require.Equal(t, + "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", did2.String()) } @@ -153,7 +169,7 @@ func TestDID_PolygonID_Types(t *testing.T) { require.NoError(t, err) require.Equal(t, NoNetwork, networkID) require.Equal(t, - "did:polygonid:readonly:null:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", + "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", did1.String()) // Polygon | Polygon chain, Main From f4681972ad458d3aaaf32707d07ec98c8d820621 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Fri, 5 May 2023 07:01:20 -0400 Subject: [PATCH 33/37] remove unneeded public constant --- did.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/did.go b/did.go index 492e83d..f4ef1e1 100644 --- a/did.go +++ b/did.go @@ -26,9 +26,6 @@ var ( ErrNetworkNotSupportedForDID = errors.New("not supported network") ) -// DIDSchema DID Schema -const DIDSchema = "did" - // DIDMethod represents did methods type DIDMethod string @@ -266,7 +263,7 @@ func ParseDIDFromID(id ID) (*didw3c.DID, error) { ErrMethodUnknown) } - didParts := []string{DIDSchema, string(method), string(blockchain)} + didParts := []string{"did", string(method), string(blockchain)} if string(networkID) != "" { didParts = append(didParts, string(networkID)) } From 42b31ff46f370753d9c66228fc1a7e979e19d9ca Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Fri, 19 May 2023 08:47:18 -0400 Subject: [PATCH 34/37] Rename did package to w3c. Rename did.Parse function to w3c.ParseDID. Rename IdGenesisFromIdenState func to NewIDFromIdenState. Rename DIDGenesisFromIdenState to NewDIDFromIdenState. Rename DIDFromGenesis to NewDID. --- did.go | 22 ++--- did_test.go | 163 +++++++++++++++++------------------ id.go | 8 +- {did => w3c}/did_w3c.go | 16 ++-- {did => w3c}/did_w3c_test.go | 108 +++++++++++------------ {did => w3c}/json.go | 4 +- 6 files changed, 156 insertions(+), 165 deletions(-) rename {did => w3c}/did_w3c.go (98%) rename {did => w3c}/did_w3c_test.go (90%) rename {did => w3c}/json.go (87%) diff --git a/did.go b/did.go index f4ef1e1..46a89ca 100644 --- a/did.go +++ b/did.go @@ -8,7 +8,7 @@ import ( "math/big" "strings" - didw3c "github.com/iden3/go-iden3-core/v2/did" + "github.com/iden3/go-iden3-core/v2/w3c" ) var ( @@ -166,20 +166,22 @@ func FindDIDMethodByValue(b byte) (DIDMethod, error) { return DIDMethodOther, ErrDIDMethodNotSupported } -// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID -func DIDGenesisFromIdenState(typ [2]byte, state *big.Int) (*didw3c.DID, error) { - id, err := IdGenesisFromIdenState(typ, state) +// NewDIDFromIdenState calculates the genesis ID from an Identity State and +// returns it as a DID +func NewDIDFromIdenState(typ [2]byte, state *big.Int) (*w3c.DID, error) { + id, err := NewIDFromIdenState(typ, state) if err != nil { return nil, err } return ParseDIDFromID(*id) } -func DIDFromGenesis(typ [2]byte, genesis [genesisLn]byte) (*didw3c.DID, error) { +// NewDID creates a new *w3c.DID from the type and the genesis +func NewDID(typ [2]byte, genesis [genesisLn]byte) (*w3c.DID, error) { return ParseDIDFromID(NewID(typ, genesis)) } -func IDFromDID(did didw3c.DID) (ID, error) { +func IDFromDID(did w3c.DID) (ID, error) { id, err := idFromDID(did) if errors.Is(err, ErrMethodUnknown) { return newIDFromUnsupportedDID(did), nil @@ -187,7 +189,7 @@ func IDFromDID(did didw3c.DID) (ID, error) { return id, err } -func newIDFromUnsupportedDID(did didw3c.DID) ID { +func newIDFromUnsupportedDID(did w3c.DID) ID { hash := sha256.Sum256([]byte(did.String())) var genesis [genesisLn]byte copy(genesis[:], hash[len(hash)-genesisLn:]) @@ -199,7 +201,7 @@ func newIDFromUnsupportedDID(did didw3c.DID) ID { return NewID(tp, genesis) } -func idFromDID(did didw3c.DID) (ID, error) { +func idFromDID(did w3c.DID) (ID, error) { method := DIDMethod(did.Method) _, ok := DIDMethodByte[method] if !ok || method == DIDMethodOther { @@ -247,7 +249,7 @@ func idFromDID(did didw3c.DID) (ID, error) { } // ParseDIDFromID returns DID from ID -func ParseDIDFromID(id ID) (*didw3c.DID, error) { +func ParseDIDFromID(id ID) (*w3c.DID, error) { if !CheckChecksum(id) { return nil, fmt.Errorf("%w: invalid checksum", ErrUnsupportedID) @@ -272,7 +274,7 @@ func ParseDIDFromID(id ID) (*didw3c.DID, error) { didString := strings.Join(didParts, ":") - did, err := didw3c.Parse(didString) + did, err := w3c.ParseDID(didString) if err != nil { return nil, err } diff --git a/did_test.go b/did_test.go index 720fe52..1220fa9 100644 --- a/did_test.go +++ b/did_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/iden3/go-iden3-core/v2/did" + "github.com/iden3/go-iden3-core/v2/w3c" "github.com/stretchr/testify/require" ) @@ -16,10 +16,10 @@ func TestParseDID(t *testing.T) { // did didStr := "did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ" - did3, err := did.Parse(didStr) + did, err := w3c.ParseDID(didStr) require.NoError(t, err) - id, err := IDFromDID(*did3) + id, err := IDFromDID(*did) require.NoError(t, err) require.Equal(t, "wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ", id.String()) method, err := MethodFromID(id) @@ -35,10 +35,10 @@ func TestParseDID(t *testing.T) { // readonly did didStr = "did:iden3:readonly:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa" - did3, err = did.Parse(didStr) + did, err = w3c.ParseDID(didStr) require.NoError(t, err) - id, err = IDFromDID(*did3) + id, err = IDFromDID(*did) require.NoError(t, err) require.Equal(t, "tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa", id.String()) method, err = MethodFromID(id) @@ -57,10 +57,10 @@ func TestParseDID(t *testing.T) { func TestDID_MarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) - did2, err := ParseDIDFromID(id) + did, err := ParseDIDFromID(id) require.NoError(t, err) - b, err := did2.MarshalJSON() + b, err := did.MarshalJSON() require.NoError(t, err) require.Equal(t, `"did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"`, @@ -72,7 +72,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { id, err := IDFromString("wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ") require.NoError(t, err) var obj struct { - Obj *did.DID `json:"obj"` + Obj *w3c.DID `json:"obj"` } err = json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -97,7 +97,7 @@ func TestDID_UnmarshalJSON(t *testing.T) { func TestDID_UnmarshalJSON_Error(t *testing.T) { inBytes := `{"obj": "did:iden3:eth:goerli:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ"}` var obj struct { - Obj *did.DID `json:"obj"` + Obj *w3c.DID `json:"obj"` } err := json.Unmarshal([]byte(inBytes), &obj) require.NoError(t, err) @@ -113,12 +113,12 @@ func TestDIDGenesisFromState(t *testing.T) { require.NoError(t, err) genesisState := big.NewInt(1) - did2, err := DIDGenesisFromIdenState(typ0, genesisState) + did, err := NewDIDFromIdenState(typ0, genesisState) require.NoError(t, err) - require.Equal(t, string(DIDMethodIden3), did2.Method) + require.Equal(t, string(DIDMethodIden3), did.Method) - id, err := IDFromDID(*did2) + id, err := IDFromDID(*did) require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) @@ -132,7 +132,7 @@ func TestDIDGenesisFromState(t *testing.T) { require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", - did2.String()) + did.String()) } func TestDIDFromID(t *testing.T) { @@ -140,83 +140,74 @@ func TestDIDFromID(t *testing.T) { require.NoError(t, err) genesisState := big.NewInt(1) - id, err := IdGenesisFromIdenState(typ0, genesisState) + id, err := NewIDFromIdenState(typ0, genesisState) require.NoError(t, err) - did2, err := ParseDIDFromID(*id) + did, err := ParseDIDFromID(*id) require.NoError(t, err) require.Equal(t, "did:iden3:readonly:tJ93RwaVfE1PEMxd5rpZZuPtLCwbEaDCrNBhAy8HM", - did2.String()) + did.String()) } func TestDID_PolygonID_Types(t *testing.T) { + testCases := []struct { + title string + method DIDMethod + chain Blockchain + net NetworkID + wantDID string + }{ + { + title: "Polygon no chain, no network", + method: DIDMethodPolygonID, + chain: ReadOnly, + net: NoNetwork, + wantDID: "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", + }, + { + title: "Polygon | Polygon chain, Main", + method: DIDMethodPolygonID, + chain: Polygon, + net: Main, + wantDID: "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", + }, + { + title: "Polygon | Polygon chain, Mumbai", + method: DIDMethodPolygonID, + chain: Polygon, + net: Mumbai, + wantDID: "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", + }, + } - // Polygon no chain, no network - did1 := helperBuildDIDFromType(t, DIDMethodPolygonID, ReadOnly, NoNetwork) - - require.Equal(t, string(DIDMethodPolygonID), did1.Method) - id, err := IDFromDID(*did1) - require.NoError(t, err) - method, err := MethodFromID(id) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method) - blockchain, err := BlockchainFromID(id) - require.NoError(t, err) - require.Equal(t, ReadOnly, blockchain) - networkID, err := NetworkIDFromID(id) - require.NoError(t, err) - require.Equal(t, NoNetwork, networkID) - require.Equal(t, - "did:polygonid:readonly:2mbH5rt9zKT1mTivFAie88onmfQtBU9RQhjNPLwFZh", - did1.String()) - - // Polygon | Polygon chain, Main - did2 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Main) - - require.Equal(t, string(DIDMethodPolygonID), did2.Method) - id2, err := IDFromDID(*did2) - require.NoError(t, err) - method2, err := MethodFromID(id2) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method2) - blockchain2, err := BlockchainFromID(id2) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain2) - networkID2, err := NetworkIDFromID(id2) - require.NoError(t, err) - require.Equal(t, Main, networkID2) - require.Equal(t, - "did:polygonid:polygon:main:2pzr1wiBm3Qhtq137NNPPDFvdk5xwRsjDFnMxpnYHm", - did2.String()) - - // Polygon | Polygon chain, Mumbai - did3 := helperBuildDIDFromType(t, DIDMethodPolygonID, Polygon, Mumbai) - - require.Equal(t, string(DIDMethodPolygonID), did3.Method) - id3, err := IDFromDID(*did3) - require.NoError(t, err) - method3, err := MethodFromID(id3) - require.NoError(t, err) - require.Equal(t, DIDMethodPolygonID, method3) - blockchain3, err := BlockchainFromID(id3) - require.NoError(t, err) - require.Equal(t, Polygon, blockchain3) - networkID3, err := NetworkIDFromID(id3) - require.NoError(t, err) - require.Equal(t, Mumbai, networkID3) - require.Equal(t, - "did:polygonid:polygon:mumbai:2qCU58EJgrELNZCDkSU23dQHZsBgAFWLNpNezo1g6b", - did3.String()) - + for i := range testCases { + tc := testCases[i] + t.Run(tc.title, func(t *testing.T) { + did := helperBuildDIDFromType(t, tc.method, tc.chain, tc.net) + require.Equal(t, string(tc.method), did.Method) + id, err := IDFromDID(*did) + require.NoError(t, err) + method, err := MethodFromID(id) + require.NoError(t, err) + require.Equal(t, tc.method, method) + blockchain, err := BlockchainFromID(id) + require.NoError(t, err) + require.Equal(t, tc.chain, blockchain) + networkID, err := NetworkIDFromID(id) + require.NoError(t, err) + require.Equal(t, tc.net, networkID) + require.Equal(t, tc.wantDID, did.String()) + }) + } } func TestDID_PolygonID_ParseDIDFromID(t *testing.T) { id1, err := IDFromString("2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x") require.NoError(t, err) - did1, err := ParseDIDFromID(id1) + did, err := ParseDIDFromID(id1) require.NoError(t, err) var addressBytesExp [20]byte @@ -224,11 +215,11 @@ func TestDID_PolygonID_ParseDIDFromID(t *testing.T) { []byte("A51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0")) require.NoError(t, err) - require.Equal(t, string(DIDMethodPolygonID), did1.Method) + require.Equal(t, string(DIDMethodPolygonID), did.Method) wantIDs := []string{"polygon", "mumbai", "2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x"} - require.Equal(t, wantIDs, did1.IDStrings) - id, err := IDFromDID(*did1) + require.Equal(t, wantIDs, did.IDStrings) + id, err := IDFromDID(*did) require.NoError(t, err) method, err := MethodFromID(id) require.NoError(t, err) @@ -246,7 +237,7 @@ func TestDID_PolygonID_ParseDIDFromID(t *testing.T) { require.Equal(t, "did:polygonid:polygon:mumbai:2qCU58EJgrEM9NKvHkvg5NFWUiJPgN3M3LnCr98j3x", - did1.String()) + did.String()) } func TestDecompose(t *testing.T) { @@ -259,13 +250,13 @@ func TestDecompose(t *testing.T) { s := fmt.Sprintf("did:polygonid:polygon:mumbai:%v", id0.String()) - did3, err := did.Parse(s) + did, err := w3c.ParseDID(s) require.NoError(t, err) wantID, err := IDFromString(wantIDHex) require.NoError(t, err) - id, err := IDFromDID(*did3) + id, err := IDFromDID(*did) require.NoError(t, err) require.Equal(t, wantID, id) @@ -287,30 +278,30 @@ func TestDecompose(t *testing.T) { } func helperBuildDIDFromType(t testing.TB, method DIDMethod, - blockchain Blockchain, network NetworkID) *did.DID { + blockchain Blockchain, network NetworkID) *w3c.DID { t.Helper() typ, err := BuildDIDType(method, blockchain, network) require.NoError(t, err) genesisState := big.NewInt(1) - did1, err := DIDGenesisFromIdenState(typ, genesisState) + did, err := NewDIDFromIdenState(typ, genesisState) require.NoError(t, err) - return did1 + return did } func TestNewIDFromDID(t *testing.T) { - did1, err := did.Parse("did:something:x") + did, err := w3c.ParseDID("did:something:x") require.NoError(t, err) - id := newIDFromUnsupportedDID(*did1) + id := newIDFromUnsupportedDID(*did) require.Equal(t, []byte{0xff, 0xff}, id[:2]) wantID, err := hex.DecodeString( "ffff84b1e6d0d9ecbe951348ea578dbacc022cdbbff4b11218671dca871c11") require.NoError(t, err) require.Equal(t, wantID, id[:]) - id2, err := IDFromDID(*did1) + id2, err := IDFromDID(*did) require.NoError(t, err) require.Equal(t, id, id2) } diff --git a/id.go b/id.go index 53affb9..39c7001 100644 --- a/id.go +++ b/id.go @@ -200,10 +200,8 @@ func CheckChecksum(id ID) bool { return bytes.Equal(c[:], checksum[:]) } -// IdGenesisFromIdenState calculates the genesis ID from an Identity State. -func IdGenesisFromIdenState(typ [2]byte, //nolint:revive - state *big.Int) (*ID, error) { - +// NewIDFromIdenState calculates the genesis ID from an Identity State. +func NewIDFromIdenState(typ [2]byte, state *big.Int) (*ID, error) { var idGenesisBytes [genesisLn]byte idenStateData, err := NewElemBytesFromInt(state) @@ -229,7 +227,7 @@ func CheckGenesisStateID(id, state *big.Int) (bool, error) { if err != nil { return false, err } - identifier, err := IdGenesisFromIdenState(userID.Type(), state) + identifier, err := NewIDFromIdenState(userID.Type(), state) if err != nil { return false, err } diff --git a/did/did_w3c.go b/w3c/did_w3c.go similarity index 98% rename from did/did_w3c.go rename to w3c/did_w3c.go index 17ebb76..c0040f0 100644 --- a/did/did_w3c.go +++ b/w3c/did_w3c.go @@ -1,7 +1,7 @@ -// Package core is a set of tools to work with Decentralized Identifiers (DIDs) as described -// in the DID spec https://w3c.github.io/did-core/ +// Package w3c is a set of tools to work with Decentralized Identifiers (DIDs) +// as described in the DID spec https://w3c.github.io/did-core/ // Got from https://github.com/build-trust/did -package did +package w3c import ( "fmt" @@ -161,9 +161,9 @@ func (d *DID) String() string { return buf.String() } -// Parse parses the input string into a DID structure. -func Parse(input string) (*DID, error) { - // intialize the parser state +// ParseDID parses the input string into a DID structure. +func ParseDID(input string) (*DID, error) { + // initialize the parser state p := &parser{input: input, out: &DID{}} // the parser state machine is implemented as a loop over parser steps @@ -201,7 +201,7 @@ func Parse(input string) (*DID, error) { // + `:` (1 char) // + atleast one idchar (1 char) // -// i.e atleast 7 chars +// i.e. at least 7 chars // The current specification does not take a position on maximum length of a DID. // https://w3c-ccg.github.io/did-spec/#upper-limits-on-did-character-length func (p *parser) checkLength() parserStep { @@ -286,7 +286,7 @@ func (p *parser) parseMethod() parserStep { // idstring = 1*idchar // idchar = ALPHA / DIGIT / "." / "-" // -// p.out.IDStrings is later concatented by the Parse function before it returns. +// p.out.IDStrings is later concatented by the ParseDID function before it returns. func (p *parser) parseID() parserStep { input := p.input inputLength := len(input) diff --git a/did/did_w3c_test.go b/w3c/did_w3c_test.go similarity index 90% rename from did/did_w3c_test.go rename to w3c/did_w3c_test.go index 9c17a5b..436d900 100644 --- a/did/did_w3c_test.go +++ b/w3c/did_w3c_test.go @@ -1,5 +1,5 @@ // Got from https://github.com/build-trust/did -package did +package w3c import ( "fmt" @@ -160,28 +160,28 @@ func TestString(t *testing.T) { func TestParse(t *testing.T) { t.Run("returns error if input is empty", func(t *testing.T) { - _, err := Parse("") + _, err := ParseDID("") assert(t, false, err == nil) }) t.Run("returns error if input length is less than length 7", func(t *testing.T) { - _, err := Parse("did:") + _, err := ParseDID("did:") assert(t, false, err == nil) - _, err = Parse("did:a") + _, err = ParseDID("did:a") assert(t, false, err == nil) - _, err = Parse("did:a:") + _, err = ParseDID("did:a:") assert(t, false, err == nil) }) t.Run("returns error if input does not have a second : to mark end of method", func(t *testing.T) { - _, err := Parse("did:aaaaaaaaaaa") + _, err := ParseDID("did:aaaaaaaaaaa") assert(t, false, err == nil) }) t.Run("returns error if method is empty", func(t *testing.T) { - _, err := Parse("did::aaaaaaaaaaa") + _, err := ParseDID("did::aaaaaaaaaaa") assert(t, false, err == nil) }) @@ -194,53 +194,53 @@ func TestParse(t *testing.T) { "did:a:123:#abc", } for _, did := range dids { - _, err := Parse(did) + _, err := ParseDID(did) assert(t, false, err == nil, "Input: %s", did) } }) t.Run("returns error if input does not begin with did: scheme", func(t *testing.T) { - _, err := Parse("a:12345") + _, err := ParseDID("a:12345") assert(t, false, err == nil) }) t.Run("returned value is nil if input does not begin with did: scheme", func(t *testing.T) { - d, _ := Parse("a:12345") + d, _ := ParseDID("a:12345") assert(t, true, d == nil) }) t.Run("succeeds if it has did prefix and length is greater than 7", func(t *testing.T) { - d, err := Parse("did:a:1") + d, err := ParseDID("did:a:1") assert(t, nil, err) assert(t, true, d != nil) }) t.Run("succeeds to extract method", func(t *testing.T) { - d, err := Parse("did:a:1") + d, err := ParseDID("did:a:1") assert(t, nil, err) assert(t, "a", d.Method) - d, err = Parse("did:abcdef:11111") + d, err = ParseDID("did:abcdef:11111") assert(t, nil, err) assert(t, "abcdef", d.Method) }) t.Run("returns error if method has any other char than 0-9 or a-z", func(t *testing.T) { - _, err := Parse("did:aA:1") + _, err := ParseDID("did:aA:1") assert(t, false, err == nil) - _, err = Parse("did:aa-aa:1") + _, err = ParseDID("did:aa-aa:1") assert(t, false, err == nil) }) t.Run("succeeds to extract id", func(t *testing.T) { - d, err := Parse("did:a:1") + d, err := ParseDID("did:a:1") assert(t, nil, err) assert(t, "1", d.ID) }) t.Run("succeeds to extract id parts", func(t *testing.T) { - d, err := Parse("did:a:123:456") + d, err := ParseDID("did:a:123:456") assert(t, nil, err) parts := d.IDStrings @@ -249,39 +249,39 @@ func TestParse(t *testing.T) { }) t.Run("returns error if ID has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:1&&111") + _, err := ParseDID("did:a:1&&111") assert(t, false, err == nil) }) t.Run("returns error if param name is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456;") + _, err := ParseDID("did:a:123:456;") assert(t, false, err == nil) }) t.Run("returns error if Param name has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456;serv&ce") + _, err := ParseDID("did:a:123:456;serv&ce") assert(t, false, err == nil) }) t.Run("returns error if Param value has an invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456;service=ag&nt") + _, err := ParseDID("did:a:123:456;service=ag&nt") assert(t, false, err == nil) }) t.Run("returns error if Param name has an invalid percent encoded", func(t *testing.T) { - _, err := Parse("did:a:123:456;ser%2ge") + _, err := ParseDID("did:a:123:456;ser%2ge") assert(t, false, err == nil) }) t.Run("returns error if Param does not exist for value", func(t *testing.T) { - _, err := Parse("did:a:123:456;=value") + _, err := ParseDID("did:a:123:456;=value") assert(t, false, err == nil) }) // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract generic param with name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;service==agent") + d, err := ParseDID("did:a:123:456;service==agent") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service=agent", d.Params[0].String()) @@ -292,7 +292,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract generic param with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;service") + d, err := ParseDID("did:a:123:456;service") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service", d.Params[0].String()) @@ -303,7 +303,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract generic param with name only and empty param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=") + d, err := ParseDID("did:a:123:456;service=") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service", d.Params[0].String()) @@ -314,7 +314,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract method param with name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo:bar=baz") + d, err := ParseDID("did:a:123:456;foo:bar=baz") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "foo:bar=baz", d.Params[0].String()) @@ -325,7 +325,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract method param with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo:bar") + d, err := ParseDID("did:a:123:456;foo:bar") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "foo:bar", d.Params[0].String()) @@ -336,7 +336,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds with percent encoded chars in param name and value", func(t *testing.T) { - d, err := Parse("did:a:123:456;serv%20ice=val%20ue") + d, err := ParseDID("did:a:123:456;serv%20ice=val%20ue") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "serv%20ice=val%20ue", d.Params[0].String()) @@ -347,7 +347,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract multiple generic params with name only", func(t *testing.T) { - d, err := Parse("did:a:123:456;foo;bar") + d, err := ParseDID("did:a:123:456;foo;bar") assert(t, nil, err) assert(t, 2, len(d.Params)) assert(t, "foo", d.Params[0].Name) @@ -359,7 +359,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract multiple params with names and values", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=agent;foo:bar=baz") + d, err := ParseDID("did:a:123:456;service=agent;foo:bar=baz") assert(t, nil, err) assert(t, 2, len(d.Params)) assert(t, "service", d.Params[0].Name) @@ -371,7 +371,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract path after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service==value/a/b") + d, err := ParseDID("did:a:123:456;service==value/a/b") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service=value", d.Params[0].String()) @@ -386,7 +386,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract path after generic param name and no value", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=/a/b") + d, err := ParseDID("did:a:123:456;service=/a/b") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service", d.Params[0].String()) @@ -401,7 +401,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract query after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=value?abc") + d, err := ParseDID("did:a:123:456;service=value?abc") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service=value", d.Params[0].String()) @@ -413,7 +413,7 @@ func TestParse(t *testing.T) { // nolint: dupl // test for params look similar to linter t.Run("succeeds to extract fragment after generic param", func(t *testing.T) { - d, err := Parse("did:a:123:456;service=value#xyz") + d, err := ParseDID("did:a:123:456;service=value#xyz") assert(t, nil, err) assert(t, 1, len(d.Params)) assert(t, "service=value", d.Params[0].String()) @@ -423,13 +423,13 @@ func TestParse(t *testing.T) { }) t.Run("succeeds to extract path", func(t *testing.T) { - d, err := Parse("did:a:123:456/someService") + d, err := ParseDID("did:a:123:456/someService") assert(t, nil, err) assert(t, "someService", d.Path) }) t.Run("succeeds to extract path segements", func(t *testing.T) { - d, err := Parse("did:a:123:456/a/b") + d, err := ParseDID("did:a:123:456/a/b") assert(t, nil, err) segments := d.PathSegments @@ -438,7 +438,7 @@ func TestParse(t *testing.T) { }) t.Run("succeeds with percent encoded chars in path", func(t *testing.T) { - d, err := Parse("did:a:123:456/a/%20a") + d, err := ParseDID("did:a:123:456/a/%20a") assert(t, nil, err) assert(t, "a/%20a", d.Path) }) @@ -453,38 +453,38 @@ func TestParse(t *testing.T) { "did:a:123:456/%A%", } for _, did := range dids { - _, err := Parse(did) + _, err := ParseDID(did) assert(t, false, err == nil, "Input: %s", did) } }) t.Run("returns error if path is empty but there is a slash", func(t *testing.T) { - _, err := Parse("did:a:123:456/") + _, err := ParseDID("did:a:123:456/") assert(t, false, err == nil) }) t.Run("returns error if first path segment is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456//abc") + _, err := ParseDID("did:a:123:456//abc") assert(t, false, err == nil) }) t.Run("does not fail if second path segment is empty", func(t *testing.T) { - _, err := Parse("did:a:123:456/abc//pqr") + _, err := ParseDID("did:a:123:456/abc//pqr") assert(t, nil, err) }) t.Run("returns error if path has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456/ssss^sss") + _, err := ParseDID("did:a:123:456/ssss^sss") assert(t, false, err == nil) }) t.Run("does not fail if path has atleast one segment and a trailing slash", func(t *testing.T) { - _, err := Parse("did:a:123:456/a/b/") + _, err := ParseDID("did:a:123:456/a/b/") assert(t, nil, err) }) t.Run("succeeds to extract query after idstring", func(t *testing.T) { - d, err := Parse("did:a:123?abc") + d, err := ParseDID("did:a:123?abc") assert(t, nil, err) assert(t, "a", d.Method) assert(t, "123", d.ID) @@ -492,7 +492,7 @@ func TestParse(t *testing.T) { }) t.Run("succeeds to extract query after path", func(t *testing.T) { - d, err := Parse("did:a:123/a/b/c?abc") + d, err := ParseDID("did:a:123/a/b/c?abc") assert(t, nil, err) assert(t, "a", d.Method) assert(t, "123", d.ID) @@ -501,14 +501,14 @@ func TestParse(t *testing.T) { }) t.Run("succeeds to extract fragment after query", func(t *testing.T) { - d, err := Parse("did:a:123?abc#xyz") + d, err := ParseDID("did:a:123?abc#xyz") assert(t, nil, err) assert(t, "abc", d.Query) assert(t, "xyz", d.Fragment) }) t.Run("succeeds with percent encoded chars in query", func(t *testing.T) { - d, err := Parse("did:a:123?ab%20c") + d, err := ParseDID("did:a:123?ab%20c") assert(t, nil, err) assert(t, "ab%20c", d.Query) }) @@ -523,24 +523,24 @@ func TestParse(t *testing.T) { "did:a:123:456?%A%", } for _, did := range dids { - _, err := Parse(did) + _, err := ParseDID(did) assert(t, false, err == nil, "Input: %s", did) } }) t.Run("returns error if query has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456?ssss^sss") + _, err := ParseDID("did:a:123:456?ssss^sss") assert(t, false, err == nil) }) t.Run("succeeds to extract fragment", func(t *testing.T) { - d, err := Parse("did:a:123:456#keys-1") + d, err := ParseDID("did:a:123:456#keys-1") assert(t, nil, err) assert(t, "keys-1", d.Fragment) }) t.Run("succeeds with percent encoded chars in fragment", func(t *testing.T) { - d, err := Parse("did:a:123:456#aaaaaa%20a") + d, err := ParseDID("did:a:123:456#aaaaaa%20a") assert(t, nil, err) assert(t, "aaaaaa%20a", d.Fragment) }) @@ -555,13 +555,13 @@ func TestParse(t *testing.T) { "did:xyz:pqr#%A%", } for _, did := range dids { - _, err := Parse(did) + _, err := ParseDID(did) assert(t, false, err == nil, "Input: %s", did) } }) t.Run("fails if fragment has invalid char", func(t *testing.T) { - _, err := Parse("did:a:123:456#ssss^sss") + _, err := ParseDID("did:a:123:456#ssss^sss") assert(t, false, err == nil) }) } diff --git a/did/json.go b/w3c/json.go similarity index 87% rename from did/json.go rename to w3c/json.go index 1c6c011..faf8343 100644 --- a/did/json.go +++ b/w3c/json.go @@ -1,4 +1,4 @@ -package did +package w3c import "encoding/json" @@ -9,7 +9,7 @@ func (did *DID) UnmarshalJSON(bytes []byte) error { return err } - did3, err := Parse(didStr) + did3, err := ParseDID(didStr) if err != nil { return err } From 9b738bfdd1fdb89da2f1166abbaadcd878f40f28 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Tue, 30 May 2023 11:14:50 -0400 Subject: [PATCH 35/37] Remove unused constant NoChaine --- did.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/did.go b/did.go index 46a89ca..505d254 100644 --- a/did.go +++ b/did.go @@ -50,8 +50,6 @@ const ( UnknownChain Blockchain = "unknown" // ReadOnly should be used for readonly identity to build readonly flag ReadOnly Blockchain = "readonly" - // NoChain can be used for identity to build readonly flag - NoChain Blockchain = "" ) // NetworkID is method specific network identifier From 014f51e92da5c0c89c95c31e42bfca1652d2ad14 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Tue, 30 May 2023 11:25:09 -0400 Subject: [PATCH 36/37] Revert "Remove unused constant NoChaine" This reverts commit 9b738bfdd1fdb89da2f1166abbaadcd878f40f28. --- did.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/did.go b/did.go index 505d254..46a89ca 100644 --- a/did.go +++ b/did.go @@ -50,6 +50,8 @@ const ( UnknownChain Blockchain = "unknown" // ReadOnly should be used for readonly identity to build readonly flag ReadOnly Blockchain = "readonly" + // NoChain can be used for identity to build readonly flag + NoChain Blockchain = "" ) // NetworkID is method specific network identifier From 10cc46459e27fc5c249a0a0e4e405dd415fcd543 Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Thu, 27 Jul 2023 05:30:03 -0400 Subject: [PATCH 37/37] add sepolia testnet (#453) --- did.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/did.go b/did.go index 46a89ca..7d0a989 100644 --- a/did.go +++ b/did.go @@ -65,6 +65,8 @@ const ( // Goerli is ethereum goerli test network Goerli NetworkID = "goerli" // goerli + // Sepolia is ethereum Sepolia test network + Sepolia NetworkID = "sepolia" // UnknownNetwork is used when it's not possible to retrieve network from identifier UnknownNetwork NetworkID = "unknown" @@ -93,8 +95,9 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, - {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, - {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, + {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + {Blockchain: Ethereum, NetworkID: Sepolia}: 0b00100000 | 0b00000011, }, DIDMethodPolygonID: { {Blockchain: ReadOnly, NetworkID: NoNetwork}: 0b00000000, @@ -102,8 +105,9 @@ var DIDMethodNetwork = map[DIDMethod]map[DIDNetworkFlag]byte{ {Blockchain: Polygon, NetworkID: Main}: 0b00010000 | 0b00000001, {Blockchain: Polygon, NetworkID: Mumbai}: 0b00010000 | 0b00000010, - {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, - {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + {Blockchain: Ethereum, NetworkID: Main}: 0b00100000 | 0b00000001, + {Blockchain: Ethereum, NetworkID: Goerli}: 0b00100000 | 0b00000010, + {Blockchain: Ethereum, NetworkID: Sepolia}: 0b00100000 | 0b00000011, }, DIDMethodOther: { {Blockchain: UnknownChain, NetworkID: UnknownNetwork}: 0b11111111,