From 5ae142ec5af66d92619c873f3c1884af97f4ac52 Mon Sep 17 00:00:00 2001 From: ryan-wong-coder Date: Sun, 2 Aug 2026 21:11:41 +0800 Subject: [PATCH] fix(desktop): bind local FISCO BCOS trust config --- clients/desktop/README.md | 14 +++-- clients/desktop/app.go | 6 ++ .../desktop/frontend/src/pages/Settings.vue | 16 ++++- clients/desktop/frontend/src/pages/Verify.vue | 9 ++- .../desktop/frontend/src/stores/settings.ts | 1 + clients/desktop/frontend/wailsjs/go/models.ts | 4 ++ clients/desktop/go.mod | 2 +- clients/desktop/store.go | 1 + clients/desktop/verify.go | 50 +++++++++++++++- clients/desktop/verify_test.go | 60 +++++++++++++++++++ 10 files changed, 153 insertions(+), 10 deletions(-) diff --git a/clients/desktop/README.md b/clients/desktop/README.md index 9405a02..8cb739d 100644 --- a/clients/desktop/README.md +++ b/clients/desktop/README.md @@ -36,15 +36,19 @@ Trust comes only from verifier-local inputs: - a local Key Registry V2 verifier descriptor when lifecycle evidence is required; - local client and server CA roots when certificate chains are used; +- a local canonical FISCO BCOS `TrustConfig` when the evidence contains a + FISCO BCOS anchor; - the built-in, offline-verifiable anchor formats implemented by the SDK. Descriptors, certificates, CRLs, validator sets, or checkpoints embedded in the evidence file never become trust roots by carrying themselves. Missing or -mismatched local trust fails closed. The desktop client has no configurable -anchor policy or provider trust-root input: custom/provider-backed anchors and -the local-only `file`/`noop` sinks fail the anchor stage and cannot receive L5. -Selecting “skip anchor” explicitly limits verification to the available L1-L4 -evidence instead. +mismatched local trust fails closed. The FISCO BCOS TrustConfig is read from a +local canonical CBOR file selected in Settings; it is used only to check the +chain, contract, validator set, and checkpoint carried by the evidence. The +desktop client never connects to a FISCO BCOS endpoint during verification. +Custom/provider-backed anchors and the local-only `file`/`noop` sinks fail the +anchor stage and cannot receive L5. Selecting “skip anchor” explicitly limits +verification to the available L1-L4 evidence instead. ## Development diff --git a/clients/desktop/app.go b/clients/desktop/app.go index 0fe69c8..5d52cfc 100644 --- a/clients/desktop/app.go +++ b/clients/desktop/app.go @@ -476,8 +476,14 @@ func (a *App) SaveSettings(s Settings) error { s.ClientVerifierDescriptor = strings.TrimSpace(s.ClientVerifierDescriptor) s.ServerVerifierDescriptor = strings.TrimSpace(s.ServerVerifierDescriptor) s.RegistryVerifierDescriptor = strings.TrimSpace(s.RegistryVerifierDescriptor) + s.FISCOBCOSTrustConfigFile = strings.TrimSpace(s.FISCOBCOSTrustConfigFile) s.ClientCertificateRoots = strings.TrimSpace(s.ClientCertificateRoots) s.ServerCertificateRoots = strings.TrimSpace(s.ServerCertificateRoots) + if s.FISCOBCOSTrustConfigFile != "" { + if _, err := readDesktopFISCOBCOSTrustConfig(s.FISCOBCOSTrustConfigFile); err != nil { + return fmt.Errorf("FISCO BCOS trust config: %w", err) + } + } if (s.ClientTLSCertFile == "") != (s.ClientTLSKeyFile == "") { return errors.New("client TLS certificate and key must be configured together") } diff --git a/clients/desktop/frontend/src/pages/Settings.vue b/clients/desktop/frontend/src/pages/Settings.vue index 5dd8008..a6bb2fd 100644 --- a/clients/desktop/frontend/src/pages/Settings.vue +++ b/clients/desktop/frontend/src/pages/Settings.vue @@ -8,7 +8,7 @@ import Button from '@/components/Button.vue' import Input from '@/components/Input.vue' import Field from '@/components/Field.vue' import StatusDot from '@/components/StatusDot.vue' -import { Save, RotateCcw, PlugZap, Sparkles } from 'lucide-vue-next' +import { Save, RotateCcw, PlugZap, Sparkles, FolderOpen } from 'lucide-vue-next' const settings = useSettings() const toasts = useToasts() @@ -36,6 +36,7 @@ const dirty = computed(() => { form.client_verifier_descriptor !== settings.settings.client_verifier_descriptor || form.server_verifier_descriptor !== settings.settings.server_verifier_descriptor || form.registry_verifier_descriptor !== settings.settings.registry_verifier_descriptor || + form.fisco_bcos_trust_config_file !== settings.settings.fisco_bcos_trust_config_file || form.client_certificate_roots !== settings.settings.client_certificate_roots || form.server_certificate_roots !== settings.settings.server_certificate_roots || form.require_identity_evidence !== settings.settings.require_identity_evidence || @@ -62,6 +63,11 @@ function revert() { Object.assign(form, settings.settings) } +async function pickFISCOBCOSTrustConfig() { + const path = await api.chooseOpenPath('选择 FISCO BCOS TrustConfig') + if (path) form.fisco_bcos_trust_config_file = path +} + // ----- Ping test (uses the NEW url from the form, not the saved one) ----- const pingLoading = ref(false) const pingResult = ref(null) @@ -212,6 +218,14 @@ function reopenOnboarding() { +
+ +
+ + +
+
+
diff --git a/clients/desktop/frontend/src/pages/Verify.vue b/clients/desktop/frontend/src/pages/Verify.vue index d275e22..7e96200 100644 --- a/clients/desktop/frontend/src/pages/Verify.vue +++ b/clients/desktop/frontend/src/pages/Verify.vue @@ -16,7 +16,7 @@ import { bytesToHex, formatTime, humanSize, nanoToDate } from '@/lib/format' const settings = useSettings() const toasts = useToasts() type Mode = 'local' | 'remote' -type Picker = 'file' | 'single' | 'proof' | 'global' | 'anchor' | 'clientDesc' | 'serverDesc' | 'registry' | 'clientRoot' | 'serverRoot' +type Picker = 'file' | 'single' | 'proof' | 'global' | 'anchor' | 'clientDesc' | 'serverDesc' | 'registry' | 'clientRoot' | 'serverRoot' | 'fiscoConfig' const mode = ref('local') const filePath = ref('') @@ -35,6 +35,7 @@ const serverDescriptors = ref('') const registryDescriptor = ref('') const clientRoots = ref('') const serverRoots = ref('') +const fiscoConfigPath = ref('') const requireEvidence = ref(false) const requireCertificateStatus = ref(false) const showTrustOverrides = ref(false) @@ -54,6 +55,7 @@ async function pick(target: Picker) { registry: '选择 registry verifier descriptor', clientRoot: '选择客户端本地 CA 根', serverRoot: '选择服务器本地 CA 根', + fiscoConfig: '选择 FISCO BCOS TrustConfig', } const path = await api.chooseOpenPath(titles[target]) if (!path) return @@ -67,6 +69,7 @@ async function pick(target: Picker) { if (target === 'registry') registryDescriptor.value = path if (target === 'clientRoot') clientRoots.value = path if (target === 'serverRoot') serverRoots.value = path + if (target === 'fiscoConfig') fiscoConfigPath.value = path } const canVerify = computed(() => @@ -94,6 +97,7 @@ async function verify() { registry_verifier_descriptor: registryDescriptor.value || undefined, client_certificate_roots: clientRoots.value || undefined, server_certificate_roots: serverRoots.value || undefined, + fisco_bcos_trust_config_file: fiscoConfigPath.value || undefined, require_identity_evidence: requireEvidence.value, require_certificate_status: requireCertificateStatus.value, } @@ -188,7 +192,7 @@ function stageClass(status: string) {

- 留空使用 Settings 中的 verifier-local trust。下列路径只从本机读取;证据文件自身携带的证书不会填充这些字段。 + 留空使用 Settings 中的 verifier-local trust。下列路径只从本机读取;证据文件自身携带的证书不会填充这些字段。FISCO BCOS TrustConfig 只用于本机复算,不会连接链节点。

@@ -196,6 +200,7 @@ function stageClass(status: string) {
+
diff --git a/clients/desktop/frontend/src/stores/settings.ts b/clients/desktop/frontend/src/stores/settings.ts index 5780916..819c5be 100644 --- a/clients/desktop/frontend/src/stores/settings.ts +++ b/clients/desktop/frontend/src/stores/settings.ts @@ -16,6 +16,7 @@ const defaultSettings: Settings = { client_verifier_descriptor: '', server_verifier_descriptor: '', registry_verifier_descriptor: '', + fisco_bcos_trust_config_file: '', client_certificate_roots: '', server_certificate_roots: '', require_identity_evidence: false, diff --git a/clients/desktop/frontend/wailsjs/go/models.ts b/clients/desktop/frontend/wailsjs/go/models.ts index 687e2e3..b76a83f 100644 --- a/clients/desktop/frontend/wailsjs/go/models.ts +++ b/clients/desktop/frontend/wailsjs/go/models.ts @@ -583,6 +583,7 @@ export namespace main { client_verifier_descriptor: string; server_verifier_descriptor: string; registry_verifier_descriptor: string; + fisco_bcos_trust_config_file: string; client_certificate_roots: string; server_certificate_roots: string; require_identity_evidence: boolean; @@ -610,6 +611,7 @@ export namespace main { this.client_verifier_descriptor = source["client_verifier_descriptor"]; this.server_verifier_descriptor = source["server_verifier_descriptor"]; this.registry_verifier_descriptor = source["registry_verifier_descriptor"]; + this.fisco_bcos_trust_config_file = source["fisco_bcos_trust_config_file"]; this.client_certificate_roots = source["client_certificate_roots"]; this.server_certificate_roots = source["server_certificate_roots"]; this.require_identity_evidence = source["require_identity_evidence"]; @@ -671,6 +673,7 @@ export namespace main { registry_verifier_descriptor?: string; client_certificate_roots?: string; server_certificate_roots?: string; + fisco_bcos_trust_config_file?: string; require_identity_evidence?: boolean; require_certificate_status?: boolean; @@ -696,6 +699,7 @@ export namespace main { this.registry_verifier_descriptor = source["registry_verifier_descriptor"]; this.client_certificate_roots = source["client_certificate_roots"]; this.server_certificate_roots = source["server_certificate_roots"]; + this.fisco_bcos_trust_config_file = source["fisco_bcos_trust_config_file"]; this.require_identity_evidence = source["require_identity_evidence"]; this.require_certificate_status = source["require_certificate_status"]; } diff --git a/clients/desktop/go.mod b/clients/desktop/go.mod index 425ec75..2612f96 100644 --- a/clients/desktop/go.mod +++ b/clients/desktop/go.mod @@ -4,6 +4,7 @@ go 1.26.5 require ( github.com/cockroachdb/pebble v1.1.5 + github.com/ethereum/go-ethereum v1.17.0 github.com/wailsapp/wails/v2 v2.12.0 github.com/wowtrust/trustdb/v2 v2.0.0-00010101000000-000000000000 ) @@ -33,7 +34,6 @@ require ( github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/emmansun/gmsm v0.44.0 // indirect - github.com/ethereum/go-ethereum v1.17.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect diff --git a/clients/desktop/store.go b/clients/desktop/store.go index 531cd15..5ae98fc 100644 --- a/clients/desktop/store.go +++ b/clients/desktop/store.go @@ -46,6 +46,7 @@ type Settings struct { ClientVerifierDescriptor string `json:"client_verifier_descriptor"` ServerVerifierDescriptor string `json:"server_verifier_descriptor"` RegistryVerifierDescriptor string `json:"registry_verifier_descriptor"` + FISCOBCOSTrustConfigFile string `json:"fisco_bcos_trust_config_file"` ClientCertificateRoots string `json:"client_certificate_roots"` ServerCertificateRoots string `json:"server_certificate_roots"` RequireIdentityEvidence bool `json:"require_identity_evidence"` diff --git a/clients/desktop/verify.go b/clients/desktop/verify.go index 9e9260e..7e584a4 100644 --- a/clients/desktop/verify.go +++ b/clients/desktop/verify.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/wowtrust/trustdb/v2/internal/anchor/fiscobcos" "github.com/wowtrust/trustdb/v2/internal/cborx" "github.com/wowtrust/trustdb/v2/internal/cryptosuite" "github.com/wowtrust/trustdb/v2/internal/keydescriptor" @@ -43,6 +44,7 @@ type VerifyRequest struct { RegistryVerifierDescriptor string `json:"registry_verifier_descriptor,omitempty"` ClientCertificateRoots string `json:"client_certificate_roots,omitempty"` ServerCertificateRoots string `json:"server_certificate_roots,omitempty"` + FISCOBCOSTrustConfigFile string `json:"fisco_bcos_trust_config_file,omitempty"` RequireIdentityEvidence bool `json:"require_identity_evidence,omitempty"` RequireCertificateStatus bool `json:"require_certificate_status,omitempty"` } @@ -226,6 +228,10 @@ func (a *App) desktopOfflineTrust( if strings.TrimSpace(serverRootPaths) == "" { serverRootPaths = settings.ServerCertificateRoots } + fiscoConfigPath := req.FISCOBCOSTrustConfigFile + if strings.TrimSpace(fiscoConfigPath) == "" { + fiscoConfigPath = settings.FISCOBCOSTrustConfigFile + } var clientKeys []sdk.KeyDescriptor switch { @@ -339,6 +345,14 @@ func (a *App) desktopOfflineTrust( } registryKey = keys[0] } + var fiscoConfig *sdk.FISCOBCOSTrustConfig + if strings.TrimSpace(fiscoConfigPath) != "" { + config, configErr := readDesktopFISCOBCOSTrustConfig(fiscoConfigPath) + if configErr != nil { + return sdk.OfflineTrust{}, 0, fmt.Errorf("FISCO BCOS trust config: %w", configErr) + } + fiscoConfig = config + } return sdk.OfflineTrust{ Proof: proofKeys, Identity: sdk.OfflineIdentityTrust{ @@ -350,7 +364,41 @@ func (a *App) desktopOfflineTrust( RequireEvidence: req.RequireIdentityEvidence || settings.RequireIdentityEvidence, RequireCertificateStatus: req.RequireCertificateStatus || settings.RequireCertificateStatus, }, - }, len(clientRoots) + len(serverRoots), nil + FISCOBCOS: fiscoConfig, + }, len(clientRoots) + len(serverRoots) + boolInt(fiscoConfig != nil), nil +} + +func boolInt(value bool) int { + if value { + return 1 + } + return 0 +} + +// readDesktopFISCOBCOSTrustConfig accepts only the canonical public TrustConfig +// CBOR format. Endpoints and credentials in the config are never contacted by +// the desktop verifier; the SDK uses the config as local anchor trust only. +func readDesktopFISCOBCOSTrustConfig(path string) (*sdk.FISCOBCOSTrustConfig, error) { + path = strings.TrimSpace(path) + if path == "" { + return nil, errors.New("path is empty") + } + data, err := readCBORFileLimit(path, fiscobcos.MaxTrustConfigBytes) + if err != nil { + return nil, err + } + config, err := fiscobcos.UnmarshalTrustConfig(data) + if err != nil { + return nil, err + } + canonical, err := fiscobcos.MarshalTrustConfig(config) + if err != nil { + return nil, err + } + if !bytes.Equal(canonical, data) { + return nil, errors.New("file is not canonical TrustConfig CBOR") + } + return (*sdk.FISCOBCOSTrustConfig)(&config), nil } func readDesktopVerifierDescriptors(paths string, suite cryptosuite.ID) ([]sdk.KeyDescriptor, error) { diff --git a/clients/desktop/verify_test.go b/clients/desktop/verify_test.go index b5ca8a1..e43ad4e 100644 --- a/clients/desktop/verify_test.go +++ b/clients/desktop/verify_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/ed25519" + "encoding/hex" "fmt" "os" "path/filepath" @@ -11,6 +12,8 @@ import ( "testing" "time" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/wowtrust/trustdb/v2/internal/anchor/fiscobcos" internalapp "github.com/wowtrust/trustdb/v2/internal/app" "github.com/wowtrust/trustdb/v2/internal/cborx" "github.com/wowtrust/trustdb/v2/internal/cryptosuite" @@ -25,6 +28,63 @@ import ( "github.com/wowtrust/trustdb/v2/sdk" ) +func TestReadDesktopFISCOBCOSTrustConfigAcceptsCanonicalCBOR(t *testing.T) { + t.Parallel() + config, err := fiscobcos.NewTrustConfig(fiscobcos.CryptoModeStandard) + if err != nil { + t.Fatal(err) + } + config.ChainID = "local-chain" + config.GroupID = "group0" + config.GenesisHash = bytes.Repeat([]byte{0x11}, 32) + config.TrustedCheckpoint = fiscobcos.BlockCheckpoint{BlockNumber: 1, BlockHash: bytes.Repeat([]byte{0x22}, 32)} + config.Contract = fiscobcos.ContractBinding{ + Address: bytes.Repeat([]byte{0x33}, 20), + CodeHash: bytes.Repeat([]byte{0x44}, 32), + ProtocolVersion: fiscobcos.TrustDBAnchorV1ProtocolVersion, + EventSignature: fiscobcos.TrustDBAnchorV1EventSignature, + } + config.Endpoints = []string{"tls://127.0.0.1:20200"} + config.ReadQuorum = 1 + config.AccountProvider = fiscobcos.AccountProviderConfig{ + Provider: "keydescriptor", KeyID: "account-1", KeyReference: "keys/account-1", Algorithm: fiscobcos.StandardAccountAlg, + } + config.Certificates = fiscobcos.CertificateConfig{ + TransportMode: fiscobcos.StandardTransport, + TrustedCAReferences: []string{"certs/ca.pem"}, + TrustedCACertificateHashes: [][]byte{bytes.Repeat([]byte{0x55}, 32)}, + ClientSigningCertificateRef: "certs/client.pem", + ClientSigningKeyRef: "keys/client.key", + } + key, err := ethcrypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + public := ethcrypto.FromECDSAPub(&key.PublicKey) + config.Validators = []fiscobcos.ValidatorDescriptor{{ + NodeID: "0x" + hex.EncodeToString(public[1:]), + Algorithm: fiscobcos.StandardAccountAlg, + PublicKeyEncoding: fiscobcos.StandardKeyEncoding, + PublicKey: public, + VoteWeight: 1, + }} + data, err := fiscobcos.MarshalTrustConfig(config) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "trust-config.cbor") + if err := os.WriteFile(path, data, 0o600); err != nil { + t.Fatal(err) + } + loaded, err := readDesktopFISCOBCOSTrustConfig(path) + if err != nil { + t.Fatalf("readDesktopFISCOBCOSTrustConfig() error = %v", err) + } + if loaded == nil || loaded.ChainID != config.ChainID || loaded.CryptoMode != config.CryptoMode { + t.Fatalf("loaded config = %+v, want chain=%q mode=%q", loaded, config.ChainID, config.CryptoMode) + } +} + func TestReadGlobalProofFileExplainsAnchorResultMixup(t *testing.T) { t.Parallel()