Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(unchecked-extrinsic): ecdsa signature verification #303

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified build/runtime.wasm
Binary file not shown.
46 changes: 36 additions & 10 deletions execution/types/unchecked_extrinsic.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import (
"bytes"
"fmt"
"reflect"

sc "github.com/LimeChain/goscale"
"github.com/LimeChain/gosemble/primitives/io"
Expand Down Expand Up @@ -138,28 +140,27 @@
if signature.IsEd25519() {
sigEd25519, err := signature.AsEd25519()
if err != nil {
// TODO: return err

Check warning on line 143 in execution/types/unchecked_extrinsic.go

View check run for this annotation

Codecov / codecov/patch

execution/types/unchecked_extrinsic.go#L143

Added line #L143 was not covered by tests
log.Critical(err.Error())
}
sigBytes := sc.FixedSequenceU8ToBytes(sigEd25519.FixedSequence)
return uxt.crypto.Ed25519Verify(sigBytes, msgBytes, signerBytes)
} else if signature.IsSr25519() {

sigSr25519, err := signature.AsSr25519()
if err != nil {
// TODO: return err

Check warning on line 151 in execution/types/unchecked_extrinsic.go

View check run for this annotation

Codecov / codecov/patch

execution/types/unchecked_extrinsic.go#L151

Added line #L151 was not covered by tests
log.Critical(err.Error())
}
sigBytes := sc.FixedSequenceU8ToBytes(sigSr25519.FixedSequence)
return uxt.crypto.Sr25519Verify(sigBytes, msgBytes, signerBytes)
} else if signature.IsEcdsa() {
return true
// TODO:
// let m = sp_io::hashing::blake2_256(msg.get());
// match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) {
// Ok(pubkey) =>
// &sp_io::hashing::blake2_256(pubkey.as_ref()) ==
// <dyn AsRef<[u8; 32]>>::as_ref(who),
// _ => false,
// }
sigEcdsa, err := signature.AsEcdsa()
if err != nil {
// TODO: return err
log.Critical(err.Error())
}

Check warning on line 161 in execution/types/unchecked_extrinsic.go

View check run for this annotation

Codecov / codecov/patch

execution/types/unchecked_extrinsic.go#L159-L161

Added lines #L159 - L161 were not covered by tests

return uxt.verifyEcdsa(sigEcdsa, msgBytes, signerBytes)
}

log.Critical("invalid MultiSignature type in Verify")
Expand All @@ -177,3 +178,28 @@
return sc.BytesToSequenceU8(enc)
}
}

func (uxt uncheckedExtrinsic) verifyEcdsa(signature primitives.SignatureEcdsa, msgBytes []byte, signer []byte) bool {
sigBytes := sc.FixedSequenceU8ToBytes(signature.FixedSequence)
msg := uxt.hashing.Blake256(msgBytes)

// This returns either the 33-byte ECDSA Public Key or an error.
recovered := uxt.crypto.EcdsaRecoverCompressed(sigBytes, msg)
buffer := bytes.NewBuffer(recovered)

result, err := sc.DecodeResult(buffer, primitives.DecodeEcdsaPublicKey, primitives.DecodeEcdsaVerifyError)
if err != nil {
// TODO: return err
log.Critical(err.Error())
}

if result.HasError {
log.Debug(fmt.Sprintf("Failed to verify signature. Error: [%s]", result.Value.(error).Error()))
return false
}

// In order to match AccountId, ECDSA public keys are hashed to 32 bytes.
hashPublicKey := uxt.hashing.Blake256(result.Value.Bytes())

return reflect.DeepEqual(hashPublicKey, signer)
}
108 changes: 105 additions & 3 deletions execution/types/unchecked_extrinsic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,27 @@ var (
types.NewInvalidTransactionBadProof(),
)

signerAddressBytes = []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
signerAddressBytes = make([]byte, 32)
signer25519Address, _ = types.NewEd25519PublicKey(sc.BytesToSequenceU8(signerAddressBytes)...)
signerAccountId = types.NewAccountId[types.PublicKey](signer25519Address)
signer = types.NewMultiAddressId(signerAccountId)

ecdsaAddressBytes = make([]byte, 33)
ecdsaPublicKey, _ = types.NewEcdsaPublicKey(sc.BytesToSequenceU8(ecdsaAddressBytes)...)

signatureBytes = []byte{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
}
ecdsaSignatureBytes = []byte{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
}

signatureEd25519 = types.NewMultiSignatureEd25519(
types.NewSignatureEd25519(
sc.BytesToFixedSequenceU8(signatureBytes)...,
Expand All @@ -51,6 +59,12 @@ var (
),
)

signatureEcdsa = types.NewMultiSignatureEcdsa(
types.NewSignatureEcdsa(
sc.BytesToFixedSequenceU8(ecdsaSignatureBytes)...,
),
)

unknownMultisignature = types.MultiSignature{
VaryingData: sc.NewVaryingData(sc.U8(3), signatureEd25519),
}
Expand Down Expand Up @@ -334,6 +348,94 @@ func Test_Check_SignedUncheckedExtrinsic_Success_Sr25519(t *testing.T) {
mockCrypto.AssertCalled(t, "Sr25519Verify", signatureBytes, encodedPayloadBytes, signerAddressBytes)
}

func Test_SignedUncheckedExtrinsic_Check_Ecdsa_Success(t *testing.T) {
setup(signatureEcdsa)
recoverResult := sc.Result[sc.Encodable]{
HasError: false,
Value: ecdsaPublicKey,
}
expect := NewCheckedExtrinsic(sc.NewOption[types.AccountId[types.PublicKey]](signerAccountId), mockCall, mockSignedExtra).(checkedExtrinsic)

mocksSignedPayload.On("Bytes").Return(encodedPayloadBytes)
mockHashing.On("Blake256", encodedPayloadBytes).Return(encodedPayloadBytes)
mockCrypto.On("EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes).Return(recoverResult.Bytes())
mockHashing.On("Blake256", ecdsaAddressBytes).Return(signerAddressBytes)

result, err := targetSigned.Check()

assert.Nil(t, err)
checked := result.(checkedExtrinsic)
assert.Equal(t, expect.extra, checked.extra)
assert.Equal(t, expect.signer, checked.signer)
assert.Equal(t, expect.function, checked.function)

mocksSignedPayload.AssertCalled(t, "Bytes")
mockHashing.AssertCalled(t, "Blake256", encodedPayloadBytes)
mockCrypto.AssertCalled(t, "EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes)
mockHashing.AssertCalled(t, "Blake256", ecdsaAddressBytes)
}

func Test_SignedUncheckedExtrinsic_Check_Ecdsa_BadProof_MismatchingAddresses(t *testing.T) {
setup(signatureEcdsa)
recoverResult := sc.Result[sc.Encodable]{
HasError: false,
Value: ecdsaPublicKey,
}

mocksSignedPayload.On("Bytes").Return(encodedPayloadBytes)
mockHashing.On("Blake256", encodedPayloadBytes).Return(encodedPayloadBytes)
mockCrypto.On("EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes).Return(recoverResult.Bytes())
mockHashing.On("Blake256", ecdsaAddressBytes).Return(ecdsaAddressBytes) // Set invalid address

result, err := targetSigned.Check()

assert.Nil(t, result)
assert.Equal(t, invalidTransactionBadProofError, err)

mocksSignedPayload.AssertCalled(t, "Bytes")
mockHashing.AssertCalled(t, "Blake256", encodedPayloadBytes)
mockCrypto.AssertCalled(t, "EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes)
mockHashing.AssertCalled(t, "Blake256", ecdsaAddressBytes)
}

func Test_SignedUncheckedExtrinsic_Check_Ecdsa_BadProof_BadSignature(t *testing.T) {
setup(signatureEcdsa)
recoverResult := sc.Result[sc.Encodable]{
HasError: true,
Value: types.NewEcdsaVerifyErrorBadSignature(),
}

mocksSignedPayload.On("Bytes").Return(encodedPayloadBytes)
mockHashing.On("Blake256", encodedPayloadBytes).Return(encodedPayloadBytes)
mockCrypto.On("EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes).Return(recoverResult.Bytes())

result, err := targetSigned.Check()

assert.Nil(t, result)
assert.Equal(t, invalidTransactionBadProofError, err)

mocksSignedPayload.AssertCalled(t, "Bytes")
mockHashing.AssertCalled(t, "Blake256", encodedPayloadBytes)
mockCrypto.AssertCalled(t, "EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes)
}

func Test_SignedUncheckedExtrinsic_Check_Ecdsa_Panics_InvalidResultBytes(t *testing.T) {
setup(signatureEcdsa)
recoverResult := sc.U8(5)

mocksSignedPayload.On("Bytes").Return(encodedPayloadBytes)
mockHashing.On("Blake256", encodedPayloadBytes).Return(encodedPayloadBytes)
mockCrypto.On("EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes).Return(recoverResult.Bytes())

assert.PanicsWithValue(t, "invalid bool representation", func() {
targetSigned.Check()
})

mocksSignedPayload.AssertCalled(t, "Bytes")
mockHashing.AssertCalled(t, "Blake256", encodedPayloadBytes)
mockCrypto.AssertCalled(t, "EcdsaRecoverCompressed", ecdsaSignatureBytes, encodedPayloadBytes)
}

func Test_Check_SignedUncheckedExtrinsic_UnknownSignatureType(t *testing.T) {
setup(unknownMultisignature)

Expand Down
5 changes: 5 additions & 0 deletions mocks/io_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ func (m *IoCrypto) EcdsaGenerate(keyTypeId []byte, seed []byte) []byte {
return args.Get(0).([]byte)
}

func (m *IoCrypto) EcdsaRecoverCompressed(signature []byte, msg []byte) []byte {
args := m.Called(signature, msg)
return args.Get(0).([]byte)
}

func (m *IoCrypto) Ed25519Generate(keyTypeId []byte, seed []byte) []byte {
args := m.Called(keyTypeId, seed)

Expand Down
14 changes: 14 additions & 0 deletions primitives/io/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

type Crypto interface {
EcdsaGenerate(keyTypeId []byte, seed []byte) []byte
EcdsaRecoverCompressed(signature []byte, msg []byte) []byte

Ed25519Generate(keyTypeId []byte, seed []byte) []byte
Ed25519Verify(signature []byte, message []byte, pubKey []byte) bool
Expand All @@ -32,6 +33,19 @@
//return c.memoryTranslator.ToWasmMemorySlice(r, 32)
}

func (c crypto) EcdsaRecoverCompressed(signature []byte, msg []byte) []byte {
sigOffsetSize := c.memoryTranslator.BytesToOffsetAndSize(signature)
sigOffset, _ := c.memoryTranslator.Int64ToOffsetAndSize(sigOffsetSize) // signature: 65-byte

msgOffsetSize := c.memoryTranslator.BytesToOffsetAndSize(msg)
msgOffset, _ := c.memoryTranslator.Int64ToOffsetAndSize(msgOffsetSize) // message: 32-byte

r := env.ExtCryptoSecp256k1EcdsaRecoverCompressedVersion2(sigOffset, msgOffset)
offset, size := c.memoryTranslator.Int64ToOffsetAndSize(r)

return c.memoryTranslator.GetWasmMemorySlice(offset, size)

Check warning on line 46 in primitives/io/crypto.go

View check run for this annotation

Codecov / codecov/patch

primitives/io/crypto.go#L36-L46

Added lines #L36 - L46 were not covered by tests
}

func (c crypto) Ed25519Generate(keyTypeId []byte, seed []byte) []byte {
r := env.ExtCryptoEd25519GenerateVersion1(
c.memoryTranslator.Offset32(keyTypeId),
Expand Down
73 changes: 73 additions & 0 deletions primitives/types/ecdsa_verify_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package types

import (
"bytes"

sc "github.com/LimeChain/goscale"
)

const (
EcdsaVerifyErrorBadRS sc.U8 = iota
EcdsaVerifyErrorBadV
EcdsaVerifyErrorBadSignature
)

func NewEcdsaVerifyErrorBadRS() EcdsaVerifyError {
return EcdsaVerifyError(sc.NewVaryingData(EcdsaVerifyErrorBadRS))
}

func NewEcdsaVerifyErrorBadV() EcdsaVerifyError {
return EcdsaVerifyError(sc.NewVaryingData(EcdsaVerifyErrorBadV))
}

func NewEcdsaVerifyErrorBadSignature() EcdsaVerifyError {
return EcdsaVerifyError(sc.NewVaryingData(EcdsaVerifyErrorBadSignature))
}

type EcdsaVerifyError sc.VaryingData

func (err EcdsaVerifyError) Encode(buffer *bytes.Buffer) error {
if len(err) == 0 {
return newTypeError("EcdsaVerifyError")
}
return err[0].Encode(buffer)
}

func DecodeEcdsaVerifyError(buffer *bytes.Buffer) (EcdsaVerifyError, error) {
b, err := sc.DecodeU8(buffer)
if err != nil {
return nil, err
}

switch b {
case EcdsaVerifyErrorBadRS:
return NewEcdsaVerifyErrorBadRS(), nil
case EcdsaVerifyErrorBadV:
return NewEcdsaVerifyErrorBadV(), nil
case EcdsaVerifyErrorBadSignature:
return NewEcdsaVerifyErrorBadSignature(), nil
default:
return nil, newTypeError("EcdsaVerifyError")
}
}

func (err EcdsaVerifyError) Bytes() []byte {
return sc.EncodedBytes(err)
}

func (err EcdsaVerifyError) Error() string {
if len(err) == 0 {
return newTypeError("EcdsaVerifyError").Error()
}

switch err[0] {
case EcdsaVerifyErrorBadRS:
return "Bad RS"
case EcdsaVerifyErrorBadV:
return "Bad V"
case EcdsaVerifyErrorBadSignature:
return "Bad signature"
default:
return newTypeError("EcdsaVerifyError").Error()
}
}
Loading