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(wire): add ecdsa authentication to wire #51

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
4 changes: 3 additions & 1 deletion wallet/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ func (a *Address) Equal(addr wallet.Address) bool {
}

// Cmp checks ordering of two addresses.
// 0 if a==b,
//
// 0 if a==b,
//
// -1 if a < b,
// +1 if a > b.
// https://godoc.org/bytes#Compare
Expand Down
37 changes: 33 additions & 4 deletions wire/account.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 - See NOTICE file for copyright holders.
// Copyright 2024 - See NOTICE file for copyright holders.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,19 +15,40 @@
package wire

import (
"crypto/ecdsa"
"math/rand"

"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/perun-network/perun-eth-backend/wallet"
"github.com/pkg/errors"
"perun.network/go-perun/wire"
)

// SigLen length of a signature in byte.
// ref https://godoc.org/github.com/ethereum/go-ethereum/crypto/secp256k1#Sign
// ref https://github.com/ethereum/go-ethereum/blob/54b271a86dd748f3b0bcebeaf678dc34e0d6177a/crypto/signature_cgo.go#L66
const SigLen = 65

// sigVSubtract value that is subtracted from the last byte of a signature if
// the last bytes exceeds it.
const sigVSubtract = 27

// Account is a wire account.
type Account struct {
addr *Address
key *ecdsa.PrivateKey
}

// Sign signs the given message with the account's private key.
func (acc *Account) Sign(_ []byte) ([]byte, error) {
return []byte("Authenticate"), nil
func (acc *Account) Sign(data []byte) ([]byte, error) {
hash := PrefixedHash(data)
sig, err := crypto.Sign(hash, acc.key)
if err != nil {
return nil, errors.Wrap(err, "SignHash")
}
sig[64] += 27
return sig, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wenn wir schon sigVSubtract als konstante haben sollten wir die hier auch verwenden. Evtl. ist sigVOffset ein passenderer Name.

}

// Address returns the account's address.
Expand All @@ -37,7 +58,15 @@ func (acc *Account) Address() wire.Address {

// NewRandomAccount generates a new random account.
func NewRandomAccount(rng *rand.Rand) *Account {
privateKey, err := ecdsa.GenerateKey(secp256k1.S256(), rng)
if err != nil {
panic(err)
}

addr := crypto.PubkeyToAddress(privateKey.PublicKey)

return &Account{
addr: NewRandomAddress(rng),
addr: &Address{wallet.AsWalletAddr(addr)},
key: privateKey,
}
}
32 changes: 26 additions & 6 deletions wire/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
package wire

import (
"bytes"
"errors"
"math/rand"

"github.com/ethereum/go-ethereum/crypto"
"github.com/perun-network/perun-eth-backend/wallet"
"github.com/perun-network/perun-eth-backend/wallet/test"
"github.com/pkg/errors"

"perun.network/go-perun/wire"
)
Expand All @@ -32,7 +32,7 @@ type Address struct {

// NewAddress returns a new address.
func NewAddress() *Address {
return &Address{}
return &Address{Address: &wallet.Address{}}
}

// Equal returns whether the two addresses are equal.
Expand All @@ -41,6 +41,7 @@ func (a Address) Equal(b wire.Address) bool {
if !ok {
panic("wrong type")
}

return a.Address.Equal(bTyped.Address)
}

Expand All @@ -62,9 +63,28 @@ func NewRandomAddress(rng *rand.Rand) *Address {

// Verify verifies a message signature.
// It returns an error if the signature is invalid.
func (a Address) Verify(_ []byte, sig []byte) error {
if !bytes.Equal(sig, []byte("Authenticate")) {
return errors.New("invalid signature")
func (a Address) Verify(msg []byte, sig []byte) error {
hash := PrefixedHash(msg)
sigCopy := make([]byte, SigLen)
copy(sigCopy, sig)
if len(sigCopy) == SigLen && (sigCopy[SigLen-1] >= sigVSubtract) {
Copy link
Contributor

@DragonDev1906 DragonDev1906 Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that we don't compare sig but we compare the length after we have copied? I don't think it's a problem, but it might be better if we have an early length check and return an error immediately if sig is not of length 65. Could also result in a nicer error message if the length doesn't match.

I think this implementation allows 65 byte signatures with arbitrary bytes after it, not sure if that's a problem.

From the copy() docs: minimum of len(src) and len(dst) (https://pkg.go.dev/builtin#copy)

For example, the following would be seen as valid by this function:

  • <65-bytes-valid-signature>
  • <65-bytes-valid-signature>

As said: I don't think it's problematic, but I think it would be cleaner if this function would only allow signatures of exactly 65 bytes.

sigCopy[SigLen-1] -= sigVSubtract
}
pk, err := crypto.SigToPub(hash, sigCopy)
if err != nil {
return errors.WithStack(err)
}
addr := crypto.PubkeyToAddress(*pk)
if !a.Equal(&Address{wallet.AsWalletAddr(addr)}) {
return errors.New("signature verification failed")
}
return nil
}

// PrefixedHash adds an ethereum specific prefix to the hash of given data, rehashes the results
// and returns it.
func PrefixedHash(data []byte) []byte {
hash := crypto.Keccak256(data)
prefix := []byte("\x19Ethereum Signed Message:\n32")
return crypto.Keccak256(prefix, hash)
}
19 changes: 19 additions & 0 deletions wire/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2024 - See NOTICE file for copyright holders.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package wire contains a simplistic implementation of the perun wire's
// account, and address interfaces.
// An account can be instantiated directly with a random secret key.
// The account and address offer Handshake Authentication through Go-Perun Wire.
package wire // import "github.com/perun-network/perun-eth-backend/wire"
46 changes: 46 additions & 0 deletions wire/wire_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 - See NOTICE file for copyright holders.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package wire_test

import (
"math/rand"
"testing"

"github.com/perun-network/perun-eth-backend/wire"
"github.com/stretchr/testify/assert"
perunwire "perun.network/go-perun/wire"
"perun.network/go-perun/wire/test"
pkgtest "polycry.pt/poly-go/test"
)

var dataToSign = []byte("SomeLongDataThatShouldBeSignedPlease")

func TestAddress(t *testing.T) {
test.TestAddressImplementation(t, func() perunwire.Address {
return wire.NewAddress()
}, func(rng *rand.Rand) perunwire.Address {
return wire.NewRandomAddress(rng)
})
}

func TestSignatures(t *testing.T) {
acc := wire.NewRandomAccount(pkgtest.Prng(t))
sig, err := acc.Sign(dataToSign)
assert.NoError(t, err, "Sign with new account should succeed")
assert.NotNil(t, sig)
assert.Equal(t, len(sig), wire.SigLen, "Ethereum signature has wrong length")
err = acc.Address().Verify(dataToSign, sig)
assert.NoError(t, err, "Verification should succeed")
DragonDev1906 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading