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

fix: signatures replayed across different networks #331

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Contains all the PRs that improved the code without changing the behaviors.
- Fixed bug allowing double claims with Thorchain claims.
- Fixed non deterministic map iteration to sorted iteration
- Fixed fee stuck in arkeo module by moving fees to arkeo-reserve
- Fixed signature replay on different network

---

Expand Down
14 changes: 13 additions & 1 deletion arkeocli/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,19 @@ func runClaimCmd(cmd *cobra.Command, args []string) (err error) {
return err
}

signBytes := types.GetBytesToSign(contract.Id, nonce)
chainId, err := cmd.Flags().GetString("chain-id")
if err != nil {
return err
}

if len(chainId) == 0 {
chainId, err = promptForArg(cmd, "specify the chain id:")
if err != nil {
return err
}
}

signBytes := types.GetBytesToSign(contract.Id, nonce, chainId)
signature, _, err := clientCtx.Keyring.Sign(key.Name, signBytes, signing.SignMode_SIGN_MODE_DIRECT)
if err != nil {
return errors.Wrapf(err, "error signing")
Expand Down
32 changes: 21 additions & 11 deletions sentinel/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,35 @@ type ContractAuth struct {
ContractId uint64
Timestamp int64
Signature []byte
ChainId string
}

type ArkAuth struct {
ContractId uint64
Spender common.PubKey
Nonce int64
Signature []byte
ChainId string
}

// String implement fmt.Stringer
func (aa ArkAuth) String() string {
return GenerateArkAuthString(aa.ContractId, aa.Nonce, aa.Signature)
return GenerateArkAuthString(aa.ContractId, aa.Nonce, aa.Signature, aa.ChainId)
}

func GenerateArkAuthString(contractId uint64, nonce int64, signature []byte) string {
return fmt.Sprintf("%s:%s", GenerateMessageToSign(contractId, nonce), hex.EncodeToString(signature))
func GenerateArkAuthString(contractId uint64, nonce int64, signature []byte, chainId string) string {
return fmt.Sprintf("%s:%s", GenerateMessageToSign(contractId, nonce, chainId), hex.EncodeToString(signature))
}

func GenerateMessageToSign(contractId uint64, nonce int64) string {
return fmt.Sprintf("%d:%d", contractId, nonce)
func GenerateMessageToSign(contractId uint64, nonce int64, chainId string) string {
return fmt.Sprintf("%d:%d:%s", contractId, nonce, chainId)
}

func parseContractAuth(raw string) (ContractAuth, error) {
var auth ContractAuth
var err error

parts := strings.SplitN(raw, ":", 3)
parts := strings.SplitN(raw, ":", 4)

if len(parts) > 0 {
auth.ContractId, err = strconv.ParseUint(parts[0], 10, 64)
Expand All @@ -74,8 +76,12 @@ func parseContractAuth(raw string) (ContractAuth, error) {
}
}

if len(parts) > 2 {
auth.Signature, err = hex.DecodeString(parts[2])
if len(parts) > 0 {
auth.ChainId = parts[2]
}

if len(parts) > 3 {
auth.Signature, err = hex.DecodeString(parts[3])
if err != nil {
return auth, err
}
Expand All @@ -87,7 +93,7 @@ func parseArkAuth(raw string) (ArkAuth, error) {
var aa ArkAuth
var err error

parts := strings.SplitN(raw, ":", 3)
parts := strings.SplitN(raw, ":", 4)

if len(parts) > 0 {
aa.ContractId, err = strconv.ParseUint(parts[0], 10, 64)
Expand All @@ -104,7 +110,11 @@ func parseArkAuth(raw string) (ArkAuth, error) {
}

if len(parts) > 2 {
aa.Signature, err = hex.DecodeString(parts[2])
aa.ChainId = parts[2]
}

if len(parts) > 3 {
aa.Signature, err = hex.DecodeString(parts[3])
if err != nil {
return aa, err
}
Expand Down Expand Up @@ -134,7 +144,7 @@ func (auth ContractAuth) Validate(lastTimestamp int64, client common.PubKey) err
if err != nil {
return err
}
msg := fmt.Sprintf("%d:%d", auth.ContractId, auth.Timestamp)
msg := fmt.Sprintf("%d:%d:%s", auth.ContractId, auth.Timestamp, auth.ChainId)
if !pk.VerifySignature([]byte(msg), auth.Signature) {
return fmt.Errorf("invalid signature")
}
Expand Down
4 changes: 2 additions & 2 deletions sentinel/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ func TestArkAuth(t *testing.T) {
require.NoError(t, err)

// happy path
raw := GenerateArkAuthString(contractId, nonce, signature)
raw := GenerateArkAuthString(contractId, nonce, signature, "arkeo")
_, err = parseArkAuth(raw)
require.NoError(t, err)

// bad signature
raw = GenerateArkAuthString(contractId, nonce, signature)
raw = GenerateArkAuthString(contractId, nonce, signature, "arkeo")
_, err = parseArkAuth(raw + "randome not hex!")
require.Error(t, err)
}
Expand Down
4 changes: 2 additions & 2 deletions test/regression/cmd/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func createContractAuth(input map[string]string) (string, bool, error) {
}

// sign our msg
msg := sentinel.GenerateMessageToSign(id, timestamp)
msg := sentinel.GenerateMessageToSign(id, timestamp, "arkeo")
auth, err := signThis(msg, input["signer"])
return auth, true, err
}
Expand Down Expand Up @@ -323,7 +323,7 @@ func createAuth(input map[string]string) (string, bool, error) {
if err != nil {
return "", true, fmt.Errorf("failed to parse nonce: %s", err)
}
msg := sentinel.GenerateMessageToSign(id, nonce)
msg := sentinel.GenerateMessageToSign(id, nonce, "arkeo")
auth, err := signThis(msg, input["signer"])
return auth, true, err
}
Expand Down
4 changes: 2 additions & 2 deletions test/regression/suites/contracts/pay-as-you-go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ description: ensure contract shows up in active contracts
endpoint: http://localhost:3636/open-claims
asserts:
- length == 1
- .[0].signature == "1332d913e9bd87df5b11964a2c6abc770e3fe5bf5228d38e59f23aed97708c4a669c8e050baadca897ad7bec74b7951009b38d477bccdc2427eae8cec90dce39"
- .[0].signature == "0e2ad0a02bf31ca722d67ef1af03e3f955f878e48fbc476e658b320ebbe1e9364b397a57328cf4ee6ed4cbb0cbb2bb4d538c165a3950a02ae2912c0ef370d766"
- .[0].claimed == false
---
########################################################################################
Expand Down Expand Up @@ -143,7 +143,7 @@ type: check
description: ensure contract shows up in active contracts
endpoint: http://localhost:3636/claim/1
asserts:
- .signature == "1332d913e9bd87df5b11964a2c6abc770e3fe5bf5228d38e59f23aed97708c4a669c8e050baadca897ad7bec74b7951009b38d477bccdc2427eae8cec90dce39"
- .signature == "0e2ad0a02bf31ca722d67ef1af03e3f955f878e48fbc476e658b320ebbe1e9364b397a57328cf4ee6ed4cbb0cbb2bb4d538c165a3950a02ae2912c0ef370d766"
- .claimed == false
- .contract_id == 1
---
Expand Down
11 changes: 7 additions & 4 deletions tools/curleo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ func main() {
}
metadata := curl.parseMetadata()
spender := curl.getSpender(*user)

chainID := flag.String("chain-id", "arkeo", "chain ID to use for signing")

contract := curl.getActiveContract(metadata.Configuration.ProviderPubKey.String(), service, spender)
if contract.Height == 0 {
println(fmt.Sprintf("no active contract found for provider:%s cbhain:%s - will attempt free tier", metadata.Configuration.ProviderPubKey.String(), service))
println(fmt.Sprintf("no active contract found for provider:%s chain:%s - will attempt free tier", metadata.Configuration.ProviderPubKey.String(), service))
} else {
claim := curl.getClaim(contract.Id)
auth := curl.sign(*user, contract.Id, claim.Nonce+1)
auth := curl.sign(*user, contract.Id, claim.Nonce+1, *chainID)
values.Add(sentinel.QueryArkAuth, auth)
}
u.RawQuery = values.Encode()
Expand Down Expand Up @@ -189,7 +192,7 @@ func (c Curl) parseMetadata() sentinel.Metadata {
return meta
}

func (c Curl) sign(user string, contractId uint64, nonce int64) string {
func (c Curl) sign(user string, contractId uint64, nonce int64, chainId string) string {
interfaceRegistry := codectypes.NewInterfaceRegistry()
std.RegisterInterfaces(interfaceRegistry)
ModuleBasics.RegisterInterfaces(interfaceRegistry)
Expand All @@ -203,7 +206,7 @@ func (c Curl) sign(user string, contractId uint64, nonce int64) string {
if err != nil {
log.Fatal(err)
}
msg := sentinel.GenerateMessageToSign(contractId, nonce)
msg := sentinel.GenerateMessageToSign(contractId, nonce, chainId)

println("invoking Sign...")
signature, pk, err := kb.Sign(user, []byte(msg), signing.SignMode_SIGN_MODE_DIRECT)
Expand Down
4 changes: 2 additions & 2 deletions x/arkeo/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func SetupKeeper(t testing.TB) (cosmos.Context, Keeper) {
"ArkeoParams",
)

ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())
ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()).WithChainID("arkeo")

_ = paramskeeper.NewKeeper(cdc, amino, keyParams, tkeyParams)
govModuleAddr := authtypes.NewModuleAddress(govtypes.ModuleName).String()
Expand Down Expand Up @@ -217,7 +217,7 @@ func SetupKeeperWithStaking(t testing.TB) (cosmos.Context, Keeper, stakingkeeper
"ArkeoParams",
)

ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())
ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()).WithChainID("arkeo")

govModuleAddr := "tarkeo1krj9ywwmqcellgunxg66kjw5dtt402kq0uf6pu"
_ = paramskeeper.NewKeeper(cdc, amino, keyParams, tkeyParams)
Expand Down
2 changes: 1 addition & 1 deletion x/arkeo/keeper/msg_server_claim_contract_income.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (k msgServer) HandlerClaimContractIncome(ctx cosmos.Context, msg *types.Msg
if err != nil {
return err
}
if !pk.VerifySignature(msg.GetBytesToSign(), msg.Signature) {
if !pk.VerifySignature(msg.GetBytesToSign(ctx.ChainID()), msg.Signature) {
return errors.Wrap(types.ErrClaimContractIncomeInvalidSignature, "signature mismatch")
}
}
Expand Down
76 changes: 69 additions & 7 deletions x/arkeo/keeper/msg_server_claim_contract_income_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestValidate(t *testing.T) {
Nonce: 20,
}

message := msg.GetBytesToSign()
message := msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)
require.NoError(t, s.HandlerClaimContractIncome(ctx, &msg))
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestHandlePayAsYouGo(t *testing.T) {
Nonce: 20,
}

message := msg.GetBytesToSign()
message := msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand All @@ -143,7 +143,7 @@ func TestHandlePayAsYouGo(t *testing.T) {
Nonce: 21,
}

message = msg.GetBytesToSign()
message = msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand All @@ -157,7 +157,7 @@ func TestHandlePayAsYouGo(t *testing.T) {
msg.Nonce = 25

// update signature for message
message = msg.GetBytesToSign()
message = msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand All @@ -174,7 +174,7 @@ func TestHandlePayAsYouGo(t *testing.T) {
msg.Nonce = contract.Deposit.Int64() / contract.Rate.Amount.Int64() * 1000000000000

// update signature for message
message = msg.GetBytesToSign()
message = msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand Down Expand Up @@ -311,7 +311,7 @@ func TestClaimContractIncomeHandler(t *testing.T) {
Nonce: 20,
}

message := msg.GetBytesToSign()
message := msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand All @@ -324,7 +324,7 @@ func TestClaimContractIncomeHandler(t *testing.T) {
// bad nonce
msg.Nonce = 0

message = msg.GetBytesToSign()
message = msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

Expand Down Expand Up @@ -381,3 +381,65 @@ func TestClaimContractIncomeHandlerSignatureVerification(t *testing.T) {
err = s.HandlerClaimContractIncome(ctx, &msg)
require.Error(t, err, types.ErrClaimContractIncomeInvalidSignature)
}

func TestSignatureReplay(t *testing.T) {
var err error
// Setup keeper, context, and staking
ctx, k, sk := SetupKeeperWithStaking(t)
ctx = ctx.WithBlockHeight(20)

s := newMsgServer(k, sk)

// Set up necessary codec and keyring for signing.
interfaceRegistry := codectypes.NewInterfaceRegistry()
std.RegisterInterfaces(interfaceRegistry)
module.NewBasicManager().RegisterInterfaces(interfaceRegistry)
types.RegisterInterfaces(interfaceRegistry)
cdc := codec.NewProtoCodec(interfaceRegistry)

pubkey := types.GetRandomPubKey()
acc := types.GetRandomBech32Addr()
service := common.BTCService
kb := cKeys.NewInMemory(cdc)
info, _, err := kb.NewMnemonic("whatever", cKeys.English, `m/44'/931'/0'/0/0`, "", hd.Secp256k1)
require.NoError(t, err)
pk, err := info.GetPubKey()
require.NoError(t, err)
client, err := common.NewPubKeyFromCrypto(pk)
require.NoError(t, err)
rate, err := cosmos.ParseCoin("10uarkeo")
require.NoError(t, err)

contract := types.NewContract(pubkey, service, client)
contract.Duration = 100
contract.Rate = rate
contract.Height = 10
contract.Nonce = 0
contract.Type = types.ContractType_PAY_AS_YOU_GO
contract.Deposit = cosmos.NewInt(contract.Duration * contract.Rate.Amount.Int64())
contract.Id = 1
require.NoError(t, k.SetContract(ctx, contract))

require.NoError(t, k.MintToModule(ctx, types.ReserveName, getCoin(common.Tokens(10000*100*2))))
require.NoError(t, k.SendFromModuleToModule(ctx, types.ReserveName, types.ContractName, getCoins(1000*100)))

// Create a claim message
msg := types.MsgClaimContractIncome{
ContractId: contract.Id,
Creator: acc.String(),
Nonce: 20,
}

// Sign the message using chain ID "arkeo"
messageBytes := msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", messageBytes, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)

require.NoError(t, s.HandlerClaimContractIncome(ctx, &msg))

ctxReplay := ctx.WithChainID("otherchain")
msg.Nonce = 21
err = s.HandlerClaimContractIncome(ctxReplay, &msg)
require.Error(t, err)
require.Contains(t, err.Error(), "signature mismatch", "expected signature mismatch error when using a replayed signature on a different chain")
}
2 changes: 1 addition & 1 deletion x/arkeo/keeper/msg_server_open_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func TestOpenContractWithSettlementPeriod(t *testing.T) {
Creator: clientAddress.String(),
Nonce: 20,
}
message := claimMsg.GetBytesToSign()
message := claimMsg.GetBytesToSign("arkeo")
claimMsg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)
_, err = s.ClaimContractIncome(ctx, &claimMsg)
Expand Down
8 changes: 4 additions & 4 deletions x/arkeo/types/message_claim_contract_income.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func (msg *MsgClaimContractIncome) GetSignBytes() []byte {
return sdk.MustSortJSON(bz)
}

func (msg *MsgClaimContractIncome) GetBytesToSign() []byte {
return GetBytesToSign(msg.ContractId, msg.Nonce)
func (msg *MsgClaimContractIncome) GetBytesToSign(chainId string) []byte {
return GetBytesToSign(msg.ContractId, msg.Nonce, chainId)
}

func GetBytesToSign(contractId uint64, nonce int64) []byte {
return []byte(fmt.Sprintf("%d:%d", contractId, nonce))
func GetBytesToSign(contractId uint64, nonce int64, chainID string) []byte {
return []byte(fmt.Sprintf("%d:%d:%s", contractId, nonce, chainID))
}

func (msg *MsgClaimContractIncome) ValidateBasic() error {
Expand Down
2 changes: 1 addition & 1 deletion x/arkeo/types/message_claim_contract_income_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestClaimContractIncomeValidateBasic(t *testing.T) {
Nonce: 24,
}

message := msg.GetBytesToSign()
message := msg.GetBytesToSign("arkeo")
msg.Signature, _, err = kb.Sign("whatever", message, signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)
err = msg.ValidateBasic()
Expand Down
Loading