From 59ba6d7e557eaa219e7cf74b38af9c9de6171778 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Wed, 17 Sep 2025 18:35:44 +0200 Subject: [PATCH 1/7] feat: add certificate server example - Implemented configuration loading and validation in config package. - Added constants for configuration and certificate validation. - Created domain logic for certificate validation and signing. - Developed example setup for local and remote storage management. - Established server structure with HTTP routing for certificate signing requests. - Integrated service layer for handling certificate operations. - Implemented HTTP transport for processing certificate signing requests. --- .../cmd/server/main.go | 37 ++ .../config.example.yaml | 18 + .../certifier_server_example/go.mod | 66 ++++ .../certifier_server_example/go.sum | 326 ++++++++++++++++++ .../internal/config/config.go | 100 ++++++ .../internal/constants/constants.go | 15 + .../internal/domain/certificate.go | 71 ++++ .../internal/example_setup/create_storage.go | 93 +++++ .../internal/example_setup/setup.go | 134 +++++++ .../internal/server/server.go | 97 ++++++ .../internal/service/certificate_service.go | 74 ++++ .../internal/transport/http/certificate.go | 85 +++++ 12 files changed, 1116 insertions(+) create mode 100644 examples/complex_wallet_examples/certifier_server_example/cmd/server/main.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/config.example.yaml create mode 100644 examples/complex_wallet_examples/certifier_server_example/go.mod create mode 100644 examples/complex_wallet_examples/certifier_server_example/go.sum create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/config/config.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/domain/certificate.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/example_setup/create_storage.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/server/server.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go create mode 100644 examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go diff --git a/examples/complex_wallet_examples/certifier_server_example/cmd/server/main.go b/examples/complex_wallet_examples/certifier_server_example/cmd/server/main.go new file mode 100644 index 00000000..4c48ffe3 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/cmd/server/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/bsv-blockchain/certifier-server-example/internal/config" + "github.com/bsv-blockchain/certifier-server-example/internal/server" +) + +func main() { + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + + cfg, err := config.LoadConfig("", logger) + if err != nil { + logger.Error("Failed to load config", "error", err) + os.Exit(1) + } + + if err := cfg.Validate(); err != nil { + logger.Error("Invalid configuration", "error", err) + os.Exit(1) + } + + srv, err := server.New(cfg, logger) + if err != nil { + logger.Error("Failed to create server", "error", err) + os.Exit(1) + } + + if err := srv.Start(); err != nil { + logger.Error("Server failed", "error", err) + os.Exit(1) + } +} diff --git a/examples/complex_wallet_examples/certifier_server_example/config.example.yaml b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml new file mode 100644 index 00000000..9b50c895 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml @@ -0,0 +1,18 @@ +server: + port: "3000" + network: test + +# certifier_storage: +# path: "" + +certifier_wallet: + identity_key: 03a6f9ed671bba9f41d3a2b93448700ad14b5eb0eee86a565f86b77fea8119d34e + private_key: 7180af43d6ca7ee37ec460505d6512d1f58a3fd926b0110fa163d0aea1615c47 + +user_wallet: + identity_key: 02764e1a4165d387ed9914c7b8e4607e55cda2ff3453efb4c2b18843b986bd3b62 + private_key: f58bf314315ca34c85e92ca263f72cabd32b6d355b43481215530ca1d0671693 + +storage: + url: http://localhost:8100 + private_key: 2b32d442b25d6e7447a1f9ca41a2a15de5004498dc4ffc43b7b009a96724c30d diff --git a/examples/complex_wallet_examples/certifier_server_example/go.mod b/examples/complex_wallet_examples/certifier_server_example/go.mod new file mode 100644 index 00000000..09d0e539 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/go.mod @@ -0,0 +1,66 @@ +module github.com/bsv-blockchain/certifier-server-example + +go 1.24.3 + +require ( + github.com/bsv-blockchain/go-sdk v1.2.9 + github.com/bsv-blockchain/go-wallet-toolbox v0.127.0 + github.com/go-softwarelab/common v1.7.2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/filecoin-project/go-jsonrpc v0.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-co-op/gocron-gorm-lock/v2 v2.0.2 // indirect + github.com/go-co-op/gocron/v2 v2.16.5 // indirect + github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/ipfs/go-log/v2 v2.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gorm.io/datatypes v1.2.6 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/gen v0.3.27 // indirect + gorm.io/gorm v1.30.3 // indirect + gorm.io/hints v1.1.2 // indirect + gorm.io/plugin/dbresolver v1.6.2 // indirect +) diff --git a/examples/complex_wallet_examples/certifier_server_example/go.sum b/examples/complex_wallet_examples/certifier_server_example/go.sum new file mode 100644 index 00000000..fd882c52 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/go.sum @@ -0,0 +1,326 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/bsv-blockchain/go-sdk v1.2.9 h1:LwFzuts+J5X7A+ECx0LNowtUgIahCkNNlXckdiEMSDk= +github.com/bsv-blockchain/go-sdk v1.2.9/go.mod h1:KiHWa/hblo3Bzr+IsX11v0sn1E6elGbNX0VXl5mOq6E= +github.com/bsv-blockchain/go-wallet-toolbox v0.127.0 h1:1JbxDzPtIgq4TadRVDVpJZUS1p7xsSeK6UOGgKm38J8= +github.com/bsv-blockchain/go-wallet-toolbox v0.127.0/go.mod h1:C0u6GDuIdrn6AXg6KTvEBrvk3PutghOJ9qNGKpUkoFQ= +github.com/bsv-blockchain/universal-test-vectors v0.6.1 h1:6mRV8T4ug8456p/rufoDselui3eKY6kr9mRYx8e87Rw= +github.com/bsv-blockchain/universal-test-vectors v0.6.1/go.mod h1:aNNGIH9aN/aCQ9vw0gTiQiOajkyBQIPJM9O6nHhhF5g= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/filecoin-project/go-jsonrpc v0.8.0 h1:2yqlN3Vd8Gx5UtA3fib7tQu2aW1cSOJt253LEBWExo4= +github.com/filecoin-project/go-jsonrpc v0.8.0/go.mod h1:p8WGOwQGYbFugSdK7qKIGhhb1VVcQ2rtBLdEiik1QWI= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-co-op/gocron-gorm-lock/v2 v2.0.2 h1:isn63wxb3V7xHqHF15YHaICdUaAx1HLHRODIRzwfbNo= +github.com/go-co-op/gocron-gorm-lock/v2 v2.0.2/go.mod h1:QUNfp+9/Ee6+fVIJPq5ju1h5qLWsgVl2pNffQpxoKa4= +github.com/go-co-op/gocron/v2 v2.16.5 h1:j228Jxk7bb9CF8LKR3gS+bK3rcjRUINjlVI+ZMp26Ss= +github.com/go-co-op/gocron/v2 v2.16.5/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= +github.com/go-softwarelab/common v1.7.2 h1:j8BE4mghOJ/5K+7O4cz9ZKIoeHNeOnfw/AELrn6iVJI= +github.com/go-softwarelab/common v1.7.2/go.mod h1:od6rXr9CkUq8nRGFyiuc1D2k+CDumk0EQy4P1CuetcY= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg= +github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= +github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= +github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 h1:LqUos1oR5iuuzorFnSvxsHNdYdCHB/DfI82CuT58wbI= +github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0 h1:hsVwFkS6s+79MbKEO+W7A1wNIw1fmkMtF4fg83m6kbc= +github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0/go.mod h1:Qj/eGbRbO/rEYdcRLmN+bEojzatP/+NS1y8ojl2PQsc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.6 h1:KafLdXvFUhzNeL2ncm03Gl3eTLONQfNKZ+wJ+9Y4Nck= +gorm.io/datatypes v1.2.6/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc= +gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw= +gorm.io/gen v0.3.27 h1:ziocAFLpE7e0g4Rum69pGfB9S6DweTxK8gAun7cU8as= +gorm.io/gen v0.3.27/go.mod h1:9zquz2xD1f3Eb/eHq4oLn2z6vDVvQlCY5S3uMBLv4EA= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.30.3 h1:QiG8upl0Sg9ba2Zatfjy0fy4It2iNBL2/eMdvEkdXNs= +gorm.io/gorm v1.30.3/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= +gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go b/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go new file mode 100644 index 00000000..ecad017f --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go @@ -0,0 +1,100 @@ +package config + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + "runtime" + + "github.com/bsv-blockchain/certifier-server-example/internal/constants" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs" + "github.com/go-softwarelab/common/pkg/to" + "gopkg.in/yaml.v3" +) + +type Config struct { + Server struct { + Port string `yaml:"port"` + Network string `yaml:"network"` + } `yaml:"server"` + + // CertifierStorage struct { + // Path string `yaml:"path"` + // } `yaml:"certifier_storage"` + + CertifierWallet struct { + IdentityKey string `yaml:"identity_key"` + PrivateKey string `yaml:"private_key"` + } `yaml:"certifier_wallet"` + + UserWallet struct { + IdentityKey string `yaml:"identity_key"` + PrivateKey string `yaml:"private_key"` + } `yaml:"user_wallet"` + + Storage struct { + URL string `yaml:"url"` + PrivateKey string `yaml:"private_key"` + } `yaml:"storage"` +} + +func LoadConfig(path string, log *slog.Logger) (*Config, error) { + path = to.IfThen(path != "", path).ElseThen(getConfigFilePath()) + + data, err := os.ReadFile(path) + if err != nil { + log.Warn("Could not read config file, using defaults", "error", err) + return nil, fmt.Errorf("could not read config file: %w", err) + } + + var cfg Config + err = yaml.Unmarshal(data, &cfg) + if err != nil { + return nil, fmt.Errorf("could not parse YAML: %w", err) + } + + return &cfg, nil +} + +func getConfigFilePath() string { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("failed to get current file path") + } + + examplesDir := filepath.Dir(filepath.Dir(filepath.Dir(filename))) + return filepath.Join(examplesDir, constants.ConfigFileName) +} + +func (c *Config) Validate() error { + if c.Server.Port == "" { + c.Server.Port = constants.DefaultServerPort + } + + if c.Server.Network == "" { + c.Server.Network = to.String(defs.NetworkTestnet) + } + + if c.Storage.PrivateKey == "" { + return fmt.Errorf("server private key is required") + } + + if c.CertifierWallet.PrivateKey == "" { + return fmt.Errorf("certifier wallet private key is required") + } + + if c.CertifierWallet.IdentityKey == "" { + return fmt.Errorf("certifier wallet identity key is required") + } + + if c.UserWallet.PrivateKey == "" { + return fmt.Errorf("user wallet private key is required") + } + + if c.UserWallet.IdentityKey == "" { + return fmt.Errorf("user wallet identity key is required") + } + + return nil +} diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go b/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go new file mode 100644 index 00000000..be7be8b8 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go @@ -0,0 +1,15 @@ +package constants + +const ( + SQLiteStorageFile = "storage.sqlite" + DefaultServerPort = "3000" + SupportedCertType = "TestType" + ConfigFileName = "config.yaml" + ContentTypeJSON = "application/json" + + // Field names for certificate validation + EmailField = "Email" + FirstNameField = "FirstName" + LastNameField = "LastName" + CountryField = "Country" +) diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/domain/certificate.go b/examples/complex_wallet_examples/certifier_server_example/internal/domain/certificate.go new file mode 100644 index 00000000..eafc4730 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/domain/certificate.go @@ -0,0 +1,71 @@ +package domain + +import ( + "context" + "errors" + + "github.com/bsv-blockchain/go-sdk/auth/certificates" + "github.com/bsv-blockchain/go-sdk/wallet" + "github.com/bsv-blockchain/certifier-server-example/internal/constants" +) + +type CertificateService interface { + SignCertificate(ctx context.Context, masterCert *certificates.MasterCertificate, counterparty wallet.Counterparty) ([]byte, error) +} + +type CertificateValidator interface { + ValidateRequest(masterCert *certificates.MasterCertificate) error + ValidateDecryptedFields(fields map[wallet.CertificateFieldNameUnder50Bytes]string) error +} + +type certificateValidator struct{} + +func NewCertificateValidator() CertificateValidator { + return &certificateValidator{} +} + +func (v *certificateValidator) ValidateRequest(masterCert *certificates.MasterCertificate) error { + if masterCert == nil { + return certificates.ErrInvalidMasterCertificate + } + + if len(masterCert.Type) == 0 { + return errors.New("empty certificate type") + } + + if len(masterCert.Fields) == 0 { + return errors.New("empty certificate subject") + } + + if len(masterCert.MasterKeyring) == 0 { + return certificates.ErrMissingMasterKeyring + } + + if masterCert.Type != constants.SupportedCertType { + return errors.New("unsupported certificate type") + } + + return nil +} + +func (v *certificateValidator) ValidateDecryptedFields(fields map[wallet.CertificateFieldNameUnder50Bytes]string) error { + var err error + if fields[constants.EmailField] == "" { + err = errors.Join(err, errors.New("Email field not decrypted")) + } + if fields[constants.FirstNameField] == "" { + err = errors.Join(err, errors.New("FirstName field not decrypted")) + } + if fields[constants.LastNameField] == "" { + err = errors.Join(err, errors.New("LastName field not decrypted")) + } + return err +} + +func ConvertFieldsToString(fields map[wallet.CertificateFieldNameUnder50Bytes]string) map[string]string { + stringFields := make(map[string]string) + for k, v := range fields { + stringFields[string(k)] = v + } + return stringFields +} \ No newline at end of file diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/create_storage.go b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/create_storage.go new file mode 100644 index 00000000..c962a3a9 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/create_storage.go @@ -0,0 +1,93 @@ +package example_setup + +import ( + "context" + "fmt" + "log/slog" + "path/filepath" + "runtime" + + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/monitor" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/storage" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wdk" + + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/infra" +) + +const ( + SQLiteStorageFile = "storage.sqlite" +) + +func getExamplesDir() string { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("failed to get current file path") + } + + const parentLevels = 3 + for range parentLevels { + filename = filepath.Dir(filename) + } + return filename +} + +func CreateLocalStorage(ctx context.Context, network defs.BSVNetwork, serverPrivateKey string) (*storage.Provider, func(), error) { + logger := slog.Default() + + cfg := infra.Defaults() + cfg.ServerPrivateKey = serverPrivateKey + if network == defs.NetworkTestnet { + cfg.BSVNetwork = network + cfg.Services = defs.DefaultServicesConfig(network) + } + + cfg.DBConfig.SQLite.ConnectionString = filepath.Join(getExamplesDir(), SQLiteStorageFile) + + storageIdentityKey, err := wdk.IdentityKey(cfg.ServerPrivateKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to create storage identity key: %w", err) + } + + activeServices := services.New(logger, cfg.Services) + + options := append( + infra.GORMProviderOptionsFromConfig(&cfg), + storage.WithLogger(logger), + storage.WithBackgroundBroadcasterContext(ctx), + ) + + activeStorage, err := storage.NewGORMProvider(cfg.BSVNetwork, activeServices, options...) + if err != nil { + return nil, nil, fmt.Errorf("failed to create storage: %w", err) + } + + _, err = activeStorage.Migrate(ctx, cfg.Name, storageIdentityKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to migrate storage: %w", err) + } + + var daemon *monitor.Daemon + if cfg.Monitor.Enabled { + daemon, err = monitor.NewDaemonWithGORMLocker(ctx, logger, activeStorage, activeStorage.Database.DB) + if err != nil { + return nil, nil, fmt.Errorf("failed to create daemon: %w", err) + } + + if err = daemon.Start(cfg.Monitor.Tasks.EnabledTasks()); err != nil { + return nil, nil, fmt.Errorf("failed to start storage monitor: %w", err) + } + } + + cleanup := func() { + if daemon != nil { + if err := daemon.Stop(); err != nil { + slog.Error(fmt.Sprintf("failed to stop storage monitor: %v", err)) + } + } + activeStorage.Stop() + } + + return activeStorage, cleanup, nil +} diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go new file mode 100644 index 00000000..cd82a6ee --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go @@ -0,0 +1,134 @@ +package example_setup + +import ( + "context" + "fmt" + "log/slog" + + ec "github.com/bsv-blockchain/go-sdk/primitives/ec" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/storage" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wallet" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wdk" + "github.com/go-softwarelab/common/pkg/to" +) + +type Setup struct { + Environment Environment + IdentityKey *ec.PublicKey + PrivateKey *ec.PrivateKey + ServerPrivateKey string +} + +type Environment struct { + BSVNetwork defs.BSVNetwork `mapstructure:"bsv_network"` + ServerURL string `mapstructure:"server_url"` +} + +type SetupConfig struct { + Network defs.BSVNetwork `mapstructure:"network"` + ServerURL string `mapstructure:"server_url"` + ServerPrivateKey string `mapstructure:"server_private_key"` + Alice UserConfig `mapstructure:"alice"` + Bob UserConfig `mapstructure:"bob"` +} + +type UserConfig struct { + IdentityKey string `mapstructure:"identity_key"` + PrivateKey string `mapstructure:"private_key"` +} + +func (u *UserConfig) Verify() error { + if len(u.IdentityKey) == 0 { + return fmt.Errorf("identity key value is required") + } + + if len(u.PrivateKey) == 0 { + return fmt.Errorf("private key value is required") + } + + return nil +} + +func (c *SetupConfig) Validate() error { + if _, err := defs.ParseBSVNetworkStr(string(c.Network)); err != nil { + return fmt.Errorf("invalid BSV network: %w", err) + } + + if c.ServerPrivateKey == "" { + return fmt.Errorf("server_private_key is required") + } + + if err := c.Alice.Verify(); err != nil { + return fmt.Errorf("alice user config is invalid: %w", err) + } + + if err := c.Bob.Verify(); err != nil { + return fmt.Errorf("bob user config is invalid: %w", err) + } + + return nil +} + +func (s *Setup) CreateAliceWallet(ctx context.Context) (*wallet.Wallet, func()) { + var storageProvider wdk.WalletStorageProvider + var cleanup func() + var err error + + remoteStorage := s.Environment.ServerURL != "" + + if remoteStorage { + slog.Info("Using remote storage", s.Environment.ServerURL) + storageProvider, cleanup, err = storage.NewClient(s.Environment.ServerURL) + } else { + slog.Info("Using local storage", SQLiteStorageFile) + storageProvider, cleanup, err = CreateLocalStorage(ctx, s.Environment.BSVNetwork, s.ServerPrivateKey) + } + + if err != nil { + name := to.IfThen(remoteStorage, "remote").ElseThen("local") + panic(fmt.Errorf("failed to create %s storage provider: %w", name, err)) + } + + userWallet, err := wallet.New(s.Environment.BSVNetwork, s.PrivateKey, storageProvider) + if err != nil { + cleanup() + panic(fmt.Errorf("failed to create wallet: %w", err)) + } + + slog.Info("CreateWallet", s.IdentityKey.ToDERHex()) + return userWallet, cleanup +} + +// CreateWallet creates a new wallet for the user +// It uses either local storage or connects to remote server +// It returns the wallet and a cleanup function, panicking if wallet creation fails +func (s *Setup) CreateCertifierWallet(ctx context.Context) (*wallet.Wallet, func()) { + var storageProvider wdk.WalletStorageProvider + var cleanup func() + var err error + + remoteStorage := s.Environment.ServerURL != "" + + if remoteStorage { + slog.Info("Using remote storage", s.Environment.ServerURL) + storageProvider, cleanup, err = storage.NewClient(s.Environment.ServerURL) + } else { + slog.Info("Using local storage", SQLiteStorageFile) + storageProvider, cleanup, err = CreateLocalStorage(ctx, s.Environment.BSVNetwork, s.ServerPrivateKey) + } + + if err != nil { + name := to.IfThen(remoteStorage, "remote").ElseThen("local") + panic(fmt.Errorf("failed to create %s storage provider: %w", name, err)) + } + + userWallet, err := wallet.New(s.Environment.BSVNetwork, s.PrivateKey, storageProvider) + if err != nil { + cleanup() + panic(fmt.Errorf("failed to create wallet: %w", err)) + } + + slog.Info("CreateWallet", s.IdentityKey.ToDERHex()) + return userWallet, cleanup +} diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go b/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go new file mode 100644 index 00000000..9fa7f0de --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go @@ -0,0 +1,97 @@ +package server + +import ( + "context" + "fmt" + "log/slog" + "net/http" + + "github.com/bsv-blockchain/certifier-server-example/internal/config" + "github.com/bsv-blockchain/certifier-server-example/internal/example_setup" + "github.com/bsv-blockchain/certifier-server-example/internal/service" + httpTransport "github.com/bsv-blockchain/certifier-server-example/internal/transport/http" + "github.com/bsv-blockchain/go-sdk/auth/certificates" + ec "github.com/bsv-blockchain/go-sdk/primitives/ec" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/storage" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wallet" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wdk" +) + +type Server struct { + config *config.Config + logger *slog.Logger + wallet certificates.CertifierWallet +} + +func New(cfg *config.Config, logger *slog.Logger) (*Server, error) { + server := &Server{ + config: cfg, + logger: logger, + } + + if err := server.initializeCertifierWallet(); err != nil { + return nil, fmt.Errorf("failed to initialize wallet: %w", err) + } + + return server, nil +} + +func (s *Server) initializeCertifierWallet() error { + privateKey, err := ec.PrivateKeyFromHex(s.config.CertifierWallet.PrivateKey) + if err != nil { + return fmt.Errorf("invalid certifier private key: %w", err) + } + + identityKey := privateKey.PubKey() + if identityKey.ToDERHex() != s.config.CertifierWallet.IdentityKey { + return fmt.Errorf("identity key does not match the public key derived from private key") + } + + storageProvider, _, err := s.createStorageProvider() + if err != nil { + return fmt.Errorf("failed to create storage provider: %w", err) + } + + s.wallet, err = wallet.New( + defs.BSVNetwork(s.config.Server.Network), + s.config.CertifierWallet.PrivateKey, + storageProvider, + ) + if err != nil { + return fmt.Errorf("failed to create wallet: %w", err) + } + + return nil +} + +func (s *Server) createStorageProvider() (wdk.WalletStorageProvider, func(), error) { + if s.config.Storage.URL != "" { + return storage.NewClient(s.config.Storage.URL) + } + + return example_setup.CreateLocalStorage( + context.TODO(), + defs.BSVNetwork(s.config.Server.Network), + s.config.Storage.PrivateKey, + ) +} + +func (s *Server) setupRoutes() http.Handler { + certificateService := service.NewCertificateService(s.wallet, s.logger) + certificateHandler := httpTransport.NewCertificateHandler(certificateService, s.config, s.logger) + + mux := http.NewServeMux() + mux.Handle("/", certificateHandler) + + return mux +} + +func (s *Server) Start() error { + handler := s.setupRoutes() + addr := ":" + s.config.Server.Port + + s.logger.Info("Starting certificate server", "addr", addr, "network", s.config.Server.Network) + + return http.ListenAndServe(addr, handler) +} diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go b/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go new file mode 100644 index 00000000..6435c37b --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go @@ -0,0 +1,74 @@ +package service + +import ( + "context" + "log/slog" + + "github.com/bsv-blockchain/certifier-server-example/internal/domain" + "github.com/bsv-blockchain/go-sdk/auth/certificates" + ec "github.com/bsv-blockchain/go-sdk/primitives/ec" + "github.com/bsv-blockchain/go-sdk/wallet" +) + +type CertificateService struct { + wallet certificates.CertifierWallet + validator domain.CertificateValidator + logger *slog.Logger +} + +func NewCertificateService(wallet certificates.CertifierWallet, logger *slog.Logger) *CertificateService { + return &CertificateService{ + wallet: wallet, + validator: domain.NewCertificateValidator(), + logger: logger, + } +} + +func (cs *CertificateService) SignCertificate(masterCertificate *certificates.MasterCertificate, counterPartyPubKey *ec.PublicKey) ([]byte, error) { + ctx := context.Background() + + if err := cs.validator.ValidateRequest(masterCertificate); err != nil { + return nil, err + } + + // TODO: implement nonce verification (to mirror ts implementation) + + counterparty := wallet.Counterparty{ + Type: wallet.CounterpartyTypeOther, + Counterparty: counterPartyPubKey, + } + + fields, err := certificates.DecryptFields( + ctx, + cs.wallet, + masterCertificate.MasterKeyring, + masterCertificate.Fields, + counterparty, + false, + "", + ) + if err != nil { + return nil, err + } + + if err := cs.validator.ValidateDecryptedFields(fields); err != nil { + return nil, err + } + + stringFields := domain.ConvertFieldsToString(fields) + + signedMasterCert, err := certificates.IssueCertificateForSubject( + ctx, + cs.wallet, + counterparty, + stringFields, + string(masterCertificate.Type), + nil, + "", + ) + if err != nil { + return nil, err + } + + return signedMasterCert.ToBinary(true) +} diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go new file mode 100644 index 00000000..cb5c40c6 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go @@ -0,0 +1,85 @@ +package http + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + + "github.com/bsv-blockchain/certifier-server-example/internal/config" + "github.com/bsv-blockchain/certifier-server-example/internal/constants" + "github.com/bsv-blockchain/certifier-server-example/internal/service" + "github.com/bsv-blockchain/go-sdk/auth/certificates" + ec "github.com/bsv-blockchain/go-sdk/primitives/ec" +) + +type CertificateHandler struct { + service *service.CertificateService + cfg *config.Config // until we not using auth middleware we will take key from config + logger *slog.Logger +} + +func NewCertificateHandler(svc *service.CertificateService, config *config.Config, logger *slog.Logger) *CertificateHandler { + return &CertificateHandler{ + service: svc, + cfg: config, + logger: logger, + } +} + +func (h *CertificateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + h.writeError(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + h.logger.Info("Received certificate signing request", "path", r.URL.Path) + + body, err := h.readRequestBody(r) + if err != nil { + h.logger.Error("Failed to read request body", "error", err) + h.writeError(w, "Bad Request", http.StatusBadRequest) + return + } + + var masterCert certificates.MasterCertificate + if err := json.Unmarshal(body, &masterCert); err != nil { + h.logger.Error("Failed to unmarshal JSON", "error", err, "body", string(body)) + h.writeError(w, "Invalid JSON", http.StatusBadRequest) + return + } + + h.logger.Info("Parsed certificate request", "cert_type", masterCert.Type) + + // TODO: change to take pubkey from request when middleware with auth will be attached + counterPartyPrivKey, err := ec.PrivateKeyFromHex(h.cfg.UserWallet.PrivateKey) + if err != nil { + return + } + counterPartyPubKey := counterPartyPrivKey.PubKey() + + signedCert, err := h.service.SignCertificate(&masterCert, counterPartyPubKey) + if err != nil { + h.logger.Error("Certificate signing failed", "error", err) + h.writeError(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + h.writeJSONResponse(w, signedCert, http.StatusOK) + h.logger.Info("Certificate signed and response sent", "status", http.StatusOK) +} + +func (h *CertificateHandler) readRequestBody(r *http.Request) ([]byte, error) { + defer r.Body.Close() + return io.ReadAll(r.Body) +} + +func (h *CertificateHandler) writeError(w http.ResponseWriter, message string, statusCode int) { + http.Error(w, message, statusCode) +} + +func (h *CertificateHandler) writeJSONResponse(w http.ResponseWriter, data []byte, statusCode int) { + w.Header().Set("Content-Type", constants.ContentTypeJSON) + w.WriteHeader(statusCode) + w.Write(data) +} From cc1b9f21f398819fec70d5074e60958198f5c9dc Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Wed, 17 Sep 2025 18:36:10 +0200 Subject: [PATCH 2/7] feat: implement certifier server client example with certificate creation and handling --- .../test_sign_certificate/main.go | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go diff --git a/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go new file mode 100644 index 00000000..33f42766 --- /dev/null +++ b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go @@ -0,0 +1,209 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "os" + + "github.com/bsv-blockchain/certifier-server-example/internal/config" + "github.com/bsv-blockchain/certifier-server-example/internal/constants" + "github.com/bsv-blockchain/certifier-server-example/internal/example_setup" + "github.com/bsv-blockchain/go-sdk/auth/certificates" + primitives "github.com/bsv-blockchain/go-sdk/primitives/ec" + gosdk "github.com/bsv-blockchain/go-sdk/wallet" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs" + "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wallet" +) + +var ( + CertifierAddress = "http://localhost" +) + +func main() { + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + + cfg, err := setupConfig(logger) + if err != nil { + os.Exit(1) + } + + aliceWallet, cleanup, _, aliceIdentityKey, err := setupWallet(cfg, logger) + if err != nil { + os.Exit(1) + } + defer cleanup() + + masterCertificate, err := createCertificate(context.Background(), *aliceWallet, cfg, aliceIdentityKey, logger) + if err != nil { + logger.Error("Failed to create certificate", "error", err) + os.Exit(1) + } + + resp, err := sendCertificateToServer(masterCertificate, cfg, logger) + if err != nil { + os.Exit(1) + } + + err = handleResponse(resp, logger) + if err != nil { + os.Exit(1) + } +} + +func setupConfig(logger *slog.Logger) (*config.Config, error) { + cfg, err := config.LoadConfig("", logger) + if err != nil { + logger.Error("Failed to load config", "error", err) + return nil, err + } + + if err := cfg.Validate(); err != nil { + logger.Error("Invalid configuration", "error", err) + return nil, err + } + + return cfg, nil +} + +func setupWallet(cfg *config.Config, logger *slog.Logger) (*wallet.Wallet, func(), *primitives.PrivateKey, *primitives.PublicKey, error) { + alicePrivateKey, err := primitives.PrivateKeyFromHex(cfg.UserWallet.PrivateKey) + if err != nil { + logger.Error("Invalid user private key", "error", err) + return nil, nil, nil, nil, err + } + + aliceIdentityKey := alicePrivateKey.PubKey() + if aliceIdentityKey.ToDERHex() != cfg.UserWallet.IdentityKey { + logger.Error("User identity key mismatch") + return nil, nil, nil, nil, fmt.Errorf("user identity key mismatch") + } + + alice := &example_setup.Setup{ + Environment: example_setup.Environment{ + BSVNetwork: defs.BSVNetwork(cfg.Server.Network), + ServerURL: cfg.Storage.URL, + }, + IdentityKey: aliceIdentityKey, + PrivateKey: alicePrivateKey, + ServerPrivateKey: cfg.Storage.PrivateKey, + } + + aliceWallet, cleanup := alice.CreateAliceWallet(context.Background()) + + return aliceWallet, cleanup, alicePrivateKey, aliceIdentityKey, nil +} + +func createCertificate(ctx context.Context, aliceWallet wallet.Wallet, cfg *config.Config, aliceIdentityKey *primitives.PublicKey, logger *slog.Logger) (*certificates.MasterCertificate, error) { + fields := map[gosdk.CertificateFieldNameUnder50Bytes]string{ + constants.FirstNameField: "John", + constants.LastNameField: "Doe", + constants.CountryField: "US", + constants.EmailField: "john.doe@example.com", + } + + certifierIdentityKey, err := primitives.PublicKeyFromString(cfg.CertifierWallet.IdentityKey) + if err != nil { + logger.Error("Invalid certifier identity key", "error", err) + return nil, err + } + + certifierCounterparty := gosdk.Counterparty{ + Type: gosdk.CounterpartyTypeOther, + Counterparty: certifierIdentityKey, + } + + createCertificateResults, err := certificates.CreateCertificateFields( + ctx, + &aliceWallet, + certifierCounterparty, + fields, + false, + "", + ) + if err != nil { + return nil, err + } + + certificateFields := createCertificateResults.CertificateFields + certificateMasterKeyring := createCertificateResults.MasterKeyring + + certificate := certificates.NewCertificate( + constants.SupportedCertType, + "", + *aliceIdentityKey, + *certifierIdentityKey, + nil, + certificateFields, + []byte(""), + ) + + masterCertificate := &certificates.MasterCertificate{ + Certificate: *certificate, + MasterKeyring: certificateMasterKeyring, + } + + return masterCertificate, nil +} + +func sendCertificateToServer(masterCertificate *certificates.MasterCertificate, cfg *config.Config, logger *slog.Logger) (*http.Response, error) { + bytesToSend, err := json.Marshal(masterCertificate) + if err != nil { + logger.Error("Failed to marshal master certificate", "error", err) + return nil, err + } + + if cfg.Server.Port == "" { + logger.Error("Server port is not configured") + return nil, fmt.Errorf("server port is not configured") + } + + CertifierAddress = fmt.Sprintf("%s:%s", CertifierAddress, cfg.Server.Port) + + logger.Info("Sending certificate to server", "address", CertifierAddress) + resp, err := http.Post(CertifierAddress, constants.ContentTypeJSON, bytes.NewReader(bytesToSend)) + if err != nil { + logger.Error("Failed to send request to certifier", "error", err) + return nil, err + } + + return resp, nil +} + +func handleResponse(resp *http.Response, logger *slog.Logger) error { + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + logger.Error("Certificate signing failed", "status", resp.Status, "response", string(bodyBytes)) + return fmt.Errorf("certificate signing failed with status: %s", resp.Status) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + logger.Error("Failed to read response body", "error", err) + return err + } + + signedCertificate, err := certificates.CertificateFromBinary(bodyBytes) + if err != nil { + logger.Error("Failed to parse signed certificate", "error", err) + return err + } + + fmt.Println("=== Certificate Signing Successful ===") + fmt.Printf("Status: %s\n", resp.Status) + fmt.Printf("Certificate Type: %s\n", signedCertificate.Type) + fmt.Printf("Subject: %s\n", signedCertificate.Subject.ToDERHex()) + fmt.Printf("Certifier: %s\n", signedCertificate.Certifier.ToDERHex()) + logger.Info("Certificate validation completed successfully") + fmt.Println("=====================================") + + return nil +} From 36383c6926351dde946f2596fcc4a4196b857782 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Fri, 26 Sep 2025 12:08:28 +0200 Subject: [PATCH 3/7] refactor: unify wallet creation method to accept private key as parameter --- .../internal/example_setup/setup.go | 34 ++----------------- .../test_sign_certificate/main.go | 2 +- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go index cd82a6ee..e7a2ac2e 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go @@ -70,40 +70,10 @@ func (c *SetupConfig) Validate() error { return nil } -func (s *Setup) CreateAliceWallet(ctx context.Context) (*wallet.Wallet, func()) { - var storageProvider wdk.WalletStorageProvider - var cleanup func() - var err error - - remoteStorage := s.Environment.ServerURL != "" - - if remoteStorage { - slog.Info("Using remote storage", s.Environment.ServerURL) - storageProvider, cleanup, err = storage.NewClient(s.Environment.ServerURL) - } else { - slog.Info("Using local storage", SQLiteStorageFile) - storageProvider, cleanup, err = CreateLocalStorage(ctx, s.Environment.BSVNetwork, s.ServerPrivateKey) - } - - if err != nil { - name := to.IfThen(remoteStorage, "remote").ElseThen("local") - panic(fmt.Errorf("failed to create %s storage provider: %w", name, err)) - } - - userWallet, err := wallet.New(s.Environment.BSVNetwork, s.PrivateKey, storageProvider) - if err != nil { - cleanup() - panic(fmt.Errorf("failed to create wallet: %w", err)) - } - - slog.Info("CreateWallet", s.IdentityKey.ToDERHex()) - return userWallet, cleanup -} - // CreateWallet creates a new wallet for the user // It uses either local storage or connects to remote server // It returns the wallet and a cleanup function, panicking if wallet creation fails -func (s *Setup) CreateCertifierWallet(ctx context.Context) (*wallet.Wallet, func()) { +func (s *Setup) CreateWallet(ctx context.Context, privkey *ec.PrivateKey) (*wallet.Wallet, func()) { var storageProvider wdk.WalletStorageProvider var cleanup func() var err error @@ -123,7 +93,7 @@ func (s *Setup) CreateCertifierWallet(ctx context.Context) (*wallet.Wallet, func panic(fmt.Errorf("failed to create %s storage provider: %w", name, err)) } - userWallet, err := wallet.New(s.Environment.BSVNetwork, s.PrivateKey, storageProvider) + userWallet, err := wallet.New(s.Environment.BSVNetwork, privkey, storageProvider) if err != nil { cleanup() panic(fmt.Errorf("failed to create wallet: %w", err)) diff --git a/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go index 33f42766..eeae4154 100644 --- a/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go +++ b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go @@ -95,7 +95,7 @@ func setupWallet(cfg *config.Config, logger *slog.Logger) (*wallet.Wallet, func( ServerPrivateKey: cfg.Storage.PrivateKey, } - aliceWallet, cleanup := alice.CreateAliceWallet(context.Background()) + aliceWallet, cleanup := alice.CreateWallet(context.Background(), alicePrivateKey) return aliceWallet, cleanup, alicePrivateKey, aliceIdentityKey, nil } From 81bfb6325977872d8626c6ae3f1607de841a4266 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Fri, 26 Sep 2025 12:11:24 +0200 Subject: [PATCH 4/7] refactor: remove unused certifier storage configuration and wallet creation comments --- .../certifier_server_example/config.example.yaml | 3 --- .../certifier_server_example/internal/config/config.go | 4 ---- .../certifier_server_example/internal/example_setup/setup.go | 3 --- .../certifier_server_example/internal/server/server.go | 2 +- .../internal/service/certificate_service.go | 2 -- 5 files changed, 1 insertion(+), 13 deletions(-) diff --git a/examples/complex_wallet_examples/certifier_server_example/config.example.yaml b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml index 9b50c895..f4e3d566 100644 --- a/examples/complex_wallet_examples/certifier_server_example/config.example.yaml +++ b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml @@ -2,9 +2,6 @@ server: port: "3000" network: test -# certifier_storage: -# path: "" - certifier_wallet: identity_key: 03a6f9ed671bba9f41d3a2b93448700ad14b5eb0eee86a565f86b77fea8119d34e private_key: 7180af43d6ca7ee37ec460505d6512d1f58a3fd926b0110fa163d0aea1615c47 diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go b/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go index ecad017f..cc25fc79 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/config/config.go @@ -19,10 +19,6 @@ type Config struct { Network string `yaml:"network"` } `yaml:"server"` - // CertifierStorage struct { - // Path string `yaml:"path"` - // } `yaml:"certifier_storage"` - CertifierWallet struct { IdentityKey string `yaml:"identity_key"` PrivateKey string `yaml:"private_key"` diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go index e7a2ac2e..3117bcf4 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/example_setup/setup.go @@ -70,9 +70,6 @@ func (c *SetupConfig) Validate() error { return nil } -// CreateWallet creates a new wallet for the user -// It uses either local storage or connects to remote server -// It returns the wallet and a cleanup function, panicking if wallet creation fails func (s *Setup) CreateWallet(ctx context.Context, privkey *ec.PrivateKey) (*wallet.Wallet, func()) { var storageProvider wdk.WalletStorageProvider var cleanup func() diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go b/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go index 9fa7f0de..fb57f1ac 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/server/server.go @@ -71,7 +71,7 @@ func (s *Server) createStorageProvider() (wdk.WalletStorageProvider, func(), err } return example_setup.CreateLocalStorage( - context.TODO(), + context.Background(), defs.BSVNetwork(s.config.Server.Network), s.config.Storage.PrivateKey, ) diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go b/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go index 6435c37b..39a76cf4 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/service/certificate_service.go @@ -31,8 +31,6 @@ func (cs *CertificateService) SignCertificate(masterCertificate *certificates.Ma return nil, err } - // TODO: implement nonce verification (to mirror ts implementation) - counterparty := wallet.Counterparty{ Type: wallet.CounterpartyTypeOther, Counterparty: counterPartyPubKey, From 7139ce7b063fa301d9cdb7470ab3191782f935b1 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Fri, 26 Sep 2025 12:15:13 +0200 Subject: [PATCH 5/7] fix: correct indentation in storage configuration of certifier server example --- .../certifier_server_example/config.example.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/complex_wallet_examples/certifier_server_example/config.example.yaml b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml index f4e3d566..deb0f6d6 100644 --- a/examples/complex_wallet_examples/certifier_server_example/config.example.yaml +++ b/examples/complex_wallet_examples/certifier_server_example/config.example.yaml @@ -11,5 +11,5 @@ user_wallet: private_key: f58bf314315ca34c85e92ca263f72cabd32b6d355b43481215530ca1d0671693 storage: - url: http://localhost:8100 - private_key: 2b32d442b25d6e7447a1f9ca41a2a15de5004498dc4ffc43b7b009a96724c30d + url: http://localhost:8100 + private_key: 2b32d442b25d6e7447a1f9ca41a2a15de5004498dc4ffc43b7b009a96724c30d From 0111fa987519f076c5fafa0f894107f9198255f1 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Fri, 26 Sep 2025 12:24:11 +0200 Subject: [PATCH 6/7] fix: handle error when parsing private key in certificate signing request --- .../internal/transport/http/certificate.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go index cb5c40c6..2e6876e8 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go @@ -54,8 +54,11 @@ func (h *CertificateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO: change to take pubkey from request when middleware with auth will be attached counterPartyPrivKey, err := ec.PrivateKeyFromHex(h.cfg.UserWallet.PrivateKey) if err != nil { + h.logger.Error("Failed to parse private key", "error", err) + h.writeError(w, "Internal Server Error", http.StatusInternalServerError) return } + counterPartyPubKey := counterPartyPrivKey.PubKey() signedCert, err := h.service.SignCertificate(&masterCert, counterPartyPubKey) From 84e4d0915c66ef5cffc82b2e4ab55f6c8bb76049 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Fri, 26 Sep 2025 12:49:25 +0200 Subject: [PATCH 7/7] fix: improve error handling in certificate response and writing JSON response --- .../internal/constants/constants.go | 11 +++++------ .../internal/transport/http/certificate.go | 4 +++- .../test_sign_certificate/main.go | 6 +++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go b/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go index be7be8b8..a0bbc687 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/constants/constants.go @@ -1,12 +1,11 @@ package constants const ( - SQLiteStorageFile = "storage.sqlite" - DefaultServerPort = "3000" - SupportedCertType = "TestType" - ConfigFileName = "config.yaml" - ContentTypeJSON = "application/json" - + SQLiteStorageFile = "storage.sqlite" + DefaultServerPort = "3000" + SupportedCertType = "TestType" + ConfigFileName = "config.yaml" + ContentTypeJSON = "application/json" // Field names for certificate validation EmailField = "Email" FirstNameField = "FirstName" diff --git a/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go index 2e6876e8..49693734 100644 --- a/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go +++ b/examples/complex_wallet_examples/certifier_server_example/internal/transport/http/certificate.go @@ -84,5 +84,7 @@ func (h *CertificateHandler) writeError(w http.ResponseWriter, message string, s func (h *CertificateHandler) writeJSONResponse(w http.ResponseWriter, data []byte, statusCode int) { w.Header().Set("Content-Type", constants.ContentTypeJSON) w.WriteHeader(statusCode) - w.Write(data) + if _, err := w.Write(data); err != nil { + h.logger.Error("Failed to write response", "error", err) + } } diff --git a/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go index eeae4154..06978ff8 100644 --- a/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go +++ b/examples/complex_wallet_examples/certifier_server_example/test_sign_certificate/main.go @@ -180,7 +180,11 @@ func handleResponse(resp *http.Response, logger *slog.Logger) error { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + logger.Error("Certificate signing failed", "status", resp.Status, "error", err, "response", "") + return fmt.Errorf("certificate signing failed with status: %s (failed to read response body: %v)", resp.Status, err) + } logger.Error("Certificate signing failed", "status", resp.Status, "response", string(bodyBytes)) return fmt.Errorf("certificate signing failed with status: %s", resp.Status) }