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
124 changes: 124 additions & 0 deletions chain/consensus/mir/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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"
mir "github.com/filecoin-project/mir/pkg/modules"
t "github.com/filecoin-project/mir/pkg/types"
)

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

var _ mir.Crypto = &CryptoManager{}

type CryptoManager struct {
addr address.Address
wallet api.Wallet
}

func NewCryptoManager(addr address.Address, wallet api.Wallet) (*CryptoManager, error) {
if addr.Protocol() != address.SECP256K1 {
return nil, xerrors.New("must be SECP address")
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
}
return &CryptoManager{addr, 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 VerifyNodeSig or VerifyClientSig,
// 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) (bytes []byte, err error) {
signature, err := c.wallet.WalletSign(context.Background(), c.addr, hash(data), MsgMeta)
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
return signature.MarshalBinary()
}

// VerifyNodeSig verifies a signature produced by the node with numeric 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 VerifyNodeSig,
// otherwise VerifyNodeSig will fail.
func (c *CryptoManager) VerifyNodeSig(data [][]byte, signature []byte, nodeID t.NodeID) error {
nodeAddr, err := getAddr(nodeID.Pb())
if err != nil {
return err
}
return c.verifySig(data, signature, nodeAddr)
}

// RegisterNodeKey associates a public key with a numeric node ID.
// The representation of the key is implementation-dependent.
// Calls to VerifyNodeSig will fail until RegisterNodeKey is successfully called with the corresponding node ID.
// Returns nil on success, a non-nil error on failure.
func (c *CryptoManager) RegisterNodeKey(pubKey []byte, nodeID t.NodeID) error {
panic("not implemented")
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
}

// RegisterClientKey associates a public key with a numeric client ID.
// The representation of the key is implementation-dependent.
// Calls to VerifyClientSig will fail until RegisterClientKey is successfully called with the corresponding client ID.
// Returns nil on success, a non-nil error on failure.
func (c *CryptoManager) RegisterClientKey(pubKey []byte, clientID t.ClientID) error {
panic("not implemented")
}

// DeleteNodeKey removes the public key associated with nodeID from the module's state.
// Any subsequent call to VerifyNodeSig(..., nodeID) will fail.
func (c *CryptoManager) DeleteNodeKey(nodeID t.NodeID) {
panic("not implemented")
}

// DeleteClientKey removes the public key associated with clientID from the module's state.
// Any subsequent call to VerifyClientSig(..., clientID) will fail.
func (c *CryptoManager) DeleteClientKey(clientID t.ClientID) {
panic("not implemented")
}
matejpavlovic marked this conversation as resolved.
Show resolved Hide resolved

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

func (c *CryptoManager) verifySig(data [][]byte, signature []byte, addr address.Address) error {
var sig filcrypto.Signature
if err := sig.UnmarshalBinary(signature); 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
}
51 changes: 51 additions & 0 deletions chain/consensus/mir/crypto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package mir

import (
"context"
"testing"

"github.com/stretchr/testify/require"

"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/wallet"
mirTypes "github.com/filecoin-project/mir/pkg/types"
)

func TestMirCryptoManager(t *testing.T) {
ctx := context.Background()

w, err := wallet.NewWallet(wallet.NewMemKeyStore())
require.NoError(t, err)

addr, err := w.WalletNew(ctx, types.KTSecp256k1)
require.NoError(t, err)

c, err := NewCryptoManager(addr, w)
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.VerifyNodeSig([][]byte{{1, 2, 3}}, sigBytes, nodeID)
require.Error(t, err)

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

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

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

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

nodeID = mirTypes.NodeID(newMirID("/root:", addr.String()))
err = c.VerifyNodeSig(data, sigBytes, nodeID)
require.Error(t, err)
}
18 changes: 9 additions & 9 deletions chain/consensus/mir/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type Manager struct {
MirID string
Validators []hierarchical.Validator
EudicoNode v1api.FullNode
Cache *requestCache
Pool *requestPool
MirNode *mir.Node
Wal *simplewal.WAL
Net *grpctransport.GrpcTransport
Expand Down Expand Up @@ -109,7 +109,7 @@ func NewManager(ctx context.Context, addr address.Address, api v1api.FullNode) (
return nil, xerrors.New("empty validator set")
}

mirID := fmt.Sprintf("%s:%s", subnetID, addr)
mirID := newMirID(subnetID.String(), addr.String())

log.Debugf("Mir manager %v is being created", mirID)
defer log.Debugf("Mir manager %v has been created", mirID)
Expand Down Expand Up @@ -169,7 +169,7 @@ func NewManager(ctx context.Context, addr address.Address, api v1api.FullNode) (
NetName: netName,
Validators: validators,
EudicoNode: api,
Cache: newRequestCache(),
Pool: newRequestPool(),
MirID: mirID,
MirNode: node,
Wal: wal,
Expand Down Expand Up @@ -231,7 +231,7 @@ func (m *Manager) SubmitRequests(ctx context.Context, refs []*RequestRef) {
func (m *Manager) GetMessagesByHashes(blockRequestHashes []Tx) (msgs []*types.SignedMessage, crossMsgs []*types.Message) {
log.Infof("received a block with %d hashes", len(blockRequestHashes))
for _, h := range blockRequestHashes {
req, found := m.Cache.getDel(string(h))
req, found := m.Pool.getDel(string(h))
if !found {
log.Errorf("unable to find a request with %v hash", h)
continue
Expand All @@ -255,15 +255,15 @@ func (m *Manager) GetMessagesByHashes(blockRequestHashes []Tx) (msgs []*types.Si
func (m *Manager) AddSignedMessages(dst []*RequestRef, msgs []*types.SignedMessage) ([]*RequestRef, error) {
for _, msg := range msgs {
hash := msg.Cid()
clientID := fmt.Sprintf("%s:%s", m.SubnetID, msg.Message.From.String())
clientID := newMirID(m.SubnetID.String(), msg.Message.From.String())
nonce := msg.Message.Nonce
r := RequestRef{
ClientID: t.ClientID(clientID),
ReqNo: t.ReqNo(nonce),
Type: common.SignedMessageType,
Hash: hash.Bytes(),
}
alreadyExist := m.Cache.addIfNotExist(clientID, string(r.Hash), msg)
alreadyExist := m.Pool.addIfNotExist(clientID, string(r.Hash), msg)
if !alreadyExist {
log.Infof(">>>> added message (%s, %d) to cache: client ID %s", msg.Message.To, nonce, clientID)
dst = append(dst, &r)
Expand All @@ -283,15 +283,15 @@ func (m *Manager) AddCrossMessages(dst []*RequestRef, msgs []*types.UnverifiedCr
log.Error("unable to get subnet from message:", err)
continue
}
clientID := fmt.Sprintf("%s:%s", msn, msg.Message.From.String())
clientID := newMirID(msn.String(), msg.Message.From.String())
nonce := msg.Message.Nonce
r := RequestRef{
ClientID: t.ClientID(clientID),
ReqNo: t.ReqNo(nonce),
Type: common.CrossMessageType,
Hash: hash.Bytes(),
}
alreadyExist := m.Cache.addIfNotExist(clientID, string(r.Hash), msg)
alreadyExist := m.Pool.addIfNotExist(clientID, string(r.Hash), msg)
if !alreadyExist {
log.Infof(">>>> added cross-message (%s, %d) to cache: clientID %s", msg.Message.To, nonce, clientID)
dst = append(dst, &r)
Expand All @@ -303,5 +303,5 @@ func (m *Manager) AddCrossMessages(dst []*RequestRef, msgs []*types.UnverifiedCr

// GetRequest gets the request from the cache by the key.
func (m *Manager) GetRequest(h string) (Request, bool) {
return m.Cache.getRequest(h)
return m.Pool.getRequest(h)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
// It may happen that Eudico receives a hash from the block and the corresponding message is in the cache,
// but not not in the mempool.

// Request cache is the simplest temporal cache to store mapping between request hashes and client requests.
func newRequestCache() *requestCache {
return &requestCache{
// Request pool is the simplest temporal pool to store mapping between request hashes and client requests.
func newRequestPool() *requestPool {
return &requestPool{
cache: make(map[string]ClientRequest),
handledClients: make(map[string]bool),
}
Expand All @@ -25,15 +25,15 @@ type ClientRequest struct {
ClientID string
}

type requestCache struct {
type requestPool struct {
lk sync.Mutex

cache map[string]ClientRequest
handledClients map[string]bool
}

// addIfNotExist adds the request if key h doesn't exist .
func (c *requestCache) addIfNotExist(clientID, h string, r Request) (exist bool) {
func (c *requestPool) addIfNotExist(clientID, h string, r Request) (exist bool) {
c.lk.Lock()
defer c.lk.Unlock()

Expand All @@ -45,7 +45,7 @@ func (c *requestCache) addIfNotExist(clientID, h string, r Request) (exist bool)
}

// getDel gets the target request by the key h and deletes the keys.
func (c *requestCache) getDel(h string) (Request, bool) {
func (c *requestPool) getDel(h string) (Request, bool) {
c.lk.Lock()
defer c.lk.Unlock()

Expand All @@ -58,7 +58,7 @@ func (c *requestCache) getDel(h string) (Request, bool) {
}

// getRequest gets the request by the key.
func (c *requestCache) getRequest(h string) (Request, bool) {
func (c *requestPool) getRequest(h string) (Request, bool) {
c.lk.Lock()
defer c.lk.Unlock()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestMirRequestCache(t *testing.T) {
c := newRequestCache()
c := newRequestPool()

c.addIfNotExist("client1", "key1", 0)

Expand Down
10 changes: 9 additions & 1 deletion chain/consensus/mir/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package mir

import t "github.com/filecoin-project/mir/pkg/types"
import (
"fmt"

t "github.com/filecoin-project/mir/pkg/types"
)

type RequestType int

Expand All @@ -12,3 +16,7 @@ type RequestRef struct {
Type RequestType
Hash []byte
}

func newMirID(subnet, addr string) string {
return fmt.Sprintf("%s:%s", subnet, addr)
}