From 8874616dc972720251d3bdb27b797c34e720b7bb Mon Sep 17 00:00:00 2001 From: Oleg Lomaka Date: Fri, 21 Apr 2023 03:24:05 -0400 Subject: [PATCH 01/18] 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 02/18] 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 03/18] 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 04/18] 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 05/18] 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 06/18] 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 07/18] 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 08/18] 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 09/18] 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 10/18] 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 11/18] 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 12/18] 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 13/18] 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 14/18] 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 15/18] 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 16/18] 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 17/18] 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 18/18] 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) +}