diff --git a/app/app.go b/app/app.go index ce5143035c..9aa8b1c0ca 100644 --- a/app/app.go +++ b/app/app.go @@ -228,7 +228,9 @@ var ( wasm.ModuleName: {authtypes.Burner}, evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - cttypes.ModuleName: {}, + // Confidential Transfers module is not live yet, but we add the permissions for testing + cttypes.ModuleName: nil, + // this line is used by starport scaffolding # stargate/app/maccPerms } @@ -562,7 +564,8 @@ func New( appCodec, app.keys[(cttypes.StoreKey)], app.GetSubspace(cttypes.ModuleName), - app.AccountKeeper) + app.AccountKeeper, + app.BankKeeper) // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks diff --git a/app/apptesting/test_suite.go b/app/apptesting/test_suite.go index 4b81862c1f..ca17c21b5c 100644 --- a/app/apptesting/test_suite.go +++ b/app/apptesting/test_suite.go @@ -2,6 +2,8 @@ package apptesting import ( "context" + "crypto/ecdsa" + "github.com/ethereum/go-ethereum/crypto" "time" "github.com/cosmos/cosmos-sdk/baseapp" @@ -176,6 +178,20 @@ func CreateRandomAccounts(numAccts int) []sdk.AccAddress { return testAddrs } +// CreateRandomAccountKeys is a function return a list of randomly generated private keys +func CreateRandomAccountKeys(numAccts int) []*ecdsa.PrivateKey { + testAccts := make([]*ecdsa.PrivateKey, numAccts) + for i := 0; i < numAccts; i++ { + pk, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + testAccts[i] = pk + } + + return testAccts +} + func GenerateTestAddrs() (string, string) { pk1 := ed25519.GenPrivKey().PubKey() validAddr := sdk.AccAddress(pk1.Address()).String() diff --git a/proto/confidentialtransfers/tx.proto b/proto/confidentialtransfers/tx.proto index 5fe08319bd..2f9891b07e 100644 --- a/proto/confidentialtransfers/tx.proto +++ b/proto/confidentialtransfers/tx.proto @@ -27,7 +27,6 @@ service Msg { // CloseAccount defines a method for closing an account. rpc CloseAccount(MsgCloseAccount) returns (MsgCloseAccountResponse); - } // MsgTransfer represents a message to send coins from one account to another. @@ -70,7 +69,10 @@ message MsgInitializeAccount { string denom = 2 [(gogoproto.moretags) = "yaml:\"denom\""]; bytes public_key = 3 [(gogoproto.moretags) = "yaml:\"public_key\""]; string decryptable_balance = 4 [(gogoproto.moretags) = "yaml:\"decryptable_balance\""]; - InitializeAccountMsgProofs proofs = 5 [(gogoproto.moretags) = "yaml:\"proofs\""]; + Ciphertext pending_balance_lo = 5 [(gogoproto.moretags) = "yaml:\"pending_balance_lo\""]; + Ciphertext pending_balance_hi = 6 [(gogoproto.moretags) = "yaml:\"pending_balance_hi\""]; + Ciphertext available_balance = 7 [(gogoproto.moretags) = "yaml:\"available_balance\""]; + InitializeAccountMsgProofs proofs = 8 [(gogoproto.moretags) = "yaml:\"proofs\""]; } // MsgInitializeAccountResponse defines the Msg/Send response type. @@ -110,9 +112,9 @@ message MsgApplyPendingBalance { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; - string address = 1; - string denom = 2; - string new_decryptable_available_balance = 3; + string address = 1 [(gogoproto.moretags) = "yaml:\"address\""]; + string denom = 2 [(gogoproto.moretags) = "yaml:\"denom\""]; + string new_decryptable_available_balance = 3 [(gogoproto.moretags) = "yaml:\"new_decryptable_available_balance\""]; } message MsgApplyPendingBalanceResponse {} diff --git a/proto/confidentialtransfers/zk.proto b/proto/confidentialtransfers/zk.proto index 7e1497545f..5a2d695f0e 100644 --- a/proto/confidentialtransfers/zk.proto +++ b/proto/confidentialtransfers/zk.proto @@ -18,6 +18,9 @@ message TransferMsgProofs { message InitializeAccountMsgProofs { PubkeyValidityProof pubkey_validity_proof = 1; + ZeroBalanceProof zero_pending_balance_lo_proof = 2; + ZeroBalanceProof zero_pending_balance_hi_proof = 3; + ZeroBalanceProof zero_available_balance_proof = 4; } message WithdrawMsgProofs { diff --git a/x/confidentialtransfers/keeper/genesis.go b/x/confidentialtransfers/keeper/genesis.go index 10f22062e7..aab049a744 100644 --- a/x/confidentialtransfers/keeper/genesis.go +++ b/x/confidentialtransfers/keeper/genesis.go @@ -2,13 +2,15 @@ package keeper import ( "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/sei-protocol/sei-chain/x/confidentialtransfers/types" ) func (k BaseKeeper) InitGenesis(ctx sdk.Context, gs *types.GenesisState) { + moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName) + k.accountKeeper.SetModuleAccount(ctx, moduleAcc) k.SetParams(ctx, gs.Params) store := k.getAccountStore(ctx) for i := range gs.Accounts { diff --git a/x/confidentialtransfers/keeper/keeper.go b/x/confidentialtransfers/keeper/keeper.go index fd446aa86a..312ea8353d 100644 --- a/x/confidentialtransfers/keeper/keeper.go +++ b/x/confidentialtransfers/keeper/keeper.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/cosmos/cosmos-sdk/store/prefix" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -11,8 +12,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) var _ Keeper = (*BaseKeeper)(nil) @@ -24,9 +23,14 @@ type Keeper interface { GetAccount(ctx sdk.Context, addrString string, denom string) (types.Account, bool) SetAccount(ctx sdk.Context, addrString string, denom string, account types.Account) error + DeleteAccount(ctx sdk.Context, addrString string, denom string) error GetParams(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) + // TODO: See if there's a way to put this somewhere else + SendTokens(ctx sdk.Context, to sdk.AccAddress, amount sdk.Coins) error + ReceiveTokens(ctx sdk.Context, from sdk.AccAddress, amount sdk.Coins) error + CreateModuleAccount(ctx sdk.Context) types.QueryServer @@ -39,6 +43,7 @@ type BaseKeeper struct { paramSpace paramtypes.Subspace accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper } // NewKeeper returns a new instance of the x/confidentialtransfers keeper @@ -47,6 +52,7 @@ func NewKeeper( storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, ) Keeper { if !paramSpace.HasKeyTable() { @@ -57,6 +63,7 @@ func NewKeeper( cdc: codec, storeKey: storeKey, accountKeeper: accountKeeper, + bankKeeper: bankKeeper, paramSpace: paramSpace, } } @@ -77,6 +84,17 @@ func (k BaseKeeper) GetAccount(ctx sdk.Context, address string, denom string) (t return *account, true } +func (k BaseKeeper) DeleteAccount(ctx sdk.Context, addrString, denom string) error { + address, err := sdk.AccAddressFromBech32(addrString) + if err != nil { + return err + } + + store := k.getAccountStoreForAddress(ctx, address) + store.Delete([]byte(denom)) // Store the serialized account under the key + return nil +} + func (k BaseKeeper) getCtAccount(ctx sdk.Context, address sdk.AccAddress, denom string) (types.CtAccount, bool) { store := k.getAccountStoreForAddress(ctx, address) key := []byte(denom) @@ -139,6 +157,15 @@ func (k BaseKeeper) GetAccountsForAddress(ctx sdk.Context, address sdk.AccAddres return accounts, nil } +// CreateModuleAccount creates the module account for confidentialtransfers +func (k BaseKeeper) CreateModuleAccount(ctx sdk.Context) { + account := k.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + if account == nil { + moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName) + k.accountKeeper.SetModuleAccount(ctx, moduleAcc) + } +} + func (k BaseKeeper) GetParams(ctx sdk.Context) (params types.Params) { k.paramSpace.GetParamSet(ctx, ¶ms) return params @@ -149,9 +176,12 @@ func (k BaseKeeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSpace.SetParamSet(ctx, ¶ms) } -func (k BaseKeeper) CreateModuleAccount(ctx sdk.Context) { - moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName, authtypes.Minter, authtypes.Burner) - k.accountKeeper.SetModuleAccount(ctx, moduleAcc) +func (k BaseKeeper) SendTokens(ctx sdk.Context, to sdk.AccAddress, amount sdk.Coins) error { + return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, to, amount) +} + +func (k BaseKeeper) ReceiveTokens(ctx sdk.Context, from sdk.AccAddress, amount sdk.Coins) error { + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, from, types.ModuleName, amount) } func (k BaseKeeper) getAccountStore(ctx sdk.Context) prefix.Store { diff --git a/x/confidentialtransfers/keeper/keeper_test.go b/x/confidentialtransfers/keeper/keeper_test.go index b2ffece545..5695f65f02 100644 --- a/x/confidentialtransfers/keeper/keeper_test.go +++ b/x/confidentialtransfers/keeper/keeper_test.go @@ -1,13 +1,20 @@ package keeper_test import ( + "crypto/ecdsa" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/app/apptesting" "github.com/sei-protocol/sei-chain/x/confidentialtransfers/keeper" "github.com/sei-protocol/sei-chain/x/confidentialtransfers/types" + "github.com/sei-protocol/sei-cryptography/pkg/encryption" + "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" "github.com/stretchr/testify/suite" "testing" ) +const DefaultTestDenom = "test" + type KeeperTestSuite struct { apptesting.KeeperTestHelper @@ -15,6 +22,8 @@ type KeeperTestSuite struct { msgServer types.MsgServer // defaultDenom is on the suite, as it depends on the creator test address. defaultDenom string + + PrivKeys []*ecdsa.PrivateKey } func TestKeeperTestSuite(t *testing.T) { @@ -27,4 +36,98 @@ func (suite *KeeperTestSuite) SetupTest() { suite.queryClient = types.NewQueryClient(suite.QueryHelper) suite.msgServer = keeper.NewMsgServerImpl(suite.App.ConfidentialTransfersKeeper) + // TODO: remove this once the app initializes confidentialtransfers keeper + suite.App.ConfidentialTransfersKeeper = keeper.NewKeeper( + suite.App.AppCodec(), + suite.App.GetKey(types.StoreKey), + suite.App.GetSubspace(types.ModuleName), + suite.App.AccountKeeper, + suite.App.BankKeeper, + ) + suite.msgServer = keeper.NewMsgServerImpl(suite.App.ConfidentialTransfersKeeper) + suite.PrivKeys = apptesting.CreateRandomAccountKeys(3) +} + +func (suite *KeeperTestSuite) SetupAccount() { + suite.queryClient = types.NewQueryClient(suite.QueryHelper) + // TODO: remove this once the app initializes confidentialtransfers keeper + suite.App.ConfidentialTransfersKeeper = keeper.NewKeeper( + suite.App.AppCodec(), + suite.App.GetKey(types.StoreKey), + suite.App.GetSubspace(types.ModuleName), + suite.App.AccountKeeper, + suite.App.BankKeeper, + ) + suite.msgServer = keeper.NewMsgServerImpl(suite.App.ConfidentialTransfersKeeper) + suite.PrivKeys = apptesting.CreateRandomAccountKeys(4) +} + +func (suite *KeeperTestSuite) SetupAccountState(privateKey *ecdsa.PrivateKey, denom string, pendingBalanceCreditCounter uint16, initialAvailableBalance, initialPendingBalance, bankAmount uint64) (types.Account, error) { + aesKey, err := encryption.GetAESKey(*privateKey, denom) + if err != nil { + return types.Account{}, err + } + + teg := elgamal.NewTwistedElgamal() + keypair, err := teg.KeyGen(*privateKey, denom) + if err != nil { + return types.Account{}, err + } + + availableBalance := initialAvailableBalance + pendingBalance := initialPendingBalance + + // Extract the bottom 16 bits (rightmost 16 bits) + pendingBalanceLo := uint16(pendingBalance & 0xFFFF) + + // Extract the next 32 bits (from bit 16 to bit 47) + pendingBalanceHi := uint32((pendingBalance >> 16) & 0xFFFFFFFF) + + if err != nil { + return types.Account{}, err + } + + availableBalanceCipherText, _, err := teg.Encrypt(keypair.PublicKey, uint64(availableBalance)) + if err != nil { + return types.Account{}, err + } + + pendingBalanceLoCipherText, _, err := teg.Encrypt(keypair.PublicKey, uint64(pendingBalanceLo)) + if err != nil { + return types.Account{}, err + } + + pendingBalanceHiCipherText, _, err := teg.Encrypt(keypair.PublicKey, uint64(pendingBalanceHi)) + if err != nil { + return types.Account{}, err + } + + decryptableAvailableBalance, err := encryption.EncryptAESGCM(uint64(availableBalance), aesKey) + if err != nil { + return types.Account{}, err + } + + initialAccountState := types.Account{ + PublicKey: keypair.PublicKey, + PendingBalanceLo: pendingBalanceLoCipherText, + PendingBalanceHi: pendingBalanceHiCipherText, + PendingBalanceCreditCounter: pendingBalanceCreditCounter, + AvailableBalance: availableBalanceCipherText, + DecryptableAvailableBalance: decryptableAvailableBalance, + } + + addr := privkeyToAddress(privateKey) + suite.App.ConfidentialTransfersKeeper.SetAccount(suite.Ctx, addr.String(), denom, initialAccountState) + + bankModuleTokens := sdk.NewCoins(sdk.Coin{Amount: sdk.NewInt(int64(bankAmount)), Denom: denom}) + + suite.FundAcc(addr, bankModuleTokens) + + return initialAccountState, nil +} + +func privkeyToAddress(privateKey *ecdsa.PrivateKey) sdk.AccAddress { + publicKeyBytes := crypto.FromECDSAPub(&privateKey.PublicKey) + testAddr := sdk.AccAddress(crypto.Keccak256(publicKeyBytes[1:])[12:]) + return testAddr } diff --git a/x/confidentialtransfers/keeper/msg_server.go b/x/confidentialtransfers/keeper/msg_server.go index fdfd871272..78ec9f94a1 100644 --- a/x/confidentialtransfers/keeper/msg_server.go +++ b/x/confidentialtransfers/keeper/msg_server.go @@ -2,48 +2,446 @@ package keeper import ( "context" - + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/sei-protocol/sei-chain/x/confidentialtransfers/types" + "github.com/sei-protocol/sei-chain/x/confidentialtransfers/utils" + "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" + "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" + "math" ) type msgServer struct { Keeper } -func (m msgServer) Transfer(ctx context.Context, transfer *types.MsgTransfer) (*types.MsgTransferResponse, error) { - //TODO implement me - panic("implement me") +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return msgServer{keeper} } -func (m msgServer) InitializeAccount(ctx context.Context, account *types.MsgInitializeAccount) (*types.MsgInitializeAccountResponse, error) { - //TODO implement me - panic("implement me") -} +var _ types.MsgServer = msgServer{} + +func (m msgServer) InitializeAccount(goCtx context.Context, req *types.MsgInitializeAccount) (*types.MsgInitializeAccountResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Convert the instruction from proto. This also validates the request. + instruction, err := req.FromProto() + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid msg") + } + + // Check if the account already exists + _, exists := m.Keeper.GetAccount(ctx, req.FromAddress, instruction.Denom) + if exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "account already exists") + } + + // Validate the public key + validated := zkproofs.VerifyPubKeyValidity(*instruction.Pubkey, *instruction.Proofs.PubkeyValidityProof) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid public key") + } -func (m msgServer) Deposit(ctx context.Context, deposit *types.MsgDeposit) (*types.MsgDepositResponse, error) { - //TODO implement me - panic("implement me") + // Validate the pending balance lo is zero. + validated = zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroPendingBalanceLoProof, instruction.Pubkey, instruction.PendingBalanceLo) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid pending balance lo") + } + + // Validate the pending balance hi is zero. + validated = zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroPendingBalanceHiProof, instruction.Pubkey, instruction.PendingBalanceHi) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid pending balance hi") + } + + // Validate the available balance is zero. + validated = zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroAvailableBalanceProof, instruction.Pubkey, instruction.AvailableBalance) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid available balance") + } + + // Create the account + account := types.Account{ + PublicKey: *instruction.Pubkey, + PendingBalanceLo: instruction.PendingBalanceLo, + PendingBalanceHi: instruction.PendingBalanceHi, + AvailableBalance: instruction.AvailableBalance, + DecryptableAvailableBalance: instruction.DecryptableBalance, + PendingBalanceCreditCounter: 0, + } + + // Store the account + err = m.Keeper.SetAccount(ctx, req.FromAddress, req.Denom, account) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error setting account") + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeInitializeAccount, + sdk.NewAttribute(types.AttributeDenom, instruction.Denom), + sdk.NewAttribute(types.AttributeAddress, instruction.FromAddress), + ), + }) + + return &types.MsgInitializeAccountResponse{}, nil } -func (m msgServer) Withdraw(ctx context.Context, withdraw *types.MsgWithdraw) (*types.MsgWithdrawResponse, error) { - //TODO implement me - panic("implement me") +func (m msgServer) Deposit(goCtx context.Context, req *types.MsgDeposit) (*types.MsgDepositResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Validate request + err := req.ValidateBasic() + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid request") + } + + // Check if the account exists + address, err := sdk.AccAddressFromBech32(req.FromAddress) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid address") + } + + account, exists := m.Keeper.GetAccount(ctx, req.FromAddress, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "account does not exist") + } + + // The maximum transfer amount is 2^48 + if req.Amount > uint64((2<<48)-1) { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceeded maximum deposit amount of 2^48") + } + + // Check that account does not have the maximum limit of pending transactions. + if account.PendingBalanceCreditCounter == math.MaxUint16 { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "account has too many pending transactions") + } + + // Deduct amount from user's token balance. + // Define the amount to be transferred as sdk.Coins + coins := sdk.NewCoins(sdk.NewCoin(req.Denom, sdk.NewIntFromUint64(req.Amount))) + + // Transfer the amount from the sender's account to the module account + if err := m.Keeper.ReceiveTokens(ctx, address, coins); err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to deposit %d %s", req.Amount, req.Denom) + } + + // Split the deposit amount into lo and hi bits. + // Extract the bottom 16 bits (rightmost 16 bits) + bottom16, next32, err := utils.SplitTransferBalance(req.Amount) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error splitting transfer balance") + } + + // Compute the new balances + teg := elgamal.NewTwistedElgamal() + newPendingBalanceLo, err := teg.AddScalar(account.PendingBalanceLo, uint64(bottom16)) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error adding pending balance lo") + } + + newPendingBalanceHi, err := teg.AddScalar(account.PendingBalanceHi, uint64(next32)) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error adding pending balance hi") + } + + // Update the account state + account.PendingBalanceLo = newPendingBalanceLo + account.PendingBalanceHi = newPendingBalanceHi + account.PendingBalanceCreditCounter += 1 + + // Save the changes to the account state + err = m.Keeper.SetAccount(ctx, req.FromAddress, req.Denom, account) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error setting account") + } + + // Emit any required events + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeDeposit, + sdk.NewAttribute(types.AttributeDenom, req.Denom), + sdk.NewAttribute(types.AttributeAddress, req.FromAddress), + sdk.NewAttribute(sdk.AttributeKeyAmount, sdk.NewCoin(req.Denom, sdk.NewIntFromUint64(req.Amount)).String()), + ), + }) + + return &types.MsgDepositResponse{}, nil } -func (m msgServer) ApplyPendingBalance(ctx context.Context, balance *types.MsgApplyPendingBalance) (*types.MsgApplyPendingBalanceResponse, error) { - //TODO implement me - panic("implement me") +func (m msgServer) Withdraw(goCtx context.Context, req *types.MsgWithdraw) (*types.MsgWithdrawResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Get the requested address. + address, err := sdk.AccAddressFromBech32(req.FromAddress) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid address") + } + + // Get the user's account + account, exists := m.Keeper.GetAccount(ctx, req.FromAddress, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "account does not exist") + } + + // Convert the struct to a usable form. This also validates the request. + instruction, err := req.FromProto() + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid msg") + } + + // Verify that the account has sufficient funds (Remaining balance after making the transfer is greater than or equal to zero.) + // This range proof verification is performed on the RemainingBalanceCommitment sent by the user. + // An additional check is required to ensure that this matches the remaining balance calculated by the server. + verified, _ := zkproofs.VerifyRangeProof(instruction.Proofs.RemainingBalanceRangeProof, instruction.RemainingBalanceCommitment, 64) + if !verified { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "range proof verification failed") + } + + // Verify that the remaining balance sent by the user matches the remaining balance calculated by the server. + teg := elgamal.NewTwistedElgamal() + remainingBalanceCalculated, err := teg.SubScalar(account.AvailableBalance, instruction.Amount) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error subtracting amount") + } + + verified = zkproofs.VerifyCiphertextCommitmentEquality( + instruction.Proofs.RemainingBalanceEqualityProof, + &account.PublicKey, remainingBalanceCalculated, + &instruction.RemainingBalanceCommitment.C) + if !verified { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "ciphertext commitment equality verification failed") + } + + // Update the account state + account.DecryptableAvailableBalance = instruction.DecryptableBalance + account.AvailableBalance = remainingBalanceCalculated + + // Save the account state + err = m.Keeper.SetAccount(ctx, req.FromAddress, req.Denom, account) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error setting account") + } + + // Return the tokens to the sender + coins := sdk.NewCoins(sdk.NewCoin(instruction.Denom, sdk.NewIntFromUint64(instruction.Amount))) + if err := m.Keeper.SendTokens(ctx, address, coins); err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to withdraw %d %s", req.Amount, req.Denom) + } + + // Emit any required events + //TODO: Look into whether we can use EmitTypedEvents instead since EmitEvents is deprecated + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeWithdraw, + sdk.NewAttribute(types.AttributeDenom, instruction.Denom), + sdk.NewAttribute(types.AttributeAddress, instruction.FromAddress), + sdk.NewAttribute(sdk.AttributeKeyAmount, sdk.NewCoin(instruction.Denom, sdk.NewIntFromUint64(instruction.Amount)).String()), + ), + }) + + return &types.MsgWithdrawResponse{}, nil } -func (m msgServer) CloseAccount(ctx context.Context, account *types.MsgCloseAccount) (*types.MsgCloseAccountResponse, error) { - //TODO implement me - panic("implement me") +func (m msgServer) ApplyPendingBalance(goCtx context.Context, req *types.MsgApplyPendingBalance) (*types.MsgApplyPendingBalanceResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if the account exists + account, exists := m.Keeper.GetAccount(ctx, req.Address, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "account does not exist") + } + + if account.PendingBalanceCreditCounter == 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "no pending balances to apply") + } + + // Calculate updated balances + teg := elgamal.NewTwistedElgamal() + newAvailableBalance, err := teg.AddWithLoHi(account.AvailableBalance, account.PendingBalanceLo, account.PendingBalanceHi) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error summing balances") + } + + zeroCiphertextLo, err := elgamal.SubtractCiphertext(account.PendingBalanceLo, account.PendingBalanceLo) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error zeroing pending balance lo") + } + zeroCiphertextHi, err := elgamal.SubtractCiphertext(account.PendingBalanceHi, account.PendingBalanceHi) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error zeroing pending balance hi") + } + + // Apply the changes to the account state + account.AvailableBalance = newAvailableBalance + account.DecryptableAvailableBalance = req.NewDecryptableAvailableBalance + account.PendingBalanceLo = zeroCiphertextLo + account.PendingBalanceHi = zeroCiphertextHi + account.PendingBalanceCreditCounter = 0 + + // Save the changes to the account state + m.Keeper.SetAccount(ctx, req.Address, req.Denom, account) + + // Emit any required events + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeApplyPendingBalance, + sdk.NewAttribute(types.AttributeDenom, req.Denom), + sdk.NewAttribute(types.AttributeAddress, req.Address), + ), + }) + + return &types.MsgApplyPendingBalanceResponse{}, nil } -// NewMsgServerImpl returns an implementation of the MsgServer interface -// for the provided Keeper. -func NewMsgServerImpl(keeper Keeper) types.MsgServer { - return msgServer{keeper} +func (m msgServer) CloseAccount(goCtx context.Context, req *types.MsgCloseAccount) (*types.MsgCloseAccountResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if the account exists + account, exists := m.Keeper.GetAccount(ctx, req.Address, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "account does not exist") + } + + instruction, err := req.FromProto() + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid msg") + } + + // Validate proof that pending balance lo is zero. + validated := zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroPendingBalanceLoProof, &account.PublicKey, account.PendingBalanceLo) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "pending balance lo must be 0") + } + + // Validate proof that pending balance hi is zero. + validated = zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroPendingBalanceHiProof, &account.PublicKey, account.PendingBalanceHi) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "pending balance hi must be 0") + } + + // Validate proof that available balance is zero. + validated = zkproofs.VerifyZeroBalance(instruction.Proofs.ZeroAvailableBalanceProof, &account.PublicKey, account.AvailableBalance) + if !validated { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "available balance must be 0") + } + + // Delete the account + err = m.Keeper.DeleteAccount(ctx, req.Address, req.Denom) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error deleting account") + } + + // Emit any required events + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeCloseAccount, + sdk.NewAttribute(types.AttributeDenom, req.Denom), + sdk.NewAttribute(types.AttributeAddress, req.Address), + ), + }) + + return &types.MsgCloseAccountResponse{}, nil } -var _ types.MsgServer = msgServer{} +func (m msgServer) Transfer(goCtx context.Context, req *types.MsgTransfer) (*types.MsgTransferResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + instruction, err := req.FromProto() + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid msg") + } + + // Check that sender and recipient accounts exist. + senderAccount, exists := m.Keeper.GetAccount(ctx, req.FromAddress, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "sender account does not exist") + } + + recipientAccount, exists := m.Keeper.GetAccount(ctx, req.ToAddress, req.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "recipient account does not exist") + } + + // Check that account does not have the maximum limit of pending transactions. + if recipientAccount.PendingBalanceCreditCounter == math.MaxUint16 { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "recipient account has too many pending transactions") + } + + // Calculate senders new available balance. + teg := elgamal.NewTwistedElgamal() + newSenderBalanceCiphertext, err := teg.SubWithLoHi(senderAccount.AvailableBalance, instruction.SenderTransferAmountLo, instruction.SenderTransferAmountHi) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error subtracting sender transfer amount") + } + + // Validate proofs + err = types.VerifyTransferProofs(instruction, &senderAccount.PublicKey, &recipientAccount.PublicKey, newSenderBalanceCiphertext) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + } + + // Validate proofs for each auditor + for _, auditorParams := range instruction.Auditors { + + auditorAccount, exists := m.Keeper.GetAccount(ctx, auditorParams.Address, instruction.Denom) + if !exists { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "auditor account does not exist") + } + + err = types.VerifyAuditorProof( + instruction.SenderTransferAmountLo, + instruction.SenderTransferAmountHi, + auditorParams, + &senderAccount.PublicKey, + &auditorAccount.PublicKey) + + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + } + } + + // Calculate and Update the account states. + // TODO: Is there a possibility for a race condition here if multiple Transfer transactions are made to the same account at the same time? + recipientPendingBalanceLo, err := elgamal.AddCiphertext(recipientAccount.PendingBalanceLo, instruction.RecipientTransferAmountLo) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error adding recipient transfer amount lo") + } + + recipientPendingBalanceHi, err := elgamal.AddCiphertext(recipientAccount.PendingBalanceHi, instruction.RecipientTransferAmountHi) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error adding recipient transfer amount hi") + } + + recipientAccount.PendingBalanceLo = recipientPendingBalanceLo + recipientAccount.PendingBalanceHi = recipientPendingBalanceHi + recipientAccount.PendingBalanceCreditCounter += 1 + + senderAccount.DecryptableAvailableBalance = instruction.DecryptableBalance + senderAccount.AvailableBalance = newSenderBalanceCiphertext + + // Save the account states + err = m.Keeper.SetAccount(ctx, req.FromAddress, req.Denom, senderAccount) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error setting sender account") + } + + err = m.Keeper.SetAccount(ctx, req.ToAddress, req.Denom, recipientAccount) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error setting recipient account") + } + + // Emit any required events + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeDenom, req.Denom), + sdk.NewAttribute(types.AttributeSender, req.FromAddress), + sdk.NewAttribute(types.AttributeRecipient, req.ToAddress), + ), + }) + + return &types.MsgTransferResponse{}, nil +} diff --git a/x/confidentialtransfers/keeper/msg_server_test.go b/x/confidentialtransfers/keeper/msg_server_test.go new file mode 100644 index 0000000000..0b8d577e85 --- /dev/null +++ b/x/confidentialtransfers/keeper/msg_server_test.go @@ -0,0 +1,1216 @@ +package keeper_test + +import ( + "github.com/coinbase/kryptology/pkg/core/curves" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/x/confidentialtransfers/types" + "github.com/sei-protocol/sei-chain/x/confidentialtransfers/utils" + "github.com/sei-protocol/sei-cryptography/pkg/encryption" + "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" + "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "math" + "math/big" +) + +// Tests the InitializeAccount method of the MsgServer. +func (suite *KeeperTestSuite) TestMsgServer_InitializeAccountBasic() { + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Test empty request + req := &types.MsgInitializeAccount{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + } + _, err := suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing using struct with missing fields") + + // Happy Path + initializeStruct, _ := types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error initializing valid account state") + + // Check that account exists in storage now + account, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().True(exists, "Account should exist after successful initialization") + + // Check that account state matches the one submitted. + initializeStructPubkey := *initializeStruct.Pubkey + suite.Require().Equal(initializeStructPubkey.ToAffineCompressed(), account.PublicKey.ToAffineCompressed(), "Public keys should match") + suite.Require().Equal(uint16(0), account.PendingBalanceCreditCounter, "PendingBalanceCreditCounter should be 0") + suite.Require().Equal(initializeStruct.DecryptableBalance, account.DecryptableAvailableBalance, "DecryptableAvailableBalance should match") + suite.Require().True(initializeStruct.PendingBalanceLo.C.Equal(account.PendingBalanceLo.C), "PendingBalanceLo.C should match") + suite.Require().True(initializeStruct.PendingBalanceLo.D.Equal(account.PendingBalanceLo.D), "PendingBalanceLo.D should match") + suite.Require().True(initializeStruct.PendingBalanceHi.C.Equal(account.PendingBalanceHi.C), "PendingBalanceHi.C should match") + suite.Require().True(initializeStruct.PendingBalanceHi.D.Equal(account.PendingBalanceHi.D), "PendingBalanceHi.D should match") + suite.Require().True(initializeStruct.AvailableBalance.C.Equal(account.AvailableBalance.C), "AvailableBalance.C should match") + suite.Require().True(initializeStruct.AvailableBalance.D.Equal(account.AvailableBalance.D), "AvailableBalance.D should match") + + // Try to initialize the account again - this should produce an error + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account that already exists") + suite.Require().ErrorContains(err, "account already exists") + + // Try to initialize another account for a different denom + otherDenom := "otherdenom" + initializeStruct, _ = types.NewInitializeAccount(testAddr.String(), otherDenom, *testPk) + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error initializing valid account state on a different denom") + + // Check that other account exists in storage as well + _, exists = suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), otherDenom) + suite.Require().True(exists, "Account should exist after successful initialization") + + // Check that initial account still exists independently and is unchanged. + accountAgain, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().True(exists, "Account should still exist") + suite.Require().Equal(account, accountAgain, "Account should be unchanged") +} + +// Tests scenarios in which a user tries to Initialize an account with a pubkey that doesn't match the proofs. +func (suite *KeeperTestSuite) TestMsgServer_InitializeAccountModifyPubkey() { + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Test that modifying the PublicKey without modifying the proof fails the PubkeyValidityProof test. + initializeStruct, _ := types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + + // Modify the pubkey used after. + otherPk, _ := crypto.GenerateKey() + teg := elgamal.NewTwistedElgamal() + otherKeyPair, _ := teg.KeyGen(*otherPk, DefaultTestDenom) + initializeStruct.Pubkey = &otherKeyPair.PublicKey + + req := types.NewMsgInitializeAccountProto(initializeStruct) + _, err := suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with mismatched pubkey") + suite.Require().ErrorContains(err, "invalid public key") + + // Check that account does not exist in storage + _, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().False(exists, "Account should not exist after failed initialization") + + // Now try modifying the Pubkey Validity Proof as well. + // This should still throw an error as the ZeroBalanceProofs will fail, since the balances were generated with the original Pubkey. + otherKeyPairProof, _ := zkproofs.NewPubKeyValidityProof(otherKeyPair.PublicKey, otherKeyPair.PrivateKey) + initializeStruct.Proofs.PubkeyValidityProof = otherKeyPairProof + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with mismatched pubkey despite valid PubkeyValidityProof") + suite.Require().ErrorContains(err, "invalid pending balance lo") +} + +// Tests scenarios where the client tries to initialize an account with balances that are not zero. +func (suite *KeeperTestSuite) TestMsgServer_InitializeAccountModifyBalances() { + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Create a ciphertext on a non zero value. + teg := elgamal.NewTwistedElgamal() + keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom) + nonZeroCiphertext, _, _ := teg.Encrypt(keyPair.PublicKey, 100000) + + // Generate a 'ZeroBalanceProof' on the non-zero balance + // Proof generation should be successful, but the initialization should still fail. + zeroBalanceProof, err := zkproofs.NewZeroBalanceProof(keyPair, nonZeroCiphertext) + suite.Require().NoError(err, "Should not have error creating zero balance proof") + + // Test that submitting an initialization request with non-zero balances will fail. + initializeStruct, err := types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + suite.Require().NoError(err, "Should not have error creating account state") + + // Modify the available balance. This should fail the zero balance check for the available balance. + initializeStruct.AvailableBalance = nonZeroCiphertext + req := types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero available balance") + suite.Require().ErrorContains(err, "invalid available balance") + + // Try modifying the proof as well. + initializeStruct.Proofs.ZeroAvailableBalanceProof = zeroBalanceProof + req = types.NewMsgInitializeAccountProto(initializeStruct) + + // ZeroBalanceProof validation on ZeroAvailableBalance should fail. + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero available balance despite generating a proof on it.") + suite.Require().ErrorContains(err, "invalid available balance") + + // Repeat tests for PendingAmountLo + initializeStruct, _ = types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + + // Modify the pending balance lo. This should fail the zero balance check for the pendingBalanceLo. + initializeStruct.PendingBalanceLo = nonZeroCiphertext + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero pending balance lo") + suite.Require().ErrorContains(err, "invalid pending balance lo") + + // Try modifying the proof as well. + initializeStruct.Proofs.ZeroPendingBalanceLoProof = zeroBalanceProof + req = types.NewMsgInitializeAccountProto(initializeStruct) + + // ZeroBalanceProof validation on PendingBalanceLo should fail. + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero pending balance lo despite generating a proof on it.") + suite.Require().ErrorContains(err, "invalid pending balance lo") + + // Repeat tests for PendingAmountHi + initializeStruct, _ = types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + + // Modify the pending balance hi. This should fail the zero balance check for the pendingBalanceHi. + initializeStruct.PendingBalanceHi = nonZeroCiphertext + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero pending balance hi") + suite.Require().ErrorContains(err, "invalid pending balance hi") + + // Try modifying the proof as well. + initializeStruct.Proofs.ZeroPendingBalanceHiProof = zeroBalanceProof + req = types.NewMsgInitializeAccountProto(initializeStruct) + + // ZeroBalanceProof validation on PendingBalanceHi should fail. + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error initializing account with non-zero pending balance hi despite generating a proof on it.") + suite.Require().ErrorContains(err, "invalid pending balance hi") + +} + +// Validate alternate scenarios that are technically allowed, but will cause incompatibility with the client. +func (suite *KeeperTestSuite) TestMsgServer_InitializeAccountAlternateHappyPaths() { + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Test that tampering with the denom will still lead to a successful initialization. + // However, since the client generates the keys based on the denom, + // all the fields will be encrypted with a different PublicKe than the one the client would use. + // As a result, the client will not be able to use the account. + initializeStruct, _ := types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + + // Modify the denom + otherDenom := "otherdenom" + initializeStruct.Denom = otherDenom + req := types.NewMsgInitializeAccountProto(initializeStruct) + _, err := suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error initializing account with different denom") + + _, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), otherDenom) + suite.Require().True(exists, "Account should exist after successful initialization") + + // Test that modifying the decryptableBalance will still lead to a successful initialization. + // The decryptable balance is just a convenience feature that allows the user to keep track of their balance (AvailableBalance) + // Corrupting this field will not affect the account's functionality, but will render it unusable by the client. + // If the user loses track of their balance, they may be unable to recover their funds from the account since the AvailableBalance may not be decryptable. + initializeStruct, _ = types.NewInitializeAccount(testAddr.String(), DefaultTestDenom, *testPk) + initializeStruct.DecryptableBalance = "corrupted" + req = types.NewMsgInitializeAccountProto(initializeStruct) + _, err = suite.msgServer.InitializeAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error initializing account despite corrupted decryptable balance") + + _, exists = suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().True(exists, "Account should exist after successful initialization") +} + +/// DEPOSIT TESTS + +// Tests the Deposit method of the MsgServer. +func (suite *KeeperTestSuite) TestMsgServer_DepositBasic() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + teg := elgamal.NewTwistedElgamal() + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + // Initialize an account + bankModuleInitialAmount := uint64(1000000000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, 1000000, 8000, bankModuleInitialAmount) + + // Test empty request + req := &types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + } + _, err := suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error depositing without amount") + suite.Require().ErrorContains(err, "invalid request") + + // Happy path + depositStruct := types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: 20000, + } + + _, err = suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().NoError(err, "Should not have error depositing with valid request") + + // Check that the account has been updated + account, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + // Check that available balances were not touched by this operation + suite.Require().Equal(initialState.AvailableBalance.C.ToAffineCompressed(), account.AvailableBalance.C.ToAffineCompressed(), "AvailableBalance.C should not have been touched") + suite.Require().Equal(initialState.AvailableBalance.D.ToAffineCompressed(), account.AvailableBalance.D.ToAffineCompressed(), "AvailableBalance.D should not have been touched") + suite.Require().Equal(initialState.DecryptableAvailableBalance, account.DecryptableAvailableBalance, "DecryptableAvailableBalance should not have been touched") + + // Check that pending balance counter increased by 1 + suite.Require().Equal(initialState.PendingBalanceCreditCounter+1, account.PendingBalanceCreditCounter, "PendingBalanceCreditCounter should have increased by 1") + + // Check that pending balances were increased by the deposit amount + keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom) + oldPendingBalancePlaintext, _ := initialState.GetPendingBalancePlaintext(teg, keyPair) + + newPendingBalancePlaintext, _ := account.GetPendingBalancePlaintext(teg, keyPair) + + // Check that newPendingBalancePlaintext = oldPendingBalancePlaintext + DepositAmount + suite.Require().Equal( + new(big.Int).Add(oldPendingBalancePlaintext, new(big.Int).SetUint64(depositStruct.Amount)), + newPendingBalancePlaintext, + "Pending balances should have increased by the deposit amount") + + // Lastly check that the amount in the bank module are changed correctly. + testAddrBalance := suite.App.BankKeeper.GetBalance(suite.Ctx, testAddr, DefaultTestDenom) + suite.Require().Equal(bankModuleInitialAmount-depositStruct.Amount, testAddrBalance.Amount.Uint64(), "Addresses token balance should have decreased by the deposit amount") + + // Check that the amount in the bank module has increased by the deposit amount (Assuming module account balance starts from 0) + moduleAccount := suite.App.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + moduleBalance := suite.App.BankKeeper.GetBalance(suite.Ctx, moduleAccount.GetAddress(), DefaultTestDenom) + suite.Require().Equal(depositStruct.Amount, moduleBalance.Amount.Uint64(), "Module account balance should have increased by the deposit amount") + + // Test Large Deposit + depositStruct = types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: 50000000000, + } + + _, err = suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().NoError(err, "Should not have error depositing large amount with valid request") + + // Check that the account has been updated + updatedAccount, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + oldPendingBalancePlaintext = newPendingBalancePlaintext + newPendingBalancePlaintext, _ = updatedAccount.GetPendingBalancePlaintext(teg, keyPair) + suite.Require().Equal( + new(big.Int).Add(oldPendingBalancePlaintext, new(big.Int).SetUint64(depositStruct.Amount)), + newPendingBalancePlaintext, + "Pending balances should have increased by the deposit amount") + + // Check that the amount in the bank module are changed correctly. + oldTestAddrBalance := testAddrBalance + testAddrBalance = suite.App.BankKeeper.GetBalance(suite.Ctx, testAddr, DefaultTestDenom) + suite.Require().Equal(oldTestAddrBalance.Amount.Uint64()-depositStruct.Amount, testAddrBalance.Amount.Uint64(), "Addresses token balance should have decreased by the deposit amount") + + // Check that the amount in the bank module has increased by the deposit amount (Assuming module account balance starts from 0) + moduleAccount = suite.App.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + oldModuleBalance := moduleBalance + moduleBalance = suite.App.BankKeeper.GetBalance(suite.Ctx, moduleAccount.GetAddress(), DefaultTestDenom) + suite.Require().Equal(oldModuleBalance.Amount.Uint64()+depositStruct.Amount, moduleBalance.Amount.Uint64(), "Module account balance should have increased by the deposit amount") +} + +// Tests scenario in which the client tries to deposit into an account that has not been initialized. +func (suite *KeeperTestSuite) TestMsgServer_DepositUninitialized() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + depositStruct := types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: 20000, + } + + // Test depositing into uninitialized account + _, err := suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().Error(err, "Should have error depositing into uninitialized account") +} + +// Tests scenario in which user has insufficient balances for deposit. +func (suite *KeeperTestSuite) TestMsgServer_DepositInsufficientFunds() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + // Initialize an account + bankModuleInitialAmount := uint64(1000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, 1000000, 8000, bankModuleInitialAmount) + + // Create a struct where the deposit amount is greater than the amount of token the user has. + depositStruct := types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: bankModuleInitialAmount + 1, + } + + // Test depositing into account with insufficient funds + _, err := suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().Error(err, "Should have error depositing more than available balance in bank module") + suite.Require().ErrorContains(err, "insufficient funds to deposit") + + // Test that account state is untouched + account, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + // Check that pending balances were not touched by this operation + suite.Require().Equal(initialState.PendingBalanceLo.C.ToAffineCompressed(), account.PendingBalanceLo.C.ToAffineCompressed(), "PendingBalanceLo.C should not have been modified by failed instruction") + suite.Require().Equal(initialState.PendingBalanceLo.D.ToAffineCompressed(), account.PendingBalanceLo.D.ToAffineCompressed(), "PendingBalanceLo.D should not have been modified by failed instruction") + suite.Require().Equal(initialState.PendingBalanceHi.C.ToAffineCompressed(), account.PendingBalanceHi.C.ToAffineCompressed(), "PendingBalanceHi.C should not have been modified by failed instruction") + suite.Require().Equal(initialState.PendingBalanceHi.D.ToAffineCompressed(), account.PendingBalanceHi.D.ToAffineCompressed(), "PendingBalanceHi.D should not have been modified by failed instruction") +} + +// Tests scenario in which user tries to deposit a greater than 48 bit number +func (suite *KeeperTestSuite) TestMsgServer_DepositOversizedDeposit() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + // Initialize an account + bankModuleInitialAmount := uint64(1000) + _, _ = suite.SetupAccountState(testPk, DefaultTestDenom, 50, 1000000, 8000, bankModuleInitialAmount) + + // Create a struct where the deposit amount is greater than a 48 bit number + depositStruct := types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: (2 << 48) + 1, + } + + // Test depositing an amount larger than 48 bits. + _, err := suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().Error(err, "Should not be able to deposit an amount larger than 48 bits") + suite.Require().ErrorContains(err, "exceeded maximum deposit amount of 2^48") +} + +// Tests scenario in which user tries to deposit into an amount with too many pending balances +func (suite *KeeperTestSuite) TestMsgServer_DepositTooManyPendingBalances() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + + // Generate the address from the private key + testAddr := privkeyToAddress(testPk) + + // Create an account where the pending balance counter is at the maximum value + bankModuleInitialAmount := uint64(10000000000) + suite.SetupAccountState(testPk, DefaultTestDenom, math.MaxUint16, 1000000, 8000, bankModuleInitialAmount) + + // Create a struct where the deposit amount is greater than a 48 bit number + depositStruct := types.MsgDeposit{ + FromAddress: testAddr.String(), + Denom: DefaultTestDenom, + Amount: 20000, + } + + // Test depositing an amount larger than 48 bits. + _, err := suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct) + suite.Require().Error(err, "Should not be able to deposit an amount when pending balance counter is at maximum value") + suite.Require().ErrorContains(err, "account has too many pending transactions") +} + +// WITHDRAW TESTS + +// Tests the Withdraw method of the MsgServer. +func (suite *KeeperTestSuite) TestMsgServer_WithdrawHappyPath() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Fund the module account + initialModuleBalance := int64(1000000000000) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(initialModuleBalance)))) + + _ = suite.App.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(1000000000000)))) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account + bankModuleInitialAmount := uint64(1000000000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, 1000000, 8000, bankModuleInitialAmount) + + // Create a withdraw request + withdrawAmount := uint64(50000) + withdrawStruct, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount) + + // Execute the withdraw + req := types.NewMsgWithdrawProto(withdrawStruct) + _, err := suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error calling valid withdraw") + + // Check that the account has been updated + account, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + // Check that pending balances are left untouched + suite.Require().Equal(initialState.PendingBalanceLo.C.ToAffineCompressed(), account.PendingBalanceLo.C.ToAffineCompressed(), "PendingBalanceLo.C should not have been modified by withdraw") + suite.Require().Equal(initialState.PendingBalanceLo.D.ToAffineCompressed(), account.PendingBalanceLo.D.ToAffineCompressed(), "PendingBalanceLo.D should not have been modified by withdraw") + suite.Require().Equal(initialState.PendingBalanceHi.C.ToAffineCompressed(), account.PendingBalanceHi.C.ToAffineCompressed(), "PendingBalanceHi.C should not have been modified by withdraw") + suite.Require().Equal(initialState.PendingBalanceHi.D.ToAffineCompressed(), account.PendingBalanceHi.D.ToAffineCompressed(), "PendingBalanceHi.D should not have been modified by withdraw") + suite.Require().Equal(initialState.PendingBalanceCreditCounter, account.PendingBalanceCreditCounter, "PendingBalanceCreditCounter should not have been modified by withdraw") + + // Check that available balances were updated correctly + teg := elgamal.NewTwistedElgamal() + keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom) + oldBalanceDecrypted, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, initialState.AvailableBalance, elgamal.MaxBits40) + newBalanceDecrypted, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.AvailableBalance, elgamal.MaxBits40) + newTotal := oldBalanceDecrypted - withdrawAmount + suite.Require().Equal(newTotal, newBalanceDecrypted) + + // Check that the DecryptableAvailableBalances were updated correctly + suite.Require().Equal(req.DecryptableBalance, account.DecryptableAvailableBalance) + + // Check that account balances were updated properly + moduleAccount := suite.App.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + moduleBalance := suite.App.BankKeeper.GetBalance(suite.Ctx, moduleAccount.GetAddress(), DefaultTestDenom) + suite.Require().Equal(uint64(initialModuleBalance)-withdrawAmount, moduleBalance.Amount.Uint64(), "Module account balance should have decreased by the withdraw amount") + + userBalance := suite.App.BankKeeper.GetBalance(suite.Ctx, testAddr, DefaultTestDenom) + suite.Require().Equal(bankModuleInitialAmount+withdrawAmount, userBalance.Amount.Uint64(), "User balance should have increased by the withdraw amount") +} + +// Test that we cannot perform successive withdraws. The second withdraw struct is invalidated after the first withdraw. +func (suite *KeeperTestSuite) TestMsgServer_WithdrawSuccessive() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Fund the module account + initialModuleBalance := int64(1000000000000) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(initialModuleBalance)))) + + _ = suite.App.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(1000000000000)))) + + // Initialize an account + initialAvailableBalance := uint64(1000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, 1000000000000) + + // Create a withdraw request with an invalid amount + withdrawAmount := initialAvailableBalance + 1 + _, err := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount) + suite.Require().Error(err, "Cannot use client to create withdraw for more than the account balance") + suite.Require().ErrorContains(err, "insufficient balance") + + // Create two withdraw requests for the entire balance + withdrawStruct1, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, initialAvailableBalance) + withdrawStruct2, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, initialAvailableBalance) + + // Execute the first withdraw + req1 := types.NewMsgWithdrawProto(withdrawStruct1) + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req1) + suite.Require().NoError(err, "Should not have error calling first valid withdraw") + + // Execute the second withdraw + req2 := types.NewMsgWithdrawProto(withdrawStruct2) + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req2) + suite.Require().Error(err, "The withdraw should have failed since the withdraw struct is now invalid (has wrong newBalanceCommitment)") + suite.Require().ErrorContains(err, "ciphertext commitment equality verification failed") +} + +// Test that we cannot perform withdraws with an invalid amount. +func (suite *KeeperTestSuite) TestMsgServer_WithdrawInvalidAmount() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Fund the module account + initialModuleBalance := int64(1000000000000) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(initialModuleBalance)))) + + _ = suite.App.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(1000000000000)))) + + // Initialize an account + initialAvailableBalance := uint64(1000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, 1000000000000) + + // Create a withdraw request + withdrawAmount := initialAvailableBalance + withdrawStruct, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount) + + // Manually modify the withdraw struct to have an invalid amount (since we can't do that via the client) + withdrawStruct.Amount = initialAvailableBalance + 1 + + // Try executing the withdraw. This should fail since the proofs generated before are invalid + req := types.NewMsgWithdrawProto(withdrawStruct) + _, err := suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "The withdraw should have failed since the withdraw struct has an invalid amount") + suite.Require().ErrorContains(err, "ciphertext commitment equality verification failed") + + // Try creating proofs on the new withdraw amount. This should not work since range proofs cannnot be generated on negative numbers. + teg := elgamal.NewTwistedElgamal() + keys, _ := teg.KeyGen(*testPk, DefaultTestDenom) + newBalanceNegative := int64(initialAvailableBalance) - int64(withdrawStruct.Amount) + + // NOTE: This should be encrypting a negative number, but this cannot be done using the teg library. + // This is not important for the test since we just want to test that we cannot create a range proof on a negative number. + _, randomness, _ := teg.Encrypt(keys.PublicKey, 0) + + _, err = zkproofs.NewRangeProof(64, int(newBalanceNegative), randomness) + suite.Require().Error(err, "Cannot create a range proof on a negative number") +} + +// Test that we cannot reuse a withdraw struct even if the account has sufficient funds to support it twice. +func (suite *KeeperTestSuite) TestMsgServer_RepeatWithdraw() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Fund the module account + initialModuleBalance := int64(1000000000000) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(initialModuleBalance)))) + + _ = suite.App.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(1000000000000)))) + + // Initialize an account + initialAvailableBalance := uint64(1000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, 1000000000000) + + // Create a withdraw request + withdrawAmount := initialAvailableBalance / 5 + withdrawStruct, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount) + + // Execute the first withdraw + req := types.NewMsgWithdrawProto(withdrawStruct) + _, err := suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error calling valid withdraw") + + // Execute the same withdraw again + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should not be able to repeat withdraw") + suite.Require().ErrorContains(err, "ciphertext commitment equality verification failed") +} + +// Tetst the scenario where +func (suite *KeeperTestSuite) TestMsgServer_ModifiedDecryptableBalance() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Fund the module account + initialModuleBalance := int64(1000000000000) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(initialModuleBalance)))) + + _ = suite.App.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(sdk.NewCoin(DefaultTestDenom, sdk.NewInt(1000000000000)))) + + // Initialize an account + initialAvailableBalance := uint64(1000000) + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, 1000000000000) + + // Create a withdraw request + withdrawAmount := initialAvailableBalance / 5 + withdrawStruct, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount) + + // Modify the decryptable balance + aesKey, _ := encryption.GetAESKey(*testPk, DefaultTestDenom) + fraudulentDecryptableBalance, _ := encryption.EncryptAESGCM(10000000000, aesKey) + withdrawStruct.DecryptableBalance = fraudulentDecryptableBalance + + // Execute the withdraw + req := types.NewMsgWithdrawProto(withdrawStruct) + _, err := suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error calling withdraw despite incorrect decryptable balance submitted") + + // At this point, the decryptable available balance is corrupted. + // Any withdraw struct we create based on the decryptable balance in the account will be invalid. + accountState, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + nextWithdrawStruct, err := types.NewWithdraw(*testPk, accountState.AvailableBalance, DefaultTestDenom, testAddr.String(), accountState.DecryptableAvailableBalance, withdrawAmount) + req = types.NewMsgWithdrawProto(nextWithdrawStruct) + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error withdrawing since withdraw struct is invalid") + suite.Require().ErrorContains(err, "range proof verification failed") + + // We can still fix this at this point if we know the correct decryptable balance. + // This is because the account state is still correct; the decryptable balance is just a convenience feature. + // The rest of this test shows how to manually create a withdraw struct that will be accepted by the server. + + // First get the actual balance in the account by decrypting the available balance. + // This will only work if the encrypted value is small enough to be decrypted. + teg := elgamal.NewTwistedElgamal() + keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom) + actualBalance, err := teg.DecryptLargeNumber(keyPair.PrivateKey, accountState.AvailableBalance, elgamal.MaxBits40) + suite.Require().NoError(err, "Should be able to decrypt actual balance since the encrypted value is small") + + // Re-encrypt the actual balance as the current decryptable balance. + aesEncryptedActualBalance, _ := encryption.EncryptAESGCM(actualBalance, aesKey) + + // Then create the correct struct for the withdraw + correctedWithdrawStruct, err := types.NewWithdraw(*testPk, accountState.AvailableBalance, DefaultTestDenom, testAddr.String(), aesEncryptedActualBalance, withdrawAmount) + + // Execute the withdraw + req = types.NewMsgWithdrawProto(correctedWithdrawStruct) + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error calling withdraw despite incorrect decryptable balance submitted") + + // Validate that the number is correct + accountState, _ = suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + decryptedAvailableBalance, err := teg.DecryptLargeNumber(keyPair.PrivateKey, accountState.AvailableBalance, elgamal.MaxBits40) + suite.Require().NoError(err, "Should be decryptable") + newBalance := actualBalance - withdrawAmount + suite.Require().Equal(decryptedAvailableBalance, newBalance, "New account value should have been updated") + + // Validate that I can create regular transactions with the account again + nextWithdrawStruct, err = types.NewWithdraw(*testPk, accountState.AvailableBalance, DefaultTestDenom, testAddr.String(), accountState.DecryptableAvailableBalance, 1) + req = types.NewMsgWithdrawProto(nextWithdrawStruct) + _, err = suite.msgServer.Withdraw(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error withdrawing since decryptable balance is no longer corrupted") +} + +// Tests the CloseAccount method of the MsgServer. +func (suite *KeeperTestSuite) TestMsgServer_CloseAccountHappyPath() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 0, 0, 0, 100) + + // Create a close account request + closeAccountStruct, err := types.NewCloseAccount( + *testPk, + testAddr.String(), + DefaultTestDenom, + initialState.PendingBalanceLo, + initialState.PendingBalanceHi, + initialState.AvailableBalance) + suite.Require().NoError(err, "Should not have error creating close account struct") + + // Execute the close account + req := types.NewMsgCloseAccountProto(closeAccountStruct) + _, err = suite.msgServer.CloseAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error closing account") + + // Check that the account has been deleted + _, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().False(exists, "Account should not exist") + + // Try sending the close account instruction again. This should fail now since the account doesn't exist. + _, err = suite.msgServer.CloseAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error closing account that doesn't exist") + suite.Require().ErrorContains(err, "account does not exist") + +} + +func (suite *KeeperTestSuite) TestMsgServer_CloseAccountHasPendingBalance() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account that still has pending balances. + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 3, 0, 200, 100) + + // Create a close account request + closeAccountStruct, _ := types.NewCloseAccount( + *testPk, + testAddr.String(), + DefaultTestDenom, + initialState.PendingBalanceLo, + initialState.PendingBalanceHi, + initialState.AvailableBalance) + + // Execute the close account. This should fail since the account has pending balances on it. + req := types.NewMsgCloseAccountProto(closeAccountStruct) + _, err := suite.msgServer.CloseAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error closing account with pending balance") + suite.Require().ErrorContains(err, "pending balance lo must be 0") + + // Check that the account still exists + _, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().True(exists, "Account should still exist") +} + +// Test the scenario where a close account instruction is applied for an account that still contains available balances. +func (suite *KeeperTestSuite) TestMsgServer_CloseAccountHasAvailableBalance() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account + initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 0, 900, 0, 100) + + // Create a close account request + closeAccountStruct, _ := types.NewCloseAccount( + *testPk, + testAddr.String(), + DefaultTestDenom, + initialState.PendingBalanceLo, + initialState.PendingBalanceHi, + initialState.AvailableBalance) + + // Execute the close account. This should fail since the account still has available balances. + req := types.NewMsgCloseAccountProto(closeAccountStruct) + _, err := suite.msgServer.CloseAccount(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error closing account with available balance") + suite.Require().ErrorContains(err, "available balance must be 0") + + // Check that the account still exists + _, exists := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + suite.Require().True(exists, "Account should still exist") +} + +// Tests the ApplyPendingBalance method of the MsgServer. +func (suite *KeeperTestSuite) TestMsgServer_ApplyPendingBalance() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account + initialAvailableBalance := uint64(20000000) + initialPendingBalance := uint64(100000) + suite.SetupAccountState(testPk, DefaultTestDenom, 10, initialAvailableBalance, initialPendingBalance, 1000) + + // Create an apply pending balance request + aesKey, _ := encryption.GetAESKey(*testPk, DefaultTestDenom) + newBalance := initialAvailableBalance + initialPendingBalance + newDecryptableBalance, _ := encryption.EncryptAESGCM(newBalance, aesKey) + req := types.MsgApplyPendingBalance{ + testAddr.String(), + DefaultTestDenom, + newDecryptableBalance, + } + + // Execute the apply pending balance + _, err := suite.msgServer.ApplyPendingBalance(sdk.WrapSDKContext(suite.Ctx), &req) + suite.Require().NoError(err, "Should not have error applying pending balance") + + // Check that the account has been updated + account, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + // Decrypt and check balances + teg := elgamal.NewTwistedElgamal() + keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom) + + // Check that the balances were correctly added to the available balance. + actualAvailableBalance, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.AvailableBalance, elgamal.MaxBits40) + suite.Require().Equal(newBalance, actualAvailableBalance, "Available balance should match") + + actualDecryptableAvailableBalance, _ := encryption.DecryptAESGCM(account.DecryptableAvailableBalance, aesKey) + suite.Require().Equal(newBalance, actualDecryptableAvailableBalance, "Decryptable available balance should match") + + // Check that the pending balances are set to 0. + actualPendingBalanceLo, _ := teg.Decrypt(keyPair.PrivateKey, account.PendingBalanceLo, elgamal.MaxBits32) + suite.Require().Equal(uint64(0), actualPendingBalanceLo, "Pending balance lo not 0") + + actualPendingBalanceHi, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.PendingBalanceHi, elgamal.MaxBits48) + suite.Require().Equal(uint64(0), actualPendingBalanceHi, "Pending balance hi not 0") + + // Check that the pending balance credit counter is reset to 0. + suite.Require().Equal(uint16(0), account.PendingBalanceCreditCounter, "Pending balance credit counter should be set to 0 after applying") +} + +// Tests the ApplyPendingBalance method of the MsgServer on an account with no Pending Balances or doesn't exist. These should both fail. +func (suite *KeeperTestSuite) TestMsgServer_ApplyPendingBalanceNoPendingBalances() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + testPk := suite.PrivKeys[0] + testAddr := privkeyToAddress(testPk) + + // Initialize an account + initialAvailableBalance := uint64(20000000) + suite.SetupAccountState(testPk, DefaultTestDenom, 0, initialAvailableBalance, uint64(0), 1000) + + // Create an apply pending balance request + aesKey, _ := encryption.GetAESKey(*testPk, DefaultTestDenom) + newDecryptableBalance, _ := encryption.EncryptAESGCM(initialAvailableBalance, aesKey) + req := types.MsgApplyPendingBalance{ + testAddr.String(), + DefaultTestDenom, + newDecryptableBalance, + } + + // Execute the apply pending balance. This should fail since there are no pending balances to apply. + _, err := suite.msgServer.ApplyPendingBalance(sdk.WrapSDKContext(suite.Ctx), &req) + suite.Require().Error(err, "Should have error applying pending balance on account with no pending balances") + + // Delete the account so we can test running the instruction on an account that doesn't exist. + suite.App.ConfidentialTransfersKeeper.DeleteAccount(suite.Ctx, testAddr.String(), DefaultTestDenom) + + // Execute the apply pending balance. This should fail since the account doesn't exist. + _, err = suite.msgServer.ApplyPendingBalance(sdk.WrapSDKContext(suite.Ctx), &req) + suite.Require().Error(err, "Should have error applying pending balance on account that doesn't exist") + suite.Require().ErrorContains(err, "account does not exist") +} + +func (suite *KeeperTestSuite) TestMsgServer_TransferHappyPath() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Setup the accounts used for the test + senderPk := suite.PrivKeys[0] + senderAddr := privkeyToAddress(senderPk) + recipientPk := suite.PrivKeys[1] + recipientAddr := privkeyToAddress(recipientPk) + auditorPk := suite.PrivKeys[2] + auditorAddr := privkeyToAddress(auditorPk) + + // Initialize an account + initialSenderState, _ := suite.SetupAccountState(senderPk, DefaultTestDenom, 10, 2000, 3000, 1000) + initialRecipientState, _ := suite.SetupAccountState(recipientPk, DefaultTestDenom, 12, 5000, 21000, 201000) + initialAuditorState, _ := suite.SetupAccountState(auditorPk, DefaultTestDenom, 12, 5000, 21000, 201000) + + teg := elgamal.NewTwistedElgamal() + senderKeypair, _ := teg.KeyGen(*senderPk, DefaultTestDenom) + + recipientKeypair, _ := teg.KeyGen(*recipientPk, DefaultTestDenom) + + transferAmount := uint64(500) + + // Happy Path + auditorsInput := []types.AuditorInput{{auditorAddr.String(), &initialAuditorState.PublicKey}} + transferStruct, err := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + transferAmount, + &initialRecipientState.PublicKey, + auditorsInput) + suite.Require().NoError(err, "Should not have error creating valid transfer struct") + + req := types.NewMsgTransferProto(transferStruct) + _, err = suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().NoError(err, "Should not have error calling valid transfer") + + senderAccountState, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, senderAddr.String(), DefaultTestDenom) + + // Pending Balances should not be altered by this instruction + suite.Require().Equal(initialSenderState.PendingBalanceLo.C.ToAffineCompressed(), senderAccountState.PendingBalanceLo.C.ToAffineCompressed(), "PendingBalanceLo should not have been touched") + suite.Require().Equal(initialSenderState.PendingBalanceLo.D.ToAffineCompressed(), senderAccountState.PendingBalanceLo.D.ToAffineCompressed(), "PendingBalanceLo should not have been touched") + suite.Require().Equal(initialSenderState.PendingBalanceHi.C.ToAffineCompressed(), senderAccountState.PendingBalanceHi.C.ToAffineCompressed(), "PendingBalanceHi should not have been touched") + suite.Require().Equal(initialSenderState.PendingBalanceHi.D.ToAffineCompressed(), senderAccountState.PendingBalanceHi.D.ToAffineCompressed(), "PendingBalanceHi should not have been touched") + suite.Require().Equal(initialSenderState.PendingBalanceCreditCounter, senderAccountState.PendingBalanceCreditCounter, "PendingBalanceCreditCounter should not have been touched") + + // NonEncryptableBalance and Account metadata should also not be altered by this instruction. + suite.Require().Equal(initialSenderState.PublicKey.ToAffineCompressed(), senderAccountState.PublicKey.ToAffineCompressed(), "PublicKey should not have been touched") + + // Check that new balance encrypts the sum of oldBalance and withdrawAmount + senderOldBalanceDecrypted, _ := teg.DecryptLargeNumber(senderKeypair.PrivateKey, initialSenderState.AvailableBalance, elgamal.MaxBits40) + senderNewBalanceDecrypted, _ := teg.DecryptLargeNumber(senderKeypair.PrivateKey, senderAccountState.AvailableBalance, elgamal.MaxBits40) + suite.Require().Equal(senderOldBalanceDecrypted-transferAmount, senderNewBalanceDecrypted, "AvailableBalance of sender should be decreased") + + // Verify that the DecryptableAvailableBalances were updated as well and that they match the available balances. + senderAesKey, _ := encryption.GetAESKey(*senderPk, DefaultTestDenom) + senderOldDecryptableBalanceDecrypted, _ := encryption.DecryptAESGCM(initialSenderState.DecryptableAvailableBalance, senderAesKey) + senderNewDecryptableBalanceDecrypted, _ := encryption.DecryptAESGCM(senderAccountState.DecryptableAvailableBalance, senderAesKey) + suite.Require().Equal(senderOldDecryptableBalanceDecrypted-transferAmount, senderNewDecryptableBalanceDecrypted) + suite.Require().Equal(senderNewBalanceDecrypted, senderNewDecryptableBalanceDecrypted) + + // On the other hand, available balances of the recipient account should not have been altered + recipientAccountState, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, recipientAddr.String(), DefaultTestDenom) + suite.Require().Equal(initialRecipientState.AvailableBalance.C.ToAffineCompressed(), recipientAccountState.AvailableBalance.C.ToAffineCompressed(), "AvailableBalance should not have been touched") + suite.Require().Equal(initialRecipientState.AvailableBalance.D.ToAffineCompressed(), recipientAccountState.AvailableBalance.D.ToAffineCompressed(), "AvailableBalance should not have been touched") + suite.Require().Equal(initialRecipientState.DecryptableAvailableBalance, recipientAccountState.DecryptableAvailableBalance, "DecryptableAvailableBalance should not have been touched") + + // NonEncryptableBalance and Account metadata should also not be altered by this instruction. + suite.Require().Equal(initialRecipientState.PublicKey.ToAffineCompressed(), recipientAccountState.PublicKey.ToAffineCompressed(), "PublicKey should not have been touched") + + // Check that new pending balances of the recipient account have been updated to reflect the change + suite.Require().Equal(initialRecipientState.PendingBalanceCreditCounter+1, recipientAccountState.PendingBalanceCreditCounter) + oldRecipientPendingBalance, _ := initialRecipientState.GetPendingBalancePlaintext(teg, recipientKeypair) + newRecipientPendingBalance, _ := recipientAccountState.GetPendingBalancePlaintext(teg, recipientKeypair) + + transferAmountBigInt := new(big.Int).SetUint64(transferAmount) + newTotal := new(big.Int).Add(oldRecipientPendingBalance, transferAmountBigInt) + suite.Require().Equal(newTotal, newRecipientPendingBalance, "New pending balance should be equal to transfer amount added to old pending balance") +} + +func (suite *KeeperTestSuite) TestMsgServer_TransferToMaxPendingRecipient() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Setup the accounts used for the test + senderPk := suite.PrivKeys[0] + senderAddr := privkeyToAddress(senderPk) + recipientPk := suite.PrivKeys[1] + recipientAddr := privkeyToAddress(recipientPk) + + // Initialize the sender account + initialSenderState, _ := suite.SetupAccountState(senderPk, DefaultTestDenom, 10, 2000, 3000, 1000) + initialRecipientState, _ := suite.SetupAccountState(recipientPk, DefaultTestDenom, 10, 2000, 3000, 1000) + + // Initialize the recipient account with max pending balances + _, _ = suite.SetupAccountState(recipientPk, DefaultTestDenom, math.MaxUint16, 1000000, 100, 500) + + transferAmount := uint64(50) + + // Attempt to transfer to account with max pending balances + transferStruct, _ := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + transferAmount, + &initialRecipientState.PublicKey, + nil, + ) + + req := types.NewMsgTransferProto(transferStruct) + _, err := suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should not be able to transfer to account with max pending balances") + suite.Require().ErrorContains(err, "recipient account has too many pending transactions") +} + +func (suite *KeeperTestSuite) TestMsgServer_TransferInsufficientBalance() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Setup the accounts used for the test + senderPk := suite.PrivKeys[0] + senderAddr := privkeyToAddress(senderPk) + recipientPk := suite.PrivKeys[1] + recipientAddr := privkeyToAddress(recipientPk) + + // Initialize the sender account + initialSenderState, _ := suite.SetupAccountState(senderPk, DefaultTestDenom, 10, 2000, 3000, 1000) + // Initialize the recipient account + recipientAccountState, _ := suite.SetupAccountState(recipientPk, DefaultTestDenom, 10, 2000, 3000, 1000) + + senderAesKey, _ := encryption.GetAESKey(*senderPk, DefaultTestDenom) + + initialBalance, _ := encryption.DecryptAESGCM(initialSenderState.DecryptableAvailableBalance, senderAesKey) + + // Set transfer amount to greater than the initial balance. + transferAmount := initialBalance + 1 + + // Attempt to create transfer object. + _, err := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + transferAmount, + &recipientAccountState.PublicKey, + nil, + ) + + suite.Require().Error(err, "Should have error creating transfer struct with insufficient balances using the client") + suite.Require().ErrorContains(err, "insufficient balance") + + // First create a regular transfer with a normal transfer amount + transferStruct, _ := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + initialBalance, + &recipientAccountState.PublicKey, + nil, + ) + + // Substitute the transfer amounts after + // Split the transfer amount into bottom 16 bits and top 32 bits. + transferLoBits := uint16(initialBalance & 0xFFFF) + transferHiBits := uint32((initialBalance >> 16) & 0xFFFFFFFF) + + teg := elgamal.NewTwistedElgamal() + senderAmountLo, senderLoRandomness, _ := teg.Encrypt(initialSenderState.PublicKey, uint64(transferLoBits)) + senderAmountHi, senderHiRandomness, _ := teg.Encrypt(initialSenderState.PublicKey, uint64(transferHiBits)) + + recipientAmountLo, recipientLoRandomness, _ := teg.Encrypt(recipientAccountState.PublicKey, uint64(transferLoBits)) + recipientAmountHi, recipientHiRandomness, _ := teg.Encrypt(recipientAccountState.PublicKey, uint64(transferHiBits)) + + transferStruct.SenderTransferAmountLo = senderAmountLo + transferStruct.SenderTransferAmountHi = senderAmountHi + transferStruct.RecipientTransferAmountLo = recipientAmountLo + transferStruct.RecipientTransferAmountHi = recipientAmountHi + + // Try to execute the modified transfer instruction. This should fail since the balances don't match the proof generated + req := types.NewMsgTransferProto(transferStruct) + _, err = suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should have error transferring more than the account balance") + suite.Require().ErrorContains(err, "Failed to verify senderTransferAmountLo") + + // Try to modify the proofs as well + senderLoValidityProof, _ := zkproofs.NewCiphertextValidityProof(&senderLoRandomness, initialSenderState.PublicKey, senderAmountLo, uint64(transferLoBits)) + senderHiValidityProof, _ := zkproofs.NewCiphertextValidityProof(&senderHiRandomness, initialSenderState.PublicKey, senderAmountHi, uint64(transferHiBits)) + recipientLoValidityProof, _ := zkproofs.NewCiphertextValidityProof(&recipientLoRandomness, recipientAccountState.PublicKey, recipientAmountLo, uint64(transferLoBits)) + recipientHiValidityProof, _ := zkproofs.NewCiphertextValidityProof(&recipientHiRandomness, recipientAccountState.PublicKey, recipientAmountHi, uint64(transferHiBits)) + + transferStruct.Proofs.SenderTransferAmountLoValidityProof = senderLoValidityProof + transferStruct.Proofs.SenderTransferAmountHiValidityProof = senderHiValidityProof + transferStruct.Proofs.RecipientTransferAmountLoValidityProof = recipientLoValidityProof + transferStruct.Proofs.RecipientTransferAmountHiValidityProof = recipientHiValidityProof + + // Try to run the bad transfer instruction again. + // This should still fail since the ciphertext commitment equality proof will catch that the NewBalanceCommitment (0) does not equal account.AvailableBalance - transferAmount (-1) + // We can also swap NewBalanceCommitment out to be -1 to make the proof pass, but the instruction should still fail since we cannot generate a range proof on -1 + req = types.NewMsgTransferProto(transferStruct) + _, err = suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should still have error transferring more than the account balance despite modifying the proofs") + suite.Require().ErrorContains(err, "Ciphertext Commitment equality verification failed") +} + +func (suite *KeeperTestSuite) TestMsgServer_TransferWrongRecipient() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + // Setup the accounts used for the test + senderPk := suite.PrivKeys[0] + senderAddr := privkeyToAddress(senderPk) + recipientPk := suite.PrivKeys[1] + recipientAddr := privkeyToAddress(recipientPk) + otherPk := suite.PrivKeys[2] + otherAddr := privkeyToAddress(otherPk) + + // Initialize the sender account + initialSenderState, _ := suite.SetupAccountState(senderPk, DefaultTestDenom, 10, 2000, 3000, 1000) + initialRecipientState, _ := suite.SetupAccountState(recipientPk, DefaultTestDenom, 10, 2000, 3000, 1000) + suite.SetupAccountState(otherPk, DefaultTestDenom, 10, 2000, 3000, 1000) + + senderAesKey, _ := encryption.GetAESKey(*senderPk, DefaultTestDenom) + + initialBalance, _ := encryption.DecryptAESGCM(initialSenderState.DecryptableAvailableBalance, senderAesKey) + + // Set transfer amount to half of the initial balance. + transferAmount := initialBalance / 2 + transferStruct, _ := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + transferAmount, + &initialRecipientState.PublicKey, + nil, + ) + + // Set the transferStruct recipient to the wrong recipient + transferStruct.ToAddress = otherAddr.String() + + // However, since the balance used to calculate the proofs in the transfer structs are false, the equality proof verification will fail + req := types.NewMsgTransferProto(transferStruct) + _, err := suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should fail ciphertext validity proof since we created those ciphertexts using recipient's public key") + suite.Require().ErrorContains(err, "Failed to verify recipientTransferAmountLo") +} + +func (suite *KeeperTestSuite) TestMsgServer_TransferDifferentAmounts() { + suite.Ctx = suite.App.BaseApp.NewContext(false, tmproto.Header{}) + + teg := elgamal.NewTwistedElgamal() + + // Setup the accounts used for the test + senderPk := suite.PrivKeys[0] + senderAddr := privkeyToAddress(senderPk) + recipientPk := suite.PrivKeys[1] + recipientAddr := privkeyToAddress(recipientPk) + + // Initialize the sender account + initialSenderState, _ := suite.SetupAccountState(senderPk, DefaultTestDenom, 10, 2000, 3000, 1000) + initialRecipientState, _ := suite.SetupAccountState(recipientPk, DefaultTestDenom, 10, 2000, 3000, 1000) + + senderAesKey, _ := encryption.GetAESKey(*senderPk, DefaultTestDenom) + + initialBalance, _ := encryption.DecryptAESGCM(initialSenderState.DecryptableAvailableBalance, senderAesKey) + + // Set transfer amount to a fraction of the initial balance. + transferAmount := initialBalance / 5 + transferStruct, _ := types.NewTransfer( + senderPk, + senderAddr.String(), + recipientAddr.String(), + DefaultTestDenom, + initialSenderState.DecryptableAvailableBalance, + initialSenderState.AvailableBalance, + transferAmount, + &initialRecipientState.PublicKey, + nil, + ) + + // Now we change the transfer amounts encoded with the recipient's keys to attempt to send them more than we lose. + fakeTransferAmount := transferAmount * 2 + + // Split the transfer amount into bottom 16 bits and top 32 bits. + transferLoBits, transferHiBits, _ := utils.SplitTransferBalance(fakeTransferAmount) + + // Encrypt the transfer amounts for the recipient + recipientKeyPair, _ := teg.KeyGen(*recipientPk, DefaultTestDenom) + + encryptedTransferLoBits, loBitsRandomness, _ := teg.Encrypt(recipientKeyPair.PublicKey, uint64(transferLoBits)) + + encryptedTransferHiBits, hiBitsRandomness, _ := teg.Encrypt(recipientKeyPair.PublicKey, uint64(transferHiBits)) + + // Set the transferStruct recipient to the new amounts + transferStruct.RecipientTransferAmountLo = encryptedTransferLoBits + transferStruct.RecipientTransferAmountHi = encryptedTransferHiBits + + // Attempt to make the transfer. This call should fail since the ciphertext validity proofs generated are specific to the underlying value and have not been updated. + req := types.NewMsgTransferProto(transferStruct) + _, err := suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should fail validity proof since we created those proofs using ciphertexts on the original value.") + suite.Require().ErrorContains(err, "Failed to verify recipientTransferAmountLo") + + // Generate the validity proofs of the new amounts + loBitsValidityProof, _ := zkproofs.NewCiphertextValidityProof(&loBitsRandomness, recipientKeyPair.PublicKey, encryptedTransferLoBits, uint64(transferLoBits)) + hiBitsValidityProof, _ := zkproofs.NewCiphertextValidityProof(&hiBitsRandomness, recipientKeyPair.PublicKey, encryptedTransferHiBits, uint64(transferHiBits)) + + transferStruct.Proofs.RecipientTransferAmountLoValidityProof = loBitsValidityProof + transferStruct.Proofs.RecipientTransferAmountHiValidityProof = hiBitsValidityProof + + // However, since the equality proofs are generated on the original recipient transfer amounts, the equality proof verification will fail + req = types.NewMsgTransferProto(transferStruct) + _, err = suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should fail equality proof since we created those proofs using ciphertexts on the original value.") + suite.Require().ErrorContains(err, "Ciphertext Ciphertext equality verification on transferAmountLo failed") + + // So we attempt to generate new equality proofs for the amounts as well. + bigIntLoBits := new(big.Int).SetUint64(uint64(transferLoBits)) + loBitsScalar, _ := curves.ED25519().Scalar.SetBigInt(bigIntLoBits) + + bigIntHiBits := new(big.Int).SetUint64(uint64(transferHiBits)) + hiBitsScalar, _ := curves.ED25519().Scalar.SetBigInt(bigIntHiBits) + + senderKeyPair, _ := teg.KeyGen(*senderPk, DefaultTestDenom) + + ciphertextEqualityLoProof, err := zkproofs.NewCiphertextCiphertextEqualityProof(senderKeyPair, &recipientKeyPair.PublicKey, transferStruct.SenderTransferAmountLo, &loBitsRandomness, &loBitsScalar) + suite.Require().NoError(err, "Should have no error generating lo bits equality proof despite mismatch in transfer amounts") + + ciphertextEqualityHiProof, err := zkproofs.NewCiphertextCiphertextEqualityProof(senderKeyPair, &recipientKeyPair.PublicKey, transferStruct.SenderTransferAmountHi, &hiBitsRandomness, &hiBitsScalar) + suite.Require().NoError(err, "Should have no error generating hi bits equality proof despite mismatch in transfer amounts") + + transferStruct.Proofs.TransferAmountLoEqualityProof = ciphertextEqualityLoProof + transferStruct.Proofs.TransferAmountHiEqualityProof = ciphertextEqualityHiProof + + // However, the equality proofs should still fail here since the sender and recipient ciphertexts encode different values. + req = types.NewMsgTransferProto(transferStruct) + _, err = suite.msgServer.Transfer(sdk.WrapSDKContext(suite.Ctx), req) + suite.Require().Error(err, "Should still fail equality proof since transfer amount ciphertexts encode different values.") + suite.Require().ErrorContains(err, "Ciphertext Ciphertext equality verification on transferAmountLo failed") +} diff --git a/x/confidentialtransfers/types/account.go b/x/confidentialtransfers/types/account.go index 104d6d125e..9ab3a7183d 100644 --- a/x/confidentialtransfers/types/account.go +++ b/x/confidentialtransfers/types/account.go @@ -3,6 +3,7 @@ package types import ( "github.com/coinbase/kryptology/pkg/core/curves" "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" + "math/big" ) type Account struct { @@ -31,3 +32,24 @@ type Account struct { // This is stored as the Base64-encoded ciphertext DecryptableAvailableBalance string } + +func (a *Account) GetPendingBalancePlaintext(decryptor *elgamal.TwistedElGamal, keypair *elgamal.KeyPair) (*big.Int, error) { + actualPendingBalanceLo, err := decryptor.Decrypt(keypair.PrivateKey, a.PendingBalanceLo, elgamal.MaxBits32) + if err != nil { + return big.NewInt(0), err + } + actualPendingBalanceHi, err := decryptor.DecryptLargeNumber(keypair.PrivateKey, a.PendingBalanceHi, elgamal.MaxBits48) + if err != nil { + return big.NewInt(0), err + } + + loBig := new(big.Int).SetUint64(actualPendingBalanceLo) + hiBig := new(big.Int).SetUint64(actualPendingBalanceHi) + + // Shift the 48-bit number by 32 bits to the left + hiBig.Lsh(hiBig, 16) // Equivalent to hi << 32 + + // Combine by adding hiBig with loBig + combined := new(big.Int).Add(hiBig, loBig) + return combined, nil +} diff --git a/x/confidentialtransfers/types/close_account.go b/x/confidentialtransfers/types/close_account.go new file mode 100644 index 0000000000..97c4949093 --- /dev/null +++ b/x/confidentialtransfers/types/close_account.go @@ -0,0 +1,57 @@ +package types + +import ( + "crypto/ecdsa" + "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" + "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" +) + +type CloseAccount struct { + Address string `json:"from_address"` + Denom string `json:"denom"` + + Proofs *CloseAccountProofs `json:"proofs"` +} + +type CloseAccountProofs struct { + // Proof that the current available balance is zero. + ZeroAvailableBalanceProof *zkproofs.ZeroBalanceProof `json:"zero_available_balance_proof"` + + // Proof that the current pending balance lo is zero. + ZeroPendingBalanceLoProof *zkproofs.ZeroBalanceProof `json:"zero_pending_balance_lo_proof"` + + // Proof that the current pending balance hi is zero. + ZeroPendingBalanceHiProof *zkproofs.ZeroBalanceProof `json:"zero_pending_balance_hi_proof"` +} + +func NewCloseAccount(privateKey ecdsa.PrivateKey, address, denom string, currPendingBalanceLo, currPendingBalanceHi, currAvailableBalance *elgamal.Ciphertext) (*CloseAccount, error) { + teg := elgamal.NewTwistedElgamal() + keyPair, err := teg.KeyGen(privateKey, denom) + if err != nil { + return nil, err + } + zeroPendingBalanceLoProof, err := zkproofs.NewZeroBalanceProof(keyPair, currPendingBalanceLo) + if err != nil { + return nil, err + } + + zeroPendingBalanceHiProof, err := zkproofs.NewZeroBalanceProof(keyPair, currPendingBalanceHi) + if err != nil { + return nil, err + } + + zeroAvailableBalanceProof, err := zkproofs.NewZeroBalanceProof(keyPair, currAvailableBalance) + if err != nil { + return nil, err + } + + return &CloseAccount{ + Address: address, + Denom: denom, + Proofs: &CloseAccountProofs{ + ZeroAvailableBalanceProof: zeroAvailableBalanceProof, + ZeroPendingBalanceLoProof: zeroPendingBalanceLoProof, + ZeroPendingBalanceHiProof: zeroPendingBalanceHiProof, + }, + }, nil +} diff --git a/x/confidentialtransfers/types/events.go b/x/confidentialtransfers/types/events.go new file mode 100644 index 0000000000..8c1c8f8894 --- /dev/null +++ b/x/confidentialtransfers/types/events.go @@ -0,0 +1,16 @@ +package types + +// Minting module event types +const ( + EventTypeInitializeAccount = "initialize_account" + EventTypeDeposit = "deposit" + EventTypeWithdraw = "withdraw" + EventTypeApplyPendingBalance = "apply_pending_balance" + EventTypeTransfer = "transfer" + EventTypeCloseAccount = "close_account" + + AttributeDenom = "denom" + AttributeAddress = "address" + AttributeSender = "sender" + AttributeRecipient = "recipient" +) diff --git a/x/confidentialtransfers/types/expected_keepers.go b/x/confidentialtransfers/types/expected_keepers.go index f4b4f7ddd7..bff4dc26d9 100644 --- a/x/confidentialtransfers/types/expected_keepers.go +++ b/x/confidentialtransfers/types/expected_keepers.go @@ -2,11 +2,21 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" ) +// AccountKeeper defines the contract required for account APIs. type AccountKeeper interface { GetModuleAddress(name string) sdk.AccAddress - SetModuleAccount(ctx sdk.Context, macc authtypes.ModuleAccountI) - GetAccount(sdk.Context, sdk.AccAddress) authtypes.AccountI + + // TODO remove with genesis 2-phases refactor https://github.com/cosmos/cosmos-sdk/issues/2862 + SetModuleAccount(sdk.Context, types.ModuleAccountI) + GetModuleAccount(ctx sdk.Context, moduleName string) types.ModuleAccountI +} + +// BankKeeper defines the contract needed to be fulfilled for banking and supply +// dependencies. +type BankKeeper interface { + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } diff --git a/x/confidentialtransfers/types/initialize_account.go b/x/confidentialtransfers/types/initialize_account.go index 01d68bb329..ed789c78ac 100644 --- a/x/confidentialtransfers/types/initialize_account.go +++ b/x/confidentialtransfers/types/initialize_account.go @@ -1,7 +1,10 @@ package types import ( + "crypto/ecdsa" "github.com/coinbase/kryptology/pkg/core/curves" + "github.com/sei-protocol/sei-cryptography/pkg/encryption" + "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" ) @@ -9,10 +12,90 @@ type InitializeAccount struct { FromAddress string `json:"from_address"` Denom string `json:"denom"` Pubkey *curves.Point `json:"pubkey"` + PendingBalanceLo *elgamal.Ciphertext `json:"pending_balance_lo"` + PendingBalanceHi *elgamal.Ciphertext `json:"pending_balance_hi"` + AvailableBalance *elgamal.Ciphertext `json:"available_balance"` DecryptableBalance string `json:"decryptable_balance"` Proofs *InitializeAccountProofs `json:"proofs"` } type InitializeAccountProofs struct { - PubkeyValidityProof *zkproofs.PubKeyValidityProof `json:"pubkey_validity_proof"` + PubkeyValidityProof *zkproofs.PubKeyValidityProof `json:"pubkey_validity_proof"` + ZeroPendingBalanceLoProof *zkproofs.ZeroBalanceProof `json:"zero_pending_balance_lo_proof"` + ZeroPendingBalanceHiProof *zkproofs.ZeroBalanceProof `json:"zero_pending_balance_hi_proof"` + ZeroAvailableBalanceProof *zkproofs.ZeroBalanceProof `json:"zero_available_balance_proof"` +} + +func NewInitializeAccount(address, denom string, privateKey ecdsa.PrivateKey) (*InitializeAccount, error) { + teg := elgamal.NewTwistedElgamal() + keys, err := teg.KeyGen(privateKey, denom) + if err != nil { + return &InitializeAccount{}, err + } + + aesKey, err := encryption.GetAESKey(privateKey, denom) + if err != nil { + return &InitializeAccount{}, err + } + + // Encrypt the 0 value using the aesKey + decryptableBalance, err := encryption.EncryptAESGCM(0, aesKey) + if err != nil { + return &InitializeAccount{}, err + } + + // Encrypt the 0 value thrice using the public key for the account balances. + zeroCiphertextLo, _, err := teg.Encrypt(keys.PublicKey, 0) + if err != nil { + return &InitializeAccount{}, err + } + + zeroCiphertextHi, _, err := teg.Encrypt(keys.PublicKey, 0) + if err != nil { + return &InitializeAccount{}, err + } + + zeroCiphertextAvailable, _, err := teg.Encrypt(keys.PublicKey, 0) + if err != nil { + return &InitializeAccount{}, err + } + + pubkeyValidityProof, err := zkproofs.NewPubKeyValidityProof(keys.PublicKey, keys.PrivateKey) + if err != nil { + return &InitializeAccount{}, err + } + + // Generate proofs for the zero values + proofLo, err := zkproofs.NewZeroBalanceProof(keys, zeroCiphertextLo) + if err != nil { + return &InitializeAccount{}, err + } + + proofHi, err := zkproofs.NewZeroBalanceProof(keys, zeroCiphertextHi) + if err != nil { + return &InitializeAccount{}, err + } + + proofAvailable, err := zkproofs.NewZeroBalanceProof(keys, zeroCiphertextAvailable) + if err != nil { + return &InitializeAccount{}, err + } + + proofs := InitializeAccountProofs{ + PubkeyValidityProof: pubkeyValidityProof, + ZeroPendingBalanceLoProof: proofLo, + ZeroPendingBalanceHiProof: proofHi, + ZeroAvailableBalanceProof: proofAvailable, + } + + return &InitializeAccount{ + FromAddress: address, + Denom: denom, + Pubkey: &keys.PublicKey, + DecryptableBalance: decryptableBalance, + PendingBalanceLo: zeroCiphertextLo, + PendingBalanceHi: zeroCiphertextHi, + AvailableBalance: zeroCiphertextAvailable, + Proofs: &proofs, + }, nil } diff --git a/x/confidentialtransfers/types/msgs.go b/x/confidentialtransfers/types/msgs.go index 04b0454973..384947b32a 100644 --- a/x/confidentialtransfers/types/msgs.go +++ b/x/confidentialtransfers/types/msgs.go @@ -27,12 +27,12 @@ func (m *MsgTransfer) Type() string { return TypeMsgTransfer } func (m *MsgTransfer) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.FromAddress) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } _, err = sdk.AccAddressFromBech32(m.ToAddress) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid recipient address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid recipient address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -41,27 +41,27 @@ func (m *MsgTransfer) ValidateBasic() error { } if m.FromAmountLo == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "FromAmountLo is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "from amount lo is required") } if m.FromAmountHi == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "FromAmountHi is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "from amount hi is required") } if m.ToAmountLo == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "ToAmountLo is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "to amount lo is required") } if m.ToAmountHi == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "ToAmountHi is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "to amount hi is required") } if m.RemainingBalance == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "RemainingBalance is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "remaining balance is required") } if m.Proofs == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Proofs is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "proofs is required") } err = m.Proofs.Validate() @@ -141,6 +141,36 @@ func (m *MsgTransfer) FromProto() (*Transfer, error) { }, nil } +func NewMsgTransferProto(transfer *Transfer) *MsgTransfer { + fromAmountLo := NewCiphertextProto(transfer.SenderTransferAmountLo) + fromAmountHi := NewCiphertextProto(transfer.SenderTransferAmountHi) + toAmountLo := NewCiphertextProto(transfer.RecipientTransferAmountLo) + toAmountHi := NewCiphertextProto(transfer.RecipientTransferAmountHi) + remainingBalance := NewCiphertextProto(transfer.RemainingBalanceCommitment) + proofs := NewTransferMsgProofs(transfer.Proofs) + + // iterate over transfer.Auditors and convert them to types.Auditor + auditors := make([]*Auditor, 0, len(transfer.Auditors)) + for _, auditorData := range transfer.Auditors { + auditor := NewAuditorProto(auditorData) + auditors = append(auditors, auditor) + } + + return &MsgTransfer{ + FromAddress: transfer.FromAddress, + ToAddress: transfer.ToAddress, + Denom: transfer.Denom, + FromAmountLo: fromAmountLo, + FromAmountHi: fromAmountHi, + ToAmountLo: toAmountLo, + ToAmountHi: toAmountHi, + RemainingBalance: remainingBalance, + DecryptableBalance: transfer.DecryptableBalance, + Proofs: proofs, + Auditors: auditors, + } +} + var _ sdk.Msg = &MsgInitializeAccount{} // Route Implements Msg. @@ -152,7 +182,7 @@ func (m *MsgInitializeAccount) Type() string { return TypeMsgInitializeAccount } func (m *MsgInitializeAccount) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.FromAddress) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -161,15 +191,27 @@ func (m *MsgInitializeAccount) ValidateBasic() error { } if m.PublicKey == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "PublicKey is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "public key is required") } if m.DecryptableBalance == "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "DecryptableBalance is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "decryptable balance is required") + } + + if m.PendingBalanceLo == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "pending amount lo is required") + } + + if m.PendingBalanceHi == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "pending amount hi is required") + } + + if m.AvailableBalance == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "available balance is required") } if m.Proofs == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Proofs is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "proofs is required") } err = m.Proofs.Validate() @@ -201,6 +243,21 @@ func (m *MsgInitializeAccount) FromProto() (*InitializeAccount, error) { return nil, err } + pendingBalanceLo, err := m.PendingBalanceLo.FromProto() + if err != nil { + return nil, err + } + + pendingBalanceHi, err := m.PendingBalanceHi.FromProto() + if err != nil { + return nil, err + } + + availableBalance, err := m.AvailableBalance.FromProto() + if err != nil { + return nil, err + } + proofs, err := m.Proofs.FromProto() if err != nil { return nil, err @@ -210,11 +267,39 @@ func (m *MsgInitializeAccount) FromProto() (*InitializeAccount, error) { FromAddress: m.FromAddress, Denom: m.Denom, Pubkey: &pubkey, + PendingBalanceLo: pendingBalanceLo, + PendingBalanceHi: pendingBalanceHi, + AvailableBalance: availableBalance, DecryptableBalance: m.DecryptableBalance, Proofs: proofs, }, nil } +// convert the InitializeAccount to MsgInitializeAccount +func NewMsgInitializeAccountProto(initializeAccount *InitializeAccount) *MsgInitializeAccount { + pubkeyRaw := *initializeAccount.Pubkey + pubkey := pubkeyRaw.ToAffineCompressed() + + pendingBalanceLo := NewCiphertextProto(initializeAccount.PendingBalanceLo) + + pendingBalanceHi := NewCiphertextProto(initializeAccount.PendingBalanceHi) + + availableBalance := NewCiphertextProto(initializeAccount.AvailableBalance) + + proofs := NewInitializeAccountMsgProofs(initializeAccount.Proofs) + + return &MsgInitializeAccount{ + FromAddress: initializeAccount.FromAddress, + Denom: initializeAccount.Denom, + PublicKey: pubkey, + DecryptableBalance: initializeAccount.DecryptableBalance, + PendingBalanceLo: pendingBalanceLo, + PendingBalanceHi: pendingBalanceHi, + AvailableBalance: availableBalance, + Proofs: proofs, + } +} + var _ sdk.Msg = &MsgApplyPendingBalance{} // Route Implements Msg. @@ -226,7 +311,7 @@ func (m *MsgApplyPendingBalance) Type() string { return TypeMsgApplyPendingBalan func (m *MsgApplyPendingBalance) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.Address) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -235,7 +320,7 @@ func (m *MsgApplyPendingBalance) ValidateBasic() error { } if len(m.NewDecryptableAvailableBalance) == 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "NewDecryptableAvailableBalance is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "new decryptable available balance is required") } return nil } @@ -260,7 +345,7 @@ func (m *MsgCloseAccount) Type() string { return TypeMsgCloseAccount } func (m *MsgCloseAccount) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.Address) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -269,7 +354,7 @@ func (m *MsgCloseAccount) ValidateBasic() error { } if m.Proofs == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Proofs is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "proofs is required") } err = m.Proofs.Validate() @@ -281,6 +366,24 @@ func (m *MsgCloseAccount) ValidateBasic() error { return nil } +func (m *MsgCloseAccount) FromProto() (*CloseAccount, error) { + err := m.ValidateBasic() + if err != nil { + return nil, err + } + + proofs, err := m.Proofs.FromProto() + if err != nil { + return nil, err + } + + return &CloseAccount{ + Address: m.Address, + Denom: m.Denom, + Proofs: proofs, + }, nil +} + func (m *MsgCloseAccount) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(m)) } @@ -290,6 +393,15 @@ func (m *MsgCloseAccount) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sender} } +func NewMsgCloseAccountProto(closeAccount *CloseAccount) *MsgCloseAccount { + proofs := NewCloseAccountMsgProofs(closeAccount.Proofs) + return &MsgCloseAccount{ + Address: closeAccount.Address, + Denom: closeAccount.Denom, + Proofs: proofs, + } +} + var _ sdk.Msg = &MsgDeposit{} // Route Implements Msg. @@ -301,7 +413,7 @@ func (m *MsgDeposit) Type() string { return TypeMsgDeposit } func (m *MsgDeposit) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.FromAddress) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -310,7 +422,7 @@ func (m *MsgDeposit) ValidateBasic() error { } if m.Amount <= 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Positive amount is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "positive amount is required") } return nil @@ -336,7 +448,7 @@ func (m *MsgWithdraw) Type() string { return TypeMsgWithdraw } func (m *MsgWithdraw) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.FromAddress) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } err = sdk.ValidateDenom(m.Denom) @@ -345,19 +457,19 @@ func (m *MsgWithdraw) ValidateBasic() error { } if m.Amount <= 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Positive amount is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "positive amount is required") } if m.RemainingBalanceCommitment == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "RemainingBalanceCommitment is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "remainingBalanceCommitment is required") } if m.DecryptableBalance == "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "DecryptableBalance is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "decryptableBalance is required") } if m.Proofs == nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Proofs is required") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "proofs is required") } err = m.Proofs.Validate() @@ -402,3 +514,18 @@ func (m *MsgWithdraw) FromProto() (*Withdraw, error) { Proofs: proofs, }, nil } + +func NewMsgWithdrawProto(withdraw *Withdraw) *MsgWithdraw { + remainingBalanceCommitment := NewCiphertextProto(withdraw.RemainingBalanceCommitment) + + proofs := NewWithdrawMsgProofs(withdraw.Proofs) + + return &MsgWithdraw{ + FromAddress: withdraw.FromAddress, + Denom: withdraw.Denom, + Amount: withdraw.Amount, + RemainingBalanceCommitment: remainingBalanceCommitment, + DecryptableBalance: withdraw.DecryptableBalance, + Proofs: proofs, + } +} diff --git a/x/confidentialtransfers/types/msgs_test.go b/x/confidentialtransfers/types/msgs_test.go index 44d8fe2435..5a6c1b09e1 100644 --- a/x/confidentialtransfers/types/msgs_test.go +++ b/x/confidentialtransfers/types/msgs_test.go @@ -261,8 +261,8 @@ func TestMsgTransfer_FromProto(t *testing.T) { result.Auditors[0].TransferAmountHiEqualityProof, &sourceKeypair.PublicKey, &auditorKeypair.PublicKey, - result.SenderTransferAmountLo, - result.Auditors[0].EncryptedTransferAmountLo)) + result.SenderTransferAmountHi, + result.Auditors[0].EncryptedTransferAmountHi)) } func TestMsgTransfer_ValidateBasic(t *testing.T) { @@ -405,23 +405,35 @@ func TestMsgInitializeAccount_FromProto(t *testing.T) { require.NoError(t, err) decryptableBalance, err := encryption.EncryptAESGCM(0, aesPK) + encryptedZero, _, err := eg.Encrypt(sourceKeypair.PublicKey, 0) // Generate the proof pubkeyValidityProof, _ := zkproofs.NewPubKeyValidityProof( sourceKeypair.PublicKey, sourceKeypair.PrivateKey) + zeroBalProof, _ := zkproofs.NewZeroBalanceProof( + sourceKeypair, + encryptedZero) + proofs := &InitializeAccountProofs{ pubkeyValidityProof, + zeroBalProof, + zeroBalProof, + zeroBalProof, } address1 := sdk.AccAddress("address1") + encryptedZeroProto := NewCiphertextProto(encryptedZero) proofsProto := NewInitializeAccountMsgProofs(proofs) m := &MsgInitializeAccount{ FromAddress: address1.String(), Denom: testDenom, PublicKey: sourceKeypair.PublicKey.ToAffineCompressed(), DecryptableBalance: decryptableBalance, + PendingBalanceLo: encryptedZeroProto, + PendingBalanceHi: encryptedZeroProto, + AvailableBalance: encryptedZeroProto, Proofs: proofsProto, } @@ -554,8 +566,7 @@ func TestMsgWithdraw_FromProto(t *testing.T) { newBalanceProto := NewCiphertextProto(newBalanceCommitment) - withdrawMsgProof := WithdrawMsgProofs{} - proofsProto := withdrawMsgProof.NewWithdrawMsgProofs(proofs) + proofsProto := NewWithdrawMsgProofs(proofs) m := &MsgWithdraw{ FromAddress: address1.String(), Denom: testDenom, diff --git a/x/confidentialtransfers/types/transfer.go b/x/confidentialtransfers/types/transfer.go index cde9d3107a..2d98447303 100644 --- a/x/confidentialtransfers/types/transfer.go +++ b/x/confidentialtransfers/types/transfer.go @@ -1,8 +1,14 @@ package types import ( + "crypto/ecdsa" + "errors" + "github.com/coinbase/kryptology/pkg/core/curves" + "github.com/sei-protocol/sei-chain/x/confidentialtransfers/utils" + "github.com/sei-protocol/sei-cryptography/pkg/encryption" "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" + "math/big" ) type Transfer struct { @@ -40,3 +46,311 @@ type TransferAuditor struct { TransferAmountLoEqualityProof *zkproofs.CiphertextCiphertextEqualityProof `json:"transfer_amount_lo_equality_proof"` TransferAmountHiEqualityProof *zkproofs.CiphertextCiphertextEqualityProof `json:"transfer_amount_hi_equality_proof"` } + +type AuditorInput struct { + Address string + Pubkey *curves.Point +} + +// NewTransfer creates a new Transfer object. +func NewTransfer( + privateKey *ecdsa.PrivateKey, + senderAddr, + recipientAddr, + denom, + senderCurrentDecryptableBalance string, + senderCurrentAvailableBalance *elgamal.Ciphertext, + amount uint64, + recipientPubkey *curves.Point, + auditors []AuditorInput) (*Transfer, error) { + // Get the current balance of the account from the decryptableBalance + aesKey, err := encryption.GetAESKey(*privateKey, denom) + if err != nil { + return &Transfer{}, err + } + + currentBalance, err := encryption.DecryptAESGCM(senderCurrentDecryptableBalance, aesKey) + if err != nil { + return &Transfer{}, err + } + + // Check that account has sufficient balance to make the transfer. + if currentBalance < amount { + return &Transfer{}, errors.New("insufficient balance") + } + + // Encrypt the new balance using the user's AES Key. + newBalance := currentBalance - amount + decryptableNewBalance, err := encryption.EncryptAESGCM(newBalance, aesKey) + if err != nil { + return &Transfer{}, err + } + + // Now we want to encrypt the commitment to the new balance. This is used to generate the range proof. + teg := elgamal.NewTwistedElgamal() + senderKeyPair, err := teg.KeyGen(*privateKey, denom) + if err != nil { + return &Transfer{}, err + } + + newBalanceCommitment, newBalanceRandomness, err := teg.Encrypt(senderKeyPair.PublicKey, newBalance) + if err != nil { + return &Transfer{}, err + } + + // Split the transfer amount into bottom 16 bits and top 32 bits. + // Extract the bottom 16 bits (rightmost 16 bits) + transferLoBits, transferHiBits, err := utils.SplitTransferBalance(amount) + if err != nil { + return &Transfer{}, err + } + + // Encrypt the transfer amounts for the sender + senderEncryptedTransferLoBits, senderLoBitsRandomness, err := teg.Encrypt(senderKeyPair.PublicKey, uint64(transferLoBits)) + if err != nil { + return &Transfer{}, err + } + + senderEncryptedTransferHiBits, senderHiBitsRandomness, err := teg.Encrypt(senderKeyPair.PublicKey, uint64(transferHiBits)) + if err != nil { + return &Transfer{}, err + } + + // Now that we have all the params we need, start generating the proofs wrt the Sender params. + // First we generate validity proofs that all the ciphertexts are valid. + newCommitmentValidityProof, err := zkproofs.NewCiphertextValidityProof(&newBalanceRandomness, senderKeyPair.PublicKey, newBalanceCommitment, newBalance) + if err != nil { + return &Transfer{}, err + } + + senderLoBitsValidityProof, err := zkproofs.NewCiphertextValidityProof(&senderLoBitsRandomness, senderKeyPair.PublicKey, senderEncryptedTransferLoBits, uint64(transferLoBits)) + if err != nil { + return &Transfer{}, err + } + + senderHiBitsValidityProof, err := zkproofs.NewCiphertextValidityProof(&senderHiBitsRandomness, senderKeyPair.PublicKey, senderEncryptedTransferHiBits, uint64(transferHiBits)) + if err != nil { + return &Transfer{}, err + } + + // Secondly, we generate a Range Proof to prove that the PedersonCommitment to the new balance is greater than zero. + newBalanceRangeProof, err := zkproofs.NewRangeProof(64, int(newBalance), newBalanceRandomness) + if err != nil { + return &Transfer{}, err + } + + // Thirdly we generate proof that the PedersonCommitment we generated encrypts the same value as AvailableBalance - TransferAmount + bigIntNewBalance := new(big.Int).SetUint64(newBalance) + newBalanceScalar, err := curves.ED25519().Scalar.SetBigInt(bigIntNewBalance) + if err != nil { + return &Transfer{}, err + } + + newBalanceCiphertext, err := teg.SubWithLoHi(senderCurrentAvailableBalance, senderEncryptedTransferLoBits, senderEncryptedTransferHiBits) + if err != nil { + return &Transfer{}, err + } + + commitmentCiphertextEqualityProof, err := zkproofs.NewCiphertextCommitmentEqualityProof(senderKeyPair, newBalanceCiphertext, &newBalanceRandomness, &newBalanceScalar) + if err != nil { + return &Transfer{}, err + } + + // Now, we create params and proofs specific to the recipient + recipientParams, err := createTransferPartyParams(recipientAddr, transferLoBits, transferHiBits, senderKeyPair, senderEncryptedTransferLoBits, senderEncryptedTransferHiBits, recipientPubkey) + if err != nil { + return &Transfer{}, err + } + + proofs := TransferProofs{ + RemainingBalanceCommitmentValidityProof: newCommitmentValidityProof, + SenderTransferAmountLoValidityProof: senderLoBitsValidityProof, + SenderTransferAmountHiValidityProof: senderHiBitsValidityProof, + RecipientTransferAmountLoValidityProof: recipientParams.TransferAmountLoValidityProof, + RecipientTransferAmountHiValidityProof: recipientParams.TransferAmountHiValidityProof, + RemainingBalanceRangeProof: newBalanceRangeProof, + RemainingBalanceEqualityProof: commitmentCiphertextEqualityProof, + TransferAmountLoEqualityProof: recipientParams.TransferAmountLoEqualityProof, + TransferAmountHiEqualityProof: recipientParams.TransferAmountHiEqualityProof, + } + + // Lastly we generate the Auditor parameters, if required. + auditorsData := []*TransferAuditor{} + for _, auditor := range auditors { + auditorData, err := createTransferPartyParams(auditor.Address, transferLoBits, transferHiBits, senderKeyPair, senderEncryptedTransferLoBits, senderEncryptedTransferHiBits, auditor.Pubkey) + if err != nil { + return &Transfer{}, err + } + auditorsData = append(auditorsData, auditorData) + } + + return &Transfer{ + FromAddress: senderAddr, + ToAddress: recipientAddr, + Denom: denom, + SenderTransferAmountLo: senderEncryptedTransferLoBits, + SenderTransferAmountHi: senderEncryptedTransferHiBits, + RecipientTransferAmountLo: recipientParams.EncryptedTransferAmountLo, + RecipientTransferAmountHi: recipientParams.EncryptedTransferAmountHi, + RemainingBalanceCommitment: newBalanceCommitment, + DecryptableBalance: decryptableNewBalance, + Proofs: &proofs, + Auditors: auditorsData, + }, nil +} + +func createTransferPartyParams( + partyAddress string, + transferLoBits uint16, + transferHiBits uint32, + senderKeyPair *elgamal.KeyPair, + senderEncryptedTransferLoBits, + senderEncryptedTransferHiBits *elgamal.Ciphertext, + partyPubkey *curves.Point) (*TransferAuditor, error) { + teg := elgamal.NewTwistedElgamal() + + // Encrypt the transfer amounts using the party's public key. + encryptedTransferLoBits, loBitsRandomness, err := teg.Encrypt(*partyPubkey, uint64(transferLoBits)) + if err != nil { + return &TransferAuditor{}, err + } + + encryptedTransferHiBits, hiBitsRandomness, err := teg.Encrypt(*partyPubkey, uint64(transferHiBits)) + if err != nil { + return &TransferAuditor{}, err + } + + // Create validity proofs that the ciphertexts are valid (encrypted with the correct pubkey). + loBitsValidityProof, err := zkproofs.NewCiphertextValidityProof(&loBitsRandomness, *partyPubkey, encryptedTransferLoBits, uint64(transferLoBits)) + if err != nil { + return &TransferAuditor{}, err + } + + hiBitsValidityProof, err := zkproofs.NewCiphertextValidityProof(&hiBitsRandomness, *partyPubkey, encryptedTransferHiBits, uint64(transferHiBits)) + if err != nil { + return &TransferAuditor{}, err + } + + // Lastly, we need to generate proof that the ciphertexts of the transfer amounts encrypt the same value as those for the sender. + bigIntLoBits := new(big.Int).SetUint64(uint64(transferLoBits)) + loBitsScalar, err := curves.ED25519().Scalar.SetBigInt(bigIntLoBits) + if err != nil { + return &TransferAuditor{}, err + } + + bigIntHiBits := new(big.Int).SetUint64(uint64(transferHiBits)) + hiBitsScalar, err := curves.ED25519().Scalar.SetBigInt(bigIntHiBits) + if err != nil { + return &TransferAuditor{}, err + } + + ciphertextLoEqualityProof, err := zkproofs.NewCiphertextCiphertextEqualityProof(senderKeyPair, partyPubkey, senderEncryptedTransferLoBits, &loBitsRandomness, &loBitsScalar) + if err != nil { + return &TransferAuditor{}, err + } + + ciphertextHiEqualityProof, err := zkproofs.NewCiphertextCiphertextEqualityProof(senderKeyPair, partyPubkey, senderEncryptedTransferHiBits, &hiBitsRandomness, &hiBitsScalar) + if err != nil { + return &TransferAuditor{}, err + } + + return &TransferAuditor{ + Address: partyAddress, + EncryptedTransferAmountLo: encryptedTransferLoBits, + EncryptedTransferAmountHi: encryptedTransferHiBits, + TransferAmountLoValidityProof: loBitsValidityProof, + TransferAmountHiValidityProof: hiBitsValidityProof, + TransferAmountLoEqualityProof: ciphertextLoEqualityProof, + TransferAmountHiEqualityProof: ciphertextHiEqualityProof, + }, nil +} + +// Verifies the proofs sent in the transfer request. This does not verify proofs for auditors. +func VerifyTransferProofs(params *Transfer, senderPubkey *curves.Point, recipientPubkey *curves.Point, newBalanceCiphertext *elgamal.Ciphertext) error { + // Verify the validity proofs that the ciphertexts sent are valid (encrypted with the correct pubkey). + ok := zkproofs.VerifyCiphertextValidity(params.Proofs.RemainingBalanceCommitmentValidityProof, *senderPubkey, params.RemainingBalanceCommitment) + if !ok { + return errors.New("Failed to verify remaining balance commitment") + } + + ok = zkproofs.VerifyCiphertextValidity(params.Proofs.SenderTransferAmountLoValidityProof, *senderPubkey, params.SenderTransferAmountLo) + if !ok { + return errors.New("Failed to verify senderTransferAmountLo") + } + + ok = zkproofs.VerifyCiphertextValidity(params.Proofs.SenderTransferAmountHiValidityProof, *senderPubkey, params.SenderTransferAmountHi) + if !ok { + return errors.New("Failed to verify senderTransferAmountHi") + } + + ok = zkproofs.VerifyCiphertextValidity(params.Proofs.RecipientTransferAmountLoValidityProof, *recipientPubkey, params.RecipientTransferAmountLo) + if !ok { + return errors.New("Failed to verify recipientTransferAmountLo") + } + + ok = zkproofs.VerifyCiphertextValidity(params.Proofs.RecipientTransferAmountHiValidityProof, *recipientPubkey, params.RecipientTransferAmountHi) + if !ok { + return errors.New("Failed to verify recipientTransferAmountHi") + } + + // Verify that the account's remaining balance is greater than zero after this transfer. + // This validates the RemainingBalanceCommitment sent by the user, so an additional check is needed to make sure this matches what is calculated by the server. + ok, err := zkproofs.VerifyRangeProof(params.Proofs.RemainingBalanceRangeProof, params.RemainingBalanceCommitment, 64) + if err != nil { + return err + } + if !ok { + return errors.New("Range proof verification failed") + } + + // As part of the range proof above, we verify that the RemainingBalanceCommitment sent by the user is equal to the remaining balance calculated by the server. + ok = zkproofs.VerifyCiphertextCommitmentEquality(params.Proofs.RemainingBalanceEqualityProof, senderPubkey, newBalanceCiphertext, ¶ms.RemainingBalanceCommitment.C) + if !ok { + return errors.New("Ciphertext Commitment equality verification failed") + } + + // Lastly verify that the transferAmount ciphertexts encode the same value + ok = zkproofs.VerifyCiphertextCiphertextEquality(params.Proofs.TransferAmountLoEqualityProof, senderPubkey, recipientPubkey, params.SenderTransferAmountLo, params.RecipientTransferAmountLo) + if !ok { + return errors.New("Ciphertext Ciphertext equality verification on transferAmountLo failed") + } + + ok = zkproofs.VerifyCiphertextCiphertextEquality(params.Proofs.TransferAmountHiEqualityProof, senderPubkey, recipientPubkey, params.SenderTransferAmountHi, params.RecipientTransferAmountHi) + if !ok { + return errors.New("Ciphertext Ciphertext equality verification on transferAmountHi failed") + } + + return nil +} + +// Verifies the proofs sent for an individual auditor. +func VerifyAuditorProof( + senderTransferAmountLo, + senderTransferAmountHi *elgamal.Ciphertext, + auditorParams *TransferAuditor, + senderPubkey *curves.Point, + auditorPubkey *curves.Point) error { + // Verify that the transfer amounts are valid (encrypted with the correct pubkey). + ok := zkproofs.VerifyCiphertextValidity(auditorParams.TransferAmountLoValidityProof, *auditorPubkey, auditorParams.EncryptedTransferAmountLo) + if !ok { + return errors.New("Failed to verify auditor TransferAmountLo") + } + + ok = zkproofs.VerifyCiphertextValidity(auditorParams.TransferAmountHiValidityProof, *auditorPubkey, auditorParams.EncryptedTransferAmountHi) + if !ok { + return errors.New("Failed to verify auditor TransferAmountHi") + } + + // Then, verify that the transferAmount ciphertexts encode the same value + ok = zkproofs.VerifyCiphertextCiphertextEquality(auditorParams.TransferAmountLoEqualityProof, senderPubkey, auditorPubkey, senderTransferAmountLo, auditorParams.EncryptedTransferAmountLo) + if !ok { + return errors.New("Ciphertext Ciphertext equality verification on auditor transferAmountLo failed") + } + + ok = zkproofs.VerifyCiphertextCiphertextEquality(auditorParams.TransferAmountHiEqualityProof, senderPubkey, auditorPubkey, senderTransferAmountHi, auditorParams.EncryptedTransferAmountHi) + if !ok { + return errors.New("Ciphertext Ciphertext equality verification on auditor transferAmountHi failed") + } + + return nil +} diff --git a/x/confidentialtransfers/types/tx.pb.go b/x/confidentialtransfers/types/tx.pb.go index d15d690101..dc07df1641 100644 --- a/x/confidentialtransfers/types/tx.pb.go +++ b/x/confidentialtransfers/types/tx.pb.go @@ -211,7 +211,10 @@ type MsgInitializeAccount struct { Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty" yaml:"public_key"` DecryptableBalance string `protobuf:"bytes,4,opt,name=decryptable_balance,json=decryptableBalance,proto3" json:"decryptable_balance,omitempty" yaml:"decryptable_balance"` - Proofs *InitializeAccountMsgProofs `protobuf:"bytes,5,opt,name=proofs,proto3" json:"proofs,omitempty" yaml:"proofs"` + PendingBalanceLo *Ciphertext `protobuf:"bytes,5,opt,name=pending_balance_lo,json=pendingBalanceLo,proto3" json:"pending_balance_lo,omitempty" yaml:"pending_balance_lo"` + PendingBalanceHi *Ciphertext `protobuf:"bytes,6,opt,name=pending_balance_hi,json=pendingBalanceHi,proto3" json:"pending_balance_hi,omitempty" yaml:"pending_balance_hi"` + AvailableBalance *Ciphertext `protobuf:"bytes,7,opt,name=available_balance,json=availableBalance,proto3" json:"available_balance,omitempty" yaml:"available_balance"` + Proofs *InitializeAccountMsgProofs `protobuf:"bytes,8,opt,name=proofs,proto3" json:"proofs,omitempty" yaml:"proofs"` } func (m *MsgInitializeAccount) Reset() { *m = MsgInitializeAccount{} } @@ -443,9 +446,9 @@ var xxx_messageInfo_MsgWithdrawResponse proto.InternalMessageInfo // Message to be used in apply pending balance instruction/transaction type MsgApplyPendingBalance struct { - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty"` - NewDecryptableAvailableBalance string `protobuf:"bytes,3,opt,name=new_decryptable_available_balance,json=newDecryptableAvailableBalance,proto3" json:"new_decryptable_available_balance,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty" yaml:"address"` + Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` + NewDecryptableAvailableBalance string `protobuf:"bytes,3,opt,name=new_decryptable_available_balance,json=newDecryptableAvailableBalance,proto3" json:"new_decryptable_available_balance,omitempty" yaml:"new_decryptable_available_balance"` } func (m *MsgApplyPendingBalance) Reset() { *m = MsgApplyPendingBalance{} } @@ -612,81 +615,85 @@ func init() { func init() { proto.RegisterFile("confidentialtransfers/tx.proto", fileDescriptor_34e86c2ca2c678f9) } var fileDescriptor_34e86c2ca2c678f9 = []byte{ - // 1169 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6c, 0x1b, 0x45, - 0x14, 0xce, 0xe6, 0xc7, 0x49, 0x5e, 0x42, 0xd2, 0x6c, 0x12, 0xea, 0x58, 0x61, 0x37, 0x5d, 0x24, - 0x08, 0xa8, 0x38, 0x52, 0x5a, 0x15, 0x29, 0x15, 0x14, 0x3b, 0x05, 0xa5, 0x80, 0x45, 0xb5, 0x42, - 0x20, 0xb8, 0x58, 0x1b, 0x7b, 0xb2, 0x3b, 0xea, 0x7a, 0x67, 0xb3, 0xb3, 0x69, 0xe2, 0xde, 0x10, - 0x20, 0x71, 0xec, 0x91, 0x0b, 0x22, 0x1c, 0x7a, 0xe7, 0xce, 0x19, 0x89, 0x63, 0x8f, 0x9c, 0x0c, - 0x4a, 0x2e, 0x9c, 0x2d, 0xce, 0x08, 0x79, 0x66, 0x67, 0xbc, 0x6b, 0x6f, 0xd2, 0xac, 0xdd, 0x46, - 0xbd, 0x79, 0x76, 0xe6, 0x7d, 0xdf, 0xf7, 0xe6, 0xfd, 0xcc, 0x4b, 0x40, 0xab, 0x11, 0x6f, 0x0f, - 0xd7, 0x91, 0x17, 0x62, 0xcb, 0x0d, 0x03, 0xcb, 0xa3, 0x7b, 0x28, 0xa0, 0x1b, 0xe1, 0x51, 0xd1, - 0x0f, 0x48, 0x48, 0xd4, 0xb7, 0x29, 0xc2, 0xec, 0x57, 0x8d, 0xb8, 0x45, 0x8a, 0x70, 0xcd, 0xb1, - 0xb0, 0x57, 0x4c, 0x35, 0x2a, 0x2c, 0xd9, 0xc4, 0x26, 0xec, 0xf0, 0x46, 0xe7, 0x17, 0x47, 0x28, - 0xac, 0xa7, 0x33, 0xd4, 0x82, 0xa6, 0x1f, 0x12, 0x3b, 0xb0, 0x7c, 0xa7, 0x19, 0x9d, 0x3c, 0x43, - 0xcb, 0xa3, 0x07, 0x7c, 0xdf, 0xf8, 0x75, 0x0a, 0x66, 0x2a, 0xd4, 0xfe, 0x3c, 0xda, 0x51, 0xb7, - 0x60, 0x76, 0x2f, 0x20, 0x8d, 0xaa, 0x55, 0xaf, 0x07, 0x88, 0xd2, 0xbc, 0xb2, 0xa6, 0xac, 0x4f, - 0x97, 0xaf, 0xb6, 0x5b, 0xfa, 0x62, 0xd3, 0x6a, 0xb8, 0x5b, 0x46, 0x7c, 0xd7, 0x30, 0x67, 0x3a, - 0xcb, 0x12, 0x5f, 0xa9, 0x37, 0x01, 0x42, 0x22, 0x2d, 0x47, 0x99, 0xe5, 0x72, 0xbb, 0xa5, 0x2f, - 0x70, 0xcb, 0xee, 0x9e, 0x61, 0x4e, 0x87, 0x44, 0x58, 0xbd, 0x01, 0x13, 0x75, 0xe4, 0x91, 0x46, - 0x7e, 0x8c, 0x19, 0x5c, 0x69, 0xb7, 0xf4, 0x59, 0x6e, 0xc0, 0x3e, 0x1b, 0x26, 0xdf, 0x56, 0x0f, - 0x61, 0x8e, 0x73, 0x37, 0xc8, 0x81, 0x17, 0x56, 0x5d, 0x92, 0x1f, 0x5f, 0x53, 0xd6, 0x67, 0x36, - 0x6f, 0x15, 0x2f, 0x7e, 0x9d, 0xc5, 0x6d, 0xec, 0x3b, 0x28, 0x08, 0xd1, 0x51, 0x58, 0x5e, 0x69, - 0xb7, 0xf4, 0xe5, 0xb8, 0x4f, 0x02, 0xd7, 0x30, 0xd9, 0x15, 0x94, 0xd8, 0xfa, 0x53, 0xd2, 0x4b, - 0xec, 0xe0, 0xfc, 0xc4, 0x8b, 0x20, 0x76, 0x70, 0x82, 0x78, 0x07, 0xab, 0xfb, 0x30, 0xdb, 0xb9, - 0x33, 0xe9, 0x6f, 0x6e, 0x28, 0xda, 0x58, 0x0c, 0xe3, 0xa8, 0x86, 0x09, 0x21, 0x91, 0xbe, 0x26, - 0x28, 0x1d, 0x9c, 0x9f, 0x7c, 0xfe, 0x94, 0x1d, 0x3f, 0x25, 0xe5, 0x0e, 0x56, 0xbf, 0x55, 0x60, - 0x21, 0x40, 0x0d, 0x0b, 0x7b, 0xd8, 0xb3, 0xab, 0xbb, 0x96, 0x6b, 0x79, 0x35, 0x94, 0x9f, 0x1a, - 0x8a, 0x58, 0x6b, 0xb7, 0xf4, 0x82, 0x48, 0x22, 0x56, 0x15, 0xd6, 0xae, 0x8b, 0x04, 0xb8, 0x61, - 0x5e, 0x91, 0x84, 0x65, 0xfe, 0x49, 0xfd, 0x0c, 0x16, 0x53, 0x4e, 0xe6, 0xa7, 0x59, 0x4e, 0x3e, - 0x0b, 0x4e, 0x8d, 0x7d, 0x15, 0x80, 0x0e, 0xe4, 0xfc, 0x80, 0x90, 0x3d, 0x9a, 0x07, 0xe6, 0xca, - 0x7b, 0x59, 0x5c, 0x11, 0xe5, 0x58, 0xa1, 0xf6, 0x7d, 0x06, 0x52, 0x5e, 0x68, 0xb7, 0xf4, 0x57, - 0xb8, 0x04, 0x0e, 0x6b, 0x98, 0x11, 0xbe, 0x5a, 0x87, 0x29, 0xeb, 0xa0, 0x8e, 0x43, 0x12, 0xd0, - 0xfc, 0xcc, 0xda, 0xd8, 0xfa, 0xcc, 0xe6, 0x8d, 0x2c, 0x5c, 0x25, 0x6e, 0x5b, 0x5e, 0x6c, 0xb7, - 0xf4, 0x79, 0xce, 0x20, 0xe0, 0x0c, 0x53, 0x22, 0x6f, 0x4d, 0xfd, 0x70, 0xac, 0x8f, 0xfc, 0x73, - 0xac, 0x8f, 0x18, 0xcb, 0xb0, 0x18, 0xeb, 0x18, 0x26, 0xa2, 0x3e, 0xf1, 0x28, 0x32, 0x7e, 0x9c, - 0x86, 0xc9, 0x08, 0x4b, 0xbd, 0x0d, 0xf3, 0x91, 0x61, 0x4f, 0x23, 0x51, 0xdb, 0x2d, 0x7d, 0x2e, - 0x22, 0x11, 0xbd, 0x60, 0x2e, 0x3a, 0x2a, 0x1a, 0xc2, 0xb1, 0x02, 0xab, 0xc8, 0x63, 0x17, 0x8a, - 0xea, 0x55, 0x21, 0x34, 0x56, 0x07, 0xa3, 0x43, 0xe5, 0xc6, 0x9b, 0xed, 0x96, 0xfe, 0x3a, 0x97, - 0x70, 0x1e, 0x8b, 0x61, 0xae, 0xc8, 0x6d, 0xe1, 0xab, 0x2c, 0x93, 0xf3, 0x25, 0x3a, 0x98, 0xf5, - 0xb2, 0x17, 0x2c, 0xb1, 0x53, 0x47, 0x67, 0x49, 0xdc, 0xc1, 0xea, 0x6f, 0x0a, 0x5c, 0xeb, 0xf7, - 0xaa, 0xfa, 0xd0, 0x72, 0x71, 0x1d, 0x87, 0xcd, 0x2a, 0x4b, 0x9e, 0xa8, 0x85, 0x6e, 0x0f, 0xa6, - 0xf3, 0x8b, 0x08, 0x8b, 0xa5, 0x68, 0xf9, 0x7a, 0xbb, 0xa5, 0xaf, 0x47, 0xc5, 0xfe, 0x2c, 0x5e, - 0xc3, 0x7c, 0x2d, 0xec, 0xb9, 0xd3, 0x04, 0x58, 0xaa, 0x7a, 0x07, 0xf7, 0xaa, 0x9f, 0xb8, 0x14, - 0xf5, 0x7d, 0xbc, 0x7d, 0xea, 0x77, 0x70, 0x52, 0xfd, 0xef, 0xe9, 0x77, 0x8f, 0xf6, 0x0f, 0x2c, - 0xb7, 0xab, 0x9e, 0xb7, 0xf3, 0xca, 0x60, 0xea, 0xbb, 0xbf, 0x3e, 0x8c, 0x50, 0x2f, 0x12, 0x85, - 0xa4, 0x82, 0x94, 0x28, 0x24, 0xc0, 0x52, 0xfd, 0x70, 0x70, 0xaf, 0x1f, 0x93, 0x97, 0xec, 0x47, - 0x9f, 0x82, 0x94, 0x78, 0x24, 0xc0, 0x8c, 0xff, 0x46, 0x61, 0xa9, 0x42, 0xed, 0x7b, 0x1e, 0xee, - 0xc8, 0xc0, 0x8f, 0x50, 0xa9, 0x56, 0xeb, 0x9c, 0x1b, 0x6a, 0xda, 0x91, 0x73, 0xcb, 0xe8, 0xf9, - 0x73, 0xcb, 0x4d, 0x00, 0xff, 0x60, 0xd7, 0xc5, 0xb5, 0xea, 0x03, 0xd4, 0x64, 0x8d, 0x61, 0x36, - 0x3e, 0x15, 0x75, 0xf7, 0x0c, 0x73, 0x9a, 0x2f, 0x3e, 0x41, 0xcd, 0xb3, 0xde, 0xa3, 0xf1, 0x81, - 0xdf, 0xa3, 0x7d, 0xf9, 0x1e, 0xf1, 0xaa, 0xf9, 0x28, 0x4b, 0xbc, 0xfa, 0x6e, 0xee, 0x22, 0x0f, - 0x53, 0xec, 0xc9, 0xd0, 0x60, 0x35, 0xed, 0xfe, 0xe5, 0xdb, 0xf1, 0x44, 0x01, 0xa8, 0x50, 0xfb, - 0x2e, 0xf2, 0x09, 0xc5, 0x97, 0x13, 0x96, 0xb7, 0x20, 0xc7, 0xf3, 0x89, 0x85, 0x64, 0x3c, 0xee, - 0x07, 0xff, 0x6e, 0x98, 0xd1, 0x81, 0x98, 0x1f, 0x4b, 0xa0, 0x76, 0x65, 0x4a, 0xf5, 0xdf, 0x8f, - 0xb3, 0x19, 0xfa, 0x4b, 0x1c, 0x3a, 0xf5, 0xc0, 0x3a, 0x7c, 0xc9, 0xe4, 0x3f, 0xff, 0x54, 0xfa, - 0x45, 0x81, 0xd5, 0xbe, 0x89, 0xad, 0x5a, 0x23, 0x8d, 0x06, 0x0e, 0x1b, 0xc8, 0x0b, 0x87, 0x9c, - 0x8f, 0x63, 0xaf, 0xdf, 0x79, 0x2c, 0x86, 0x59, 0xe8, 0x9d, 0xe2, 0xb6, 0xe5, 0x66, 0x6c, 0xfc, - 0xca, 0x65, 0x1f, 0xbf, 0x44, 0x24, 0x33, 0x66, 0x39, 0x1f, 0x8c, 0x84, 0xb1, 0x4c, 0x8f, 0x9f, - 0x14, 0x78, 0xb5, 0x42, 0xed, 0x92, 0xef, 0xbb, 0xcd, 0xfb, 0xc8, 0xab, 0xc7, 0xa6, 0xce, 0x3c, - 0x4c, 0x26, 0x92, 0xc4, 0x14, 0x4b, 0x75, 0x29, 0x91, 0x07, 0x22, 0xea, 0xf7, 0xe0, 0x9a, 0x87, - 0x0e, 0xab, 0xf1, 0x48, 0x59, 0x0f, 0x2d, 0xec, 0x26, 0x02, 0xcb, 0xfe, 0x8e, 0x32, 0x35, 0x0f, - 0x1d, 0xde, 0xed, 0x9e, 0x2b, 0x89, 0x63, 0x11, 0x75, 0x4c, 0xf6, 0x1a, 0x68, 0xe9, 0xf2, 0xa4, - 0x07, 0x7f, 0x29, 0x30, 0x5f, 0xa1, 0xf6, 0xb6, 0x4b, 0xa8, 0x6c, 0x9d, 0xd7, 0x7b, 0xa4, 0xa7, - 0x8e, 0x76, 0xd2, 0x9d, 0x8b, 0xa6, 0xb5, 0x2b, 0xc3, 0xc6, 0x27, 0xa8, 0x52, 0xa6, 0x1c, 0x8a, - 0xe9, 0xcb, 0x18, 0xba, 0x15, 0xb8, 0xda, 0xe3, 0xa0, 0x70, 0x7e, 0xf3, 0xdf, 0x1c, 0x8c, 0x55, - 0xa8, 0xad, 0x7e, 0xa7, 0xc0, 0x94, 0xfc, 0x33, 0xf9, 0xdd, 0x2c, 0xba, 0x62, 0xd3, 0x72, 0xe1, - 0xce, 0x80, 0x86, 0x42, 0x8e, 0xfa, 0xb3, 0x02, 0x0b, 0xfd, 0x0f, 0xd9, 0x07, 0x19, 0x61, 0xfb, - 0x10, 0x0a, 0x3b, 0xc3, 0x22, 0x48, 0x85, 0xdf, 0x28, 0x30, 0x29, 0x3a, 0xf9, 0xad, 0x8c, 0xa8, - 0x91, 0x5d, 0xe1, 0xfd, 0xc1, 0xec, 0xa4, 0x86, 0x4e, 0xb0, 0x64, 0x3f, 0xce, 0x1a, 0x2c, 0x61, - 0x98, 0x39, 0x58, 0xbd, 0xa5, 0xaf, 0x3e, 0x51, 0x60, 0x31, 0xad, 0xee, 0xcb, 0x19, 0x81, 0x53, - 0x30, 0x0a, 0x1f, 0x0f, 0x8f, 0x21, 0x75, 0x3e, 0x56, 0x60, 0x36, 0x51, 0xdd, 0xb7, 0x33, 0x82, - 0xc7, 0x8d, 0x0b, 0xdb, 0x43, 0x18, 0x0b, 0x49, 0xe5, 0xaf, 0xfe, 0x38, 0xd1, 0x94, 0xa7, 0x27, - 0x9a, 0xf2, 0xf7, 0x89, 0xa6, 0x3c, 0x3e, 0xd5, 0x46, 0x9e, 0x9e, 0x6a, 0x23, 0x7f, 0x9e, 0x6a, - 0x23, 0x5f, 0xdf, 0xb1, 0x71, 0xe8, 0x1c, 0xec, 0x16, 0x6b, 0xa4, 0xb1, 0x41, 0x11, 0x7e, 0x47, - 0x30, 0xb1, 0x05, 0xa3, 0xda, 0x38, 0xda, 0x38, 0xe3, 0x5f, 0x70, 0x4d, 0x1f, 0xd1, 0xdd, 0x1c, - 0xb3, 0xb8, 0xf1, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x72, 0x45, 0x61, 0x21, 0xa8, 0x13, 0x00, - 0x00, + // 1234 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x6f, 0xdc, 0x44, + 0x14, 0x8f, 0xf3, 0xb1, 0x49, 0x5e, 0x42, 0x3e, 0x9c, 0x84, 0x6e, 0x56, 0xa9, 0x9d, 0x1a, 0x09, + 0x02, 0x2a, 0x1b, 0x29, 0xad, 0x8a, 0x94, 0x0a, 0xca, 0x6e, 0x0a, 0x5a, 0xa0, 0x2b, 0x2a, 0x0b, + 0x81, 0xe0, 0xb2, 0x72, 0x76, 0x27, 0xf6, 0xa8, 0x5e, 0x8f, 0x63, 0x3b, 0x4d, 0xb6, 0xb7, 0xaa, + 0x20, 0x71, 0x42, 0x3d, 0x72, 0x23, 0x1c, 0x7a, 0xe7, 0xce, 0x19, 0x89, 0x63, 0x8f, 0x9c, 0x0c, + 0x4a, 0x2e, 0x5c, 0xb1, 0xf8, 0x03, 0xd0, 0x7a, 0x3c, 0xb3, 0x5e, 0xaf, 0xf3, 0xe1, 0xb8, 0x89, + 0xb8, 0xed, 0x7c, 0xbc, 0xdf, 0xef, 0xf7, 0x66, 0xde, 0x7b, 0xf3, 0xbc, 0x20, 0x35, 0x89, 0xb5, + 0x83, 0x5b, 0xc8, 0xf2, 0xb0, 0x66, 0x7a, 0x8e, 0x66, 0xb9, 0x3b, 0xc8, 0x71, 0xd7, 0xbd, 0x83, + 0xb2, 0xed, 0x10, 0x8f, 0x88, 0xef, 0xb8, 0x08, 0x87, 0xbf, 0x9a, 0xc4, 0x2c, 0xbb, 0x08, 0x37, + 0x0d, 0x0d, 0x5b, 0xe5, 0x54, 0xa3, 0xd2, 0xa2, 0x4e, 0x74, 0x12, 0x6e, 0x5e, 0xef, 0xfe, 0xa2, + 0x08, 0xa5, 0xb5, 0x74, 0x86, 0xa6, 0xd3, 0xb1, 0x3d, 0xa2, 0x3b, 0x9a, 0x6d, 0x74, 0xa2, 0x9d, + 0x27, 0x68, 0x79, 0xf2, 0x88, 0xae, 0x2b, 0xbf, 0x4c, 0xc0, 0x54, 0xdd, 0xd5, 0xbf, 0x88, 0x56, + 0xc4, 0x4d, 0x98, 0xde, 0x71, 0x48, 0xbb, 0xa1, 0xb5, 0x5a, 0x0e, 0x72, 0xdd, 0xa2, 0xb0, 0x2a, + 0xac, 0x4d, 0x56, 0xaf, 0x05, 0xbe, 0xbc, 0xd0, 0xd1, 0xda, 0xe6, 0xa6, 0x12, 0x5f, 0x55, 0xd4, + 0xa9, 0xee, 0xb0, 0x42, 0x47, 0xe2, 0x6d, 0x00, 0x8f, 0x70, 0xcb, 0xe1, 0xd0, 0x72, 0x29, 0xf0, + 0xe5, 0x79, 0x6a, 0xd9, 0x5b, 0x53, 0xd4, 0x49, 0x8f, 0x30, 0xab, 0x37, 0x61, 0xac, 0x85, 0x2c, + 0xd2, 0x2e, 0x8e, 0x84, 0x06, 0x73, 0x81, 0x2f, 0x4f, 0x53, 0x83, 0x70, 0x5a, 0x51, 0xe9, 0xb2, + 0xb8, 0x0f, 0x33, 0x94, 0xbb, 0x4d, 0xf6, 0x2c, 0xaf, 0x61, 0x92, 0xe2, 0xe8, 0xaa, 0xb0, 0x36, + 0xb5, 0x71, 0xa7, 0x7c, 0xfe, 0xe3, 0x2c, 0x6f, 0x61, 0xdb, 0x40, 0x8e, 0x87, 0x0e, 0xbc, 0xea, + 0x72, 0xe0, 0xcb, 0x4b, 0x71, 0x9f, 0x18, 0xae, 0xa2, 0x86, 0x47, 0x50, 0x09, 0xc7, 0x0f, 0x48, + 0x92, 0xd8, 0xc0, 0xc5, 0xb1, 0xcb, 0x20, 0x36, 0x70, 0x1f, 0x71, 0x0d, 0x8b, 0xbb, 0x30, 0xdd, + 0x3d, 0x33, 0xee, 0x6f, 0x21, 0x17, 0x6d, 0xec, 0x0e, 0xe3, 0xa8, 0x8a, 0x0a, 0x1e, 0xe1, 0xbe, + 0xf6, 0x51, 0x1a, 0xb8, 0x38, 0xfe, 0xea, 0x29, 0xbb, 0x7e, 0x72, 0xca, 0x1a, 0x16, 0x9f, 0x09, + 0x30, 0xef, 0xa0, 0xb6, 0x86, 0x2d, 0x6c, 0xe9, 0x8d, 0x6d, 0xcd, 0xd4, 0xac, 0x26, 0x2a, 0x4e, + 0xe4, 0x22, 0x96, 0x02, 0x5f, 0x2e, 0xb1, 0x20, 0x0a, 0xb3, 0x42, 0xdb, 0x36, 0x11, 0x03, 0x57, + 0xd4, 0x39, 0x4e, 0x58, 0xa5, 0x53, 0xe2, 0xe7, 0xb0, 0x90, 0xb2, 0xb3, 0x38, 0x19, 0xc6, 0xe4, + 0x59, 0x70, 0x62, 0x6c, 0x96, 0x01, 0x1a, 0x50, 0xb0, 0x1d, 0x42, 0x76, 0xdc, 0x22, 0x84, 0xae, + 0xbc, 0x9f, 0xc5, 0x15, 0x96, 0x8e, 0x75, 0x57, 0x7f, 0x18, 0x82, 0x54, 0xe7, 0x03, 0x5f, 0x7e, + 0x8d, 0x4a, 0xa0, 0xb0, 0x8a, 0x1a, 0xe1, 0x8b, 0x2d, 0x98, 0xd0, 0xf6, 0x5a, 0xd8, 0x23, 0x8e, + 0x5b, 0x9c, 0x5a, 0x1d, 0x59, 0x9b, 0xda, 0xb8, 0x95, 0x85, 0xab, 0x42, 0x6d, 0xab, 0x0b, 0x81, + 0x2f, 0xcf, 0x52, 0x06, 0x06, 0xa7, 0xa8, 0x1c, 0x79, 0x73, 0xe2, 0xfb, 0x43, 0x79, 0xe8, 0xef, + 0x43, 0x79, 0x48, 0x59, 0x82, 0x85, 0x58, 0xc5, 0x50, 0x91, 0x6b, 0x13, 0xcb, 0x45, 0xca, 0x8f, + 0x93, 0x30, 0x1e, 0x61, 0x89, 0x77, 0x61, 0x36, 0x32, 0x4c, 0x14, 0x12, 0x31, 0xf0, 0xe5, 0x99, + 0x88, 0x84, 0xd5, 0x82, 0x99, 0x68, 0x2b, 0x2b, 0x08, 0x87, 0x02, 0xac, 0x20, 0x2b, 0x3c, 0x50, + 0xd4, 0x6a, 0x30, 0xa1, 0xb1, 0x3c, 0x18, 0xce, 0x15, 0x1b, 0x6f, 0x05, 0xbe, 0xfc, 0x06, 0x95, + 0x70, 0x1a, 0x8b, 0xa2, 0x2e, 0xf3, 0x65, 0xe6, 0x2b, 0x4f, 0x93, 0xd3, 0x25, 0x1a, 0x38, 0xac, + 0x65, 0x97, 0x2c, 0xb1, 0x9b, 0x47, 0x27, 0x49, 0xac, 0x61, 0xf1, 0x57, 0x01, 0x6e, 0x0c, 0x7a, + 0xd5, 0x78, 0xac, 0x99, 0xb8, 0x85, 0xbd, 0x4e, 0x23, 0x0c, 0x9e, 0xa8, 0x84, 0x6e, 0x5d, 0x4c, + 0xe7, 0x97, 0x11, 0x56, 0x18, 0xa2, 0xd5, 0x9b, 0x81, 0x2f, 0xaf, 0x45, 0xc9, 0x7e, 0x16, 0xaf, + 0xa2, 0x5e, 0xf7, 0x12, 0x67, 0xda, 0x07, 0x96, 0xaa, 0xde, 0xc0, 0x49, 0xf5, 0x63, 0x57, 0xa2, + 0x7e, 0x80, 0x77, 0x40, 0x7d, 0x0d, 0xf7, 0xab, 0xff, 0x2d, 0xfd, 0xec, 0xd1, 0xee, 0x9e, 0x66, + 0xf6, 0xd4, 0xd3, 0x72, 0x5e, 0xbf, 0x98, 0xfa, 0xde, 0xaf, 0x8f, 0x22, 0xd4, 0xf3, 0xdc, 0x42, + 0xbf, 0x82, 0x94, 0x5b, 0xe8, 0x03, 0x4b, 0xf5, 0xc3, 0xc0, 0x49, 0x3f, 0xc6, 0xaf, 0xd8, 0x8f, + 0x01, 0x05, 0x29, 0xf7, 0xd1, 0x07, 0xa6, 0xfc, 0x50, 0x80, 0xc5, 0xba, 0xab, 0x7f, 0x62, 0xe1, + 0xae, 0x0c, 0xfc, 0x04, 0x55, 0x9a, 0xcd, 0xee, 0xbe, 0x5c, 0xdd, 0x0e, 0xef, 0x5b, 0x86, 0x4f, + 0xef, 0x5b, 0x6e, 0x03, 0xd8, 0x7b, 0xdb, 0x26, 0x6e, 0x36, 0x1e, 0xa1, 0x4e, 0x58, 0x18, 0xa6, + 0xe3, 0x5d, 0x51, 0x6f, 0x4d, 0x51, 0x27, 0xe9, 0xe0, 0x33, 0xd4, 0x39, 0xe9, 0x3d, 0x1a, 0xbd, + 0xf0, 0x7b, 0xf4, 0x4c, 0x00, 0xd1, 0x46, 0x56, 0x2b, 0xf6, 0xc8, 0x76, 0x6b, 0x69, 0xbe, 0x56, + 0xe6, 0x7a, 0xe0, 0xcb, 0xcb, 0x91, 0x1f, 0x03, 0xd8, 0x8a, 0x3a, 0x17, 0x4d, 0x46, 0x1a, 0x1e, + 0x90, 0x54, 0x15, 0x06, 0xce, 0xd9, 0xd9, 0x9c, 0xa2, 0xa2, 0x5b, 0x24, 0x13, 0x2a, 0x6a, 0x58, + 0x7c, 0x2a, 0xc0, 0xbc, 0xf6, 0x58, 0xc3, 0x66, 0xdf, 0xd9, 0xe6, 0xeb, 0x75, 0x56, 0x02, 0x5f, + 0x2e, 0x46, 0x2f, 0x5b, 0x12, 0x5a, 0x51, 0xe7, 0xf8, 0x1c, 0xbb, 0x8f, 0x5d, 0xde, 0x1f, 0xd0, + 0x56, 0xe7, 0xe3, 0x2c, 0xbc, 0x03, 0x91, 0x7c, 0x9e, 0x46, 0x21, 0xf6, 0x84, 0x4b, 0xb0, 0x92, + 0x96, 0x0f, 0xfc, 0x2d, 0x7f, 0x21, 0x00, 0xd4, 0x5d, 0xfd, 0x3e, 0xb2, 0x89, 0x8b, 0xaf, 0x26, + 0x4d, 0xde, 0x86, 0x02, 0xcd, 0xef, 0x30, 0x45, 0x46, 0xe3, 0x7e, 0xd0, 0x79, 0x45, 0x8d, 0x36, + 0xc4, 0xfc, 0x58, 0x04, 0xb1, 0x27, 0x93, 0xab, 0xff, 0x6e, 0x34, 0xfc, 0xa6, 0xf9, 0x0a, 0x7b, + 0x46, 0xcb, 0xd1, 0xf6, 0xff, 0x67, 0xf2, 0x5f, 0x7d, 0x6a, 0xff, 0x2c, 0xc0, 0xca, 0x40, 0x07, + 0xdd, 0x68, 0x92, 0x76, 0x1b, 0x7b, 0x6d, 0x64, 0x79, 0x39, 0x93, 0x3c, 0xd6, 0x8d, 0x9c, 0xc6, + 0xa2, 0xa8, 0xa5, 0x64, 0x57, 0xbd, 0xc5, 0x17, 0x63, 0xed, 0x70, 0x21, 0x7b, 0x3b, 0xcc, 0x6e, + 0x32, 0x63, 0x94, 0xd3, 0x46, 0x95, 0x19, 0xf3, 0xf0, 0xf8, 0x47, 0x80, 0xd7, 0xeb, 0xae, 0x5e, + 0xb1, 0x6d, 0xb3, 0xf3, 0xb0, 0xaf, 0x34, 0x88, 0x37, 0x61, 0xfc, 0xec, 0x7e, 0x95, 0x6d, 0x39, + 0x77, 0x6c, 0xec, 0xc3, 0x0d, 0x0b, 0xed, 0x37, 0xe2, 0xf7, 0x39, 0x58, 0x7d, 0xe8, 0xd7, 0x6f, + 0xec, 0xd9, 0x3b, 0xd3, 0x44, 0x51, 0x25, 0x0b, 0xed, 0xdf, 0xef, 0x6d, 0xa9, 0x24, 0x6a, 0x4c, + 0xec, 0x28, 0x56, 0x41, 0x4a, 0x77, 0x99, 0x9f, 0xca, 0x9f, 0x02, 0xcc, 0xd6, 0x5d, 0x7d, 0xcb, + 0x24, 0x2e, 0x7f, 0x1e, 0x2f, 0xe7, 0x38, 0x4c, 0x1e, 0x0a, 0xb4, 0x4b, 0xae, 0x64, 0x8a, 0xcb, + 0x98, 0xbe, 0x8c, 0xe1, 0xb0, 0x0c, 0xd7, 0x12, 0x0e, 0x32, 0xe7, 0x37, 0xfe, 0x2d, 0xc0, 0x48, + 0xdd, 0xd5, 0xc5, 0x6f, 0x05, 0x98, 0xe0, 0x7f, 0x85, 0xbc, 0x97, 0x45, 0x57, 0xec, 0x8b, 0xa8, + 0x74, 0xef, 0x82, 0x86, 0x4c, 0x8e, 0xf8, 0x93, 0x00, 0xf3, 0x83, 0xcd, 0xca, 0x87, 0x19, 0x61, + 0x07, 0x10, 0x4a, 0xb5, 0xbc, 0x08, 0x5c, 0xe1, 0x53, 0x01, 0xc6, 0xd9, 0xeb, 0x70, 0x27, 0x23, + 0x6a, 0x64, 0x57, 0xfa, 0xe0, 0x62, 0x76, 0x5c, 0x43, 0xf7, 0xb2, 0x78, 0x8d, 0xcf, 0x7a, 0x59, + 0xcc, 0x30, 0xf3, 0x65, 0x25, 0xcb, 0x89, 0xf8, 0x42, 0x80, 0x85, 0xb4, 0x5a, 0x52, 0xcd, 0x08, + 0x9c, 0x82, 0x51, 0xfa, 0x34, 0x3f, 0x06, 0xd7, 0xf9, 0x5c, 0x80, 0xe9, 0xbe, 0xec, 0xbe, 0x9b, + 0x11, 0x3c, 0x6e, 0x5c, 0xda, 0xca, 0x61, 0xcc, 0x24, 0x55, 0xbf, 0xfe, 0xfd, 0x48, 0x12, 0x5e, + 0x1e, 0x49, 0xc2, 0x5f, 0x47, 0x92, 0xf0, 0xfc, 0x58, 0x1a, 0x7a, 0x79, 0x2c, 0x0d, 0xfd, 0x71, + 0x2c, 0x0d, 0x7d, 0x73, 0x4f, 0xc7, 0x9e, 0xb1, 0xb7, 0x5d, 0x6e, 0x92, 0xf6, 0xba, 0x8b, 0xf0, + 0xbb, 0x8c, 0x29, 0x1c, 0x84, 0x54, 0xeb, 0x07, 0xeb, 0x27, 0xfc, 0xcd, 0xda, 0xb1, 0x91, 0xbb, + 0x5d, 0x08, 0x2d, 0x6e, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x8b, 0x32, 0x35, 0x8c, 0x15, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1253,6 +1260,42 @@ func (m *MsgInitializeAccount) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x42 + } + if m.AvailableBalance != nil { + { + size, err := m.AvailableBalance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.PendingBalanceHi != nil { + { + size, err := m.PendingBalanceHi.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.PendingBalanceLo != nil { + { + size, err := m.PendingBalanceLo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x2a } if len(m.DecryptableBalance) > 0 { @@ -1743,6 +1786,18 @@ func (m *MsgInitializeAccount) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + if m.PendingBalanceLo != nil { + l = m.PendingBalanceLo.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.PendingBalanceHi != nil { + l = m.PendingBalanceHi.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.AvailableBalance != nil { + l = m.AvailableBalance.Size() + n += 1 + l + sovTx(uint64(l)) + } if m.Proofs != nil { l = m.Proofs.Size() n += 1 + l + sovTx(uint64(l)) @@ -2831,6 +2886,114 @@ func (m *MsgInitializeAccount) Unmarshal(dAtA []byte) error { m.DecryptableBalance = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingBalanceLo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PendingBalanceLo == nil { + m.PendingBalanceLo = &Ciphertext{} + } + if err := m.PendingBalanceLo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingBalanceHi", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PendingBalanceHi == nil { + m.PendingBalanceHi = &Ciphertext{} + } + if err := m.PendingBalanceHi.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AvailableBalance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AvailableBalance == nil { + m.AvailableBalance = &Ciphertext{} + } + if err := m.AvailableBalance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Proofs", wireType) } diff --git a/x/confidentialtransfers/types/withdraw.go b/x/confidentialtransfers/types/withdraw.go index 81f882ce34..8cb19ae8f3 100644 --- a/x/confidentialtransfers/types/withdraw.go +++ b/x/confidentialtransfers/types/withdraw.go @@ -1,8 +1,13 @@ package types import ( + "crypto/ecdsa" + "errors" + "github.com/coinbase/kryptology/pkg/core/curves" + "github.com/sei-protocol/sei-cryptography/pkg/encryption" "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" "github.com/sei-protocol/sei-cryptography/pkg/zkproofs" + "math/big" ) type Withdraw struct { @@ -24,3 +29,81 @@ type WithdrawProofs struct { // Equality proof that AvaialbleBalance - Enc(Amount) is equal to the RemainingBalance sent. RemainingBalanceEqualityProof *zkproofs.CiphertextCommitmentEqualityProof `json:"remaining_balance_equality_proof"` } + +func NewWithdraw( + privateKey ecdsa.PrivateKey, + currentAvailableBalance *elgamal.Ciphertext, + denom, + address, + currentDecryptableBalance string, + amount uint64) (*Withdraw, error) { + aesKey, err := encryption.GetAESKey(privateKey, denom) + if err != nil { + return &Withdraw{}, err + } + + teg := elgamal.NewTwistedElgamal() + keyPair, err := teg.KeyGen(privateKey, denom) + if err != nil { + return &Withdraw{}, err + } + + currentBalance, err := encryption.DecryptAESGCM(currentDecryptableBalance, aesKey) + if err != nil { + return &Withdraw{}, err + } + + if currentBalance < amount { + return &Withdraw{}, errors.New("insufficient balance") + } + + newBalance := currentBalance - amount + + // Encrypt the new value using the aesKey + newDecryptableBalance, err := encryption.EncryptAESGCM(newBalance, aesKey) + if err != nil { + return &Withdraw{}, err + } + + // Create the commitment on the new balance + newBalanceCommitment, randomness, err := teg.Encrypt(keyPair.PublicKey, newBalance) + if err != nil { + return &Withdraw{}, err + } + + // Create the range proof of the new balance to show that it is greater than 0. + rangeProof, err := zkproofs.NewRangeProof(64, int(newBalance), randomness) + if err != nil { + return &Withdraw{}, err + } + + // Create the equality proof to show that the new balance is equal to the difference between availableBalance and scalar. + newBalanceCiphertext, err := teg.SubScalar(currentAvailableBalance, amount) + if err != nil { + return &Withdraw{}, err + } + + bigIntNewBalance := new(big.Int).SetUint64(newBalance) + newBalanceScalar, err := curves.ED25519().Scalar.SetBigInt(bigIntNewBalance) + if err != nil { + return &Withdraw{}, err + } + + equalityProof, err := zkproofs.NewCiphertextCommitmentEqualityProof(keyPair, newBalanceCiphertext, &randomness, &newBalanceScalar) + if err != nil { + return &Withdraw{}, err + } + + proofs := WithdrawProofs{ + RemainingBalanceRangeProof: rangeProof, + RemainingBalanceEqualityProof: equalityProof, + } + return &Withdraw{ + FromAddress: address, + Denom: denom, + DecryptableBalance: newDecryptableBalance, + Amount: amount, + RemainingBalanceCommitment: newBalanceCommitment, + Proofs: &proofs, + }, nil +} diff --git a/x/confidentialtransfers/types/zk.go b/x/confidentialtransfers/types/zk.go index c10579c292..75321b41e8 100644 --- a/x/confidentialtransfers/types/zk.go +++ b/x/confidentialtransfers/types/zk.go @@ -129,15 +129,64 @@ func (c *InitializeAccountMsgProofs) Validate() error { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "pubkey validity proof is required") } + if c.ZeroPendingBalanceLoProof == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "zero pending balance lo proof is required") + } + + if c.ZeroPendingBalanceHiProof == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "zero pending balance hi proof is required") + } + + if c.ZeroAvailableBalanceProof == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "zero available balance proof is required") + } + return nil } func NewInitializeAccountMsgProofs(proofs *InitializeAccountProofs) *InitializeAccountMsgProofs { return &InitializeAccountMsgProofs{ - PubkeyValidityProof: NewPubkeyValidityProofProto(proofs.PubkeyValidityProof), + PubkeyValidityProof: NewPubkeyValidityProofProto(proofs.PubkeyValidityProof), + ZeroPendingBalanceLoProof: NewZeroBalanceProofProto(proofs.ZeroPendingBalanceLoProof), + ZeroPendingBalanceHiProof: NewZeroBalanceProofProto(proofs.ZeroPendingBalanceHiProof), + ZeroAvailableBalanceProof: NewZeroBalanceProofProto(proofs.ZeroAvailableBalanceProof), } } +func (c *InitializeAccountMsgProofs) FromProto() (*InitializeAccountProofs, error) { + err := c.Validate() + if err != nil { + return nil, err + } + + pubkeyValidityProof, err := c.PubkeyValidityProof.FromProto() + if err != nil { + return nil, err + } + + zeroPendingBalanceLoProof, err := c.ZeroPendingBalanceLoProof.FromProto() + if err != nil { + return nil, err + } + + zeroPendingBalanceHiProof, err := c.ZeroPendingBalanceHiProof.FromProto() + if err != nil { + return nil, err + } + + zeroAvailableBalanceProof, err := c.ZeroAvailableBalanceProof.FromProto() + if err != nil { + return nil, err + } + + return &InitializeAccountProofs{ + PubkeyValidityProof: pubkeyValidityProof, + ZeroPendingBalanceLoProof: zeroPendingBalanceLoProof, + ZeroPendingBalanceHiProof: zeroPendingBalanceHiProof, + ZeroAvailableBalanceProof: zeroAvailableBalanceProof, + }, nil +} + func (w *WithdrawMsgProofs) FromProto() (*WithdrawProofs, error) { err := w.Validate() if err != nil { @@ -172,29 +221,13 @@ func (w *WithdrawMsgProofs) Validate() error { return nil } -func (w *WithdrawMsgProofs) NewWithdrawMsgProofs(proofs *WithdrawProofs) *WithdrawMsgProofs { +func NewWithdrawMsgProofs(proofs *WithdrawProofs) *WithdrawMsgProofs { return &WithdrawMsgProofs{ RemainingBalanceRangeProof: NewRangeProofProto(proofs.RemainingBalanceRangeProof), RemainingBalanceEqualityProof: NewCiphertextCommitmentEqualityProofProto(proofs.RemainingBalanceEqualityProof), } } -func (c *InitializeAccountMsgProofs) FromProto() (*InitializeAccountProofs, error) { - err := c.Validate() - if err != nil { - return nil, err - } - - pubkeyValidityProof, err := c.PubkeyValidityProof.FromProto() - if err != nil { - return nil, err - } - - return &InitializeAccountProofs{ - PubkeyValidityProof: pubkeyValidityProof, - }, nil -} - func (c *CiphertextValidityProof) Validate() error { if c.Commitment_1 == nil || c.Commitment_2 == nil || c.Response_1 == nil || c.Response_2 == nil { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "ciphertext validity proof is invalid") @@ -472,7 +505,7 @@ func (a *Auditor) FromProto() (*TransferAuditor, error) { if err != nil { return nil, err } - transferAmountHiEqualityProof, err := a.TransferAmountLoEqualityProof.FromProto() + transferAmountHiEqualityProof, err := a.TransferAmountHiEqualityProof.FromProto() if err != nil { return nil, err } @@ -567,12 +600,6 @@ func (z *ZeroBalanceProof) FromProto() (*zkproofs.ZeroBalanceProof, error) { }, nil } -type CloseAccountProofs struct { - ZeroAvailableBalanceProof *zkproofs.ZeroBalanceProof - ZeroPendingBalanceLoProof *zkproofs.ZeroBalanceProof - ZeroPendingBalanceHiProof *zkproofs.ZeroBalanceProof -} - func (c *CloseAccountMsgProofs) Validate() error { if c.ZeroAvailableBalanceProof == nil || c.ZeroPendingBalanceLoProof == nil || c.ZeroPendingBalanceHiProof == nil { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "close account proof is invalid") diff --git a/x/confidentialtransfers/types/zk.pb.go b/x/confidentialtransfers/types/zk.pb.go index fb6c00564f..71eed279d4 100644 --- a/x/confidentialtransfers/types/zk.pb.go +++ b/x/confidentialtransfers/types/zk.pb.go @@ -131,7 +131,10 @@ func (m *TransferMsgProofs) GetTransferAmountHiEqualityProof() *CiphertextCipher } type InitializeAccountMsgProofs struct { - PubkeyValidityProof *PubkeyValidityProof `protobuf:"bytes,1,opt,name=pubkey_validity_proof,json=pubkeyValidityProof,proto3" json:"pubkey_validity_proof,omitempty"` + PubkeyValidityProof *PubkeyValidityProof `protobuf:"bytes,1,opt,name=pubkey_validity_proof,json=pubkeyValidityProof,proto3" json:"pubkey_validity_proof,omitempty"` + ZeroPendingBalanceLoProof *ZeroBalanceProof `protobuf:"bytes,2,opt,name=zero_pending_balance_lo_proof,json=zeroPendingBalanceLoProof,proto3" json:"zero_pending_balance_lo_proof,omitempty"` + ZeroPendingBalanceHiProof *ZeroBalanceProof `protobuf:"bytes,3,opt,name=zero_pending_balance_hi_proof,json=zeroPendingBalanceHiProof,proto3" json:"zero_pending_balance_hi_proof,omitempty"` + ZeroAvailableBalanceProof *ZeroBalanceProof `protobuf:"bytes,4,opt,name=zero_available_balance_proof,json=zeroAvailableBalanceProof,proto3" json:"zero_available_balance_proof,omitempty"` } func (m *InitializeAccountMsgProofs) Reset() { *m = InitializeAccountMsgProofs{} } @@ -174,6 +177,27 @@ func (m *InitializeAccountMsgProofs) GetPubkeyValidityProof() *PubkeyValidityPro return nil } +func (m *InitializeAccountMsgProofs) GetZeroPendingBalanceLoProof() *ZeroBalanceProof { + if m != nil { + return m.ZeroPendingBalanceLoProof + } + return nil +} + +func (m *InitializeAccountMsgProofs) GetZeroPendingBalanceHiProof() *ZeroBalanceProof { + if m != nil { + return m.ZeroPendingBalanceHiProof + } + return nil +} + +func (m *InitializeAccountMsgProofs) GetZeroAvailableBalanceProof() *ZeroBalanceProof { + if m != nil { + return m.ZeroAvailableBalanceProof + } + return nil +} + type WithdrawMsgProofs struct { RemainingBalanceRangeProof *RangeProof `protobuf:"bytes,1,opt,name=remaining_balance_range_proof,json=remainingBalanceRangeProof,proto3" json:"remaining_balance_range_proof,omitempty"` RemainingBalanceEqualityProof *CiphertextCommitmentEqualityProof `protobuf:"bytes,2,opt,name=remaining_balance_equality_proof,json=remainingBalanceEqualityProof,proto3" json:"remaining_balance_equality_proof,omitempty"` @@ -722,58 +746,59 @@ func init() { func init() { proto.RegisterFile("confidentialtransfers/zk.proto", fileDescriptor_71d8640cbb42ddec) } var fileDescriptor_71d8640cbb42ddec = []byte{ - // 813 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x96, 0x51, 0x6b, 0xd3, 0x50, - 0x14, 0xc7, 0x77, 0x53, 0xb7, 0xb9, 0xbb, 0xa2, 0x2e, 0x73, 0xa8, 0xc3, 0xc6, 0x2d, 0x82, 0x0e, - 0xc5, 0x76, 0xed, 0xc0, 0x27, 0x61, 0xac, 0x55, 0x98, 0xb0, 0x41, 0x29, 0xa2, 0xb8, 0x97, 0x90, - 0x26, 0x77, 0xed, 0x65, 0xe9, 0xbd, 0xf1, 0xde, 0x74, 0x36, 0x01, 0x7d, 0xf1, 0x0b, 0x28, 0xc2, - 0x04, 0x41, 0xd0, 0x27, 0xbf, 0x8a, 0x8f, 0x7b, 0xf4, 0x51, 0xb6, 0x0f, 0xa2, 0xe4, 0x26, 0x4d, - 0x93, 0xb4, 0xae, 0xdb, 0xec, 0xe6, 0x5b, 0xce, 0x39, 0xf7, 0x9e, 0xff, 0x2f, 0xe7, 0x9e, 0x93, - 0x1b, 0xa8, 0x18, 0x94, 0x6c, 0x63, 0x13, 0x11, 0x07, 0xeb, 0x96, 0xc3, 0x74, 0xc2, 0xb7, 0x11, - 0xe3, 0x05, 0x6f, 0x27, 0x6f, 0x33, 0xea, 0x50, 0xf9, 0x1e, 0x47, 0x58, 0x3c, 0x19, 0xd4, 0xca, - 0x73, 0x84, 0x8d, 0xa6, 0x8e, 0x49, 0x7e, 0xe0, 0x26, 0xf5, 0x37, 0x84, 0x33, 0xcf, 0x42, 0x6b, - 0x93, 0x37, 0xaa, 0x8c, 0xd2, 0x6d, 0x2e, 0x7f, 0x03, 0xf0, 0x3e, 0x43, 0x2d, 0x1d, 0x13, 0x4c, - 0x1a, 0x5a, 0x5d, 0xb7, 0x74, 0x62, 0x20, 0xcd, 0xa0, 0xad, 0x16, 0x76, 0x5a, 0x88, 0x38, 0xda, - 0xae, 0x6e, 0x61, 0x13, 0x3b, 0xae, 0x66, 0xfb, 0x1b, 0xae, 0x83, 0x05, 0xb0, 0x34, 0x5d, 0xaa, - 0xe4, 0x8f, 0x2f, 0x9c, 0xaf, 0x60, 0xbb, 0x89, 0x98, 0x83, 0x3a, 0xce, 0xf3, 0x30, 0x97, 0xd0, - 0xae, 0xdd, 0x8d, 0x74, 0xcb, 0x81, 0x6c, 0x25, 0x52, 0x4d, 0x2c, 0x94, 0x3f, 0x03, 0xb8, 0xc4, - 0x11, 0x31, 0x11, 0xd3, 0xba, 0x59, 0x35, 0xbd, 0x45, 0xdb, 0xc4, 0xd1, 0x2c, 0x9a, 0x06, 0x94, - 0x46, 0x07, 0x78, 0x3b, 0x10, 0xed, 0x16, 0x6d, 0x4d, 0x48, 0x6e, 0xd0, 0x63, 0xc3, 0x35, 0x71, - 0x1a, 0x2e, 0x73, 0xc6, 0x70, 0xeb, 0x38, 0x09, 0xf7, 0x55, 0x9c, 0xae, 0x81, 0x6d, 0xec, 0x1f, - 0xe5, 0xf0, 0xe2, 0x5d, 0x18, 0x1d, 0xdf, 0x9d, 0x48, 0xf7, 0xe8, 0xfa, 0x1d, 0x8d, 0xd8, 0x5f, - 0xc2, 0xf1, 0xb3, 0x47, 0x4c, 0x57, 0xd1, 0x85, 0xb9, 0xfe, 0x11, 0x61, 0x3a, 0x69, 0xa0, 0x90, - 0x69, 0x42, 0x30, 0x3d, 0x3c, 0x09, 0x53, 0xcd, 0xdf, 0x1e, 0x60, 0xcc, 0xa7, 0xe7, 0xa0, 0x17, - 0x93, 0xf7, 0x00, 0x5c, 0xe8, 0xd7, 0x46, 0xaf, 0xda, 0xba, 0xd5, 0x2b, 0xc9, 0xa4, 0x90, 0xdf, - 0x3c, 0x5d, 0x49, 0x7a, 0x43, 0xf7, 0x24, 0xcc, 0x1a, 0x50, 0xe5, 0xd2, 0x54, 0x89, 0xb0, 0xfc, - 0x09, 0xc0, 0xc5, 0x01, 0xfd, 0x94, 0x22, 0xbb, 0xf8, 0x4f, 0x64, 0xd1, 0x53, 0x8a, 0xcc, 0x49, - 0x35, 0xd4, 0x70, 0xb2, 0x26, 0x4e, 0x93, 0x4d, 0x9d, 0x03, 0xd9, 0x3a, 0x4e, 0x84, 0xd5, 0x0f, - 0x00, 0xce, 0x3f, 0x25, 0xd8, 0xcf, 0x8a, 0x3d, 0xb4, 0x66, 0x18, 0xfe, 0xa2, 0xde, 0xa7, 0x98, - 0xc3, 0x39, 0xbb, 0x5d, 0xdf, 0x41, 0xee, 0xe0, 0x6f, 0xee, 0xea, 0x49, 0x58, 0xab, 0x22, 0x51, - 0xb2, 0xdd, 0x67, 0xed, 0x7e, 0xa7, 0xfa, 0x5d, 0x82, 0x33, 0x2f, 0xb0, 0xd3, 0x34, 0x99, 0xfe, - 0xba, 0x87, 0x32, 0xb4, 0xe3, 0xc1, 0xff, 0xed, 0x78, 0xe9, 0xfc, 0x3b, 0x5e, 0xdd, 0xcb, 0xc0, - 0xb9, 0x8a, 0x45, 0x79, 0xff, 0xc1, 0xbd, 0x81, 0x37, 0x3d, 0xc4, 0xa8, 0xa6, 0xef, 0xea, 0xd8, - 0xd2, 0xeb, 0x16, 0x8a, 0xb0, 0xe3, 0xc5, 0x7a, 0x74, 0x12, 0xda, 0x2d, 0xc4, 0x68, 0x48, 0x11, - 0xc0, 0xdd, 0xf0, 0x15, 0xd6, 0xba, 0x02, 0xf1, 0x90, 0xfc, 0x16, 0xe6, 0x84, 0xbc, 0x8d, 0x88, - 0x19, 0xaf, 0x99, 0x45, 0x13, 0xd5, 0x1a, 0x81, 0x7e, 0x35, 0x50, 0x08, 0x03, 0x1b, 0xf4, 0x68, - 0xfd, 0x26, 0x4e, 0xdc, 0x7a, 0x23, 0xd7, 0x5f, 0xc7, 0xc1, 0xc1, 0x14, 0xe1, 0xec, 0x80, 0x76, - 0x97, 0xb3, 0x10, 0xb8, 0xa2, 0xf4, 0xd9, 0x1a, 0x70, 0x7d, 0xcb, 0x13, 0x85, 0xc8, 0xd6, 0x80, - 0xa7, 0x7e, 0x01, 0xf0, 0xda, 0x5f, 0x6e, 0x05, 0x79, 0x11, 0x66, 0x63, 0xbf, 0x3f, 0xc5, 0x30, - 0xc5, 0x74, 0xcf, 0x57, 0x4c, 0x2d, 0x29, 0x85, 0x79, 0x63, 0x4b, 0x4a, 0x72, 0x0e, 0x42, 0x86, - 0xb8, 0x4d, 0x09, 0x47, 0x5a, 0x51, 0xdc, 0xab, 0xd9, 0xda, 0x54, 0xd7, 0x53, 0x4c, 0x84, 0x4b, - 0xe2, 0x4e, 0x8b, 0x85, 0x4b, 0xaa, 0x01, 0x61, 0x6c, 0x24, 0xae, 0xc2, 0xf1, 0x5e, 0x23, 0x65, - 0x6b, 0x81, 0x21, 0x2b, 0x10, 0x32, 0x9d, 0x98, 0xb4, 0x45, 0x10, 0xe7, 0x21, 0x42, 0xcc, 0x23, - 0xdf, 0x82, 0xd3, 0x6d, 0xdb, 0x46, 0x4c, 0xab, 0xd3, 0x36, 0x31, 0xc5, 0x21, 0x64, 0x6a, 0x50, - 0xb8, 0xca, 0xbe, 0x47, 0x7d, 0x07, 0xe0, 0xe2, 0xd0, 0xa9, 0x90, 0x2f, 0x41, 0xc9, 0x5d, 0x0e, - 0x95, 0x25, 0x77, 0x59, 0xd8, 0xc5, 0x50, 0x4e, 0x72, 0x8b, 0xc2, 0x2e, 0x89, 0xec, 0xbe, 0x5d, - 0xf2, 0x6d, 0x8f, 0x87, 0x2f, 0x2c, 0x79, 0x5c, 0xd8, 0x9d, 0xf0, 0x0d, 0x25, 0xaf, 0x23, 0x6c, - 0x26, 0x6e, 0x4c, 0xdf, 0x66, 0xea, 0xc7, 0x24, 0xc5, 0xe0, 0x2f, 0xeb, 0x69, 0x28, 0xdc, 0x95, - 0x2e, 0x85, 0xbb, 0x12, 0x52, 0x8d, 0xa7, 0xa8, 0x26, 0x52, 0x54, 0x93, 0x11, 0x55, 0x19, 0x5e, - 0x49, 0xb7, 0xa0, 0x7c, 0x19, 0x66, 0x5c, 0xcd, 0x8e, 0x20, 0xaa, 0x81, 0xc3, 0x8c, 0x28, 0x1e, - 0x07, 0x4d, 0x96, 0x09, 0x9b, 0xac, 0xfc, 0xf2, 0xc7, 0x81, 0x02, 0xf6, 0x0f, 0x14, 0xf0, 0xeb, - 0x40, 0x01, 0xef, 0x0f, 0x95, 0xb1, 0xfd, 0x43, 0x65, 0xec, 0xe7, 0xa1, 0x32, 0xb6, 0xb5, 0xda, - 0xc0, 0x4e, 0xb3, 0x5d, 0xcf, 0x1b, 0xb4, 0x55, 0xe0, 0x08, 0x3f, 0xe8, 0x4e, 0x85, 0x30, 0xc4, - 0x58, 0x14, 0x3a, 0x85, 0xc1, 0xbf, 0xfe, 0x8e, 0x6b, 0x23, 0x5e, 0x9f, 0x10, 0x3b, 0x56, 0xfe, - 0x04, 0x00, 0x00, 0xff, 0xff, 0x3a, 0xd7, 0x9a, 0x56, 0x20, 0x0c, 0x00, 0x00, + // 831 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0xcf, 0x6f, 0xd3, 0x4a, + 0x10, 0xc7, 0xbb, 0x4e, 0x7f, 0xbc, 0x6e, 0xa3, 0xf7, 0x5e, 0x5d, 0x2a, 0xa0, 0x22, 0xa6, 0x35, + 0x12, 0x54, 0x20, 0x92, 0x26, 0x95, 0x38, 0x21, 0x55, 0x4d, 0x40, 0x2a, 0x52, 0x2b, 0x45, 0x11, + 0x02, 0xd1, 0x8b, 0xe5, 0xd8, 0xdb, 0x64, 0x55, 0x67, 0xd7, 0xec, 0x3a, 0x25, 0xb6, 0x04, 0x17, + 0xfe, 0x81, 0x4a, 0x48, 0x45, 0x42, 0x42, 0x82, 0x13, 0xff, 0x0a, 0xc7, 0x1e, 0x39, 0xa2, 0xf6, + 0x0f, 0x01, 0x79, 0xed, 0x38, 0xb6, 0x13, 0x9a, 0xb6, 0xa4, 0xe5, 0xc0, 0x2d, 0xb3, 0xb3, 0x3b, + 0xdf, 0x8f, 0x67, 0x67, 0xc6, 0x0e, 0x54, 0x0c, 0x4a, 0x76, 0xb0, 0x89, 0x88, 0x83, 0x75, 0xcb, + 0x61, 0x3a, 0xe1, 0x3b, 0x88, 0xf1, 0x82, 0xb7, 0x9b, 0xb7, 0x19, 0x75, 0xa8, 0x7c, 0x97, 0x23, + 0x2c, 0x7e, 0x19, 0xd4, 0xca, 0x73, 0x84, 0x8d, 0xa6, 0x8e, 0x49, 0x7e, 0xe0, 0x21, 0xf5, 0x07, + 0x84, 0xb3, 0x4f, 0x43, 0x6b, 0x8b, 0x37, 0xaa, 0x8c, 0xd2, 0x1d, 0x2e, 0x7f, 0x06, 0xf0, 0x1e, + 0x43, 0x2d, 0x1d, 0x13, 0x4c, 0x1a, 0x5a, 0x5d, 0xb7, 0x74, 0x62, 0x20, 0xcd, 0xa0, 0xad, 0x16, + 0x76, 0x5a, 0x88, 0x38, 0xda, 0x9e, 0x6e, 0x61, 0x13, 0x3b, 0xae, 0x66, 0xfb, 0x07, 0xae, 0x81, + 0x45, 0xb0, 0x3c, 0x53, 0xaa, 0xe4, 0x4f, 0x2f, 0x9c, 0xaf, 0x60, 0xbb, 0x89, 0x98, 0x83, 0x3a, + 0xce, 0xb3, 0x30, 0x96, 0xd0, 0xae, 0xdd, 0x89, 0x74, 0xcb, 0x81, 0x6c, 0x25, 0x52, 0x4d, 0x6c, + 0x94, 0x3f, 0x00, 0xb8, 0xcc, 0x11, 0x31, 0x11, 0xd3, 0xba, 0x51, 0x35, 0xbd, 0x45, 0xdb, 0xc4, + 0xd1, 0x2c, 0x9a, 0x06, 0x94, 0x46, 0x07, 0x78, 0x2b, 0x10, 0xed, 0x26, 0x6d, 0x5d, 0x48, 0x6e, + 0xd2, 0x53, 0xc3, 0x35, 0x71, 0x1a, 0x2e, 0x73, 0xc1, 0x70, 0x1b, 0x38, 0x09, 0xf7, 0x49, 0xdc, + 0xae, 0x81, 0x6d, 0xec, 0x5f, 0xe5, 0xf0, 0xe4, 0x8d, 0x8f, 0x8e, 0xef, 0x76, 0xa4, 0x7b, 0x72, + 0xfe, 0x4e, 0x46, 0xec, 0x4f, 0xe1, 0xc4, 0xc5, 0x23, 0xa6, 0xb3, 0xe8, 0xc2, 0x5c, 0x7f, 0x8b, + 0x30, 0x9d, 0x34, 0x50, 0xc8, 0x34, 0x29, 0x98, 0x1e, 0x9c, 0x85, 0xa9, 0xe6, 0x1f, 0x0f, 0x30, + 0x16, 0xd2, 0x7d, 0xd0, 0xf3, 0xc9, 0x07, 0x00, 0x2e, 0xf6, 0x6b, 0xa3, 0x97, 0x6d, 0xdd, 0xea, + 0xa5, 0x64, 0x4a, 0xc8, 0x6f, 0x9d, 0x2f, 0x25, 0xbd, 0xa6, 0x7b, 0x1c, 0x46, 0x0d, 0xa8, 0x72, + 0x69, 0xaa, 0x84, 0x5b, 0x7e, 0x0f, 0xe0, 0xd2, 0x80, 0x7a, 0x4a, 0x91, 0xfd, 0xf3, 0x5b, 0x64, + 0xd1, 0xaf, 0x14, 0x99, 0x93, 0x2a, 0xa8, 0xe1, 0x64, 0x4d, 0x9c, 0x26, 0x9b, 0xbe, 0x04, 0xb2, + 0x0d, 0x9c, 0x70, 0xab, 0xfb, 0xe3, 0x70, 0xe1, 0x09, 0xc1, 0x7e, 0x54, 0xec, 0xa1, 0x75, 0xc3, + 0xf0, 0x37, 0xf5, 0x46, 0x31, 0x87, 0xf3, 0x76, 0xbb, 0xbe, 0x8b, 0xdc, 0xc1, 0x33, 0x77, 0xed, + 0x2c, 0xac, 0x55, 0x11, 0x28, 0x59, 0xee, 0x73, 0x76, 0xff, 0xa2, 0xfc, 0x06, 0xe6, 0x3c, 0xc4, + 0xa8, 0x66, 0x23, 0x62, 0xc6, 0x4b, 0xcc, 0xa2, 0x89, 0x79, 0xfa, 0xf0, 0x2c, 0xe2, 0xdb, 0x88, + 0xd1, 0xb0, 0x68, 0x02, 0xe5, 0xeb, 0xbe, 0x44, 0x35, 0x50, 0x08, 0x1d, 0x9b, 0xf4, 0x64, 0xfd, + 0x26, 0x4e, 0x8c, 0xcc, 0x91, 0xeb, 0x6f, 0xe0, 0x40, 0xff, 0x35, 0xbc, 0x21, 0xf4, 0xf5, 0x3d, + 0x1d, 0x5b, 0x7a, 0xdd, 0x42, 0x11, 0x41, 0x7c, 0x22, 0x8e, 0x40, 0x7e, 0xbd, 0x2b, 0x10, 0x77, + 0xa9, 0x5f, 0x24, 0x38, 0xfb, 0x1c, 0x3b, 0x4d, 0x93, 0xe9, 0xaf, 0x7a, 0x95, 0x30, 0x74, 0xe0, + 0x80, 0x3f, 0x3b, 0x70, 0xa4, 0xcb, 0x1f, 0x38, 0xea, 0x41, 0x06, 0xce, 0x57, 0x2c, 0xca, 0xfb, + 0xfb, 0x66, 0xd8, 0x15, 0x82, 0x0b, 0xbd, 0xc2, 0xbf, 0xbd, 0x83, 0xd4, 0x22, 0x9c, 0x1b, 0x30, + 0x6d, 0xe4, 0x2c, 0x04, 0xae, 0x48, 0x7d, 0xb6, 0x06, 0x5c, 0xdf, 0xf2, 0x44, 0x22, 0xb2, 0x35, + 0xe0, 0xa9, 0x1f, 0x01, 0xbc, 0xfa, 0x8b, 0x97, 0xb2, 0xbc, 0x04, 0xb3, 0xb1, 0xaf, 0xcf, 0x62, + 0x18, 0x62, 0xa6, 0xb7, 0x56, 0x4c, 0x6d, 0x29, 0x85, 0x71, 0x63, 0x5b, 0x4a, 0x72, 0x0e, 0x42, + 0x86, 0xb8, 0x4d, 0x09, 0x47, 0x5a, 0x51, 0x34, 0x71, 0xb6, 0x36, 0xdd, 0x5d, 0x29, 0x26, 0xdc, + 0x25, 0xf1, 0x49, 0x11, 0x73, 0x97, 0x54, 0x03, 0xc2, 0x58, 0x4b, 0x5c, 0x81, 0x13, 0xbd, 0x42, + 0xca, 0xd6, 0x02, 0x43, 0x56, 0x20, 0x64, 0x3a, 0x31, 0x69, 0x8b, 0x20, 0xce, 0x43, 0x84, 0xd8, + 0x8a, 0x7c, 0x13, 0xce, 0xb4, 0x6d, 0x1b, 0x31, 0xad, 0x4e, 0xdb, 0xc4, 0x14, 0x97, 0x90, 0xa9, + 0x41, 0xb1, 0x54, 0xf6, 0x57, 0xd4, 0xb7, 0x00, 0x2e, 0x0d, 0xed, 0x0a, 0xf9, 0x5f, 0x28, 0xb9, + 0x2b, 0xa1, 0xb2, 0xe4, 0xae, 0x08, 0xbb, 0x18, 0xca, 0x49, 0x6e, 0x51, 0xd8, 0x25, 0x11, 0xdd, + 0xb7, 0x4b, 0xbe, 0xed, 0xf1, 0xf0, 0x81, 0x25, 0x8f, 0x0b, 0xbb, 0x13, 0x3e, 0xa1, 0xe4, 0x75, + 0x84, 0xcd, 0xc4, 0x07, 0x8b, 0x6f, 0x33, 0xf5, 0x5d, 0x92, 0x62, 0xf0, 0x8b, 0xed, 0x3c, 0x14, + 0xee, 0x6a, 0x97, 0xc2, 0x5d, 0x0d, 0xa9, 0x26, 0x52, 0x54, 0x93, 0x29, 0xaa, 0xa9, 0x88, 0xaa, + 0x0c, 0xff, 0x4f, 0x97, 0xa0, 0xfc, 0x1f, 0xcc, 0xb8, 0x9a, 0x1d, 0x41, 0x54, 0x83, 0x05, 0x33, + 0xa2, 0x78, 0x14, 0x14, 0x59, 0x26, 0x2c, 0xb2, 0xf2, 0x8b, 0xaf, 0x47, 0x0a, 0x38, 0x3c, 0x52, + 0xc0, 0xf7, 0x23, 0x05, 0xec, 0x1f, 0x2b, 0x63, 0x87, 0xc7, 0xca, 0xd8, 0xb7, 0x63, 0x65, 0x6c, + 0x7b, 0xad, 0x81, 0x9d, 0x66, 0xbb, 0x9e, 0x37, 0x68, 0xab, 0xc0, 0x11, 0xbe, 0xdf, 0xed, 0x0a, + 0x61, 0x88, 0xb6, 0x28, 0x74, 0x0a, 0x83, 0xff, 0x79, 0x39, 0xae, 0x8d, 0x78, 0x7d, 0x52, 0x9c, + 0x58, 0xfd, 0x19, 0x00, 0x00, 0xff, 0xff, 0xde, 0xce, 0x41, 0x46, 0x9f, 0x0d, 0x00, 0x00, } func (m *TransferMsgProofs) Marshal() (dAtA []byte, err error) { @@ -927,6 +952,42 @@ func (m *InitializeAccountMsgProofs) MarshalToSizedBuffer(dAtA []byte) (int, err _ = i var l int _ = l + if m.ZeroAvailableBalanceProof != nil { + { + size, err := m.ZeroAvailableBalanceProof.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintZk(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.ZeroPendingBalanceHiProof != nil { + { + size, err := m.ZeroPendingBalanceHiProof.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintZk(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ZeroPendingBalanceLoProof != nil { + { + size, err := m.ZeroPendingBalanceLoProof.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintZk(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } if m.PubkeyValidityProof != nil { { size, err := m.PubkeyValidityProof.MarshalToSizedBuffer(dAtA[:i]) @@ -1425,6 +1486,18 @@ func (m *InitializeAccountMsgProofs) Size() (n int) { l = m.PubkeyValidityProof.Size() n += 1 + l + sovZk(uint64(l)) } + if m.ZeroPendingBalanceLoProof != nil { + l = m.ZeroPendingBalanceLoProof.Size() + n += 1 + l + sovZk(uint64(l)) + } + if m.ZeroPendingBalanceHiProof != nil { + l = m.ZeroPendingBalanceHiProof.Size() + n += 1 + l + sovZk(uint64(l)) + } + if m.ZeroAvailableBalanceProof != nil { + l = m.ZeroAvailableBalanceProof.Size() + n += 1 + l + sovZk(uint64(l)) + } return n } @@ -2064,6 +2137,114 @@ func (m *InitializeAccountMsgProofs) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ZeroPendingBalanceLoProof", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZk + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthZk + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthZk + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ZeroPendingBalanceLoProof == nil { + m.ZeroPendingBalanceLoProof = &ZeroBalanceProof{} + } + if err := m.ZeroPendingBalanceLoProof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ZeroPendingBalanceHiProof", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZk + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthZk + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthZk + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ZeroPendingBalanceHiProof == nil { + m.ZeroPendingBalanceHiProof = &ZeroBalanceProof{} + } + if err := m.ZeroPendingBalanceHiProof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ZeroAvailableBalanceProof", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZk + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthZk + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthZk + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ZeroAvailableBalanceProof == nil { + m.ZeroAvailableBalanceProof = &ZeroBalanceProof{} + } + if err := m.ZeroAvailableBalanceProof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipZk(dAtA[iNdEx:]) diff --git a/x/confidentialtransfers/types/zk_test.go b/x/confidentialtransfers/types/zk_test.go index 3110c755f4..227550ea53 100644 --- a/x/confidentialtransfers/types/zk_test.go +++ b/x/confidentialtransfers/types/zk_test.go @@ -275,16 +275,53 @@ func TestInitializeAccountMsgProofs_Validate(t *testing.T) { { name: "valid proofs", proofs: InitializeAccountMsgProofs{ - PubkeyValidityProof: &PubkeyValidityProof{}, + PubkeyValidityProof: &PubkeyValidityProof{}, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{}, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{}, + ZeroAvailableBalanceProof: &ZeroBalanceProof{}, }, wantErr: false, }, { - name: "missing PubkeyValidityProof", - proofs: InitializeAccountMsgProofs{}, + name: "missing PubkeyValidityProof", + proofs: InitializeAccountMsgProofs{ + ZeroPendingBalanceLoProof: &ZeroBalanceProof{}, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{}, + ZeroAvailableBalanceProof: &ZeroBalanceProof{}, + }, wantErr: true, errMsg: "pubkey validity proof is required", }, + { + name: "missing ZeroPendingBalanceLoProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{}, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{}, + ZeroAvailableBalanceProof: &ZeroBalanceProof{}, + }, + wantErr: true, + errMsg: "zero pending balance lo proof is required", + }, + { + name: "missing ZeroPendingBalanceHiProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{}, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{}, + ZeroAvailableBalanceProof: &ZeroBalanceProof{}, + }, + wantErr: true, + errMsg: "zero pending balance hi proof is required", + }, + { + name: "missing ZeroAvailableBalanceProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{}, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{}, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{}, + }, + wantErr: true, + errMsg: "zero available balance proof is required", + }, } for _, tt := range tests { @@ -314,15 +351,109 @@ func TestInitializeAccountMsgProofs_FromProto(t *testing.T) { Y: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), }, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroAvailableBalanceProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, }, wantErr: false, }, { - name: "missing PubkeyValidityProof", - proofs: InitializeAccountMsgProofs{}, + name: "missing PubkeyValidityProof", + proofs: InitializeAccountMsgProofs{ + ZeroPendingBalanceLoProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroAvailableBalanceProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + }, wantErr: true, errMsg: "pubkey validity proof is required", }, + { + name: "missing ZeroPendingBalanceLoProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{ + Y: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroAvailableBalanceProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + }, + wantErr: true, + errMsg: "zero pending balance lo proof is required", + }, + { + name: "missing ZeroPendingBalanceHiProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{ + Y: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroAvailableBalanceProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + }, + wantErr: true, + errMsg: "zero pending balance hi proof is required", + }, + { + name: "missing ZeroAvailableBalanceProof", + proofs: InitializeAccountMsgProofs{ + PubkeyValidityProof: &PubkeyValidityProof{ + Y: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceLoProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + ZeroPendingBalanceHiProof: &ZeroBalanceProof{ + YP: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + YD: curves.ED25519().Point.Random(crand.Reader).ToAffineCompressed(), + Z: curves.ED25519().Scalar.Random(crand.Reader).Bytes(), + }, + }, + wantErr: true, + errMsg: "zero available balance proof is required", + }, } for _, tt := range tests { diff --git a/x/confidentialtransfers/utils/utils.go b/x/confidentialtransfers/utils/utils.go new file mode 100644 index 0000000000..fb929d7806 --- /dev/null +++ b/x/confidentialtransfers/utils/utils.go @@ -0,0 +1,19 @@ +package utils + +import "errors" + +// Splits some amount (maximum of 48 bit) into two parts: the bottom 16 bits and the next 32 bits +func SplitTransferBalance(amount uint64) (uint16, uint32, error) { + + if amount > uint64((2<<48)-1) { + return 0, 0, errors.New("amount is too large") + } + + // Extract the bottom 16 bits (rightmost 16 bits) + bottom16 := uint16(amount & 0xFFFF) + + // Extract the next 32 bits (from bit 16 to bit 47) (Everything else is ignored since the max is 48 bits) + next32 := uint32((amount >> 16) & 0xFFFFFFFF) + + return bottom16, next32, nil +}