From e6dac5d92fce8005faeba7c1152a2f43f1f36433 Mon Sep 17 00:00:00 2001 From: shreyasbhat0 Date: Thu, 18 Jul 2024 18:56:10 +0530 Subject: [PATCH] feat: implement thor claims with verifiable data --- proto/arkeo/claim/tx.proto | 14 ++++- x/claim/client/offchain/thor_tx_data.go | 49 +++------------ x/claim/keeper/msg_server_claim_arkeo.go | 7 ++- x/claim/keeper/msg_server_claim_eth.go | 2 +- x/claim/keeper/msg_server_claim_thorchain.go | 66 +++++++++++--------- x/claim/types/thor_tx.go | 37 +++++++++++ 6 files changed, 97 insertions(+), 78 deletions(-) create mode 100644 x/claim/types/thor_tx.go diff --git a/proto/arkeo/claim/tx.proto b/proto/arkeo/claim/tx.proto index e5db7efa..88950e3b 100644 --- a/proto/arkeo/claim/tx.proto +++ b/proto/arkeo/claim/tx.proto @@ -13,20 +13,30 @@ service Msg { rpc AddClaim(MsgAddClaim) returns (MsgAddClaimResponse); // this line is used by starport scaffolding # proto/tx/rpc } + + +message MsgThorTxData { + string thor_data = 1; + string proof_signature = 2; + string proof_pubkey = 3; +} + + message MsgClaimEth { bytes creator = 1 [ (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; string eth_address = 2; // the adress the claim is for string signature = 3; // EIP712 signature that has to be signed by ethAddress - string thor_tx = 4; // the tx hash of the thorchain tx that delegates the arkeo claim + MsgThorTxData thor_tx = 4; // the tx hash of the thorchain tx that delegates the arkeo claim } message MsgClaimEthResponse {} + message MsgClaimArkeo { bytes creator = 1 [ (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; - string thor_tx = 2; // the tx hash of the thorchain tx that delegates the arkeo claim + MsgThorTxData thor_tx_data = 2; } message MsgClaimArkeoResponse {} diff --git a/x/claim/client/offchain/thor_tx_data.go b/x/claim/client/offchain/thor_tx_data.go index e1731361..b666a721 100644 --- a/x/claim/client/offchain/thor_tx_data.go +++ b/x/claim/client/offchain/thor_tx_data.go @@ -9,44 +9,9 @@ import ( "net/http" "cosmossdk.io/errors" + "github.com/arkeonetwork/arkeo/x/claim/types" ) -type Coin struct { - Asset string `json:"asset"` - Amount string `json:"amount"` -} - -type Tx struct { - ID string `json:"id"` - Chain string `json:"chain"` - FromAddress string `json:"from_address"` - ToAddress string `json:"to_address"` - Coins []Coin `json:"coins"` - Gas interface{} `json:"gas"` - Memo string `json:"memo"` -} - -type ObservedTx struct { - Tx Tx `json:"tx"` -} - -type KeysignMetric struct { - TxID string `json:"tx_id"` - NodeTSSTimes interface{} `json:"node_tss_times"` -} - -type ThorChainTxData struct { - ObservedTx ObservedTx `json:"observed_tx"` - ConsensusHeight int `json:"consensus_height"` - FinalisedHeight int `json:"finalised_height"` - KeysignMetric KeysignMetric `json:"keysign_metric"` -} - -type ThorTxData struct { - Hash string `json:"hash"` - TxData string `json:"tx_data"` -} - func fetchThorChainTxData(hash string) (string, error) { url := fmt.Sprintf("https://thornode.ninerealms.com/thorchain/tx/%s", hash) @@ -70,7 +35,7 @@ func fetchThorChainTxData(hash string) (string, error) { return "", fmt.Errorf("error reading response body: %w", err) } - var result ThorChainTxData + var result types.ThorChainTxData if err = json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("error unmarshalling response: %w", err) } @@ -81,12 +46,12 @@ func fetchThorChainTxData(hash string) (string, error) { } txDataHash := sha512.Sum512(resultBytes) - txHex := base64.RawURLEncoding.EncodeToString(txDataHash[:]) - txDataHex := base64.RawURLEncoding.EncodeToString(resultBytes) + txHashBase64 := base64.StdEncoding.EncodeToString(txDataHash[:]) + txDataBase64 := base64.StdEncoding.EncodeToString(resultBytes) - txData := ThorTxData{ - Hash: txHex, - TxData: txDataHex, + txData := types.ThorTxData{ + Hash: txHashBase64, + TxData: txDataBase64, } txDataBytes, err := json.Marshal(txData) diff --git a/x/claim/keeper/msg_server_claim_arkeo.go b/x/claim/keeper/msg_server_claim_arkeo.go index 0c085911..3db8ba66 100644 --- a/x/claim/keeper/msg_server_claim_arkeo.go +++ b/x/claim/keeper/msg_server_claim_arkeo.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "github.com/arkeonetwork/arkeo/x/claim/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" @@ -14,10 +15,10 @@ func (k msgServer) ClaimArkeo(goCtx context.Context, msg *types.MsgClaimArkeo) ( return nil, errors.Wrapf(err, "failed to get claim record for %s", msg.Creator) } - if msg.ThorTx != "" { - arkeoClaimRecord, err = k.updateThorClaimRecord(ctx, msg.Creator.String(), msg.ThorTx, arkeoClaimRecord) + if msg.ThorTxData != nil { + arkeoClaimRecord, err = k.updateThorClaimRecord(ctx, msg.Creator.String(), msg.ThorTxData, arkeoClaimRecord) if err != nil { - return nil, errors.Wrapf(err, "failed to get claim record for %s", msg.ThorTx) + return nil, errors.Wrapf(err, "failed to get claim record for %s", msg.ThorTxData) } } diff --git a/x/claim/keeper/msg_server_claim_eth.go b/x/claim/keeper/msg_server_claim_eth.go index a1d138b5..d6570b5e 100644 --- a/x/claim/keeper/msg_server_claim_eth.go +++ b/x/claim/keeper/msg_server_claim_eth.go @@ -81,7 +81,7 @@ func (k msgServer) ClaimEth(goCtx context.Context, msg *types.MsgClaimEth) (*typ return nil, errors.Wrapf(err, "failed to set claim record for %s", msg.Creator) } - if msg.ThorTx != "" { + if msg.ThorTx != nil { arkeoClaim, err = k.updateThorClaimRecord(ctx, msg.Creator.String(), msg.ThorTx, arkeoClaim) if err != nil { return nil, errors.Wrapf(err, "failed to get claim record for %s", msg.ThorTx) diff --git a/x/claim/keeper/msg_server_claim_thorchain.go b/x/claim/keeper/msg_server_claim_thorchain.go index 6fed0ea4..ba5bc746 100644 --- a/x/claim/keeper/msg_server_claim_thorchain.go +++ b/x/claim/keeper/msg_server_claim_thorchain.go @@ -1,55 +1,47 @@ package keeper import ( + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" + "strings" + "github.com/arkeonetwork/arkeo/x/claim/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" - "io" - "net/http" - "strings" ) -type ThorTxData struct { - ObservedTx struct { - Tx struct { - FromAddress string `json:"from_address"` - Memo string `json:"memo"` - } `json:"tx"` - } `json:"observed_tx"` -} - // Verify and update the claim record based on the memo of the thorchain tx -func (k msgServer) updateThorClaimRecord(ctx sdk.Context, creator string, thorTx string, arkeoClaimRecord types.ClaimRecord) (types.ClaimRecord, error) { - url := fmt.Sprintf("https://thornode.ninerealms.com/thorchain/tx/%s", thorTx) +func (k msgServer) updateThorClaimRecord(ctx sdk.Context, creator string, thorTxMsg *types.MsgThorTxData, arkeoClaimRecord types.ClaimRecord) (types.ClaimRecord, error) { - req, err := http.NewRequest(http.MethodGet, url, nil) + thorDataDecoded, err := base64.StdEncoding.DecodeString(thorTxMsg.ThorData) if err != nil { - return types.ClaimRecord{}, errors.Wrapf(err, "failed to build request %s", req.RequestURI) + return types.ClaimRecord{}, fmt.Errorf("error base64 decoding faild: %w", err) } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return types.ClaimRecord{}, errors.Wrapf(err, "failed to get thorchain tx for %s", thorTx) + var thorData *types.ThorTxData + if err := json.Unmarshal(thorDataDecoded, &thorData); err != nil { + return types.ClaimRecord{}, fmt.Errorf("error unmarshalling transaction data: %w", err) } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return types.ClaimRecord{}, fmt.Errorf("received non-OK HTTP status: %d", resp.StatusCode) + var thorTxData *types.ThorChainTxData + if err := json.Unmarshal([]byte(thorData.TxData), &thorTxData); err != nil { + return types.ClaimRecord{}, fmt.Errorf("error unmarshalling transaction data: %w", err) } - body, err := io.ReadAll(resp.Body) + marshalledtxBytes, err := json.Marshal(thorData) if err != nil { - return types.ClaimRecord{}, fmt.Errorf("error reading response body: %w", err) + return types.ClaimRecord{}, fmt.Errorf("error marshalling tx data: %w", err) } - var txData ThorTxData - if err := json.Unmarshal(body, &txData); err != nil { - return types.ClaimRecord{}, fmt.Errorf("error unmarshalling transaction data: %w", err) + verifyTxData := base64.StdEncoding.EncodeToString(marshalledtxBytes) + + if verifyTxData != thorData.Hash { + return types.ClaimRecord{}, fmt.Errorf("transaction data cehcksum failed") } - thorAddress := txData.ObservedTx.Tx.FromAddress - memo := txData.ObservedTx.Tx.Memo + + thorAddress := thorTxData.ObservedTx.Tx.FromAddress + memo := thorTxData.ObservedTx.Tx.Memo thorAddressBytes, err := sdk.GetFromBech32(thorAddress, "thor") if err != nil { @@ -63,6 +55,20 @@ func (k msgServer) updateThorClaimRecord(ctx sdk.Context, creator string, thorTx return types.ClaimRecord{}, fmt.Errorf("failed to encode address bytes with new prefix: %w", err) } + decodedPubKey, err := hex.DecodeString(thorTxMsg.ProofPubkey) + if err != nil { + return types.ClaimRecord{}, fmt.Errorf("error in decoding pubkey: %w", err) + } + + proofAddress, err := sdk.Bech32ifyAddressBytes(prefix, decodedPubKey) + if err != nil { + return types.ClaimRecord{}, fmt.Errorf("invalid thorchain address: %w", err) + } + + if proofAddress != thorDerivedArkeoAddress { + return types.ClaimRecord{}, fmt.Errorf("address validation failed: %w", err) + } + thorClaimRecord, err := k.GetClaimRecord(ctx, thorDerivedArkeoAddress, types.ARKEO) if err != nil { return types.ClaimRecord{}, errors.Wrapf(err, "failed to get claim record for %s", thorDerivedArkeoAddress) diff --git a/x/claim/types/thor_tx.go b/x/claim/types/thor_tx.go new file mode 100644 index 00000000..db04b031 --- /dev/null +++ b/x/claim/types/thor_tx.go @@ -0,0 +1,37 @@ +package types + +type Coin struct { + Asset string `json:"asset"` + Amount string `json:"amount"` +} + +type Tx struct { + ID string `json:"id"` + Chain string `json:"chain"` + FromAddress string `json:"from_address"` + ToAddress string `json:"to_address"` + Coins []Coin `json:"coins"` + Gas interface{} `json:"gas"` + Memo string `json:"memo"` +} + +type ObservedTx struct { + Tx Tx `json:"tx"` +} + +type KeysignMetric struct { + TxID string `json:"tx_id"` + NodeTSSTimes interface{} `json:"node_tss_times"` +} + +type ThorChainTxData struct { + ObservedTx ObservedTx `json:"observed_tx"` + ConsensusHeight int `json:"consensus_height"` + FinalisedHeight int `json:"finalised_height"` + KeysignMetric KeysignMetric `json:"keysign_metric"` +} + +type ThorTxData struct { + Hash string `json:"hash"` + TxData string `json:"tx_data"` +}