Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Confidential Transfers Module #1980

Open
wants to merge 83 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
b667e02
ciphertext example type
dssei Oct 16, 2024
e6dab7b
fix linting errors
dssei Oct 16, 2024
4bd729e
commit generated code
dssei Oct 16, 2024
aad5138
codec + some validation + routing
dssei Oct 16, 2024
6797664
adding zk related objects
dssei Oct 17, 2024
592beb2
tests
dssei Oct 17, 2024
b33ad17
remove challenge from ciphertext validity proof
dssei Oct 17, 2024
a565c04
revert params.pb.go
dssei Oct 17, 2024
3333a06
Fix test to use correct result object for proofs. Add range proofs
dssei Oct 29, 2024
46aac3c
Add decryption test
dssei Oct 29, 2024
085deb9
RemainingBalanceEqualityProof
dssei Oct 29, 2024
675c39e
TransferAmountLoEqualityProof & TransferAmountHiEqualityProof tests
dssei Oct 29, 2024
3e15326
fix formatting
dssei Oct 29, 2024
8a73f57
add missing validations
dssei Oct 29, 2024
7f7550f
auditors
dssei Oct 30, 2024
a792340
add marshalling/unmarshalling of the messages
dssei Oct 30, 2024
81265bc
go mod tidy
dssei Oct 30, 2024
bcdb017
fix linting issues
dssei Oct 30, 2024
fd16a60
add more tests
dssei Oct 30, 2024
0775f98
tests for zk.go
dssei Oct 30, 2024
1b07386
more tests
dssei Oct 30, 2024
ee14d68
Scaffolding (#1916)
mj850 Nov 1, 2024
6129d89
CT types - Apply balance close account (#1919)
dssei Nov 4, 2024
71ad41a
Add Deposit and InitializeAccount types (#1921)
mj850 Nov 5, 2024
92bbcf5
Genesis draft (#1924)
dssei Nov 7, 2024
77c7fb4
Ct queries (#1927)
dssei Nov 12, 2024
094bf82
Add msg_server methods (#1923)
mj850 Nov 13, 2024
26a9971
basic cli and first draft command for ct module (#1929)
dssei Nov 14, 2024
6c66a70
Add confidential transfer queries to seid (#1931)
mj850 Nov 15, 2024
a7d5cc5
Update confidential transfers swagger/openAPI spec (#1937)
dssei Nov 18, 2024
c51f80d
Add other confidential tx methods to seid (#1933)
mj850 Nov 19, 2024
a45b042
Ct ACL mappings (#1947)
dssei Nov 19, 2024
435c029
Add query decrypted tx method to confidential transfers client (#1950)
mj850 Nov 21, 2024
9a565b8
Add Integration tests for CT Module (#1958)
mj850 Nov 26, 2024
fcfa23b
add ct to load test framework (#1959)
dssei Dec 3, 2024
193a429
Use BigInt in CT Module (#1966)
mj850 Dec 3, 2024
8f1b057
Manually consume gas for expensive txes and add params (#1972)
mj850 Dec 6, 2024
cb36ad7
refactor ct cli to use SDK coin format (#1977)
dssei Dec 6, 2024
03f7e49
Use updated CachedRangeVerifier (#1975)
mj850 Dec 6, 2024
2147df3
params revert
mj850 Dec 7, 2024
a04736a
fix deps
mj850 Dec 7, 2024
7f04330
Merge branch 'main' of github.com:sei-protocol/sei-chain into feature…
dssei Dec 9, 2024
49203d9
rollback evm params
dssei Dec 9, 2024
e5953b7
change pendingBalanceLo in decryptable account object
dssei Dec 6, 2024
06ab705
Bump coinbase crypto lib version (#1985)
dssei Dec 10, 2024
b18cbc9
Increase test coverage (#1986)
dssei Dec 11, 2024
2a61565
Merge branch 'main' into feature/ct_module
dssei Dec 11, 2024
2a81859
increase test time-out
dssei Dec 11, 2024
dccadcc
tests for VerifyAuditorProof
dssei Dec 11, 2024
3ea3b67
manual merge with main
dssei Dec 11, 2024
5ff5ab3
MsgInitializeAccount tests
dssei Dec 12, 2024
5e292e3
MsgTransfer#FromProto invalid inputs tests
dssei Dec 12, 2024
dd546bc
Merge branch 'main' of github.com:sei-protocol/sei-chain into feature…
dssei Dec 12, 2024
1f75a8e
- tests for transfer msg decrypt
dssei Dec 12, 2024
616733e
- tests for InitializeAccount invalid fields
dssei Dec 12, 2024
5073bd0
- tests for MsgInitializeAccount_Decrypt
dssei Dec 12, 2024
c1d2146
- tests for MsgApplyPendingBalance
dssei Dec 12, 2024
88dd53d
- tests for MsgWithdraw
dssei Dec 13, 2024
2b6bea0
- route and type message tests
dssei Dec 13, 2024
0999e20
Use Signed Message for Key Devrivation (#1981)
mj850 Dec 13, 2024
e19c16c
Consume gas before verifying range proof (#1987)
mj850 Dec 13, 2024
ff2acb6
Merge branch 'main' into feature/ct_module
dssei Dec 13, 2024
e188015
- fix tests
dssei Dec 13, 2024
e4233ec
- few more tests
dssei Dec 13, 2024
7fa9de1
Ct transfer precompile (#1994)
dssei Dec 19, 2024
108b09f
Transfer with auditors precompile (#1997)
dssei Dec 20, 2024
ec80923
Ct init account precompile (#2017)
dssei Jan 6, 2025
417b4a4
Support Sei addresses in transfer precompiles (#2021)
dssei Jan 6, 2025
7a462fe
Ct deposit precompile (#2023)
dssei Jan 8, 2025
c398a7a
Use caller instead of from address (#2030)
dssei Jan 13, 2025
636ae87
Add ApplyPendingBalance, Withdraw and CloseAccount to ConfidentialTra…
mj850 Jan 15, 2025
f39d303
Ct Account query precompile (#2038)
dssei Jan 15, 2025
6bb5ffa
Fix account not found error code (#2041)
dssei Jan 17, 2025
b1b9047
Ct EVM transactions decryption (#2046)
dssei Jan 27, 2025
6e44b42
update artifacts ver
mj850 Feb 19, 2025
818ef99
Add ACL Mappings for InitializeAccount (#2076)
mj850 Feb 28, 2025
13f4775
L-09 Proto Serialization Failures (#2079)
mj850 Feb 28, 2025
aaca330
L02/05 Update proto messages (#2077)
mj850 Feb 28, 2025
322346d
Add range proofs for transfer amounts (#2070)
mj850 Feb 28, 2025
f54784f
Update RPC Response in CT CLI Query with Error (#2078)
mj850 Mar 5, 2025
9826597
Consume more gas in each CT operation. Limit number of auditors (#2080)
dssei Mar 6, 2025
eab8e9e
adding allowlist to CT (#2082)
dssei Mar 6, 2025
94c9f52
Validation fixes (#2084)
dssei Mar 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add confidential transfer queries to seid (#1931)
* update codec

* cleanup and queryies

* enable in module

* add other query

* fix bug

* fix lint

* add decryption query

* cleanup

* validate address

* comments

* rectify test failures

* speed up withdraw test

* make other tests slightly faster
mj850 committed Dec 6, 2024
commit 6c66a700006e56003dc8c97026b6cac46012636c
3 changes: 2 additions & 1 deletion app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
@@ -3,9 +3,10 @@ package apptesting
import (
"context"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/crypto"
"time"

"github.com/ethereum/go-ethereum/crypto"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
13 changes: 13 additions & 0 deletions proto/confidentialtransfers/query.proto
Original file line number Diff line number Diff line change
@@ -47,4 +47,17 @@ message GetAllCtAccountsResponse {

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message DecryptedCtAccount {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

bytes public_key = 1; // serialized public key
uint32 pending_balance_lo = 2; // lo bits of the pending balance
uint64 pending_balance_hi = 3; // hi bits of the pending balance
uint64 combined_pending_balance = 4; // combined pending balance
uint32 pending_balance_credit_counter = 5;
string available_balance = 6; // decrypted available balance
uint64 decryptable_available_balance = 7; // decrypted aes encrypted available balance
}
180 changes: 180 additions & 0 deletions x/confidentialtransfers/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package cli

import (
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"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/spf13/cobra"
)

const decryptAvailableBalanceFlag = "decrypt-available-balance"

// GetQueryCmd returns the cli query commands for the minting module.
func GetQueryCmd() *cobra.Command {
confidentialTransfersQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the confidential transfer module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

confidentialTransfersQueryCmd.AddCommand(
GetCmdQueryAccount(),
GetCmdQueryAllAccount(),
)

return confidentialTransfersQueryCmd
}

// GetCmdQueryAccount implements a command to return an account asssociated with the address and denom
func GetCmdQueryAccount() *cobra.Command {
cmd := &cobra.Command{
Use: "account [denom] [address] [flags]",
Short: "Query the account state",
Long: "Queries the account state associated with the address and denom." +
"Pass the --from flag to decrypt the account" +
"Pass the --decrypt-available-balance flag to attempt to decrypt the available balance.",
Args: cobra.ExactArgs(2),
RunE: queryAccount,
}

flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().String(flags.FlagFrom, "", "Name or address of private key to decrypt the account")
cmd.Flags().Bool(decryptAvailableBalanceFlag, false, "Set this to attempt to decrypt the available balance")
return cmd
}

func queryAccount(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

denom := args[0]

// Validate denom
err = sdk.ValidateDenom(denom)
if err != nil {
return fmt.Errorf("invalid denom: %v", err)
}

address := args[1]
// Validate address
_, err = sdk.AccAddressFromBech32(address)
if err != nil {
return fmt.Errorf("invalid address: %v", err)
}

from, err := cmd.Flags().GetString(flags.FlagFrom)
if err != nil {
return err
}
fromAddr, _, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, from)
if err != nil {
return err
}

res, err := queryClient.GetCtAccount(cmd.Context(), &types.GetCtAccountRequest{
Address: address,
Denom: denom,
})
if err != nil {
return err
}

// If the --from flag passed matches the queried address, attempt to decrypt the contents
if fromAddr.String() == address {
account, err := res.Account.FromProto()
if err != nil {
return err
}
privateKey, err := getPrivateKey(cmd)
if err != nil {
return err
}

aesKey, err := encryption.GetAESKey(*privateKey, denom)
if err != nil {
return err
}

decryptor := elgamal.NewTwistedElgamal()
keyPair, err := decryptor.KeyGen(*privateKey, denom)
if err != nil {
return err
}

decryptAvailableBalance, err := cmd.Flags().GetBool(decryptAvailableBalanceFlag)
if err != nil {
return err
}

decryptedAccount, err := account.Decrypt(decryptor, keyPair, aesKey, decryptAvailableBalance)
if err != nil {
return err
}

return clientCtx.PrintProto(decryptedAccount)

} else {
return clientCtx.PrintProto(res.Account)
}
}

// GetCmdQueryAccount implements a command to return an account asssociated with the address and denom
func GetCmdQueryAllAccount() *cobra.Command {
cmd := &cobra.Command{
Use: "accounts [address]",
Short: "Query all the confidential token accounts associated with the address",
Args: cobra.ExactArgs(1),
RunE: queryAllAccounts,
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func queryAllAccounts(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

address := args[0]
// Validate address
_, err = sdk.AccAddressFromBech32(address)
if err != nil {
return fmt.Errorf("invalid address: %v", err)
}

res, err := queryClient.GetAllCtAccounts(cmd.Context(), &types.GetAllCtAccountsRequest{
Address: args[0],
})

if err != nil {
return err
}

for i := range res.Accounts {
err = clientCtx.PrintString("\n")
if err != nil {
return err
}

err = clientCtx.PrintProto(&res.Accounts[i])
if err != nil {
return err
}
}

return nil
}
12 changes: 3 additions & 9 deletions x/confidentialtransfers/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -28,9 +28,7 @@ type Keeper interface {
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
BankKeeper() types.BankKeeper

CreateModuleAccount(ctx sdk.Context)

@@ -177,12 +175,8 @@ func (k BaseKeeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}

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) BankKeeper() types.BankKeeper {
return k.bankKeeper
}

func (k BaseKeeper) getAccountStore(ctx sdk.Context) prefix.Store {
5 changes: 2 additions & 3 deletions x/confidentialtransfers/keeper/msg_server.go
Original file line number Diff line number Diff line change
@@ -125,7 +125,7 @@ func (m msgServer) Deposit(goCtx context.Context, req *types.MsgDeposit) (*types
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 {
if err := m.Keeper.BankKeeper().SendCoinsFromAccountToModule(ctx, address, types.ModuleName, coins); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to deposit %d %s", req.Amount, req.Denom)
}

@@ -228,7 +228,7 @@ func (m msgServer) Withdraw(goCtx context.Context, req *types.MsgWithdraw) (*typ

// 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 {
if err := m.Keeper.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, address, coins); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to withdraw %d %s", req.Amount, req.Denom)
}

@@ -408,7 +408,6 @@ func (m msgServer) Transfer(goCtx context.Context, req *types.MsgTransfer) (*typ
}

// 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")
56 changes: 29 additions & 27 deletions x/confidentialtransfers/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package keeper_test

import (
"math"
"math/big"

"github.com/coinbase/kryptology/pkg/core/curves"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -10,8 +13,6 @@ import (
"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.
@@ -279,9 +280,9 @@ func (suite *KeeperTestSuite) TestMsgServer_DepositBasic() {

// Check that pending balances were increased by the deposit amount
keyPair, _ := teg.KeyGen(*testPk, DefaultTestDenom)
oldPendingBalancePlaintext, _ := initialState.GetPendingBalancePlaintext(teg, keyPair)
oldPendingBalancePlaintext, _, _, _ := initialState.GetPendingBalancePlaintext(teg, keyPair)

newPendingBalancePlaintext, _ := account.GetPendingBalancePlaintext(teg, keyPair)
newPendingBalancePlaintext, _, _, _ := account.GetPendingBalancePlaintext(teg, keyPair)

// Check that newPendingBalancePlaintext = oldPendingBalancePlaintext + DepositAmount
suite.Require().Equal(
@@ -302,7 +303,7 @@ func (suite *KeeperTestSuite) TestMsgServer_DepositBasic() {
depositStruct = types.MsgDeposit{
FromAddress: testAddr.String(),
Denom: DefaultTestDenom,
Amount: 50000000000,
Amount: math.MaxUint32 + 1,
}

_, err = suite.msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), &depositStruct)
@@ -312,7 +313,7 @@ func (suite *KeeperTestSuite) TestMsgServer_DepositBasic() {
updatedAccount, _ := suite.App.ConfidentialTransfersKeeper.GetAccount(suite.Ctx, testAddr.String(), DefaultTestDenom)

oldPendingBalancePlaintext = newPendingBalancePlaintext
newPendingBalancePlaintext, _ = updatedAccount.GetPendingBalancePlaintext(teg, keyPair)
newPendingBalancePlaintext, _, _, _ = updatedAccount.GetPendingBalancePlaintext(teg, keyPair)
suite.Require().Equal(
new(big.Int).Add(oldPendingBalancePlaintext, new(big.Int).SetUint64(depositStruct.Amount)),
newPendingBalancePlaintext,
@@ -453,10 +454,11 @@ func (suite *KeeperTestSuite) TestMsgServer_WithdrawHappyPath() {

// Initialize an account
bankModuleInitialAmount := uint64(1000000000000)
initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, 1000000, 8000, bankModuleInitialAmount)
initialAvailableBalance := uint64(1000)
initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, bankModuleInitialAmount)

// Create a withdraw request
withdrawAmount := uint64(50000)
withdrawAmount := initialAvailableBalance / 2
withdrawStruct, _ := types.NewWithdraw(*testPk, initialState.AvailableBalance, DefaultTestDenom, testAddr.String(), initialState.DecryptableAvailableBalance, withdrawAmount)

// Execute the withdraw
@@ -477,8 +479,8 @@ func (suite *KeeperTestSuite) TestMsgServer_WithdrawHappyPath() {
// 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)
oldBalanceDecrypted, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, initialState.AvailableBalance, elgamal.MaxBits32)
newBalanceDecrypted, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.AvailableBalance, elgamal.MaxBits32)
newTotal := oldBalanceDecrypted - withdrawAmount
suite.Require().Equal(newTotal, newBalanceDecrypted)

@@ -622,7 +624,7 @@ func (suite *KeeperTestSuite) TestMsgServer_ModifiedDecryptableBalance() {
_ = 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)
initialAvailableBalance := uint64(10000)
initialState, _ := suite.SetupAccountState(testPk, DefaultTestDenom, 50, initialAvailableBalance, 8000, 1000000000000)

// Create a withdraw request
@@ -656,7 +658,7 @@ func (suite *KeeperTestSuite) TestMsgServer_ModifiedDecryptableBalance() {
// 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)
actualBalance, err := teg.DecryptLargeNumber(keyPair.PrivateKey, accountState.AvailableBalance, elgamal.MaxBits32)
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.
@@ -672,7 +674,7 @@ func (suite *KeeperTestSuite) TestMsgServer_ModifiedDecryptableBalance() {

// 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)
decryptedAvailableBalance, err := teg.DecryptLargeNumber(keyPair.PrivateKey, accountState.AvailableBalance, elgamal.MaxBits32)
suite.Require().NoError(err, "Should be decryptable")
newBalance := actualBalance - withdrawAmount
suite.Require().Equal(decryptedAvailableBalance, newBalance, "New account value should have been updated")
@@ -787,7 +789,7 @@ func (suite *KeeperTestSuite) TestMsgServer_ApplyPendingBalance() {
testAddr := privkeyToAddress(testPk)

// Initialize an account
initialAvailableBalance := uint64(20000000)
initialAvailableBalance := uint64(2000)
initialPendingBalance := uint64(100000)
suite.SetupAccountState(testPk, DefaultTestDenom, 10, initialAvailableBalance, initialPendingBalance, 1000)

@@ -813,17 +815,17 @@ func (suite *KeeperTestSuite) TestMsgServer_ApplyPendingBalance() {
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)
actualAvailableBalance, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.AvailableBalance, elgamal.MaxBits32)
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)
actualPendingBalanceLo, _ := teg.DecryptLargeNumber(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)
actualPendingBalanceHi, _ := teg.DecryptLargeNumber(keyPair.PrivateKey, account.PendingBalanceHi, elgamal.MaxBits32)
suite.Require().Equal(uint64(0), actualPendingBalanceHi, "Pending balance hi not 0")

// Check that the pending balance credit counter is reset to 0.
@@ -917,8 +919,8 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferHappyPath() {
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)
senderOldBalanceDecrypted, _ := teg.DecryptLargeNumber(senderKeypair.PrivateKey, initialSenderState.AvailableBalance, elgamal.MaxBits32)
senderNewBalanceDecrypted, _ := teg.DecryptLargeNumber(senderKeypair.PrivateKey, senderAccountState.AvailableBalance, elgamal.MaxBits32)
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.
@@ -939,8 +941,8 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferHappyPath() {

// 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)
oldRecipientPendingBalance, _, _, _ := initialRecipientState.GetPendingBalancePlaintext(teg, recipientKeypair)
newRecipientPendingBalance, _, _, _ := recipientAccountState.GetPendingBalancePlaintext(teg, recipientKeypair)

transferAmountBigInt := new(big.Int).SetUint64(transferAmount)
newTotal := new(big.Int).Add(oldRecipientPendingBalance, transferAmountBigInt)
@@ -1055,7 +1057,7 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferInsufficientBalance() {
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")
suite.Require().ErrorContains(err, "failed to verify sender transfer amount lo")

// Try to modify the proofs as well
senderLoValidityProof, _ := zkproofs.NewCiphertextValidityProof(&senderLoRandomness, initialSenderState.PublicKey, senderAmountLo, uint64(transferLoBits))
@@ -1074,7 +1076,7 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferInsufficientBalance() {
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")
suite.Require().ErrorContains(err, "ciphertext commitment equality verification failed")
}

func (suite *KeeperTestSuite) TestMsgServer_TransferWrongRecipient() {
@@ -1118,7 +1120,7 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferWrongRecipient() {
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")
suite.Require().ErrorContains(err, "failed to verify recipient transfer amount lo")
}

func (suite *KeeperTestSuite) TestMsgServer_TransferDifferentAmounts() {
@@ -1175,7 +1177,7 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferDifferentAmounts() {
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")
suite.Require().ErrorContains(err, "failed to verify recipient transfer amount lo")

// Generate the validity proofs of the new amounts
loBitsValidityProof, _ := zkproofs.NewCiphertextValidityProof(&loBitsRandomness, recipientKeyPair.PublicKey, encryptedTransferLoBits, uint64(transferLoBits))
@@ -1188,7 +1190,7 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferDifferentAmounts() {
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")
suite.Require().ErrorContains(err, "ciphertext ciphertext equality verification on transfer amount lo failed")

// So we attempt to generate new equality proofs for the amounts as well.
bigIntLoBits := new(big.Int).SetUint64(uint64(transferLoBits))
@@ -1212,5 +1214,5 @@ func (suite *KeeperTestSuite) TestMsgServer_TransferDifferentAmounts() {
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")
suite.Require().ErrorContains(err, "ciphertext ciphertext equality verification on transfer amount lo failed")
}
9 changes: 1 addition & 8 deletions x/confidentialtransfers/module.go
Original file line number Diff line number Diff line change
@@ -86,11 +86,9 @@ func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config clien
return nil
}

// TODO: Look into whether we require REST endpoints
// RegisterRESTRoutes registers the capability module's REST service handlers.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}

// TODO: Look into whether we require gRPC Gateway support
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck
@@ -101,26 +99,22 @@ func (am AppModuleBasic) GetTxCmd() *cobra.Command {
return cli.NewTxCmd()
}

// TODO: Implement this when we add the CLI methods
// GetQueryCmd returns the x/confidentialtransfers module's root query command.
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
//return cli.GetQueryCmd()
return nil
return cli.GetQueryCmd()
}

// ----------------------------------------------------------------------------
// AppModule
// ----------------------------------------------------------------------------

// TODO: Add any required keepers here
// AppModule implements the AppModule interface for the capability module.
type AppModule struct {
AppModuleBasic

keeper keeper.Keeper
}

// TODO: Revisit if any other keepers are required.
func NewAppModule(
keeper keeper.Keeper,
) AppModule {
@@ -178,7 +172,6 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.Ra
// ExportGenesis returns the x/confidentialtransfers module's exported genesis state as raw
// JSON bytes.
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
// TODO: Implement once we implement keeper/Genesis
genState := am.keeper.ExportGenesis(ctx)
return cdc.MustMarshalJSON(genState)
}
45 changes: 41 additions & 4 deletions x/confidentialtransfers/types/account.go
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@ package types

import (
"math/big"
"strconv"

"github.com/coinbase/kryptology/pkg/core/curves"
"github.com/sei-protocol/sei-cryptography/pkg/encryption"
"github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal"
)

@@ -34,14 +36,14 @@ type Account struct {
DecryptableAvailableBalance string
}

func (a *Account) GetPendingBalancePlaintext(decryptor *elgamal.TwistedElGamal, keypair *elgamal.KeyPair) (*big.Int, error) {
func (a *Account) GetPendingBalancePlaintext(decryptor *elgamal.TwistedElGamal, keypair *elgamal.KeyPair) (*big.Int, uint64, uint64, error) {
actualPendingBalanceLo, err := decryptor.Decrypt(keypair.PrivateKey, a.PendingBalanceLo, elgamal.MaxBits32)
if err != nil {
return big.NewInt(0), err
return big.NewInt(0), 0, 0, err
}
actualPendingBalanceHi, err := decryptor.DecryptLargeNumber(keypair.PrivateKey, a.PendingBalanceHi, elgamal.MaxBits48)
if err != nil {
return big.NewInt(0), err
return big.NewInt(0), 0, 0, err
}

loBig := new(big.Int).SetUint64(actualPendingBalanceLo)
@@ -52,5 +54,40 @@ func (a *Account) GetPendingBalancePlaintext(decryptor *elgamal.TwistedElGamal,

// Combine by adding hiBig with loBig
combined := new(big.Int).Add(hiBig, loBig)
return combined, nil
return combined, actualPendingBalanceLo, actualPendingBalanceHi, nil
}

// Returns the decrypted account state.
// Does not decyrpt the available balance unless specified. Decrypting the is only feasible for small numbers under 40 bits.
// TODO: Add tests for this method
func (a *Account) Decrypt(decryptor *elgamal.TwistedElGamal, keypair *elgamal.KeyPair, aesKey []byte, decryptAvailableBalance bool) (*DecryptedCtAccount, error) {
pendingBalanceCombined, pendingBalanceLo, pendingBalanceHi, err := a.GetPendingBalancePlaintext(decryptor, keypair)
if err != nil {
return nil, err
}

aesAvailableBalance, err := encryption.DecryptAESGCM(a.DecryptableAvailableBalance, aesKey)
if err != nil {
return nil, err
}

availableBalanceString := "Not Decrypted"
if decryptAvailableBalance {
availableBalance, err := decryptor.DecryptLargeNumber(keypair.PrivateKey, a.AvailableBalance, elgamal.MaxBits40)
if err != nil {
return nil, err
}

availableBalanceString = strconv.FormatUint(availableBalance, 10)
}

return &DecryptedCtAccount{
PublicKey: a.PublicKey.ToAffineCompressed(),
PendingBalanceLo: uint32(pendingBalanceLo),
PendingBalanceHi: pendingBalanceHi,
CombinedPendingBalance: pendingBalanceCombined.Uint64(),
PendingBalanceCreditCounter: uint32(a.PendingBalanceCreditCounter),
AvailableBalance: availableBalanceString,
DecryptableAvailableBalance: aesAvailableBalance,
}, nil
}
8 changes: 8 additions & 0 deletions x/confidentialtransfers/types/codec.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgInitializeAccount{}, "confidentialtransfers/MsgInitializeAccount", nil)
cdc.RegisterConcrete(&MsgDeposit{}, "confidentialtransfers/MsgDeposit", nil)
cdc.RegisterConcrete(&MsgWithdraw{}, "confidentialtransfers/MsgWithdraw", nil)
cdc.RegisterConcrete(&MsgCloseAccount{}, "confidentialtransfers/MsgCloseAccount", nil)
cdc.RegisterConcrete(&MsgApplyPendingBalance{}, "confidentialtransfers/MsgApplyPendingBalance", nil)
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
@@ -27,6 +29,12 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgWithdraw{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgCloseAccount{},
)
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgApplyPendingBalance{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}
4 changes: 0 additions & 4 deletions x/confidentialtransfers/types/keys.go
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/address"
)

// TODO: Remove keys that are eventually not required
const (
// ModuleName defines the module name
ModuleName = "confidentialtransfers"
@@ -20,9 +19,6 @@ const (

// QuerierRoute defines the module's query routing key
QuerierRoute = ModuleName

// MemStoreKey defines the in-memory store key
MemStoreKey = "mem_confidential"
)

var (
4 changes: 2 additions & 2 deletions x/confidentialtransfers/types/msgs_test.go
Original file line number Diff line number Diff line change
@@ -314,7 +314,7 @@ func TestMsgTransfer_ValidateBasic(t *testing.T) {
Denom: validDenom,
},
wantErr: true,
errMsg: "FromAmountLo is required",
errMsg: "from amount lo is required",
},
{
name: "missing from amount hi",
@@ -325,7 +325,7 @@ func TestMsgTransfer_ValidateBasic(t *testing.T) {
FromAmountLo: &Ciphertext{},
},
wantErr: true,
errMsg: sdkerrors.ErrInvalidRequest.Error(),
errMsg: "from amount hi is required",
},
{
name: "missing to amount lo",
426 changes: 393 additions & 33 deletions x/confidentialtransfers/types/query.pb.go

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions x/confidentialtransfers/types/transfer.go
Original file line number Diff line number Diff line change
@@ -271,27 +271,27 @@ func VerifyTransferProofs(params *Transfer, senderPubkey *curves.Point, recipien
// 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")
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")
return errors.New("failed to verify sender transfer amount lo")
}

ok = zkproofs.VerifyCiphertextValidity(params.Proofs.SenderTransferAmountHiValidityProof, *senderPubkey, params.SenderTransferAmountHi)
if !ok {
return errors.New("Failed to verify senderTransferAmountHi")
return errors.New("failed to verify sender transfer amount hi")
}

ok = zkproofs.VerifyCiphertextValidity(params.Proofs.RecipientTransferAmountLoValidityProof, *recipientPubkey, params.RecipientTransferAmountLo)
if !ok {
return errors.New("Failed to verify recipientTransferAmountLo")
return errors.New("failed to verify recipient transfer amount lo")
}

ok = zkproofs.VerifyCiphertextValidity(params.Proofs.RecipientTransferAmountHiValidityProof, *recipientPubkey, params.RecipientTransferAmountHi)
if !ok {
return errors.New("Failed to verify recipientTransferAmountHi")
return errors.New("failed to verify recipient transfer amount hi")
}

// Verify that the account's remaining balance is greater than zero after this transfer.
@@ -301,24 +301,24 @@ func VerifyTransferProofs(params *Transfer, senderPubkey *curves.Point, recipien
return err
}
if !ok {
return errors.New("Range proof verification failed")
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, &params.RemainingBalanceCommitment.C)
if !ok {
return errors.New("Ciphertext Commitment equality verification failed")
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")
return errors.New("ciphertext ciphertext equality verification on transfer amount lo 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 errors.New("ciphertext ciphertext equality verification on transfer amount hi failed")
}

return nil
@@ -334,23 +334,23 @@ func VerifyAuditorProof(
// 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")
return errors.New("failed to verify auditor transfer amoun lo")
}

ok = zkproofs.VerifyCiphertextValidity(auditorParams.TransferAmountHiValidityProof, *auditorPubkey, auditorParams.EncryptedTransferAmountHi)
if !ok {
return errors.New("Failed to verify auditor TransferAmountHi")
return errors.New("failed to verify auditor transfer amount hi")
}

// 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")
return errors.New("ciphertext ciphertext equality verification on auditor transfer amount lo 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 errors.New("ciphertext ciphertext equality verification on auditor transfer amount hi failed")
}

return nil