Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server:
port: "3000"
network: test

certifier_wallet:
identity_key: 03a6f9ed671bba9f41d3a2b93448700ad14b5eb0eee86a565f86b77fea8119d34e
private_key: 7180af43d6ca7ee37ec460505d6512d1f58a3fd926b0110fa163d0aea1615c47

user_wallet:
identity_key: 02764e1a4165d387ed9914c7b8e4607e55cda2ff3453efb4c2b18843b986bd3b62
private_key: f58bf314315ca34c85e92ca263f72cabd32b6d355b43481215530ca1d0671693

storage:
url: http://localhost:8100
private_key: 2b32d442b25d6e7447a1f9ca41a2a15de5004498dc4ffc43b7b009a96724c30d
66 changes: 66 additions & 0 deletions examples/complex_wallet_examples/certifier_server_example/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
326 changes: 326 additions & 0 deletions examples/complex_wallet_examples/certifier_server_example/go.sum

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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"`

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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"
)
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading