Skip to content

Commit

Permalink
Recover signer from payer envelopes (#234)
Browse files Browse the repository at this point in the history
## tl;dr

- Adds method to recover the signer from payer envelopes (#232)
- Refactors domain separation for jwt's and payer envelopes

### AI Assisted Summary

Introduced domain separation for payer signatures, and added functionality to recover signer addresses from payer envelopes.

### What changed?

- Created a new `constants` package with domain separation labels for JWT and payer signatures.
- Updated the JWT signing method to use the new constant for domain separation.
- Implemented a `RecoverSigner` method for `PayerEnvelope` to extract the signer's address.
- Added utility functions for hashing and signing payer envelopes.
- Expanded test coverage for envelope signer recovery.

### Why make this change?

This change enhances security and consistency by introducing domain separation for signatures. It also adds the ability to recover signer addresses from payer envelopes, which is crucial for verifying the authenticity of messages and implementing payer-based features in the system.
  • Loading branch information
neekolas authored Oct 18, 2024
1 parent ccda751 commit 4ecf00b
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 11 deletions.
18 changes: 7 additions & 11 deletions pkg/authn/signingMethod.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (

ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/golang-jwt/jwt/v5"
"github.com/xmtp/xmtpd/pkg/utils"
)

const (
ALGORITHM = "ES256K"
SIG_LENGTH = 65
R_LENGTH = 32
S_LENGTH = 32
DOMAIN_SEPARATION_LABEL = "jwt"
ALGORITHM = "ES256K"
SIG_LENGTH = 65
R_LENGTH = 32
S_LENGTH = 32
)

var (
Expand All @@ -36,7 +36,7 @@ func (sm *SigningMethodSecp256k1) Verify(signingString string, sig []byte, key i
return ErrWrongKeyFormat
}

hashedString := hashStringWithDomainSeparation(signingString)
hashedString := utils.HashJWTSignatureInput([]byte(signingString))

if len(sig) != SIG_LENGTH {
return ErrBadSignature
Expand All @@ -58,7 +58,7 @@ func (sm *SigningMethodSecp256k1) Sign(signingString string, key interface{}) ([
return nil, ErrWrongKeyFormat
}

hashedString := hashStringWithDomainSeparation(signingString)
hashedString := utils.HashJWTSignatureInput([]byte(signingString))

sig, err := ethcrypto.Sign(hashedString, priv)
if err != nil {
Expand All @@ -76,10 +76,6 @@ func (sm *SigningMethodSecp256k1) Alg() string {
return ALGORITHM
}

func hashStringWithDomainSeparation(signingString string) []byte {
return ethcrypto.Keccak256([]byte(DOMAIN_SEPARATION_LABEL + signingString))
}

func init() {
method := &SigningMethodSecp256k1{}
jwt.RegisterSigningMethod(ALGORITHM, func() jwt.SigningMethod {
Expand Down
6 changes: 6 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package constants

const (
JWT_DOMAIN_SEPARATION_LABEL = "jwt|"
PAYER_DOMAIN_SEPARATION_LABEL = "payer|"
)
43 changes: 43 additions & 0 deletions pkg/envelopes/envelopes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package envelopes
import (
"testing"

ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/xmtp/xmtpd/pkg/proto/identity/associations"
"github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api"
"github.com/xmtp/xmtpd/pkg/testutils"
envelopeTestUtils "github.com/xmtp/xmtpd/pkg/testutils/envelopes"
"github.com/xmtp/xmtpd/pkg/topic"
"github.com/xmtp/xmtpd/pkg/utils"
"google.golang.org/protobuf/proto"
)

Expand Down Expand Up @@ -132,3 +136,42 @@ func TestPayloadType(t *testing.T) {
require.False(t, clientEnvelope.TopicMatchesPayload())

}

func TestRecoverSigner(t *testing.T) {
payerPrivateKey := testutils.RandomPrivateKey(t)
rawPayerEnv := envelopeTestUtils.CreatePayerEnvelope(t)

payerSignature, err := utils.SignPayerEnvelope(
rawPayerEnv.UnsignedClientEnvelope,
payerPrivateKey,
)
require.NoError(t, err)
rawPayerEnv.PayerSignature = &associations.RecoverableEcdsaSignature{
Bytes: payerSignature,
}

payerEnv, err := NewPayerEnvelope(rawPayerEnv)
require.NoError(t, err)

signer, err := payerEnv.RecoverSigner()
require.NoError(t, err)
require.Equal(t, ethcrypto.PubkeyToAddress(payerPrivateKey.PublicKey).Hex(), signer.Hex())

// Now test with an incorrect signature
wrongPayerSignature, err := utils.SignPayerEnvelope(
testutils.RandomBytes(128),
payerPrivateKey,
)
require.NoError(t, err)
rawPayerEnv.PayerSignature = &associations.RecoverableEcdsaSignature{
Bytes: wrongPayerSignature,
}
payerEnv, err = NewPayerEnvelope(rawPayerEnv)
require.NoError(t, err)

// This will recover an incorrect signer address because the inputs to the signature
// do not match the unsigned client envelope
newSigner, err := payerEnv.RecoverSigner()
require.NoError(t, err)
require.NotEqual(t, signer.Hex(), newSigner.Hex())
}
20 changes: 20 additions & 0 deletions pkg/envelopes/payer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package envelopes
import (
"errors"

"github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api"
"github.com/xmtp/xmtpd/pkg/utils"
"google.golang.org/protobuf/proto"
)

Expand Down Expand Up @@ -35,3 +38,20 @@ func (p *PayerEnvelope) Bytes() ([]byte, error) {
}
return bytes, nil
}

func (p *PayerEnvelope) RecoverSigner() (*common.Address, error) {
payerSignature := p.proto.PayerSignature
if payerSignature == nil {
return nil, errors.New("payer signature is missing")
}

hash := utils.HashPayerSignatureInput(p.proto.UnsignedClientEnvelope)
signer, err := ethcrypto.SigToPub(hash, payerSignature.Bytes)
if err != nil {
return nil, err
}

address := ethcrypto.PubkeyToAddress(*signer)

return &address, nil
}
20 changes: 20 additions & 0 deletions pkg/utils/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package utils

import (
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/xmtp/xmtpd/pkg/constants"
)

func HashPayerSignatureInput(unsignedClientEnvelope []byte) []byte {
return ethcrypto.Keccak256(
[]byte(constants.PAYER_DOMAIN_SEPARATION_LABEL),
unsignedClientEnvelope,
)
}

func HashJWTSignatureInput(textToSign []byte) []byte {
return ethcrypto.Keccak256(
[]byte(constants.JWT_DOMAIN_SEPARATION_LABEL),
textToSign,
)
}
20 changes: 20 additions & 0 deletions pkg/utils/signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package utils

import (
"crypto/ecdsa"

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

func SignPayerEnvelope(
unsignedClientEnvelope []byte,
payerPrivateKey *ecdsa.PrivateKey,
) ([]byte, error) {
hash := HashPayerSignatureInput(unsignedClientEnvelope)
signature, err := ethcrypto.Sign(hash, payerPrivateKey)
if err != nil {
return nil, err
}

return signature, nil
}

0 comments on commit 4ecf00b

Please sign in to comment.