Skip to content
This repository has been archived by the owner on Jan 3, 2023. It is now read-only.

Mir crypto #208

Merged
merged 18 commits into from
Jun 21, 2022
Merged
2 changes: 1 addition & 1 deletion api/api_net.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"time"

metrics "github.com/libp2p/go-libp2p-core/metrics"
"github.com/libp2p/go-libp2p-core/metrics"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/protocol"
Expand Down
2 changes: 1 addition & 1 deletion api/proxy_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 42 additions & 9 deletions chain/consensus/mir/app.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,64 @@
package mir

import (
"fmt"

"github.com/filecoin-project/mir/pkg/events"
"github.com/filecoin-project/mir/pkg/modules"
"github.com/filecoin-project/mir/pkg/pb/eventpb"
"github.com/filecoin-project/mir/pkg/pb/requestpb"
t "github.com/filecoin-project/mir/pkg/types"
)

type Tx []byte

type Application struct {
reqStore modules.RequestStore
ChainNotify chan []Tx
}

func NewApplication(reqStore modules.RequestStore) *Application {
func NewApplication() *Application {
app := Application{
reqStore: reqStore,
ChainNotify: make(chan []Tx),
}
return &app
}

func (app *Application) Apply(batch *requestpb.Batch) error {
var block []Tx
func (app *Application) ApplyEvents(eventsIn *events.EventList) (*events.EventList, error) {
return modules.ApplyEventsSequentially(eventsIn, app.ApplyEvent)
}

for _, reqRef := range batch.Requests {
msg, err := app.reqStore.GetRequest(reqRef)
func (app *Application) ApplyEvent(event *eventpb.Event) (*events.EventList, error) {
switch e := event.Type.(type) {
case *eventpb.Event_Deliver:
if err := app.ApplyBatch(e.Deliver.Batch); err != nil {
return nil, fmt.Errorf("app batch delivery error: %w", err)
}
case *eventpb.Event_AppSnapshotRequest:
data, err := app.Snapshot()
if err != nil {
return err
return nil, fmt.Errorf("app snapshot error: %w", err)
}
return (&events.EventList{}).PushBack(events.AppSnapshot(
t.ModuleID(e.AppSnapshotRequest.Module),
t.EpochNr(e.AppSnapshotRequest.Epoch),
data,
)), nil
case *eventpb.Event_AppRestoreState:
if err := app.RestoreState(e.AppRestoreState.Data); err != nil {
return nil, fmt.Errorf("app restore state error: %w", err)
}
block = append(block, msg)
default:
return nil, fmt.Errorf("unexpected type of App event: %T", event.Type)
}

return &events.EventList{}, nil
}

func (app *Application) ApplyBatch(batch *requestpb.Batch) error {
var block []Tx

for _, req := range batch.Requests {
block = append(block, req.Req.Data)
}

app.ChainNotify <- block
Expand All @@ -51,3 +81,6 @@ func (app *Application) Snapshot() ([]byte, error) {
func (app *Application) RestoreState(snapshot []byte) error {
return nil
}

// The ImplementsModule method only serves the purpose of indicating that this is a Module and must not be called.
func (app *Application) ImplementsModule() {}
92 changes: 92 additions & 0 deletions chain/consensus/mir/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package mir

import (
"context"
"crypto/sha256"
"strings"

"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
filcrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/lib/sigs"
mircrypto "github.com/filecoin-project/mir/pkg/crypto"
t "github.com/filecoin-project/mir/pkg/types"
)

var MsgMeta = api.MsgMeta{Type: "mir-message"}

type WalletCrypto interface {
WalletSign(ctx context.Context, k address.Address, msg []byte) (*filcrypto.Signature, error)
WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *filcrypto.Signature) (bool, error)
}

var _ mircrypto.Crypto = &CryptoManager{}

type CryptoManager struct {
key address.Address // The address corresponding to the private key.
api WalletCrypto // API used to sign data in HSM-model.
}

func NewCryptoManager(key address.Address, wallet WalletCrypto) (*CryptoManager, error) {
if key.Protocol() != address.SECP256K1 {
return nil, xerrors.New("must be SECP address")
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
}
return &CryptoManager{key, wallet}, nil
}

// Sign signs the provided data and returns the resulting signature.
// The data to be signed is the concatenation of all the passed byte slices.
// A signature produced by Sign is verifiable using Verify,
// if, respectively, RegisterNodeKey or RegisterClientKey has been invoked with the corresponding public key.
// Note that the private key used to produce the signature cannot be set ("registered") through this interface.
// Storing and using the private key is completely implementation-dependent.
func (c *CryptoManager) Sign(data [][]byte) ([]byte, error) {
signature, err := c.api.WalletSign(context.Background(), c.key, hash(data))
if err != nil {
return nil, err
}
return signature.MarshalBinary()
}

// Verify verifies a signature produced by the node with ID nodeID over data.
// Returns nil on success (i.e., if the given signature is valid) and a non-nil error otherwise.
// Note that RegisterNodeKey must be used to register the node's public key before calling Verify,
// otherwise Verify will fail.
func (c *CryptoManager) Verify(data [][]byte, sigBytes []byte, nodeID t.NodeID) error {
nodeAddr, err := getAddr(nodeID.Pb())
if err != nil {
return err
}
return c.verifySig(data, sigBytes, nodeAddr)
}

func (c *CryptoManager) verifySig(data [][]byte, sigBytes []byte, addr address.Address) error {
var sig filcrypto.Signature
if err := sig.UnmarshalBinary(sigBytes); err != nil {
return err
}

return sigs.Verify(&sig, addr, hash(data))
}

func hash(data [][]byte) []byte {
h := sha256.New()
for _, d := range data {
h.Write(d)
}
return h.Sum(nil)
}

func getAddr(nodeID string) (address.Address, error) {
addrParts := strings.Split(nodeID, ":")
if len(addrParts) != 2 {
return address.Undef, xerrors.Errorf("invalid node ID: %s", nodeID)
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
}
nodeAddr, err := address.NewFromString(addrParts[1])
if err != nil {
return address.Undef, err
}
return nodeAddr, nil
}
81 changes: 81 additions & 0 deletions chain/consensus/mir/crypto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package mir

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
filcrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/lib/sigs"
mirTypes "github.com/filecoin-project/mir/pkg/types"
)

type cryptoNode struct {
api *wallet.LocalWallet
key address.Address
}

func newCryptoNode() (*cryptoNode, error) {
w, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
return nil, err
}

addr, err := w.WalletNew(context.Background(), types.KTSecp256k1)
if err != nil {
return nil, err
}
return &cryptoNode{w, addr}, nil
}

func (n *cryptoNode) WalletSign(ctx context.Context, k address.Address, msg []byte) (signature *filcrypto.Signature, err error) {
if k.Protocol() != address.SECP256K1 {
return nil, xerrors.New("must be SECP address")
}
if k != n.key {
return nil, xerrors.New("wrong address")
}
signature, err = n.api.WalletSign(ctx, k, msg, MsgMeta)
return
}

func (n *cryptoNode) WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *filcrypto.Signature) (bool, error) {
err := sigs.Verify(sig, k, msg)
return err == nil, err
}

func TestCryptoManager(t *testing.T) {
node, err := newCryptoNode()
require.NoError(t, err)

addr := node.key
c, err := NewCryptoManager(addr, node)
require.NoError(t, err)

data := [][]byte{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}}
sigBytes, err := c.Sign(data)
require.NoError(t, err)

nodeID := mirTypes.NodeID(newMirID("/root", addr.String()))
err = c.Verify([][]byte{{1, 2, 3}}, sigBytes, nodeID)
require.Error(t, err)

err = c.Verify([][]byte{{1, 2, 3}}, sigBytes, nodeID)
require.Error(t, err)

err = c.Verify(data, []byte{1, 2, 3}, nodeID)
require.Error(t, err)

nodeID = mirTypes.NodeID(addr.String())
err = c.Verify(data, sigBytes, nodeID)
require.Error(t, err)

nodeID = mirTypes.NodeID(newMirID("/root:", addr.String()))
err = c.Verify(data, sigBytes, nodeID)
require.Error(t, err)
}
Loading