Skip to content

03 · 交易跨包全链路(详细签名 + 验证版)

本章以 机构间 Transfer 为主线,给出从 Bank gRPC 入口到 Committer 落账的每一步代码事实:调用入口、谁签名(用什么 key 签什么数据)、验证什么、落到哪个字段。Issue / Redeem 在最后用差异说明。

只引用源码与运行配置;不引用 cbdc-chain/docs/ 与对外公告。

0. 一笔 Transfer 涉及的角色与密钥

角色调用形态关键私钥私钥落点
Institution(Bank,发起方)FSC view + Token SDK walletwallet recipient identity(X.509,长期身份)sign-kms partition(BCCSP PKCS#11,cbdc-biz/conf/institution-001/core.yaml:93-98
Institution(收款方)FSC view(被动响应)wallet recipient identity同上(每家银行各自 partition)
Endorser FSC nodeFSC view + Token SDK 自动注册的 RequestApprovalResponderViewendorser MSP node identitysign-kms partition KMS_TKconf/endorser1/core.yaml:78-92
AuditorFSC view + Token SDK auditor walletauditor wallet identitysign-kms partition
Issuer(Transfer 链路不签;Issue 链路必签)FSC view + Token SDK issuer walletissuer wallet identitysign-kms partition
Orderer (Router)gRPC mTLS serverTLS server certfabric-ca 签发,私钥在节点本地或 KMS(部署方决定)
Committer Verifier按 namespace 查策略后 verify无私钥(只验证)

外部签名(非链上):

用途私钥数据出处
客户端外部 X.509 签名(Bank API 鉴权)Bank 外部钱包私钥base64(MessageToSign),由 PrepareTransfer 返回cbdc-biz/app/institution/internal/service/transfer_service.go:101-123
RTGS MT298 报文签名Bank RTGS jks keystore 私钥MT298 报文cbdc-biz/pkg/rtgs/crypto.goconf/bizhub/core.yaml:79-112

1. 总体步骤一览(Transfer 主线 · App 视角)

participant 是部署节点(app 进程)。整笔 Transfer 共 36 步,分 4 个阶段展示,每阶段一张图;步骤号与 §2–§5 文字一一对应。

1.1 一图骨架(仅数据流向)

1.2 阶段 A · 准备待签消息(步骤 1–9)

1.3 阶段 B · Endorser 代理审计(步骤 10–19)

1.4 阶段 C · 收 Owner 签名 + 链码背书(步骤 20–26)

1.5 阶段 D · Broadcast + Committer 落账(步骤 27–36)

下文 §2–§5 按编号给出每一步的代码位置、签名 / 验证细节。

2. 步骤 1–9:Bank 端入口与待签消息生成

步骤 1 — Client → TransferService.PrepareTransfer

来源:cbdc-biz/app/institution/internal/service/transfer_service.go:24-99

  • 入口:gRPC cbdc_biz.v1.InstitutionOpsService/PrepareTransfer
  • 入参(核心):senderrecipients[](每项含 wallet_id + amount)、message、可选 use_asset_mark / attach_asset_mark(二者不可同设)
  • 校验:金额合法、use_asset_markattach_asset_mark 互斥(service 层)

不签名、不验签。这是外部 gRPC 入口。

步骤 2–3 — Biz 编排 + 启动 View

  • Biz 编排:cbdc-biz/app/institution/internal/biz/transfer_biz.go:80-167
    • :93-138 pre-view(bizhub 权限 / 路由 / pending 检查)
    • :141-149 viewregistry.GetManager(b.node).InitiateView(prepareView) 起 FSC View

步骤 4 — PrepareTransferView.Call

来源:cbdc-biz/pkg/views/transfer.go:267-633

主要动作:

行号段动作
:267-337解析 sender wallet + 解析所有 recipient identity(wallet_common.GetIdentityResolver().ResolveIdentity()
:339-379fee.CalculateAndResolveIdentity() 计算费用 + 费用收集方
:387-402ttx.NewTransaction(vctx, nil) 创建 Token SDK Transaction,设置 ApplicationMetadata
:429-562tx.Transfer(...) —— 选币(memory 或 distributed token_selector)+ ZK 范围证明承诺;标记转账走 marked.NewAssetMarkSelector()
:565-571senderWallet.GetRecipientIdentity() 得签名身份;tx.TokenRequest.MarshalToSign() 序列化待签消息
:572-603构造 PrepareTransferResult{TxID, MessageToSign(base64), SignerIdentity, RecipientResults, FeeInfo}Result.SignerIdentity = base64(原始 signerIdentity):596),而 ExtractPEMCertificate 抽取的 cert PEM 写入 PendingTx.SignerIdentity:625,见步骤 4 下方 PendingTransaction 行)
:620-632构造 PendingTransaction(持 activeSelector,已选中 token 锁定);committed=true 避免 defer 释放

此时尚无任何签名

tx.TokenRequest还没有 owner sig / auditor sig / endorser sig;仅有 token graph + ZK 承诺。

步骤 5 — 返回 MessageToSign

PrepareTransferResult.MessageToSign 是 base64(待签字节);客户端外部钱包用 sender 私钥对其 ECDSA 签名。

步骤 6 — 外部钱包签名(链外)

私钥位置:调用方银行自己保管(不在本仓库范围)。

步骤 7–9 — Client → SubmitTransferSubmitSignatureView

来源:

  • cbdc-biz/app/institution/internal/service/transfer_service.go:101-123
  • cbdc-biz/app/institution/internal/biz/transfer_biz.go:239-283
  • cbdc-biz/pkg/views/transfer.goSubmitSignatureView 定义于该文件)

SubmitTransfer 解码 base64 signature → PopPendingTransaction(txID) 取出 prepare 阶段 cache 的 selector + tx 句柄 → 构造 SubmitSignatureView{PendingTx, Signature, FinalityRegistry}InitiateView(cert PEM 不是独立字段,已包含在 PendingTx.SignerIdentity 内)。

SubmitSignatureView.Calltransfer.go:680-741)的主体只做 3 件事:

子步骤行号动作
构造 signer:693-698wallet_common.NewPreSubmittedSignatureSigner(signature, MessageToSign, SignerIdentity)
[A] Audit + Endorse:703-711RunView(NewAuditAndEndorseView(tx, ttx.WithExternalWalletSigner(...))) → 进入步骤 10–24
[B] Broadcast:723-730RunView(ttx.NewOrderingView(tx)) → 步骤 27–34
[C] Hand off:735-737FinalityRegistry.Register(tx, Selector) 把 selector 移交 worker 异步等 finality

步骤 7–9 本身不做任何签名校验

service / biz / view.Call 这三层函数体内没有任何 CheckSignature 调用。验签延迟到 步骤 21,由 Token SDK 调 PreSubmittedSignatureSigner.Sign() 时才触发 —— 详见 步骤 21 § external signer 验签流程

3. 步骤 10–19:Endorser 代理审计(双签前置)

步骤 10 — AuditAndEndorseView.Call

来源:cbdc-biz/pkg/views/endorsement.go:25-98

Phase 1 (:35-67)  Bank → Endorser  发 tx bytes,等 sigma
Phase 2 (:69-81)  Bank 内部       verifyAuditorSignature + AddAuditorSignature
Phase 3 (:83-95)  Bank 内部       ttx.NewCollectEndorsementsView(tx, WithSkipAuditing())

步骤 11 — Bank FSC P2P → Endorser

endorsement.go:44 ctx.GetSession(v, view.Identity("endorser1")) → 通过 FSC P2P 连接到 endorser1 节点(连接对端由 cbdc-biz/conf/institution-001/core.yaml: fsc.endpoint.resolvers[] 解析)。

只挡 mTLS:FSC P2P 是 gRPC + mTLS,证书由 fabric-ca 签发;TLS 握手层不查 CRL(见 09 § 8)。

步骤 12 — Endorser 端 AuditResponderView.Call

来源:cbdc-biz/pkg/views/audit.go:86-128

阶段行号行为是否校验
ar_recv_tx:93-100接收 tx bytes(含 trace context)仅反序列化完整性
ar_audit_call:108-114ctx.RunView(&RequestAuditView{TxRaw: txRaw}) 转发到 Auditor
ar_send_sigma:121-126发 sigma 回 Bank

Endorser 在此处不验任何业务。它只是 "Bank 与 Auditor 的中继"。

一个常见误解:Endorser 是不是"被动转发"

对审计签名而言:是,纯转发。 对链码背书(步骤 22-24)而言:不是,Endorser 用 Token SDK 自动注册的 RequestApprovalResponderView 跑 9 道闸门后由 Endorser 自己签 proposal endorsement。

步骤 13 — Endorser FSC P2P → Auditor

audit.go:55-58 ctx.GetSession(v, view.Identity("auditor"))。Bank 永不直连 Auditor。

步骤 14 — Auditor 端 AuditView.Call

来源:cbdc-biz/pkg/views/audit.go:171-573这是整条链路里业务校验最重的一段

阶段行号校验内容失败动作
av_recv_tx:186-190ttx.ReceiveTransaction()panic(assert.NoError)
av_gated:213-259workerpool 限并发(CPU-bound ZK + 解密)返回 ErrPoolSaturated
audit.Validate(tx):220ZK 范围证明验证(zkatdlog 同态承诺)panic
↳ ApplicationMetadata:223-230issue / meta metadata 字段合法panic
audit.Audit():232-242解密 inputs/outputs 明文金额、owner、typeerror
av_match_tx:284-290tx_selector SDK 按 marked 类型匹配规则error
av_prefetch:308-313并发预查 sender + recipient 余额(max 8 并发)warn 继续
av_rules:315-5057 条拒绝规则(见 §6logRejection + error,标 auditOutcomeRejectedLimit
av_revocation:524-555inputs/outputs 的 RevocationHandle 不在撤销缓存同上,标 auditOutcomeRejectedRevoke
av_approve:560-572ttx.NewAuditApproveView(w, tx).Call() 签 sigma 并发回error,标 auditOutcomeRejectedValidation

撤销缓存是 Token SDK 的 RevocationHandle,不是 X.509 CRL

来源:cbdc-biz/pkg/common/revocation/cache.go:1-33。读取 FSC KVS 前缀 revocationList,每 30 秒刷新。与 fabric-ca 签发的 X.509 CRL 是两套机制。

步骤 15 — AuditApproveView.signAndSendBack

来源:fabric-token-sdk/token/services/ttx/auditor.go:374-403

:377-385  aid     = a.w.GetAuditorIdentity()
          signer  = a.w.GetSigner(ctx, aid)
:386      raw     = a.tx.MarshallToAudit()      // 待签字节
:392      sigma   = signer.Sign(raw)            // 走 BCCSP PKCS#11 → sign-kms
:398      session.SendTyped(ctx, &SignaturePayload{Signature: sigma}, TypeSignature)

签名落点(在 Bank 侧组装时)TokenRequest.AuditorSignatures[*].Signaturefabric-token-sdk/token/request.go:1113-1118,由 AddAuditorSignature 追加)。

步骤 16–18 — sigma 经 Endorser 回到 Bank

纯网络传输,无校验。

步骤 19 — Bank verifyAuditorSignature

来源:cbdc-biz/pkg/views/endorsement.go:69-81 调本地 verifyAuditorSignatureendorsement.go:102-122,逻辑等价于 SDK fabric-token-sdk/token/services/ttx/auditor.go:287-318,但为本地复刻、非调用)。

逻辑:遍历 TMS PublicParameters 的 Auditors(),逐个用对应 AuditorVerifierVerify(MarshallToAudit, sigma),匹配上的 auditor identity 由 endorsement.go:76 AddAuditorSignature 写入 TokenRequest.AuditorSignatures

这一步是 Bank 在本地验证 Auditor 的签名,不发到任何节点。

4. 步骤 20–24:Owner 签名 + Endorser 9 道闸门

步骤 20 — CollectEndorsementsView(WithSkipAuditing)

来源:fabric-token-sdk/token/services/ttx/collectendorsements.go:95-160Call)。

跳过审计(因为已在步骤 12-19 完成),直接进入 owner 签名 + chaincode endorsement 阶段。

步骤 21 — Owner 签名

来源:collectendorsements.go:95-160(Call 调度)+ :200-322requestSignatures 实际签名分支)。

行号段分支签名方式
:105issue signersrequestSignaturesOnIssues,Transfer 路径下空)
:110transfer signersrequestSignaturesOnTransfers,全体 sender wallet identity)见下
:117tx.TokenRequest.SetSignatures(mergeSigmas(...))落到 TokenRequest.Signatures[]request.go:1121-1138

签名分支(requestSignatures:200-322,按代码内顺序逐 case 命中):

类型行号实际 signer
MultiSig 身份:225-245递归 + multisig.JoinSignatures()
Policy 身份(OR/AND/N-of-M):247-272递归 + boolpolicy.JoinSignatures()
外部 signer(wallet 存在且已注册 ExternalWalletSigner先于本地 signer 探测:276-296signExternal()externalWalletSigner.Sign(party, requestRaw)(即 SubmitSignatureView 注入的 PendingTx 中带的 base64 signature)
本地 signer(GetSigner() 成功):298-310signLocal()signer.Sign(requestRaw) —— 走 BCCSP PKCS#11 → sign-kms
远程 signer:312-320signRemote():FSC session → SignatureRequest → 收 sigma

Bank 实际走"外部 signer"分支

SubmitSignatureView 把 client 在步骤 6 用外部钱包做的签名注入到 ExternalWalletSigner,Token SDK :276-296signExternal)直接取用而不调 BCCSP 再次签名。这意味着 sender 长期私钥不必在 sign-kms 内(Bank 侧密钥由调用方自行托管)。

外部 signer 分支:PreSubmittedSignatureSigner.Sign 的 4 步本地验签

来源:cbdc-biz/pkg/common/wallet_common/pre_submitted_signature_signer.go:71-127

这是 Token SDK 调 externalWalletSigner.Sign(party, message) 时实际执行的代码 —— 它不真去签(签名已在步骤 6 由调用方做好),只做自验然后把签名以 ASN.1 DER 形式回传给 Token SDK:

子步骤行为
1. PEM 解 cert:81-86pem.Decode(s.signerCertificate),失败直接 fmt.Errorf("failed to decode stored certificate PEM")
2. 解析 X.509:87-90x509.ParseCertificate(block.Bytes)
3. 签名格式转换:107-115convertIEEEP1363ToASN1DER —— Web Crypto API 的 r‖s 64 字节转 ASN.1 DER;同时强制 Low-S:48-54,防 ECDSA 延展性)。非 64 字节直接原样透传(:38-40
4. 验签:118-121cert.CheckSignature(cert.SignatureAlgorithm, message, derSignature);失败 → fmt.Errorf("X.509 signature verification failed: %w", err)

错误传播链:

PreSubmittedSignatureSigner.Sign → error
  └─ CollectEndorsementsView.requestSignatures → signExternal (collectendorsements.go:276-296)
       └─ AuditAndEndorseView.Phase3 (endorsement.go:84-95)
            └─ SubmitSignatureView.Call:710 "failed to collect endorsements"
                 └─ biz releasePendingTx (UnlockAll selector)
                      └─ service 返回 5xx,整笔 tx 终止

消息一致性是"软告警",不阻断

:97-102message != s.messageToSign(Token SDK 现在传入的字节 ≠ prepare 阶段返回给客户端的 base64 解码字节)只 logger.Warnf仍用 Token SDK 当前传入的 message 完成验签:118 第二参数)。也就是说"client 在步骤 6 究竟签了哪段数据"由代码运行时实际传入决定,与 prepare 阶段返回的 MessageToSign 不强绑定。

这是 Bank 自验自,不是独立第三方

私钥在调用方银行手里、cert 是它自己回传的(PendingTx.SignerIdentity)、验签在同一 Bank 节点跑。意义只在 fail-fast —— 避免把烂签名扔到 Auditor / Endorser / Committer 再被拒。owner sig 首次被独立第三方验证仍发生在 步骤 14(Auditor)

仅支持 ECDSA P-256

convertIEEEP1363ToASN1DER:48 写死 elliptic.P256(),且 64 字节才进 r/s 拆分分支。其它曲线 / 算法走 :38-40 早返回,是否能过最终的 cert.CheckSignature 取决于 cert 自带的 SignatureAlgorithm 与原始签名格式是否吻合。

步骤 22 — requestApproval

来源:fabric-token-sdk/token/services/ttx/collectendorsements.go:396-417requestApproval)。

go
requestRaw, _ := c.tx.TokenRequest.RequestToBytes()
env, err := network.GetInstance(ctx, c.tx.Network(), c.tx.Channel()).
    RequestApproval(ctx, c.tx.TokenRequest.TokenService, requestRaw,
                    c.tx.Signer, c.tx.TxID, c.Opts.ApprovalMetadata)
c.tx.Envelope = env

实际通过 FSC P2P 连接 endorser1 节点的 RequestApprovalView

步骤 23 — Endorser 端 9 道闸门

Endorser 在这条路径上是真签名者,不是转发器

触发自动注册的条件:cbdc-biz/conf/endorser1/core.yaml:142-143 fsc_endorsement.endorser: true

注册位置:fabric-token-sdk/token/services/network/fabric/endorsement/fsc/service.go:56-73,当 AmIAnEndorserKey= services.network.fabric.fsc_endorsement.endorser)为 true 时 Token SDK 自动注册 RequestApprovalResponderView

入口:fabric-token-sdk/token/services/network/fabric/endorsement/fsc/responder.go:72-106(Call)。

9 个连续校验点:

#闸门代码检查内容失败错误
1接收 + transient 字段数receive():108-123TX bytes + transient = 2 或 3(tmsID + token_request ± approval_metadata)ErrReceivedProposal / ErrInvalidTransient
2TMSID 解析receive():125-138TMSID 各字段非空 + TMS 存在同上
3RWS 完整性receive():159-173proposal RWS 的 namespaces 为空(防止 RWS 注入)ErrInvalidProposal
4链码字段receive():175-184名 = TMSID.Namespace,版本 "1.0",函数 "invoke",无参数同上
5Proposal 完整性validateProposal():206-233creator/proposalBytes/signature/header/payload 均非空ErrValidateProposal
6Creator MSP 合法性validateProposal():242-251mspManager.IsValid(creator) + acl.CheckACL()同上
7Proposal 签名验证validateProposal():254-262verifier.Verify(proposalBytes, signature)同上
8Token 业务校验validate():293-329validator.UnmarshallAndVerifyWithMetadata():ZK 范围证明、owner 签名、auditor σ、metadata 一致同上
9RWS 翻译 + endorseendorse():332-355把每个 token action 翻译进 RWS(隐式检测 key 冲突);然后 endorserService.Endorse(ctx, tx, endorserID) 用 endorser FSC node 私钥签 proposal endorsementErrEndorseProposal

步骤 24 — proposal endorsement 写入 envelope

responder.go:348endorse() 用 endorser FSC node 私钥(PKCS#11 → sign-kms KMS_TK partition)对 Fabric proposal hash 签名,写入 tx.Envelope.Endorsements[] 标准字段。

至此 envelope 内含三类签名:

TokenRequest.Signatures[]            ← sender (owner)            步骤 21
TokenRequest.AuditorSignatures[*]    ← auditor                  步骤 19
Envelope.Endorsements[]              ← endorser FSC node         步骤 24

5. 步骤 25–35:分发收方 + Orderer + Committer

步骤 25–26 — 分发 + 收款方 finality

来源:fabric-token-sdk/token/services/ttx/collectendorsements.go:141(Call 内调 distributeTxToParties,函数定义 :469)。

收款方 AcceptTransferView.Callcbdc-biz/pkg/views/accept_transfer.go:37-72):

行为
:43RespondRequestRecipientIdentityUsingWallet()(步骤 4 已完成 identity 解析,这里是后续)
:46-48ttx.ReceiveTransaction() 接收已签的 tx
:50-57校验 outputs 列表含本身
:64-65ttx.NewAcceptView() 接受(若收方策略要求收方签名也走这里)
:67-69ttx.NewFinalityView() 异步等 finality

步骤 27 — NewOrderingViewBroadcast

来源:

  • fabric-token-sdk/token/services/ttx/ordering.go:27-92NewOrderingView :27 + orderingView.Call :40,broadcast/Broadcast 在 :74-82;Transfer 步骤 27 调的是 ttx.NewOrderingView(tx),见 transfer.go:725。Issue 链路改走 NewOrderingAndFinalityView :95-118)
  • fabric-smart-client/platform/fabric/core/generic/ordering/cft.go:45-99client.go:25-50

实际 gRPC:orderer.AtomicBroadcast/Broadcast(标准 Fabric 流式接口)— Stream.Send(env *common.Envelope)

mTLS 强制:客户端必须出示 cert。

步骤 28 — Orderer Router 的 3 道过滤

来源:fabric-x-orderer/node/router/router.go:577-583, 349-394fabric-x-orderer/common/requestfilter/sigfilter.go:35-53

go
rv := requestfilter.NewRulesVerifier(nil)
rv.AddRule(requestfilter.PayloadNotEmptyRule{})
rv.AddRule(requestfilter.NewMaxSizeFilter(config))
rv.AddStructureRule(requestfilter.NewSigFilter(config, policies.ChannelWriters))
过滤器检查失败
PayloadNotEmptyRuleenvelope.Payload 非空拒绝
MaxSizeFiltersize ≤ AbsoluteMaxBytes拒绝
SigFilter仅当 clientSignatureVerificationRequired=true header type 是 CONFIG_UPDATE 时,按 policies.ChannelWriters 策略验证 creator 签名拒绝

SigFilter 默认关

fabric-x-orderer/node/config/config.go:92 ClientSignatureVerificationRequired bool 字段由 YAML 配置决定,无硬编码默认值fabric-x-orderer/test/utils/utils.go:147 中测试 fixture 显示默认 false

业务交易在默认配置下只过 size 过滤 + mTLS,签名层校验全靠下游 Committer Verifier(步骤 33)。

步骤 29 — Batcher → Consenter(BFT) → Assembler

  • Batcherfabric-x-orderer/node/batcher/):将 router 转来的请求聚合成 batch;不验签
  • Consenter(SmartBFT 共识):对 batch 的 digest / hash 达成 BFT 共识;不验 batch 内 tx 内容
  • Assembler:将达成共识的 batch 拼成 Fabric Block 结构,推送给下游 deliver stream。

步骤 30 — Sidecar deliver

来源:fabric-x-committer/service/sidecar/sidecar.go:62-144relay.go:70-118

  • :73-74 建 deliver 客户端拉 orderer block
  • :116, 140 通过 servicepb.CoordinatorClient.BlockProcessing() 双向流将 block 推给 Coordinator,并接 status 回传

步骤 31–32 — Coordinator + DependencyGraph

来源:fabric-x-committer/service/coordinator/coordinator.go:38-94service/coordinator/dependencygraph/

4 个内部通道(:67-93):

coordinatorToDepGraphTxs

depGraphToSigVerifierFreeTxs       ← 经依赖分析无冲突的 tx

sigVerifierToVCServiceValidatedTxs ← 经 Verifier 通过

vcServiceToCoordinatorTxStatus     ← 来自 VC 的最终状态

依赖分析(dependency_detector.go:9-150)按 read-only / write-only / read-write 三表追踪 RW 冲突(R-W、W-R、W-W)。依赖无关交易并发送验签。

步骤 33 — Verifier 按 namespace 验签

来源:fabric-x-committer/service/verifier/verify.go:125-167fabric-x-committer/utils/signature/verify.go:24-78

go
for _, ns := range tx.Namespaces {
    if ns.NsId == ConfigNamespaceID { continue }       // configtx 由 orderer 负责
    nsVerifier, ok := verifiers[ns.NsId]
    if !ok { return ABORTED_SIGNATURE_INVALID }
    nsVerifier.VerifyNs(tx.Ref.TxId, tx.Content, nsIndex)
}

NsVerifier 三种实现(utils/signature/verify.go:42-78):

类型用途校验对象
ThresholdRule (keyVerifier + ECDSA/BLS/EdDSA)单公钥,1 签即过namespace 默认建立时声明的 single pubkey;本部署最常见
MspRule (mspVerifier)Fabric DSL OR/AND/OutOf(...)多组织 endorsement 策略
ChannelPolicy (channelPolicyVerifier)/Channel/Application/LifecycleEndorsement仅 meta-namespace 配置变更

策略按 namespace 各持一份,通过写 meta-namespace 的事务热更新(VerifierUpdates broadcast,atomic pointer 替换)。

步骤 34 — VC(Validator-Committer)

来源:fabric-x-committer/service/vc/validator.go:22-127service/vc/committer.go:94-181

子步骤行号检查
Validatorvalidator.go:84-127validate(),:102 调 validateReads :129-149)按 namespace 检查 read conflict(MVCC)→ 冲突经 invalidateTxsOnReadConflictsStatus_ABORTED_MVCC_CONFLICT(:175)
Committer preparecommitter.go:100populateVersionsAndCategorizeBlindWrites
Committer commitcommitter.go:135db.commit 写 update writes / new writes;DB 层检测重复 tx ID + read conflict(:155-158
重试committer.go:125-159冲突重试循环
状态committer.go:165-181有效 = Status_COMMITTED;其他失败码(MVCC / 签名 / 依赖等)

步骤 35–36 — Finality 回传

来源:

  • fabric-x-committer/service/sidecar/relay.go:86-115 接 status batch
  • fabric-x-committer/service/sidecar/notify.go:71-126, 134, 219 维护订阅 + 推送

Bank 侧 ttx.NewFinalityView()OrderingAndFinalityView 里订阅该 txID 的状态,收到 COMMITTED 后 Call 返回,整笔交易闭环。

6. AuditView 的 7 条拒绝规则

来源:cbdc-biz/pkg/auditor/rejection/types.go:1-28cbdc-biz/pkg/views/audit.go:398-502, 524-554

#类型常量触发代码行
1SENDER_BALANCE_BELOW_MINsender 扣款后余额 < 规则最小值audit.go:404-415
2SENDER_NET_RECEIVEDsender 净收入 > 规则最大值(边界 check):417-428
3TRANSFER_ABOVE_MAX转账金额 > 规则最大值:437-448
4TRANSFER_BELOW_MIN转账金额 < 规则最小值:450-461
5RECIPIENT_BALANCE_ABOVE_MAX收方收账后余额 > 规则最大值:478-489
6RECIPIENT_BALANCE_BELOW_MIN收方收账后余额 < 规则最小值:491-502
7IDENTITY_REVOKEDinputs 或 outputs 的 RevocationHandle 在撤销缓存:524-554

拒绝日志落地链路:

AuditView.logRejection (audit.go:149-153)
  → auditorbiz.LogRejection (app/auditor/internal/biz/auditlog_biz.go:23-24)
    → auditlog 异步队列
      → bizhubRejectionSender.SendRejectionLogs (auditlog_biz.go:30-52)
        → bizhubv1.InsertRejectionLogs (gRPC)
          → BizHub PG audit_rejection_log 表

6.5 发起者(Owner)签名在哪几步被验证

Owner sig 由 sender 在链外步骤 6 用外部钱包对 MessageToSign 做 ECDSA P-256 签名,在步骤 21 的 external signer 分支被 Token SDK 取用并写入 TokenRequest.Signatures[]。它在整条链路上被验证 4 次(第 ⓪ 次是 Bank 自验自,①–③ 才是独立第三方),越往后越严格。

#验证点触发位置实际验证内容失败时
⓪ Bank 本地 fail-fast步骤 21(external signer 分支内)cbdc-biz/pkg/common/wallet_common/pre_submitted_signature_signer.go:71-127,被 fabric-token-sdk/token/services/ttx/collectendorsements.go:276-296signExternal)调用pem.Decodex509.ParseCertificate → IEEE P1363→ASN.1 DER(强制 Low-S,:48-54) → cert.CheckSignature(cert.SignatureAlgorithm, message, derSignature)。算法仅支持 ECDSA P-256(:48 elliptic.P256()error 返回 → AuditAndEndorseView"failed to collect endorsements" → biz releasePendingTx(UnlockAll)→ service 5xx
① Auditor 端 audit.Validate步骤 14cbdc-biz/pkg/views/audit.go:220 auditor.Validate(tx)fabric-token-sdk/token/services/auditor/auditor.go:111-112Validaterequest.AuditCheckAuditCheck 定义 token/request.go:1250)→ request.IsValid:1019)→ inputsAndOutputs(verifyActions=true):918,verifyActions 分支 :950-951 / :986-987) → 每个 Issue 调 VerifyIssue,每个 Transfer 调 VerifyTransfer。zkatdlog 驱动下,VerifyTransfer 同时校验 ZK 范围证明、token 承诺、所有 owner sigAuditor 直接 panic(assert.NoError at audit.go:220),sigma 不返回 → Bank 收不到审计签名 → 整笔交易终止
② Endorser 9 闸门 #8步骤 23fabric-token-sdk/token/services/network/fabric/endorsement/fsc/responder.go:293-329 r.validate(...) 内调 validator.UnmarshallAndVerifyWithMetadata()同样的 token-level 校验(ZK + owner sig + auditor σ + metadata 一致性);这一次连步骤 19 写入的 auditor σ 也一起验返回 ErrValidateProposal → 不出 proposal endorsement → Bank 拿不到 Envelope.Endorsements[] → 无法 Broadcast
③ Committer Verifier步骤 33fabric-x-committer/service/verifier/verify.go:125-167tx.Namespaces[i].NsIdverifiers[NsId]nsVerifier.VerifyNs(...)utils/signature/verify.go:42-78NsVerifier:根据 namespace policy 类型用 ThresholdRule(单 pubkey)/ MspRule(Fabric DSL)/ ChannelPolicy 评估提交进来的签名集合Status_ABORTED_SIGNATURE_INVALIDverify.go:146, 162),不入账

四层校验的语义差别

  • 步是 Bank 自验自(私钥、cert、验签进程都在调用方 Bank 侧),仅有 fail-fast 作用,不构成治理意义上的"被验"。
  • ①、② 步是 Token SDK 内部的 token 业务层校验(ZK + 单笔 action 的 owner sig 是否符合 zkatdlog 协议),与 namespace policy 无关。
  • 步是 Fabric-X namespace 级 policy 校验,决定"这套签名集合是否满足该 namespace 创建时声明的策略"(例如某 namespace 要求 OR('Org1.member','Org2.member'))。

①② 保证密码学正确性,③ 保证治理策略合规。任一层失败都阻断落账。

Bank 不"独立"验 owner sig,Orderer 默认也不验

  • Bank 在步骤 19 做的是 verifyAuditorSignature(验 auditor 回的 sigma),对 owner sig 再做一次。
  • 步骤 21 子步骤 ⓪ 的 PreSubmittedSignatureSigner.Sign 是"自验自",不算独立验证。
  • Router SigFilter(步骤 28)只有在 ClientSignatureVerificationRequired=true 时才验 envelope 外层 creator 签名(属 mTLS 之上的 Fabric SignatureHeader 校验),与 TokenRequest 内 owner sig 是两回事。本仓库无显式开启配置([未核])。
  • 收款方 AcceptTransferViewttx.NewAcceptView,默认不复验 owner sig。

owner sig 首次被任何"独立第三方"验,是在 Auditor 端的步骤 14;第二次在 Endorser 9 闸门 #8(步骤 23);决定能否落账的最后一次在 Committer Verifier(步骤 33)

7. 整笔 Transfer 的签名集合(汇总)

签名签名方私钥位置数据落点字段何时生成在哪里被验
外部 API 签名调用方钱包Bank 自托管PrepareTransferResult.MessageToSignSubmitTransfer 参数步骤 6Token SDK external signer 分支(步骤 21)
Auditor σAuditor walletsign-kms partitiontx.MarshallToAudit()TokenRequest.AuditorSignatures[*]步骤 15Bank 本地 verifyAuditorSignature(步骤 19)+ Endorser 9 闸门 #8(步骤 23)+ Committer Verifier(步骤 33)
Owner sigsender wallet identityexternal(Bank 自托管)TokenRequest.MarshalToSign() 字节TokenRequest.Signatures[]步骤 21(外部 signer 注入,链外步骤 6 生成)4 次(含 1 次自验):⓪ Bank 本地 PreSubmittedSignatureSigner.Sign(步骤 21 子步骤,自验自)→ ① Auditor audit.Validate(步骤 14)→ ② Endorser 9 闸门 #8(步骤 23)→ ③ Committer Verifier 按 namespace policy(步骤 33)。详见 §6.5
Institution proposal 签名institution FSC 节点身份sign-kms KMS_TK partitionFabric proposalSignedProposal.Signature步骤 22 起始:RequestApprovalViewtx.EndorseProposal()fabric-token-sdk token/services/network/fabric/endorsement/fsc/initiator.go:77 → fabric-smart-client core/generic/transaction/transasction.go:490-503sigService.GetSigner(Creator)proposal 解包校验 creator.Verify(ProposalBytes, Signature)(fabric-smart-client core/generic/transaction/proposal.go:216
Endorser proposal endorsementendorser FSC nodesign-kms KMS_TK partitionFabric proposal hashEnvelope.Endorsements[]步骤 24Committer Verifier 按 namespace 策略(步骤 33)
Institution envelope 信封签名institution FSC 节点身份(与 proposal 同一把 key,两次独立 Signsign-kms KMS_TK partitionpayload(header + 交易)Envelope.Signature步骤 24→27 之间:tx.Envelope()fsc/initiator.go:106 → fabric-smart-client core/generic/transaction/envelope.go CreateEndorserSignedTX,signer = Creator)默认链路无强制验证点(Router SigFilter 默认关,见下行)
mTLS client certBank TLS cert节点本地或 KMSTLS 握手随机数TLS layergRPC connectOrderer Router mTLS(步骤 27)
可选 Router SigFilterEnvelope creator同上protoutil.SignedData{}步骤 27 envelope.SignatureHeaderpolicies.ChannelWriters(步骤 28,默认关

实际有效身份门只有 3 个

  • Auditor 业务规则(步骤 14)
  • Endorser 9 道闸门(步骤 23)
  • Committer namespace 验签 + VC(步骤 33-34)

mTLS 只能挡证书不在受信 CA 内,不挡撤销、不识别业务身份;Router SigFilter 默认关。详见 02 信任与身份模型

KMS 调用总账(每笔 Transfer = 4 次 PKCS#11 Sign)

上表中经 sign-kms 的签名共 4 次/笔:institution 节点 2 次(proposal + envelope,同一把 key 两次独立 Sign)、endorser 1 次、auditor σ 1 次;owner 签名外部注入、不占 KMS。Issue 为 issuer 钱包 + 节点 2 次 + endorser + auditor = 5 次 KMS;Redeem 在 Transfer 基础上另加 issuer 背书(pkg/views/redeem.go:149ttx.NewEndorseView)。KMS 容量含义见 08 扩容指南 · HSM 需要几台

8. View 注册与 FSC 拓扑

来源:各 cmd/<app>/main.go 中的 viewregistry.GetRegistry(node).RegisterResponder(...) + Token SDK 自动注册。

节点显式注册的 ResponderToken SDK 自动注册出处
Institution-001AcceptTransferView(← TransferView)、AcceptIdentityRequestView(← PrepareTransferView)、AcceptPreparedTransferView(← SubmitSignatureView)、AcceptCashView(← IssueCashViewcbdc-biz/app/institution/cmd/institution/main.go:210-220
AuditorAuditView(← RequestAuditViewcbdc-biz/app/auditor/cmd/auditor/main.go:202-209
EndorserAuditResponderView(← AuditAndEndorseViewRequestApprovalResponderView(← RequestApprovalView)— service.go:56-73fsc_endorsement.endorser=truecbdc-biz/app/endorser/cmd/endorser/main.go:107-110 + conf/endorser1/core.yaml:142-143
IssuerIssuerRedeemAcceptView(← RedeemView / SubmitSignatureViewcbdc-biz/app/issuer/cmd/issuer/main.go:133-135

pkg/views/ 文件清单:transfer.go / accept_transfer.go / issue.go / redeem.go / audit.go / endorsement.go / finality_registry.go / token_optimization.go / audit_metrics.go / warmup.go

9. Issue · 发行 的差异

来源:pkg/views/issue.gocbdc-biz/app/bizhub/internal/biz/settlement_biz.go

与 Transfer 的差异:

维度TransferIssue
发起方私钥sender wallet(机构钱包)Issuer wallet
Owner sig 分支transfer signers(collectendorsements.go:110issue signers:105),用 IssuerVerifier
Token Selector必须选币(已有 UTXO)不需要(无输入)
ZK 范围证明inputs + outputs 都验仅 outputs
RTGS 联动无(除非 Bank 主动)由 BizHub SettlementPendingWorkerProcessIssuanceAsync 触发;成功后写 settlement_request 状态
Auditor 校验规则7 条全过同(sender 余额 / sender net 这两条对 Issue 不适用,但代码仍走规则树)

Issue 在 BizHub 侧的状态机

来源:cbdc-biz/app/bizhub/internal/data/ent/schema/settlement_request.goworker/settlement_*.go

PENDING ─(pending worker poll 5s, conc 10)─> PROCESSING ─(success)─> COMPLETED
   │                                              │
   ├─(retry worker, retry_count < 3)              ├─(timeout 5min)─> FAILED + RETRYABLE (PROCESSING_TIMEOUT, retry_count+1)
   │                                              └─(error type)
   └─(error_type=UNRECOVERABLE)─> FAILED ─(compensate)─> COMPENSATION_COMPLETED

3 个 worker:

Worker默认参数行为出处
SettlementPendingWorkerpoll 5s / conc 10 / batch 100status=PENDINGProcessIssuanceAsync / ProcessRedemptionAsyncworker/settlement_pending_worker.go:20-55
SettlementRetryWorkerpoll 5s / conc 10 / batch 100retriable 且 retry_count < maxRetries(3) → 重跑worker/settlement_retry_worker.go:21-58
SettlementTimeoutWorkercheck 30s / batch 50 / 串行status=PROCESSINGupdated_at > 5minFAILEDerror_code=PROCESSING_TIMEOUTerror_type=RETRYABLEretry_count+1repo.FetchAndMarkTimedOutRequestsdata/settlement_repo.go:554-603);worker 自身仅打 warn 日志worker/settlement_timeout_worker.go:19-55

error_typetypes/settlement.go:38-40,3 个):RETRYABLE / UNRECOVERABLE / MANUAL_REVIEWerror_codetypes/settlement.go:45-51,7 个):RTGS_NAK / RTGS_STATE_UNKNOWN / REMINT_FAILED / RTGS_UNAVAILABLE / SUBMIT_REDEEM_FAILED / INVALID_STATE / RTGS_SETTLED_DB_FAILURE

10. Redeem · 赎回 的差异

来源:pkg/views/redeem.go、BizHub Settlement worker 同上、cbdc-biz/pkg/rtgs/

链上侧与 Transfer 同链路(步骤 10-36),差异:

维度TransferRedeem
Token SDK 动作tx.Transfer()tx.Redeem()(销毁 token,无输出 owner)
Issuer 是否签名是(Issuer 是 redeem 的接收/销毁方,需 issuer sig)
Auditor 校验7 条规则全同(part 适用)
链下结算BizHub 侧把对应金额写 RTGS MT298 → SMB 共享目录 → 等回执

RTGS 模块(链下结算)

来源:cbdc-biz/pkg/rtgs/

文件清单:

文件用途
client.goRTGS 客户端入口
client_smb.goSMB 协议适配
smb_client.go / smb_pool.goSMB 连接池
config.go配置 + PKCS#11 keystore
crypto.goMT298 报文签名 + 验证
mt298/SWIFT MT298 编解码

BizHub core.yaml:79-112 实际配置(节选):

yaml
rtgs:
  request_signing_enabled: true
  response_verify_enabled: true
  swift:
    sender_bic: '5520010001XX'                       # Platform BIC (MT298 sender)
    settlement_payment_code: '66669999'              # 发行结算 (SMT201)
    reservation_payment_code: '90000005'             # 基金预留 (SMT211)
    redemption_settlement_payment_code: '66669999'   # 赎回结算 (SMT201)
  smb:
    host: 'host.docker.internal'                     # 本地 rtgs-mock
    port: 445
  keystore:
    type: jks                                        # "pkcs12" or "jks"
    path: ./keystore/test.jks

本地用 cbdc-biz/cmd/rtgs-mock/main.go 模拟央行 RTGS(compose-rtgs-mock.yml)。

11. 跨链路角色总图

12. [未核]

  • 两步签名 TransferPrepareTransferView + SubmitSignatureViewAcceptIdentityRequestView + AcceptPreparedTransferView 的两对 View)的具体业务触发条件 —— 需读 pkg/views/ 中两 Initiator 的实现细节。
  • fsc.persistences.default.opts 中是否 leak owner 私钥:Token SDK 在某些 BCCSP 配置下会把临时 ephemeral key 落本地 keystore,是否对 owner 长期身份生效需要进一步比对。
  • MetaNamespace policy 在 PG 表的 schemafabric-x-committer/service/vc/database.gopolicies 表的具体字段未详查。
  • Recipient 是否对 Transfer 签名(双签 / 单签):AcceptTransferView:64-65ttx.NewAcceptView(),但 NewAcceptView 内部是否对 tx sign 取决于 wallet 策略,本仓库默认 [未核]。
  • Auditor audit_rejection_log 表 schemacbdc-biz/app/bizhub/internal/data/ent/schema/audit_rejection_log.go 字段详情。
  • Router clientSignatureVerificationRequired 在生产 KMS 部署下的实际取值:取决于 cbdc-network/configs/test-full-kms*.yaml 渲染后注入到 orderer config 的字段,未在仓库现状中找到显式 true 配置。