From 0d6ee8f5ade18e5b47b0be4a57c20c321870f11e Mon Sep 17 00:00:00 2001 From: king Date: Mon, 19 Oct 2026 16:26:33 +0800 Subject: [PATCH] fix(mix): reject duplicate nullifiers and deposit amount overflow Withdraw only reads the nullifier state during proof verification and writes it after the payout, so the same proof repeated in one MixWithdrawAction was paid N times. Deposit accumulates note amounts in a uint64 without an overflow check, so several notes whose values sum past 2^64 could be recorded in the Merkle tree after paying only the wrapped remainder. - ForkMixDupNullifier: deduplicate nullifiers inside one action. - ForkMixAmountOverflow: reject v.Uint64() that would overflow MaxInt64-sum before accumulating. Both forks are registered defaulting to height 0; set a future height in the fork config to keep the legacy behavior for an existing chain. --- chain33.fork.toml | 2 ++ chain33.para.toml | 2 ++ plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml | 2 ++ plugin/dapp/mix/executor/deposit.go | 9 ++++++++- plugin/dapp/mix/executor/withdraw.go | 13 +++++++++++-- plugin/dapp/mix/types/errors.go | 2 ++ plugin/dapp/mix/types/type.go | 10 +++++++++- 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/chain33.fork.toml b/chain33.fork.toml index 79c79eece7..868dfd8485 100644 --- a/chain33.fork.toml +++ b/chain33.fork.toml @@ -165,6 +165,8 @@ Enable=0 [fork.sub.mix] Enable=0 +ForkMixAmountOverflow=0 +ForkMixDupNullifier=0 [fork.sub.multisig] Enable=0 diff --git a/chain33.para.toml b/chain33.para.toml index 00afc4248b..48662b57ea 100644 --- a/chain33.para.toml +++ b/chain33.para.toml @@ -503,6 +503,8 @@ Enable=0 [fork.sub.mix] Enable=0 +ForkMixAmountOverflow=0 +ForkMixDupNullifier=0 [fork.sub.unfreeze] Enable=0 diff --git a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml index 1316dafbbc..25531d4211 100644 --- a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml +++ b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml @@ -673,6 +673,8 @@ Enable=0 [fork.sub.mix] Enable=0 +ForkMixAmountOverflow=0 +ForkMixDupNullifier=0 [fork.sub.multisig] Enable=0 diff --git a/plugin/dapp/mix/executor/deposit.go b/plugin/dapp/mix/executor/deposit.go index ee3c985ac8..f4c32633e6 100644 --- a/plugin/dapp/mix/executor/deposit.go +++ b/plugin/dapp/mix/executor/deposit.go @@ -5,6 +5,8 @@ package executor import ( + "math" + "github.com/33cn/chain33/common/address" "github.com/33cn/chain33/types" mixTy "github.com/33cn/plugin/plugin/dapp/mix/types" @@ -42,6 +44,8 @@ func (a *action) depositVerify(proof *mixTy.ZkProofInfo) (*mixTy.DepositCircuit, 4. add new commits to merkle tree */ func (a *action) Deposit(deposit *mixTy.MixDepositAction) (*types.Receipt, error) { + cfg := a.api.GetConfig() + checkOverflow := cfg.IsDappFork(a.height, mixTy.MixX, mixTy.ForkMixAmountOverflow) var notes []string var sum uint64 //1. zk-proof校验 @@ -54,6 +58,10 @@ func (a *action) Deposit(deposit *mixTy.MixDepositAction) (*types.Receipt, error if err != nil { return nil, err } + //分叉后校验累加溢出,防止存入金额回绕(同时避免sum超出int64范围) + if checkOverflow && v.Uint64() > math.MaxInt64-sum { + return nil, errors.Wrapf(mixTy.ErrAmountOverflow, "deposit amount sum overflow,sum=%d,amount=%d", sum, v.Uint64()) + } sum += v.Uint64() noteHash, err := mixTy.VariableToElement(input.NoteHash) if err != nil { @@ -63,7 +71,6 @@ func (a *action) Deposit(deposit *mixTy.MixDepositAction) (*types.Receipt, error } //存款 - cfg := a.api.GetConfig() execer, symbol := mixTy.GetAssetExecSymbol(cfg, deposit.AssetExec, deposit.AssetSymbol) accoutDb, err := createAccount(cfg, execer, symbol, a.db) if err != nil { diff --git a/plugin/dapp/mix/executor/withdraw.go b/plugin/dapp/mix/executor/withdraw.go index a4491a3f89..13206a13cd 100644 --- a/plugin/dapp/mix/executor/withdraw.go +++ b/plugin/dapp/mix/executor/withdraw.go @@ -86,10 +86,13 @@ func (a *action) withdrawVerify(exec, symbol string, proof *mixTy.ZkProofInfo) ( 3. set nullifier exist */ func (a *action) Withdraw(withdraw *mixTy.MixWithdrawAction) (*types.Receipt, error) { - exec, symbol := mixTy.GetAssetExecSymbol(a.api.GetConfig(), withdraw.AssetExec, withdraw.AssetSymbol) + cfg := a.api.GetConfig() + exec, symbol := mixTy.GetAssetExecSymbol(cfg, withdraw.AssetExec, withdraw.AssetSymbol) + checkDupNullifier := cfg.IsDappFork(a.height, mixTy.MixX, mixTy.ForkMixDupNullifier) var nulliferSet []string var sumValue uint64 + nullifierDup := make(map[string]struct{}) for _, k := range withdraw.Proofs { input, err := a.withdrawVerify(exec, symbol, k) if err != nil { @@ -104,6 +107,13 @@ func (a *action) Withdraw(withdraw *mixTy.MixWithdrawAction) (*types.Receipt, er if err != nil { return nil, err } + //分叉后校验交易内nullifier唯一:nullifier在交易结束才落库,同一交易内重复proof会双花 + if checkDupNullifier { + if _, ok := nullifierDup[nullHash.String()]; ok { + return nil, errors.Wrapf(mixTy.ErrNulliferHashExist, "duplicate nullifier in tx,nullifier=%s", nullHash.String()) + } + nullifierDup[nullHash.String()] = struct{}{} + } nulliferSet = append(nulliferSet, nullHash.String()) } @@ -112,7 +122,6 @@ func (a *action) Withdraw(withdraw *mixTy.MixWithdrawAction) (*types.Receipt, er } //withdraw value - cfg := a.api.GetConfig() accoutDb, err := createAccount(cfg, exec, symbol, a.db) if err != nil { return nil, err diff --git a/plugin/dapp/mix/types/errors.go b/plugin/dapp/mix/types/errors.go index e1cb040bad..5e378202c3 100644 --- a/plugin/dapp/mix/types/errors.go +++ b/plugin/dapp/mix/types/errors.go @@ -18,6 +18,8 @@ var ( //ErrNulliferHashExist exist ErrNulliferHashExist = errors.New("ErrNulliferHashExist") + //ErrAmountOverflow amount sum overflow + ErrAmountOverflow = errors.New("ErrAmountOverflow") //ErrAuthorizeHashExist exist ErrAuthorizeHashExist = errors.New("ErrAuthorizeHashExist") diff --git a/plugin/dapp/mix/types/type.go b/plugin/dapp/mix/types/type.go index b3328987dc..29b04234a7 100644 --- a/plugin/dapp/mix/types/type.go +++ b/plugin/dapp/mix/types/type.go @@ -19,6 +19,13 @@ var ( glog = log.New("module", MixX) ) +const ( + // ForkMixAmountOverflow 修复存款金额以uint64累加无溢出检查的漏洞 + ForkMixAmountOverflow = "ForkMixAmountOverflow" + // ForkMixDupNullifier 修复同一笔Withdraw交易内重复proof双花的漏洞 + ForkMixDupNullifier = "ForkMixDupNullifier" +) + func init() { // init executor type types.AllowUserExec = append(types.AllowUserExec, []byte(MixX)) @@ -30,7 +37,8 @@ func init() { // InitFork ... func InitFork(cfg *types.Chain33Config) { cfg.RegisterDappFork(MixX, "Enable", 0) - + cfg.RegisterDappFork(MixX, ForkMixAmountOverflow, 0) + cfg.RegisterDappFork(MixX, ForkMixDupNullifier, 0) } // InitExecutor ...