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 query decrypted tx method to confidential transfers client #1950

Merged
merged 8 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
60 changes: 59 additions & 1 deletion proto/confidentialtransfers/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "cosmos/base/query/v1beta1/pagination.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "confidentialtransfers/confidential.proto";
import "confidentialtransfers/zk.proto";

option go_package = "github.com/sei-protocol/sei-chain/x/confidentialtransfers/types";

Expand Down Expand Up @@ -60,4 +61,61 @@ message DecryptedCtAccount {
uint32 pending_balance_credit_counter = 5;
string available_balance = 6; // decrypted available balance
uint64 decryptable_available_balance = 7; // decrypted aes encrypted available balance
}
}

// Decrypted version of ApplyPendingBalance
message ApplyPendingBalanceDecrypted {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string address = 1;
string denom = 2;
uint64 new_decryptable_available_balance = 3;
uint32 current_pending_balance_counter = 4;
string current_available_balance = 5;
}

// Decrypted version of InitializeAccount
message InitializeAccountDecrypted {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string from_address = 1;
string denom = 2;
bytes pubkey = 3;
uint32 pending_balance_lo = 4;
uint64 pending_balance_hi = 5;
string available_balance = 6;
uint64 decryptable_balance = 7;
InitializeAccountMsgProofs proofs = 8;
}

// Decrypted version of Withdraw
message WithdrawDecrypted {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string from_address = 1;
string denom = 2;
uint64 amount = 3;
uint64 decryptable_balance = 4;
string remaining_balance_commitment = 5;
WithdrawMsgProofs proofs = 6;
}

// Decrypted version of Transfer
message TransferDecrypted {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string from_address = 1;
string to_address = 2;
string denom = 3;
uint32 transfer_amount_lo = 4;
uint32 transfer_amount_hi = 5;
uint64 total_transfer_amount = 6;
string remaining_balance_commitment = 7;
string decryptable_balance = 8;
TransferMsgProofs proofs = 9;
repeated string auditors = 10;
}
167 changes: 159 additions & 8 deletions x/confidentialtransfers/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package cli

import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/gogo/protobuf/proto"
"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"
const decryptorFlag = "decryptor"

// GetQueryCmd returns the cli query commands for the minting module.
func GetQueryCmd() *cobra.Command {
confidentialTransfersQueryCmd := &cobra.Command{
Use: types.ModuleName,
Use: types.ShortModuleName,
Short: "Querying commands for the confidential transfer module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
Expand All @@ -27,6 +35,7 @@ func GetQueryCmd() *cobra.Command {
confidentialTransfersQueryCmd.AddCommand(
GetCmdQueryAccount(),
GetCmdQueryAllAccount(),
GetCmdQueryTx(),
)

return confidentialTransfersQueryCmd
Expand All @@ -38,14 +47,14 @@ func GetCmdQueryAccount() *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 --decryptor 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().String(decryptorFlag, "", "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
}
Expand All @@ -72,11 +81,11 @@ func queryAccount(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid address: %v", err)
}

from, err := cmd.Flags().GetString(flags.FlagFrom)
decryptorAccount, err := cmd.Flags().GetString(decryptorFlag)
if err != nil {
return err
}
fromAddr, _, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, from)
decryptorAddr, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, decryptorAccount)
if err != nil {
return err
}
Expand All @@ -89,13 +98,13 @@ func queryAccount(cmd *cobra.Command, args []string) error {
return err
}

// If the --from flag passed matches the queried address, attempt to decrypt the contents
if fromAddr.String() == address {
// If the decryptor flag passed matches the queried address, attempt to decrypt the contents
if decryptorAddr.String() == address {
account, err := res.Account.FromProto()
if err != nil {
return err
}
privateKey, err := getPrivateKey(cmd)
privateKey, err := getPrivateKey(cmd, name)
if err != nil {
return err
}
Expand Down Expand Up @@ -178,3 +187,145 @@ func queryAllAccounts(cmd *cobra.Command, args []string) error {

return nil
}

// GetCmdQueryTx implements a command to query a tx by it's transaction hash and return it's decrypted state by decrypting with the senders private key.
func GetCmdQueryTx() *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash] --decryptor [decryptor] [flags]",
Short: "Query the confidential transaction and decrypts it",
mj850 marked this conversation as resolved.
Show resolved Hide resolved
Long: "Query the confidential transaction by it's tx hash and decrypts it using the private key of the account in --decryptor. For decryption to work, the decryptor should be of a sender, receiver or auditor." +
"Pass the --decrypt-available-balance flag to attempt to decrypt the available balance. (This is an expensive operation and may not succeed even if the private key provided is valid)",
Args: cobra.ExactArgs(1),
RunE: queryDecryptedTx,
}

flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().String(decryptorFlag, "", "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 queryDecryptedTx(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

// Get the transaction hash
txHashHex := args[0]

decryptorAccount, err := cmd.Flags().GetString(decryptorFlag)
if err != nil {
return err
}

if decryptorAccount == "" {
return fmt.Errorf("--decryptor flag must be set since we need the private key to decrypt the transaction")
}

fromAddr, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, decryptorAccount)
if err != nil {
return err
}
clientCtx = clientCtx.WithFrom(decryptorAccount).WithFromAddress(fromAddr).WithFromName(name)

decryptAvailableBalance, err := cmd.Flags().GetBool(decryptAvailableBalanceFlag)
if err != nil {
return err
}
if decryptAvailableBalance {
err = clientCtx.PrintString("--decrypt-available-balance set to true. This operation could take a long time and may not succeed even if the private key provided is valid\n")
if err != nil {
return err
}
}

// Decode the transaction hash from hex to bytes
txHash, err := hex.DecodeString(txHashHex)
if err != nil {
return fmt.Errorf("failed to decode transaction hash: %w", err)
}

// Connect to the gRPC client
node, err := clientCtx.GetNode()
if err != nil {
return fmt.Errorf("failed to connect to node: %w", err)
}

// Query the transaction using Tendermint's Tx endpoint
res, err := node.Tx(context.Background(), txHash, false)
if err != nil {
return fmt.Errorf("failed to fetch transaction: %w", err)
}

// Decode the transaction
var rawTx tx.Tx
if err := clientCtx.Codec.Unmarshal(res.Tx, &rawTx); err != nil {
return fmt.Errorf("failed to unmarshal transaction: %w", err)
}

decryptor := elgamal.NewTwistedElgamal()
privateKey, err := getPrivateKey(cmd, name)

if err != nil {
return err
}
msgPrinted := false
for _, msg := range rawTx.Body.Messages {
result, foundMsg, err := handleDecryptableMessage(clientCtx.Codec, msg, decryptor, privateKey, decryptAvailableBalance, fromAddr.String())
if !foundMsg {
continue
} else {
if err != nil {
return err
}
err = clientCtx.PrintProto(result)
msgPrinted = true
if err != nil {
return err
}
}
}

if !msgPrinted {
return fmt.Errorf("no decryptable message found in the transaction")
}

return nil
}

// Helper function to unmarshal a message and run its Decrypt() method
func handleDecryptableMessage(
cdc codec.Codec,
msgAny *cdctypes.Any,
decryptor *elgamal.TwistedElGamal,
privKey *ecdsa.PrivateKey,
decryptAvailableBalance bool,
address string) (msg proto.Message, foundDecryptableMsg bool, error error) {
// Try to unmarshal the message as one of the known types
var sdkmsg sdk.Msg
err := cdc.UnpackAny(msgAny, &sdkmsg)
if err != nil {
return nil, false, nil
}

var result proto.Message
switch message := sdkmsg.(type) {
case *types.MsgInitializeAccount:
result, err = message.Decrypt(decryptor, *privKey, decryptAvailableBalance)
case *types.MsgWithdraw:
result, err = message.Decrypt(decryptor, *privKey, decryptAvailableBalance)
case *types.MsgApplyPendingBalance:
result, err = message.Decrypt(decryptor, *privKey, decryptAvailableBalance)
case *types.MsgTransfer:
result, err = message.Decrypt(decryptor, *privKey, decryptAvailableBalance, address)
case *types.MsgDeposit:
result = message
case *types.MsgCloseAccount:
result = message
default:
return nil, false, nil
}

return result, true, err
}
24 changes: 15 additions & 9 deletions x/confidentialtransfers/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func makeInitializeAccountCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid denom: %v", err)
}

privKey, err := getPrivateKey(cmd)
privKey, err := getPrivateKey(cmd, clientCtx.GetFromName())
if err != nil {
return err
}
Expand All @@ -90,14 +90,14 @@ func makeInitializeAccountCmd(cmd *cobra.Command, args []string) error {
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
}

func getPrivateKey(cmd *cobra.Command) (*ecdsa.PrivateKey, error) {
func getPrivateKey(cmd *cobra.Command, fromName string) (*ecdsa.PrivateKey, error) {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return nil, err
}
txf := tx.NewFactoryCLI(clientCtx, cmd.Flags())
kb := txf.Keybase()
info, err := kb.Key(clientCtx.GetFromName())
info, err := kb.Key(fromName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -152,7 +152,7 @@ func makeCloseAccountCmd(cmd *cobra.Command, args []string) error {

queryClient := types.NewQueryClient(queryClientCtx)

privKey, err := getPrivateKey(cmd)
privKey, err := getPrivateKey(cmd, clientCtx.GetFromName())
if err != nil {
return err
}
Expand Down Expand Up @@ -189,7 +189,7 @@ func NewTransferTxCmd() *cobra.Command {
Use: "transfer [denom] [to_address] [amount] [flags]",
Short: "Make a confidential transfer to another address",
Long: `Transfer command create a confidential transfer of the specified amount of the specified denomination to the specified address.
passed in .`,
passed in. To add auditors to the transaction, pass the --auditors flag with a comma separated list of auditor addresses.`,
Args: cobra.ExactArgs(3),
RunE: makeTransferCmd,
}
Expand All @@ -213,7 +213,7 @@ func makeTransferCmd(cmd *cobra.Command, args []string) error {

queryClient := types.NewQueryClient(queryClientCtx)

privKey, err := getPrivateKey(cmd)
privKey, err := getPrivateKey(cmd, clientCtx.GetFromName())
if err != nil {
return err
}
Expand Down Expand Up @@ -316,7 +316,7 @@ func makeWithdrawCmd(cmd *cobra.Command, args []string) error {

queryClient := types.NewQueryClient(queryClientCtx)

privKey, err := getPrivateKey(cmd)
privKey, err := getPrivateKey(cmd, clientCtx.GetFromName())
if err != nil {
return err
}
Expand Down Expand Up @@ -434,7 +434,7 @@ func makeApplyPendingBalanceCmd(cmd *cobra.Command, args []string) error {
}

queryClient := types.NewQueryClient(queryClientCtx)
privKey, err := getPrivateKey(cmd)
privKey, err := getPrivateKey(cmd, clientCtx.GetFromName())
if err != nil {
return err
}
Expand All @@ -451,7 +451,7 @@ func makeApplyPendingBalanceCmd(cmd *cobra.Command, args []string) error {
return err
}

msg, err := types.NewMsgApplyPendingBalance(
applyPendingBalance, err := types.NewApplyPendingBalance(
*privKey,
address,
denom,
Expand All @@ -465,6 +465,12 @@ func makeApplyPendingBalanceCmd(cmd *cobra.Command, args []string) error {
return err
}

msg := types.NewMsgApplyPendingBalanceProto(applyPendingBalance)

if err != nil {
return err
}

if err = msg.ValidateBasic(); err != nil {
return err
}
Expand Down
Loading
Loading