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

Check block integrity when necessary #1764

Merged
merged 8 commits into from
Mar 21, 2019
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
103 changes: 93 additions & 10 deletions byzcoin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ package byzcoin
import (
"bytes"
"errors"
"fmt"
"math"
"time"

"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/sign/schnorr"

"go.dedis.ch/cothority/v3"
"go.dedis.ch/cothority/v3/darc"
"go.dedis.ch/cothority/v3/darc/expression"
"go.dedis.ch/cothority/v3/skipchain"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/sign/schnorr"
"go.dedis.ch/onet/v3"
"go.dedis.ch/onet/v3/log"
"go.dedis.ch/onet/v3/network"
Expand Down Expand Up @@ -56,14 +56,30 @@ func NewLedger(msg *CreateGenesisBlock, keep bool) (*Client, *CreateGenesisBlock
} else {
c = NewClient(nil, msg.Roster)
}
reply := &CreateGenesisBlockResponse{}
if err := c.SendProtobuf(msg.Roster.List[0], msg, reply); err != nil {

reply, err := newLedgerWithClient(msg, c)
if err != nil {
return nil, nil, err
}
c.ID = reply.Skipblock.CalculateHash()

c.ID = reply.Skipblock.Hash
return c, reply, nil
}

func newLedgerWithClient(msg *CreateGenesisBlock, c *Client) (*CreateGenesisBlockResponse, error) {
reply := &CreateGenesisBlockResponse{}
if err := c.SendProtobuf(msg.Roster.List[0], msg, reply); err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's easier to just test verifyGenesisBlock by itself. then you don't need to insert test code into production code.

Copy link
Contributor

Choose a reason for hiding this comment

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

another possibility: can we register a corrupted byzcoin service that always just returns a wrong genesis block (e.g., embed byzcoin.Service inside byzcoin.BadService and overwrite the create genesis block function), it doesn't even have to do consensus or anything like that, just returns the bad block? just a few ideas to think about

Copy link
Contributor Author

@Gilthoniel Gilthoniel Mar 20, 2019

Choose a reason for hiding this comment

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

Yes for the verifyGenesisBlock but I really want to make sure the request is checking this, and that no one removed this check accidentally.

another posibility...
I tried that first, but the service name is a constant (which is fine) so you can't change which service is going to be used, or am I missing something ? I would love to do it that way..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind, I found a way to go around without too many complex mocking


// checks if the returned genesis block has the same parameters
if err := verifyGenesisBlock(reply.Skipblock, msg); err != nil {
return nil, err
}

return reply, nil
}

// AddTransaction adds a transaction. It does not return any feedback
// on the transaction. Use GetProof to find out if the transaction
// was committed. The Client's Roster and ID should be initialized before
Expand Down Expand Up @@ -91,8 +107,8 @@ func (c *Client) AddTransactionAndWait(tx ClientTransaction, wait int) (*AddTxRe
}

// GetProof returns a proof for the key stored in the skipchain by sending a
// message to the node on index 0 of the roster. The proof can be verified with
// the genesis skipblock and can prove the existence or the absence of the key.
// message to the node on index 0 of the roster. The proof can prove the existence
// or the absence of the key. Note that the integrity of the proof is verified.
// The Client's Roster and ID should be initialized before calling this method
// (see NewClientFromConfig).
func (c *Client) GetProof(key []byte) (*GetProofResponse, error) {
Expand All @@ -105,6 +121,13 @@ func (c *Client) GetProof(key []byte) (*GetProofResponse, error) {
if err != nil {
return nil, err
}

// verify the integrity of the proof only
err = reply.Proof.Verify(c.ID)
if err != nil {
return nil, err
}

return reply, nil
}

Expand Down Expand Up @@ -251,13 +274,14 @@ func (c *Client) WaitProof(id InstanceID, interval time.Duration, value []byte)
// StreamTransactions sends a streaming request to the service. If successful,
// the handler will be called whenever a new response (a new block) is
// available. This function blocks, the streaming stops if the client or the
// service stops.
// service stops. Only the integrity of the new block is verified.
func (c *Client) StreamTransactions(handler func(StreamingResponse, error)) error {
req := StreamingRequest{
ID: c.ID,
}
conn, err := c.Stream(c.Roster.List[0], &req)
if err != nil {
handler(StreamingResponse{}, err)
return err
}
for {
Expand All @@ -266,7 +290,15 @@ func (c *Client) StreamTransactions(handler func(StreamingResponse, error)) erro
handler(StreamingResponse{}, err)
return nil
}
handler(resp, nil)

if resp.Block.CalculateHash().Equal(resp.Block.Hash) {
// send the block only if the integrity is correct
handler(resp, nil)
} else {
err := fmt.Errorf("got a corrupted block from %v", c.Roster.List[0])
log.Warn(err.Error())
handler(StreamingResponse{}, err)
}
}
}

Expand Down Expand Up @@ -417,3 +449,54 @@ func DefaultGenesisMsg(v Version, r *onet.Roster, rules []string, ids ...darc.Id
}
return &m, nil
}

func verifyGenesisBlock(actual *skipchain.SkipBlock, expected *CreateGenesisBlock) error {
if !actual.CalculateHash().Equal(actual.Hash) {
return errors.New("got a corrupted block")
}

// check the block is like the proposal
ok, err := actual.Roster.Equal(&expected.Roster)
if err != nil {
return err
}
if !ok {
return errors.New("wrong roster in genesis block")
}

darcID, err := extractDarcID(actual)
if err != nil {
return err
}

if !darcID.Equal(expected.GenesisDarc.GetID()) {
return errors.New("wrong darc spawned")
}

return nil
}

func extractDarcID(sb *skipchain.SkipBlock) (darc.ID, error) {
var data DataBody
err := protobuf.Decode(sb.Payload, &data)
if err != nil {
return nil, fmt.Errorf("fail to decode data: %v", err)
}

if len(data.TxResults) != 1 || len(data.TxResults[0].ClientTransaction.Instructions) != 1 {
return nil, errors.New("genesis darc tx should only have one instruction")
}

instr := data.TxResults[0].ClientTransaction.Instructions[0]
if instr.Spawn == nil {
return nil, errors.New("didn't get a spawn instruction")
}

var darc darc.Darc
err = protobuf.Decode(instr.Spawn.Args.Search("darc"), &darc)
if err != nil {
return nil, fmt.Errorf("fail to decode the darc: %v", err)
}

return darc.GetID(), nil
}
150 changes: 150 additions & 0 deletions byzcoin/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,98 @@ import (
"github.com/stretchr/testify/require"
"go.dedis.ch/cothority/v3"
"go.dedis.ch/cothority/v3/darc"
"go.dedis.ch/cothority/v3/skipchain"
"go.dedis.ch/onet/v3"
"go.dedis.ch/onet/v3/log"
"go.dedis.ch/onet/v3/network"
"go.dedis.ch/protobuf"
)

func init() {
// register a service for the test that will do nothing but reply with a chosen response
onet.RegisterNewServiceWithSuite(testServiceName, pairingSuite, newTestService)
}

func TestClient_NewLedgerCorrupted(t *testing.T) {
l := onet.NewTCPTest(cothority.Suite)
servers, roster, _ := l.GenTree(3, true)
defer l.CloseAll()

service := servers[0].Service(testServiceName).(*corruptedService)
signer := darc.NewSignerEd25519(nil, nil)
msg, err := DefaultGenesisMsg(CurrentVersion, roster, []string{"spawn:dummy"}, signer.Identity())
require.Nil(t, err)
c := &Client{
Client: onet.NewClient(cothority.Suite, testServiceName),
Roster: *roster,
}

sb := skipchain.NewSkipBlock()
service.CreateGenesisBlockResponse = &CreateGenesisBlockResponse{Skipblock: sb}

sb.Roster = &onet.Roster{ID: onet.RosterID{}}
sb.Hash = sb.CalculateHash()
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Equal(t, "wrong roster in genesis block", err.Error())

sb.Roster = roster
sb.Payload = []byte{1, 2, 3}
sb.Hash = sb.CalculateHash()
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Contains(t, err.Error(), "fail to decode data:")

sb.Payload = []byte{}
sb.Hash = sb.CalculateHash()
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Equal(t, "genesis darc tx should only have one instruction", err.Error())

data := &DataBody{
TxResults: []TxResult{
TxResult{ClientTransaction: ClientTransaction{Instructions: []Instruction{Instruction{}}}},
},
}
sb.Payload, err = protobuf.Encode(data)
sb.Hash = sb.CalculateHash()
require.NoError(t, err)
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Equal(t, "didn't get a spawn instruction", err.Error())

data.TxResults[0].ClientTransaction.Instructions[0].Spawn = &Spawn{
Args: []Argument{
Argument{
Name: "darc",
Value: []byte{1, 2, 3},
},
},
}
sb.Payload, err = protobuf.Encode(data)
sb.Hash = sb.CalculateHash()
require.NoError(t, err)
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Contains(t, err.Error(), "fail to decode the darc:")

darcBytes, _ := protobuf.Encode(&darc.Darc{})
data.TxResults[0].ClientTransaction.Instructions[0].Spawn = &Spawn{
Args: []Argument{
Argument{
Name: "darc",
Value: darcBytes,
},
},
}
sb.Payload, err = protobuf.Encode(data)
sb.Hash = sb.CalculateHash()
require.NoError(t, err)
_, err = newLedgerWithClient(msg, c)
require.Error(t, err)
require.Equal(t, "wrong darc spawned", err.Error())
}

func TestClient_GetProof(t *testing.T) {
l := onet.NewTCPTest(cothority.Suite)
servers, roster, _ := l.GenTree(3, true)
Expand Down Expand Up @@ -64,6 +150,29 @@ func TestClient_GetProof(t *testing.T) {
require.Equal(t, value, v0)
}

func TestClient_GetProofCorrupted(t *testing.T) {
l := onet.NewTCPTest(cothority.Suite)
servers, roster, _ := l.GenTree(3, true)
defer l.CloseAll()

service := servers[0].Service(testServiceName).(*corruptedService)

c := &Client{
Client: onet.NewClient(cothority.Suite, testServiceName),
Roster: *roster,
}

sb := skipchain.NewSkipBlock()
sb.Data = []byte{1, 2, 3}
service.GetProofResponse = &GetProofResponse{
Proof: Proof{Latest: *sb},
}

_, err := c.GetProof([]byte{})
require.Error(t, err)
require.Contains(t, err.Error(), "Error while decoding field")
}

// Create a streaming client and add blocks in the background. The client
// should receive valid blocks.
func TestClient_Streaming(t *testing.T) {
Expand Down Expand Up @@ -159,3 +268,44 @@ func TestClient_Streaming(t *testing.T) {
require.Nil(t, err)
}
}

const testServiceName = "TestByzCoin"

type corruptedService struct {
*Service

// corrupted replies
GetProofResponse *GetProofResponse
CreateGenesisBlockResponse *CreateGenesisBlockResponse
}

func newTestService(c *onet.Context) (onet.Service, error) {
s := &Service{
ServiceProcessor: onet.NewServiceProcessor(c),
contracts: make(map[string]ContractFn),
txBuffer: newTxBuffer(),
storage: &bcStorage{},
darcToSc: make(map[string]skipchain.SkipBlockID),
stateChangeCache: newStateChangeCache(),
stateChangeStorage: newStateChangeStorage(c),
heartbeatsTimeout: make(chan string, 1),
closeLeaderMonitorChan: make(chan bool, 1),
heartbeats: newHeartbeats(),
viewChangeMan: newViewChangeManager(),
streamingMan: streamingManager{},
closed: true,
}

cs := &corruptedService{Service: s}
err := s.RegisterHandlers(cs.GetProof, cs.CreateGenesisBlock)

return cs, err
}

func (cs *corruptedService) GetProof(req *GetProof) (resp *GetProofResponse, err error) {
return cs.GetProofResponse, nil
}

func (cs *corruptedService) CreateGenesisBlock(req *CreateGenesisBlock) (*CreateGenesisBlockResponse, error) {
return cs.CreateGenesisBlockResponse, nil
}
20 changes: 7 additions & 13 deletions byzcoin/bcadmin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -15,27 +16,23 @@ import (
"time"

"github.com/BurntSushi/toml"
"go.dedis.ch/cothority/v3/byzcoin/contracts"
"go.dedis.ch/kyber/v3/suites"
"go.dedis.ch/kyber/v3/util/encoding"
"go.dedis.ch/protobuf"

"github.com/qantik/qrgo"
"go.dedis.ch/cothority/v3"
"go.dedis.ch/cothority/v3/byzcoin"
"go.dedis.ch/cothority/v3/byzcoin/bcadmin/lib"
"go.dedis.ch/cothority/v3/byzcoin/contracts"
"go.dedis.ch/cothority/v3/darc"
"go.dedis.ch/cothority/v3/darc/expression"
"go.dedis.ch/cothority/v3/skipchain"
"go.dedis.ch/kyber/v3/suites"
"go.dedis.ch/kyber/v3/util/encoding"
"go.dedis.ch/kyber/v3/util/random"
"go.dedis.ch/onet/v3"
"go.dedis.ch/onet/v3/app"
"go.dedis.ch/onet/v3/cfgpath"
"go.dedis.ch/onet/v3/log"
"go.dedis.ch/onet/v3/network"

"encoding/json"

"github.com/qantik/qrgo"
"go.dedis.ch/protobuf"
"gopkg.in/urfave/cli.v1"
)

Expand Down Expand Up @@ -326,10 +323,7 @@ func create(c *cli.Context) error {
}
req.BlockInterval = interval

cl := onet.NewClient(cothority.Suite, byzcoin.ServiceName)

var resp byzcoin.CreateGenesisBlockResponse
err = cl.SendProtobuf(r.List[0], req, &resp)
_, resp, err := byzcoin.NewLedger(req, false)
if err != nil {
return err
}
Expand Down
Loading