diff --git a/common/factoid/fullsignatureblock.go b/common/factoid/fullsignatureblock.go new file mode 100644 index 0000000000..4c9083494f --- /dev/null +++ b/common/factoid/fullsignatureblock.go @@ -0,0 +1,109 @@ +package factoid + +import ( + "github.com/FactomProject/factomd/common/interfaces" + "github.com/FactomProject/factomd/common/primitives" +) + +type FullSignatureBlock struct { + Signatures []interfaces.IFullSignature `json:"signatures"` +} + +var _ interfaces.IFullSignatureBlock = (*FullSignatureBlock)(nil) + +func (fsb *FullSignatureBlock) AddSignature(sig interfaces.IFullSignature) { + fsb.Signatures = append(fsb.Signatures, sig) +} + +func (fsb *FullSignatureBlock) GetSignature(index int) interfaces.IFullSignature { + if index < 0 || index >= len(fsb.Signatures) { + return nil + } + return fsb.Signatures[index] +} +func (fsb *FullSignatureBlock) GetSignatures() []interfaces.IFullSignature { + return fsb.Signatures +} +func (fsb *FullSignatureBlock) IsSameAs(other interfaces.IFullSignatureBlock) bool { + sigs := other.GetSignatures() + if len(fsb.Signatures) != len(sigs) { + return false + } + + for i := range fsb.Signatures { + if !fsb.Signatures[i].IsSameAs(sigs[i]) { + return false + } + } + return true +} + +func (fsb *FullSignatureBlock) JSONByte() ([]byte, error) { + return primitives.EncodeJSON(fsb) +} + +func (fsb *FullSignatureBlock) JSONString() (string, error) { + return primitives.EncodeJSONString(fsb) +} + +func (fsb FullSignatureBlock) MarshalBinary() ([]byte, error) { + sigs := fsb.GetSignatures() + buf := primitives.NewBuffer(nil) + buf.PushVarInt(uint64(len(sigs))) + for _, sig := range sigs { + err := buf.PushBinaryMarshallable(sig) + if err != nil { + return nil, err + } + } + return buf.DeepCopyBytes(), nil +} + +func (fsb *FullSignatureBlock) UnmarshalBinary(data []byte) error { + if _, err := fsb.UnmarshalBinaryData(data); err != nil { + return err + } + return nil +} + +func (fsb *FullSignatureBlock) UnmarshalBinaryData(data []byte) ([]byte, error) { + buf := primitives.NewBuffer(data) + + length, err := buf.PopVarInt() + if err != nil { + return nil, err + } + + fsb.Signatures = make([]interfaces.IFullSignature, length) + for i := uint64(0); i < length; i++ { + fsb.Signatures[i] = new(primitives.Signature) + if err := buf.PopBinaryMarshallable(fsb.Signatures[i]); err != nil { + return nil, err + } + } + + return buf.DeepCopyBytes(), nil +} + +func (fsb *FullSignatureBlock) String() string { + var out primitives.Buffer + + out.WriteString("Signature Block: \n") + for _, sig := range fsb.Signatures { + out.WriteString(" signature: ") + if txt, err := sig.CustomMarshalText(); err != nil { + out.WriteString(" ") + out.WriteString(err.Error()) + } else { + out.Write(txt) + } + out.WriteString("\n ") + } + + return out.String() +} + +func NewFullSignatureBlock() *FullSignatureBlock { + fsb := new(FullSignatureBlock) + return fsb +} diff --git a/common/factoid/fullsignatureblock_test.go b/common/factoid/fullsignatureblock_test.go new file mode 100644 index 0000000000..664064791e --- /dev/null +++ b/common/factoid/fullsignatureblock_test.go @@ -0,0 +1,50 @@ +package factoid_test + +import ( + "math/rand" + "testing" + + "github.com/FactomProject/factomd/common/factoid" + "github.com/FactomProject/factomd/common/interfaces" + "github.com/FactomProject/factomd/common/primitives" +) + +func testSig() interfaces.IFullSignature { + sig := new(primitives.Signature) + sig.Init() + rand.Read(sig.Pub[:]) + rand.Read(sig.Sig[:]) + return sig +} + +func TestFullSignatureBlock_AddGetSignature(t *testing.T) { + block := factoid.NewFullSignatureBlock() + other := factoid.NewFullSignatureBlock() + sigs := make([]interfaces.IFullSignature, 32) + for i := range sigs { + sigs[i] = testSig() + block.AddSignature(sigs[i]) + other.AddSignature(sigs[i]) + } + + if !block.IsSameAs(other) { + t.Errorf("two equal blocks did not match") + } + + allsigs := block.GetSignatures() + + if len(allsigs) != len(sigs) { + t.Fatalf("not enough sigs in block. got = %d, want = %d", len(allsigs), len(sigs)) + } + + for i := range sigs { + if !sigs[i].IsSameAs(block.GetSignature(i)) { + t.Errorf("Signature index mismatch. index = %d, got = %v, want = %v", i, block.GetSignature(i), sigs[i]) + } + + if !sigs[i].IsSameAs(allsigs[i]) { + t.Errorf("Signature mismatch. index = %d, got = %v, want = %v", i, block.GetSignature(i), sigs[i]) + } + } + +} diff --git a/common/interfaces/msgFactory.go b/common/interfaces/msgFactory.go index 404bb6c022..c0e2b7a9e7 100644 --- a/common/interfaces/msgFactory.go +++ b/common/interfaces/msgFactory.go @@ -14,3 +14,10 @@ type Signable interface { IsValid() bool // Signature already checked SetValid() // Mark as validated so we don't have to repeat. } + +type MultiSignable interface { + AddSignature(Signer) error + MarshalForSignature() ([]byte, error) + GetSignatures() []IFullSignature + VerifySignatures() ([]IFullSignature, error) +} diff --git a/common/interfaces/signature.go b/common/interfaces/signature.go index 9b081b2940..a8422e131c 100644 --- a/common/interfaces/signature.go +++ b/common/interfaces/signature.go @@ -69,6 +69,16 @@ type ISignatureBlock interface { IsSameAs(ISignatureBlock) bool } +type IFullSignatureBlock interface { + BinaryMarshallable + Printable + + AddSignature(sig IFullSignature) + GetSignature(int) IFullSignature + GetSignatures() []IFullSignature + IsSameAs(IFullSignatureBlock) bool +} + type ISignable interface { Sign(privateKey []byte) error MarshalBinarySig() ([]byte, error) diff --git a/common/messages/addServer.go b/common/messages/addServer.go index 87f4dff9d1..9cd783982d 100644 --- a/common/messages/addServer.go +++ b/common/messages/addServer.go @@ -6,11 +6,15 @@ package messages import ( "bytes" + "crypto/sha256" "encoding/binary" "fmt" "os" + "sort" + "sync" "github.com/FactomProject/factomd/common/constants" + "github.com/FactomProject/factomd/common/factoid" "github.com/FactomProject/factomd/common/interfaces" "github.com/FactomProject/factomd/common/primitives" @@ -27,11 +31,17 @@ type AddServerMsg struct { ServerChainID interfaces.IHash // ChainID of new server ServerType int // 0 = Federated, 1 = Audit - Signature interfaces.IFullSignature + // Unsorted list of unvalidated pubkey/sig pairs + Signatures interfaces.IFullSignatureBlock + + sigMtx sync.Mutex + sigCache bool // true if this set of Signatures has been verified + // List of validated pubkey/sig pairs, sorted by byte order + validSignatures []interfaces.IFullSignature } var _ interfaces.IMsg = (*AddServerMsg)(nil) -var _ interfaces.Signable = (*AddServerMsg)(nil) +var _ interfaces.MultiSignable = (*AddServerMsg)(nil) func (m *AddServerMsg) GetRepeatHash() (rval interfaces.IHash) { defer func() { rval = primitives.CheckNil(rval, "AddServerMsg.GetRepeatHash") }() @@ -66,20 +76,31 @@ func (m *AddServerMsg) GetTimestamp() interfaces.Timestamp { return m.Timestamp.Clone() } +// Validate takes the set of signatures in the message and compares them to the state's current authority set. +// If more than 50% ( >= n/2+1) of identities in the authority set (fed+audit) have signed the message, it is valid. func (m *AddServerMsg) Validate(state interfaces.IState) int { - //return 1 - authoritativeKey := state.GetNetworkSkeletonKey().Bytes() - if m.GetSignature() == nil || bytes.Compare(m.GetSignature().GetKey(), authoritativeKey) != 0 { - // the message was not signed with the proper authoritative signing key (from conf file) - // it is therefore considered invalid + auth := state.GetAuthorities() + valid, err := m.VerifySignatures() + if err != nil { // unable to marshal return -1 } - isVer, err := m.VerifySignature() - if err != nil || !isVer { - // if there is an error during signature verification - // or if the signature is invalid - // the message is considered invalid + // not enough signatures + if len(valid) < (len(auth)/2 + 1) { + return -1 + } + + realKeys := 0 + for _, v := range valid { + for _, a := range auth { + if bytes.Equal(v.GetKey(), a.GetSigningKey()) { + realKeys++ + break + } + } + } + + if realKeys < len(auth)/2+1 { return -1 } @@ -101,7 +122,6 @@ func (m *AddServerMsg) FollowerExecute(state interfaces.IState) { state.FollowerExecuteMsg(m) } -// Acknowledgements do not go into the process list. func (e *AddServerMsg) Process(dbheight uint32, state interfaces.IState) bool { return state.ProcessAddServer(dbheight, e) } @@ -114,21 +134,57 @@ func (e *AddServerMsg) JSONString() (string, error) { return primitives.EncodeJSONString(e) } -func (m *AddServerMsg) Sign(key interfaces.Signer) error { - signature, err := msgbase.SignSignable(m, key) +func (m *AddServerMsg) AddSignature(key interfaces.Signer) error { + data, err := m.MarshalForKambani() if err != nil { return err } - m.Signature = signature + signature := key.Sign(data) + + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + m.sigCache = false + m.validSignatures = nil + + m.Signatures.AddSignature(signature) return nil } -func (m *AddServerMsg) GetSignature() interfaces.IFullSignature { - return m.Signature +func (m *AddServerMsg) GetSignatures() []interfaces.IFullSignature { + return m.Signatures.GetSignatures() } -func (m *AddServerMsg) VerifySignature() (bool, error) { - return msgbase.VerifyMessage(m) +func (m *AddServerMsg) VerifySignatures() ([]interfaces.IFullSignature, error) { + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + + if m.sigCache { + return m.validSignatures, nil + } + + data, err := m.MarshalForKambani() + if err != nil { + return nil, err + } + + sigs := m.Signatures.GetSignatures() + + duplicate := make(map[string]bool) + valid := make([]interfaces.IFullSignature, 0, len(sigs)) // might be fewer if duplicate + for _, sig := range sigs { + key := fmt.Sprintf("%x", sig.GetKey()) + if !duplicate[key] && sig.Verify(data) { + duplicate[key] = true + valid = append(valid, sig) + } + } + + sort.Slice(valid, func(i, j int) bool { + return bytes.Compare(valid[i].GetKey(), valid[j].GetKey()) < 0 + }) + + m.validSignatures = valid + return valid, nil } func (m *AddServerMsg) UnmarshalBinaryData(data []byte) (newData []byte, err error) { @@ -159,13 +215,17 @@ func (m *AddServerMsg) UnmarshalBinaryData(data []byte) (newData []byte, err err m.ServerType = int(newData[0]) newData = newData[1:] - if len(newData) > 32 { - m.Signature = new(primitives.Signature) - newData, err = m.Signature.UnmarshalBinaryData(newData) - if err != nil { - return nil, err - } + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + m.sigCache = false + m.validSignatures = nil + + m.Signatures = new(factoid.FullSignatureBlock) + newData, err = m.Signatures.UnmarshalBinaryData(newData) + if err != nil { + return nil, err } + return } @@ -199,6 +259,17 @@ func (m *AddServerMsg) MarshalForSignature() (rval []byte, err error) { return buf.DeepCopyBytes(), nil } +func (m *AddServerMsg) MarshalForKambani() ([]byte, error) { + data, err := m.MarshalForSignature() + if err != nil { + return nil, err + } + // transform to kambani compatible format + data = []byte(fmt.Sprintf("%x", data)) + datahash := sha256.Sum256(data) + return datahash[:], nil +} + func (m *AddServerMsg) MarshalBinary() (rval []byte, err error) { defer func(pe *error) { if *pe != nil { @@ -213,8 +284,8 @@ func (m *AddServerMsg) MarshalBinary() (rval []byte, err error) { } buf.Write(data) - if m.Signature != nil { - data, err = m.Signature.MarshalBinary() + if m.Signatures != nil { + data, err = m.Signatures.MarshalBinary() if err != nil { return nil, err } @@ -257,14 +328,10 @@ func (m *AddServerMsg) IsSameAs(b *AddServerMsg) bool { if m.ServerType != b.ServerType { return false } - if m.Signature == nil && b.Signature != nil { + + if !m.Signatures.IsSameAs(b.Signatures) { return false } - if m.Signature != nil { - if m.Signature.IsSameAs(b.Signature) == false { - return false - } - } return true } @@ -273,6 +340,7 @@ func NewAddServerMsg(state interfaces.IState, serverType int) interfaces.IMsg { msg.ServerChainID = state.GetIdentityChainID() msg.ServerType = serverType msg.Timestamp = state.GetTimestamp() + msg.Signatures = factoid.NewFullSignatureBlock() return msg @@ -283,6 +351,6 @@ func NewAddServerByHashMsg(state interfaces.IState, serverType int, newServerHas msg.ServerChainID = newServerHash msg.ServerType = serverType msg.Timestamp = state.GetTimestamp() - + msg.Signatures = factoid.NewFullSignatureBlock() return msg } diff --git a/common/messages/addServer_test.go b/common/messages/addServer_test.go index dd97c79cbd..735d7be8cb 100644 --- a/common/messages/addServer_test.go +++ b/common/messages/addServer_test.go @@ -5,12 +5,18 @@ package messages_test import ( + "bytes" + "fmt" "testing" "github.com/FactomProject/factomd/common/constants" + "github.com/FactomProject/factomd/common/factoid" + "github.com/FactomProject/factomd/common/identity" + "github.com/FactomProject/factomd/common/interfaces" . "github.com/FactomProject/factomd/common/messages" "github.com/FactomProject/factomd/common/messages/msgsupport" "github.com/FactomProject/factomd/common/primitives" + "github.com/FactomProject/factomd/state" ) func TestUnmarshalNilAddServerMsg(t *testing.T) { @@ -79,12 +85,12 @@ func TestMarshalUnmarshalSignedAddServer(t *testing.T) { } t.Logf("Marshalled - %x", hex) - valid, err := addserv.VerifySignature() + valid, err := addserv.VerifySignatures() if err != nil { t.Error(err) } - if valid == false { - t.Error("Signature is not valid") + if len(valid) != len(addserv.GetSignatures()) { + t.Error("Some signatures not valid") } addserv2, err := msgsupport.UnmarshalMessage(hex) @@ -105,12 +111,12 @@ func TestMarshalUnmarshalSignedAddServer(t *testing.T) { t.Errorf("AddServer messages are not identical") } - valid, err = addserv2.(*AddServerMsg).VerifySignature() + valid, err = addserv2.(*AddServerMsg).VerifySignatures() if err != nil { t.Error(err) } - if valid == false { - t.Error("Signature is not valid") + if len(valid) != len(addserv2.(*AddServerMsg).GetSignatures()) { + t.Error("Signatures 2 are not valid") } } @@ -119,6 +125,7 @@ func newAddServer() *AddServerMsg { addserv.Timestamp = primitives.NewTimestampNow() addserv.ServerChainID = primitives.Sha([]byte("FNode0")) addserv.ServerType = 0 + addserv.Signatures = factoid.NewFullSignatureBlock() return addserv } @@ -129,7 +136,7 @@ func newSignedAddServer() *AddServerMsg { if err != nil { panic(err) } - err = addserv.Sign(key) + err = addserv.AddSignature(key) if err != nil { panic(err) } @@ -138,3 +145,156 @@ func newSignedAddServer() *AddServerMsg { } // TODO: Add test for signed messages (See ack_test.go) + +func TestMultisigAdd(t *testing.T) { + msg := newAddServer() + + keys := make([]*primitives.PrivateKey, 64) + for i := range keys { + keys[i] = primitives.RandomPrivateKey() + msg.AddSignature(keys[i]) + } + + data, err := msg.MarshalForSignature() + if err != nil { + t.Error(err) + } + + unique := make(map[string]bool) + for i, sig := range msg.GetSignatures() { + unique[fmt.Sprintf("%x", sig.GetKey())] = true + found := false + for _, key := range keys { + if bytes.Equal(sig.GetKey(), key.Public()) { + if err := primitives.VerifySignature(data, key.Public(), sig.Bytes()); err != nil { + t.Errorf("signature #%d (%x) did not verify", i, sig.GetKey()) + } else { + found = true + break + } + } + } + + if !found { + t.Errorf("signature #%d (%x) was not part of the original keyset", i, sig.GetKey()) + } + } + + if len(unique) != len(keys) { + t.Errorf("only found %d of %d signatures", len(unique), len(keys)) + } +} + +func TestSigVerify(t *testing.T) { + msg := newAddServer() + + // key 0 ok + // key 1 ok + // key 2 corrupt + // key 3 duplicate of 1 + keys := make([]*primitives.PrivateKey, 4) + for i := range keys { + if i == 3 { + keys[i] = keys[1] + } else { + keys[i] = primitives.RandomPrivateKey() + } + msg.AddSignature(keys[i]) + } + + // unmarshal and remarshal is as easy as getting through all the interfaces + raw, err := msg.MarshalBinary() + if err != nil { + t.Fatal(err) + } + // corrupt the third of four signatures + raw[1+6+32+1+(32+64)+(32+64)+32]++ + + msg = new(AddServerMsg) + if err := msg.UnmarshalBinary(raw); err != nil { + t.Error("failed to unmarshal corrupted data") + } + + sigs := msg.GetSignatures() + if vsigs, err := msg.VerifySignatures(); err != nil { + t.Error("failed to verify sigs", err) + } else { + if len(sigs) != len(vsigs)+2 { + t.Errorf("unexpected sig count. want = (4,2), got = (%d, %d)", len(sigs), len(vsigs)) + } else { + indexes := make(map[int]bool) + for i, k := range keys { + for _, sig := range vsigs { + if bytes.Equal(k.Public(), sig.GetKey()) { + indexes[i] = true + } + } + } + + if indexes[2] { + t.Error("the sig we corrupted was inside", indexes) + } + // index 3 is also set because the signature for that key exists (duplicate) + // this is just an oddity in the test script, duplicate detection is proven + // via the length check + + for _, i := range []int{0, 1} { + if !indexes[i] { + t.Errorf("sig %d missing", i) + } + } + } + } +} + +var authorities []interfaces.IAuthority + +type fakeState struct { + state.State +} + +func (fs *fakeState) GetAuthorities() []interfaces.IAuthority { + return authorities +} + +func TestMultisigValidate(t *testing.T) { + var s interfaces.IState + fs := new(fakeState) + fs.IdentityChainID = primitives.ZeroHash + + s = fs + + // generate keys and authorities) + keys := make([]*primitives.PrivateKey, 32) + authorities = make([]interfaces.IAuthority, 32) + for i := range authorities { + keys[i] = primitives.RandomPrivateKey() + authorities[i] = identity.RandomAuthority() + authorities[i].(*identity.Authority).SigningKey = *keys[i].Pub + } + + msg := newAddServer() + + if v := msg.Validate(s); v >= 0 { + t.Fatalf("message validated with 0/32 sigs: %d", v) + } + + for i := 0; i < 16; i++ { + msg.AddSignature(keys[i]) + if v := msg.Validate(s); v >= 0 { + t.Fatalf("message validated with %d/32 sigs: %d", len(msg.GetSignatures()), v) + } + } + + for i := 16; i < len(authorities); i++ { + msg.AddSignature(keys[i]) + if v := msg.Validate(s); v < 1 { + verlen, err := msg.VerifySignatures() + if err != nil { + t.Error(err) + } + t.Errorf("message failed to validate with %d/32 (%d valid) sigs: %d", len(msg.GetSignatures()), len(verlen), v) + } + } + +} diff --git a/common/messages/removeServer.go b/common/messages/removeServer.go index 2b8724c30c..6cc8d14a5d 100644 --- a/common/messages/removeServer.go +++ b/common/messages/removeServer.go @@ -6,11 +6,15 @@ package messages import ( "bytes" + "crypto/sha256" "encoding/binary" "fmt" "os" + "sort" + "sync" "github.com/FactomProject/factomd/common/constants" + "github.com/FactomProject/factomd/common/factoid" "github.com/FactomProject/factomd/common/interfaces" "github.com/FactomProject/factomd/common/messages/msgbase" "github.com/FactomProject/factomd/common/primitives" @@ -27,11 +31,16 @@ type RemoveServerMsg struct { ServerChainID interfaces.IHash // ChainID of new server ServerType int // 0 = Federated, 1 = Audit - Signature interfaces.IFullSignature + Signatures interfaces.IFullSignatureBlock + + sigMtx sync.Mutex + sigCache bool // true if this set of Signatures has been verified + // List of validated pubkey/sig pairs, sorted by byte order + validSignatures []interfaces.IFullSignature } var _ interfaces.IMsg = (*RemoveServerMsg)(nil) -var _ interfaces.Signable = (*RemoveServerMsg)(nil) +var _ interfaces.MultiSignable = (*RemoveServerMsg)(nil) func (m *RemoveServerMsg) GetRepeatHash() (rval interfaces.IHash) { defer func() { rval = primitives.CheckNil(rval, "RemoveServerMsg.GetRepeatHash") }() @@ -67,30 +76,49 @@ func (m *RemoveServerMsg) GetTimestamp() interfaces.Timestamp { } func (m *RemoveServerMsg) Validate(state interfaces.IState) int { - // Check to see if identity exists and is audit or fed server - if !state.VerifyIsAuthority(m.ServerChainID) { - //fmt.Printf("RemoveServerMsg Error: [%s] is not a server, cannot be removed\n", m.ServerChainID.String()[:8]) + auth := state.GetAuthorities() + found := false + for _, a := range auth { + if a.GetAuthorityChainID().IsSameAs(m.ServerChainID) { + found = true + break + } + } + if !found { + return -1 + } + + valid, err := m.VerifySignatures() + if err != nil { // unable to marshal return -1 } - authoritativeKey := state.GetNetworkSkeletonKey().Bytes() - if m.GetSignature() == nil || bytes.Compare(m.GetSignature().GetKey(), authoritativeKey) != 0 { - // the message was not signed with the proper authoritative signing key (from conf file) - // it is therefore considered invalid + // not enough signatures + if len(valid) < (len(auth)/2 + 1) { return -1 } - isVer, err := m.VerifySignature() - if err != nil || !isVer { - // if there is an error during signature verification - // or if the signature is invalid - // the message is considered invalid + realKeys := 0 + for _, v := range valid { + for _, a := range auth { + if bytes.Equal(v.GetKey(), a.GetSigningKey()) { + realKeys++ + break + } + } + } + + if realKeys < len(auth)/2+1 { return -1 } return 1 } +func (m *RemoveServerMsg) GetSignatures() []interfaces.IFullSignature { + return m.Signatures.GetSignatures() +} + // Returns true if this is a message for this server to execute as // a leader. func (m *RemoveServerMsg) ComputeVMIndex(state interfaces.IState) { @@ -119,21 +147,53 @@ func (e *RemoveServerMsg) JSONString() (string, error) { return primitives.EncodeJSONString(e) } -func (m *RemoveServerMsg) Sign(key interfaces.Signer) error { - signature, err := msgbase.SignSignable(m, key) +func (m *RemoveServerMsg) AddSignature(key interfaces.Signer) error { + data, err := m.MarshalForKambani() if err != nil { return err } - m.Signature = signature + signature := key.Sign(data) + + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + m.sigCache = false + m.validSignatures = nil + + m.Signatures.AddSignature(signature) return nil } -func (m *RemoveServerMsg) GetSignature() interfaces.IFullSignature { - return m.Signature -} +func (m *RemoveServerMsg) VerifySignatures() ([]interfaces.IFullSignature, error) { + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + + if m.sigCache { + return m.validSignatures, nil + } + + data, err := m.MarshalForKambani() + if err != nil { + return nil, err + } -func (m *RemoveServerMsg) VerifySignature() (bool, error) { - return msgbase.VerifyMessage(m) + sigs := m.Signatures.GetSignatures() + + duplicate := make(map[string]bool) + valid := make([]interfaces.IFullSignature, 0, len(sigs)) // might be fewer if duplicate + for _, sig := range sigs { + key := fmt.Sprintf("%x", sig.GetKey()) + if !duplicate[key] && sig.Verify(data) { + duplicate[key] = true + valid = append(valid, sig) + } + } + + sort.Slice(valid, func(i, j int) bool { + return bytes.Compare(valid[i].GetKey(), valid[j].GetKey()) < 0 + }) + + m.validSignatures = valid + return valid, nil } func (m *RemoveServerMsg) UnmarshalBinaryData(data []byte) (newData []byte, err error) { @@ -165,12 +225,15 @@ func (m *RemoveServerMsg) UnmarshalBinaryData(data []byte) (newData []byte, err m.ServerType = int(newData[0]) newData = newData[1:] - if len(newData) > 32 { - m.Signature = new(primitives.Signature) - newData, err = m.Signature.UnmarshalBinaryData(newData) - if err != nil { - return nil, err - } + m.sigMtx.Lock() + defer m.sigMtx.Unlock() + m.sigCache = false + m.validSignatures = nil + + m.Signatures = new(factoid.FullSignatureBlock) + newData, err = m.Signatures.UnmarshalBinaryData(newData) + if err != nil { + return nil, err } return } @@ -208,6 +271,17 @@ func (m *RemoveServerMsg) MarshalForSignature() (rval []byte, err error) { return buf.DeepCopyBytes(), nil } +func (m *RemoveServerMsg) MarshalForKambani() ([]byte, error) { + data, err := m.MarshalForSignature() + if err != nil { + return nil, err + } + // transform to kambani compatible format + data = []byte(fmt.Sprintf("%x", data)) + datahash := sha256.Sum256(data) + return datahash[:], nil +} + func (m *RemoveServerMsg) MarshalBinary() (rval []byte, err error) { defer func(pe *error) { if *pe != nil { @@ -222,8 +296,8 @@ func (m *RemoveServerMsg) MarshalBinary() (rval []byte, err error) { } buf.Write(data) - if m.Signature != nil { - data, err = m.Signature.MarshalBinary() + if m.Signatures != nil { + data, err = m.Signatures.MarshalBinary() if err != nil { return nil, err } @@ -268,14 +342,9 @@ func (m *RemoveServerMsg) IsSameAs(b *RemoveServerMsg) bool { if m.ServerType != b.ServerType { return false } - if m.Signature == nil && b.Signature != nil { + if !m.Signatures.IsSameAs(b.Signatures) { return false } - if m.Signature != nil { - if m.Signature.IsSameAs(b.Signature) == false { - return false - } - } return true } @@ -284,6 +353,7 @@ func NewRemoveServerMsg(state interfaces.IState, chainId interfaces.IHash, serve msg.ServerChainID = chainId msg.ServerType = serverType msg.Timestamp = state.GetTimestamp() + msg.Signatures = factoid.NewFullSignatureBlock() return msg diff --git a/common/messages/removeServer_test.go b/common/messages/removeServer_test.go index 55a2acb3de..dac8ceafdd 100644 --- a/common/messages/removeServer_test.go +++ b/common/messages/removeServer_test.go @@ -4,6 +4,171 @@ package messages_test +import ( + "bytes" + "fmt" + "testing" + + "github.com/FactomProject/factomd/common/factoid" + "github.com/FactomProject/factomd/common/identity" + "github.com/FactomProject/factomd/common/interfaces" + . "github.com/FactomProject/factomd/common/messages" + "github.com/FactomProject/factomd/common/primitives" +) + //"testing" //. "github.com/FactomProject/factomd/common/messages" + +func newRemoveServer() *RemoveServerMsg { + addserv := new(RemoveServerMsg) + addserv.Timestamp = primitives.NewTimestampNow() + addserv.ServerChainID = primitives.Sha([]byte("FNode0")) + addserv.ServerType = 0 + addserv.Signatures = factoid.NewFullSignatureBlock() + return addserv +} + +func TestRemoveServerMultisigAdd(t *testing.T) { + msg := newRemoveServer() + + keys := make([]*primitives.PrivateKey, 64) + for i := range keys { + keys[i] = primitives.RandomPrivateKey() + msg.AddSignature(keys[i]) + } + + data, err := msg.MarshalForSignature() + if err != nil { + t.Error(err) + } + + unique := make(map[string]bool) + for i, sig := range msg.GetSignatures() { + unique[fmt.Sprintf("%x", sig.GetKey())] = true + found := false + for _, key := range keys { + if bytes.Equal(sig.GetKey(), key.Public()) { + if err := primitives.VerifySignature(data, key.Public(), sig.Bytes()); err != nil { + t.Errorf("signature #%d (%x) did not verify", i, sig.GetKey()) + } else { + found = true + break + } + } + } + + if !found { + t.Errorf("signature #%d (%x) was not part of the original keyset", i, sig.GetKey()) + } + } + + if len(unique) != len(keys) { + t.Errorf("only found %d of %d signatures", len(unique), len(keys)) + } +} + +func TestRemoveServerSigVerify(t *testing.T) { + msg := newRemoveServer() + + // key 0 ok + // key 1 ok + // key 2 corrupt + // key 3 duplicate of 1 + keys := make([]*primitives.PrivateKey, 4) + for i := range keys { + if i == 3 { + keys[i] = keys[1] + } else { + keys[i] = primitives.RandomPrivateKey() + } + msg.AddSignature(keys[i]) + } + + // unmarshal and remarshal is as easy as getting through all the interfaces + raw, err := msg.MarshalBinary() + if err != nil { + t.Fatal(err) + } + // corrupt the third of four signatures + raw[1+6+32+1+(32+64)+(32+64)+32]++ + + msg = new(RemoveServerMsg) + if err := msg.UnmarshalBinary(raw); err != nil { + t.Error("failed to unmarshal corrupted data") + } + + sigs := msg.GetSignatures() + if vsigs, err := msg.VerifySignatures(); err != nil { + t.Error("failed to verify sigs", err) + } else { + if len(sigs) != len(vsigs)+2 { + t.Errorf("unexpected sig count. want = (4,2), got = (%d, %d)", len(sigs), len(vsigs)) + } else { + indexes := make(map[int]bool) + for i, k := range keys { + for _, sig := range vsigs { + if bytes.Equal(k.Public(), sig.GetKey()) { + indexes[i] = true + } + } + } + + if indexes[2] { + t.Error("the sig we corrupted was inside", indexes) + } + // index 3 is also set because the signature for that key exists (duplicate) + // this is just an oddity in the test script, duplicate detection is proven + // via the length check + + for _, i := range []int{0, 1} { + if !indexes[i] { + t.Errorf("sig %d missing", i) + } + } + } + } +} + +func TestRemoveServerMultisigValidate(t *testing.T) { + var s interfaces.IState + fs := new(fakeState) + fs.IdentityChainID = primitives.ZeroHash + + s = fs + + // generate keys and authorities) + keys := make([]*primitives.PrivateKey, 32) + authorities = make([]interfaces.IAuthority, 32) + for i := range authorities { + keys[i] = primitives.RandomPrivateKey() + authorities[i] = identity.RandomAuthority() + authorities[i].(*identity.Authority).SigningKey = *keys[i].Pub + } + + msg := newRemoveServer() + msg.ServerChainID = authorities[0].GetAuthorityChainID() + + if v := msg.Validate(s); v >= 0 { + t.Fatalf("message validated with 0/32 sigs: %d", v) + } + + for i := 0; i < 16; i++ { + msg.AddSignature(keys[i]) + if v := msg.Validate(s); v >= 0 { + t.Fatalf("message validated with %d/32 sigs: %d", len(msg.GetSignatures()), v) + } + } + + for i := 16; i < len(authorities); i++ { + msg.AddSignature(keys[i]) + if v := msg.Validate(s); v < 1 { + verlen, err := msg.VerifySignatures() + if err != nil { + t.Error(err) + } + t.Errorf("message failed to validate with %d/32 (%d valid) sigs: %d", len(msg.GetSignatures()), len(verlen), v) + } + } + +} diff --git a/engine/simControl.go b/engine/simControl.go index aeac374df3..9c66524d29 100644 --- a/engine/simControl.go +++ b/engine/simControl.go @@ -727,7 +727,7 @@ func SimControl(listenTo int, listenStdin bool) { os.Stderr.WriteString(fmt.Sprintln("Could not remove server,", err.Error())) break } - err = msg.(*messages.RemoveServerMsg).Sign(priv) + err = msg.(*messages.RemoveServerMsg).AddSignature(priv) if err != nil { os.Stderr.WriteString(fmt.Sprintln("Could not remove server,", err.Error())) break @@ -761,7 +761,7 @@ func SimControl(listenTo int, listenStdin bool) { os.Stderr.WriteString(fmt.Sprintln("Could not make an audit server,", err.Error())) break } - err = msg.(*messages.AddServerMsg).Sign(priv) + err = msg.(*messages.AddServerMsg).AddSignature(priv) if err != nil { os.Stderr.WriteString(fmt.Sprintln("Could not make an audit server,", err.Error())) break @@ -805,7 +805,7 @@ func SimControl(listenTo int, listenStdin bool) { os.Stderr.WriteString(fmt.Sprintln("Could not make a leader,", err.Error())) break } - err = msg.(*messages.AddServerMsg).Sign(priv) + err = msg.(*messages.AddServerMsg).AddSignature(priv) if err != nil { os.Stderr.WriteString(fmt.Sprintln("Could not make a leader,", err.Error())) break