Skip to content
48 changes: 16 additions & 32 deletions data/transactions/verify/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,42 +99,26 @@ func (g *GroupContext) Equal(other *GroupContext) bool {
g.minAvmVersion == other.minAvmVersion
}

// Txn verifies a SignedTxn as being signed and having no obviously inconsistent data.
Comment thread
algorandskiy marked this conversation as resolved.
// txnBatchVerifyPrep verifies a SignedTxn having no obviously inconsistent data.
// Block-assembly time checks of LogicSig and accounting rules may still block the txn.
func Txn(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext) error {
batchVerifier := crypto.MakeBatchVerifier()

if err := TxnBatchVerify(s, txnIdx, groupCtx, batchVerifier); err != nil {
return err
}

// this case is used for comapact certificate where no signature is supplied
if batchVerifier.GetNumberOfEnqueuedSignatures() == 0 {
return nil
}
return batchVerifier.Verify()
}

// TxnBatchVerify verifies a SignedTxn having no obviously inconsistent data.
// Block-assembly time checks of LogicSig and accounting rules may still block the txn.
// it is the caller responsibility to call batchVerifier.verify()
func TxnBatchVerify(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) error {
// it is the caller responsibility to call batchVerifier.Verify()
func txnBatchVerifyPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) error {
if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) {
return errors.New("nonempty AuthAddr but rekeying not supported")
return errors.New("nonempty AuthAddr but rekeying is not supported")
}

if err := s.Txn.WellFormed(groupCtx.specAddrs, groupCtx.consensusParams); err != nil {
return err
}

return stxnVerifyCore(s, txnIdx, groupCtx, verifier)
return stxnCoreChecks(s, txnIdx, groupCtx, verifier)
}

// TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data.
func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) {
batchVerifier := crypto.MakeBatchVerifier()

if groupCtx, err = TxnGroupBatchVerify(stxs, contextHdr, cache, ledger, batchVerifier); err != nil {
if groupCtx, err = txnGroupBatchVerifyPrep(stxs, contextHdr, cache, ledger, batchVerifier); err != nil {
return nil, err
}

Comment thread
algonautshant marked this conversation as resolved.
Expand All @@ -149,9 +133,9 @@ func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader,
return
}

// TxnGroupBatchVerify verifies a []SignedTxn having no obviously inconsistent data.
// it is the caller responsibility to call batchVerifier.verify()
func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) {
// txnGroupBatchVerifyPrep verifies a []SignedTxn having no obviously inconsistent data.
// it is the caller responsibility to call batchVerifier.Verify()
func txnGroupBatchVerifyPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) {
Comment thread
algonautshant marked this conversation as resolved.
Outdated
groupCtx, err = PrepareGroupContext(stxs, contextHdr, ledger)
if err != nil {
return nil, err
Expand All @@ -160,7 +144,7 @@ func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.B
minFeeCount := uint64(0)
feesPaid := uint64(0)
for i, stxn := range stxs {
err = TxnBatchVerify(&stxn, i, groupCtx, verifier)
err = txnBatchVerifyPrep(&stxn, i, groupCtx, verifier)
if err != nil {
err = fmt.Errorf("transaction %+v invalid : %w", stxn, err)
return
Expand Down Expand Up @@ -190,7 +174,7 @@ func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.B
return
}

func stxnVerifyCore(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error {
func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error {
numSigs := 0
hasSig := false
hasMsig := false
Expand Down Expand Up @@ -246,7 +230,7 @@ func stxnVerifyCore(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex
func LogicSigSanityCheck(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext) error {
batchVerifier := crypto.MakeBatchVerifier()

if err := LogicSigSanityCheckBatchVerify(txn, groupIndex, groupCtx, batchVerifier); err != nil {
if err := logicSigSanityCheckBatchVerifyPrep(txn, groupIndex, groupCtx, batchVerifier); err != nil {
return err
}

Expand All @@ -258,10 +242,10 @@ func LogicSigSanityCheck(txn *transactions.SignedTxn, groupIndex int, groupCtx *
return batchVerifier.Verify()
}

// LogicSigSanityCheckBatchVerify checks that the signature is valid and that the program is basically well formed.
// logicSigSanityCheckBatchVerifyPrep checks that the signature is valid and that the program is basically well formed.
// It does not evaluate the logic.
// it is the caller responsibility to call batchVerifier.verify()
func LogicSigSanityCheckBatchVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error {
// it is the caller responsibility to call batchVerifier.Verify()
func logicSigSanityCheckBatchVerifyPrep(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error {
lsig := txn.Lsig

if groupCtx.consensusParams.LogicSigVersion == 0 {
Expand Down Expand Up @@ -404,7 +388,7 @@ func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHea

batchVerifier := crypto.MakeBatchVerifierWithHint(len(payset))
for i, signTxnsGrp := range txnGroups {
groupCtxs[i], grpErr = TxnGroupBatchVerify(signTxnsGrp, blkHeader, nil, ledger, batchVerifier)
groupCtxs[i], grpErr = txnGroupBatchVerifyPrep(signTxnsGrp, blkHeader, nil, ledger, batchVerifier)
// abort only if it's a non-cache error.
if grpErr != nil {
return grpErr
Expand Down
44 changes: 29 additions & 15 deletions data/transactions/verify/txn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ var spec = transactions.SpecialAddresses{
RewardsPool: poolAddr,
}

func verifyTxn(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext) error {
batchVerifier := crypto.MakeBatchVerifier()

if err := txnBatchVerifyPrep(s, txnIdx, groupCtx, batchVerifier); err != nil {
return err
}

// this case is used for comapact certificate where no signature is supplied
Comment thread
algonautshant marked this conversation as resolved.
Outdated
if batchVerifier.GetNumberOfEnqueuedSignatures() == 0 {
return nil
}
Comment thread
algonautshant marked this conversation as resolved.
Outdated
return batchVerifier.Verify()
}

func keypair() *crypto.SignatureSecrets {
var seed crypto.Seed
crypto.RandBytes(seed[:])
Expand Down Expand Up @@ -117,14 +131,14 @@ func TestSignedPayment(t *testing.T) {
groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil)
require.NoError(t, err)
require.NoError(t, payment.WellFormed(spec, proto), "generateTestObjects generated an invalid payment")
require.NoError(t, Txn(&stxn, 0, groupCtx), "generateTestObjects generated a bad signedtxn")
require.NoError(t, verifyTxn(&stxn, 0, groupCtx), "generateTestObjects generated a bad signedtxn")

stxn2 := payment.Sign(secret)
require.Equal(t, stxn2.Sig, stxn.Sig, "got two different signatures for the same transaction (our signing function is deterministic)")

stxn2.MessUpSigForTesting()
require.Equal(t, stxn.ID(), stxn2.ID(), "changing sig caused txid to change")
require.Error(t, Txn(&stxn2, 0, groupCtx), "verify succeeded with bad sig")
require.Error(t, verifyTxn(&stxn2, 0, groupCtx), "verify succeeded with bad sig")

require.True(t, crypto.SignatureVerifier(addr).Verify(payment, stxn.Sig), "signature on the transaction is not the signature of the hash of the transaction under the spender's key")
}
Expand All @@ -137,15 +151,15 @@ func TestTxnValidationEncodeDecode(t *testing.T) {
for _, txn := range signed {
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
require.NoError(t, err)
if Txn(&txn, 0, groupCtx) != nil {
if verifyTxn(&txn, 0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
}

x := protocol.Encode(&txn)
var signedTx transactions.SignedTxn
protocol.Decode(x, &signedTx)

if Txn(&signedTx, 0, groupCtx) != nil {
if verifyTxn(&signedTx, 0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
}
}
Expand All @@ -159,14 +173,14 @@ func TestTxnValidationEmptySig(t *testing.T) {
for _, txn := range signed {
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
require.NoError(t, err)
if Txn(&txn, 0, groupCtx) != nil {
if verifyTxn(&txn, 0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
}

txn.Sig = crypto.Signature{}
txn.Msig = crypto.MultisigSig{}
txn.Lsig = transactions.LogicSig{}
if Txn(&txn, 0, groupCtx) == nil {
if verifyTxn(&txn, 0, groupCtx) == nil {
t.Errorf("transaction %#v verified without sig", txn)
}
}
Expand Down Expand Up @@ -205,42 +219,42 @@ func TestTxnValidationStateProof(t *testing.T) {
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil)
require.NoError(t, err)

err = Txn(&stxn, 0, groupCtx)
err = verifyTxn(&stxn, 0, groupCtx)
require.NoError(t, err, "state proof txn %#v did not verify", stxn)

stxn2 := stxn
stxn2.Txn.Type = protocol.PaymentTx
stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee}
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "payment txn %#v verified from StateProofSender", stxn2)

secret := keypair()
stxn2 = stxn
stxn2.Txn.Header.Sender = basics.Address(secret.SignatureVerifier)
stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee}
stxn2 = stxn2.Txn.Sign(secret)
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "state proof txn %#v verified from non-StateProofSender", stxn2)

// state proof txns are not allowed to have non-zero values for many fields
stxn2 = stxn
stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee}
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "state proof txn %#v verified", stxn2)

stxn2 = stxn
stxn2.Txn.Header.Note = []byte{'A'}
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "state proof txn %#v verified", stxn2)

stxn2 = stxn
stxn2.Txn.Lease[0] = 1
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "state proof txn %#v verified", stxn2)

stxn2 = stxn
stxn2.Txn.RekeyTo = basics.Address(secret.SignatureVerifier)
err = Txn(&stxn2, 0, groupCtx)
err = verifyTxn(&stxn2, 0, groupCtx)
require.Error(t, err, "state proof txn %#v verified", stxn2)
}

Expand All @@ -258,7 +272,7 @@ func TestDecodeNil(t *testing.T) {
// This used to panic when run on a zero value of SignedTxn.
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil)
require.NoError(t, err)
Txn(&st, 0, groupCtx)
verifyTxn(&st, 0, groupCtx)
}
}

Expand Down Expand Up @@ -425,7 +439,7 @@ func BenchmarkTxn(b *testing.B) {
groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil)
require.NoError(b, err)
for i, txn := range txnGroup {
err := Txn(&txn, i, groupCtx)
err := verifyTxn(&txn, i, groupCtx)
require.NoError(b, err)
}
}
Expand Down
6 changes: 3 additions & 3 deletions test/e2e-go/upgrades/rekey_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ func TestRekeyUpgrade(t *testing.T) {
_, err = client.BroadcastTransaction(rekeyed)
// non empty err means the upgrade have not happened yet (as expected), ensure the error
if err != nil {
// should be either "nonempty AuthAddr but rekeying not supported" or "txn dead"
if !strings.Contains(err.Error(), "nonempty AuthAddr but rekeying not supported") &&
// should be either "nonempty AuthAddr but rekeying is not supported" or "txn dead"
if !strings.Contains(err.Error(), "nonempty AuthAddr but rekeying is not supported") &&
!strings.Contains(err.Error(), "txn dead") {
a.NoErrorf(err, "error message should be one of :\n%s\n%s", "nonempty AuthAddr but rekeying not supported", "txn dead")
a.NoErrorf(err, "error message should be one of :\n%s\n%s", "nonempty AuthAddr but rekeying is not supported", "txn dead")
}
} else {
// if we had no error it must mean that we've upgraded already. Verify that.
Expand Down