diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6724b1f12..ce26a1031 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -215,7 +215,7 @@ jobs: run: | ./add-observer.sh cheqd-noded status -n tcp://localhost:26677 2>&1 - ./wait.sh "[[ $(cheqd-noded status -n '"'"'tcp://localhost:26677'"'"' 2>&1 | wc -l) == 1 ]] && echo "New node returns status!"" + ./wait.sh "[[ $(cheqd-noded status -n 'tcp://localhost:26677' 2>&1 | wc -l) == 1 ]] && echo "New node returns status!"" - name: Promote observer to validator working-directory: ./tests/e2e-complex/deb-install diff --git a/docker/Dockerfile b/docker/Dockerfile index 54ad0680f..81d6d7bf7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ ### STAGE 1: Build node binary pre-requisites ### ############################################################### -FROM golang:buster as builder +FROM golang:1.17.8-buster as builder RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends \ diff --git a/go.mod b/go.mod index 245165e57..1a6b59428 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,16 @@ module github.com/cheqd/cheqd-node go 1.17 require ( + filippo.io/edwards25519 v1.0.0-beta.2 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce - github.com/cosmos/cosmos-sdk v0.44.5 - github.com/cosmos/ibc-go v1.2.3 + github.com/cosmos/cosmos-sdk v0.45.1 + github.com/cosmos/ibc-go v1.4.0 + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/lestrrat-go/jwx v1.2.20 github.com/multiformats/go-multibase v0.0.3 github.com/rakyll/statik v0.1.7 github.com/spf13/cast v1.4.1 @@ -24,19 +27,19 @@ require ( ) require ( - filippo.io/edwards25519 v1.0.0-beta.2 // indirect github.com/99designs/keyring v1.1.6 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/DataDog/zstd v1.4.5 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect - github.com/armon/go-metrics v0.3.9 // indirect + github.com/armon/go-metrics v0.3.10 // indirect + github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coinbase/rosetta-sdk-go v0.6.10 // indirect - github.com/confio/ics23/go v0.6.6 // indirect + github.com/confio/ics23/go v0.7.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/iavl v0.17.3 // indirect @@ -44,6 +47,7 @@ require ( github.com/cosmos/ledger-go v0.9.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.2 // indirect github.com/dgraph-io/ristretto v0.0.3 // indirect @@ -55,6 +59,8 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/goccy/go-json v0.9.4 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/snappy v0.0.3 // indirect @@ -75,6 +81,12 @@ require ( github.com/jmhodges/levigo v1.0.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect github.com/klauspost/compress v1.13.6 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.0 // indirect + github.com/lestrrat-go/iter v1.0.1 // indirect + github.com/lestrrat-go/option v1.0.0 // indirect github.com/lib/pq v1.10.4 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.5 // indirect @@ -111,8 +123,8 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect - golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index c6f78f652..dcbb283a5 100644 --- a/go.sum +++ b/go.sum @@ -111,11 +111,14 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1: github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -184,8 +187,9 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM= github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo= -github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8= github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= +github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.2.0 h1:j/9Wnn+hrEWjLvHuIxUU1YI5JjEjVlT2AA68cse9rwY= @@ -210,8 +214,8 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.17.3 h1:s2N819a2olOmiauVa0WAhoIJq9EhSXE9HDBAoR9k+8Y= github.com/cosmos/iavl v0.17.3/go.mod h1:prJoErZFABYZGDHka1R6Oay4z9PrNeFFiMKHDAMOi4w= -github.com/cosmos/ibc-go v1.2.3 h1:+Xdhshvls9c6NQNAj7jz5YUtpqCd7AwadEImpaeVReo= -github.com/cosmos/ibc-go v1.2.3/go.mod h1:TNJMo+fPU4GmpAGxqedjuA1l6izRLGPvuIRLpWAbXuE= +github.com/cosmos/ibc-go v1.4.0 h1:oQIFITF4oOkQNTHkKTan1e4ozvFAp34z1rnIbEnn/e0= +github.com/cosmos/ibc-go v1.4.0/go.mod h1:Ntn57AowrB7bmhFKFrmXbysf290sAZHHBpwZMyNh/vo= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= @@ -229,6 +233,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= @@ -313,11 +320,15 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= @@ -331,6 +342,8 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -471,9 +484,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -572,8 +584,21 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.20 h1:ckMNlG0MqCcVp7LnD5FN2+459ndm7SW3vryE79Dz9nk= +github.com/lestrrat-go/jwx v1.2.20/go.mod h1:tLE1XszaFgd7zaS5wHe4NxA+XVhu7xgdRvDpNyi3kNM= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -999,8 +1024,10 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1098,9 +1125,11 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b h1:SXy8Ld8oKlcogOvUAh0J5Pm5RKzgYBMMxLxt6n5XW50= golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1215,7 +1244,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= diff --git a/proto/cheqd/v1/did.proto b/proto/cheqd/v1/did.proto index 10671b7eb..bbf3b98ea 100644 --- a/proto/cheqd/v1/did.proto +++ b/proto/cheqd/v1/did.proto @@ -33,5 +33,3 @@ message Service { string type = 2; string service_endpoint = 3; } - - diff --git a/proto/cheqd/v1/query.proto b/proto/cheqd/v1/query.proto index 2f0e576cf..785ba0d17 100644 --- a/proto/cheqd/v1/query.proto +++ b/proto/cheqd/v1/query.proto @@ -11,7 +11,7 @@ import "cheqd/v1/stateValue.proto"; // Query defines the gRPC querier service. service Query { rpc Did(QueryGetDidRequest) returns (QueryGetDidResponse) { - option (google.api.http).get = "/cheqd/cheqdnode/cheqd/did/{id}"; + option (google.api.http).get = "/cheqd/v1/did/{id}"; } } @@ -22,4 +22,4 @@ message QueryGetDidRequest { message QueryGetDidResponse { Did did = 1; Metadata metadata = 2; -} \ No newline at end of file +} diff --git a/tests/e2e-bash/tests/common.sh b/tests/e2e-bash/tests/common.sh index b40674da4..9b54bd648 100644 --- a/tests/e2e-bash/tests/common.sh +++ b/tests/e2e-bash/tests/common.sh @@ -22,7 +22,10 @@ export DELAYED_VESTING_ACCOUNT="delayed_vesting_account" export PERIODIC_VESTING_ACCOUNT="periodic_vesting_account" function random_string() { - echo $RANDOM | base64 | head -c 20 + LENGTH=${1:-16} # Default LENGTH is 16 + ALPHABET=${2:-"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"} # Default is base58 + + yes $RANDOM | base64 | tr -dc "$ALPHABET" | head -c "${LENGTH}" return 0 } @@ -56,3 +59,14 @@ function assert_tx_code() { CODE=$2 assert_eq "$(echo "${OUTPUT}" | jq -r ".code")" "$CODE" } + +function assert_str_contains() { + STR=$1 + SUBSTR=$2 + + if [[ $STR == *$SUBSTR* ]]; then + return 0 + fi + + return 1 +} diff --git a/tests/e2e-bash/tests/test_create_did.sh b/tests/e2e-bash/tests/test_create_did.sh index 4efe626eb..01243f114 100644 --- a/tests/e2e-bash/tests/test_create_did.sh +++ b/tests/e2e-bash/tests/test_create_did.sh @@ -32,7 +32,7 @@ MSG_CREATE_DID='{ # Post the message # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" diff --git a/tests/e2e-bash/tests/test_create_did_with_invalid_pubkey.sh b/tests/e2e-bash/tests/test_create_did_with_invalid_pubkey.sh index 75596ca70..196cfbe8f 100644 --- a/tests/e2e-bash/tests/test_create_did_with_invalid_pubkey.sh +++ b/tests/e2e-bash/tests/test_create_did_with_invalid_pubkey.sh @@ -30,7 +30,7 @@ MSG_CREATE_DID='{ # Post the message # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ - --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ + --from "${BASE_ACCOUNT_1}" ${TX_PARAMS} 2>&1 || true) # Allow command to fail, redirect stderr to stdout -assert_tx_code "$RESULT" "1100" +assert_str_contains "$RESULT" "public_key_multibase: ed25519: bad public key length: 23" diff --git a/tests/e2e-bash/tests/test_query_did.sh b/tests/e2e-bash/tests/test_query_did.sh index 9872f6232..2019aaadf 100644 --- a/tests/e2e-bash/tests/test_query_did.sh +++ b/tests/e2e-bash/tests/test_query_did.sh @@ -30,7 +30,7 @@ MSG_CREATE_DID='{ }'; # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" diff --git a/tests/e2e-bash/tests/test_query_did_via_rest.sh b/tests/e2e-bash/tests/test_query_did_via_rest.sh index 6d199bd0a..4a0c0b7bb 100644 --- a/tests/e2e-bash/tests/test_query_did_via_rest.sh +++ b/tests/e2e-bash/tests/test_query_did_via_rest.sh @@ -30,14 +30,14 @@ MSG_CREATE_DID='{ }'; # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" # Query DID -RESULT=$(curl "http://localhost:1317/cheqd/cheqdnode/cheqd/did/${DID}") +RESULT=$(curl "http://localhost:1317/cheqd/v1/did/${DID}") EXPECTED='{ "context":[], diff --git a/tests/e2e-bash/tests/test_rotate_key.sh b/tests/e2e-bash/tests/test_rotate_key.sh index eaa6b4230..69b157e1f 100644 --- a/tests/e2e-bash/tests/test_rotate_key.sh +++ b/tests/e2e-bash/tests/test_rotate_key.sh @@ -30,13 +30,12 @@ MSG_CREATE_DID='{ }'; # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${OLD_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${OLD_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" # Query DID to find out version id -# TODO: VersionId must be returned in MsgCreateDidResp # shellcheck disable=SC2086 RESULT=$(cheqd-noded query cheqd did "${DID}" ${QUERY_PARAMS}) VERSION_ID=$(echo "${RESULT}" | jq -r ".metadata.version_id") @@ -45,6 +44,7 @@ VERSION_ID=$(echo "${RESULT}" | jq -r ".metadata.version_id") # Updating DID NEW_VER_KEY="$(cheqd-noded debug ed25519 random)" NEW_VER_PUB_BASE_64=$(echo "${NEW_VER_KEY}" | jq -r ".pub_key_base_64") +NEW_VER_PRIV_BASE_64=$(echo "${NEW_VER_KEY}" | jq -r ".priv_key_base_64") NEW_VER_PUB_MULTIBASE_58=$(cheqd-noded debug encoding base64-multibase58 "${NEW_VER_PUB_BASE_64}") MSG_UPDATE_DID='{ @@ -66,7 +66,7 @@ MSG_UPDATE_DID='{ # Post the message # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd update-did "${MSG_UPDATE_DID}" "${KEY_ID}" --ver-key "${OLD_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd update-did "${MSG_UPDATE_DID}" "${KEY_ID}" "${OLD_VER_PRIV_BASE_64}" "${KEY_ID}" "${NEW_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" diff --git a/tests/e2e-bash/tests/test_update_did.sh b/tests/e2e-bash/tests/test_update_did.sh index 41926423b..fadb87683 100644 --- a/tests/e2e-bash/tests/test_update_did.sh +++ b/tests/e2e-bash/tests/test_update_did.sh @@ -30,7 +30,7 @@ MSG_CREATE_DID='{ }'; # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" @@ -62,7 +62,7 @@ MSG_UPDATE_DID='{ # Post the message # shellcheck disable=SC2086 -RESULT=$(cheqd-noded tx cheqd update-did "${MSG_UPDATE_DID}" "${KEY_ID}" --ver-key "${ALICE_VER_PRIV_BASE_64}" \ +RESULT=$(cheqd-noded tx cheqd update-did "${MSG_UPDATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ --from "${BASE_ACCOUNT_1}" ${TX_PARAMS}) assert_tx_successful "$RESULT" diff --git a/tests/e2e-complex/upgrade/common.sh b/tests/e2e-complex/upgrade/common.sh index 442e70bda..c42c46d39 100644 --- a/tests/e2e-complex/upgrade/common.sh +++ b/tests/e2e-complex/upgrade/common.sh @@ -8,7 +8,7 @@ CHEQD_IMAGE_TO="cheqd-cli" # shellcheck disable=SC2034 CHEQD_VERSION_TO=$(git describe --always --tag --match "v*" | sed 's/^v//') # shellcheck disable=SC2034 -UPGRADE_NAME="v0.4" +UPGRADE_NAME="v0.5" VOTING_PERIOD=30 EXPECTED_BLOCK_SECOND=5 EXTRA_BLOCKS=5 @@ -23,20 +23,19 @@ AMOUNT_BEFORE="19000000000000000" CHEQ_AMOUNT="1ncheq" CHEQ_AMOUNT_NUMBER="1" # shellcheck disable=SC2034 -DID_1="did:cheqd:testnet:abcdef" +DID_1="did:cheqd:testnet:1111111111111111" # shellcheck disable=SC2034 -DID_2="did:cheqd:testnet:higklm" +DID_2="did:cheqd:testnet:2222222222222222" # cheqd_noded docker wrapper - cheqd_noded_docker() { - docker run --rm \ - -v "$(pwd):/cheqd" \ - --network host \ - -u root \ - -e HOME=/cheqd \ - --entrypoint "cheqd-noded" \ - ${CHEQD_IMAGE_FROM} "$@" + docker run --rm \ + -v "$(pwd):/cheqd" \ + --network host \ + -u root \ + -e HOME=/cheqd \ + --entrypoint "cheqd-noded" \ + ${CHEQD_IMAGE_FROM} "$@" } # Parameters @@ -54,18 +53,18 @@ function docker_exec () { function docker_compose_up () { pushd "node_configs/node0" NODE_0_ID=$(cheqd_noded_docker tendermint show-node-id | sed 's/\r//g') - export NODE_0_ID + export NODE_0_ID="$NODE_0_ID" popd export CHEQD_IMAGE_NAME="$1" export MOUNT_POINT="$2" - docker-compose --env-file .env up -d + docker compose --env-file .env up -d } # Stop docker-compose function docker_compose_down () { - docker-compose --env-file .env down + docker compose --env-file .env down } # Clean environment @@ -84,7 +83,6 @@ function make_777 () { } - # Transaction related funcs function random_string() { @@ -95,6 +93,7 @@ function random_string() { function get_addresses () { all_keys=$(local_client_tx keys list) mapfile -t addresses < <(echo "$all_keys" | grep -o 'cheqd1.*') +# addresses=( $(echo "$all_keys" | grep -o 'cheqd1.*') ) echo "${addresses[@]}" } @@ -118,6 +117,36 @@ function send_tokens() { echo "$txhash" >> "$FNAME_TXHASHES" } +# Send DID +# input: DID to write +function send_did_new () { + did_to_write=$1 + + # Generate Alice identity key + ALICE_VER_KEY="$(cheqd_noded_docker debug ed25519 random)" + ALICE_VER_PUB_BASE_64=$(echo "${ALICE_VER_KEY}" | jq -r ".pub_key_base_64") + ALICE_VER_PRIV_BASE_64=$(echo "${ALICE_VER_KEY}" | jq -r ".priv_key_base_64") + ALICE_VER_PUB_MULTIBASE_58=$(cheqd_noded_docker debug encoding base64-multibase58 "${ALICE_VER_PUB_BASE_64}") + + # Build CreateDid message + KEY_ID="${did_to_write}#key1" + + # shellcheck disable=SC2089 + MSG_CREATE_DID='{"id":"'${did_to_write}'","verification_method":[{"id":"'"${KEY_ID}"'","type":"Ed25519VerificationKey2020","controller":"'${did_to_write}'","public_key_multibase":"'${ALICE_VER_PUB_MULTIBASE_58}'"}],"authentication":["'${KEY_ID}'"]}' + + # Post the message + did=$(local_client_tx tx cheqd create-did "${MSG_CREATE_DID}" "${KEY_ID}" "${ALICE_VER_PRIV_BASE_64}" \ + --from operator0 \ + --gas-prices "25ncheq" \ + --chain-id $CHAIN_ID \ + --output json \ + -y) + + + txhash=$(echo "$did" | jq ".txhash" | tr -d '"') + echo "$txhash" >> $FNAME_TXHASHES +} + # Send DID # input: DID to write function send_did () { diff --git a/tests/e2e-complex/upgrade/upgrade_and_check.sh b/tests/e2e-complex/upgrade/upgrade_and_check.sh index bd543978e..6c891698b 100755 --- a/tests/e2e-complex/upgrade/upgrade_and_check.sh +++ b/tests/e2e-complex/upgrade/upgrade_and_check.sh @@ -5,6 +5,16 @@ set -euox pipefail # shellcheck disable=SC1091 . common.sh +cheqd_noded_docker() { + docker run --rm \ + -v "$(pwd):/home/cheqd" \ + --network host \ + -u root \ + -e HOME=/home/cheqd \ + --entrypoint "cheqd-noded" \ + "${CHEQD_IMAGE_TO}" "$@" +} + # Wait for upgrade height bash ../../tools/wait-for-chain.sh "$UPGRADE_HEIGHT" $((2 * VOTING_PERIOD)) @@ -46,7 +56,7 @@ check_did "$DID_1" send_tokens "$OP_ADDRESS_AFTER" # Send DID after upgrade -send_did "$DID_2" +send_did_new "$DID_2" # Check balance after token sending check_balance "$OP_ADDRESS_AFTER" diff --git a/tests/e2e-pytest/helpers.py b/tests/e2e-pytest/helpers.py index 5bc3a2986..a0adf7506 100644 --- a/tests/e2e-pytest/helpers.py +++ b/tests/e2e-pytest/helpers.py @@ -47,6 +47,7 @@ CODE_11 = "\"code\":11" CODE_1203 = "\"code\":1203" CODE_1100 = "\"code\":1100" +CODE_1101 = "\"code\":1101" CODE_0_DIGIT = 0 @@ -160,3 +161,20 @@ def generate_ed25519_key() -> dict: "", "") return json_loads(cli.read()) + +def generate_public_multibase() -> str: + ed25519_key = generate_ed25519_key() + pub_key_base_64 = ed25519_key["pub_key_base_64"] + + # Get multibase58 represantation + cli = run( + "cheqd-noded debug", + "encoding base64-multibase58", + fr"{pub_key_base_64}", + "") + + return cli.read().strip() + +def generate_did() -> str: + letters = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + return ''.join(random.choice(letters) for i in range(16)) diff --git a/tests/e2e-pytest/test_identity.py b/tests/e2e-pytest/test_identity.py index 2e234239b..cddbd458e 100644 --- a/tests/e2e-pytest/test_identity.py +++ b/tests/e2e-pytest/test_identity.py @@ -4,8 +4,8 @@ from helpers import run, LOCAL_SENDER_ADDRESS, LOCAL_RECEIVER_ADDRESS, LOCAL_NET_DESTINATION, GAS_PRICE, YES_FLAG, \ KEYRING_BACKEND_TEST, get_gas_extimation, CODE_0, TEST_NET_GAS_X_GAS_PRICES, generate_ed25519_key, random_string, \ - build_create_did_msg, json_loads, build_update_did_msg, CODE_1100, CODE_1203, get_balance, GAS_AMOUNT, CODE_5, \ - CODE_11 + build_create_did_msg, json_loads, build_update_did_msg, CODE_1101, CODE_1203, get_balance, GAS_AMOUNT, CODE_5, \ + CODE_11, generate_public_multibase, generate_did @pytest.mark.parametrize("magic_number_positive", [1.3, 2, 3, 10]) @@ -114,11 +114,11 @@ def test_did_query_non_existent(): "cheqd-noded query", "cheqd did", fr"{did}", - fr"Error: rpc error: code = InvalidArgument desc = not found: invalid request") + fr"Error: rpc error: code = InvalidArgument desc = did:cheqd:testnet:AbCdEfGh: not found: invalid request") def test_did_wrong_version_update(): - did = fr"did:cheqd:testnet:{random_string(5)}" + did = fr"did:cheqd:testnet:{generate_did()}" key_id = fr"{did}#key1" # Generate ed25519 key @@ -143,7 +143,7 @@ def test_did_wrong_version_update(): run( "cheqd-noded tx", "cheqd create-did", - f" '{msg_create_did}' {key_id} --from {LOCAL_SENDER_ADDRESS} --ver-key {priv_key_base_64} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", + f" '{msg_create_did}' {key_id} {priv_key_base_64} --from {LOCAL_SENDER_ADDRESS} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", fr"{CODE_0}") # Get the created DID for getting version_id @@ -170,12 +170,12 @@ def test_did_wrong_version_update(): run( "cheqd-noded tx", "cheqd update-did", - f" '{json.dumps(msg_update_did)}' {key_id} --from {LOCAL_SENDER_ADDRESS} --ver-key {priv_key_base_64} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", - fr"{CODE_1203}(.*?)\"raw_log\":\"(.*?)Expected(.*?)unexpected DID version") + f" '{json.dumps(msg_update_did)}' {key_id} {priv_key_base_64} --from {LOCAL_SENDER_ADDRESS} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", + fr"{CODE_1203}(.*?)\"raw_log\":\"(.*?)unexpected DID version") def test_did_wrong_verkey_update(): - did = fr"did:cheqd:testnet:{random_string(5)}" + did = fr"did:cheqd:testnet:{generate_did()}" key_id = fr"{did}#key1" # Generate ed25519 key @@ -200,7 +200,7 @@ def test_did_wrong_verkey_update(): run( "cheqd-noded tx", "cheqd create-did", - f" '{msg_create_did}' {key_id} --from {LOCAL_SENDER_ADDRESS} --ver-key {priv_key_base_64} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", + f" '{msg_create_did}' {key_id} {priv_key_base_64} --from {LOCAL_SENDER_ADDRESS} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", fr"{CODE_0}") # Get the created DID for getting version_id @@ -216,7 +216,7 @@ def test_did_wrong_verkey_update(): # Prepare and send update did message for getting an error msg_update_did = build_update_did_msg(did, key_id, - ver_pub_multibase_58 + "abc", + generate_public_multibase(), version_id) msg_update_did["capability_delegation"] = [key_id] @@ -224,9 +224,9 @@ def test_did_wrong_verkey_update(): # Create another ed25519 key for using the new one for signing new_priv_key_base_64 = generate_ed25519_key()["priv_key_base_64"] - # here we are expecting an 1203 error about wrong version_id + # here we are expecting an 1100 error about wrong version_id run( "cheqd-noded tx", "cheqd update-did", - f" '{json.dumps(msg_update_did)}' {key_id} --from {LOCAL_SENDER_ADDRESS} --ver-key {new_priv_key_base_64} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", - fr"{CODE_1100}(.*?)\"raw_log\":\"(.*?)invalid signature detected") \ No newline at end of file + f" '{json.dumps(msg_update_did)}' {key_id} {new_priv_key_base_64} --from {LOCAL_SENDER_ADDRESS} {LOCAL_NET_DESTINATION} {TEST_NET_GAS_X_GAS_PRICES} {YES_FLAG} {KEYRING_BACKEND_TEST}", + fr"{CODE_1101}(.*?)\"raw_log\":\"(.*?)signature is required but not found") diff --git a/x/cheqd/client/cli/tx.go b/x/cheqd/client/cli/tx.go index 87f96a713..0daf21e09 100644 --- a/x/cheqd/client/cli/tx.go +++ b/x/cheqd/client/cli/tx.go @@ -1,12 +1,24 @@ package cli import ( + "bufio" + "crypto/ed25519" + "encoding/base64" "fmt" "github.com/cheqd/cheqd-node/x/cheqd/types" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/spf13/cobra" ) +type SignInput struct { + verificationMethodId string + privKey ed25519.PrivateKey +} + // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -22,3 +34,94 @@ func GetTxCmd() *cobra.Command { return cmd } + +func GetPayloadAndSignInputs(clientCtx client.Context, args []string) (string, []SignInput, error) { + // Check for args count + if len(args)%2 != 1 { + return "", []SignInput{}, fmt.Errorf("invalid number of arguments: %d. must be an odd number", len(args)) + } + + // Get payload json + payloadJson := args[0] + + // Get signInputs + var signInputs []SignInput + + for i := 1; i < len(args); i += 2 { + vmId := args[i] + privKey := args[i+1] + + if privKey == "interactive" { + inBuf := bufio.NewReader(clientCtx.Input) + + var err error + privKey, err = input.GetString("Enter base64 encoded verification key", inBuf) + + if err != nil { + return "", nil, err + } + } + + privKeyBytes, err := base64.StdEncoding.DecodeString(privKey) + if err != nil { + return "", nil, fmt.Errorf("unable to decode private key: %s", err.Error()) + } + + signInput := SignInput{ + verificationMethodId: vmId, + privKey: privKeyBytes, + } + + signInputs = append(signInputs, signInput) + } + + return payloadJson, signInputs, nil +} + +func SignWithSignInputs(signBytes []byte, signInputs []SignInput) []*types.SignInfo { + var signatures []*types.SignInfo + + for _, signInput := range signInputs { + signatureBytes := ed25519.Sign(signInput.privKey, signBytes) + + signInfo := types.SignInfo{ + VerificationMethodId: signInput.verificationMethodId, + Signature: base64.StdEncoding.EncodeToString(signatureBytes), + } + + signatures = append(signatures, &signInfo) + } + + return signatures +} + +func SetFeePayerFromSigner(ctx *client.Context) error { + if ctx.FromAddress != nil { + ctx.FeePayer = ctx.FromAddress + return nil + } + + signerAccAddr, err := AccAddrByKeyRef(ctx.Keyring, ctx.From) + if err != nil { + return err + } + + ctx.FeePayer = signerAccAddr + return nil +} + +func AccAddrByKeyRef(keyring keyring.Keyring, keyRef string) (sdk.AccAddress, error) { + // Firstly check if the keyref is a key name of a key registered in a keyring + info, err := keyring.Key(keyRef) + + if err == nil { + return info.GetAddress(), nil + } + + if !sdkerrors.IsOf(err, sdkerrors.ErrIO, sdkerrors.ErrKeyNotFound) { + return nil, err + } + + // Fallback: convert keyref to address + return sdk.AccAddressFromBech32(keyRef) +} diff --git a/x/cheqd/client/cli/tx_create_did.go b/x/cheqd/client/cli/tx_create_did.go new file mode 100644 index 000000000..110a4654a --- /dev/null +++ b/x/cheqd/client/cli/tx_create_did.go @@ -0,0 +1,63 @@ +package cli + +import ( + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/spf13/cobra" +) + +func CmdCreateDid() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-did [payload-json] [ver-method-id-1] [priv-key-1] [ver-method-id-N] [priv-key-N] ...", + Short: "Creates a new DID.", + Long: "Creates a new DID. " + + "[payload-json] is JSON encoded MsgCreateDidPayload. " + + "[ver-method-id-N] is the DID fragment that points to the public part of the key in the ledger for the signature N." + + "[priv-key-1] is base base64 encoded ed25519 private key for signature N." + + "If 'interactive' value is used for a key, the key will be read interactively. " + + "Prefer interactive mode, use inline mode only for tests.", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + payloadJson, signInputs, err := GetPayloadAndSignInputs(clientCtx, args) + if err != nil { + return err + } + + // Unmarshal payload + var payload types.MsgCreateDidPayload + err = clientCtx.Codec.UnmarshalJSON([]byte(payloadJson), &payload) + if err != nil { + return err + } + + // Build identity message + signBytes := payload.GetSignBytes() + identitySignatures := SignWithSignInputs(signBytes, signInputs) + + msg := types.MsgCreateDid{ + Payload: &payload, + Signatures: identitySignatures, + } + + //Set fee-payer if not set + err = SetFeePayerFromSigner(&clientCtx) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + diff --git a/x/cheqd/client/cli/tx_did.go b/x/cheqd/client/cli/tx_did.go deleted file mode 100644 index 330b5e81f..000000000 --- a/x/cheqd/client/cli/tx_did.go +++ /dev/null @@ -1,136 +0,0 @@ -package cli - -import ( - "crypto/ed25519" - "encoding/base64" - - "github.com/cheqd/cheqd-node/x/cheqd/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/spf13/cobra" -) - -const FlagVerKey = "ver-key" - -func CmdCreateDid() *cobra.Command { - cmd := &cobra.Command{ - Use: "create-did [payload-json] [verification-method-id]", - Short: "Creates a new DID.", - Long: "Creates a new DID. [payload-json] is JSON encoded MsgCreateDidPayload. " + - "Key to sign the identity message (verKey) will be taken either from " + FlagVerKey + " flag or interactively." + - "[verification-method-id] is the DID fragment that points to the verKey.", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - payloadJson := args[0] - verificationMethodId := args[1] - - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - // Unmarshal payload - var payload types.MsgCreateDidPayload - err = clientCtx.Codec.UnmarshalJSON([]byte(payloadJson), &payload) - if err != nil { - return err - } - - // Get verKey - verKeyPriv, err := getVerKey(cmd, clientCtx) - if err != nil { - return err - } - - // Build identity message - signBytes := payload.GetSignBytes() - signatureBytes := ed25519.Sign(verKeyPriv, signBytes) - - signInfo := types.SignInfo{ - VerificationMethodId: verificationMethodId, - Signature: base64.StdEncoding.EncodeToString(signatureBytes), - } - - msg := types.MsgCreateDid{ - Payload: &payload, - Signatures: []*types.SignInfo{&signInfo}, - } - - //Set fee-payer if not set - err = setFeePayerFromSigner(&clientCtx) - if err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - cmd.Flags().String(FlagVerKey, "", "Base64 encoded ed25519 private key to sign identity message with. "+ - "Use for testing purposes only because the key will be stored in shell history. Prefer interactive mode.") - - return cmd -} - -func CmdUpdateDid() *cobra.Command { - cmd := &cobra.Command{ - Use: "update-did [payload-json] [verification-method-id]", - Short: "Update a DID.", - Long: "Update a DID. [payload-json] is JSON encoded MsgUpdateDidPayload. " + - "Key to sign the identity message (verKey) will be taken either from " + FlagVerKey + " flag or interactively." + - "[verification-method-id] is the DID fragment that points to the verKey.", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - payloadJson := args[0] - verificationMethodId := args[1] - - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - // Unmarshal payload - var payload types.MsgUpdateDidPayload - err = clientCtx.Codec.UnmarshalJSON([]byte(payloadJson), &payload) - if err != nil { - return err - } - - // Get verKey - verKeyPriv, err := getVerKey(cmd, clientCtx) - if err != nil { - return err - } - - // Build identity message - signBytes := payload.GetSignBytes() - signatureBytes := ed25519.Sign(verKeyPriv, signBytes) - - signInfo := types.SignInfo{ - VerificationMethodId: verificationMethodId, - Signature: base64.StdEncoding.EncodeToString(signatureBytes), - } - - msg := types.MsgUpdateDid{ - Payload: &payload, - Signatures: []*types.SignInfo{&signInfo}, - } - - //Set fee-payer if not set - err = setFeePayerFromSigner(&clientCtx) - if err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - cmd.Flags().String(FlagVerKey, "", "Base64 encoded ed25519 private key to sign identity message with. "+ - "Use for testing purposes only because the key will be stored in shell history. Prefer interactive mode.") - - return cmd -} diff --git a/x/cheqd/client/cli/tx_update_did.go b/x/cheqd/client/cli/tx_update_did.go new file mode 100644 index 000000000..e1a6bc104 --- /dev/null +++ b/x/cheqd/client/cli/tx_update_did.go @@ -0,0 +1,62 @@ +package cli + +import ( + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/spf13/cobra" +) + +func CmdUpdateDid() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-did [payload-json] [ver-method-id-1] [priv-key-1] [ver-method-id-N] [priv-key-N] ...", + Short: "Update a DID.", + Long: "Updates a DID. " + + "[payload-json] is JSON encoded MsgUpdateDidPayload. " + + "[ver-method-id-N] is the DID fragment that points to the public part of the key in the ledger for the signature N." + + "[priv-key-1] is base base64 encoded ed25519 private key for signature N." + + "If 'interactive' value is used for a key, the key will be read interactively. " + + "Prefer interactive mode, use inline mode only for tests.", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + payloadJson, signInputs, err := GetPayloadAndSignInputs(clientCtx, args) + if err != nil { + return err + } + + // Unmarshal payload + var payload types.MsgUpdateDidPayload + err = clientCtx.Codec.UnmarshalJSON([]byte(payloadJson), &payload) + if err != nil { + return err + } + + // Build identity message + signBytes := payload.GetSignBytes() + identitySignatures := SignWithSignInputs(signBytes, signInputs) + + msg := types.MsgUpdateDid{ + Payload: &payload, + Signatures: identitySignatures, + } + + //Set fee-payer if not set + err = SetFeePayerFromSigner(&clientCtx) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/cheqd/client/cli/tx_utils.go b/x/cheqd/client/cli/tx_utils.go deleted file mode 100644 index af2c1fee0..000000000 --- a/x/cheqd/client/cli/tx_utils.go +++ /dev/null @@ -1,70 +0,0 @@ -package cli - -import ( - "bufio" - "crypto/ed25519" - "encoding/base64" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/input" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerr "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/spf13/cobra" -) - -func setFeePayerFromSigner(ctx *client.Context) error { - if ctx.FromAddress != nil { - ctx.FeePayer = ctx.FromAddress - return nil - } - - signerAccAddr, err := accAddrByKeyRef(ctx.Keyring, ctx.From) - if err != nil { - return err - } - - ctx.FeePayer = signerAccAddr - return nil -} - -func accAddrByKeyRef(keyring keyring.Keyring, keyRef string) (sdk.AccAddress, error) { - // Firstly check if the keyref is a key name of a key registered in a keyring - info, err := keyring.Key(keyRef) - - if err == nil { - return info.GetAddress(), nil - } - - if !sdkerr.IsOf(err, sdkerr.ErrIO, sdkerr.ErrKeyNotFound) { - return nil, err - } - - // Fallback: convert keyref to address - return sdk.AccAddressFromBech32(keyRef) -} - -func getVerKey(cmd *cobra.Command, clientCtx client.Context) (ed25519.PrivateKey, error) { - // Try getting from arg - verKeyPrivBase64, err := cmd.Flags().GetString(FlagVerKey) - if err != nil { - return nil, err - } - - // Get interactively instead if the flag isn't provided - if verKeyPrivBase64 == "" { - inBuf := bufio.NewReader(clientCtx.Input) - verKeyPrivBase64, err = input.GetString("Enter base64 encoded verification key", inBuf) - if err != nil { - return nil, err - } - } - - // Decode key - verKeyPrivBytes, err := base64.StdEncoding.DecodeString(verKeyPrivBase64) - if err != nil { - return nil, err - } - - return verKeyPrivBytes, nil -} diff --git a/x/cheqd/genesis.go b/x/cheqd/genesis.go index f4a60ab2e..45da3606b 100644 --- a/x/cheqd/genesis.go +++ b/x/cheqd/genesis.go @@ -16,13 +16,13 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) panic(fmt.Sprintf("Cannot import geneses case: %s", err.Error())) } - if err = k.SetDid(ctx, *did, elem.Metadata); err != nil { + if err = k.SetDid(&ctx, did, elem.Metadata); err != nil { panic(fmt.Sprintf("Cannot set did case: %s", err.Error())) } } // Set nym count - k.SetDidCount(ctx, uint64(len(genState.DidList))) + k.SetDidCount(&ctx, uint64(len(genState.DidList))) k.SetDidNamespace(ctx, genState.DidNamespace) } @@ -33,7 +33,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { // this line is used by starport scaffolding # genesis/module/export // Get all did - didList := k.GetAllDid(ctx) + didList := k.GetAllDid(&ctx) for _, elem := range didList { elem := elem genesis.DidList = append(genesis.DidList, &elem) diff --git a/x/cheqd/keeper/keeper_did.go b/x/cheqd/keeper/keeper_did.go index 42526230e..53e5169db 100644 --- a/x/cheqd/keeper/keeper_did.go +++ b/x/cheqd/keeper/keeper_did.go @@ -9,7 +9,7 @@ import ( ) // GetDidCount get the total number of did -func (k Keeper) GetDidCount(ctx sdk.Context) uint64 { +func (k Keeper) GetDidCount(ctx *sdk.Context) uint64 { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidCountKey)) byteKey := types.KeyPrefix(types.DidCountKey) bz := store.Get(byteKey) @@ -30,7 +30,7 @@ func (k Keeper) GetDidCount(ctx sdk.Context) uint64 { } // SetDidCount set the total number of did -func (k Keeper) SetDidCount(ctx sdk.Context, count uint64) { +func (k Keeper) SetDidCount(ctx *sdk.Context, count uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidCountKey)) byteKey := types.KeyPrefix(types.DidCountKey) bz := []byte(strconv.FormatUint(count, 10)) @@ -38,51 +38,56 @@ func (k Keeper) SetDidCount(ctx sdk.Context, count uint64) { } // AppendDid appends a did in the store with a new id and updates the count -func (k Keeper) AppendDid(ctx sdk.Context, did types.Did, metadata *types.Metadata) (*string, error) { +func (k Keeper) AppendDid(ctx *sdk.Context, did *types.Did, metadata *types.Metadata) error { + // Check that did doesn't exist + if k.HasDid(ctx, did.Id) { + return types.ErrDidDocExists.Wrapf(did.Id) + } + // Create the did count := k.GetDidCount(ctx) err := k.SetDid(ctx, did, metadata) if err != nil { - return nil, err + return err } // Update did count k.SetDidCount(ctx, count+1) - return &did.Id, nil + return nil } // SetDid set a specific did in the store -func (k Keeper) SetDid(ctx sdk.Context, did types.Did, metadata *types.Metadata) error { - stateValue, err := types.NewStateValue(&did, metadata) +func (k Keeper) SetDid(ctx *sdk.Context, did *types.Did, metadata *types.Metadata) error { + stateValue, err := types.NewStateValue(did, metadata) if err != nil { - return types.ErrSetToState.Wrap(err.Error()) + return err } store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidKey)) - b := k.cdc.MustMarshal(stateValue) + b := k.cdc.MustMarshal(&stateValue) store.Set(GetDidIDBytes(did.Id), b) return nil } // GetDid returns a did from its id -func (k Keeper) GetDid(ctx *sdk.Context, id string) (*types.StateValue, error) { +func (k Keeper) GetDid(ctx *sdk.Context, id string) (types.StateValue, error) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidKey)) - if !k.HasDid(*ctx, id) { - return nil, sdkerrors.ErrNotFound + if !k.HasDid(ctx, id) { + return types.StateValue{}, sdkerrors.ErrNotFound.Wrap(id) } var value types.StateValue var bytes = store.Get(GetDidIDBytes(id)) if err := k.cdc.Unmarshal(bytes, &value); err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidType, err.Error()) + return types.StateValue{}, sdkerrors.Wrap(sdkerrors.ErrInvalidType, err.Error()) } - return &value, nil + return value, nil } // HasDid checks if the did exists in the store -func (k Keeper) HasDid(ctx sdk.Context, id string) bool { +func (k Keeper) HasDid(ctx *sdk.Context, id string) bool { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidKey)) return store.Has(GetDidIDBytes(id)) } @@ -93,11 +98,16 @@ func GetDidIDBytes(id string) []byte { } // GetAllDid returns all did -func (k Keeper) GetAllDid(ctx sdk.Context) (list []types.StateValue) { +func (k Keeper) GetAllDid(ctx *sdk.Context) (list []types.StateValue) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.DidKey)) iterator := sdk.KVStorePrefixIterator(store, []byte{}) - defer iterator.Close() + defer func(iterator sdk.Iterator) { + err := iterator.Close() + if err != nil { + panic(err.Error()) + } + }(iterator) for ; iterator.Valid(); iterator.Next() { var val types.StateValue diff --git a/x/cheqd/keeper/msg_server.go b/x/cheqd/keeper/msg_server.go index c4bad0bba..2531f171e 100644 --- a/x/cheqd/keeper/msg_server.go +++ b/x/cheqd/keeper/msg_server.go @@ -1,7 +1,10 @@ package keeper import ( + "encoding/base64" "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + sdk "github.com/cosmos/cosmos-sdk/types" ) type msgServer struct { @@ -14,3 +17,92 @@ func NewMsgServer(keeper Keeper) types.MsgServer { } var _ types.MsgServer = msgServer{} + +func FindDid(k *Keeper, ctx *sdk.Context, inMemoryDIDs map[string]types.StateValue, did string) (res types.StateValue, found bool, err error) { + // Look in inMemory dict + value, found := inMemoryDIDs[did] + if found { + return value, true, nil + } + + // Look in state + if k.HasDid(ctx, did) { + value, err := k.GetDid(ctx, did) + if err != nil { + return types.StateValue{}, false, err + } + + return value, true, nil + } + + return types.StateValue{}, false, nil +} + +func MustFindDid(k *Keeper, ctx *sdk.Context, inMemoryDIDs map[string]types.StateValue, did string) (res types.StateValue, err error) { + res, found, err := FindDid(k, ctx, inMemoryDIDs, did) + + if err != nil { + return types.StateValue{}, err + } + + if !found { + return types.StateValue{}, types.ErrDidDocNotFound.Wrap(did) + } + + return res, nil +} + +func FindVerificationMethod(k *Keeper, ctx *sdk.Context, inMemoryDIDs map[string]types.StateValue, didUrl string) (res types.VerificationMethod, found bool, err error) { + did, _, _, _ := utils.MustSplitDIDUrl(didUrl) + + stateValue, found, err := FindDid(k, ctx, inMemoryDIDs, did) + if err != nil || !found { + return types.VerificationMethod{}, found, err + } + + didDoc, err := stateValue.UnpackDataAsDid() + if err != nil { + return types.VerificationMethod{}, false, err + } + + for _, vm := range didDoc.VerificationMethod { + if vm.Id == didUrl { + return *vm, true, nil + } + } + + return types.VerificationMethod{}, false, nil +} + +func MustFindVerificationMethod(k *Keeper, ctx *sdk.Context, inMemoryDIDs map[string]types.StateValue, didUrl string) (res types.VerificationMethod, err error) { + res, found, err := FindVerificationMethod(k, ctx, inMemoryDIDs, didUrl) + + if err != nil { + return types.VerificationMethod{}, err + } + + if !found { + return types.VerificationMethod{}, types.ErrVerificationMethodNotFound.Wrap(didUrl) + } + + return res, nil +} + +func VerifySignature(k *Keeper, ctx *sdk.Context, inMemoryDIDs map[string]types.StateValue, message []byte, signature types.SignInfo) error { + verificationMethod, err := MustFindVerificationMethod(k, ctx, inMemoryDIDs, signature.VerificationMethodId) + if err != nil { + return err + } + + signatureBytes, err := base64.StdEncoding.DecodeString(signature.Signature) + if err != nil { + return err + } + + err = types.VerifySignature(verificationMethod, message, signatureBytes) + if err != nil { + return types.ErrInvalidSignature.Wrapf("method id: %s", signature.VerificationMethodId) + } + + return nil +} diff --git a/x/cheqd/keeper/msg_server_create_did.go b/x/cheqd/keeper/msg_server_create_did.go new file mode 100644 index 000000000..0f74326a5 --- /dev/null +++ b/x/cheqd/keeper/msg_server_create_did.go @@ -0,0 +1,78 @@ +package keeper + +import ( + "context" + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k msgServer) CreateDid(goCtx context.Context, msg *types.MsgCreateDid) (*types.MsgCreateDidResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Validate DID doesn't exist + if k.HasDid(&ctx, msg.Payload.Id) { + return nil, types.ErrDidDocExists.Wrap(msg.Payload.Id) + } + + // Validate namespaces + namespace := k.GetDidNamespace(ctx) + err := msg.Validate([]string{namespace}) + if err != nil { + return nil, types.ErrNamespaceValidation.Wrap(err.Error()) + } + + // Build metadata and stateValue + did := msg.Payload.ToDid() + metadata := types.NewMetadataFromContext(ctx) + stateValue, err := types.NewStateValue(&did, &metadata) + if err != nil { + return nil, err + } + + // Consider did that we are going to create during did resolutions + inMemoryDids := map[string]types.StateValue{did.Id: stateValue} + + // Check controllers' existence + controllers := did.AllControllerDids() + for _, controller := range controllers { + _, err := MustFindDid(&k.Keeper, &ctx, inMemoryDids, controller) + + if err != nil { + return nil, err + } + } + + // Verify signatures + signers := GetSignerDIDsForDIDCreation(did) + for _, signer := range signers { + signature, found := types.FindSignInfoBySigner(msg.Signatures, signer) + + if !found { + return nil, types.ErrSignatureNotFound.Wrapf("signer: %s", signer) + } + + err := VerifySignature(&k.Keeper, &ctx, inMemoryDids, msg.Payload.GetSignBytes(), signature) + if err != nil { + return nil, err + } + } + + // Apply changes + err = k.AppendDid(&ctx, &did, &metadata) + if err != nil { + return nil, types.ErrInternal.Wrapf(err.Error()) + } + + // Build and return response + return &types.MsgCreateDidResponse{ + Id: did.Id, + }, nil +} + +func GetSignerDIDsForDIDCreation(did types.Did) []string { + res := did.GetControllersOrSubject() + res = append(res, did.GetVerificationMethodControllers()...) + + return utils.UniqueSorted(res) +} diff --git a/x/cheqd/keeper/msg_server_did.go b/x/cheqd/keeper/msg_server_did.go deleted file mode 100644 index 41df790b8..000000000 --- a/x/cheqd/keeper/msg_server_did.go +++ /dev/null @@ -1,199 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - "reflect" - - "github.com/cheqd/cheqd-node/x/cheqd/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -func (k msgServer) CreateDid(goCtx context.Context, msg *types.MsgCreateDid) (*types.MsgCreateDidResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - prefix := k.GetDidPrefix(ctx) - - didMsg := msg.GetPayload() - if err := didMsg.Validate(prefix); err != nil { - return nil, err - } - - if err := k.ValidateDidControllers(&ctx, didMsg.Id, didMsg.Controller, didMsg.VerificationMethod); err != nil { - return nil, err - } - - if err := k.VerifySignature(&ctx, didMsg, didMsg.GetSigners(), msg.GetSignatures()); err != nil { - return nil, err - } - - // Checks that the did doesn't exist - if k.HasDid(ctx, didMsg.Id) { - return nil, sdkerrors.Wrap(types.ErrDidDocExists, fmt.Sprintf("DID is already used by DIDDoc %s", didMsg.Id)) - } - - var did = types.Did{ - Context: didMsg.Context, - Id: didMsg.Id, - Controller: didMsg.Controller, - VerificationMethod: didMsg.VerificationMethod, - Authentication: didMsg.Authentication, - AssertionMethod: didMsg.AssertionMethod, - CapabilityInvocation: didMsg.CapabilityInvocation, - CapabilityDelegation: didMsg.CapabilityDelegation, - KeyAgreement: didMsg.KeyAgreement, - AlsoKnownAs: didMsg.AlsoKnownAs, - Service: didMsg.Service, - } - - metadata := types.NewMetadata(ctx) - id, err := k.AppendDid(ctx, did, &metadata) - if err != nil { - return nil, err - } - - return &types.MsgCreateDidResponse{ - Id: *id, - }, nil -} - -func (k msgServer) UpdateDid(goCtx context.Context, msg *types.MsgUpdateDid) (*types.MsgUpdateDidResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - prefix := k.GetDidPrefix(ctx) - - didMsg := msg.GetPayload() - if err := didMsg.Validate(prefix); err != nil { - return nil, err - } - - // Checks that the did doesn't exist - if !k.HasDid(ctx, didMsg.Id) { - return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %s doesn't exist", didMsg.Id)) - } - - oldStateValue, err := k.GetDid(&ctx, didMsg.Id) - if err != nil { - return nil, err - } - - oldDIDDoc, err := oldStateValue.UnpackDataAsDid() - if err != nil { - return nil, err - } - - if err := k.ValidateDidControllers(&ctx, didMsg.Id, didMsg.Controller, didMsg.VerificationMethod); err != nil { - return nil, err - } - - if err := k.VerifySignatureOnDidUpdate(&ctx, oldDIDDoc, didMsg, msg.Signatures); err != nil { - return nil, err - } - - // replay protection - if oldStateValue.Metadata.VersionId != didMsg.VersionId { - errMsg := fmt.Sprintf("Expected %s with version %s. Got version %s", didMsg.Id, oldStateValue.Metadata.VersionId, didMsg.VersionId) - return nil, sdkerrors.Wrap(types.ErrUnexpectedDidVersion, errMsg) - } - - var did = types.Did{ - Context: didMsg.Context, - Id: didMsg.Id, - Controller: didMsg.Controller, - VerificationMethod: didMsg.VerificationMethod, - Authentication: didMsg.Authentication, - AssertionMethod: didMsg.AssertionMethod, - CapabilityInvocation: didMsg.CapabilityInvocation, - CapabilityDelegation: didMsg.CapabilityDelegation, - KeyAgreement: didMsg.KeyAgreement, - AlsoKnownAs: didMsg.AlsoKnownAs, - Service: didMsg.Service, - } - - metadata := types.NewMetadata(ctx) - metadata.Created = oldStateValue.Metadata.Created - metadata.Deactivated = oldStateValue.Metadata.Deactivated - - if err = k.SetDid(ctx, did, &metadata); err != nil { - return nil, err - } - - return &types.MsgUpdateDidResponse{ - Id: didMsg.Id, - }, nil -} - -func (k msgServer) VerifySignatureOnDidUpdate(ctx *sdk.Context, oldDIDDoc *types.Did, newDIDDoc *types.MsgUpdateDidPayload, signatures []*types.SignInfo) error { - var signers []types.Signer - - // Get Old DID Doc controller if it's nil then assign self - oldController := oldDIDDoc.Controller - if len(oldController) == 0 { - oldController = []string{oldDIDDoc.Id} - } - - for _, controller := range oldController { - signers = append(signers, types.Signer{Signer: controller}) - } - - for _, oldVM := range oldDIDDoc.VerificationMethod { - newVM := FindVerificationMethod(newDIDDoc.VerificationMethod, oldVM.Id) - - // Verification Method has been deleted - if newVM == nil { - signers = AppendSignerIfNeed(signers, oldVM.Controller, newDIDDoc) - continue - } - - // Verification Method has been changed - if !reflect.DeepEqual(oldVM, newVM) { - signers = AppendSignerIfNeed(signers, newVM.Controller, newDIDDoc) - } - - // Verification Method Controller has been changed, need to add old controller - if newVM.Controller != oldVM.Controller { - signers = AppendSignerIfNeed(signers, oldVM.Controller, newDIDDoc) - } - } - - if err := k.VerifySignature(ctx, newDIDDoc, signers, signatures); err != nil { - return err - } - - return nil -} - -func AppendSignerIfNeed(signers []types.Signer, controller string, msg *types.MsgUpdateDidPayload) []types.Signer { - for _, signer := range signers { - if signer.Signer == controller { - return signers - } - } - - signer := types.Signer{ - Signer: controller, - } - - if controller == msg.Id { - signer.VerificationMethod = msg.VerificationMethod - signer.Authentication = msg.Authentication - } - - return append(signers, signer) -} - -func (k msgServer) ValidateDidControllers(ctx *sdk.Context, id string, controllers []string, verMethods []*types.VerificationMethod) error { - - for _, verificationMethod := range verMethods { - if err := k.ValidateController(ctx, id, verificationMethod.Controller); err != nil { - return err - } - } - - for _, didController := range controllers { - if err := k.ValidateController(ctx, id, didController); err != nil { - return err - } - } - return nil -} diff --git a/x/cheqd/keeper/msg_server_update_did.go b/x/cheqd/keeper/msg_server_update_did.go new file mode 100644 index 000000000..bcf29c677 --- /dev/null +++ b/x/cheqd/keeper/msg_server_update_did.go @@ -0,0 +1,184 @@ +package keeper + +import ( + "context" + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const UpdatedPostfix string = "-updated" + +func (k msgServer) UpdateDid(goCtx context.Context, msg *types.MsgUpdateDid) (*types.MsgUpdateDidResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Validate DID does exist + if !k.HasDid(&ctx, msg.Payload.Id) { + return nil, types.ErrDidDocNotFound.Wrap(msg.Payload.Id) + } + + // Validate namespaces + namespace := k.GetDidNamespace(ctx) + err := msg.Validate([]string{namespace}) + if err != nil { + return nil, types.ErrNamespaceValidation.Wrap(err.Error()) + } + + // Retrieve existing state value and did + existingStateValue, err := k.GetDid(&ctx, msg.Payload.Id) + if err != nil { + return nil, err + } + + existingDid, err := existingStateValue.UnpackDataAsDid() + if err != nil { + return nil, err + } + + // Check version id + if msg.Payload.VersionId != existingStateValue.Metadata.VersionId { + return nil, types.ErrUnexpectedDidVersion.Wrapf("got: %s, must be: %s", msg.Payload.VersionId, existingStateValue.Metadata.VersionId) + } + + // Get sign bytes before modifying payload + signBytes := msg.Payload.GetSignBytes() + + // Construct the new version of the DID and temporary rename it and its self references + // in order to consider old and new versions different DIDs during signatures validation + updatedDid := msg.Payload.ToDid() + updatedDid.ReplaceIds(updatedDid.Id, updatedDid.Id+UpdatedPostfix) + + updatedMetadata := types.NewMetadataFromContext(ctx) + updatedMetadata.Created = existingStateValue.Metadata.Created + updatedMetadata.Updated = ctx.BlockTime().String() + + updatedStateValue, err := types.NewStateValue(&updatedDid, &updatedMetadata) + if err != nil { + return nil, err + } + + // Consider the new version of the DID a separate DID + inMemoryDids := map[string]types.StateValue{updatedDid.Id: updatedStateValue} + + // Check controllers existence + controllers := updatedDid.AllControllerDids() + for _, controller := range controllers { + _, err := MustFindDid(&k.Keeper, &ctx, inMemoryDids, controller) + + if err != nil { + return nil, err + } + } + + // Verify signatures + // Duplicate signatures that reference the old version, make them reference a new (in memory) version + signers := GetSignerDIDsForDIDUpdate(*existingDid, updatedDid) + extendedSignatures := DuplicateSignatures(msg.Signatures, existingDid.Id, updatedDid.Id) + for _, signer := range signers { + signaturesBySigner := types.FindSignInfosBySigner(extendedSignatures, signer) + signerForErrorMessage := GetSignerIdForErrorMessage(signer, existingDid.Id, updatedDid.Id) + + if len(signaturesBySigner) == 0 { + return nil, types.ErrSignatureNotFound.Wrapf("there should be at least one signature by %s", signerForErrorMessage) + } + + found := false + for _, signature := range signaturesBySigner { + err := VerifySignature(&k.Keeper, &ctx, inMemoryDids, signBytes, signature) + if err == nil { + found = true + break + } + } + + if !found { + return nil, types.ErrSignatureNotFound.Wrapf("there should be at least one valid signature by %s", signerForErrorMessage) + } + } + + // Apply changes: return original id and modify state + updatedDid.ReplaceIds(updatedDid.Id, existingDid.Id) + err = k.SetDid(&ctx, &updatedDid, &updatedMetadata) + if err != nil { + return nil, types.ErrInternal.Wrapf(err.Error()) + } + + // Build and return response + return &types.MsgUpdateDidResponse{ + Id: updatedDid.Id, + }, nil +} + +func GetSignerIdForErrorMessage(signerId string, existingVersionId string, updatedVersionId string) interface{} { + if signerId == existingVersionId { // oldDid->id + return existingVersionId + " (old version)" + } + + if signerId == updatedVersionId { // oldDid->id + UpdatedPrefix + return existingVersionId + " (new version)" + } + + return signerId +} + +func DuplicateSignatures(signatures []*types.SignInfo, didToDuplicate string, newDid string) []*types.SignInfo { + var result []*types.SignInfo + + for _, signature := range signatures { + result = append(result, signature) + + did, path, query, fragment := utils.MustSplitDIDUrl(signature.VerificationMethodId) + if did == didToDuplicate { + duplicate := types.SignInfo{ + VerificationMethodId: utils.JoinDIDUrl(newDid, path, query, fragment), + Signature: signature.Signature, + } + + result = append(result, &duplicate) + } + } + + return result +} + +func GetSignerDIDsForDIDUpdate(existingDid types.Did, updatedDid types.Did) []string { + signers := existingDid.GetControllersOrSubject() + signers = append(signers, updatedDid.GetControllersOrSubject()...) + + existingVMMap := types.VerificationMethodListToMapByFragment(existingDid.VerificationMethod) + updatedVMMap := types.VerificationMethodListToMapByFragment(updatedDid.VerificationMethod) + + for _, updatedVM := range updatedDid.VerificationMethod { + _, _, _, fragment := utils.MustSplitDIDUrl(updatedVM.Id) + existingVM, found := existingVMMap[fragment] + + // VM added + if !found { + signers = append(signers, updatedVM.Controller) + continue + } + + // VM updated + // We don't compare ids because they will be different after replacing ids on the updated version of DID. + // Fragments equality is checked above. + if !types.CompareVerificationMethodsWithoutIds(existingVM, *updatedVM) { + signers = append(signers, existingVM.Controller, updatedVM.Controller) + continue + } + + // VM not changed + } + + for _, existingVM := range existingDid.VerificationMethod { + _, _, _, fragment := utils.MustSplitDIDUrl(existingVM.Id) + _, found := updatedVMMap[fragment] + + // VM removed + if !found { + signers = append(signers, existingVM.Controller) + continue + } + } + + return utils.UniqueSorted(signers) +} diff --git a/x/cheqd/keeper/grpc_query.go b/x/cheqd/keeper/query_server.go similarity index 100% rename from x/cheqd/keeper/grpc_query.go rename to x/cheqd/keeper/query_server.go diff --git a/x/cheqd/keeper/grpc_query_did.go b/x/cheqd/keeper/query_server_did.go similarity index 100% rename from x/cheqd/keeper/grpc_query_did.go rename to x/cheqd/keeper/query_server_did.go diff --git a/x/cheqd/keeper/utils.go b/x/cheqd/keeper/utils.go deleted file mode 100644 index 0c11dc4a8..000000000 --- a/x/cheqd/keeper/utils.go +++ /dev/null @@ -1,40 +0,0 @@ -package keeper - -import ( - "crypto/ed25519" - "github.com/cheqd/cheqd-node/x/cheqd/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (k *Keeper) GetDidPrefix(ctx sdk.Context) string { - prefix := types.DidPrefix + ":" + types.DidMethod + ":" - namespace := k.GetDidNamespace(ctx) - if len(namespace) > 0 { - prefix = prefix + namespace + ":" - } - return prefix -} - -func FindPublicKey(signer types.Signer, id string) (ed25519.PublicKey, error) { - for _, authentication := range signer.Authentication { - if authentication == id { - vm := FindVerificationMethod(signer.VerificationMethod, id) - if vm == nil { - return nil, types.ErrVerificationMethodNotFound.Wrap(id) - } - return vm.GetPublicKey() - } - } - - return nil, types.ErrVerificationMethodNotFound.Wrap(id) -} - -func FindVerificationMethod(vms []*types.VerificationMethod, id string) *types.VerificationMethod { - for _, vm := range vms { - if vm.Id == id { - return vm - } - } - - return nil -} diff --git a/x/cheqd/keeper/verify.go b/x/cheqd/keeper/verify.go deleted file mode 100644 index 0cf3535a2..000000000 --- a/x/cheqd/keeper/verify.go +++ /dev/null @@ -1,115 +0,0 @@ -package keeper - -import ( - "crypto/ed25519" - "encoding/base64" - "fmt" - "github.com/cheqd/cheqd-node/x/cheqd/types" - "github.com/cheqd/cheqd-node/x/cheqd/utils" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -func (k *Keeper) VerifySignature(ctx *sdk.Context, msg types.IdentityMsg, signers []types.Signer, signatures []*types.SignInfo) error { - if len(signers) == 0 { - return types.ErrInvalidSignature.Wrap("At least one signer should be present") - } - - if len(signatures) == 0 { - return types.ErrInvalidSignature.Wrap("At least one signature should be present") - } - - signingInput := msg.GetSignBytes() - - for _, signer := range signers { - if signer.VerificationMethod == nil { - state, err := k.GetDid(ctx, signer.Signer) - if err != nil { - return types.ErrDidDocNotFound.Wrap(signer.Signer) - } - - didDoc, err := state.UnpackDataAsDid() - if err != nil { - return types.ErrDidDocNotFound.Wrap(signer.Signer) - } - - signer.Authentication = didDoc.Authentication - signer.VerificationMethod = didDoc.VerificationMethod - } - - valid, err := VerifyIdentitySignature(signer, signatures, signingInput) - if err != nil { - return sdkerrors.Wrap(types.ErrInvalidSignature, err.Error()) - } - - if !valid { - return sdkerrors.Wrap(types.ErrInvalidSignature, signer.Signer) - } - } - - return nil -} - -func (k *Keeper) ValidateController(ctx *sdk.Context, id string, controller string) error { - if id == controller { - return nil - } - state, err := k.GetDid(ctx, controller) - if err != nil { - return types.ErrDidDocNotFound.Wrap(controller) - } - didDoc, err := state.UnpackDataAsDid() - if err != nil { - return types.ErrDidDocNotFound.Wrap(controller) - } - if len(didDoc.Authentication) == 0 { - return types.ErrBadRequestInvalidVerMethod.Wrap( - fmt.Sprintf("Verificatition method controller %s doesn't have an authentication keys", controller)) - } - return nil -} - -func VerifyIdentitySignature(signer types.Signer, signatures []*types.SignInfo, signingInput []byte) (bool, error) { - result := true - foundOne := false - - for _, info := range signatures { - did, _ := utils.SplitDidUrlIntoDidAndFragment(info.VerificationMethodId) - if did == signer.Signer { - pubKey, err := FindPublicKey(signer, info.VerificationMethodId) - if err != nil { - return false, err - } - - signature, err := base64.StdEncoding.DecodeString(info.Signature) - if err != nil { - return false, err - } - - verRes, err := verifyNoPanic(pubKey, signingInput, signature) - if err != nil { - return false, fmt.Errorf("signature verification error. verification method: %s. error: %s", info.VerificationMethodId, err) - } - - result = result && verRes - foundOne = true - } - } - - if !foundOne { - return false, fmt.Errorf("signature %s not found", signer.Signer) - } - - return result, nil -} - -func verifyNoPanic(publicKey ed25519.PublicKey, message, sig []byte) (res bool, err error) { - defer func() { - if rec := recover(); rec != nil { - err = fmt.Errorf("%s", rec) - } - }() - - internalRes := ed25519.Verify(publicKey, message, sig) - return internalRes, nil -} diff --git a/x/cheqd/module.go b/x/cheqd/module.go index dd3966960..a56740ff4 100644 --- a/x/cheqd/module.go +++ b/x/cheqd/module.go @@ -119,7 +119,7 @@ func NewAppModule(cdc codec.Codec, keeper keeper.Keeper) AppModule { // introduced by the module. To avoid wrong/empty versions, the initial version // should be set to 1. func (am AppModule) ConsensusVersion() uint64 { - return 2 + return 3 } // Name returns the capability module's name. diff --git a/x/cheqd/tests/constants.go b/x/cheqd/tests/constants.go index 8edd81904..f27ea58c4 100644 --- a/x/cheqd/tests/constants.go +++ b/x/cheqd/tests/constants.go @@ -1,16 +1,22 @@ package tests const ( - AliceDID = "did:cheqd:test:alice" - BobDID = "did:cheqd:test:bob" - CharlieDID = "did:cheqd:test:charlie" + AliceDID = "did:cheqd:test:aaaaaaaaaaaaaaaa" + BobDID = "did:cheqd:test:bbbbbbbbbbbbbbbb" + CharlieDID = "did:cheqd:test:cccccccccccccccc" + ImposterDID = "did:cheqd:test:nananananananana" + NotFounDID = "did:cheqd:test:nfdnfdnfdnfdnfdd" AliceKey1 = AliceDID + "#key-1" AliceKey2 = AliceDID + "#key-2" BobKey1 = BobDID + "#key-1" BobKey2 = BobDID + "#key-2" BobKey3 = BobDID + "#key-3" - BobKey4 = BobDID + "#key-4" + BobKey4 = BobDID + "#key-4" + ImposterKey1 = ImposterDID + "#key-1" + ImposterKey2 = ImposterDID + "#key-2" + NotFoundKey1 = NotFounDID + "#key-1" CharlieKey1 = CharlieDID + "#key-1" CharlieKey2 = CharlieDID + "#key-2" CharlieKey3 = CharlieDID + "#key-3" + CharlieKey4 = CharlieDID + "#key-4" ) diff --git a/x/cheqd/tests/create_did_test.go b/x/cheqd/tests/create_did_test.go new file mode 100644 index 000000000..33fa3abca --- /dev/null +++ b/x/cheqd/tests/create_did_test.go @@ -0,0 +1,402 @@ +package tests + +import ( + "crypto/ed25519" + "fmt" + "github.com/btcsuite/btcutil/base58" + "testing" + + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/multiformats/go-multibase" + + "github.com/stretchr/testify/require" +) + +func TestCreateDID(t *testing.T) { + var err error + keys := GenerateTestKeys() + cases := []struct { + valid bool + name string + keys map[string]KeyPair + signers []string + msg *types.MsgCreateDidPayload + errMsg string + }{ + { + valid: true, + name: "Valid: Works", + keys: map[string]KeyPair{ + ImposterKey1: GenerateKeyPair(), + }, + signers: []string{ImposterKey1}, + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Authentication: []string{ImposterKey1}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Works with Key Agreement", + keys: map[string]KeyPair{ + ImposterKey1: GenerateKeyPair(), + AliceKey1: keys[AliceKey1], + }, + signers: []string{ImposterKey1, AliceKey1}, + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + KeyAgreement: []string{ImposterKey1}, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Works with Assertion Method", + keys: map[string]KeyPair{ + ImposterKey1: GenerateKeyPair(), + AliceKey1: keys[AliceKey1], + }, + signers: []string{AliceKey1, ImposterKey1}, + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + AssertionMethod: []string{ImposterKey1}, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Works with Capability Delegation", + keys: map[string]KeyPair{ + ImposterKey1: GenerateKeyPair(), + AliceKey1: keys[AliceKey1], + }, + signers: []string{AliceKey1, ImposterKey1}, + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + CapabilityDelegation: []string{ImposterKey1}, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Works with Capability Invocation", + keys: map[string]KeyPair{ + ImposterKey1: GenerateKeyPair(), + AliceKey1: keys[AliceKey1], + }, + signers: []string{AliceKey1, ImposterKey1}, + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + CapabilityInvocation: []string{ImposterKey1}, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: With controller works", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID, BobDID}, + }, + signers: []string{AliceKey1, BobKey3}, + keys: map[string]KeyPair{ + AliceKey1: keys[AliceKey1], + BobKey3: keys[BobKey3], + }, + }, + { + valid: true, + name: "Valid: Full message works", + keys: map[string]KeyPair{ + "did:cheqd:test:1111111111111111#key-1": GenerateKeyPair(), + "did:cheqd:test:1111111111111111#key-2": GenerateKeyPair(), + "did:cheqd:test:1111111111111111#key-3": GenerateKeyPair(), + "did:cheqd:test:1111111111111111#key-4": GenerateKeyPair(), + "did:cheqd:test:1111111111111111#key-5": GenerateKeyPair(), + AliceKey1: keys[AliceKey1], + BobKey1: keys[BobKey1], + BobKey2: keys[BobKey2], + BobKey3: keys[BobKey3], + CharlieKey1: keys[CharlieKey1], + CharlieKey2: keys[CharlieKey2], + CharlieKey3: keys[CharlieKey3], + }, + signers: []string{ + "did:cheqd:test:1111111111111111#key-1", + "did:cheqd:test:1111111111111111#key-5", + AliceKey1, + BobKey1, + BobKey2, + BobKey3, + CharlieKey1, + CharlieKey2, + CharlieKey3, + }, + msg: &types.MsgCreateDidPayload{ + Id: "did:cheqd:test:1111111111111111", + Authentication: []string{ + "did:cheqd:test:1111111111111111#key-1", + "did:cheqd:test:1111111111111111#key-5", + }, + Context: []string{"abc", "de"}, + CapabilityInvocation: []string{"did:cheqd:test:1111111111111111#key-2"}, + CapabilityDelegation: []string{"did:cheqd:test:1111111111111111#key-3"}, + KeyAgreement: []string{"did:cheqd:test:1111111111111111#key-4"}, + AlsoKnownAs: []string{"SomeUri"}, + Service: []*types.Service{ + { + Id: "did:cheqd:test:1111111111111111#service-1", + Type: "DIDCommMessaging", + ServiceEndpoint: "ServiceEndpoint", + }, + }, + Controller: []string{"did:cheqd:test:1111111111111111", AliceDID, BobDID, CharlieDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: "did:cheqd:test:1111111111111111#key-1", + Type: Ed25519VerificationKey2020, + Controller: "did:cheqd:test:1111111111111111", + }, + { + Id: "did:cheqd:test:1111111111111111#key-2", + Type: Ed25519VerificationKey2020, + Controller: "did:cheqd:test:1111111111111111", + }, + { + Id: "did:cheqd:test:1111111111111111#key-3", + Type: Ed25519VerificationKey2020, + Controller: "did:cheqd:test:1111111111111111", + }, + { + Id: "did:cheqd:test:1111111111111111#key-4", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:test:1111111111111111", + }, + { + Id: "did:cheqd:test:1111111111111111#key-5", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:test:1111111111111111", + }, + }, + }, + }, + { + valid: false, + name: "Not Valid: Second controller did not sign request", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID, BobDID}, + }, + signers: []string{AliceKey1}, + keys: map[string]KeyPair{ + AliceKey1: keys[AliceKey1], + }, + errMsg: fmt.Sprintf("signer: %s: signature is required but not found", BobDID), + }, + { + valid: false, + name: "Not Valid: No signature", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID, BobDID}, + }, + errMsg: fmt.Sprintf("signer: %s: signature is required but not found", AliceDID), + }, + { + valid: false, + name: "Not Valid: Controller not found", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID, NotFounDID}, + }, + signers: []string{AliceKey1, ImposterKey1}, + keys: map[string]KeyPair{ + AliceKey1: keys[AliceKey1], + ImposterKey1: GenerateKeyPair(), + }, + errMsg: fmt.Sprintf("%s: DID Doc not found", NotFounDID), + }, + { + valid: false, + name: "Not Valid: Wrong signature", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID}, + }, + signers: []string{AliceKey1}, + keys: map[string]KeyPair{ + AliceKey1: keys[BobKey1], + }, + errMsg: fmt.Sprintf("method id: %s: invalid signature detected", AliceKey1), + }, + { + valid: false, + name: "Not Valid: DID signed by wrong controller", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Authentication: []string{ImposterKey1}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + PublicKeyMultibase: "z" + base58.Encode(keys[ImposterKey1].PublicKey), + }, + }, + }, + signers: []string{AliceKey1}, + keys: map[string]KeyPair{ + AliceKey1: keys[AliceKey1], + }, + errMsg: fmt.Sprintf("signer: %s: signature is required but not found", ImposterDID), + }, + { + valid: false, + name: "Not Valid: DID self-signed by not existing verification method", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Authentication: []string{ImposterKey1}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + PublicKeyMultibase: "z" + base58.Encode(keys[ImposterKey1].PublicKey), + }, + }, + }, + signers: []string{ImposterKey2}, + keys: map[string]KeyPair{ + ImposterKey2: GenerateKeyPair(), + }, + errMsg: fmt.Sprintf("%s: verification method not found", ImposterKey2), + }, + { + valid: false, + name: "Not Valid: Self-signature not found", + msg: &types.MsgCreateDidPayload{ + Id: ImposterDID, + Controller: []string{AliceDID, ImposterDID}, + Authentication: []string{ImposterKey1}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: ImposterKey1, + Type: Ed25519VerificationKey2020, + Controller: ImposterDID, + PublicKeyMultibase: "z" + base58.Encode(keys[ImposterKey1].PublicKey), + }, + }, + }, + signers: []string{AliceKey1, ImposterKey2}, + keys: map[string]KeyPair{ + AliceKey1: keys[AliceKey1], + ImposterKey2: GenerateKeyPair(), + }, + errMsg: fmt.Sprintf("%s: verification method not found", ImposterKey2), + }, + { + valid: false, + name: "Not Valid: DID Doc already exists", + keys: map[string]KeyPair{ + CharlieKey1: GenerateKeyPair(), + }, + signers: []string{CharlieKey1}, + msg: &types.MsgCreateDidPayload{ + Id: CharlieDID, + Authentication: []string{CharlieKey1}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: CharlieKey1, + Type: Ed25519VerificationKey2020, + Controller: CharlieDID, + }, + }, + }, + errMsg: fmt.Sprintf("%s: DID Doc exists", CharlieDID), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + msg := tc.msg + setup := InitEnv(t, keys) + + for _, vm := range msg.VerificationMethod { + if vm.PublicKeyMultibase == "" { + vm.PublicKeyMultibase, err = multibase.Encode(multibase.Base58BTC, tc.keys[vm.Id].PublicKey) + } + require.NoError(t, err) + } + + signerKeys := map[string]ed25519.PrivateKey{} + for _, signer := range tc.signers { + signerKeys[signer] = tc.keys[signer].PrivateKey + } + + did, err := setup.SendCreateDid(msg, signerKeys) + + if tc.valid { + require.Nil(t, err) + require.Equal(t, tc.msg.Id, did.Id) + require.Equal(t, tc.msg.Controller, did.Controller) + require.Equal(t, tc.msg.VerificationMethod, did.VerificationMethod) + require.Equal(t, tc.msg.Authentication, did.Authentication) + require.Equal(t, tc.msg.AssertionMethod, did.AssertionMethod) + require.Equal(t, tc.msg.CapabilityInvocation, did.CapabilityInvocation) + require.Equal(t, tc.msg.CapabilityDelegation, did.CapabilityDelegation) + require.Equal(t, tc.msg.KeyAgreement, did.KeyAgreement) + require.Equal(t, tc.msg.AlsoKnownAs, did.AlsoKnownAs) + require.Equal(t, tc.msg.Service, did.Service) + require.Equal(t, tc.msg.Context, did.Context) + } else { + require.Error(t, err) + require.Equal(t, tc.errMsg, err.Error()) + } + }) + } +} + +func TestHandler_DidDocAlreadyExists(t *testing.T) { + setup := Setup() + + _, _, _ = setup.InitDid(AliceDID) + _, _, err := setup.InitDid(AliceDID) + + require.Error(t, err) + require.Equal(t, fmt.Sprintf("%s: DID Doc exists", AliceDID), err.Error()) +} diff --git a/x/cheqd/tests/handler_test.go b/x/cheqd/tests/handler_test.go deleted file mode 100644 index dcffb71fa..000000000 --- a/x/cheqd/tests/handler_test.go +++ /dev/null @@ -1,951 +0,0 @@ -package tests - -import ( - "crypto/ed25519" - "testing" - - "github.com/btcsuite/btcutil/base58" - "github.com/cheqd/cheqd-node/x/cheqd/types" - "github.com/multiformats/go-multibase" - - "github.com/stretchr/testify/require" -) - -func TestCreateDID(t *testing.T) { - setup := Setup() - keys, err := setup.CreateTestDIDs() - require.NoError(t, err) - - cases := []struct { - valid bool - name string - keys map[string]KeyPair - signers []string - msg *types.MsgCreateDidPayload - errMsg string - }{ - { - valid: true, - name: "Works", - keys: map[string]KeyPair{ - "did:cheqd:test:123456qwertyui2#key-1": GenerateKeyPair(), - }, - signers: []string{"did:cheqd:test:123456qwertyui2#key-1"}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui2", - Authentication: []string{"did:cheqd:test:123456qwertyui2#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui2#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui2", - }, - }, - }, - }, - { - valid: true, - name: "Works with Key Agreement", - keys: map[string]KeyPair{ - "did:cheqd:test:KeyAgreement#key-1": GenerateKeyPair(), - AliceKey1: keys[AliceKey1], - }, - signers: []string{AliceKey1}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:KeyAgreement", - KeyAgreement: []string{"did:cheqd:test:KeyAgreement#key-1"}, - Controller: []string{AliceDID}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:KeyAgreement#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:KeyAgreement", - }, - }, - }, - }, - { - valid: true, - name: "Works with Assertion Method", - keys: map[string]KeyPair{ - "did:cheqd:test:AssertionMethod#key-1": GenerateKeyPair(), - AliceKey1: keys[AliceKey1], - }, - signers: []string{AliceKey1}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:AssertionMethod", - AssertionMethod: []string{"did:cheqd:test:AssertionMethod#key-1"}, - Controller: []string{AliceDID}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:AssertionMethod#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:AssertionMethod", - }, - }, - }, - }, - { - valid: true, - name: "Works with Capability Delegation", - keys: map[string]KeyPair{ - "did:cheqd:test:CapabilityDelegation#key-1": GenerateKeyPair(), - AliceKey1: keys[AliceKey1], - }, - signers: []string{AliceKey1}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:CapabilityDelegation", - CapabilityDelegation: []string{"did:cheqd:test:CapabilityDelegation#key-1"}, - Controller: []string{AliceDID}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:CapabilityDelegation#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:CapabilityDelegation", - }, - }, - }, - }, - { - valid: true, - name: "Works with Capability Invocation", - keys: map[string]KeyPair{ - "did:cheqd:test:CapabilityInvocation#key-1": GenerateKeyPair(), - AliceKey1: keys[AliceKey1], - }, - signers: []string{AliceKey1}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:CapabilityInvocation", - CapabilityInvocation: []string{"did:cheqd:test:CapabilityInvocation#key-1"}, - Controller: []string{AliceDID}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:CapabilityInvocation#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:CapabilityInvocation", - }, - }, - }, - }, - { - valid: true, - name: "With controller works", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, BobDID}, - }, - signers: []string{AliceKey1, BobKey3}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - BobKey3: keys[BobKey3], - }, - }, - { - valid: true, - name: "Full message works", - keys: map[string]KeyPair{ - "did:cheqd:test:123456qwertyui#key-1": GenerateKeyPair(), - "did:cheqd:test:123456qwertyui#key-2": GenerateKeyPair(), - "did:cheqd:test:123456qwertyui#key-3": GenerateKeyPair(), - "did:cheqd:test:123456qwertyui#key-4": GenerateKeyPair(), - "did:cheqd:test:123456qwertyui#key-5": GenerateKeyPair(), - AliceKey1: keys[AliceKey1], - BobKey1: keys[BobKey1], - BobKey2: keys[BobKey2], - BobKey3: keys[BobKey3], - CharlieKey1: keys[CharlieKey1], - CharlieKey2: keys[CharlieKey2], - CharlieKey3: keys[CharlieKey3], - }, - signers: []string{ - "did:cheqd:test:123456qwertyui#key-1", - "did:cheqd:test:123456qwertyui#key-5", - AliceKey1, - BobKey1, - BobKey2, - BobKey3, - CharlieKey1, - CharlieKey2, - CharlieKey3, - }, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui", - Authentication: []string{ - "did:cheqd:test:123456qwertyui#key-1", - "did:cheqd:test:123456qwertyui#key-5", - }, - Context: []string{"abc", "de"}, - CapabilityInvocation: []string{"did:cheqd:test:123456qwertyui#key-2"}, - CapabilityDelegation: []string{"did:cheqd:test:123456qwertyui#key-3"}, - KeyAgreement: []string{"did:cheqd:test:123456qwertyui#key-4"}, - AlsoKnownAs: []string{"did:cheqd:test:123456eqweqwe"}, - Service: []*types.Service{ - { - Id: "did:cheqd:test:123456qwertyui#service-1", - Type: "DIDCommMessaging", - ServiceEndpoint: "ServiceEndpoint", - }, - }, - Controller: []string{"did:cheqd:test:123456qwertyui", AliceDID, BobDID, CharlieDID}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - { - Id: "did:cheqd:test:123456qwertyui#key-2", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - { - Id: "did:cheqd:test:123456qwertyui#key-3", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - { - Id: "did:cheqd:test:123456qwertyui#key-4", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - { - Id: "did:cheqd:test:123456qwertyui#key-5", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - }, - { - valid: false, - name: "Second controller did not sign request", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, BobDID}, - }, - signers: []string{AliceKey1}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - }, - errMsg: "signature did:cheqd:test:bob not found: invalid signature detected", - }, - { - valid: false, - name: "Bad request", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - }, - signers: []string{AliceKey1}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - }, - errMsg: "The message must contain either a Controller or a Authentication: bad request", - }, - { - valid: false, - name: "No signature", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, BobDID}, - }, - errMsg: "At least one signature should be present: invalid signature detected", - }, - { - valid: false, - name: "Empty request", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, BobDID}, - }, - errMsg: "At least one signature should be present: invalid signature detected", - }, - { - valid: false, - name: "Controller not found", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, "did:cheqd:test:notfound"}, - }, - signers: []string{AliceKey1}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - }, - errMsg: "did:cheqd:test:notfound: DID Doc not found", - }, - { - valid: false, - name: "Wrong signature", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID}, - }, - signers: []string{AliceKey1}, - keys: map[string]KeyPair{ - AliceKey1: keys[BobKey1], - }, - errMsg: "did:cheqd:test:alice: invalid signature detected", - }, - { - valid: false, - name: "Controller verification method not found", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{BobDID}, - }, - signers: []string{BobKey4}, - keys: map[string]KeyPair{ - BobKey4: keys[BobKey4], - }, - errMsg: "did:cheqd:test:bob#key-4: verification method not found: invalid signature detected", - }, - { - valid: false, - name: "Second controller verification method not found", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, BobDID, CharlieDID}, - }, - signers: []string{AliceKey1, BobKey4, CharlieKey3}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - BobKey4: keys[BobKey4], - CharlieKey3: keys[CharlieKey3], - }, - errMsg: "did:cheqd:test:bob#key-4: verification method not found: invalid signature detected", - }, - { - valid: false, - name: "DID signed by wrong controller", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui", - Authentication: []string{"did:cheqd:test:123456qwertyui#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - signers: []string{AliceKey1}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - }, - errMsg: "signature did:cheqd:test:123456qwertyui not found: invalid signature detected", - }, - { - valid: false, - name: "DID self-signed by not existing verification method", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui", - Authentication: []string{"did:cheqd:test:123456qwertyui#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - signers: []string{"did:cheqd:test:123456qwertyui#key-2"}, - keys: map[string]KeyPair{ - "did:cheqd:test:123456qwertyui#key-2": GenerateKeyPair(), - }, - errMsg: "did:cheqd:test:123456qwertyui#key-2: verification method not found: invalid signature detected", - }, - { - valid: false, - name: "Self-signature not found", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui", - Controller: []string{AliceDID, "did:cheqd:test:123456qwertyui"}, - Authentication: []string{"did:cheqd:test:123456qwertyui#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - signers: []string{AliceKey1, "did:cheqd:test:123456qwertyui#key-2"}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - "did:cheqd:test:123456qwertyui#key-2": GenerateKeyPair(), - }, - errMsg: "did:cheqd:test:123456qwertyui#key-2: verification method not found: invalid signature detected", - }, - { - valid: false, - name: "DID Doc already exists", - keys: map[string]KeyPair{ - "did:cheqd:test:123456qwertyui#key-1": GenerateKeyPair(), - }, - signers: []string{"did:cheqd:test:123456qwertyui#key-1"}, - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:123456qwertyui", - Authentication: []string{"did:cheqd:test:123456qwertyui#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - errMsg: "DID is already used by DIDDoc did:cheqd:test:123456qwertyui: DID Doc exists", - }, - { - valid: false, - name: "Verification Method ID doesnt match", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, CharlieDID}, - Authentication: []string{"#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - signers: []string{AliceKey1, CharlieKey3}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - CharlieKey3: keys[CharlieKey3], - }, - errMsg: "did:cheqd:test:123456qwertyui#key-1 not belong did:cheqd:test:controller1 DID Doc: invalid verification method", - }, - { - valid: false, - name: "Full Verification Method ID doesnt match", - msg: &types.MsgCreateDidPayload{ - Id: "did:cheqd:test:controller1", - Controller: []string{AliceDID, CharlieDID}, - Authentication: []string{"did:cheqd:test:123456qwertyui#key-1"}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: "did:cheqd:test:123456qwertyui#key-1", - Type: "Ed25519VerificationKey2020", - Controller: "did:cheqd:test:123456qwertyui", - }, - }, - }, - signers: []string{AliceKey1, CharlieKey3}, - keys: map[string]KeyPair{ - AliceKey1: keys[AliceKey1], - CharlieKey3: keys[CharlieKey3], - }, - errMsg: "did:cheqd:test:123456qwertyui#key-1 not belong did:cheqd:test:controller1 DID Doc: invalid verification method", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - msg := tc.msg - - for _, vm := range msg.VerificationMethod { - vm.PublicKeyMultibase = "z" + base58.Encode(tc.keys[vm.Id].PublicKey) - } - - signerKeys := map[string]ed25519.PrivateKey{} - for _, signer := range tc.signers { - signerKeys[signer] = tc.keys[signer].PrivateKey - } - - did, err := setup.SendCreateDid(msg, signerKeys) - - if tc.valid { - require.Nil(t, err) - require.Equal(t, tc.msg.Id, did.Id) - require.Equal(t, tc.msg.Controller, did.Controller) - require.Equal(t, tc.msg.VerificationMethod, did.VerificationMethod) - require.Equal(t, tc.msg.Authentication, did.Authentication) - require.Equal(t, tc.msg.AssertionMethod, did.AssertionMethod) - require.Equal(t, tc.msg.CapabilityInvocation, did.CapabilityInvocation) - require.Equal(t, tc.msg.CapabilityDelegation, did.CapabilityDelegation) - require.Equal(t, tc.msg.KeyAgreement, did.KeyAgreement) - require.Equal(t, tc.msg.AlsoKnownAs, did.AlsoKnownAs) - require.Equal(t, tc.msg.Service, did.Service) - require.Equal(t, tc.msg.Context, did.Context) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - }) - } -} - -func TestUpdateDid(t *testing.T) { - setup := Setup() - keys, err := setup.CreateTestDIDs() - require.NoError(t, err) - - cases := []struct { - valid bool - name string - signers []string - msg *types.MsgUpdateDidPayload - errMsg string - }{ - { - valid: true, - name: "Key rotation works", - signers: []string{AliceKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - { - valid: false, - name: "Try to add controller without self-signature", - signers: []string{BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID}, - Authentication: []string{AliceKey1}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey1, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:alice not found: invalid signature detected", - }, - { - valid: false, - name: "Add controller and replace authentication without old signature do not work", - signers: []string{BobKey1, AliceKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID}, - Authentication: []string{AliceKey1}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey1, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "did:cheqd:test:alice#key-1: verification method not found: invalid signature detected", - }, - { - valid: true, - name: "Add controller work", - signers: []string{BobKey1, AliceKey2}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - { - valid: true, - name: "Add controller without signature work (signatures of old controllers are present)", - signers: []string{BobKey1, AliceKey2}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:charlie not found: invalid signature detected", - }, - { - valid: false, - name: "Replace controller work without new signature do not work", - signers: []string{BobKey1, AliceKey2}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:charlie not found: invalid signature detected", - }, - { - valid: false, - name: "Replace controller without old signature do not work", - signers: []string{AliceKey2, CharlieKey3}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:bob not found: invalid signature detected", - }, - { - valid: true, - name: "Replace controller work", - signers: []string{AliceKey2, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - { - valid: true, - name: "Add second controller works", - signers: []string{AliceKey2, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - { - valid: true, - name: "Add verification method without signature controller work", - signers: []string{CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - KeyAgreement: []string{AliceKey1}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - { - Id: AliceKey1, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - { - valid: false, - name: "Remove verification method without signature controller do not work", - signers: []string{CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:alice not found: invalid signature detected", - }, - { - valid: false, - name: "Remove verification method wrong authentication detected", - signers: []string{AliceKey1, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "did:cheqd:test:alice#key-1: verification method not found: invalid signature detected", - }, - { - valid: true, - name: "Add second authentication works", - signers: []string{AliceKey2, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey1, AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey1, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - }, - { - valid: false, - name: "Remove self authentication without signature do not work", - signers: []string{CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:alice not found: invalid signature detected", - }, - { - valid: false, - name: "Change self controller verification without signature do not work", - signers: []string{CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey1, AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey1, - Type: "Ed25519VerificationKey2020", - Controller: CharlieDID, - }, - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:alice not found: invalid signature detected", - }, - { - valid: true, - signers: []string{AliceKey2, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Controller: []string{BobDID, CharlieDID}, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - }, - { - valid: false, - name: "Change controller to self without old controllers signatures does not work", - signers: []string{AliceKey2}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:bob not found: invalid signature detected", - }, - { - valid: true, - name: "Change controller to self works", - signers: []string{AliceKey2, CharlieKey3, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: BobDID, - }, - }, - }, - }, - { - valid: false, - name: "Change verification method controller without old signature", - signers: []string{AliceKey2, CharlieKey3}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: CharlieDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:bob not found: invalid signature detected", - }, - { - valid: false, - name: "Change verification method controller without new signature", - signers: []string{AliceKey2, BobKey1}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: CharlieDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:charlie not found: invalid signature detected", - }, - { - valid: true, - name: "Change verification method controller", - signers: []string{AliceKey2, BobKey1, CharlieKey3}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: CharlieDID, - }, - }, - }, - }, - { - valid: false, - name: "Change to self verification method without controller signature", - signers: []string{AliceKey2}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - errMsg: "signature did:cheqd:test:charlie not found: invalid signature detected", - }, - { - valid: true, - name: "Change to self verification method without controller signature", - signers: []string{AliceKey2, CharlieKey3}, - msg: &types.MsgUpdateDidPayload{ - Id: AliceDID, - Authentication: []string{AliceKey2}, - VerificationMethod: []*types.VerificationMethod{ - { - Id: AliceKey2, - Type: "Ed25519VerificationKey2020", - Controller: AliceDID, - }, - }, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - msg := tc.msg - - for _, vm := range msg.VerificationMethod { - encoded, err := multibase.Encode(multibase.Base58BTC, keys[vm.Id].PublicKey) - require.NoError(t, err) - vm.PublicKeyMultibase = encoded - } - - signerKeys := map[string]ed25519.PrivateKey{} - for _, signer := range tc.signers { - signerKeys[signer] = keys[signer].PrivateKey - } - - did, err := setup.SendUpdateDid(msg, signerKeys) - - if tc.valid { - require.Nil(t, err) - require.Equal(t, tc.msg.Id, did.Id) - require.Equal(t, tc.msg.Controller, did.Controller) - require.Equal(t, tc.msg.VerificationMethod, did.VerificationMethod) - require.Equal(t, tc.msg.Authentication, did.Authentication) - require.Equal(t, tc.msg.AssertionMethod, did.AssertionMethod) - require.Equal(t, tc.msg.CapabilityInvocation, did.CapabilityInvocation) - require.Equal(t, tc.msg.CapabilityDelegation, did.CapabilityDelegation) - require.Equal(t, tc.msg.KeyAgreement, did.KeyAgreement) - require.Equal(t, tc.msg.AlsoKnownAs, did.AlsoKnownAs) - require.Equal(t, tc.msg.Service, did.Service) - require.Equal(t, tc.msg.Context, did.Context) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - }) - } -} - -func TestHandler_DidDocAlreadyExists(t *testing.T) { - setup := Setup() - - _, _, _ = setup.InitDid("did:cheqd:test:alice") - _, _, err := setup.InitDid("did:cheqd:test:alice") - - require.Error(t, err) - require.Equal(t, "DID is already used by DIDDoc did:cheqd:test:alice: DID Doc exists", err.Error()) -} diff --git a/x/cheqd/tests/handler_test_setup.go b/x/cheqd/tests/setup.go similarity index 90% rename from x/cheqd/tests/handler_test_setup.go rename to x/cheqd/tests/setup.go index ab885ade0..87405c516 100644 --- a/x/cheqd/tests/handler_test_setup.go +++ b/x/cheqd/tests/setup.go @@ -35,6 +35,12 @@ type TestSetup struct { Handler sdk.Handler } + +type SignerKey struct { + signer string + key ed25519.PrivateKey +} + func Setup() TestSetup { // Init Codec ir := codectypes.NewInterfaceRegistry() @@ -87,7 +93,7 @@ func (s *TestSetup) CreateDid(pubKey ed25519.PublicKey, did string) *types.MsgCr } Service := types.Service{ - Id: "#service-2", + Id: did + "#service-2", Type: "DIDCommMessaging", ServiceEndpoint: "endpoint", } @@ -141,14 +147,14 @@ func (s *TestSetup) WrapCreateRequest(payload *types.MsgCreateDidPayload, keys m } } -func (s *TestSetup) WrapUpdateRequest(payload *types.MsgUpdateDidPayload, keys map[string]ed25519.PrivateKey) *types.MsgUpdateDid { +func (s *TestSetup) WrapUpdateRequest(payload *types.MsgUpdateDidPayload, keys []SignerKey) *types.MsgUpdateDid { var signatures []*types.SignInfo signingInput := payload.GetSignBytes() - for privKeyId, privKey := range keys { - signature := base64.StdEncoding.EncodeToString(ed25519.Sign(privKey, signingInput)) + for _, skey := range keys { + signature := base64.StdEncoding.EncodeToString(ed25519.Sign(skey.key, signingInput)) signatures = append(signatures, &types.SignInfo{ - VerificationMethodId: privKeyId, + VerificationMethodId: skey.signer, Signature: signature, }) } @@ -186,7 +192,7 @@ func (s *TestSetup) InitDid(did string) (map[string]ed25519.PrivateKey, *types.M return keys, didMsg, nil } -func (s *TestSetup) SendUpdateDid(msg *types.MsgUpdateDidPayload, keys map[string]ed25519.PrivateKey) (*types.Did, error) { +func (s *TestSetup) SendUpdateDid(msg *types.MsgUpdateDidPayload, keys []SignerKey) (*types.Did, error) { // query Did state, _ := s.Keeper.GetDid(&s.Ctx, msg.Id) if len(msg.VersionId) == 0 { @@ -220,18 +226,18 @@ func ConcatKeys(dst map[string]ed25519.PrivateKey, src map[string]ed25519.Privat return dst } -func (s TestSetup) CreateTestDIDs() (map[string]KeyPair, error) { - keys := map[string]KeyPair{ - AliceKey1: GenerateKeyPair(), - AliceKey2: GenerateKeyPair(), - BobKey1: GenerateKeyPair(), - BobKey2: GenerateKeyPair(), - BobKey3: GenerateKeyPair(), - BobKey4: GenerateKeyPair(), - CharlieKey1: GenerateKeyPair(), - CharlieKey2: GenerateKeyPair(), - CharlieKey3: GenerateKeyPair(), +func MapToListOfSignerKeys(mp map[string]ed25519.PrivateKey) []SignerKey { + var rlist = []SignerKey{} + for k, v := range mp { + rlist = append(rlist, SignerKey{ + signer: k, + key: v, + }) } + return rlist +} + +func (s TestSetup) CreateTestDIDs(keys map[string]KeyPair) (error) { testDIDs := []struct { signers []string @@ -288,7 +294,7 @@ func (s TestSetup) CreateTestDIDs() (map[string]KeyPair, error) { }, }, { - signers: []string{CharlieKey2}, + signers: []string{CharlieKey2, BobKey2}, msg: &types.MsgCreateDidPayload{ Id: CharlieDID, Authentication: []string{ @@ -323,7 +329,7 @@ func (s TestSetup) CreateTestDIDs() (map[string]KeyPair, error) { for _, vm := range msg.VerificationMethod { encoded, err := multibase.Encode(multibase.Base58BTC, keys[vm.Id].PublicKey) if err != nil { - return nil, err + return err } vm.PublicKeyMultibase = encoded } @@ -333,12 +339,11 @@ func (s TestSetup) CreateTestDIDs() (map[string]KeyPair, error) { signerKeys[signer] = keys[signer].PrivateKey } - for keyId, key := range keys { - keys[keyId] = key + _, err := s.SendCreateDid(msg, signerKeys) + if err != nil { + return err } - - _, _ = s.SendCreateDid(msg, signerKeys) } - return keys, nil + return nil } diff --git a/x/cheqd/tests/signature_verification_test.go b/x/cheqd/tests/signature_verification_test.go index 4d6e6c97d..5a7f2ba6d 100644 --- a/x/cheqd/tests/signature_verification_test.go +++ b/x/cheqd/tests/signature_verification_test.go @@ -3,6 +3,8 @@ package tests import ( "crypto/ed25519" "crypto/rand" + "fmt" + "github.com/btcsuite/btcutil/base58" "github.com/cheqd/cheqd-node/x/cheqd/types" "github.com/stretchr/testify/require" "reflect" @@ -13,122 +15,125 @@ func TestDIDDocControllerChanged(t *testing.T) { setup := Setup() //Init did - aliceKeys, aliceDid, _ := setup.InitDid("did:cheqd:test:alice") - bobKeys, _, _ := setup.InitDid("did:cheqd:test:bob") + aliceKeys, aliceDid, _ := setup.InitDid(AliceDID) + bobKeys, _, _ := setup.InitDid(BobDID) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) - updatedDidDoc.Controller = append(updatedDidDoc.Controller, "did:cheqd:test:bob") - receivedDid, _ := setup.SendUpdateDid(updatedDidDoc, ConcatKeys(aliceKeys, bobKeys)) + updatedDidDoc.Controller = append(updatedDidDoc.Controller, BobDID) + receivedDid, _ := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(ConcatKeys(aliceKeys, bobKeys))) // check require.NotEqual(t, aliceDid.Controller, receivedDid.Controller) - require.NotEqual(t, []string{"did:cheqd:test:alice", "did:cheqd:test:bob"}, receivedDid.Controller) - require.Equal(t, []string{"did:cheqd:test:bob"}, receivedDid.Controller) + require.NotEqual(t, []string{AliceDID, BobDID}, receivedDid.Controller) + require.Equal(t, []string{BobDID}, receivedDid.Controller) } func TestDIDDocVerificationMethodChangedWithoutOldSignature(t *testing.T) { setup := Setup() //Init did - _, aliceDid, _ := setup.InitDid("did:cheqd:test:alice") - bobKeys, _, _ := setup.InitDid("did:cheqd:test:bob") + _, aliceDid, _ := setup.InitDid(AliceDID) + bobKeys, _, _ := setup.InitDid(BobDID) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) - updatedDidDoc.VerificationMethod[0].Type = "Ed25519VerificationKey2020" - _, err := setup.SendUpdateDid(updatedDidDoc, bobKeys) + updatedDidDoc.VerificationMethod[0].Type = Ed25519VerificationKey2020 + _, err := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(bobKeys)) // check require.Error(t, err) - require.Equal(t, "signature did:cheqd:test:alice not found: invalid signature detected", err.Error()) + require.Equal(t, fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), err.Error()) } func TestDIDDocVerificationMethodControllerChangedWithoutOldSignature(t *testing.T) { setup := Setup() //Init did - _, aliceDid, _ := setup.InitDid("did:cheqd:test:alice") - bobKeys, _, _ := setup.InitDid("did:cheqd:test:bob") + _, aliceDid, _ := setup.InitDid(AliceDID) + bobKeys, _, _ := setup.InitDid(BobDID) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) - updatedDidDoc.VerificationMethod[0].Controller = "did:cheqd:test:bob" - _, err := setup.SendUpdateDid(updatedDidDoc, bobKeys) + updatedDidDoc.VerificationMethod[0].Controller = BobDID + _, err := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(bobKeys)) // check require.Error(t, err) - require.Equal(t, "signature did:cheqd:test:alice not found: invalid signature detected", err.Error()) + require.Equal(t, fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), err.Error()) } func TestDIDDocControllerChangedWithoutOldSignature(t *testing.T) { setup := Setup() //Init did - _, aliceDid, _ := setup.InitDid("did:cheqd:test:alice") - bobKeys, _, _ := setup.InitDid("did:cheqd:test:bob") + _, aliceDid, _ := setup.InitDid(AliceDID) + bobKeys, _, _ := setup.InitDid(BobDID) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) - updatedDidDoc.Controller = append(updatedDidDoc.Controller, "did:cheqd:test:bob") - _, err := setup.SendUpdateDid(updatedDidDoc, bobKeys) + updatedDidDoc.Controller = append(updatedDidDoc.Controller, BobDID) + _, err := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(bobKeys)) // check require.Error(t, err) - require.Equal(t, "signature did:cheqd:test:alice not found: invalid signature detected", err.Error()) + require.Equal(t, fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), err.Error()) } func TestDIDDocVerificationMethodDeletedWithoutOldSignature(t *testing.T) { setup := Setup() //Init did - _, bodDidDoc, _ := setup.InitDid("did:cheqd:test:bob") - pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader) - aliceDid := setup.CreateDid(pubKey, "did:cheqd:test:alice") + ApubKey, AprivKey, _ := ed25519.GenerateKey(rand.Reader) + BpubKey, BprivKey, _ := ed25519.GenerateKey(rand.Reader) + aliceDid := setup.CreateDid(ApubKey, AliceDID) + bobDid := setup.CreateDid(BpubKey, BobDID) aliceDid.VerificationMethod = append(aliceDid.VerificationMethod, &types.VerificationMethod{ - Id: "did:cheqd:test:alice#key-2", - Controller: "did:cheqd:test:bob", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: bodDidDoc.VerificationMethod[0].PublicKeyMultibase, + Id: AliceKey2, + Controller: BobDID, + Type: Ed25519VerificationKey2020, + PublicKeyMultibase: "z" + base58.Encode(BpubKey), }) - aliceDid.Authentication = append(aliceDid.Authentication, "did:cheqd:test:alice#key-2") - - aliceKeys := map[string]ed25519.PrivateKey{"did:cheqd:test:alice#key-1": privKey} + aliceKeys := map[string]ed25519.PrivateKey{AliceKey1: AprivKey, BobKey1: BprivKey} + bobKeys := map[string]ed25519.PrivateKey{BobKey1: BprivKey} + _, _ = setup.SendCreateDid(bobDid, bobKeys) _, _ = setup.SendCreateDid(aliceDid, aliceKeys) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) updatedDidDoc.VerificationMethod = []*types.VerificationMethod{aliceDid.VerificationMethod[0]} updatedDidDoc.Authentication = []string{aliceDid.Authentication[0]} - _, err := setup.SendUpdateDid(updatedDidDoc, aliceKeys) + _, err := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(bobKeys)) // check require.Error(t, err) - require.Equal(t, "signature did:cheqd:test:bob not found: invalid signature detected", err.Error()) + require.Equal(t, fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), err.Error()) } func TestDIDDocVerificationMethodDeleted(t *testing.T) { setup := Setup() - //Init did - bobKeys, bodDidDoc, _ := setup.InitDid("did:cheqd:test:bob") + ApubKey, AprivKey, _ := ed25519.GenerateKey(rand.Reader) + BpubKey, BprivKey, _ := ed25519.GenerateKey(rand.Reader) - pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader) - aliceDid := setup.CreateDid(pubKey, "did:cheqd:test:alice") + aliceDid := setup.CreateDid(ApubKey, AliceDID) + bobDid := setup.CreateDid(BpubKey, BobDID) - aliceDid.Authentication = append(aliceDid.Authentication, "did:cheqd:test:alice#key-2") + aliceDid.Authentication = append(aliceDid.Authentication, AliceKey2) aliceDid.VerificationMethod = append(aliceDid.VerificationMethod, &types.VerificationMethod{ - Id: "did:cheqd:test:alice#key-2", - Controller: "did:cheqd:test:bob", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: bodDidDoc.VerificationMethod[0].PublicKeyMultibase, + Id: AliceKey2, + Controller: BobDID, + Type: Ed25519VerificationKey2020, + PublicKeyMultibase: "z" + base58.Encode(BpubKey), }) - aliceKeys := map[string]ed25519.PrivateKey{"did:cheqd:test:alice#key-1": privKey} + aliceKeys := map[string]ed25519.PrivateKey{AliceKey1: AprivKey, BobKey1: BprivKey} + bobKeys := map[string]ed25519.PrivateKey{BobKey1: BprivKey} + _, _ = setup.SendCreateDid(bobDid, bobKeys) _, _ = setup.SendCreateDid(aliceDid, aliceKeys) updatedDidDoc := setup.CreateToUpdateDid(aliceDid) updatedDidDoc.Authentication = []string{aliceDid.Authentication[0]} updatedDidDoc.VerificationMethod = []*types.VerificationMethod{aliceDid.VerificationMethod[0]} - receivedDid, _ := setup.SendUpdateDid(updatedDidDoc, ConcatKeys(aliceKeys, bobKeys)) + receivedDid, _ := setup.SendUpdateDid(updatedDidDoc, MapToListOfSignerKeys(ConcatKeys(aliceKeys, bobKeys))) // check require.NotEqual(t, len(aliceDid.VerificationMethod), len(receivedDid.VerificationMethod)) diff --git a/x/cheqd/tests/update_did_test.go b/x/cheqd/tests/update_did_test.go new file mode 100644 index 000000000..5336aec0f --- /dev/null +++ b/x/cheqd/tests/update_did_test.go @@ -0,0 +1,484 @@ +package tests + +import ( + "fmt" + "github.com/btcsuite/btcutil/base58" + "github.com/cheqd/cheqd-node/x/cheqd/types" + "github.com/multiformats/go-multibase" + "github.com/stretchr/testify/require" + "testing" +) + +func TestUpdateDid(t *testing.T) { + var err error + keys := map[string]KeyPair{ + AliceKey1: GenerateKeyPair(), + AliceKey2: GenerateKeyPair(), + BobKey1: GenerateKeyPair(), + BobKey2: GenerateKeyPair(), + BobKey3: GenerateKeyPair(), + BobKey4: GenerateKeyPair(), + CharlieKey1: GenerateKeyPair(), + CharlieKey2: GenerateKeyPair(), + CharlieKey3: GenerateKeyPair(), + CharlieKey4: GenerateKeyPair(), + ImposterKey1: GenerateKeyPair(), + } + + cases := []struct { + valid bool + name string + signerKeys []SignerKey + signers []string + msg *types.MsgUpdateDidPayload + errMsg string + }{ + { + valid: true, + name: "Valid: Key rotation works", + signerKeys: []SignerKey{ + { + signer: AliceKey1, + key: keys[AliceKey1].PrivateKey, + }, + { + signer: AliceKey1, + key: keys[AliceKey2].PrivateKey, + }, + }, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + PublicKeyMultibase: "z" + base58.Encode(keys[AliceKey2].PublicKey), + }, + }, + }, + }, + // VM and Controller replacing tests + { + valid: false, + name: "Not Valid: replacing controller and Verification method ID does not work without new sign", + signers: []string{AliceKey2, BobKey1, AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{CharlieDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", CharlieDID), + }, + { + valid: true, + name: "Valid: replacing controller and Verification method ID works with all signatures", + signers: []string{AliceKey1, CharlieKey1, AliceKey2}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{CharlieDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", CharlieDID), + }, + // Verification method's tests + // cases: + // - replacing VM controller works + // - replacing VM controller does not work without new signature + // - replacing VM controller does not work without old signature ?????? + // - replacing VM doesn't work without new signature + // - replacing VM doesn't work without old signature + // - replacing VM works with all signatures + // --- adding new VM works + // --- adding new VM without new signature + // --- adding new VM without old signature + { + valid: true, + name: "Valid: Replacing VM controller works with one signature", + signers: []string{AliceKey1, BobKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + }, + }, + }, + { + valid: false, + name: "Not Valid: Replacing VM controller does not work without new signature", + signers: []string{AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", BobDID), + }, + { + valid: false, + name: "Not Valid: Replacing VM does not work without new signature", + signers: []string{AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one valid signature by %s (new version): signature is required but not found", AliceDID), + }, + { + valid: false, + name: "Not Valid: Replacing VM does not work without old signature", + signers: []string{AliceKey2}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one valid signature by %s (old version): signature is required but not found", AliceDID), + }, + { + valid: true, + name: "Not Valid: Replacing VM works with all signatures", + signers: []string{AliceKey1, AliceKey2}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + }, + // Adding VM + { + valid: true, + name: "Valid: Adding another verification method", + signers: []string{AliceKey1, BobKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + }, + }, + }, + { + valid: false, + name: "Not Valid: Adding another verification method without new sign", + signers: []string{AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", BobDID), + }, + { + valid: false, + name: "Not Valid: Adding another verification method without old sign", + signers: []string{AliceKey2}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one valid signature by %s (old version): signature is required but not found", AliceDID), + }, + + // Controller's tests + // cases: + // - replacing Controller works with all signatures + // - replacing Controller doesn't work without old signature + // - replacing Controller doesn't work without new signature + // --- adding Controller works with all signatures + // --- adding Controller doesn't work without old signature + // --- adding Controller doesn't work without new signature + { + valid: true, + name: "Valid: Replace controller works with all signatures", + signers: []string{BobKey1, AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{BobDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + }, + { + valid: false, + name: "Not Valid: Replace controller doesn't work without old signatures", + signers: []string{BobKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{BobDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), + }, + { + valid: false, + name: "Not Valid: Replace controller doesn't work without new signatures", + signers: []string{AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{BobDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", BobDID), + }, + // add Controller + { + valid: true, + name: "Valid: Adding second controller works", + signers: []string{AliceKey1, CharlieKey3}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID, CharlieDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + }, + { + valid: false, + name: "Not Valid: Adding controller without old signature", + signers: []string{BobKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID, BobDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", AliceDID), + }, + { + valid: false, + name: "Not Valid: Add controller without new signature doesn't work", + signers: []string{AliceKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID, BobDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s: signature is required but not found", BobDID), + }, + + + { + valid: true, + name: "Valid: Adding verification method with the same controller works", + signers: []string{AliceKey1, AliceKey2}, + msg: &types.MsgUpdateDidPayload{ + Id: AliceDID, + Controller: []string{AliceDID}, + VerificationMethod: []*types.VerificationMethod{ + { + Id: AliceKey2, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + { + Id: AliceKey1, + Type: Ed25519VerificationKey2020, + Controller: AliceDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Keeping VM with controller different then subject untouched during update should not require Bob signature", + signers: []string{CharlieKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: CharlieDID, + Authentication: []string{ + CharlieKey1, + CharlieKey2, + CharlieKey3, + }, + + VerificationMethod: []*types.VerificationMethod{ + { + Id: CharlieKey1, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + { + Id: CharlieKey2, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + { + Id: CharlieKey3, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + { + Id: CharlieKey4, + Type: Ed25519VerificationKey2020, + Controller: CharlieDID, + }, + }, + }, + }, + { + valid: true, + name: "Valid: Removing verification method is possible with any kind of valid Bob's key", + signers: []string{BobKey1}, + msg: &types.MsgUpdateDidPayload{ + Id: BobDID, + VerificationMethod: []*types.VerificationMethod{ + { + Id: BobKey1, + Type: Ed25519VerificationKey2020, + Controller: BobDID, + }, + }, + }, + errMsg: fmt.Sprintf("there should be at least one signature by %s (old version): signature is required but not found", BobDID), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + setup := InitEnv(t, keys) + msg := tc.msg + + + for _, vm := range msg.VerificationMethod { + if vm.PublicKeyMultibase == "" { + vm.PublicKeyMultibase, err = multibase.Encode(multibase.Base58BTC, keys[vm.Id].PublicKey) + } + require.NoError(t, err) + } + + signerKeys := []SignerKey{} + if tc.signerKeys != nil { + signerKeys = tc.signerKeys + } else { + for _, signer := range tc.signers { + signerKeys = append(signerKeys, SignerKey{ + signer: signer, + key : keys[signer].PrivateKey, + }) + } + } + + did, err := setup.SendUpdateDid(msg, signerKeys) + + if tc.valid { + require.Nil(t, err) + require.Equal(t, tc.msg.Id, did.Id) + require.Equal(t, tc.msg.Controller, did.Controller) + require.Equal(t, tc.msg.VerificationMethod, did.VerificationMethod) + require.Equal(t, tc.msg.Authentication, did.Authentication) + require.Equal(t, tc.msg.AssertionMethod, did.AssertionMethod) + require.Equal(t, tc.msg.CapabilityInvocation, did.CapabilityInvocation) + require.Equal(t, tc.msg.CapabilityDelegation, did.CapabilityDelegation) + require.Equal(t, tc.msg.KeyAgreement, did.KeyAgreement) + require.Equal(t, tc.msg.AlsoKnownAs, did.AlsoKnownAs) + require.Equal(t, tc.msg.Service, did.Service) + require.Equal(t, tc.msg.Context, did.Context) + } else { + require.Error(t, err) + require.Equal(t, tc.errMsg, err.Error()) + } + }) + } +} diff --git a/x/cheqd/tests/utils.go b/x/cheqd/tests/utils.go new file mode 100644 index 000000000..53115e1e8 --- /dev/null +++ b/x/cheqd/tests/utils.go @@ -0,0 +1,47 @@ +package tests + +import ( + "github.com/stretchr/testify/require" + "math/rand" + "testing" +) + +var letters = []rune("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + +func randSeq(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +func GenerateDID() string { + return "did:cheqd:test:" + randSeq(16) +} + +func GenerateFragment(did string) string { + return did + "#key-1" +} + +func GenerateTestKeys() map[string]KeyPair { + return map[string]KeyPair{ + AliceKey1: GenerateKeyPair(), + AliceKey2: GenerateKeyPair(), + BobKey1: GenerateKeyPair(), + BobKey2: GenerateKeyPair(), + BobKey3: GenerateKeyPair(), + BobKey4: GenerateKeyPair(), + CharlieKey1: GenerateKeyPair(), + CharlieKey2: GenerateKeyPair(), + CharlieKey3: GenerateKeyPair(), + ImposterKey1: GenerateKeyPair(), + } +} + +func InitEnv(t *testing.T, keys map[string]KeyPair) TestSetup { + setup := Setup() + err := setup.CreateTestDIDs(keys) + require.NoError(t, err) + return setup +} diff --git a/x/cheqd/types/common_key_value_pair.go b/x/cheqd/types/common_key_value_pair.go new file mode 100644 index 000000000..5c27fb13c --- /dev/null +++ b/x/cheqd/types/common_key_value_pair.go @@ -0,0 +1,75 @@ +package types + +import ( + "encoding/json" + "errors" + "fmt" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +// Helpers + +type PublicKeyJWK []*KeyValuePair + +func PubKeyJWKToMap(key PublicKeyJWK) map[string]string { + map_ := make(map[string]string) + for _, kv := range key { + map_[kv.Key] = kv.Value + } + return map_ +} + +func JSONToPubKeyJWK(jsonStr string) PublicKeyJWK { + map_ := make(map[string]string) + res := PublicKeyJWK{} + err := json.Unmarshal([]byte(jsonStr), &map_) + if err != nil { + panic(fmt.Errorf("internal error: Cannot unmarshal JSON string: %s", jsonStr)) + } + for k, v := range map_ { + res = append(res, &KeyValuePair{ + Key: k, + Value: v, + }) + } + return res +} + +func PubKeyJWKToJson(key PublicKeyJWK) (string, error) { + map_ := PubKeyJWKToMap(key) + json_, err_ := json.Marshal(map_) + if err_ != nil { + return "", errors.New("can't marshal PublicKeyJWK map to JSON") + } + + return string(json_), nil +} + +func IsUniqueKeyValuePairListByKey(key PublicKeyJWK) bool { + map_ := PubKeyJWKToMap(key) + return len(map_) == len(key) +} + +// Validation + +func (p KeyValuePair)Validate() error { + return validation.ValidateStruct(&p, + validation.Field(&p.Key, validation.Required), + validation.Field(&p.Value, validation.Required), + ) +} + +func IsUniqueKeyValuePairListByKeyRule() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*KeyValuePair) + if !ok { + panic("IsUniqueKeyValuePairListByKeyRule must be only applied on KeyValuePair array properties") + } + + if !IsUniqueKeyValuePairListByKey(casted) { + return errors.New("the list of KeyValuePair should be without duplicates") + } + + return nil + }) +} diff --git a/x/cheqd/types/did.go b/x/cheqd/types/did.go deleted file mode 100644 index eded52861..000000000 --- a/x/cheqd/types/did.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import "github.com/multiformats/go-multibase" - -var _ StateValueData = &Did{} - -func (v VerificationMethod) GetPublicKey() ([]byte, error) { - if len(v.PublicKeyMultibase) > 0 { - _, key, err := multibase.Decode(v.PublicKeyMultibase) - if err != nil { - return nil, ErrInvalidPublicKey.Wrapf("Cannot decode verification method '%s' public key", v.Id) - } - return key, nil - } - - if len(v.PublicKeyJwk) > 0 { - return nil, ErrInvalidPublicKey.Wrap("JWK format not supported") - } - - return nil, ErrInvalidPublicKey.Wrapf("verification method '%s' public key not found", v.Id) -} diff --git a/x/cheqd/types/did_did.go b/x/cheqd/types/did_did.go new file mode 100644 index 000000000..74399eae0 --- /dev/null +++ b/x/cheqd/types/did_did.go @@ -0,0 +1,114 @@ +package types + +import ( + "github.com/cheqd/cheqd-node/x/cheqd/utils" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +var _ StateValueData = &Did{} + +func NewDid(context []string, id string, controller []string, verificationMethod []*VerificationMethod, + authentication []string, assertionMethod []string, capabilityInvocation []string, capabilityDelegation []string, + keyAgreement []string, service []*Service, alsoKnownAs []string) *Did { + + return &Did{ + Context: context, + Id: id, + Controller: controller, + VerificationMethod: verificationMethod, + Authentication: authentication, + AssertionMethod: assertionMethod, + CapabilityInvocation: capabilityInvocation, + CapabilityDelegation: capabilityDelegation, + KeyAgreement: keyAgreement, + Service: service, + AlsoKnownAs: alsoKnownAs, + } +} + +// Helpers + +// AllControllerDids returns controller DIDs used in both did.controllers and did.verification_method.controller +func (did *Did) AllControllerDids() []string { + result := did.Controller + result = append(result, did.GetVerificationMethodControllers()...) + + return utils.UniqueSorted(result) +} + +// ReplaceIds replaces ids in all controller and id fields +func (did *Did) ReplaceIds(old, new string) { + // Controllers + utils.ReplaceInSlice(did.Controller, old, new) + + // Id + if did.Id == old { + did.Id = new + } + + for _, vm := range did.VerificationMethod { + // Controller + if vm.Controller == old { + vm.Controller = new + } + + // Id + did, path, query, fragment := utils.MustSplitDIDUrl(vm.Id) + if did == old { + did = new + } + + vm.Id = utils.JoinDIDUrl(did, path, query, fragment) + } +} + +func (did *Did) GetControllersOrSubject() []string { + result := did.Controller + + if len(result) == 0 { + result = append(result, did.Id) + } + + return result +} + +func (did *Did) GetVerificationMethodControllers() []string { + var result []string + + for _, vm := range did.VerificationMethod { + result = append(result, vm.Controller) + } + + return result +} + +// Validation + +func (did Did) Validate(allowedNamespaces []string) error { + return validation.ValidateStruct(&did, + validation.Field(&did.Id, validation.Required, IsDID(allowedNamespaces)), + validation.Field(&did.Controller, IsUniqueStrList(), validation.Each(IsDID(allowedNamespaces))), + validation.Field(&did.VerificationMethod, + IsUniqueVerificationMethodListByIdRule(), validation.Each(ValidVerificationMethodRule(did.Id, allowedNamespaces)), + ), + + validation.Field(&did.Authentication, + IsUniqueStrList(), validation.Each(IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(did.Id)), + ), + validation.Field(&did.AssertionMethod, + IsUniqueStrList(), validation.Each(IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(did.Id)), + ), + validation.Field(&did.CapabilityInvocation, + IsUniqueStrList(), validation.Each(IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(did.Id)), + ), + validation.Field(&did.CapabilityDelegation, + IsUniqueStrList(), validation.Each(IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(did.Id)), + ), + validation.Field(&did.KeyAgreement, + IsUniqueStrList(), validation.Each(IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(did.Id)), + ), + + validation.Field(&did.Service, IsUniqueServiceListByIdRule(), validation.Each(ValidServiceRule(did.Id, allowedNamespaces))), + validation.Field(&did.AlsoKnownAs, IsUniqueStrList(), validation.Each(IsURI())), + ) +} diff --git a/x/cheqd/types/did_did_test.go b/x/cheqd/types/did_did_test.go new file mode 100644 index 000000000..27ab62be9 --- /dev/null +++ b/x/cheqd/types/did_did_test.go @@ -0,0 +1,231 @@ +package types + +import ( + "fmt" + "github.com/stretchr/testify/require" + "testing" +) + +var ValidTestDID = "did:cheqd:testnet:123456789abcdefg" +var ValidTestDID2 = "did:cheqd:testnet:gfedcba987654321" +var InvalidTestDID = "badDid" +var ValidEd25519PubKey = "zF1hVGXXK9rmx5HhMTpGnGQJiab9qrFJbQXBRhSmYjQWX" +var NotValidEd25519PubKey = "zF1hVGXXK9rmx5HhMTpGnGQJi" + +func TestDidValidation(t *testing.T) { + cases := []struct { + name string + struct_ *Did + allowedNamespaces []string + isValid bool + errorMsg string + }{ + { + name: "Valid: Id: allowed DID", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyJwk: nil, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: true, + errorMsg: "", + }, + { + name: "Not valid: Id: not allowed DID", + struct_: &Did{ + Id: InvalidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyJwk: nil, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: false, + errorMsg: "id: unable to split did into method, namespace and id; verification_method: (0: (id: must have prefix: badDid.).).", + }, + { + name: "Valid: Verification Method: all is fine with type Ed25519VerificationKey2020", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyJwk: nil, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: true, + errorMsg: "", + }, + { + name: "Valid: Verification Method: all is fine with type jwk", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "JsonWebKey2020", + Controller: ValidTestDID, + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + }, + }, + isValid: true, + errorMsg: "", + }, + { + name: "Not valid: Verification Method: Wrong id", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: InvalidTestDID, + Type: "JsonWebKey2020", + Controller: ValidTestDID, + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + }, + }, + isValid: false, + errorMsg: "verification_method: (0: (id: unable to split did into method, namespace and id.).).", + }, + { + name: "Not valid: Verification Method: Wrong controller", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "JsonWebKey2020", + Controller: InvalidTestDID, + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + }, + }, + isValid: false, + errorMsg: "verification_method: (0: (controller: unable to split did into method, namespace and id.).).", + }, + { + name: "Valid: Controller: List of DIDs allowed", + struct_: &Did{ + Id: ValidTestDID, + Controller: []string{ValidTestDID, ValidTestDID2}, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: true, + errorMsg: "", + }, + { + name: "Not valid: Controller: List of DIDs is not allowed", + struct_: &Did{ + Context: nil, + Id: ValidTestDID, + Controller: []string{ValidTestDID, InvalidTestDID}, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: false, + errorMsg: "controller: (1: unable to split did into method, namespace and id.).", + }, + { + name: "Allowed namespaces: Negative", + struct_: &Did{ + Id: ValidTestDID, + Controller: []string{ValidTestDID}, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + allowedNamespaces: []string{"mainnet"}, + isValid: false, + errorMsg: "controller: (0: did namespace must be one of: mainnet.); id: did namespace must be one of: mainnet; verification_method: (0: (controller: did namespace must be one of: mainnet; id: did namespace must be one of: mainnet.).).", + }, + { + name: "Controller duplicated: negative", + struct_: &Did{ + Id: ValidTestDID, + Controller: []string{ValidTestDID, ValidTestDID}, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: false, + errorMsg: "controller: there should be no duplicates.", + }, + { + name: "VM duplicated: negative", + struct_: &Did{ + Id: ValidTestDID, + VerificationMethod: []*VerificationMethod{ + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + { + Id: fmt.Sprintf("%s#fragment", ValidTestDID), + Type: "Ed25519VerificationKey2020", + Controller: ValidTestDID, + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + }, + isValid: false, + errorMsg: "verification_method: there are verification method duplicates.", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.Validate(tc.allowedNamespaces) + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errorMsg) + } + }) + } +} diff --git a/x/cheqd/types/did_service.go b/x/cheqd/types/did_service.go new file mode 100644 index 000000000..5d5ef4a6b --- /dev/null +++ b/x/cheqd/types/did_service.go @@ -0,0 +1,71 @@ +package types + +import ( + "errors" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +var SupportedServiceTypes = []string{ + "LinkedDomains", + "DIDCommMessaging", +} + +func NewService(id string, type_ string, serviceEndpoint string) *Service { + return &Service{ + Id: id, + Type: type_, + ServiceEndpoint: serviceEndpoint, + } +} + + +// Helpers + +func GetServiceIds(vms []*Service) []string { + res := make([]string, len(vms)) + + for i := range vms { + res[i] = vms[i].Id + } + + return res +} + + +// Validation + +func (s Service) Validate(baseDid string, allowedNamespaces []string) error { + return validation.ValidateStruct(&s, + validation.Field(&s.Id, validation.Required, IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(baseDid)), + validation.Field(&s.Type, validation.Required, validation.In(utils.ToInterfaces(SupportedServiceTypes)...)), + validation.Field(&s.ServiceEndpoint, validation.Required), + ) +} + +func ValidServiceRule(baseDid string, allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(Service) + if !ok { + panic("ValidVerificationMethodRule must be only applied on verification methods") + } + + return casted.Validate(baseDid, allowedNamespaces) + }) +} + +func IsUniqueServiceListByIdRule() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*Service) + if !ok { + panic("IsUniqueServiceListByIdRule must be only applied on service lists") + } + + ids := GetServiceIds(casted) + if !utils.IsUnique(ids) { + return errors.New("there are service duplicates") + } + + return nil + }) +} diff --git a/x/cheqd/types/did_service_test.go b/x/cheqd/types/did_service_test.go new file mode 100644 index 000000000..9db469cc2 --- /dev/null +++ b/x/cheqd/types/did_service_test.go @@ -0,0 +1,65 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestServiceValidation(t *testing.T) { + cases := []struct { + name string + struct_ Service + baseDid string + allowedNamespaces []string + isValid bool + errorMsg string + }{ + { + name: "positive", + struct_: Service{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#service1", + Type: "DIDCommMessaging", + ServiceEndpoint: "endpoint", + }, + baseDid: "did:cheqd:aaaaaaaaaaaaaaaa", + allowedNamespaces: []string{""}, + isValid: true, + errorMsg: "", + }, + { + name: "negative: namespace", + struct_: Service{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#service1", + Type: "DIDCommMessaging", + ServiceEndpoint: "endpoint", + }, + allowedNamespaces: []string{"mainnet"}, + isValid: false, + errorMsg: "id: did namespace must be one of: mainnet.", + }, + { + name: "negative: base did", + struct_: Service{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#service1", + Type: "DIDCommMessaging", + ServiceEndpoint: "endpoint", + }, + baseDid: "did:cheqd:baaaaaaaaaaaaaab", + isValid: false, + errorMsg: "id: must have prefix: did:cheqd:baaaaaaaaaaaaaab.", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.Validate(tc.baseDid, tc.allowedNamespaces) + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.errorMsg) + } + }) + } +} diff --git a/x/cheqd/types/did_verification_method.go b/x/cheqd/types/did_verification_method.go new file mode 100644 index 000000000..ad3c6c516 --- /dev/null +++ b/x/cheqd/types/did_verification_method.go @@ -0,0 +1,171 @@ +package types + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "errors" + "fmt" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/lestrrat-go/jwx/jwk" + "github.com/multiformats/go-multibase" + "reflect" +) + +const ( + JsonWebKey2020 = "JsonWebKey2020" + Ed25519VerificationKey2020 = "Ed25519VerificationKey2020" +) + +var SupportedMethodTypes = []string{ + JsonWebKey2020, + Ed25519VerificationKey2020, +} + +var JwkMethodTypes = []string{ + JsonWebKey2020, +} + +var MultibaseMethodTypes = []string{ + Ed25519VerificationKey2020, +} + +func NewVerificationMethod(id string, type_ string, controller string, publicKeyJwk []*KeyValuePair, publicKeyMultibase string) *VerificationMethod { + return &VerificationMethod{ + Id: id, + Type: type_, + Controller: controller, + PublicKeyJwk: publicKeyJwk, + PublicKeyMultibase: publicKeyMultibase, + } +} + +// Helpers + +func FindVerificationMethod(vms []VerificationMethod, id string) (VerificationMethod, bool) { + for _, vm := range vms { + if vm.Id == id { + return vm, true + } + } + + return VerificationMethod{}, false +} + +func GetVerificationMethodIds(vms []*VerificationMethod) []string { + res := make([]string, len(vms)) + + for i := range vms { + res[i] = vms[i].Id + } + + return res +} + +func VerifySignature(vm VerificationMethod, message []byte, signature []byte) error { + var verificationError error + + switch vm.Type { + case Ed25519VerificationKey2020: + _, keyBytes, err := multibase.Decode(vm.PublicKeyMultibase) + if err != nil { + return err + } + + verificationError = utils.VerifyED25519Signature(keyBytes, message, signature) + + case JsonWebKey2020: + keyJson, err := PubKeyJWKToJson(vm.PublicKeyJwk) + if err != nil { + return err + } + + var raw interface{} + err = jwk.ParseRawKey([]byte(keyJson), &raw) + if err != nil { + return fmt.Errorf("can't parse jwk: %s", err.Error()) + } + + switch pubKey := raw.(type) { + case *rsa.PublicKey: + verificationError = utils.VerifyRSASignature(*pubKey, message, signature) + case *ecdsa.PublicKey: + verificationError = utils.VerifyECDSASignature(*pubKey, message, signature) + case ed25519.PublicKey: + verificationError = utils.VerifyED25519Signature(pubKey, message, signature) + default: + panic("unsupported jwk key") // This should have been checked during basic validation + } + + default: + panic("unsupported verification method type") // This should have also been checked during basic validation + } + + if verificationError != nil { + return ErrInvalidSignature.Wrapf("verification method: %s, err: %s", vm.Id, verificationError.Error()) + } + + return nil +} + +func VerificationMethodListToMapByFragment(vms []*VerificationMethod) map[string]VerificationMethod { + result := map[string]VerificationMethod{} + + for _, vm := range vms { + _, _, _, fragment := utils.MustSplitDIDUrl(vm.Id) + result[fragment] = *vm + } + + return result +} + +func CompareVerificationMethodsWithoutIds(vm1, vm2 VerificationMethod) bool { + // We can override ids because on local copies + vm1.Id = "" + vm2.Id = "" + return reflect.DeepEqual(vm1, vm2) +} + +// Validation + +func (vm VerificationMethod) Validate(baseDid string, allowedNamespaces []string) error { + return validation.ValidateStruct(&vm, + validation.Field(&vm.Id, validation.Required, IsDIDUrl(allowedNamespaces, Empty, Empty, Required), HasPrefix(baseDid)), + validation.Field(&vm.Controller, validation.Required, IsDID(allowedNamespaces)), + validation.Field(&vm.Type, validation.Required, validation.In(utils.ToInterfaces(SupportedMethodTypes)...)), + validation.Field(&vm.PublicKeyJwk, + validation.When(utils.Contains(JwkMethodTypes, vm.Type), validation.Required, IsUniqueKeyValuePairListByKeyRule(), IsJWK()).Else(validation.Empty), + ), + validation.Field(&vm.PublicKeyMultibase, + validation.When(utils.Contains(MultibaseMethodTypes, vm.Type), validation.Required, IsMultibase(), IsMultibaseEncodedEd25519PubKey()).Else(validation.Empty), + ), + ) +} + +func ValidVerificationMethodRule(baseDid string, allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(VerificationMethod) + if !ok { + panic("ValidVerificationMethodRule must be only applied on verification methods") + } + + return casted.Validate(baseDid, allowedNamespaces) + }) +} + +func IsUniqueVerificationMethodListByIdRule() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*VerificationMethod) + if !ok { + panic("IsUniqueVerificationMethodListByIdRule must be only applied on VM lists") + } + + ids := GetVerificationMethodIds(casted) + if !utils.IsUnique(ids) { + return errors.New("there are verification method duplicates") + } + + return nil + }) +} diff --git a/x/cheqd/types/did_verification_method_test.go b/x/cheqd/types/did_verification_method_test.go new file mode 100644 index 000000000..0cd21a84f --- /dev/null +++ b/x/cheqd/types/did_verification_method_test.go @@ -0,0 +1,293 @@ +package types + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "testing" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/multiformats/go-multibase" + "github.com/stretchr/testify/require" +) + +type TestJWKKey struct { + Kty string `json:"kty"` + N string `json:"n"` + Use string `json:"use"` + Alg string `json:"alg"` + E string `json:"e"` + Kid string `json:"kid"` +} + +var ValidJWKKey = TestJWKKey{ + Kty: "RSA", + N: "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ", + Use: "sig", + Alg: "RS256", + E: "AQAB", + Kid: "6a8ba5652a7044121d4fedac8f14d14c54e4895b", +} + +var NotValidJWKKey = TestJWKKey{ + Kty: "SomeOtherKeyType", + N: "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ", + Use: "sig", + Alg: "RS256", + E: "AQAB", + Kid: "6a8ba5652a7044121d4fedac8f14d14c54e4895b", +} + +var ValidJWKByte, _ = json.Marshal(ValidJWKKey) +var NotValidJWKByte, _ = json.Marshal(NotValidJWKKey) + +var ValidPublicKeyJWK = JSONToPubKeyJWK(string(ValidJWKByte)) +var NotValidPublicKeyJWK = JSONToPubKeyJWK(string(NotValidJWKByte)) + +func TestVerificationMethodValidation(t *testing.T) { + cases := []struct { + name string + struct_ VerificationMethod + baseDid string + allowedNamespaces []string + isValid bool + errorMsg string + }{ + { + name: "valid method with multibase key", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#qwe", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: nil, + PublicKeyMultibase: ValidEd25519PubKey, + }, + isValid: true, + errorMsg: "", + }, + { + name: "valid method with jwk key", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#rty", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + isValid: true, + errorMsg: "", + }, + { + name: "base did: positive", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#rty", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + baseDid: "did:cheqd:aaaaaaaaaaaaaaaa", + isValid: true, + errorMsg: "", + }, + { + name: "base did: negative", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#rty", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + baseDid: "did:cheqd:bbbbbbbbbbbbbbbb", + isValid: false, + errorMsg: "id: must have prefix: did:cheqd:bbbbbbbbbbbbbbbb.", + }, + { + name: "allowed namespaces: positive", + struct_: VerificationMethod{ + Id: "did:cheqd:mainnet:aaaaaaaaaaaaaaaa#rty", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + allowedNamespaces: []string{"mainnet", ""}, + isValid: true, + }, + { + name: "allowed namespaces: positive", + struct_: VerificationMethod{ + Id: "did:cheqd:mainnet:aaaaaaaaaaaaaaaa#rty", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + allowedNamespaces: []string{"testnet"}, + isValid: false, + errorMsg: "controller: did namespace must be one of: testnet; id: did namespace must be one of: testnet.", + }, + { + name: "JWK: valid key", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#qwe", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: ValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + isValid: true, + }, + { + name: "JWK: not valid key", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#qwe", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: NotValidPublicKeyJWK, + PublicKeyMultibase: "", + }, + isValid: false, + errorMsg: "public_key_jwk: can't parse jwk: failed to parse key: invalid key type from JSON (SomeOtherKeyType).", + }, + { + name: "all keys and values are required in jwk", + struct_: VerificationMethod{ + Id: "did:cheqd:aaaaaaaaaaaaaaaa#qwe", + Type: "JsonWebKey2020", + Controller: "did:cheqd:bbbbbbbbbbbbbbbb", + PublicKeyJwk: append(ValidPublicKeyJWK, &KeyValuePair{Key: "", Value: ""}), + PublicKeyMultibase: "", + }, + isValid: false, + errorMsg: "public_key_jwk: (6: (key: cannot be blank; value: cannot be blank.).).", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.Validate(tc.baseDid, tc.allowedNamespaces) + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.errorMsg) + } + }) + } +} + +func TestEd25519SignatureVerification(t *testing.T) { + message := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua." + msgBytes := []byte(message) + + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + signature := ed25519.Sign(privKey, msgBytes) + + pubKeyStr, err := multibase.Encode(multibase.Base58BTC, pubKey) + require.NoError(t, err) + + vm := VerificationMethod{ + Id: "", + Type: "Ed25519VerificationKey2020", + Controller: "", + PublicKeyJwk: nil, + PublicKeyMultibase: pubKeyStr, + } + + err = VerifySignature(vm, msgBytes, signature) + require.NoError(t, err) + + jwk_, err := jwk.New(pubKey) + require.NoError(t, err) + json_, err := json.MarshalIndent(jwk_, "", " ") + require.NoError(t, err) + pubKeyJwk := JSONToPubKeyJWK(string(json_)) + + vm2 := VerificationMethod{ + Id: "", + Type: "JsonWebKey2020", + Controller: "", + PublicKeyJwk: pubKeyJwk, + PublicKeyMultibase: "", + } + + err = VerifySignature(vm2, msgBytes, signature) + require.NoError(t, err) +} + +func TestECDSASignatureVerification(t *testing.T) { + message := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua." + msgBytes := []byte(message) + + hasher := crypto.SHA256.New() + hasher.Write(msgBytes) + msgDigest := hasher.Sum(nil) + + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + pubKey := privKey.PublicKey + + signature, err := ecdsa.SignASN1(rand.Reader, privKey, msgDigest) + require.NoError(t, err) + + jwk_, err := jwk.New(pubKey) + require.NoError(t, err) + json_, err := json.MarshalIndent(jwk_, "", " ") + require.NoError(t, err) + pubKeyJwk := JSONToPubKeyJWK(string(json_)) + + vm2 := VerificationMethod{ + Id: "", + Type: "JsonWebKey2020", + Controller: "", + PublicKeyJwk: pubKeyJwk, + PublicKeyMultibase: "", + } + + err = VerifySignature(vm2, msgBytes, signature) + require.NoError(t, err) +} + +func TestRSASignatureVerification(t *testing.T) { + message := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua." + msgBytes := []byte(message) + + hasher := crypto.SHA256.New() + hasher.Write(msgBytes) + msgDigest := hasher.Sum(nil) + + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + pubKey := privKey.PublicKey + + signature, err := rsa.SignPSS(rand.Reader, privKey, crypto.SHA256, msgDigest, nil) + require.NoError(t, err) + + jwk_, err := jwk.New(pubKey) + require.NoError(t, err) + json_, err := json.MarshalIndent(jwk_, "", " ") + require.NoError(t, err) + pubKeyJwk := JSONToPubKeyJWK(string(json_)) + + vm2 := VerificationMethod{ + Id: "", + Type: "JsonWebKey2020", + Controller: "", + PublicKeyJwk: pubKeyJwk, + PublicKeyMultibase: "", + } + + err = VerifySignature(vm2, msgBytes, signature) + require.NoError(t, err) +} diff --git a/x/cheqd/types/error.go b/x/cheqd/types/error.go new file mode 100644 index 000000000..48bb7fc60 --- /dev/null +++ b/x/cheqd/types/error.go @@ -0,0 +1,22 @@ +package types + +// DONTCOVER + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// x/cheqd module sentinel errors +var ( + ErrBadRequest = sdkerrors.Register(ModuleName, 1000, "bad request") + ErrInvalidSignature = sdkerrors.Register(ModuleName, 1100, "invalid signature detected") + ErrSignatureNotFound = sdkerrors.Register(ModuleName, 1101, "signature is required but not found") + ErrDidDocExists = sdkerrors.Register(ModuleName, 1200, "DID Doc exists") + ErrDidDocNotFound = sdkerrors.Register(ModuleName, 1201, "DID Doc not found") + ErrVerificationMethodNotFound = sdkerrors.Register(ModuleName, 1202, "verification method not found") + ErrUnexpectedDidVersion = sdkerrors.Register(ModuleName, 1203, "unexpected DID version") + ErrBasicValidation = sdkerrors.Register(ModuleName, 1205, "basic validation failed") + ErrNamespaceValidation = sdkerrors.Register(ModuleName, 1206, "DID namespace validation failed") + ErrUnpackStateValue = sdkerrors.Register(ModuleName, 1300, "invalid did state value") + ErrInternal = sdkerrors.Register(ModuleName, 1500, "internal error") +) diff --git a/x/cheqd/types/errors.go b/x/cheqd/types/errors.go deleted file mode 100644 index 88c0b1e75..000000000 --- a/x/cheqd/types/errors.go +++ /dev/null @@ -1,26 +0,0 @@ -package types - -// DONTCOVER - -import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -// x/cheqd module sentinel errors -var ( - ErrBadRequest = sdkerrors.Register(ModuleName, 1000, "bad request") - ErrBadRequestIsRequired = sdkerrors.Register(ModuleName, 1001, "is required") - ErrBadRequestIsNotDid = sdkerrors.Register(ModuleName, 1002, "is not DID") - ErrBadRequestInvalidVerMethod = sdkerrors.Register(ModuleName, 1003, "invalid verification method") - ErrBadRequestInvalidService = sdkerrors.Register(ModuleName, 1004, "invalid service") - ErrBadRequestIsNotDidFragment = sdkerrors.Register(ModuleName, 1005, "is not DID fragment") - ErrInvalidSignature = sdkerrors.Register(ModuleName, 1100, "invalid signature detected") - ErrDidDocExists = sdkerrors.Register(ModuleName, 1200, "DID Doc exists") - ErrDidDocNotFound = sdkerrors.Register(ModuleName, 1201, "DID Doc not found") - ErrVerificationMethodNotFound = sdkerrors.Register(ModuleName, 1202, "verification method not found") - ErrUnexpectedDidVersion = sdkerrors.Register(ModuleName, 1203, "unexpected DID version") - ErrInvalidPublicKey = sdkerrors.Register(ModuleName, 1204, "invalid public key") - ErrInvalidDidStateValue = sdkerrors.Register(ModuleName, 1300, "invalid did state value") - ErrSetToState = sdkerrors.Register(ModuleName, 1304, "cannot set to state") - ErrNotImplemented = sdkerrors.Register(ModuleName, 1501, "not implemented") -) diff --git a/x/cheqd/types/identity_msg.go b/x/cheqd/types/identity_msg.go deleted file mode 100644 index f532c6959..000000000 --- a/x/cheqd/types/identity_msg.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -type ( - IdentityMsg interface { - Validate(namespace string) error - GetSigners() []Signer - GetSignBytes() []byte - } - - Signer struct { - Signer string - Authentication []string - VerificationMethod []*VerificationMethod - } -) diff --git a/x/cheqd/types/keys.go b/x/cheqd/types/keys.go index df9b64624..f7efc7bcd 100644 --- a/x/cheqd/types/keys.go +++ b/x/cheqd/types/keys.go @@ -14,8 +14,6 @@ const ( QuerierRoute = ModuleName DidMethod = ModuleName - - DidPrefix = "did" ) func KeyPrefix(p string) []byte { diff --git a/x/cheqd/types/messages.go b/x/cheqd/types/messages.go deleted file mode 100644 index 6282ff96d..000000000 --- a/x/cheqd/types/messages.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -const ( - MessageCreateDid = "/cheqdid.cheqdnode.cheqd.v1.MsgCreateDidPayload" -) - -const ( - MessageUpdateDid = "/cheqdid.cheqdnode.cheqd.v1.MsgUpdateDidPayload" -) diff --git a/x/cheqd/types/messages_did.go b/x/cheqd/types/messages_did.go deleted file mode 100644 index ab0e78c7f..000000000 --- a/x/cheqd/types/messages_did.go +++ /dev/null @@ -1,453 +0,0 @@ -package types - -import ( - "github.com/cheqd/cheqd-node/x/cheqd/utils" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "strings" -) - -var _ sdk.Msg = &MsgCreateDid{} - -func NewMsgCreateDid(payload *MsgCreateDidPayload, signatures []*SignInfo) *MsgCreateDid { - return &MsgCreateDid{ - Payload: payload, - Signatures: signatures, - } -} - -func (msg *MsgCreateDid) Route() string { - return RouterKey -} - -func (msg *MsgCreateDid) Type() string { - return "MsgCreateDid" -} - -func (msg *MsgCreateDid) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{} -} - -func (msg *MsgCreateDid) GetSignBytes() []byte { - bz := ModuleCdc.MustMarshal(msg) - return sdk.MustSortJSON(bz) -} - -func (msg *MsgCreateDid) ValidateBasic() error { - if msg.Payload == nil { - return ErrBadRequestIsRequired.Wrap("Payload") - } - - if len(msg.Signatures) == 0 { - return ErrBadRequestIsRequired.Wrap("Signatures") - } - - return nil -} - -var _ sdk.Msg = &MsgUpdateDid{} - -func NewMsgUpdateDid(payload *MsgUpdateDidPayload, signatures []*SignInfo) *MsgUpdateDid { - return &MsgUpdateDid{ - Payload: payload, - Signatures: signatures, - } -} - -func (msg *MsgUpdateDid) Route() string { - return RouterKey -} - -func (msg *MsgUpdateDid) Type() string { - return "WriteRequest" -} - -func (msg *MsgUpdateDid) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{} -} - -func (msg *MsgUpdateDid) GetSignBytes() []byte { - bz := ModuleCdc.MustMarshal(msg) - return sdk.MustSortJSON(bz) -} - -func (msg *MsgUpdateDid) ValidateBasic() error { - if msg.Payload == nil { - return ErrBadRequestIsRequired.Wrap("Payload") - } - - if len(msg.Signatures) == 0 { - return ErrBadRequestIsRequired.Wrap("Signatures") - } - - return nil -} - -func NewMsgCreateDidPayloadPayload( - context []string, - id string, - controller []string, - verificationMethod []*VerificationMethod, - authentication []string, - assertionMethod []string, - capabilityInvocation []string, - capabilityDelegation []string, - keyAgreement []string, - alsoKnownAs []string, - service []*Service, -) *MsgCreateDidPayload { - return &MsgCreateDidPayload{ - Context: context, - Id: id, - Controller: controller, - VerificationMethod: verificationMethod, - Authentication: authentication, - AssertionMethod: assertionMethod, - CapabilityInvocation: capabilityInvocation, - CapabilityDelegation: capabilityDelegation, - KeyAgreement: keyAgreement, - AlsoKnownAs: alsoKnownAs, - Service: service, - } -} - -var _ IdentityMsg = &MsgCreateDidPayload{} - -func (msg *MsgCreateDidPayload) GetSigners() []Signer { - if len(msg.Controller) > 0 { - result := make([]Signer, len(msg.Controller)) - - for i, controller := range msg.Controller { - if controller == msg.Id { - result[i] = Signer{ - Signer: controller, - Authentication: msg.Authentication, - VerificationMethod: msg.VerificationMethod, - } - } else { - result[i] = Signer{ - Signer: controller, - } - } - } - - return result - } - - if len(msg.Authentication) > 0 { - return []Signer{ - { - Signer: msg.Id, - Authentication: msg.Authentication, - VerificationMethod: msg.VerificationMethod, - }, - } - } - - return []Signer{} -} - -func (msg *MsgCreateDidPayload) Validate(namespace string) error { - if !utils.IsValidDid(namespace, msg.Id) { - return ErrBadRequestIsNotDid.Wrap("Id") - } - - if notValid, i := utils.IsNotValidDIDArray(namespace, msg.Controller); notValid { - return ErrBadRequestIsNotDid.Wrapf("Controller item %s at position %d", msg.Controller[i], i) - } - - if err := ValidateVerificationMethods(namespace, msg.Id, msg.VerificationMethod); err != nil { - return err - } - - if err := ValidateServices(namespace, msg.Id, msg.Service); err != nil { - return err - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.Authentication); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("Authentication item %s", msg.Authentication[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.CapabilityInvocation); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("CapabilityInvocation item %s", msg.CapabilityInvocation[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.CapabilityDelegation); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("CapabilityDelegation item %s", msg.CapabilityDelegation[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.KeyAgreement); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("KeyAgreement item %s", msg.KeyAgreement[i]) - } - - if len(msg.Authentication) == 0 && len(msg.Controller) == 0 { - return ErrBadRequest.Wrap("The message must contain either a Controller or a Authentication") - } - - for _, i := range msg.Authentication { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.KeyAgreement { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.CapabilityDelegation { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.CapabilityInvocation { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - return nil -} - -func (msg *MsgCreateDidPayload) GetSignBytes() []byte { - return ModuleCdc.MustMarshal(msg) -} - -var _ IdentityMsg = &MsgUpdateDidPayload{} - -func NewMsgUpdateDidPayloadPayload( - context []string, - id string, - controller []string, - verificationMethod []*VerificationMethod, - authentication []string, - assertionMethod []string, - capabilityInvocation []string, - capabilityDelegation []string, - keyAgreement []string, - alsoKnownAs []string, - service []*Service, -) *MsgUpdateDidPayload { - return &MsgUpdateDidPayload{ - Context: context, - Id: id, - Controller: controller, - VerificationMethod: verificationMethod, - Authentication: authentication, - AssertionMethod: assertionMethod, - CapabilityInvocation: capabilityInvocation, - CapabilityDelegation: capabilityDelegation, - KeyAgreement: keyAgreement, - AlsoKnownAs: alsoKnownAs, - Service: service, - } -} - -func (msg *MsgUpdateDidPayload) GetSigners() []Signer { - if len(msg.Controller) > 0 { - result := make([]Signer, len(msg.Controller)) - - for i, controller := range msg.Controller { - if controller == msg.Id { - result[i] = Signer{ - Signer: controller, - Authentication: msg.Authentication, - VerificationMethod: msg.VerificationMethod, - } - } else { - result[i] = Signer{ - Signer: controller, - } - } - } - - return result - } - - if len(msg.Authentication) > 0 { - return []Signer{ - { - Signer: msg.Id, - Authentication: msg.Authentication, - VerificationMethod: msg.VerificationMethod, - }, - } - } - - return []Signer{} -} - -func (msg *MsgUpdateDidPayload) Validate(namespace string) error { - if !utils.IsValidDid(namespace, msg.Id) { - return ErrBadRequestIsNotDid.Wrap("Id") - } - - if notValid, i := utils.IsNotValidDIDArray(namespace, msg.Controller); notValid { - return ErrBadRequestIsNotDid.Wrapf("Controller item %s at position %d", msg.Controller[i], i) - } - - if err := ValidateVerificationMethods(namespace, msg.Id, msg.VerificationMethod); err != nil { - return err - } - - if err := ValidateServices(namespace, msg.Id, msg.Service); err != nil { - return err - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.Authentication); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("Authentication item %s", msg.Authentication[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.CapabilityInvocation); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("CapabilityInvocation item %s", msg.CapabilityInvocation[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.CapabilityDelegation); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("CapabilityDelegation item %s", msg.CapabilityDelegation[i]) - } - - if notValid, i := utils.IsNotValidDIDArrayFragment(namespace, msg.KeyAgreement); notValid { - return ErrBadRequestIsNotDidFragment.Wrapf("KeyAgreement item %s", msg.KeyAgreement[i]) - } - - if len(msg.Authentication) == 0 && len(msg.Controller) == 0 { - return ErrBadRequest.Wrap("The message must contain either a Controller or a Authentication") - } - - for _, i := range msg.Authentication { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.KeyAgreement { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.CapabilityDelegation { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - for _, i := range msg.CapabilityInvocation { - if !IncludeVerificationMethod(msg.Id, msg.VerificationMethod, i) { - return ErrVerificationMethodNotFound.Wrap(i) - } - } - - return nil -} - -func (msg *MsgUpdateDidPayload) GetSignBytes() []byte { - return ModuleCdc.MustMarshal(msg) -} - -func ValidateVerificationMethods(namespace string, did string, vms []*VerificationMethod) error { - for i, vm := range vms { - if err := ValidateVerificationMethod(namespace, vm); err != nil { - return ErrBadRequestInvalidVerMethod.Wrap(sdkerrors.Wrapf(err, "index %d, value %s", i, vm.Id).Error()) - } - } - - for i, vm := range vms { - if !strings.HasPrefix(vm.Id, did) { - return ErrBadRequestInvalidVerMethod.Wrapf("%s not belong %s DID Doc", vm.Id, did) - } - - if IncludeVerificationMethod(did, vms[i+1:], vm.Id) { - return ErrBadRequestInvalidVerMethod.Wrapf("%s is duplicated", vm.Id) - } - } - - return nil -} - -func ValidateVerificationMethod(namespace string, vm *VerificationMethod) error { - if !utils.IsFullDidFragment(namespace, vm.Id) { - return ErrBadRequestIsNotDidFragment.Wrap(vm.Id) - } - - if len(vm.PublicKeyMultibase) != 0 && len(vm.PublicKeyJwk) != 0 { - return ErrBadRequest.Wrap("contains multiple verification material properties") - } - - switch utils.GetVerificationMethodType(vm.Type) { - case utils.PublicKeyJwk: - if len(vm.PublicKeyJwk) == 0 { - return ErrBadRequest.Wrapf("%s: should contain `PublicKeyJwk` verification material property", vm.Type) - } - case utils.PublicKeyMultibase: - if len(vm.PublicKeyMultibase) == 0 { - return ErrBadRequest.Wrapf("%s: should contain `PublicKeyMultibase` verification material property", vm.Type) - } - default: - return ErrBadRequest.Wrapf("%s: unsupported verification method type", vm.Type) - } - - if len(vm.PublicKeyMultibase) == 0 && vm.PublicKeyJwk == nil { - return ErrBadRequest.Wrap("The verification method must contain either a PublicKeyMultibase or a PublicKeyJwk") - } - - if len(vm.Controller) == 0 { - return ErrBadRequestIsRequired.Wrap("Controller") - } - - return nil -} - -func ValidateServices(namespace string, did string, services []*Service) error { - for i, s := range services { - if err := ValidateService(namespace, s); err != nil { - return ErrBadRequestInvalidService.Wrap(sdkerrors.Wrapf(err, "index %d, value %s", i, s.Id).Error()) - } - } - - for i, s := range services { - if !strings.HasPrefix(utils.ResolveId(did, s.Id), did) { - return ErrBadRequestInvalidService.Wrapf("%s not belong %s DID Doc", s.Id, did) - } - - if IncludeService(did, services[i+1:], s.Id) { - return ErrBadRequestInvalidService.Wrapf("%s is duplicated", s.Id) - } - } - - return nil -} - -func ValidateService(namespace string, s *Service) error { - if !utils.IsDidFragment(namespace, s.Id) { - return ErrBadRequestIsNotDidFragment.Wrap(s.Id) - } - - if !utils.IsValidDidServiceType(s.Type) { - return ErrBadRequest.Wrapf("%s: unsupported service type", s.Type) - } - - return nil -} - -func IncludeVerificationMethod(did string, vms []*VerificationMethod, id string) bool { - for _, vm := range vms { - if vm.Id == utils.ResolveId(did, id) { - return true - } - } - - return false -} - -func IncludeService(did string, services []*Service, id string) bool { - for _, s := range services { - if utils.ResolveId(did, s.Id) == utils.ResolveId(did, id) { - return true - } - } - - return false -} diff --git a/x/cheqd/types/msg_test.go b/x/cheqd/types/msg_test.go deleted file mode 100644 index 2cdc703d0..000000000 --- a/x/cheqd/types/msg_test.go +++ /dev/null @@ -1,856 +0,0 @@ -package types - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -const Prefix = "did:cheqd:test:" - -func TestNewMsgCreateDidValidation(t *testing.T) { - cases := []struct { - valid bool - name string - msg *MsgCreateDid - errMsg string - }{ - {true, "Valid Create Did Msg", NewMsgCreateDid(&MsgCreateDidPayload{Id: "1"}, []*SignInfo{{VerificationMethodId: "foo", Signature: "bar"}}), ""}, - {false, "Payload is missed", NewMsgCreateDid(nil, nil), "Payload: is required"}, - {false, "Signatures is missed", NewMsgCreateDid(&MsgCreateDidPayload{Id: "1"}, nil), "Signatures: is required"}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.msg.ValidateBasic() - - if tc.valid { - require.Nil(t, err) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - }) - } -} - -func TestNewMsgUpdateDidValidation(t *testing.T) { - cases := []struct { - valid bool - name string - msg *MsgUpdateDid - errMsg string - }{ - {true, "Valid Update Did Msg", NewMsgUpdateDid(&MsgUpdateDidPayload{Id: "1"}, []*SignInfo{{VerificationMethodId: "foo", Signature: "bar"}}), ""}, - {false, "Payload is missed", NewMsgUpdateDid(nil, nil), "Payload: is required"}, - {false, "Signatures is missed", NewMsgUpdateDid(&MsgUpdateDidPayload{Id: "1"}, nil), "Signatures: is required"}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.msg.ValidateBasic() - - if tc.valid { - require.Nil(t, err) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - }) - } -} - -func TestMsgCreateDidPayloadPayload(t *testing.T) { - cases := []struct { - valid bool - msg *MsgCreateDidPayload - errMsg string - }{ - { - false, - &MsgCreateDidPayload{}, - "Id: is not DID", - }, - { - false, - &MsgCreateDidPayload{Id: ""}, - "Id: is not DID", - }, - { - false, - &MsgCreateDidPayload{Id: "did:ch:test:alice"}, - "Id: is not DID", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice"}, - "The message must contain either a Controller or a Authentication: bad request", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{}, - }, - "The message must contain either a Controller or a Authentication: bad request", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{{}}, - }, - "index 0, value : : is not DID fragment: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{"dd"}}, - "Authentication item dd: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{""}}, - "Authentication item : is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{"did:cheqd:test:alice"}}, - "Authentication item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Authentication: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{"dd"}}, - "CapabilityInvocation item dd: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{""}}, - "CapabilityInvocation item : is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{"did:cheqd:test:alice"}}, - "CapabilityInvocation item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - CapabilityInvocation: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{"dd"}}, - "CapabilityDelegation item dd: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{""}}, - "CapabilityDelegation item : is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{"did:cheqd:test:alice"}}, - "CapabilityDelegation item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - CapabilityDelegation: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{"dd"}}, - "KeyAgreement item dd: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{""}}, - "KeyAgreement item : is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{"did:cheqd:test:alice"}}, - "KeyAgreement item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - KeyAgreement: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - KeyAgreement: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd::alice"}, - }, - "Controller item did:cheqd::alice at position 0: is not DID", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "dasda"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value dasda: dasda: is not DID fragment: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "did:cheqd:test:alice#key-1"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: : unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "did:cheqd:test:alice#key-1", Type: "YES"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: YES: unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "JsonWebKey2020", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: JsonWebKey2020: should contain `PublicKeyJwk` verification material property: bad request: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "JsonWebKey2020", - PublicKeyMultibase: "tetetet", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: JsonWebKey2020: should contain `PublicKeyJwk` verification material property: bad request: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1 is duplicated: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-2", - Type: "JsonWebKey2020", - PublicKeyJwk: []*KeyValuePair{ - { - Key: "x", - Value: "sadad", - }, - }, - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-3", - Type: "JsonWebKey20212", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 2, value did:cheqd:test:alice#key-3: JsonWebKey20212: unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - {}, - }, - }, - "index 0, value : : is not DID fragment: invalid service", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "weqweqw", - }, - }, - }, - "index 0, value weqweqw: weqweqw: is not DID fragment: invalid service", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "#service-1", - }, - }, - }, - "index 0, value #service-1: : unsupported service type: bad request: invalid service", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - }, - }, - "#service-1 is duplicated: invalid service", - }, - { - false, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "did:cheqd:test:alice#service-1", - Type: "DIDCommMessaging", - }, - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - }, - }, - "did:cheqd:test:alice#service-1 is duplicated: invalid service", - }, - { - true, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - VerificationMethod: []*VerificationMethod{}, - }, - "", - }, - { - true, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{}, - }, - "", - }, - { - true, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}}, - "", - }, - { - true, - &MsgCreateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice", "did:cheqd:test:bob"}, - Authentication: []string{"#key-1", "did:cheqd:test:alice#key-2"}, - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "JsonWebKey2020", - PublicKeyJwk: []*KeyValuePair{ - { - Key: "x", - Value: "sadad", - }, - }, - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-2", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - }, - "", - }, - } - - for _, tc := range cases { - err := tc.msg.Validate(Prefix) - - if tc.valid { - require.Nil(t, err) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - } -} - -func TestNewMsgUpdateDidPayload(t *testing.T) { - cases := []struct { - valid bool - msg *MsgUpdateDidPayload - errMsg string - }{ - { - false, - &MsgUpdateDidPayload{}, - "Id: is not DID", - }, - { - false, - &MsgUpdateDidPayload{Id: ""}, - "Id: is not DID", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:ch:test:alice"}, - "Id: is not DID", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice"}, - "The message must contain either a Controller or a Authentication: bad request", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{}, - }, - "The message must contain either a Controller or a Authentication: bad request", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{{}}, - }, - "index 0, value : : is not DID fragment: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{"dd"}}, - "Authentication item dd: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{""}}, - "Authentication item : is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", Authentication: []string{"did:cheqd:test:alice"}}, - "Authentication item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Authentication: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{"dd"}}, - "CapabilityInvocation item dd: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{""}}, - "CapabilityInvocation item : is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityInvocation: []string{"did:cheqd:test:alice"}}, - "CapabilityInvocation item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - CapabilityInvocation: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{"dd"}}, - "CapabilityDelegation item dd: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{""}}, - "CapabilityDelegation item : is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", CapabilityDelegation: []string{"did:cheqd:test:alice"}}, - "CapabilityDelegation item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - CapabilityDelegation: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{"dd"}}, - "KeyAgreement item dd: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{""}}, - "KeyAgreement item : is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{Id: "did:cheqd:test:alice", KeyAgreement: []string{"did:cheqd:test:alice"}}, - "KeyAgreement item did:cheqd:test:alice: is not DID fragment", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - KeyAgreement: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1: verification method not found", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - KeyAgreement: []string{"did:cheqd:test:alice#key-1"}, - Controller: []string{"did:cheqd::alice"}, - }, - "Controller item did:cheqd::alice at position 0: is not DID", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "dasda"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value dasda: dasda: is not DID fragment: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "did:cheqd:test:alice#key-1"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: : unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - {Id: "did:cheqd:test:alice#key-1", Type: "YES"}, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: YES: unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: Ed25519VerificationKey2020: should contain `PublicKeyMultibase` verification material property: bad request: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "JsonWebKey2020", - PublicKeyJwk: []*KeyValuePair{ - {Key: "x", Value: "y"}, - }, - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 0, value did:cheqd:test:alice#key-1: Controller: is required: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "did:cheqd:test:alice#key-1 is duplicated: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-2", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-3", - Type: "JsonWebKey20212", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - Controller: []string{"did:cheqd:test:alice"}, - }, - "index 2, value did:cheqd:test:alice#key-3: JsonWebKey20212: unsupported verification method type: bad request: invalid verification method", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - {}, - }, - }, - "index 0, value : : is not DID fragment: invalid service", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "weqweqw", - }, - }, - }, - "index 0, value weqweqw: weqweqw: is not DID fragment: invalid service", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "#service-1", - }, - }, - }, - "index 0, value #service-1: : unsupported service type: bad request: invalid service", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - }, - }, - "#service-1 is duplicated: invalid service", - }, - { - false, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{ - { - Id: "did:cheqd:test:alice#service-1", - Type: "DIDCommMessaging", - }, - { - Id: "#service-1", - Type: "DIDCommMessaging", - }, - }, - }, - "did:cheqd:test:alice#service-1 is duplicated: invalid service", - }, - { - true, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - VerificationMethod: []*VerificationMethod{}, - }, - "", - }, - { - true, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}, - Service: []*Service{}, - }, - "", - }, - { - true, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice"}}, - "", - }, - { - true, - &MsgUpdateDidPayload{ - Id: "did:cheqd:test:alice", - Controller: []string{"did:cheqd:test:alice", "did:cheqd:test:bob"}, - Authentication: []string{"#key-1", "did:cheqd:test:alice#key-2"}, - VerificationMethod: []*VerificationMethod{ - { - Id: "did:cheqd:test:alice#key-1", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - { - Id: "did:cheqd:test:alice#key-2", - Type: "Ed25519VerificationKey2020", - PublicKeyMultibase: "tetetet", - Controller: "did:cheqd:test:alice", - }, - }, - }, - "", - }, - } - - for _, tc := range cases { - err := tc.msg.Validate(Prefix) - - if tc.valid { - require.Nil(t, err) - } else { - require.Error(t, err) - require.Equal(t, tc.errMsg, err.Error()) - } - } -} diff --git a/x/cheqd/types/query.pb.go b/x/cheqd/types/query.pb.go index b83d8e4a8..3556eff0c 100644 --- a/x/cheqd/types/query.pb.go +++ b/x/cheqd/types/query.pb.go @@ -132,7 +132,7 @@ func init() { func init() { proto.RegisterFile("cheqd/v1/query.proto", fileDescriptor_a2982774eb5e71a9) } var fileDescriptor_a2982774eb5e71a9 = []byte{ - // 317 bytes of a gzipped FileDescriptorProto + // 313 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0xce, 0x48, 0x2d, 0x4c, 0xd1, 0x2f, 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x02, 0x8b, 0x66, 0xa6, 0xe8, 0x81, 0xe9, 0xbc, 0xfc, 0x94, 0x54, 0x08, 0x4b, 0xaf, @@ -145,14 +145,14 @@ var fileDescriptor_a2982774eb5e71a9 = []byte{ 0x3f, 0xaf, 0x38, 0x55, 0xc8, 0x90, 0x8b, 0x39, 0x05, 0xaa, 0x90, 0xdb, 0x48, 0x5e, 0x0f, 0xb7, 0xa3, 0xf5, 0x40, 0xba, 0x40, 0x6a, 0x85, 0x1c, 0xb8, 0x38, 0x72, 0x53, 0x4b, 0x12, 0x53, 0x12, 0x4b, 0x12, 0x25, 0x98, 0xc0, 0xfa, 0x54, 0xf0, 0xe9, 0xf3, 0x85, 0xaa, 0x0d, 0x82, 0xeb, 0x32, - 0x9a, 0xc9, 0xc8, 0xc5, 0x0a, 0x76, 0x8c, 0x50, 0x3f, 0x23, 0x17, 0xb3, 0x4b, 0x66, 0x8a, 0x90, - 0x1e, 0x3e, 0x13, 0x30, 0xbd, 0x27, 0xa5, 0x4f, 0xb4, 0x7a, 0x88, 0x3f, 0x95, 0xd4, 0x9b, 0x2e, - 0x3f, 0x99, 0xcc, 0xa4, 0x28, 0x24, 0xaf, 0x0f, 0x09, 0x49, 0xb8, 0x36, 0x28, 0x3f, 0x25, 0x33, - 0x45, 0xbf, 0x3a, 0x33, 0xa5, 0xd6, 0xc9, 0xf9, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, - 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, - 0x18, 0xa2, 0x34, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0x91, 0x0d, 0xd1, - 0x05, 0x9b, 0x52, 0x01, 0x15, 0x2a, 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, 0x47, 0x8d, 0x31, - 0x20, 0x00, 0x00, 0xff, 0xff, 0x72, 0x36, 0xfe, 0xb4, 0x1b, 0x02, 0x00, 0x00, + 0xea, 0x61, 0xe4, 0x62, 0x05, 0x3b, 0x46, 0xa8, 0x89, 0x91, 0x8b, 0xd9, 0x25, 0x33, 0x45, 0x48, + 0x0f, 0x9f, 0x09, 0x98, 0xde, 0x93, 0xd2, 0x27, 0x5a, 0x3d, 0xc4, 0x9f, 0x4a, 0x52, 0x4d, 0x97, + 0x9f, 0x4c, 0x66, 0x12, 0x11, 0x12, 0xd2, 0x47, 0x0e, 0x5d, 0xfd, 0xea, 0xcc, 0x94, 0x5a, 0x27, + 0xe7, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, + 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x4c, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0x85, 0xea, 0x03, 0x93, 0xba, 0x20, 0xfb, 0xf4, 0x2b, 0xa0, + 0x42, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xd8, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, + 0xff, 0x78, 0x27, 0xa2, 0xa0, 0x0e, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/cheqd/types/query.pb.gw.go b/x/cheqd/types/query.pb.gw.go index 899cb303d..7a537fc1b 100644 --- a/x/cheqd/types/query.pb.gw.go +++ b/x/cheqd/types/query.pb.gw.go @@ -181,7 +181,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_Did_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 0, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"cheqd", "cheqdnode", "did", "id"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_Query_Did_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"cheqd", "v1", "did", "id"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( diff --git a/x/cheqd/types/stateValue.go b/x/cheqd/types/stateValue.go index 0b2560ee3..7fab95501 100644 --- a/x/cheqd/types/stateValue.go +++ b/x/cheqd/types/stateValue.go @@ -4,10 +4,16 @@ import ( "encoding/base64" "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gogo/protobuf/proto" "github.com/tendermint/tendermint/crypto/tmhash" "reflect" ) +// StateValueData is interface uniting possible types to be used for stateValue.data field +type StateValueData interface { + proto.Message +} + var _ types.UnpackInterfacesMessage = &StateValue{} func (m *StateValue) UnpackInterfaces(unpacker types.AnyUnpacker) error { @@ -15,16 +21,16 @@ func (m *StateValue) UnpackInterfaces(unpacker types.AnyUnpacker) error { return unpacker.UnpackAny(m.Data, &data) } -func NewStateValue(data StateValueData, metadata *Metadata) (*StateValue, error) { +func NewStateValue(data StateValueData, metadata *Metadata) (StateValue, error) { any, err := types.NewAnyWithValue(data) if err != nil { - return nil, ErrInvalidDidStateValue.Wrap(err.Error()) + return StateValue{}, err } - return &StateValue{Data: any, Metadata: metadata}, nil + return StateValue{Data: any, Metadata: metadata}, nil } -func NewMetadata(ctx sdk.Context) Metadata { +func NewMetadataFromContext(ctx sdk.Context) Metadata { created := ctx.BlockTime().String() txHash := base64.StdEncoding.EncodeToString(tmhash.Sum(ctx.TxBytes())) @@ -34,7 +40,7 @@ func NewMetadata(ctx sdk.Context) Metadata { func (m StateValue) UnpackData() (StateValueData, error) { value, isOk := m.Data.GetCachedValue().(StateValueData) if !isOk { - return nil, ErrInvalidDidStateValue.Wrap(m.Data.TypeUrl) + return nil, ErrUnpackStateValue.Wrapf("invalid type url: %s", m.Data.TypeUrl) } return value, nil @@ -48,7 +54,7 @@ func (m StateValue) UnpackDataAsDid() (*Did, error) { value, isValue := data.(*Did) if !isValue { - return nil, ErrInvalidDidStateValue.Wrap(reflect.TypeOf(data).String()) + return nil, ErrUnpackStateValue.Wrap(reflect.TypeOf(data).String()) } return value, nil diff --git a/x/cheqd/types/stateValueData.go b/x/cheqd/types/stateValueData.go deleted file mode 100644 index 0697ec122..000000000 --- a/x/cheqd/types/stateValueData.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -import "github.com/gogo/protobuf/proto" - -// StateValueData is interface uniting possible types to be used for stateValue.data field -type StateValueData interface { - proto.Message -} diff --git a/x/cheqd/types/stateValue_test.go b/x/cheqd/types/stateValue_test.go index 689350499..f37b55e1f 100644 --- a/x/cheqd/types/stateValue_test.go +++ b/x/cheqd/types/stateValue_test.go @@ -1,6 +1,7 @@ package types import ( + "github.com/cheqd/cheqd-node/x/cheqd/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/stretchr/testify/assert" @@ -26,7 +27,7 @@ func Test_PackUnpackAny(t *testing.T) { var any types.Any err = any.Unmarshal(bz) assert.NoError(t, err) - assert.Equal(t, any.TypeUrl, MsgTypeURL(&Did{})) + assert.Equal(t, any.TypeUrl, utils.MsgTypeURL(&Did{})) // Unmarshal var decoded StateValueData diff --git a/x/cheqd/types/tx.go b/x/cheqd/types/tx.go new file mode 100644 index 000000000..404b931a2 --- /dev/null +++ b/x/cheqd/types/tx.go @@ -0,0 +1,5 @@ +package types + +type IdentityMsg interface { + GetSignBytes() []byte +} diff --git a/x/cheqd/types/tx_msg_create_did.go b/x/cheqd/types/tx_msg_create_did.go new file mode 100644 index 000000000..d50caab72 --- /dev/null +++ b/x/cheqd/types/tx_msg_create_did.go @@ -0,0 +1,50 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +var _ sdk.Msg = &MsgCreateDid{} + +func NewMsgCreateDid(payload *MsgCreateDidPayload, signatures []*SignInfo) *MsgCreateDid { + return &MsgCreateDid{ + Payload: payload, + Signatures: signatures, + } +} + +func (msg *MsgCreateDid) Route() string { + return RouterKey +} + +func (msg *MsgCreateDid) Type() string { + return "MsgCreateDid" +} + +func (msg *MsgCreateDid) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{} +} + +func (msg *MsgCreateDid) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshal(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgCreateDid) ValidateBasic() error { + err := msg.Validate(nil) + if err != nil { + return ErrBasicValidation.Wrap(err.Error()) + } + + return nil +} + +// Validate + +func (msg MsgCreateDid) Validate(allowedNamespaces []string) error { + return validation.ValidateStruct(&msg, + validation.Field(&msg.Payload, validation.Required, ValidMsgCreateDidPayloadRule(allowedNamespaces)), + validation.Field(&msg.Signatures, IsUniqueSignInfoListByIdRule(), validation.Each(ValidSignInfoRule(allowedNamespaces))), + ) +} diff --git a/x/cheqd/types/tx_msg_create_did_payload.go b/x/cheqd/types/tx_msg_create_did_payload.go new file mode 100644 index 000000000..3c3c6406d --- /dev/null +++ b/x/cheqd/types/tx_msg_create_did_payload.go @@ -0,0 +1,40 @@ +package types + +var _ IdentityMsg = &MsgCreateDidPayload{} + +func (msg *MsgCreateDidPayload) GetSignBytes() []byte { + return ModuleCdc.MustMarshal(msg) +} + +func (msg *MsgCreateDidPayload) ToDid() Did { + return Did{ + Context: msg.Context, + Id: msg.Id, + Controller: msg.Controller, + VerificationMethod: msg.VerificationMethod, + Authentication: msg.Authentication, + AssertionMethod: msg.AssertionMethod, + CapabilityInvocation: msg.CapabilityInvocation, + CapabilityDelegation: msg.CapabilityDelegation, + KeyAgreement: msg.KeyAgreement, + AlsoKnownAs: msg.AlsoKnownAs, + Service: msg.Service, + } +} + +// Validation + +func (msg MsgCreateDidPayload) Validate(allowedNamespaces []string) error { + return msg.ToDid().Validate(allowedNamespaces) +} + +func ValidMsgCreateDidPayloadRule(allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(*MsgCreateDidPayload) + if !ok { + panic("ValidMsgCreateDidPayloadRule must be only applied on MsgCreateDidPayload properties") + } + + return casted.Validate(allowedNamespaces) + }) +} diff --git a/x/cheqd/types/tx_msg_create_did_test.go b/x/cheqd/types/tx_msg_create_did_test.go new file mode 100644 index 000000000..230234cc8 --- /dev/null +++ b/x/cheqd/types/tx_msg_create_did_test.go @@ -0,0 +1,68 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestMsgCreateDidValidation(t *testing.T) { + cases := []struct { + name string + struct_ *MsgCreateDid + isValid bool + errorMsg string + }{ + { + name: "positive", + struct_: &MsgCreateDid{ + Payload: &MsgCreateDidPayload{ + Id: "did:cheqd:testnet:123456789abcdefg", + VerificationMethod: []*VerificationMethod{ + { + Id: "did:cheqd:testnet:123456789abcdefg#key1", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:testnet:123456789abcdefg", + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + Authentication: []string{"did:cheqd:testnet:123456789abcdefg#key1", "did:cheqd:testnet:123456789abcdefg#aaa"}, + }, + Signatures: nil, + }, + isValid: true, + }, + { + name: "negative: relationship duplicates", + struct_: &MsgCreateDid{ + Payload: &MsgCreateDidPayload{ + Id: "did:cheqd:testnet:123456789abcdefg", + VerificationMethod: []*VerificationMethod{ + { + Id: "did:cheqd:testnet:123456789abcdefg#key1", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:testnet:123456789abcdefg", + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + Authentication: []string{"did:cheqd:testnet:123456789abcdefg#key1", "did:cheqd:testnet:123456789abcdefg#key1"}, + }, + Signatures: nil, + }, + isValid: false, + errorMsg: "payload: (authentication: there should be no duplicates.).: basic validation failed", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.ValidateBasic() + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.errorMsg) + } + }) + } +} diff --git a/x/cheqd/types/tx_msg_update_did.go b/x/cheqd/types/tx_msg_update_did.go new file mode 100644 index 000000000..7e21901b6 --- /dev/null +++ b/x/cheqd/types/tx_msg_update_did.go @@ -0,0 +1,50 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +var _ sdk.Msg = &MsgUpdateDid{} + +func NewMsgUpdateDid(payload *MsgUpdateDidPayload, signatures []*SignInfo) *MsgUpdateDid { + return &MsgUpdateDid{ + Payload: payload, + Signatures: signatures, + } +} + +func (msg *MsgUpdateDid) Route() string { + return RouterKey +} + +func (msg *MsgUpdateDid) Type() string { + return "WriteRequest" +} + +func (msg *MsgUpdateDid) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{} +} + +func (msg *MsgUpdateDid) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshal(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgUpdateDid) ValidateBasic() error { + err := msg.Validate(nil) + if err != nil { + return ErrBasicValidation.Wrap(err.Error()) + } + + return nil +} + +// Validate + +func (msg MsgUpdateDid) Validate(allowedNamespaces []string) error { + return validation.ValidateStruct(&msg, + validation.Field(&msg.Payload, validation.Required, ValidMsgUpdateDidPayloadRule(allowedNamespaces)), + validation.Field(&msg.Signatures, IsUniqueSignInfoListRule(), validation.Each(ValidSignInfoRule(allowedNamespaces))), + ) +} diff --git a/x/cheqd/types/tx_msg_update_did_payload.go b/x/cheqd/types/tx_msg_update_did_payload.go new file mode 100644 index 000000000..fd9f2fd6a --- /dev/null +++ b/x/cheqd/types/tx_msg_update_did_payload.go @@ -0,0 +1,49 @@ +package types + +import validation "github.com/go-ozzo/ozzo-validation/v4" + +var _ IdentityMsg = &MsgUpdateDidPayload{} + +func (msg *MsgUpdateDidPayload) GetSignBytes() []byte { + return ModuleCdc.MustMarshal(msg) +} + +func (msg *MsgUpdateDidPayload) ToDid() Did { + return Did{ + Context: msg.Context, + Id: msg.Id, + Controller: msg.Controller, + VerificationMethod: msg.VerificationMethod, + Authentication: msg.Authentication, + AssertionMethod: msg.AssertionMethod, + CapabilityInvocation: msg.CapabilityInvocation, + CapabilityDelegation: msg.CapabilityDelegation, + KeyAgreement: msg.KeyAgreement, + AlsoKnownAs: msg.AlsoKnownAs, + Service: msg.Service, + } +} + +// Validation + +func (msg MsgUpdateDidPayload) Validate(allowedNamespaces []string) error { + err := msg.ToDid().Validate(allowedNamespaces) + if err != nil { + return err + } + + return validation.ValidateStruct(&msg, + validation.Field(&msg.VersionId, validation.Required), + ) +} + +func ValidMsgUpdateDidPayloadRule(allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(*MsgUpdateDidPayload) + if !ok { + panic("ValidMsgUpdateDidPayloadRule must be only applied on MsgUpdateDidPayload properties") + } + + return casted.Validate(allowedNamespaces) + }) +} diff --git a/x/cheqd/types/tx_msg_update_did_test.go b/x/cheqd/types/tx_msg_update_did_test.go new file mode 100644 index 000000000..15df0da52 --- /dev/null +++ b/x/cheqd/types/tx_msg_update_did_test.go @@ -0,0 +1,90 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestMsgUpdateDidValidation(t *testing.T) { + cases := []struct { + name string + struct_ *MsgUpdateDid + isValid bool + errorMsg string + }{ + { + name: "positive", + struct_: &MsgUpdateDid{ + Payload: &MsgUpdateDidPayload{ + Id: "did:cheqd:testnet:123456789abcdefg", + VerificationMethod: []*VerificationMethod{ + { + Id: "did:cheqd:testnet:123456789abcdefg#key1", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:testnet:123456789abcdefg", + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + Authentication: []string{"did:cheqd:testnet:123456789abcdefg#key1", "did:cheqd:testnet:123456789abcdefg#aaa"}, + VersionId: "version1", + }, + Signatures: nil, + }, + isValid: true, + }, + { + name: "negative: relationship duplicates", + struct_: &MsgUpdateDid{ + Payload: &MsgUpdateDidPayload{ + Id: "did:cheqd:testnet:123456789abcdefg", + VerificationMethod: []*VerificationMethod{ + { + Id: "did:cheqd:testnet:123456789abcdefg#key1", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:testnet:123456789abcdefg", + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + Authentication: []string{"did:cheqd:testnet:123456789abcdefg#key1", "did:cheqd:testnet:123456789abcdefg#key1"}, + VersionId: "version1", + }, + Signatures: nil, + }, + isValid: false, + errorMsg: "payload: (authentication: there should be no duplicates.).: basic validation failed", + }, + { + name: "negative: version id is required", + struct_: &MsgUpdateDid{ + Payload: &MsgUpdateDidPayload{ + Id: "did:cheqd:testnet:123456789abcdefg", + VerificationMethod: []*VerificationMethod{ + { + Id: "did:cheqd:testnet:123456789abcdefg#key1", + Type: "Ed25519VerificationKey2020", + Controller: "did:cheqd:testnet:123456789abcdefg", + PublicKeyMultibase: ValidEd25519PubKey, + }, + }, + Authentication: []string{"did:cheqd:testnet:123456789abcdefg#key1", "did:cheqd:testnet:123456789abcdefg#aaa"}, + }, + Signatures: nil, + }, + isValid: false, + errorMsg: "payload: (version_id: cannot be blank.).: basic validation failed", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.ValidateBasic() + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.errorMsg) + } + }) + } +} diff --git a/x/cheqd/types/tx_sign_info.go b/x/cheqd/types/tx_sign_info.go new file mode 100644 index 000000000..cd57eded9 --- /dev/null +++ b/x/cheqd/types/tx_sign_info.go @@ -0,0 +1,113 @@ +package types + +import ( + "errors" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/go-ozzo/ozzo-validation/v4/is" +) + +func NewSignInfo(verificationMethodId string, signature string) *SignInfo { + return &SignInfo{VerificationMethodId: verificationMethodId, Signature: signature} +} + +// Helpers + +func GetSignInfoIds(infos []*SignInfo) []string { + res := make([]string, len(infos)) + + for i := range infos { + res[i] = infos[i].VerificationMethodId + } + + return res +} + +func IsUniqueSignInfoList(infos []*SignInfo) bool { + var tmp_ = map[SignInfo]bool{} + for _, si := range infos { + _, found := tmp_[*si] + if found { + return false + } + tmp_[*si] = true + } + return true +} + +// FindSignInfosBySigner returns the sign infos that corresponds to the provided signer's did +func FindSignInfosBySigner(infos []*SignInfo, signer string) []SignInfo { + var result []SignInfo + + for _, info := range infos { + did, _, _, _ := utils.MustSplitDIDUrl(info.VerificationMethodId) + + if did == signer { + result = append(result, *info) + } + } + + return result +} + +// FindSignInfoBySigner returns the first sign info that corresponds to the provided signer's did +func FindSignInfoBySigner(infos []*SignInfo, signer string) (info SignInfo, found bool) { + infos_ := FindSignInfosBySigner(infos, signer) + + if len(infos_) == 0 { + return SignInfo{}, false + } + + return infos_[0], true +} + +// Validate + +func (si SignInfo) Validate(allowedNamespaces []string) error { + return validation.ValidateStruct(&si, + validation.Field(&si.VerificationMethodId, validation.Required, IsDIDUrl(allowedNamespaces, Empty, Empty, Required)), + validation.Field(&si.Signature, validation.Required, is.Base64), + ) +} + +func ValidSignInfoRule(allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(SignInfo) + if !ok { + panic("ValidSignInfoRule must be only applied on sign infos") + } + + return casted.Validate(allowedNamespaces) + }) +} + +func IsUniqueSignInfoListByIdRule() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*SignInfo) + if !ok { + panic("IsUniqueVerificationMethodListByIdRule must be only applied on VM lists") + } + + ids := GetSignInfoIds(casted) + if !utils.IsUnique(ids) { + return errors.New("there are sign info records with the same ID") + } + + return nil + }) +} + +func IsUniqueSignInfoListRule() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*SignInfo) + if !ok { + panic("IsUniqueVerificationMethodListByIdRule must be only applied on VM lists") + } + + if !IsUniqueSignInfoList(casted){ + return errors.New("there are full sign info duplicates") + } + + return nil + }) +} diff --git a/x/cheqd/types/tx_sign_ingo_test.go b/x/cheqd/types/tx_sign_ingo_test.go new file mode 100644 index 000000000..dff012be4 --- /dev/null +++ b/x/cheqd/types/tx_sign_ingo_test.go @@ -0,0 +1,125 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestSignInfoValidation(t *testing.T) { + cases := []struct { + name string + struct_ SignInfo + allowedNamespaces []string + isValid bool + errorMsg string + }{ + { + name: "positive", + struct_: SignInfo{ + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa=", + }, + isValid: true, + errorMsg: "", + }, + { + name: "negative: namespace", + struct_: SignInfo{ + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#service1", + Signature: "DIDCommMessaging", + }, + allowedNamespaces: []string{"mainnet"}, + isValid: false, + errorMsg: "verification_method_id: did namespace must be one of: mainnet.", + }, + { + name: "negative: signature", + struct_: SignInfo{ + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#service1", + Signature: "!@#", + }, + isValid: false, + errorMsg: "signature: must be encoded in Base64.", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.struct_.Validate(tc.allowedNamespaces) + + if tc.isValid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.errorMsg) + } + }) + } +} + +func TestFullSignInfoDublicateValidation(t *testing.T) { + cases := []struct { + name string + structs_ []*SignInfo + isValid bool + }{ + { + name: "positive", + structs_ : []*SignInfo{ + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "bbb="}, + }, + isValid: true, + }, + { + name: "positive with all different pieces", + structs_ : []*SignInfo{ + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + { + VerificationMethodId: "did:cheqd:bbbbbbbbbbbbbbbb#method1", + Signature: "bbb="}, + }, + isValid: true, + }, + { + name: "negative", + structs_ : []*SignInfo{ + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + }, + isValid: false, + }, + { + name: "negative with a lot of same elems", + structs_ : []*SignInfo{ + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + { + VerificationMethodId: "did:cheqd:aaaaaaaaaaaaaaaa#method1", + Signature: "aaa="}, + }, + isValid: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + res_ := IsUniqueSignInfoList(tc.structs_) + require.Equal(t, res_, tc.isValid) + }) + } +} diff --git a/x/cheqd/types/types.go b/x/cheqd/types/types.go deleted file mode 100644 index ab1254f4c..000000000 --- a/x/cheqd/types/types.go +++ /dev/null @@ -1 +0,0 @@ -package types diff --git a/x/cheqd/types/utils_test.go b/x/cheqd/types/utils_test.go deleted file mode 100644 index 4e5191838..000000000 --- a/x/cheqd/types/utils_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package types - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func Test_MsgTypeUrl(t *testing.T) { - assert.Equal(t, "/cheqdid.cheqdnode.cheqd.v1.Did", MsgTypeURL(&Did{})) -} diff --git a/x/cheqd/types/validate.go b/x/cheqd/types/validate.go new file mode 100644 index 000000000..18f04a2b8 --- /dev/null +++ b/x/cheqd/types/validate.go @@ -0,0 +1,180 @@ +package types + +import ( + "errors" + "fmt" + "github.com/cheqd/cheqd-node/x/cheqd/utils" + "github.com/multiformats/go-multibase" + "strings" +) + +// Helper enums + +type ValidationType int + +const ( + Optional ValidationType = iota + Required ValidationType = iota + Empty ValidationType = iota +) + +// Custom error rule + +type CustomErrorRule struct { + fn func(value interface{}) error +} + +func NewCustomErrorRule(fn func(value interface{}) error) *CustomErrorRule { + return &CustomErrorRule{fn: fn} +} + +func (c CustomErrorRule) Validate(value interface{}) error { + return c.fn(value) +} + +// Validation helpers + +func IsDID(allowedNamespaces []string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("IsDID must be only applied on string properties") + } + + return utils.ValidateDID(casted, DidMethod, allowedNamespaces) + }) +} + +func IsDIDUrl(allowedNamespaces []string, pathRule, queryRule, fragmentRule ValidationType) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("IsDIDUrl must be only applied on string properties") + } + + if err := utils.ValidateDIDUrl(casted, DidMethod, allowedNamespaces); err != nil { + return err + } + + _, path, query, fragment, err := utils.TrySplitDIDUrl(casted) + if err != nil { + return err + } + + if pathRule == Required && path == "" { + return errors.New("path is required") + } + + if pathRule == Empty && path != "" { + return errors.New("path must be empty") + } + + if queryRule == Required && query == "" { + return errors.New("query is required") + } + + if queryRule == Empty && query != "" { + return errors.New("query must be empty") + } + + if fragmentRule == Required && fragment == "" { + return errors.New("fragment is required") + } + + if fragmentRule == Empty && fragment != "" { + return errors.New("fragment must be empty") + } + + return nil + }) +} + +func IsURI() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("IsURI must be only applied on string properties") + } + + return utils.ValidateURI(casted) + }) +} + +func IsMultibase() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("IsMultibase must be only applied on string properties") + } + + return utils.ValidateMultibase(casted) + }) +} + +func IsMultibaseEncodedEd25519PubKey() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("IsMultibaseEncodedEd25519PubKey must be only applied on string properties") + } + + _, keyBytes, err := multibase.Decode(casted) + if err != nil { + return err + } + + err = utils.ValidateEd25519PubKey(keyBytes) + if err != nil { + return err + } + + return nil + }) +} + +func IsJWK() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]*KeyValuePair) + if !ok { + panic("IsJWK must be only applied on KeyValuePair array properties") + } + + keyJson, err := PubKeyJWKToJson(casted) + if err != nil { + return err + } + + return utils.ValidateJWK(keyJson) + }) +} + +func HasPrefix(prefix string) *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.(string) + if !ok { + panic("HasPrefix must be only applied on string properties") + } + + if !strings.HasPrefix(casted, prefix) { + return fmt.Errorf("must have prefix: %s", prefix) + } + + return nil + }) +} + + +func IsUniqueStrList() *CustomErrorRule { + return NewCustomErrorRule(func(value interface{}) error { + casted, ok := value.([]string) + if !ok { + panic("IsSet must be only applied on string array properties") + } + + if !utils.IsUnique(casted) { + return errors.New("there should be no duplicates") + } + + return nil + }) +} diff --git a/x/cheqd/utils/crypto.go b/x/cheqd/utils/crypto.go new file mode 100644 index 000000000..ea249d351 --- /dev/null +++ b/x/cheqd/utils/crypto.go @@ -0,0 +1,84 @@ +package utils + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "errors" + "filippo.io/edwards25519" + "fmt" + "github.com/lestrrat-go/jwx/jwk" + "reflect" +) + +func ValidateJWK(jwk_string string) error { + var raw interface{} + err := jwk.ParseRawKey([]byte(jwk_string), &raw) + if err != nil { + return fmt.Errorf("can't parse jwk: %s", err.Error()) + } + + switch key := raw.(type) { + case *rsa.PublicKey: + break + case *ecdsa.PublicKey: + break + case ed25519.PublicKey: + err := ValidateEd25519PubKey(key) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported jwk type: %s. supported types are: rsa/pub, ecdsa/pub, ed25519/pub", reflect.TypeOf(raw).Name()) + } + + return nil +} + +func ValidateEd25519PubKey(keyBytes []byte) error { + if l := len(keyBytes); l != ed25519.PublicKeySize { + return fmt.Errorf("ed25519: bad public key length: %d", l) + } + _, err := (&edwards25519.Point{}).SetBytes(keyBytes) + if err != nil { + return err + } + return nil +} + +func VerifyED25519Signature(pubKey ed25519.PublicKey, message []byte, signature []byte) error { + valid := ed25519.Verify(pubKey, message, signature) + if !valid { + return errors.New("invalid ed25519 signature") + } + + return nil +} + +// VerifyRSASignature uses PSS padding and SHA256 digest +// A good explanation of different paddings: https://security.stackexchange.com/questions/183179/what-is-rsa-oaep-rsa-pss-in-simple-terms +func VerifyRSASignature(pubKey rsa.PublicKey, message []byte, signature []byte) error { + hasher := crypto.SHA256.New() + hasher.Write(message) + digest := hasher.Sum(nil) + + err := rsa.VerifyPSS(&pubKey, crypto.SHA256, digest, signature, nil) + if err != nil { + return err + } + return nil +} + +// VerifyECDSASignature uses ASN1 to decode r and s, SHA265 to calculate message digest +func VerifyECDSASignature(pubKey ecdsa.PublicKey, message []byte, signature []byte) error { + hasher := crypto.SHA256.New() + hasher.Write(message) + digest := hasher.Sum(nil) + + ok := ecdsa.VerifyASN1(&pubKey, digest, signature) + if !ok { + return errors.New("invalid ecdsa signature") + } + return nil +} diff --git a/x/cheqd/utils/crypto_test.go b/x/cheqd/utils/crypto_test.go new file mode 100644 index 000000000..225ac3fca --- /dev/null +++ b/x/cheqd/utils/crypto_test.go @@ -0,0 +1,59 @@ +package utils + +import ( + "github.com/multiformats/go-multibase" + "github.com/stretchr/testify/require" + "testing" +) + +func TestValidateEd25519PubKey(t *testing.T) { + cases := []struct { + name string + key string + valid bool + errorMsg string + }{ + {"Valid: General Ed25519 public key", "zF1hVGXXK9rmx5HhMTpGnGQJiab9qrFJbQXBRhSmYjQWX", true, ""}, + {"Valid: General Ed25519 public key", "zF1hVGXXK9rmx5HhMTpGnGQJiab9qr1111111111111", false, "ed25519: bad public key length: 31"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, keyBytes, _ := multibase.Decode(tc.key) + err := ValidateEd25519PubKey(keyBytes) + + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errorMsg) + } + }) + } +} + +func TestValidateJwk(t *testing.T) { + cases := []struct { + name string + key string + valid bool + errorMsg string + }{ + {"positive ed25519", "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"9Ov80OqMlNrILAUG8DBBlYQ1rUhp7wDomr2I5muzpTc\"}", true, ""}, + {"positive ecdsa", "{\"crv\":\"P-256\",\"kty\":\"EC\",\"x\":\"tcEgxIPyYMiyR2_Vh_YMYG6Grg7axhK2N8JjWta5C0g\",\"y\":\"imiXD9ahVA_MKY066TrNA9r6l35lRrerP6JRey5SryQ\"}", true, ""}, + {"positive rsa", "{\"e\":\"AQAB\",\"kty\":\"RSA\",\"n\":\"skKXRn44WN2DpXDwm4Ip25kIAGRA8y3iXlaoAhPmFiuSDkx97lXcJYrjxX0wSfehgCiSoZOBv6mFzgSVv0_pXQ6zI35xi2dsbexrc87m7Q24q2chpG33ttnVwQkoXrrm0zDzSX32EVxYQyTu9aWp-zxUdAWcrWUarT24RmgjU78v8JmUzkLmwbzsEImnIZ8Hce2ruisAmuAQBVVA4bWwQm_x1KPoQW-TP5_UR3gGugvf0XrQfMJaVpcxcJ9tduMUw6ffZOsqgbvAiZYnrezxSIjnd5lFTFBIEYdGR6ZgjYZoWvQB7U72o_TJoka-zfSODOUbxNBvxvFhA3uhoo3ZKw\"}", true, ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateJWK(tc.key) + + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errorMsg) + } + }) + } +} diff --git a/x/cheqd/utils/did.go b/x/cheqd/utils/did.go index f0a033ffb..c73dce886 100644 --- a/x/cheqd/utils/did.go +++ b/x/cheqd/utils/did.go @@ -1,80 +1,96 @@ package utils import ( + "errors" + "fmt" "regexp" "strings" ) -var DidForbiddenSymbolsRegexp, _ = regexp.Compile(`^[^#?&/\\]+$`) +var SplitDIDRegexp, _ = regexp.Compile(`^did:([^:]+?)(:([^:]+?))?:([^:]+)$`) +var DidNamespaceRegexp, _ = regexp.Compile(`^[a-zA-Z0-9]*$`) + +// TrySplitDID Validates generic format of DID. It doesn't validate method, name and id content. +// Call ValidateDID for further validation. +func TrySplitDID(did string) (method string, namespace string, id string, err error) { + // Example: did:cheqd:testnet:base58str1ng1111 + // match [0] - the whole string + // match [1] - cheqd - method + // match [2] - :testnet + // match [3] - testnet - namespace + // match [4] - base58str1ng1111 - id + matches := SplitDIDRegexp.FindAllStringSubmatch(did, -1) + if len(matches) != 1 { + return "", "", "", errors.New("unable to split did into method, namespace and id") + } -func SplitDidUrlIntoDidAndFragment(didUrl string) (string, string) { - fragments := strings.Split(didUrl, "#") - return fragments[0], fragments[1] + match := matches[0] + return match[1], match[3], match[4], nil } -func IsDidFragment(prefix string, didUrl string) bool { - if !strings.Contains(didUrl, "#") { - return false - } - - if didUrl[0] == '#' { - return true +func MustSplitDID(did string) (method string, namespace string, id string) { + method, namespace, id, err := TrySplitDID(did) + if err != nil { + panic(err.Error()) } - - did, _ := SplitDidUrlIntoDidAndFragment(didUrl) - return IsValidDid(prefix, did) + return } -func IsFullDidFragment(prefix string, didUrl string) bool { - if !strings.Contains(didUrl, "#") { - return false +func JoinDID(method, namespace, id string) string { + res := "did:" + method + + if namespace != "" { + res = res + ":" + namespace } - did, _ := SplitDidUrlIntoDidAndFragment(didUrl) - return IsValidDid(prefix, did) + return res + ":" + id } -func ResolveId(did string, methodId string) string { - result := methodId - - methodDid, methodFragment := SplitDidUrlIntoDidAndFragment(methodId) - if len(methodDid) == 0 { - result = did + "#" + methodFragment +// ValidateDID checks method and allowed namespaces only when the corresponding parameters are specified. +func ValidateDID(did string, method string, allowedNamespaces []string) error { + sMethod, sNamespace, sUniqueId, err := TrySplitDID(did) + if err != nil { + return err } - return result -} + // check method + if method != "" && method != sMethod { + return fmt.Errorf("did method must be: %s", method) + } -func IsNotValidDIDArray(prefix string, array []string) (bool, int) { - for i, did := range array { - if !IsValidDid(prefix, did) { - return true, i - } + // check namespaces + if !DidNamespaceRegexp.MatchString(sNamespace) { + return errors.New("invalid did namespace") } - return false, 0 -} + if len(allowedNamespaces) > 0 && !Contains(allowedNamespaces, sNamespace) { + return fmt.Errorf("did namespace must be one of: %s", strings.Join(allowedNamespaces[:], ", ")) + } -func IsNotValidDIDArrayFragment(prefix string, array []string) (bool, int) { - for i, did := range array { - if !IsDidFragment(prefix, did) { - return true, i - } + // check unique-id + err = ValidateUniqueId(sUniqueId) + if err != nil { + return err } - return false, 0 + return err } -func IsValidDid(prefix string, did string) bool { - if len(did) == 0 { - return false +func ValidateUniqueId(uniqueId string) error { + // Length should be 16 or 32 symbols + if len(uniqueId) != 16 && len(uniqueId) != 32 { + return fmt.Errorf("unique id length should be 16 or 32 symbols") } - if !DidForbiddenSymbolsRegexp.MatchString(did) { - return false + // Base58 check + if err := ValidateBase58(uniqueId); err != nil { + return fmt.Errorf("unique id must be valid base58 string: %s", err) } - // FIXME: Empty namespace must be allowed even if namespace is set in state - // https://github.com/cheqd/cheqd-node/blob/main/architecture/adr-list/adr-002-cheqd-did-method.md#method-specific-identifier - return strings.HasPrefix(did, prefix) + return nil +} + +func IsValidDID(did string, method string, allowedNamespaces []string) bool { + err := ValidateDID(did, method, allowedNamespaces) + return err == nil } diff --git a/x/cheqd/utils/did_doc.go b/x/cheqd/utils/did_doc.go deleted file mode 100644 index abf3e33d8..000000000 --- a/x/cheqd/utils/did_doc.go +++ /dev/null @@ -1,26 +0,0 @@ -package utils - -import "github.com/cheqd/cheqd-node/x/cheqd/utils/strings" - -const ( - PublicKeyJwk = "PublicKeyJwk" - PublicKeyMultibase = "PublicKeyMultibase" -) - -var VerificationMethodType = map[string]string{ - "JsonWebKey2020": PublicKeyJwk, - "Ed25519VerificationKey2020": PublicKeyMultibase, -} - -var ServiceType = []string{ - "LinkedDomains", - "DIDCommMessaging", -} - -func GetVerificationMethodType(vmType string) string { - return VerificationMethodType[vmType] -} - -func IsValidDidServiceType(sType string) bool { - return strings.Contains(ServiceType, sType) -} diff --git a/x/cheqd/utils/did_test.go b/x/cheqd/utils/did_test.go index 76d8bd56a..6719c905e 100644 --- a/x/cheqd/utils/did_test.go +++ b/x/cheqd/utils/did_test.go @@ -7,30 +7,90 @@ import ( func TestIsDid(t *testing.T) { cases := []struct { - valid bool - did string + name string + valid bool + did string + method string + allowedNS []string }{ - {true, "did:cheqd:test:wyywywywyw"}, - {true, "did:cheqd:test:wyywywywyw:sdadasda"}, - {false, "did1:cheqd:test:wyywywywyw:sdadasda"}, - {false, "did:cheqd2:test:wyywywywyw:sdadasda"}, - {false, "did:cheqd:test4:wyywywywyw:sdadasda"}, - {false, ""}, - {false, "did:cheqd"}, - {false, "did:cheqd:test"}, - {false, "did:cheqd:test:dsdasdad#weqweqwew"}, - {false, "did:cheqd:test:sdasdasdasd/qeweqweqwee"}, - {false, "did:cheqd:test:sdasdasdasd?=qeweqweqwee"}, - {false, "did:cheqd:test:sdasdasdasd&qeweqweqwee"}, + {"Valid: Inputs: Method and namespace are set", true, "did:cheqd:testnet:123456789abcdefg", "cheqd", []string{"testnet"}}, + {"Valid: Inputs: Method and namespaces are set", true, "did:cheqd:testnet:123456789abcdefg", "cheqd", []string{"testnet", "mainnet"}}, + {"Valid: Inputs: Method not set", true, "did:cheqd:testnet:123456789abcdefg", "", []string{"testnet"}}, + {"Valid: Inputs: Method and namespaces are empty", true, "did:cheqd:testnet:123456789abcdefg", "", []string{}}, + {"Valid: Namespace is absent in DID", true, "did:cheqd:123456789abcdefg", "", []string{}}, + // Generic method validation + {"Valid: Inputs: Method is not set and passed for NOTcheqd", true, "did:NOTcheqd:123456789abcdefg", "", []string{}}, + {"Valid: Inputs: Method and Namespaces are not set and passed for NOTcheqd", true, "did:NOTcheqd:123456789abcdefg123456789abcdefg", "", []string{}}, + + {"Valid: Inputs: Order of namespaces changed", true, "did:cheqd:testnet:123456789abcdefg", "cheqd", []string{"mainnet", "testnet"}}, + // Wrong splitting (^did:([^:]+?)(:([^:]+?))?:([^:]+)$) + {"Not valid: DID is not started from 'did'", false, "did123:cheqd:::123456789abcdefg", "cheqd", []string{"testnet"}}, + {"Not valid: empty namespace", false, "did:cheqd::123456789abcdefg", "cheqd", []string{"testnet"}}, + {"Not valid: a lot of ':'", false, "did:cheqd:::123456789abcdefg", "cheqd", []string{"testnet"}}, + {"Not valid: several DIDs in one string", false, "did:cheqd:testnet:123456789abcdefg:did:cheqd:testnet:123456789abcdefg", "cheqd", []string{"testnet"}}, + // Wrong method + {"Not valid: method in DID is not the same as from Inputs", false, "did:NOTcheqd:testnet:123456789abcdefg", "cheqd", []string{"mainnet", "testnet"}}, + {"Not valid: method in Inputs is not the same as from DID", false, "did:cheqd:testnet:123456789abcdefg", "NOTcheqd", []string{"mainnet", "testnet"}}, + // Wrong namespace (^[a-zA-Z0-9]*) + {"Not valid: / is not allowed for namespace", false, "did:cheqd:testnet/:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: _ is not allowed for namespace", false, "did:cheqd:testnet_:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: % is not allowed for namespace", false, "did:cheqd:testnet%:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: * is not allowed for namespace", false, "did:cheqd:testnet*:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: & is not allowed for namespace", false, "did:cheqd:testnet&:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: @ is not allowed for namespace", false, "did:cheqd:testnet@:123456789abcdefg", "cheqd", []string{}}, + {"Not valid: namespace from Inputs is not the same as from DID", false, "did:cheqd:testnet:123456789abcdefg", "cheqd", []string{"not_testnet"}}, + // Base58 checks (^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]*$) + {"Not valid: O - is not allowed for UniqueID", false, "did:cheqd:testnet:123456789abcdefO", "cheqd", []string{}}, + {"Not valid: I - is not allowed for UniqueID", false, "did:cheqd:testnet:123456789abcdefI", "cheqd", []string{}}, + {"Not valid: l - is not allowed for UniqueID", false, "did:cheqd:testnet:123456789abcdefl", "cheqd", []string{}}, + {"Not valid: 0 - is not allowed for UniqueID", false, "did:cheqd:testnet:123456789abcdef0", "cheqd", []string{}}, + // Length checks (should be exactly 16 or 32) + {"Not valid: UniqueID less then 16 symbols", false, "did:cheqd:testnet:123", "cheqd", []string{}}, + {"Not valid: UniqueID more then 16 symbols but less then 32", false, "did:cheqd:testnet:123456789abcdefgABCDEF", "cheqd", []string{}}, + {"Not valid: UniqueID more then 32 symbols", false, "did:cheqd:testnet:123456789abcdefg123456789abcdefgABCDEF", "cheqd", []string{}}, } for _, tc := range cases { - isDid := IsValidDid("did:cheqd:test:", tc.did) + t.Run(tc.name, func(t *testing.T) { + isDid := IsValidDID(tc.did, tc.method, tc.allowedNS) + + if tc.valid { + require.True(t, isDid) + } else { + require.False(t, isDid) + } + }) + } +} + +func TestSplitJoin(t *testing.T) { + cases := []string{ + "did:cheqd:testnet:123456789abcdefg", + "did:cheqd:testnet:123456789abcdefg", + "did:cheqd:testnet:123456789abcdefg", + "did:cheqd:testnet:123456789abcdefg", + "did:cheqd:123456789abcdefg", + "did:NOTcheqd:123456789abcdefg", + "did:NOTcheqd:123456789abcdefg123456789abcdefg", + "did:cheqd:testnet:123456789abcdefg", + } - if tc.valid { - require.True(t, isDid) - } else { - require.False(t, isDid) - } + for _, tc := range cases { + // Test split/join + t.Run("split/join "+tc, func(t *testing.T) { + method, namespace, id := MustSplitDID(tc) + require.Equal(t, tc, JoinDID(method, namespace, id)) + }) } } + +func TestMustSplitDID(t *testing.T) { + require.Panicsf(t, func() { + MustSplitDID("not did") + }, "must panic") + + method, namespace, id := MustSplitDID("did:cheqd:mainnet:qqqqqqqqqqqqqqqq") + require.Equal(t, "cheqd", method) + require.Equal(t, "mainnet", namespace) + require.Equal(t, "qqqqqqqqqqqqqqqq", id) +} diff --git a/x/cheqd/utils/did_url.go b/x/cheqd/utils/did_url.go new file mode 100644 index 000000000..94835420b --- /dev/null +++ b/x/cheqd/utils/did_url.go @@ -0,0 +1,115 @@ +package utils + +import ( + "errors" + "fmt" + "regexp" +) + +// That for groups: +// Example: did:cheqd:testnet:fafdsffq11213343/path-to-s/ome-external-resource?query#key1??? +// 1 - [^/?#]* - all the symbols except / and ? and # . This is the DID part (did:cheqd:testnet:fafdsffq11213343) +// 2 - [^?#]* - all the symbols except ? and #. it means te section started from /, path-abempty (/path-to-s/ome-external-resource) +// 3 - \?([^#]*) - group for `query` part but with ? symbol (?query) +// 4 - [^#]* - group inside query string, match only exact query (query) +// 5 - #([^#]+[\$]?) - group for fragment, starts with #, includes # (#key1???) +// 6 - [^#]+[\$]? - fragment only (key1???) +// Number of queries is not limited. +var SplitDIDURLRegexp, _ = regexp.Compile(`([^/?#]*)?([^?#]*)(\?([^#]*))?(#([^#]+$))?$`) +var DIDPathAbemptyRegexp, _ = regexp.Compile(`^([/a-zA-Z0-9\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@]*|(%[0-9A-Fa-f]{2})*)*$`) +var DIDQueryRegexp, _ = regexp.Compile(`^([/a-zA-Z0-9\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]*|(%[0-9A-Fa-f]{2})*)*$`) +var DIDFragmentRegexp, _ = regexp.Compile(`^([/a-zA-Z0-9\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]*|(%[0-9A-Fa-f]{2})*)*$`) + +// TrySplitDIDUrl Validates generic format of DIDUrl. It doesn't validate path, query and fragment content. +// Call ValidateDIDUrl for further validation. +func TrySplitDIDUrl(didUrl string) (did string, path string, query string, fragment string, err error) { + matches := SplitDIDURLRegexp.FindAllStringSubmatch(didUrl, -1) + + if len(matches) != 1 { + return "", "", "", "", errors.New("unable to split did url into did, path, query and fragment") + } + + match := matches[0] + + return match[1], match[2], match[4], match[6], nil +} + +func MustSplitDIDUrl(didUrl string) (did string, path string, query string, fragment string) { + did, path, query, fragment, err := TrySplitDIDUrl(didUrl) + if err != nil { + panic(err.Error()) + } + return +} + +func JoinDIDUrl(did string, path string, query string, fragment string) string { + res := did + path + + if query != "" { + res = res + "?" + query + } + + if fragment != "" { + res = res + "#" + fragment + } + + return res +} + +// ValidateDIDUrl checks method and allowed namespaces only when the corresponding parameters are specified. +func ValidateDIDUrl(didUrl string, method string, allowedNamespaces []string) error { + did, path, query, fragment, err := TrySplitDIDUrl(didUrl) + if err != nil { + return err + } + + // Validate DID + err = ValidateDID(did, method, allowedNamespaces) + if err != nil { + return err + } + // Validate path + err = ValidatePath(path) + if err != nil { + return err + } + // Validate query + err = ValidateQuery(query) + if err != nil { + return err + } + // Validate fragment + err = ValidateFragment(fragment) + if err != nil { + return err + } + + return nil +} + +func ValidateFragment(fragment string) error { + if !DIDFragmentRegexp.MatchString(fragment) { + return fmt.Errorf("did url fragmnt must match the following regexp: %s", DIDFragmentRegexp) + } + return nil +} + +func ValidateQuery(query string) error { + if !DIDQueryRegexp.MatchString(query) { + return fmt.Errorf("did url query must match the following regexp: %s", DIDQueryRegexp) + } + return nil +} + +func ValidatePath(path string) error { + if !DIDPathAbemptyRegexp.MatchString(path) { + return fmt.Errorf("did url path abempty must match the following regexp: %s", DIDPathAbemptyRegexp) + } + return nil +} + +func IsValidDIDUrl(didUrl string, method string, allowedNamespaces []string) bool { + err := ValidateDIDUrl(didUrl, method, allowedNamespaces) + + return nil == err +} diff --git a/x/cheqd/utils/did_url_test.go b/x/cheqd/utils/did_url_test.go new file mode 100644 index 000000000..caaecca00 --- /dev/null +++ b/x/cheqd/utils/did_url_test.go @@ -0,0 +1,92 @@ +package utils + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestIsDidURL(t *testing.T) { + cases := []struct { + name string + valid bool + didUrl string + }{ + // Path: all the possible symbols + {"Valid: the whole alphabet for path", true, "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff"}, + {"Valid: several paths", true, "did:cheqd:testnet:123456789abcdefg/path/to/some/other/place/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff/"}, + {"Valid: the whole alphabet with query", true, "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query"}, + {"Valid: the whole alphabet with query and fragment", true, "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query#fragment"}, + {"Valid: each possible symbols as a path", true, "did:cheqd:testnet:123456789abcdefg/12/ab/AB/-/./_/~/!/$/&/'/(/)/*/+/,/;/=/:/@/%20/%ff"}, + {"Valid: empty path", true, "did:cheqd:testnet:123456789abcdefg/"}, + // Query: all the possible variants + {"Valid: the whole alphabet for query", true, "did:cheqd:testnet:123456789abcdefg/path?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?"}, + {"Valid: the whole alphabet for query and another query", true, "did:cheqd:testnet:123456789abcdefg/path?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query=2?query=3/query=%A4"}, + // Fragment: + {"Valid: the whole alphabet for fragment", true, "did:cheqd:testnet:123456789abcdefg/path?query#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff"}, + {"Valid: the whole alphabet with query and apth", true, "did:cheqd:testnet:123456789abcdefg/path?query#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?"}, + {"Valid: only fragment", true, "did:cheqd:testnet:123456789abcdefg#fragment"}, + {"Valid: only query", true, "did:cheqd:testnet:123456789abcdefg?query"}, + // Wrong cases + {"Not valid: wrong HEXDIG for path (pct-encoded phrase)", false, "did:cheqd:testnet:123456789abcdefg/path%20%zz"}, + {"Not valid: wrong HEXDIG for query (pct-encoded phrase)", false, "did:cheqd:testnet:123456789abcdefg/path?query%20%zz"}, + {"Not valid: wrong HEXDIG for fragment (pct-encoded phrase)", false, "did:cheqd:testnet:123456789abcdefg/path?query#fragment%20%zz"}, + // Wrong splitting (^did:([^:]+?)(:([^:]+?))?:([^:]+)$) + {"Not valid: starts with not 'did'", false, "did123:cheqd:::123456789abcdefg/path?query#fragment"}, + {"Not valid: empty namespace", false, "did:cheqd::123456789abcdefg/path?query#fragment"}, + {"Not valid: a lot of ':'", false, "did:cheqd:::123456789abcdefg/path?query#fragment"}, + {"Not valid: two DIDs in one", false, "did:cheqd:testnet:123456789abcdefg:did:cheqd:testnet:123456789abcdefg/path?query#fragment"}, + // Wrong namespace (^[a-zA-Z0-9]*) + {"Not valid: / - is not allowed for namespace", false, "did:cheqd:testnet/:123456789abcdefg/path?query#fragment"}, + {"Not valid: _ - is not allowed for namespace", false, "did:cheqd:testnet_:123456789abcdefg/path?query#fragment"}, + {"Not valid: % - is not allowed for namespace", false, "did:cheqd:testnet%:123456789abcdefg/path?query#fragment"}, + {"Not valid: * - is not allowed for namespace", false, "did:cheqd:testnet*:123456789abcdefg/path?query#fragment"}, + {"Not valid: & - is not allowed for namespace", false, "did:cheqd:testnet&:123456789abcdefg/path?query#fragment"}, + {"Not valid: @ - is not allowed for namespace", false, "did:cheqd:testnet@/:123456789abcdefg/path?query#fragment"}, + // Base58 checks (^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]*$) + {"Not valid: O - is not allowed for base58", false, "did:cheqd:testnet:123456789abcdefO/path?query#fragment"}, + {"Not valid: I - is not allowed for base58", false, "did:cheqd:testnet:123456789abcdefI/path?query#fragment"}, + {"Not valid: l - is not allowed for base58", false, "did:cheqd:testnet:123456789abcdefl/path?query#fragment"}, + {"Not valid: 0 - is not allowed for base58", false, "did:cheqd:testnet:123456789abcdef0/path?query#fragment"}, + // Length checks (should be exactly 16 or 32) + {"Not valid: UniqueID less then 16 symbols", false, "did:cheqd:testnet:123/path?query#fragment"}, + {"Not valid: UniqueID more then 16 symbols but less then 32", false, "did:cheqd:testnet:123456789abcdefgABCDEF/path?query#fragment"}, + {"Not valid: UniqueID more then 32 symbols", false, "did:cheqd:testnet:123456789abcdefg123456789abcdefgABCDEF/path?query#fragment"}, + {"Not valid: Split should return error", false, "qwerty"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + isDid := IsValidDIDUrl(tc.didUrl, "", []string{}) + + if tc.valid { + require.True(t, isDid) + } else { + require.False(t, isDid) + } + }) + } +} + +func TestDidURLJoin(t *testing.T) { + cases := []string{ + "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff", + "did:cheqd:testnet:123456789abcdefg/path/to/some/other/place/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff/", + "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query", + "did:cheqd:testnet:123456789abcdefg/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query#fragment", + "did:cheqd:testnet:123456789abcdefg/12/ab/AB/-/./_/~/!/$/&/'/(/)/*/+/,/;/=/:/@/%20/%ff", + "did:cheqd:testnet:123456789abcdefg/", + "did:cheqd:testnet:123456789abcdefg/path?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?", + "did:cheqd:testnet:123456789abcdefg/path?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?query=2?query=3/query=%A4", + "did:cheqd:testnet:123456789abcdefg/path?query#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff", + "did:cheqd:testnet:123456789abcdefg/path?query#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&'()*+,;=:@%20%ff?", + "did:cheqd:testnet:123456789abcdefg#fragment", + "did:cheqd:testnet:123456789abcdefg?query", + } + + for _, tc := range cases { + t.Run("split/join" + tc, func(t *testing.T) { + did, path, query, fragment := MustSplitDIDUrl(tc) + require.Equal(t, tc, JoinDIDUrl(did, path, query, fragment)) + }) + } +} diff --git a/x/cheqd/utils/encoding.go b/x/cheqd/utils/encoding.go new file mode 100644 index 000000000..10de42937 --- /dev/null +++ b/x/cheqd/utils/encoding.go @@ -0,0 +1,29 @@ +package utils + +import ( + "fmt" + multibase "github.com/multiformats/go-multibase" +) + +func ValidateMultibase(data string) error { + _, _, err := multibase.Decode(data) + return err +} + +func ValidateMultibaseEncoding(data string, expectedEncoding multibase.Encoding) error { + actualEncoding, _, err := multibase.Decode(data) + if err != nil { + return err + } + + if actualEncoding != expectedEncoding { + return fmt.Errorf("invalid actualEncoding. expected: %s actual: %s", + multibase.EncodingToStr[expectedEncoding], multibase.EncodingToStr[actualEncoding]) + } + + return nil +} + +func ValidateBase58(data string) error { + return ValidateMultibaseEncoding(string(multibase.Base58BTC) + data, multibase.Base58BTC) +} diff --git a/x/cheqd/utils/encoding_test.go b/x/cheqd/utils/encoding_test.go new file mode 100644 index 000000000..8a6b7d636 --- /dev/null +++ b/x/cheqd/utils/encoding_test.go @@ -0,0 +1,112 @@ +package utils + +import ( + "encoding/json" + "github.com/multiformats/go-multibase" + "github.com/stretchr/testify/require" + "testing" +) + +type TestJWKKey struct { + Kty string `json:"kty"` + N string `json:"n"` + Use string `json:"use"` + Alg string `json:"alg"` + E string `json:"e"` + Kid string `json:"kid"` +} + +var ValidJWKKey = TestJWKKey{ + Kty: "RSA", + N: "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ", + Use: "sig", + Alg: "RS256", + E: "AQAB", + Kid: "6a8ba5652a7044121d4fedac8f14d14c54e4895b", + } +var NotValidJWKKey = TestJWKKey{ + Kty: "SomeOtherKeyType", + N: "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ", + Use: "sig", + Alg: "RS256", + E: "AQAB", + Kid: "6a8ba5652a7044121d4fedac8f14d14c54e4895b", +} + +var ValidJWKByte, _ = json.Marshal(ValidJWKKey) +var NotValidJWKByte, _ = json.Marshal(NotValidJWKKey) + +func TestValidateMultibase(t *testing.T) { + cases := []struct { + name string + data string + encoding multibase.Encoding + valid bool + }{ + {"Valid: General pmbkey", "zABCDEFG123456789", multibase.Base58BTC, true}, + {"Not Valid: cannot be empty", "", multibase.Base58BTC, false}, + {"Not Valid: without z but base58", "ABCDEFG123456789", multibase.Base58BTC, false}, + {"Not Valid: without z and not base58", "OIl0ABCDEFG123456789", multibase.Base58BTC, false}, + {"Not Valid: with z but not base58", "zOIl0ABCDEFG123456789", multibase.Base58BTC, false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateMultibase(tc.data) + + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestValidateBase58(t *testing.T) { + cases := []struct { + name string + data string + valid bool + }{ + {"Valid: General pmbkey", "ABCDEFG123456789", true}, + {"Not Valid: cannot be empty", "", false}, + {"Not Valid: not base58", "OIl0ABCDEFG123456789", false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateBase58(tc.data) + + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestValidateJWK(t *testing.T) { + cases := []struct { + name string + data string + valid bool + }{ + {"Valid: General jwk", string(ValidJWKByte), true}, + {"Not Valid: Bad jwk", string(NotValidJWKByte), false}, + + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateJWK(tc.data) + + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/cheqd/types/utils.go b/x/cheqd/utils/proto.go similarity index 92% rename from x/cheqd/types/utils.go rename to x/cheqd/utils/proto.go index 80f3a0312..e25f37e8a 100644 --- a/x/cheqd/types/utils.go +++ b/x/cheqd/utils/proto.go @@ -1,4 +1,4 @@ -package types +package utils import "github.com/gogo/protobuf/proto" diff --git a/x/cheqd/utils/proto_test.go b/x/cheqd/utils/proto_test.go new file mode 100644 index 000000000..70aeacfb2 --- /dev/null +++ b/x/cheqd/utils/proto_test.go @@ -0,0 +1,11 @@ +package utils + +import ( + bank_types "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_MsgTypeUrl(t *testing.T) { + assert.Equal(t, "/cosmos.bank.v1beta1.DenomUnit", MsgTypeURL(&bank_types.DenomUnit{})) +} diff --git a/x/cheqd/utils/str.go b/x/cheqd/utils/str.go new file mode 100644 index 000000000..723d539f4 --- /dev/null +++ b/x/cheqd/utils/str.go @@ -0,0 +1,98 @@ +package utils + +import "sort" + +func IndexOf(array []string, searchElement string, fromIndex int) int { + for i, v := range array[fromIndex:] { + if v == searchElement { + return fromIndex + i + } + } + + return -1 +} + +func Contains(vs []string, t string) bool { + return IndexOf(vs, t, 0) >= 0 +} + +func Filter(vs []string, f func(string) bool) []string { + vsf := make([]string, 0) + for _, v := range vs { + if f(v) { + vsf = append(vsf, v) + } + } + return vsf +} + +func Subtract(minuend []string, subtrahend []string) []string { + m := map[string]bool{} + + for _, v := range minuend { + m[v] = true + } + + for _, v := range subtrahend { + delete(m, v) + } + + result := make([]string, 0, len(m)) + + for k := range m { + result = append(result, k) + } + + return result +} + +// Unique returns a copy of the passed array with duplicates removed +func Unique(array []string) []string { + m := map[string]bool{} + + for _, v := range array { + m[v] = true + } + + result := make([]string, 0, len(m)) + + for k := range m { + result = append(result, k) + } + + return result +} + +func IsUnique(list []string) bool { + set := map[string]bool{} + + for _, did := range list { + set[did] = true + } + + return len(list) == len(set) +} + +func ToInterfaces(list []string) []interface{} { + res := make([]interface{}, len(list)) + + for i := range list { + res[i] = list[i] + } + + return res +} + +func ReplaceInSlice(list []string, old, new string) { + for i := range list { + if list[i] == old { + list[i] = new + } + } +} + +func UniqueSorted(ls []string) []string { + tmp_ := Unique(ls) + sort.Strings(tmp_) + return tmp_ +} diff --git a/x/cheqd/utils/str_test.go b/x/cheqd/utils/str_test.go new file mode 100644 index 000000000..80a91eb4f --- /dev/null +++ b/x/cheqd/utils/str_test.go @@ -0,0 +1,122 @@ +package utils + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIndexOf(t *testing.T) { + cases := []struct { + array []string + searchElement string + fromIndex int + expectedResult int + }{ + {[]string{}, "", 0, -1}, + {nil, "", 0, -1}, + {[]string{"1", "2"}, "1", 0, 0}, + {[]string{"1", "2", "3"}, "3", 0, 2}, + {[]string{"1", "2", "3"}, "4", 0, -1}, + {[]string{"4", "1", "6", "2", "3", "4"}, "4", 0, 0}, + {[]string{"4", "1", "6", "2", "3", "4"}, "4", 1, 5}, + {[]string{"4", "1", "6", "2", "3", "4"}, "4", 3, 5}, + } + + for _, tc := range cases { + actual := IndexOf(tc.array, tc.searchElement, tc.fromIndex) + require.Equal(t, tc.expectedResult, actual) + } +} + +func TestContains(t *testing.T) { + cases := []struct { + array []string + searchElement string + expectedResult bool + }{ + {[]string{}, "", false}, + {nil, "", false}, + {[]string{"1", "2"}, "1", true}, + {[]string{"1", "2", "3"}, "2", true}, + {[]string{"1", "2", "3"}, "3", true}, + {[]string{"1", "2", "3"}, "123", false}, + } + + for _, tc := range cases { + actual := Contains(tc.array, tc.searchElement) + require.Equal(t, tc.expectedResult, actual) + } +} + +func TestSubtract(t *testing.T) { + cases := []struct { + first []string + second []string + expected []string + }{ + {[]string{}, []string{}, []string{}}, + {nil, []string{}, []string{}}, + {nil, nil, []string{}}, + {[]string{"1", "2"}, []string{"1", "2"}, []string{}}, + {[]string{"1", "2", "3"}, []string{}, []string{"1", "2", "3"}}, + {[]string{"1", "2", "3"}, nil, []string{"1", "2", "3"}}, + {[]string{"1", "2", "3"}, []string{"4", "5", "6"}, []string{"1", "2", "3"}}, + {[]string{"1", "2", "3"}, []string{"1", "5", "2"}, []string{"3"}}, + {[]string{"4", "1", "6", "2", "3"}, []string{"1", "5", "2"}, []string{"3", "4", "6"}}, + } + + for _, tc := range cases { + actual := Subtract(tc.first, tc.second) + // We can't compare arrays directly cause result of `subtract` is not deterministic + sort.Strings(actual) + require.Equal(t, tc.expected, actual) + } +} + +func TestUnique(t *testing.T) { + cases := []struct { + array []string + expected []string + }{ + {[]string{}, []string{}}, + {nil, []string{}}, + {[]string{"1", "2"}, []string{"1", "2"}}, + {[]string{"1", "3", "2"}, []string{"1", "2", "3"}}, + {[]string{"4", "1", "6", "2", "3", "1", "3", "1"}, []string{"1", "2", "3", "4", "6"}}, + } + + for _, tc := range cases { + actual := Unique(tc.array) + // We can't compare arrays directly cause result of `unique` is not deterministic + sort.Strings(actual) + require.Equal(t, tc.expected, actual) + } +} + +func TestReplaceInList(t *testing.T) { + list := []string{"1", "2", "3", "2"} + ReplaceInSlice(list, "2", "3") + + require.Equal(t, []string{"1", "3", "3", "3"}, list) +} + +func TestUniqueSorted(t *testing.T) { + cases := []struct { + name string + input []string + output []string + }{ + {"General alphabet list", []string{"aa", "bb"}, []string{"aa", "bb"}}, + {"General alphabet reverse list", []string{"bb", "aa"}, []string{"aa", "bb"}}, + {"General number list", []string{"22", "11"}, []string{"11", "22"}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + res := UniqueSorted(tc.input) + require.Equal(t, res, tc.output) + }) + } +} diff --git a/x/cheqd/utils/strings/array.go b/x/cheqd/utils/strings/array.go deleted file mode 100644 index 0b4ffeab3..000000000 --- a/x/cheqd/utils/strings/array.go +++ /dev/null @@ -1,31 +0,0 @@ -package strings - -func IndexOf(vs []string, t string) int { - for i, v := range vs { - if v == t { - return i - } - } - - return -1 -} - -func Contains(vs []string, t string) bool { - return IndexOf(vs, t) >= 0 -} - -func Filter(vs []string, f func(string) bool) []string { - vsf := make([]string, 0) - for _, v := range vs { - if f(v) { - vsf = append(vsf, v) - } - } - return vsf -} - -func Complement(vs []string, ts []string) []string { - return Filter(vs, func(s string) bool { - return !Contains(ts, s) - }) -} diff --git a/x/cheqd/utils/strings/arrays_test.go b/x/cheqd/utils/strings/arrays_test.go deleted file mode 100644 index c8fe87b24..000000000 --- a/x/cheqd/utils/strings/arrays_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package strings - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func TestComplement(t *testing.T) { - cases := []struct { - first []string - second []string - expected []string - }{ - {[]string{}, []string{}, []string{}}, - {nil, []string{}, []string{}}, - {nil, nil, []string{}}, - {[]string{"1", "2"}, []string{"1", "2"}, []string{}}, - {[]string{"1", "2", "3"}, []string{}, []string{"1", "2", "3"}}, - {[]string{"1", "2", "3"}, nil, []string{"1", "2", "3"}}, - {[]string{"1", "2", "3"}, []string{"4", "5", "6"}, []string{"1", "2", "3"}}, - {[]string{"1", "2", "3"}, []string{"1", "5", "2"}, []string{"3"}}, - {[]string{"4", "1", "6", "2", "3"}, []string{"1", "5", "2"}, []string{"4", "6", "3"}}, - } - - for _, tc := range cases { - actual := Complement(tc.first, tc.second) - require.Equal(t, tc.expected, actual) - } -} diff --git a/x/cheqd/utils/uri.go b/x/cheqd/utils/uri.go new file mode 100644 index 000000000..b8f5fcc1c --- /dev/null +++ b/x/cheqd/utils/uri.go @@ -0,0 +1,18 @@ +package utils + +import ( + "fmt" + "regexp" +) + +// Goes from RFC: https://www.rfc-editor.org/rfc/rfc3986#appendix-B +var ValidURIRegexp, _ = regexp.Compile(`^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?`) + +func ValidateURI(uri string) error { + // Match with Regexp from RFC + if !ValidURIRegexp.MatchString(uri) { + return fmt.Errorf("URI: %s does not match regexp: %s", uri, ValidURIRegexp) + } + + return nil +} diff --git a/x/cheqd/utils/uri_test.go b/x/cheqd/utils/uri_test.go new file mode 100644 index 000000000..a06c9033a --- /dev/null +++ b/x/cheqd/utils/uri_test.go @@ -0,0 +1,31 @@ +package utils + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestValidateURI(t *testing.T) { + cases := []struct { + name string + valid bool + URI string + }{ + // Path: all the possible symbols + {"Valid: General http URI path", true, "http://a.com/a/b/c/d/?query=123#fragment=another_part"}, + {"Valid: General https URI path", true, "https://a.com/a/b/c/d/?query=123#fragment=another_part"}, + {"Valid: only alphabet symbols", true, "SomeAnotherPath"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err_ := ValidateURI(tc.URI) + + if tc.valid { + require.NoError(t, err_) + } else { + require.Error(t, err_) + } + }) + } +}