PRODUCT · BLOCKCHAIN · WALLET CUSTODY

1U夺宝的钱包与链上资金架构What the product does, why blockchain is involved, and how wallets are separated

1U夺宝是一个用 USDT 参与的夺宝/抽奖产品:用户充值、购票,活动售罄后开奖,中奖者领取实物或 USDT 奖励。 区块链在这里不是全部业务系统,而是负责资金进出、可验证开奖、链上签名与资金托管边界。

产品先说清楚

这不是一个“纯链上游戏”,而是一个平台托管的夺宝产品。链上能力服务于资金、开奖可信度和审计边界。

PRODUCT CONTEXT

1U夺宝做什么

用户把 USDT 充值到平台分配的链上地址,在平台内用余额购票参与活动。活动售罄或到达开奖条件后,系统按 VRF_ONLY 模式开奖,并把奖品履约、USDT 派奖、退款或提现继续走平台账本和链上执行。

01 DEPOSIT充值

用户从交易所或外部钱包转 USDT 到自己的托管充值地址。

02 BUY购票

余额进入平台账本后,用户用 1U 单价参与活动。

03 DRAW开奖

活动售罄后按 VRF_ONLY 模式开奖。

04 PAYOUT派奖 / 提现

中奖、退款和提现在账本里确认,再按需要发起链上出款。

产品账本和链上资金不是一回事

用户看到的可用余额来自 wallet-service 的账本;链上 USDT 只是资金真实流入、归集和提现的外部结算层。账本负责用户体验和幂等,链上负责资产最终转移和公开可验证记录。

因此,系统必须同时管理两层事实:平台内部余额,以及链上地址、交易、确认数和钱包分层。

区块链在产品里的作用

区块链不是替代所有后端服务;它负责外部资产流转、开奖可信增强和资金托管安全边界。

BLOCKCHAIN ROLE
ROLE 01

收款与提现结算

用户充值 USDT 到独立 HD 地址;提现审核通过后从平台 Payout wallet 出款到用户外部地址。

ROLE 02

开奖可信度

产品口径采用 VRF_ONLY:随机数来源走 VRF,业务账本和履约仍由后端服务承接。

ROLE 03

资金托管隔离

合约部署、运行调用、HD 充值、Gas、Collection、Payout、Operations Hot 和 Cold Wallet 分开控制。

CHAINS

支持链与执行网络

ETH
ETH_LOCALETH_SEPOLIAETH_MAINNET
BSC
BSC_LOCALBSC_TESTNETBSC_MAINNET
TRON
TRON_LOCALTRON_TESTNETTRON_MAINNET
ASSETS

资产边界

USDT

v1.0 主资产,用于充值、购票余额、派奖和提现。

Native

ETH / BNB / TRX 只作为 gas 或 energy 成本,不作为用户主余额。

NOT CHAIN

不放到链上的部分

登录、活动配置、用户余额账本、订单状态、风控审核、客服与运营后台仍由后端服务和数据库承担。

我们需要几个钱包

按生产安全边界,每条链、每个环境都要拆分钱包职责。不要用一把私钥贯穿部署、充值、归集、提现和冷储备。

WALLET BASELINE

最小基线

每个 env × chain 至少准备 8 类业务钱包角色。平台资金钱包登记使用 COLLECTION / HOT / WARM / COLD;其中 HOT 通过 label/SOP 区分 Payout Hot 和 Operations Hot。

8类必备钱包角色 / per chain per env
5+类签名能力 / HD Wrap、Gas、Runtime、Payout、Owner
01 OWNER合约部署钱包

部署合约、初始化参数、授予/撤销角色;部署后高权限迁 Safe/Timelock。

02 RUNTIME合约调用钱包

后端运行时调用合约、VRF request 和链上操作;不能复用 deployer。

03 HD用户充值钱包

派生每个用户每条链的充值地址;归集时用 child key 签名。

04 GASGas Wallet

给用户充值地址补 native gas;TRON 还要处理 energy/bandwidth。

05 COLLECTION归集钱包

接收用户充值地址 sweep 来的 USDT;不直接给用户出款。

06 PAYOUT出款钱包

用户提现审核通过后从这里出款;可以 1 个或 N 个。

07 OPS HOT运营热钱包

保存少量平台运营流动性,给 Payout 补库存、收 Collection 调拨、转利润。

08 COLD冷钱包

老板/财务长期储备;离线/硬件/多签保管,不接普通后台自动出金。

每个钱包具体干什么

这部分再进入细节:谁使用、控制方式、主要动作和禁止动作。平台资金钱包登记使用 COLLECTION / HOT / WARM / COLD 作为资金层级,业务角色靠 label、keyId 和 SOP 区分。

WALLET DETAILS
01 · CONTRACT DEPLOYER

合约部署 / Owner 钱包

负责部署合约、初始化参数、授予/撤销角色和 pause/unpause,不参与日常充值、归集或用户提现。

used by合约部署负责人、SRE/Security。
controlOwner KMS 或 Safe/Timelock;无多签时用双人审批 SOP。
  • 部署后高权限迁 Safe/Timelock。
  • 禁止和出款钱包共用。
  • 不要长期由开发个人钱包控制。
02 · CONTRACT RUNTIME CALLER

合约运行调用钱包

后端运行时调用合约方法、VRF request 和链上操作,用 runtime operator key 签名。

used bychain-service 通过 sign-service 调 KMS。
controlRuntime Operator KMS;真实 KMS ARN 只在 sign-service 配置里。
  • 只做运行时链上调用。
  • 不得复用 deployer 私钥。
  • 启动时校验 operator-address。
03 · HD DEPOSIT WALLET

用户充值地址池

一套 HD seed 派生大量用户充值地址,不为每个用户单独创建 KMS key。

used bywallet-service 派发地址;chain-service 触发归集;sign-service 派生/签名。
controlHD Wrap KMS 加密 seed;明文只在 sign-service 内存中短暂使用。
  • 用于收用户充值 USDT。
  • 归集时用对应 child key 签名。
  • 禁止把 seed/xprv 写入 DB、Nacos 或日志。
04 · GAS WALLET

补 gas 钱包

用户充值地址只有 USDT 时,不能自己支付链上转账成本,需要 gas wallet 补少量 native token 或 TRON energy。

used bychain-service 的 gas station / sweep planner。
controlGas KMS;只放有限 gas 额度。
  • EVM 给地址补 ETH/BNB。
  • TRON 处理 Energy/Bandwidth。
  • 不承载用户主资产。
05 · COLLECTION WALLET

归集钱包

用户 HD 地址里的 USDT 应归集到 Collection Wallet,再按 SOP 调拨到运营热钱包或出款钱包。

used bychain-service sweep;SRE/Finance treasury SOP。
controlWalletTier.COLLECTION;自动转出时用独立 KMS 或多签。
  • 接收 sweep 归集。
  • 不直接给用户出款。
  • 归集目标固定为 active Collection Wallet。
06 · PAYOUT WALLET

用户出款钱包

用户提现审核通过后,从 Payout Wallet 给用户外部地址出款。Treasury tier 使用 HOT,key label 使用 withdraw-hot-*

used bychain-service 构造交易;sign-service 用 Payout Hot KMS 签名。
controlPayout Hot KMS;1 个或 N 个,支持 active/standby 或额度分片。
  • 只保留日常提现额度。
  • 不接收用户充值地址归集。
  • 和 Operations Hot 不共用 key。
07 · OPERATIONS HOT WALLET

运营热钱包

保存少量平台运营资金和短期流动性:接收 Collection 调拨、给 Payout 补库存、向老板/财务转利润、前期接收运营拨款。

used bySRE / Finance 按 SOP 操作。
controlOperations Hot KMS 或人工多签;treasury tier 使用 HOT,label 使用 ops-hot-*
  • 不是长期储备。
  • 不直接处理用户提现申请。
  • 转出必须有审批和审计。
08 · COLD WALLET

长期储备冷钱包

Cold wallet 是老板/财务长期储备和大额资金保管层,必须离线/人工保管,不能被普通业务服务直接调用。

used by老板/财务 + 核心负责人 + Security 多人审批。
control离线/硬件钱包;使用 Safe 多签 + 硬件钱包。
  • 保存大部分平台储备。
  • 大额调拨走人工 SOP。
  • 禁止接入普通 admin 自动出金。

低层实现细节

上面先讲业务和钱包分工;这里再看运行路径:keyId 从配置进入 sign-service,KMS 只签 digest,HD 地址由 master seed 派生。

CODE PATH

1 · keyId 从配置来

chain-service 保存逻辑名,sign-service 才保存 KMS ARN。

chain.evm.BSC.operator-key-id = operator-hot-evm-bsc-1

sign.keys[0].id = operator-hot-evm-bsc-1
sign.keys[0].kms-key-arn = arn:aws:kms:...:key/...
sign.keys[0].chain-id = 56

2 · sign-service 解析 keyId

KmsSigner.resolveArn()duobao.sign.keys[] 查找;找不到就失败,不允许默认 key。

private String resolveArn(String keyId) {
  return props.keys().stream()
    .filter(k -> keyId.equals(k.id()))
    .findFirst()
    .map(KeyEntry::kmsKeyArn)
    .orElseThrow(SIGN_KEY_NOT_FOUND);
}

3 · KMS 签名

KmsSigner 传 32-byte digest,使用 MessageType.DIGEST,KMS 不再 hash。

SignResponse resp = kms.sign(SignRequest.builder()
  .keyId(kmsArn)
  .messageType(MessageType.DIGEST)
  .message(SdkBytes.fromByteArray(messageHash))
  .signingAlgorithm(ECDSA_SHA_256)
  .build());

(r, s) = decodeDer(resp.signature());
s = canonicalizeLowS(s);
v = recoverV(r, s, messageHash, kmsPublicKey);

4 · EVM 交易签名

EvmSignService 解 raw tx,算 EIP-155 / EIP-1559 preimage,再把签名装回 RLP。

RawTransaction tx = TransactionDecoder.decode(rawTxHex);
byte[] preimage = legacy
  ? TransactionEncoder.encode(tx, chainId)
  : TransactionEncoder.encode(tx);

byte[] digest = Hash.sha3(preimage);
SignatureData sig = signer.signEcdsa(keyId, digest);
String signedTx = hex(TransactionEncoder.encode(tx, sig));

5 · TRON 签名

TronSignService 只签 32-byte txId,返回 r + s + recId

byte[] txId = parseTxId(txIdHex);
SignatureData sig = signer.signEcdsa(keyId, txId);

signatureHex =
  hex(sig.r) + hex(sig.s) + tronRecoveryId(sig.v);

6 · HD 地址派生

AddressDeriveService 从 master seed 派生 EVM/TRON 用户充值地址。

master = seedProvider.getMasterSeedBytes();
seed = HMAC_SHA512(master, "duobao:hd:seed:v1");
priv = derive(seed, "m/44'/60'/<uid>'");
address = Keys.toChecksumAddress(pubkeyToAddress(priv));

return address, seedVersion, derivationPath, keyId;

区块链模块地图

这不是单独一个“钱包服务”能做完的事情;区块链能力被拆在账本、链上执行、签名、合约和运维配置里。

PROJECT BLOCKCHAIN MAP
LEDGER

wallet-service

负责

余额账本、充值入账、提现冻结、充值地址记录、平台资金钱包登记。

不负责

不签名、不广播、不保存私钥。

配置 / 数据

wallet_deposit_addresswallet_treasury

CHAIN EXECUTION

chain-service

负责

充值 txHash 验证、构造交易、nonce、广播、确认轮询、VRF 请求和合约调用。

不负责

不保存私钥、不直接改用户余额。

配置 / 数据

operator-key-idoperator-address、RPC、USDT contract。

SIGNING BOUNDARY

sign-service

负责

KMS 在线钱包签名、HD 地址派生、签名审计、keyId 解析。

不负责

不审核提现、不判断余额、不广播交易。

配置 / 数据

duobao.sign.keys[]、KMS ARN、HD seed ciphertext。

CONTRACTS

Contracts

负责

VRF_ONLY 模式下的 VRF request、callback 和相关链上事件。

不负责

不承担中心化账本职责。

配置 / 数据

ABI、contract address、ChainAdapter

INDEXING

RPC / Indexing

负责

读链、查交易、确认数、event log、自动充值扫块。

不负责

不作为用户余额唯一事实源。

配置 / 数据

RPC URL、confirmation depth、block cursor。

OPS CONTROL

Admin / SRE

负责

提现审核、资金面板、treasury movement SOP、KMS/IAM/CloudTrail。

不负责

普通后台不直接控制冷钱包出金。

配置 / 数据

runbook、IAM role、KMS audit、Safe/multisig SOP。

核心术语

这些词要在 docs、prototype、代码和运维里保持同一个意思。

GLOSSARY
keyId
逻辑密钥名

业务服务只传 operator-hot-evm-1,不接触真实 KMS ARN 或私钥。

AWS KeyId
AWS API 参数

sign-service 解析项目 keyId 后传给 KMS。

KMS key ARN
真实 KMS key

只放在 sign-service 配置里,用来调用 AWS KMS Sign。

operator address
出款 / 运行时地址

chain-service 用它分配 nonce、广播出款或发起合约调用。

HD master seed
充值地址主种子

生产由 KMS-wrapped ciphertext 解密,用于派生用户地址。

seedVersion
种子版本

种子轮换后,旧用户地址仍能按旧版本恢复。

derivationPath
派生路径

标记用户充值地址如何由 HD seed 派生。

sweep
用户地址归集

把用户充值地址里的 USDT 转到平台 Collection wallet。

Safe / multisig
多签钱包

Cold/Warm/大额调拨使用的多签控制能力;按人工 SOP 执行和留痕。

EncryptionContext
KMS 解密上下文

只用于 HD seed 的对称 Encrypt/Decrypt,不用于 asymmetric Sign。

keyId 不是钥匙,是项目内部别名

chain-service 只知道 operator-hot-evm-sepolia-1,不知道 KMS ARN,更不知道私钥。sign-service 把这个别名解析成 AWS KMS 可识别的 KeyId,然后调用 KMS 签名。

project keyIdoperator-hot-evm-sepolia-1,业务服务可见的逻辑名。
AWS KeyIdarn:aws:kms:...:key/abcd 或 alias,AWS SDK 请求参数。
KMS key ARNAWS 里真实 KMS key 的资源名,只放在 sign-service/IaC。
address0xabc...,公开链上地址,用于 nonce、余额、广播。

不要这样用

  • 不要把 KMS ARN 配到 chain-service
  • 不要把私钥、助记词、HD seed 写进 .env 或 Nacos。
  • 不要用同一个 keyId 同时表示出款钱包、Gas 钱包和 HD seed。
  • 不要让 wallet-service 调出款签名接口。
  • 日志里不要打印 raw tx、signature、KMS ARN。

keyId 如何执行

keyId 不是私钥。它是跨服务传递的逻辑名,最终只在 sign-service 内解析到 KMS key ARN。

WITHDRAW SIGNING FLOW
01 · wallet-service

业务审核

校验余额、提现白名单、24h 冷却,创建提现单并冻结余额。

02 · chain-service

选择出款钱包

读取出款 operator-key-idoperator-address,分配 nonce,构造 unsigned tx。

03 · sign-service

解析 keyId

duobao.sign.keys[] 找到 KMS ARN。项目 keyId 在这里变成 AWS KeyId

04 · AWS KMS

签 digest

调用 Sign(MessageType=DIGEST),返回 DER 编码签名。

05 · chain-service

广播确认

sign-service 恢复 v 后返回 signed tx;chain-service 广播并回调 wallet-service。

sign-service 如何启动

启动阶段只做配置绑定、安全保护、KMS/Local signer 选择、master seed 加载和内部接口注册。

STARTUP FLOW
01 · config

加载配置

Spring 从 env、Nacos、application.yml 绑定 SignPropertiesKmsProperties

02 · guard

启动保护

SignBootValidator 拒绝非 local profile 使用 local-mode=true

03 · mode

选择签名实现

local 启用 LocalSigner;测试和生产启用 KmsSignerKmsClient

04 · seed

加载 HD seed

KMS 模式启动时 decode ciphertext,并调用 kms:Decrypt + EncryptionContext,明文 seed 只留在内存里。

05 · ready

内部接口就绪

只暴露 /internal/v1/sign/**/actuator/* 和 error dispatch。

本地、测试、生产的区别

  • LOCAL:默认可用 local-mode=true,不依赖 KMS,只能用于开发。
  • TESTNET / STAGING:必须 local-mode=false,走 KMS 或 LocalStack。
  • PRODUCTION:必须 local-mode=false,用 AWS KMS 和 workload IAM role。

chain-service 启动校验

chain-service 启动时读取 operator-key-idoperator-address,调用 GET /operator-addr/{keyId}sign-service 从 KMS public key 恢复地址,返回给 chain-service 比对;不一致时不能进入可出款状态。

chain-service 如何启动

chain-service 启动后不直接拥有私钥;它只校验 operator address、注册链适配器,并启动确认轮询。

CHAIN STARTUP FLOW
01 · config

加载链配置

读取 duobao.chain.evmduobao.chain.tron、RPC、USDT contract、confirmations、gas policy。

02 · adapters

注册 ChainAdapter

EVM 用 web3j adapter;TRON 用 full-node HTTP adapter;未配置链返回 stub 并 fail fast。

03 · verify

校验 operator

调用 sign-service /operator-addr/{keyId},确认配置地址等于 KMS public key 恢复地址。

04 · schedulers

启动轮询器

BroadcastConfirmationPoller 处理提现;SweepConfirmationPoller 处理归集确认。

05 · ready

内部接口就绪

处理广播、查询余额、查询交易、VRF request 和 sweep 请求。

服务启动顺序

生产和测试环境里,sign-service 必须先就绪;chain-service 需要用它校验 operator 地址,之后才允许提现或归集。

BOOT ORDER
01 · config source

配置进入容器

application.yml 提供默认值,Nacos 或 env 注入 RPC、KMS ARN、wrapped seed 和 service token。

02 · sign-service

签名边界先启动

加载 KmsClient、解 HD master seed、注册 CallerAuthFilter,只开放内部签名接口。

03 · chain-service

链执行服务启动

加载 EVM/TRON adapter,然后用 operator-key-id 向 sign-service 查询公开地址。

04 · readiness

地址一致才可用

operator-address 与 KMS public key 恢复地址一致时,提现、广播、sweep 和轮询器进入可用状态。

运行时请求分流

sign-service 只接内部服务请求。它不审核提现、不查余额、不分配 nonce、不广播交易。

REQUEST ROUTING
01 · request

内部请求进入

调用方带 X-Caller-Service 和 service token。只读地址查询按内部只读策略放行。

02 · auth

CallerAuthFilter

duobao.sign.allowed-callers 校验 token,成功后写入 sign.caller

03 · controller

接口分发

SignInternalController 按 endpoint 调用 EVM、TRON、derive 或 address service。

04 · signer

KMS / Local

生产走 KmsSignerkms:Sign(MessageType=DIGEST);本地走 LocalSigner

05 · audit

审计后返回

写请求记录 sign_audit。成功审计失败时不释放签名。

POST /evm

EVM 出款签名

解 raw tx、计算 digest、用 payout/operator keyId 签名并返回 signed tx。

caller: chain-service
POST /tron

TRON 出款签名

校验 32-byte txId,返回 r+s+recId 签名。

caller: chain-service
POST /derive-addr

用户充值地址派生

按 uid、executionChain、seedVersion 派生地址,返回 address/path/keyId。

caller: wallet-service
POST /evm-derived

EVM 归集签名

按 uid/path/seedVersion 重建 HD child key,校验 fromAddress 后签 unsigned tx。

caller: chain-service
POST /tron-derived

TRON 归集签名

按用户地址派生 child key,签 32-byte txId,返回 r+s+recId。

caller: chain-service
GET /operator-addr/{keyId}

operator 地址校验

从 KMS public key 恢复地址,供 chain-service 启动时比对配置。

caller: chain-service startup
GET address endpoints

只读地址查询

/main-addr 等只读地址查询只返回公开地址,不返回 key material。

caller: internal read-only

chain-service 处理哪些请求

chain-service 是链上执行层。它接收 wallet-service、game-service 或运维内部请求,然后选择 adapter、sign-service 和 poller 完成链上动作。

CHAIN REQUEST MAP
wallet-service提现已审核
POST /withdraw/process reserve nonce build unsigned tx sign-service /evm or /tron broadcast poll confirmed
outputtxHash + state
wallet-service充值 txHash 验证
POST /deposit/verify-claim get receipt decode USDT transfer check to/amount/confirmations
outputVERIFIED / rejected
sweeper / ops用户地址归集
POST /evm/sweep PLANNED row build sweep tx sign-service /derived broadcast SweepConfirmationPoller
outputchain_sweep_record
game-serviceVRF 请求
POST /vrf/request VrfRequestService ChainAdapter / local fulfillment GET /vrf/{requestId}
outputrandomness state
admin / opsNonce 管理
GET /evm/nonce/gaps POST /heal POST /resync/{executionChain} NonceAdminService
outputnonce health

服务边界

sign-service 的合理设计是“签名边界”,不是钱包业务服务。

BOUNDARIES
wallet-service
余额账本 提现白名单 冻结 / 解冻 充值地址记录 不签名 不广播交易
chain-service
nonce 构造 unsigned tx 广播 确认轮询 sweep broadcast / confirm planner / gas station 不保存私钥
sign-service
keyId -> KMS ARN KMS Sign HD derive sign_audit derived sweep signing 不管余额 / 白名单 / nonce
admin / SRE
提现审核 treasury 只读 COLLECTION/HOT/COLD SOP 普通后台不直接冷钱包出金

接口矩阵

需要按 endpoint 做授权,不能只要 token 对就能调所有签名接口。

SIGN-SERVICE API POLICY
EndpointAllowed callerPolicyPurpose
/derive-addrwallet-servicederive only派生用户充值地址。
/evmchain-serviceoperator平台 Payout / Runtime EVM 签名。
/tronchain-serviceoperatorTRON 出款签名。
/operator-addr/{keyId}chain-service startupreadiness启动时校验 operator-address 与 KMS public key 恢复地址一致。
/main-addrinternal read-onlyread-only返回配置的主地址,不返回私钥材料。
/evm-derivedchain-servicesweep only用户充值地址 EVM sweep 签名。
/tron-derivedchain-servicesweep only用户充值地址 TRON sweep 签名。

用户充值地址归集

sweep planner、gas station、签名、广播和确认状态推进必须形成闭环;所有用户充值地址归集目标都是 active Collection Wallet,TRON 按 energy/bandwidth 资源模型处理。

SWEEP TARGET DESIGN
01 · detect

发现余额

chain-service 查询用户充值地址,找到 USDT 超过阈值的地址。

02 · gas

补 native gas

如果用户地址没有 BNB/ETH/TRX,由 gas station 先小额补 gas。

03 · plan

选择 Collection

写入 chain_sweep_record,归集目标为 active COLLECTION 钱包。

04 · sign

HD child 签名

sign-service 根据 uid/path/seedVersion 派生临时 child key 签名。

05 · settle

广播与确认

chain-service 广播,确认后标记 CONFIRMED,失败进入人工队列。

上线要求

真实资金上线前,资金闭环、签名权限和 KMS context 必须按最终方案通过门禁。

RELEASE REQUIREMENTS

P0 · sweep 自动化

必须满足 自动充值扫块、SweepPlanner、gas station、chain_sweep_record、derived signing endpoint、EVM/TRON broadcast 和 confirmation poller 形成闭环;sweep 目标固定为 active Collection Wallet。

P1 · 签名权限

必须满足 endpoint 级 caller policy:wallet-service 只能 derive,chain-service 只能链上签名和 derived sweep;sign-service 只允许内部网络、service token 和 IP allowlist 访问。

P1 · KMS context

必须满足 HD master seed Encrypt/Decrypt 使用完整 encryption context,包含 duobao-envduobao-serviceduobao-purposeduobao-seed-version;它不用于 KMS Sign。

测试入口

测试入口按资金托管门禁组织;smoke 只证明主流程,真实资金还必须通过 KMS、HD、资金钱包配置和 sweep 门禁。

TEST GATES
Command / TestWhat it provesCoverage
./duobao-backend/scripts/test-kms-e2e.shKMS decrypt/sign path works through LocalStack.KMS
./duobao-backend/scripts/test-hd-address-allocation.shHD deposit address stability, uniqueness, chain isolation.HD
./duobao-backend/scripts/withdraw-wallets.sh checkoperator key/address and platform wallet config alignment.withdraw
./duobao-backend/scripts/smoke-e2e.shMain app flow: login, buy, draw, withdraw review.app
POST /internal/v1/chain/evm/sweepSigns with HD child key, broadcasts, records chain_sweep_record, then poller confirms.core