Skip to content

Commit

Permalink
feat: signature verification cache (#41) (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinsan-line authored Apr 26, 2021
1 parent fe9c41e commit 60ec2db
Showing 1 changed file with 93 additions and 17 deletions.
110 changes: 93 additions & 17 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package ante

import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"sync"

"github.com/line/lbm-sdk/v2/crypto/keys/ed25519"
kmultisig "github.com/line/lbm-sdk/v2/crypto/keys/multisig"
Expand Down Expand Up @@ -165,12 +167,14 @@ func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
type SigVerificationDecorator struct {
ak AccountKeeper
signModeHandler authsigning.SignModeHandler
txHashCache sync.Map
}

func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator {
return SigVerificationDecorator{
func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) *SigVerificationDecorator {
return &SigVerificationDecorator{
ak: ak,
signModeHandler: signModeHandler,
txHashCache: sync.Map{},
}
}

Expand All @@ -195,7 +199,8 @@ func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
}
}

func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
func (svd *SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// TODO https://github.com/line/link/issues/1136
// no need to verify signatures on recheck tx
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
Expand All @@ -219,15 +224,30 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs))
}

newSigKeys := make([]string, 0, len(sigs))
defer func() {
// remove txHashCash if got an error
if err != nil {
for _, sigKey := range newSigKeys {
svd.txHashCache.Delete(sigKey)
}
}
}()

for i, sig := range sigs {
acc, err := GetSignerAcc(ctx, svd.ak, signerAddrs[i])
var acc types.AccountI
acc, err = GetSignerAcc(ctx, svd.ak, signerAddrs[i])
if err != nil {
return ctx, err
}

if simulate {
continue
}

// retrieve pubkey
pubKey := acc.GetPubKey()
if !simulate && pubKey == nil {
if pubKey == nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
}

Expand Down Expand Up @@ -259,26 +279,82 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
Sequence: acc.GetSequence(),
}

if !simulate {
err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx)
if err != nil {
var errMsg string
if onlyAminoSigners {
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
// and therefore communicate sequence number as a potential cause of error.
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
} else {
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
}
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg)
if !genesis {
sigKey := fmt.Sprintf("%d:%d", signerData.AccountNumber, signerData.Sequence)
// TODO could we use `tx.(*wrapper).getBodyBytes()` instead of `ctx.TxBytes()`?
txHash := sha256.Sum256(ctx.TxBytes())
stored := false

stored, err = svd.verifySignatureWithCache(ctx, pubKey, signerData, sig.Data, tx, sigKey, txHash[:])

if stored {
newSigKeys = append(newSigKeys, sigKey)
}
} else {
err = authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx)
}

if err != nil {
var errMsg string
if onlyAminoSigners {
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
// and therefore communicate sequence number as a potential cause of error.
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
} else {
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
}
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg)
}
}

return next(ctx, tx, simulate)
}

func (svd *SigVerificationDecorator) verifySignatureWithCache(
ctx sdk.Context,
pubKey cryptotypes.PubKey,
signerData authsigning.SignerData,
sigData signing.SignatureData,
tx sdk.Tx,
sigKey string,
txHash []byte,
) (stored bool, err error) {
switch {
case ctx.IsCheckTx() && !ctx.IsReCheckTx(): // CheckTx
err = authsigning.VerifySignature(pubKey, signerData, sigData, svd.signModeHandler, tx)
if err == nil {
svd.txHashCache.Store(sigKey, txHash)
stored = true
}

case ctx.IsReCheckTx(): // ReCheckTx
verified, exist := svd.checkCache(sigKey, txHash)
if !verified {
if exist {
svd.txHashCache.Delete(sigKey)
}
err = fmt.Errorf("unable to verify signature")
}

default: // DeliverTx
verified, exist := svd.checkCache(sigKey, txHash)
if exist {
svd.txHashCache.Delete(sigKey)
}
if !verified {
err = authsigning.VerifySignature(pubKey, signerData, sigData, svd.signModeHandler, tx)
}
}

return stored, err
}

func (svd *SigVerificationDecorator) checkCache(sigKey string, txHash []byte) (verified, exist bool) {
cached, exist := svd.txHashCache.Load(sigKey)
verified = exist && bytes.Equal(cached.([]byte), txHash)
return verified, exist
}

// IncrementSequenceDecorator handles incrementing sequences of all signers.
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
// there is no need to execute IncrementSequenceDecorator on RecheckTX since
Expand Down

0 comments on commit 60ec2db

Please sign in to comment.