From df12adaa9ebd4b63338829de03ee1b5fb0e0474d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 25 Mar 2021 15:22:16 +0300 Subject: [PATCH] crypto: remove crypto.Verifiable interface We can now verify any hash.Hashable thing. --- internal/fakechain/fakechain.go | 7 ++++--- pkg/compiler/interop_test.go | 4 +++- pkg/consensus/payload_test.go | 3 ++- pkg/core/blockchain.go | 3 +-- pkg/core/blockchainer/blockchainer.go | 4 ++-- pkg/core/interop/context.go | 6 ++++-- pkg/core/interop/crypto/ecdsa.go | 7 +++---- pkg/core/interop/crypto/ecdsa_test.go | 3 ++- pkg/crypto/hash/hash.go | 23 +++++++++++++++++++++++ pkg/crypto/keys/private_key.go | 7 +++++++ pkg/crypto/keys/publickey.go | 7 +++++++ pkg/crypto/verifiable.go | 9 --------- pkg/network/extpool/pool_test.go | 4 ++-- pkg/smartcontract/context/context_test.go | 2 +- 14 files changed, 61 insertions(+), 28 deletions(-) diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index 9fed4b11b..587a51c5d 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services" @@ -14,7 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" @@ -49,7 +50,7 @@ func NewFakeChain() *FakeChain { blocks: make(map[util.Uint256]*block.Block), hdrHashes: make(map[uint32]util.Uint256), txs: make(map[util.Uint256]*transaction.Transaction), - ProtocolConfiguration: config.ProtocolConfiguration{P2PNotaryRequestPayloadPoolSize: 10}, + ProtocolConfiguration: config.ProtocolConfiguration{Magic: netmode.UnitTestNet, P2PNotaryRequestPayloadPoolSize: 10}, } } @@ -393,7 +394,7 @@ func (chain *FakeChain) VerifyTx(*transaction.Transaction) error { } // VerifyWitness implements Blockchainer interface. -func (chain *FakeChain) VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error { +func (chain *FakeChain) VerifyWitness(util.Uint160, hash.Hashable, *transaction.Witness, int64) error { if chain.VerifyWitnessF != nil { return chain.VerifyWitnessF() } diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 4fe4c25e0..aeb81cf20 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/nspcc-dev/neo-go/internal/fakechain" "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" @@ -186,7 +187,8 @@ func TestAppCall(t *testing.T) { return nil, errors.New("not found") } - ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t)) + fc := fakechain.NewFakeChain() + ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t)) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) diff --git a/pkg/consensus/payload_test.go b/pkg/consensus/payload_test.go index 2b24c9985..7a313dd42 100644 --- a/pkg/consensus/payload_test.go +++ b/pkg/consensus/payload_test.go @@ -79,7 +79,7 @@ func TestConsensusPayload_Setters(t *testing.T) { func TestConsensusPayload_Serializable(t *testing.T) { for _, mt := range messageTypes { p := randomPayload(t, mt) - actual := new(Payload) + actual := &Payload{Extensible: npayload.Extensible{Network: netmode.UnitTestNet}} data, err := testserdes.EncodeBinary(p) require.NoError(t, err) require.NoError(t, testserdes.DecodeBinary(data, &actual.Extensible)) @@ -158,6 +158,7 @@ func randomPayload(t *testing.T, mt messageType) *Payload { payload: randomMessage(t, mt), }, Extensible: npayload.Extensible{ + Network: netmode.UnitTestNet, Witness: transaction.Witness{ InvocationScript: random.Bytes(3), VerificationScript: []byte{byte(opcode.PUSH0)}, diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index f09d77cf6..f2aaa284f 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -25,7 +25,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/stateroot" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" @@ -1742,7 +1741,7 @@ func (bc *Blockchain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160 } // VerifyWitness checks that w is a correct witness for c signed by h. -func (bc *Blockchain) VerifyWitness(h util.Uint160, c crypto.Verifiable, w *transaction.Witness, gas int64) error { +func (bc *Blockchain) VerifyWitness(h util.Uint160, c hash.Hashable, w *transaction.Witness, gas int64) error { ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) ic.Container = c _, err := bc.verifyHashAgainstScript(h, w, ic, gas) diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index c9936e9d0..9ea0ad873 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -9,7 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" @@ -71,7 +71,7 @@ type Blockchainer interface { SubscribeForNotifications(ch chan<- *state.NotificationEvent) SubscribeForTransactions(ch chan<- *transaction.Transaction) VerifyTx(*transaction.Transaction) error - VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error + VerifyWitness(util.Uint160, hash.Hashable, *transaction.Witness, int64) error GetMemPool() *mempool.Pool UnsubscribeFromBlocks(ch chan<- *block.Block) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 9f7471ab7..e973a68a2 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -12,7 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -34,7 +34,8 @@ const ( // Context represents context in which interops are executed. type Context struct { Chain blockchainer.Blockchainer - Container crypto.Verifiable + Container hash.Hashable + Network uint32 Natives []Contract Trigger trigger.Type Block *block.Block @@ -55,6 +56,7 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, nes := make([]state.NotificationEvent, 0) return &Context{ Chain: bc, + Network: uint32(bc.GetConfig().Magic), Natives: natives, Trigger: trigger, Block: block, diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 78b9ddb1e..14c93aed4 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/vm" ) @@ -14,7 +15,6 @@ import ( // ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using // Secp256r1 elliptic curve. func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { - hashToCheck := ic.Container.GetSignedHash() pkeys, err := ic.VM.Estack().PopSigElements() if err != nil { return fmt.Errorf("wrong parameters: %w", err) @@ -31,21 +31,20 @@ func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { if len(pkeys) < len(sigs) { return errors.New("more signatures than there are keys") } - sigok := vm.CheckMultisigPar(ic.VM, elliptic.P256(), hashToCheck.BytesBE(), pkeys, sigs) + sigok := vm.CheckMultisigPar(ic.VM, elliptic.P256(), hash.NetSha256(ic.Network, ic.Container).BytesBE(), pkeys, sigs) ic.VM.Estack().PushVal(sigok) return nil } // ECDSASecp256r1CheckSig checks ECDSA signature using Secp256r1 elliptic curve. func ECDSASecp256r1CheckSig(ic *interop.Context) error { - hashToCheck := ic.Container.GetSignedHash() keyb := ic.VM.Estack().Pop().Bytes() signature := ic.VM.Estack().Pop().Bytes() pkey, err := keys.NewPublicKeyFromBytes(keyb, elliptic.P256()) if err != nil { return err } - res := pkey.Verify(signature, hashToCheck.BytesBE()) + res := pkey.VerifyHashable(signature, ic.Network, ic.Container) ic.VM.Estack().PushVal(res) return nil } diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index e82f64414..01342ef70 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -67,6 +67,7 @@ func initCheckMultisigVMNoArgs(container *transaction.Transaction) *vm.VM { binary.LittleEndian.PutUint32(buf[1:], neoCryptoCheckMultisigID) ic := &interop.Context{ + Network: uint32(netmode.UnitTestNet), Trigger: trigger.Verification, Container: container, } @@ -172,7 +173,7 @@ func TestCheckSig(t *testing.T) { verifyFunc := ECDSASecp256r1CheckSig d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - ic := &interop.Context{DAO: dao.NewCached(d)} + ic := &interop.Context{Network: uint32(netmode.UnitTestNet), DAO: dao.NewCached(d)} runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) { ic.SpawnVM() for i := range args { diff --git a/pkg/crypto/hash/hash.go b/pkg/crypto/hash/hash.go index e161e4051..06ce1f421 100644 --- a/pkg/crypto/hash/hash.go +++ b/pkg/crypto/hash/hash.go @@ -2,11 +2,34 @@ package hash import ( "crypto/sha256" + "encoding/binary" "github.com/nspcc-dev/neo-go/pkg/util" "golang.org/x/crypto/ripemd160" ) +// Hashable represents an object which can be hashed. Usually these objects +// are io.Serializable and signable. They tend to cache the hash inside for +// effectiveness, providing this accessor method. Anything that can be +// identified with a hash can then be signed and verified. +type Hashable interface { + Hash() util.Uint256 +} + +func getSignedData(net uint32, hh Hashable) []byte { + var b = make([]byte, 4+32) + binary.LittleEndian.PutUint32(b, net) + h := hh.Hash() + copy(b[4:], h[:]) + return b +} + +// NetSha256 calculates network-specific hash of Hashable item that can then +// be signed/verified. +func NetSha256(net uint32, hh Hashable) util.Uint256 { + return Sha256(getSignedData(net, hh)) +} + // Sha256 hashes the incoming byte slice // using the sha256 algorithm. func Sha256(data []byte) util.Uint256 { diff --git a/pkg/crypto/keys/private_key.go b/pkg/crypto/keys/private_key.go index 111f3283e..adb4460c9 100644 --- a/pkg/crypto/keys/private_key.go +++ b/pkg/crypto/keys/private_key.go @@ -11,6 +11,7 @@ import ( "math/big" "github.com/btcsuite/btcd/btcec" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/rfc6979" ) @@ -154,6 +155,12 @@ func (p *PrivateKey) SignHash(digest util.Uint256) []byte { return getSignatureSlice(p.PrivateKey.Curve, r, s) } +// SignHashable signs some Hashable item for the network specified using +// hash.NetSha256() with the private key. +func (p *PrivateKey) SignHashable(net uint32, hh hash.Hashable) []byte { + return p.SignHash(hash.NetSha256(net, hh)) +} + func getSignatureSlice(curve elliptic.Curve, r, s *big.Int) []byte { params := curve.Params() curveOrderByteSize := params.P.BitLen() / 8 diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index c5ae4de3f..b4e86d0e9 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -343,6 +343,13 @@ func (p *PublicKey) Verify(signature []byte, hash []byte) bool { return ecdsa.Verify(&pk, hash, rBytes, sBytes) } +// VerifyHashable returns true if the signature is valid and corresponds +// to the hash and public key. +func (p *PublicKey) VerifyHashable(signature []byte, net uint32, hh hash.Hashable) bool { + var digest = hash.NetSha256(net, hh) + return p.Verify(signature, digest[:]) +} + // IsInfinity checks if the key is infinite (null, basically). func (p *PublicKey) IsInfinity() bool { return p.X == nil && p.Y == nil diff --git a/pkg/crypto/verifiable.go b/pkg/crypto/verifiable.go index 10468f6f9..40ff7ba1a 100644 --- a/pkg/crypto/verifiable.go +++ b/pkg/crypto/verifiable.go @@ -1,17 +1,8 @@ package crypto -import "github.com/nspcc-dev/neo-go/pkg/util" - -// Verifiable represents an object which can be verified. -type Verifiable interface { - GetSignedPart() []byte - GetSignedHash() util.Uint256 -} - // VerifiableDecodable represents an object which can be verified and // those hashable part can be encoded/decoded. type VerifiableDecodable interface { - Verifiable EncodeHashableFields() ([]byte, error) DecodeHashableFields([]byte) error } diff --git a/pkg/network/extpool/pool_test.go b/pkg/network/extpool/pool_test.go index 5757b5310..aaf554050 100644 --- a/pkg/network/extpool/pool_test.go +++ b/pkg/network/extpool/pool_test.go @@ -6,7 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" @@ -93,7 +93,7 @@ func newTestChain() *testChain { }, } } -func (c *testChain) VerifyWitness(u util.Uint160, _ crypto.Verifiable, _ *transaction.Witness, _ int64) error { +func (c *testChain) VerifyWitness(u util.Uint160, _ hash.Hashable, _ *transaction.Witness, _ int64) error { if !c.verifyWitness(u) { return errVerification } diff --git a/pkg/smartcontract/context/context_test.go b/pkg/smartcontract/context/context_test.go index e7876afd6..58c232ea6 100644 --- a/pkg/smartcontract/context/context_test.go +++ b/pkg/smartcontract/context/context_test.go @@ -119,7 +119,7 @@ func TestParameterContext_AddSignatureMultisig(t *testing.T) { } func newTestVM(w *transaction.Witness, tx *transaction.Transaction) *vm.VM { - ic := &interop.Context{Container: tx} + ic := &interop.Context{Network: uint32(netmode.UnitTestNet), Container: tx} crypto.Register(ic) v := ic.SpawnVM() v.LoadScript(w.VerificationScript)