diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 4feb86b..f09c5b4 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -7,7 +7,7 @@ on: jobs: run_e2e: env: - STATE_CONTRACT_ADDRESS_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" + STATE_CONTRACT_ADDRESS_POLYGON_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" runs-on: ubuntu-20.04 steps: @@ -21,7 +21,7 @@ jobs: run: | echo -e "polygon:" > resolvers.settings.yaml echo -e " amoy:" >> resolvers.settings.yaml - echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_AMOY }}" >> resolvers.settings.yaml + echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_POLYGON_AMOY }}" >> resolvers.settings.yaml echo -e " networkURL: ${{ secrets.POLYGON_AMOY_NODE_URL }}" >> resolvers.settings.yaml - name: Docker build driver container diff --git a/.github/workflows/publis-container-dev.yaml b/.github/workflows/publis-container-dev.yaml index fb707fe..ae8ace3 100644 --- a/.github/workflows/publis-container-dev.yaml +++ b/.github/workflows/publis-container-dev.yaml @@ -13,10 +13,10 @@ env: jobs: build_driver: env: - STATE_CONTRACT_ADDRESS_MAIN: "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D" - STATE_CONTRACT_ADDRESS_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" - STATE_CONTRACT_ADDRESS_PRIVADO_MAIN: "0x0DDd8701C91d8d1Ba35c9DbA98A45fe5bA8A877E" - STATE_CONTRACT_ADDRESS_PRIVADO_TEST: "0xE5BfD683F1Ca574B5be881b7DbbcFDCE9DDBAb90" + STATE_CONTRACT_ADDRESS_POLYGON_MAIN: "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D" + STATE_CONTRACT_ADDRESS_POLYGON_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" + STATE_CONTRACT_ADDRESS_PRIVADO_MAIN: "0x975556428F077dB5877Ea2474D783D6C69233742" + STATE_CONTRACT_ADDRESS_PRIVADO_TEST: "0x975556428F077dB5877Ea2474D783D6C69233742" runs-on: ubuntu-latest permissions: id-token: write @@ -47,11 +47,11 @@ jobs: cd driver-did-iden3 echo -e "polygon:" > resolvers.settings.yaml echo -e " amoy:" >> resolvers.settings.yaml - echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_AMOY }}" >> resolvers.settings.yaml + echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_POLYGON_AMOY }}" >> resolvers.settings.yaml echo -e " networkURL: ${{ secrets.POLYGON_AMOY_NODE_URL }}" >> resolvers.settings.yaml echo -e " walletKey: ${{ secrets.SIGNER_PRIVATE_KEY }}" >> resolvers.settings.yaml echo -e " main:" >> resolvers.settings.yaml - echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_MAIN }}" >> resolvers.settings.yaml + echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_POLYGON_MAIN }}" >> resolvers.settings.yaml echo -e " networkURL: ${{ secrets.POLYGON_MAIN_NODE_URL }}" >> resolvers.settings.yaml echo -e " walletKey: ${{ secrets.SIGNER_PRIVATE_KEY }}" >> resolvers.settings.yaml echo -e "privado:" >> resolvers.settings.yaml diff --git a/.github/workflows/push-container.yaml b/.github/workflows/push-container.yaml index 9a29055..3b44599 100644 --- a/.github/workflows/push-container.yaml +++ b/.github/workflows/push-container.yaml @@ -11,8 +11,8 @@ on: jobs: build_driver: env: - STATE_CONTRACT_ADDRESS_MAIN: "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D" - STATE_CONTRACT_ADDRESS_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" + STATE_CONTRACT_ADDRESS_POLYGON_MAIN: "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D" + STATE_CONTRACT_ADDRESS_POLYGON_AMOY: "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" STATE_CONTRACT_ADDRESS_PRIVADO: "0x975556428F077dB5877Ea2474D783D6C69233742" runs-on: ubuntu-latest steps: @@ -33,10 +33,10 @@ jobs: cd driver-did-iden3 echo -e "polygon:" > resolvers.settings.yaml echo -e " amoy:" >> resolvers.settings.yaml - echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_AMOY }}" >> resolvers.settings.yaml + echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_POLYGON_AMOY }}" >> resolvers.settings.yaml echo -e " networkURL: ${{ secrets.POLYGON_AMOY_NODE_URL }}" >> resolvers.settings.yaml echo -e " main:" >> resolvers.settings.yaml - echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_MAIN }}" >> resolvers.settings.yaml + echo -e " contractAddress: ${{ env.STATE_CONTRACT_ADDRESS_POLYGON_MAIN }}" >> resolvers.settings.yaml echo -e " networkURL: ${{ secrets.POLYGON_MAIN_NODE_URL }}" >> resolvers.settings.yaml echo -e "privado:" >> resolvers.settings.yaml echo -e " main:" >> resolvers.settings.yaml diff --git a/.gitignore b/.gitignore index 5815575..bb777aa 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ configs/driver.yaml .env* resolvers.settings.yaml +test.http diff --git a/.golangci.yml b/.golangci.yml index c547f45..96a41b5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,22 +39,16 @@ linters-settings: linters: enable: - bodyclose - - megacheck - revive - govet - unconvert - - megacheck - - structcheck - - gas + - gosec - gocyclo - dupl - misspell - unparam - - varcheck - - deadcode - typecheck - ineffassign - - varcheck - stylecheck - gochecknoinits - exportloopref @@ -66,6 +60,8 @@ linters: - errcheck - gofmt - goimports + - staticcheck + - unused fast: false disable-all: true diff --git a/Dockerfile b/Dockerfile index 80d0165..130d0d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ## ## Build did driver ## -FROM golang:1.18-alpine as base +FROM golang:1.18-alpine AS base WORKDIR /build diff --git a/README.md b/README.md index 726d57d..a336e3e 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,9 @@ Driver for the iden3 DID method ```bash docker run -p 8080:8080 driver-did-iden3:local ``` + + `WALLET_KEY` is only needed for the resolver if it's a trusted resolver that includes signature of EIP712 message when requested in the resolution with `signature=EthereumEip712Signature2021`. + In this case you have to run: + ```bash + docker run -p 8080:8080 -e WALLET_KEY= driver-did-iden3:local + ``` diff --git a/cmd/driver/main.go b/cmd/driver/main.go index e697237..3a7c7eb 100644 --- a/cmd/driver/main.go +++ b/cmd/driver/main.go @@ -10,9 +10,11 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/iden3/driver-did-iden3/pkg/app" "github.com/iden3/driver-did-iden3/pkg/app/configs" + "github.com/iden3/driver-did-iden3/pkg/document" "github.com/iden3/driver-did-iden3/pkg/services" "github.com/iden3/driver-did-iden3/pkg/services/blockchain/eth" "github.com/iden3/driver-did-iden3/pkg/services/ens" + "github.com/iden3/driver-did-iden3/pkg/services/provers" ) func main() { @@ -32,15 +34,21 @@ func main() { log.Fatal("can't create registry:", err) } } + var proverRegistry *services.DIDResolutionProverRegistry + if cfg.WalletKey != "" { + proverRegistry, err = initDIDResolutionProverRegistry(*cfg) + if err != nil { + log.Fatal("can't create registry:", err) + } + } mux := app.Handlers{DidDocumentHandler: &app.DidDocumentHandler{ - DidDocumentService: services.NewDidDocumentServices(initResolvers(), r), - }, + DidDocumentService: services.NewDidDocumentServices(initResolvers(), r, services.WithProvers(proverRegistry))}, } server := http.Server{ Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port), - Handler: mux.Routes(), + Handler: addCORSHeaders(mux.Routes()), ReadHeaderTimeout: time.Second, } log.Printf("HTTP server start on '%s:%d'\n", cfg.Server.Host, cfg.Server.Port) @@ -73,3 +81,30 @@ func initResolvers() *services.ResolverRegistry { return resolvers } + +func initDIDResolutionProverRegistry(cfg configs.Config) (*services.DIDResolutionProverRegistry, error) { + + proverRegistry := services.NewDIDResolutionProverRegistry() + + prover, err := provers.NewEIP712Prover(cfg.WalletKey) + if err != nil { + return nil, err + } + proverRegistry.Add(document.EthereumEip712SignatureProof2021Type, prover) + + return proverRegistry, nil +} + +func addCORSHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/go.mod b/go.mod index 2aabf24..4f10990 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( ) require ( + github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect + github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect github.com/dchest/blake512 v1.0.0 // indirect github.com/deckarep/golang-set/v2 v2.2.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -55,6 +57,8 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect + github.com/tyler-smith/go-bip32 v1.0.0 + github.com/tyler-smith/go-bip39 v1.1.0 github.com/wealdtech/go-multicodec v1.4.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index e0b8dc9..eb756b6 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,8 @@ github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -20,6 +24,7 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= @@ -147,6 +152,7 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -155,7 +161,10 @@ github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+Kd github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE= +github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/wealdtech/go-ens/v3 v3.5.5 h1:/jq3CDItK0AsFnZtiFJK44JthkAMD5YE3WAJOh4i7lc= github.com/wealdtech/go-ens/v3 v3.5.5/go.mod h1:w0EDKIm0dIQnqEKls6ORat/or+AVfPEdEXVfN71EeEE= @@ -166,11 +175,13 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= @@ -220,5 +231,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= diff --git a/pkg/app/configs/driver.go b/pkg/app/configs/driver.go index 42c1479..c0f88d5 100644 --- a/pkg/app/configs/driver.go +++ b/pkg/app/configs/driver.go @@ -20,7 +20,8 @@ type ResolverSettings map[string]map[string]struct { // Config structure represent yaml config for did driver. type Config struct { - Server struct { + WalletKey string `envconfig:"WALLET_KEY" default:""` + Server struct { Port int `envconfig:"PORT" default:"8080"` Host string `envconfig:"HOST" default:"localhost"` } diff --git a/pkg/app/handler.go b/pkg/app/handler.go index cc8b45c..1807b1b 100644 --- a/pkg/app/handler.go +++ b/pkg/app/handler.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + "github.com/iden3/driver-did-iden3/pkg/document" "github.com/iden3/driver-did-iden3/pkg/services" core "github.com/iden3/go-iden3-core/v2" "github.com/iden3/go-merkletree-sql/v2" @@ -23,6 +24,7 @@ func (d *DidDocumentHandler) Get(w http.ResponseWriter, r *http.Request) { opts, err := getResolverOpts( r.URL.Query().Get("state"), r.URL.Query().Get("gist"), + r.URL.Query().Get("signature"), ) if err != nil { log.Println("invalid options query:", err) @@ -96,7 +98,7 @@ func (d *DidDocumentHandler) GetGist(w http.ResponseWriter, r *http.Request) { gistInfo, err := d.DidDocumentService.GetGist(r.Context(), chain, networkid, nil) if errors.Is(err, services.ErrNetworkIsNotSupported) { w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, `{"error":"resolver for '%s:%s' network not found"}`, chain, networkid) + log.Printf(`{"error":"resolver for '%s:%s' network not found"}`, chain, networkid) return } else if err != nil { log.Printf("failed get info about latest gist from network '%s:%s': %v\n", chain, networkid, err) @@ -110,7 +112,7 @@ func (d *DidDocumentHandler) GetGist(w http.ResponseWriter, r *http.Request) { } } -func getResolverOpts(state, gistRoot string) (ro services.ResolverOpts, err error) { +func getResolverOpts(state, gistRoot, signature string) (ro services.ResolverOpts, err error) { if state != "" && gistRoot != "" { return ro, errors.New("'state' and 'gist root' cannot be used together") } @@ -128,5 +130,11 @@ func getResolverOpts(state, gistRoot string) (ro services.ResolverOpts, err erro } ro.GistRoot = g.BigInt() } + if signature != "" && signature != string(document.EthereumEip712SignatureProof2021Type) { + return ro, fmt.Errorf("not supported signature type %s", signature) + } + if signature != "" { + ro.Signature = signature + } return } diff --git a/pkg/document/did.go b/pkg/document/did.go index 715e305..c4154c7 100644 --- a/pkg/document/did.go +++ b/pkg/document/did.go @@ -15,6 +15,7 @@ const ( ErrUnknownNetwork ErrorCode = "unknownNetwork" StateType = "Iden3StateInfo2023" + Iden3ResolutionMetadataType = "Iden3ResolutionMetadata" EcdsaSecp256k1RecoveryMethod2020Type = "EcdsaSecp256k1RecoveryMethod2020" ) @@ -24,6 +25,8 @@ const ( iden3Context = "https://schema.iden3.io/core/jsonld/auth.jsonld" EcdsaSecp256k1RecoveryContext = "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020/lds-ecdsa-secp256k1-recovery2020-2.0.jsonld" defaultContentType = "application/did+ld+json" + iden3ResolutionContext = "https://schema.iden3.io/core/jsonld/resolution.jsonld" + eip712sigContext = "https://w3id.org/security/suites/eip712sig-2021/v1" ) // DidResolution representation of did resolution. @@ -45,6 +48,8 @@ func NewDidResolution() *DidResolution { VerificationMethod: []verifiable.CommonVerificationMethod{}, }, DidResolutionMetadata: &DidResolutionMetadata{ + Context: []string{iden3ResolutionContext}, + Type: Iden3ResolutionMetadataType, ContentType: defaultContentType, Retrieved: time.Now(), }, @@ -52,6 +57,10 @@ func NewDidResolution() *DidResolution { } } +func DidResolutionMetadataSigContext() []string { + return []string{iden3ResolutionContext, eip712sigContext} +} + func NewDidMethodNotSupportedResolution(msg string) *DidResolution { return NewDidErrorResolution(ErrMethodNotSupported, msg) } @@ -81,10 +90,13 @@ func NewDidErrorResolution(errCode ErrorCode, errMsg string) *DidResolution { // DidResolutionMetadata representation of resolution metadata. type DidResolutionMetadata struct { - Error ErrorCode `json:"error,omitempty"` - Message string `json:"message,omitempty"` - ContentType string `json:"contentType,omitempty"` - Retrieved time.Time `json:"retrieved,omitempty"` + Context []string `json:"@context,omitempty"` + Error ErrorCode `json:"error,omitempty"` + Message string `json:"message,omitempty"` + ContentType string `json:"contentType,omitempty"` + Retrieved time.Time `json:"retrieved,omitempty"` + Type string `json:"type,omitempty"` + Proof DidResolutionProofs `json:"proof,omitempty"` } // DidDocumentMetadata metadata of did document. diff --git a/pkg/document/proof.go b/pkg/document/proof.go new file mode 100644 index 0000000..3a28daf --- /dev/null +++ b/pkg/document/proof.go @@ -0,0 +1,31 @@ +package document + +import ( + "time" + + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/iden3/go-schema-processor/v2/verifiable" +) + +type DidResolutionProof interface { + ProofType() verifiable.ProofType +} + +type DidResolutionProofs []DidResolutionProof + +type EthereumEip712SignatureProof2021 struct { + Type verifiable.ProofType `json:"type"` + ProofPurpose string `json:"proofPurpose"` + ProofValue string `json:"proofValue"` + VerificationMethod string `json:"verificationMethod"` + Created time.Time `json:"created"` + Eip712 apitypes.TypedData `json:"eip712"` +} + +// EthereumEip712SignatureProof2021Type is a proof type for EIP172 signature proofs +// nolint:stylecheck // we need to keep the name as it is +const EthereumEip712SignatureProof2021Type verifiable.ProofType = "EthereumEip712Signature2021" + +func (p *EthereumEip712SignatureProof2021) ProofType() verifiable.ProofType { + return p.Type +} diff --git a/pkg/document/proof_test.go b/pkg/document/proof_test.go new file mode 100644 index 0000000..ef73a4d --- /dev/null +++ b/pkg/document/proof_test.go @@ -0,0 +1,123 @@ +package document + +import ( + "encoding/json" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/stretchr/testify/require" +) + +func TestEthereumEip712SignatureProof2021_JSONUnmarshal(t *testing.T) { + in := `{ + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xd5e5ffe290a258116a0f7acb4c9a5bbfdd842516061c6a794892b6db05fbd14706de7e189d965bead2ffb23e30d2f6b02ecf764e6fe24be788721049b7e331481c", + "verificationMethod": "did:pkh:eip155:1:0x5b18eF56aA61eeAE0E3434e3c3d8AEB19b141fe7#blockchainAccountId", + "created": "2021-09-23T20:21:34Z", + "eip712": { + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "IdentityState": [ + { "name": "from", "type": "address" }, + { "name": "timestamp", "type": "uint256" }, + { "name": "state", "type": "uint256" }, + { "name": "stateCreatedAtTimestamp", "type": "uint256" }, + { "name": "stateReplacedByState", "type": "uint256" }, + { "name": "stateReplacedAtTimestamp", "type": "uint256" }, + { "name": "gistRoot", "type": "uint256" }, + { "name": "gistRootCreatedAtTimestamp", "type": "uint256" }, + { "name": "gistRootReplacedByRoot", "type": "uint256" }, + { "name": "gistRootReplacedAtTimestamp", "type": "uint256" }, + { "name": "identity", "type": "uint256" } + ] + }, + "primaryType": "IdentityState", + "domain": { + "name": "StateInfo", + "version": "1", + "chainId": "0x1", + "verifyingContract": "0x0000000000000000000000000000000000000000" + }, + "message": { + "from": "0x5b18eF56aA61eeAE0E3434e3c3d8AEB19b141fe7", + "timestamp": "0", + "state": "444", + "stateCreatedAtTimestamp": "0", + "stateReplacedByState": "0", + "stateReplacedAtTimestamp": "0", + "gistRoot": "555", + "gistRootCreatedAtTimestamp": "0", + "gistRootReplacedByRoot": "0", + "gistRootReplacedAtTimestamp": "0", + "identity": "19090607534999372304474213543962416547920895595808567155882840509226423042" + } + } + }` + var proof EthereumEip712SignatureProof2021 + err := json.Unmarshal([]byte(in), &proof) + require.NoError(t, err) + + timeParsed, _ := time.Parse("2006-01-02T15:04:05Z", "2021-09-23T20:21:34Z") + + var apiTypes = apitypes.Types{ + "IdentityState": []apitypes.Type{ + {Name: "from", Type: "address"}, + {Name: "timestamp", Type: "uint256"}, + {Name: "state", Type: "uint256"}, + {Name: "stateCreatedAtTimestamp", Type: "uint256"}, + {Name: "stateReplacedByState", Type: "uint256"}, + {Name: "stateReplacedAtTimestamp", Type: "uint256"}, + {Name: "gistRoot", Type: "uint256"}, + {Name: "gistRootCreatedAtTimestamp", Type: "uint256"}, + {Name: "gistRootReplacedByRoot", Type: "uint256"}, + {Name: "gistRootReplacedAtTimestamp", Type: "uint256"}, + {Name: "identity", Type: "uint256"}, + }, + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + } + + wantProof := EthereumEip712SignatureProof2021{ + Type: "EthereumEip712Signature2021", + ProofPurpose: "assertionMethod", + ProofValue: "0xd5e5ffe290a258116a0f7acb4c9a5bbfdd842516061c6a794892b6db05fbd14706de7e189d965bead2ffb23e30d2f6b02ecf764e6fe24be788721049b7e331481c", + VerificationMethod: "did:pkh:eip155:1:0x5b18eF56aA61eeAE0E3434e3c3d8AEB19b141fe7#blockchainAccountId", + Created: timeParsed, + Eip712: apitypes.TypedData{ + Types: apiTypes, + PrimaryType: "IdentityState", + Domain: apitypes.TypedDataDomain{ + Name: "StateInfo", + Version: "1", + ChainId: math.NewHexOrDecimal256(int64(1)), + VerifyingContract: "0x0000000000000000000000000000000000000000", + }, + Message: apitypes.TypedDataMessage{ + "from": "0x5b18eF56aA61eeAE0E3434e3c3d8AEB19b141fe7", + "timestamp": "0", + "state": "444", + "stateCreatedAtTimestamp": "0", + "stateReplacedByState": "0", + "stateReplacedAtTimestamp": "0", + "gistRoot": "555", + "gistRootCreatedAtTimestamp": "0", + "gistRootReplacedByRoot": "0", + "gistRootReplacedAtTimestamp": "0", + "identity": "19090607534999372304474213543962416547920895595808567155882840509226423042", + }, + }, + } + require.Equal(t, wantProof, proof) +} diff --git a/pkg/services/blockchain/eth/resolver.go b/pkg/services/blockchain/eth/resolver.go index 6210c3c..f530d51 100644 --- a/pkg/services/blockchain/eth/resolver.go +++ b/pkg/services/blockchain/eth/resolver.go @@ -32,6 +32,8 @@ type Resolver struct { chainID int } +type ResolverOption func(*Resolver) + var ( gistNotFoundException = "execution reverted: Root does not exist" identityNotFoundException = "execution reverted: Identity does not exist" @@ -53,6 +55,7 @@ func NewResolver(url, address string) (*Resolver, error) { state: sc, contractAddress: address, } + chainID, err := c.NetworkID(context.Background()) if err != nil { return nil, err @@ -125,6 +128,10 @@ func (r *Resolver) Resolve( stateInfo, gistInfo, err = r.resolveLatest(ctx, userID) } + if err != nil && !errors.Is(err, services.ErrNotFound) { + return services.IdentityState{}, err + } + identityState := services.IdentityState{} if stateInfo != nil { identityState.StateInfo = &services.StateInfo{ diff --git a/pkg/services/blockchain/eth/resolver_test.go b/pkg/services/blockchain/eth/resolver_test.go index 89fa004..d497031 100644 --- a/pkg/services/blockchain/eth/resolver_test.go +++ b/pkg/services/blockchain/eth/resolver_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -var userDID, _ = w3c.ParseDID("did:polygonid:polygon:mumbai:2qJEaVmT5jBrtgBQ4m7b7bRYzWmvMyDjBZGP24QwvD") +var userDID, _ = w3c.ParseDID("did:polygonid:polygon:amoy:2qY71pSkdCsRetTHbUA4YqG7Hx63Ej2PeiJMzAdJ2V") func TestResolveGist_Success(t *testing.T) { tests := []struct { diff --git a/pkg/services/did.go b/pkg/services/did.go index d1d2e7f..87c64d7 100644 --- a/pkg/services/did.go +++ b/pkg/services/did.go @@ -22,15 +22,30 @@ const ( type DidDocumentServices struct { resolvers *ResolverRegistry ens *ens.Registry + provers *DIDResolutionProverRegistry } type ResolverOpts struct { - State *big.Int - GistRoot *big.Int + State *big.Int + GistRoot *big.Int + Signature string } -func NewDidDocumentServices(resolvers *ResolverRegistry, registry *ens.Registry) *DidDocumentServices { - return &DidDocumentServices{resolvers, registry} +type DidDocumentOption func(*DidDocumentServices) + +func WithProvers(provers *DIDResolutionProverRegistry) DidDocumentOption { + return func(d *DidDocumentServices) { + d.provers = provers + } +} + +func NewDidDocumentServices(resolvers *ResolverRegistry, registry *ens.Registry, opts ...DidDocumentOption) *DidDocumentServices { + didDocumentService := &DidDocumentServices{resolvers, registry, nil} + + for _, opt := range opts { + opt(didDocumentService) + } + return didDocumentService } // GetDidDocument return did document by identifier. @@ -78,6 +93,8 @@ func (d *DidDocumentServices) GetDidDocument(ctx context.Context, did string, op if !gen { return document.NewDidNotFoundResolution(err.Error()), nil } + } else if err != nil && opts.State != nil { + return document.NewDidNotFoundResolution(err.Error()), nil } info, err := identityState.StateInfo.ToDidRepresentation() @@ -128,6 +145,40 @@ func (d *DidDocumentServices) GetDidDocument(ctx context.Context, did string, op }, ) + if opts.Signature != "" { + if d.provers == nil { + return nil, errors.New("provers are not initialized") + } + prover, err := d.provers.GetDIDResolutionProverByProofType(verifiable.ProofType(opts.Signature)) + if err != nil { + return nil, err + } + stateType := IdentityStateType + if opts.GistRoot != nil { + stateType = GlobalStateType + } + + if opts.State != nil && identityState.StateInfo == nil { // this case is genesis state + // fill state info for genesis state to be able to prove it + identityState.StateInfo = &StateInfo{ + ID: *userDID, + State: opts.State, + ReplacedByState: big.NewInt(0), + CreatedAtTimestamp: big.NewInt(0), + ReplacedAtTimestamp: big.NewInt(0), + CreatedAtBlock: big.NewInt(0), + ReplacedAtBlock: big.NewInt(0), + } + } + + didResolutionProof, err := prover.Prove(*userDID, identityState, stateType) + if err != nil { + return nil, err + } + + didResolution.DidResolutionMetadata.Context = document.DidResolutionMetadataSigContext() + didResolution.DidResolutionMetadata.Proof = append(didResolution.DidResolutionMetadata.Proof, didResolutionProof) + } return didResolution, nil } diff --git a/pkg/services/provers.go b/pkg/services/provers.go new file mode 100644 index 0000000..d4440de --- /dev/null +++ b/pkg/services/provers.go @@ -0,0 +1,46 @@ +package services + +import ( + "github.com/iden3/driver-did-iden3/pkg/document" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/iden3/go-schema-processor/v2/verifiable" +) + +type StateType int32 + +const ( + IdentityStateType StateType = 0 + GlobalStateType StateType = 1 +) + +type DIDResolutionProver interface { + Prove(did w3c.DID, info IdentityState, dataType StateType) (document.DidResolutionProof, error) +} + +type DIDResolutionProverRegistry map[verifiable.ProofType]DIDResolutionProver + +func NewDIDResolutionProverRegistry() *DIDResolutionProverRegistry { + return &DIDResolutionProverRegistry{} +} + +func (ch *DIDResolutionProverRegistry) Add(proofType verifiable.ProofType, prover DIDResolutionProver) { + (*ch)[proofType] = prover +} + +func (ch *DIDResolutionProverRegistry) Append(proofType verifiable.ProofType, prover DIDResolutionProver) error { + _, ok := (*ch)[proofType] + if ok { + return ErrResolverAlreadyExists + } + (*ch)[proofType] = prover + return nil +} + +func (ch *DIDResolutionProverRegistry) GetDIDResolutionProverByProofType(proofType verifiable.ProofType) (DIDResolutionProver, error) { + signer, ok := (*ch)[proofType] + if !ok { + return nil, ErrProofTypeIsNotSupported + } + + return signer, nil +} diff --git a/pkg/services/provers/EthereumEip712Signature2021.go b/pkg/services/provers/EthereumEip712Signature2021.go new file mode 100644 index 0000000..dd445ce --- /dev/null +++ b/pkg/services/provers/EthereumEip712Signature2021.go @@ -0,0 +1,162 @@ +package provers + +import ( + "encoding/hex" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/iden3/driver-did-iden3/pkg/document" + "github.com/iden3/driver-did-iden3/pkg/services" + "github.com/iden3/driver-did-iden3/pkg/services/signers" + "github.com/iden3/driver-did-iden3/pkg/services/utils" + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-iden3-core/v2/w3c" + "github.com/pkg/errors" +) + +type EIP712Prover struct { + signer *signers.Secp256k1Signer + address common.Address +} + +func NewEIP712Prover(walletKey string) (*EIP712Prover, error) { + + s, err := signers.NewSecp256k1Signer(walletKey) // eip712 provers uses ecdsa signer + if err != nil { + return nil, err + } + addr, err := utils.PrivateKeyToAddress(walletKey) + if err != nil { + return nil, err + } + globalStateSigner := &EIP712Prover{ + signer: s, + address: addr, + } + + return globalStateSigner, nil +} + +func (p *EIP712Prover) Prove(did w3c.DID, state services.IdentityState, stateType services.StateType) (document.DidResolutionProof, error) { + + typedData, err := p.getTypedData(did, state, stateType) + if err != nil { + return nil, err + } + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + return nil, errors.New("error hashing EIP712Domain for signing") + } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + return nil, errors.New("error hashing PrimaryType message for signing") + } + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) + dataHash := crypto.Keccak256(rawData) + + signature, err := p.signer.Sign(dataHash) + if err != nil { + return nil, err + } + + messageSignature := "0x" + hex.EncodeToString(signature) + + eip712Proof := &document.EthereumEip712SignatureProof2021{ + Type: document.EthereumEip712SignatureProof2021Type, + ProofPurpose: "assertionMethod", + ProofValue: messageSignature, + VerificationMethod: fmt.Sprintf("did:pkh:eip155:0:%s#blockchainAccountId", p.address), + Eip712: typedData, + Created: time.Now(), + } + + return eip712Proof, nil +} + +func (p *EIP712Prover) getTypedData(did w3c.DID, identityState services.IdentityState, stateType services.StateType) (apitypes.TypedData, error) { + id, err := core.IDFromDID(did) + if err != nil { + return apitypes.TypedData{}, + fmt.Errorf("invalid did format for did '%s': %v", did, err) + } + + timestamp := utils.TimeStamp() + + var apiTypes apitypes.Types + var message apitypes.TypedDataMessage + var primaryType string + + switch stateType { + case services.IdentityStateType: + primaryType = "IdentityState" + apiTypes = apitypes.Types{ + "IdentityState": []apitypes.Type{ + {Name: "timestamp", Type: "uint256"}, + {Name: "id", Type: "uint256"}, + {Name: "state", Type: "uint256"}, + {Name: "replacedAtTimestamp", Type: "uint256"}, + }, + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + } + ID := id.BigInt().String() + state := identityState.StateInfo.State.String() + replacedAtTimestamp := identityState.StateInfo.ReplacedAtTimestamp.String() + message = apitypes.TypedDataMessage{ + "timestamp": timestamp, + "id": ID, + "state": state, + "replacedAtTimestamp": replacedAtTimestamp, + } + + case services.GlobalStateType: + primaryType = "GlobalState" + apiTypes = apitypes.Types{ + "GlobalState": []apitypes.Type{ + {Name: "timestamp", Type: "uint256"}, + {Name: "idType", Type: "bytes2"}, + {Name: "root", Type: "uint256"}, + {Name: "replacedAtTimestamp", Type: "uint256"}, + }, + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + } + idType := fmt.Sprintf("0x%X", id.Type()) + root := identityState.GistInfo.Root.String() + replacedAtTimestamp := identityState.GistInfo.ReplacedAtTimestamp.String() + message = apitypes.TypedDataMessage{ + "timestamp": timestamp, + "idType": idType, + "root": root, + "replacedAtTimestamp": replacedAtTimestamp, + } + default: + return apitypes.TypedData{}, fmt.Errorf("type of state info %d is not supportede", stateType) + } + + typedData := apitypes.TypedData{ + Types: apiTypes, + PrimaryType: primaryType, + Domain: apitypes.TypedDataDomain{ + Name: "StateInfo", + Version: "1", + ChainId: math.NewHexOrDecimal256(int64(0)), + VerifyingContract: common.Address{}.String(), + }, + Message: message, + } + + return typedData, nil +} diff --git a/pkg/services/registry.go b/pkg/services/registry.go index 86dc1d0..80eed33 100644 --- a/pkg/services/registry.go +++ b/pkg/services/registry.go @@ -12,7 +12,9 @@ import ( ) var ( - ErrNetworkIsNotSupported = errors.New("network is not supported") + ErrNetworkIsNotSupported = errors.New("network is not supported") + ErrProofTypeIsNotSupported = errors.New("proof is not supported") + ErrResolverAlreadyExists = errors.New("resolver already exists") ErrNotFound = errors.New("not found") diff --git a/pkg/services/signers/secp256k1.go b/pkg/services/signers/secp256k1.go new file mode 100644 index 0000000..ebf6ef8 --- /dev/null +++ b/pkg/services/signers/secp256k1.go @@ -0,0 +1,38 @@ +package signers + +import ( + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + secp256k1VValue = 27 +) + +type Secp256k1Signer struct { + walletKey string +} + +func NewSecp256k1Signer(walletKey string) (*Secp256k1Signer, error) { + globalStateSigner := &Secp256k1Signer{ + walletKey: walletKey, + } + + return globalStateSigner, nil +} + +func (s *Secp256k1Signer) Sign(payload []byte) ([]byte, error) { + privateKey, err := crypto.HexToECDSA(s.walletKey) + if err != nil { + return nil, err + } + + signature, err := crypto.Sign(payload, privateKey) + if err != nil { + return nil, err + } + + if signature[64] < secp256k1VValue { // Invalid Ethereum signature (V is not 27 or 28) + signature[64] += secp256k1VValue // Transform yellow paper V from 0/1 to 27/28 + } + return signature, nil +} diff --git a/pkg/services/utils/convert.go b/pkg/services/utils/convert.go new file mode 100644 index 0000000..275f21e --- /dev/null +++ b/pkg/services/utils/convert.go @@ -0,0 +1,30 @@ +package utils + +import ( + "crypto/ecdsa" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" +) + +func PrivateKeyToAddress(key string) (common.Address, error) { + privateKey, err := crypto.HexToECDSA(key) + if err != nil { + return common.Address{}, err + } + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return common.Address{}, errors.New("error casting public key to ECDSA") + } + return crypto.PubkeyToAddress(*publicKeyECDSA), nil +} + +func TimeStamp() string { + timestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10) + return timestamp +}