aboutsummaryrefslogtreecommitdiff
path: root/pkg/state
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/state')
-rw-r--r--pkg/state/mocks/signer.go23
-rw-r--r--pkg/state/mocks/state_manager.go107
-rw-r--r--pkg/state/single.go156
-rw-r--r--pkg/state/single_test.go (renamed from pkg/state/state_manager_test.go)233
-rw-r--r--pkg/state/state_manager.go148
5 files changed, 412 insertions, 255 deletions
diff --git a/pkg/state/mocks/signer.go b/pkg/state/mocks/signer.go
new file mode 100644
index 0000000..7c699dd
--- /dev/null
+++ b/pkg/state/mocks/signer.go
@@ -0,0 +1,23 @@
+package mocks
+
+import (
+ "crypto"
+ "crypto/ed25519"
+ "io"
+)
+
+// TestSign implements the signer interface. It can be used to mock an Ed25519
+// signer that always return the same public key, signature, and error.
+type TestSigner struct {
+ PublicKey [ed25519.PublicKeySize]byte
+ Signature [ed25519.SignatureSize]byte
+ Error error
+}
+
+func (ts *TestSigner) Public() crypto.PublicKey {
+ return ed25519.PublicKey(ts.PublicKey[:])
+}
+
+func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
+ return ts.Signature[:], ts.Error
+}
diff --git a/pkg/state/mocks/state_manager.go b/pkg/state/mocks/state_manager.go
new file mode 100644
index 0000000..009e20f
--- /dev/null
+++ b/pkg/state/mocks/state_manager.go
@@ -0,0 +1,107 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: git.sigsum.org/sigsum-log-go/pkg/state (interfaces: StateManager)
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ context "context"
+ reflect "reflect"
+
+ types "git.sigsum.org/sigsum-lib-go/pkg/types"
+ gomock "github.com/golang/mock/gomock"
+)
+
+// MockStateManager is a mock of StateManager interface.
+type MockStateManager struct {
+ ctrl *gomock.Controller
+ recorder *MockStateManagerMockRecorder
+}
+
+// MockStateManagerMockRecorder is the mock recorder for MockStateManager.
+type MockStateManagerMockRecorder struct {
+ mock *MockStateManager
+}
+
+// NewMockStateManager creates a new mock instance.
+func NewMockStateManager(ctrl *gomock.Controller) *MockStateManager {
+ mock := &MockStateManager{ctrl: ctrl}
+ mock.recorder = &MockStateManagerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder {
+ return m.recorder
+}
+
+// AddCosignature mocks base method.
+func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *types.PublicKey, arg2 *types.Signature) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "AddCosignature", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// AddCosignature indicates an expected call of AddCosignature.
+func (mr *MockStateManagerMockRecorder) AddCosignature(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCosignature", reflect.TypeOf((*MockStateManager)(nil).AddCosignature), arg0, arg1, arg2)
+}
+
+// Cosigned mocks base method.
+func (m *MockStateManager) Cosigned(arg0 context.Context) (*types.CosignedTreeHead, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Cosigned", arg0)
+ ret0, _ := ret[0].(*types.CosignedTreeHead)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Cosigned indicates an expected call of Cosigned.
+func (mr *MockStateManagerMockRecorder) Cosigned(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cosigned", reflect.TypeOf((*MockStateManager)(nil).Cosigned), arg0)
+}
+
+// Latest mocks base method.
+func (m *MockStateManager) Latest(arg0 context.Context) (*types.SignedTreeHead, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Latest", arg0)
+ ret0, _ := ret[0].(*types.SignedTreeHead)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Latest indicates an expected call of Latest.
+func (mr *MockStateManagerMockRecorder) Latest(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Latest", reflect.TypeOf((*MockStateManager)(nil).Latest), arg0)
+}
+
+// Run mocks base method.
+func (m *MockStateManager) Run(arg0 context.Context) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Run", arg0)
+}
+
+// Run indicates an expected call of Run.
+func (mr *MockStateManagerMockRecorder) Run(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStateManager)(nil).Run), arg0)
+}
+
+// ToSign mocks base method.
+func (m *MockStateManager) ToSign(arg0 context.Context) (*types.SignedTreeHead, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ToSign", arg0)
+ ret0, _ := ret[0].(*types.SignedTreeHead)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ToSign indicates an expected call of ToSign.
+func (mr *MockStateManagerMockRecorder) ToSign(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToSign", reflect.TypeOf((*MockStateManager)(nil).ToSign), arg0)
+}
diff --git a/pkg/state/single.go b/pkg/state/single.go
new file mode 100644
index 0000000..d02b86f
--- /dev/null
+++ b/pkg/state/single.go
@@ -0,0 +1,156 @@
+package state
+
+import (
+ "context"
+ "crypto"
+ "crypto/ed25519"
+ "fmt"
+ "reflect"
+ "sync"
+ "time"
+
+ "git.sigsum.org/sigsum-lib-go/pkg/types"
+ "git.sigsum.org/sigsum-log-go/pkg/db"
+ "github.com/golang/glog"
+ "github.com/google/certificate-transparency-go/schedule"
+)
+
+// StateManagerSingle implements a single-instance StateManager. In other
+// words, there's no other state that needs to be synced on any remote machine.
+type StateManagerSingle struct {
+ client db.Client
+ signer crypto.Signer
+ interval time.Duration
+ deadline time.Duration
+ sync.RWMutex
+
+ // cosigned is the current cosigned tree head that is being served
+ cosigned types.CosignedTreeHead
+
+ // toSign is the current tree head that is being cosigned by witnesses
+ toSign types.SignedTreeHead
+
+ // cosignatures keep track of all cosignatures for the toSign tree head
+ cosignatures map[types.Hash]*types.Signature
+}
+
+func NewStateManagerSingle(client db.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) {
+ sm := &StateManagerSingle{
+ client: client,
+ signer: signer,
+ interval: interval,
+ deadline: deadline,
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), sm.deadline)
+ defer cancel()
+
+ sth, err := sm.Latest(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("Latest: %v", err)
+ }
+ sm.toSign = *sth
+ sm.cosignatures = make(map[types.Hash]*types.Signature)
+ sm.cosigned = types.CosignedTreeHead{
+ SignedTreeHead: *sth,
+ Cosignature: make([]types.Signature, 0),
+ KeyHash: make([]types.Hash, 0),
+ }
+ return sm, nil
+}
+
+func (sm *StateManagerSingle) Run(ctx context.Context) {
+ schedule.Every(ctx, sm.interval, func(ctx context.Context) {
+ ictx, cancel := context.WithTimeout(ctx, sm.deadline)
+ defer cancel()
+
+ nextSTH, err := sm.Latest(ictx)
+ if err != nil {
+ glog.Warningf("rotate failed: Latest: %v", err)
+ return
+ }
+
+ sm.Lock()
+ defer sm.Unlock()
+ sm.rotate(nextSTH)
+ })
+}
+
+func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) {
+ th, err := sm.client.GetTreeHead(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("LatestTreeHead: %v", err)
+ }
+
+ namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey))
+ return th.Sign(sm.signer, namespace)
+}
+
+func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) {
+ sm.RLock()
+ defer sm.RUnlock()
+ return &sm.toSign, nil
+}
+
+func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.CosignedTreeHead, error) {
+ sm.RLock()
+ defer sm.RUnlock()
+ if len(sm.cosigned.Cosignature) == 0 {
+ return nil, fmt.Errorf("no witness cosignatures available")
+ }
+ return &sm.cosigned, nil
+}
+
+func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *types.PublicKey, sig *types.Signature) error {
+ sm.Lock()
+ defer sm.Unlock()
+
+ namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey))
+ msg := sm.toSign.TreeHead.ToBinary(namespace)
+ if !ed25519.Verify(ed25519.PublicKey(vk[:]), msg, sig[:]) {
+ return fmt.Errorf("invalid tree head signature")
+ }
+
+ witness := types.HashFn(vk[:])
+ if _, ok := sm.cosignatures[*witness]; ok {
+ return fmt.Errorf("signature-signer pair is a duplicate") // TODO: maybe not an error
+ }
+ sm.cosignatures[*witness] = sig
+
+ glog.V(3).Infof("accepted new cosignature from witness: %x", *witness)
+ return nil
+}
+
+// rotate rotates the log's cosigned and stable STH. The caller must aquire the
+// source's read-write lock if there are concurrent reads and/or writes.
+func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) {
+ if reflect.DeepEqual(sm.cosigned.SignedTreeHead, sm.toSign) {
+ // cosigned and toSign are the same. So, we need to merge all
+ // cosignatures that we already had with the new collected ones.
+ for i := 0; i < len(sm.cosigned.Cosignature); i++ {
+ kh := sm.cosigned.KeyHash[i]
+ sig := sm.cosigned.Cosignature[i]
+
+ if _, ok := sm.cosignatures[kh]; !ok {
+ sm.cosignatures[kh] = &sig
+ }
+ }
+ glog.V(3).Infof("cosigned tree head repeated, merged signatures")
+ }
+ var cosignatures []types.Signature
+ var keyHashes []types.Hash
+
+ for keyHash, cosignature := range sm.cosignatures {
+ cosignatures = append(cosignatures, *cosignature)
+ keyHashes = append(keyHashes, keyHash)
+ }
+
+ // Update cosigned tree head
+ sm.cosigned.SignedTreeHead = sm.toSign
+ sm.cosigned.Cosignature = cosignatures
+ sm.cosigned.KeyHash = keyHashes
+
+ // Update to-sign tree head
+ sm.toSign = *next
+ sm.cosignatures = make(map[types.Hash]*types.Signature, 0) // TODO: on repeat we might want to not zero this
+ glog.V(3).Infof("rotated tree heads")
+}
diff --git a/pkg/state/state_manager_test.go b/pkg/state/single_test.go
index f11ba28..b315b3e 100644
--- a/pkg/state/state_manager_test.go
+++ b/pkg/state/single_test.go
@@ -11,36 +11,34 @@ import (
"testing"
"time"
+ "git.sigsum.org/sigsum-lib-go/pkg/types"
+ mocksTrillian "git.sigsum.org/sigsum-log-go/pkg/db/mocks"
+ mocksSigner "git.sigsum.org/sigsum-log-go/pkg/state/mocks"
"github.com/golang/mock/gomock"
- "git.sigsum.org/sigsum-log-go/pkg/mocks"
- "git.sigsum.org/sigsum-log-go/pkg/types"
)
var (
- testSig = &[types.SignatureSize]byte{}
- testPub = &[types.VerificationKeySize]byte{}
- testTH = &types.TreeHead{
+ testTH = types.TreeHead{
Timestamp: 0,
TreeSize: 0,
- RootHash: types.Hash(nil),
- KeyHash: types.Hash(testPub[:]),
+ RootHash: types.Hash{},
}
- testSigIdent = &types.SigIdent{
- Signature: testSig,
- KeyHash: types.Hash(testPub[:]),
+ testSTH = types.SignedTreeHead{
+ TreeHead: testTH,
+ Signature: types.Signature{},
}
- testSTH = &types.SignedTreeHead{
- TreeHead: *testTH,
- Signature: testSig,
- }
- testCTH = &types.CosignedTreeHead{
- SignedTreeHead: *testSTH,
- SigIdent: []*types.SigIdent{
- testSigIdent,
+ testCTH = types.CosignedTreeHead{
+ SignedTreeHead: testSTH,
+ Cosignature: []types.Signature{
+ types.Signature{},
+ },
+ KeyHash: []types.Hash{
+ types.Hash{},
},
}
- testSignerOK = &mocks.TestSigner{testPub, testSig, nil}
- testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")}
+
+ testSignerOK = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, nil}
+ testSignerErr = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, fmt.Errorf("something went wrong")}
)
func TestNewStateManagerSingle(t *testing.T) {
@@ -61,15 +59,15 @@ func TestNewStateManagerSingle(t *testing.T) {
{
description: "valid",
signer: testSignerOK,
- rsp: testTH,
- wantSth: testSTH,
+ rsp: &testTH,
+ wantSth: &testSTH,
},
} {
// Run deferred functions at the end of each iteration
func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
- client := mocks.NewMockClient(ctrl)
+ client := mocksTrillian.NewMockClient(ctrl)
client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)
sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0))
@@ -82,11 +80,11 @@ func TestNewStateManagerSingle(t *testing.T) {
if got, want := &sm.cosigned.SignedTreeHead, table.wantSth; !reflect.DeepEqual(got, want) {
t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
}
- if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) {
- t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
+ if got, want := &sm.toSign, table.wantSth; !reflect.DeepEqual(got, want) {
+ t.Errorf("got toSign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
}
// we only have log signature on startup
- if got, want := len(sm.cosignature), 0; got != want {
+ if got, want := len(sm.cosignatures), 0; got != want {
t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description)
}
}()
@@ -110,22 +108,22 @@ func TestLatest(t *testing.T) {
},
{
description: "invalid: signature failure",
- rsp: testTH,
+ rsp: &testTH,
signer: testSignerErr,
wantErr: true,
},
{
description: "valid",
signer: testSignerOK,
- rsp: testTH,
- wantSth: testSTH,
+ rsp: &testTH,
+ wantSth: &testSTH,
},
} {
// Run deferred functions at the end of each iteration
func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
- client := mocks.NewMockClient(ctrl)
+ client := mocksTrillian.NewMockClient(ctrl)
client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)
sm := StateManagerSingle{
client: client,
@@ -149,14 +147,14 @@ func TestLatest(t *testing.T) {
func TestToSign(t *testing.T) {
description := "valid"
sm := StateManagerSingle{
- tosign: *testSTH,
+ toSign: testSTH,
}
sth, err := sm.ToSign(context.Background())
if err != nil {
t.Errorf("ToSign should not fail with error: %v", err)
return
}
- if got, want := sth, testSTH; !reflect.DeepEqual(got, want) {
+ if got, want := sth, &testSTH; !reflect.DeepEqual(got, want) {
t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description)
}
}
@@ -164,18 +162,19 @@ func TestToSign(t *testing.T) {
func TestCosigned(t *testing.T) {
description := "valid"
sm := StateManagerSingle{
- cosigned: *testCTH,
+ cosigned: testCTH,
}
cth, err := sm.Cosigned(context.Background())
if err != nil {
t.Errorf("Cosigned should not fail with error: %v", err)
return
}
- if got, want := cth, testCTH; !reflect.DeepEqual(got, want) {
+ if got, want := cth, &testCTH; !reflect.DeepEqual(got, want) {
t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description)
}
- sm.cosigned.SigIdent = make([]*types.SigIdent, 0)
+ sm.cosigned.Cosignature = make([]types.Signature, 0)
+ sm.cosigned.KeyHash = make([]types.Hash, 0)
cth, err = sm.Cosigned(context.Background())
if err == nil {
t.Errorf("Cosigned should fail without witness cosignatures")
@@ -188,57 +187,55 @@ func TestAddCosignature(t *testing.T) {
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
- if bytes.Equal(vk[:], testPub[:]) {
+ if bytes.Equal(vk[:], new(types.PublicKey)[:]) {
t.Fatalf("Sampled same key as testPub, aborting...")
}
- var vkArray [types.VerificationKeySize]byte
+ var vkArray types.PublicKey
copy(vkArray[:], vk[:])
for _, table := range []struct {
description string
signer crypto.Signer
- vk *[types.VerificationKeySize]byte
- th *types.TreeHead
+ vk types.PublicKey
+ th types.TreeHead
wantErr bool
}{
{
description: "invalid: signature error",
signer: sk,
- vk: testPub, // wrong key for message
+ vk: types.PublicKey{}, // wrong key for message
th: testTH,
wantErr: true,
},
{
description: "valid",
signer: sk,
- vk: &vkArray,
+ vk: vkArray,
th: testTH,
},
} {
- sth, _ := table.th.Sign(testSignerOK)
+ kh := types.HashFn(testSignerOK.Public().(ed25519.PublicKey))
+ sth := mustSign(t, testSignerOK, &table.th, kh)
cth := &types.CosignedTreeHead{
SignedTreeHead: *sth,
- SigIdent: []*types.SigIdent{},
+ Cosignature: make([]types.Signature, 0),
+ KeyHash: make([]types.Hash, 0),
}
sm := &StateManagerSingle{
- signer: testSignerOK,
- cosigned: *cth,
- tosign: *sth,
- cosignature: map[[types.HashSize]byte]*types.SigIdent{},
+ signer: testSignerOK,
+ cosigned: *cth,
+ toSign: *sth,
+ cosignatures: make(map[types.Hash]*types.Signature, 0),
}
// Prepare witness signature
- sth, err := table.th.Sign(table.signer)
- if err != nil {
- t.Fatalf("Sign: %v", err)
- }
- si := &types.SigIdent{
- KeyHash: types.Hash(table.signer.Public().(ed25519.PublicKey)[:]),
- Signature: sth.Signature,
- }
+ var vk types.PublicKey
+ copy(vk[:], table.vk[:]) //table.signer.Public().(ed25519.PublicKey))
+ sth = mustSign(t, table.signer, &table.th, kh)
+ kh = types.HashFn(vk[:])
// Add witness signature
- err = sm.AddCosignature(context.Background(), table.vk, si.Signature)
+ err = sm.AddCosignature(context.Background(), &vk, &sth.Signature)
if got, want := err != nil, table.wantErr; got != want {
t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err)
}
@@ -247,48 +244,47 @@ func TestAddCosignature(t *testing.T) {
}
// We should have one witness signature
- if got, want := len(sm.cosignature), 1; got != want {
+ if got, want := len(sm.cosignatures), 1; got != want {
t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description)
continue
}
// check that witness signature is there
- sigident, ok := sm.cosignature[*si.KeyHash]
+ sig, ok := sm.cosignatures[*kh]
if !ok {
t.Errorf("witness signature is missing")
continue
}
- if got, want := si, sigident; !reflect.DeepEqual(got, want) {
+ if got, want := sig[:], sth.Signature[:]; !bytes.Equal(got, want) {
t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
continue
}
// Adding a duplicate signature should give an error
- if err := sm.AddCosignature(context.Background(), table.vk, si.Signature); err == nil {
+ if err := sm.AddCosignature(context.Background(), &vk, &sth.Signature); err == nil {
t.Errorf("duplicate witness signature accepted as valid")
}
}
}
func TestRotate(t *testing.T) {
- log := testSigIdent
- wit1 := &types.SigIdent{
- Signature: testSig,
- KeyHash: types.Hash([]byte("wit1 key")),
- }
- wit2 := &types.SigIdent{
- Signature: testSig,
- KeyHash: types.Hash([]byte("wit2 key")),
- }
- th0 := testTH
+ logSig := types.Signature{}
+ wit1Sig := types.Signature{}
+ wit2Sig := types.Signature{}
+
+ //logKH := &types.Hash{}
+ wit1KH := types.HashFn([]byte("wit1 key"))
+ wit2KH := types.HashFn([]byte("wit2 key"))
+
+ th0 := &testTH
th1 := &types.TreeHead{
Timestamp: 1,
TreeSize: 1,
- RootHash: types.Hash([]byte("1")),
+ RootHash: *types.HashFn([]byte("1")),
}
th2 := &types.TreeHead{
Timestamp: 2,
TreeSize: 2,
- RootHash: types.Hash([]byte("2")),
+ RootHash: *types.HashFn([]byte("2")),
}
for _, table := range []struct {
@@ -297,77 +293,81 @@ func TestRotate(t *testing.T) {
next *types.SignedTreeHead
}{
{
- description: "tosign tree head repated, but got one new witnes signature",
+ description: "toSign tree head repated, but got one new witnes signature",
before: &StateManagerSingle{
cosigned: types.CosignedTreeHead{
SignedTreeHead: types.SignedTreeHead{
TreeHead: *th0,
- Signature: log.Signature,
+ Signature: logSig,
},
- SigIdent: []*types.SigIdent{wit1},
+ Cosignature: []types.Signature{wit1Sig},
+ KeyHash: []types.Hash{*wit1KH},
},
- tosign: types.SignedTreeHead{
+ toSign: types.SignedTreeHead{
TreeHead: *th0,
- Signature: log.Signature,
+ Signature: logSig,
},
- cosignature: map[[types.HashSize]byte]*types.SigIdent{
- *wit2.KeyHash: wit2, // the new witness signature
+ cosignatures: map[types.Hash]*types.Signature{
+ *wit2KH: &wit2Sig, // the new witness signature
},
},
next: &types.SignedTreeHead{
TreeHead: *th1,
- Signature: log.Signature,
+ Signature: logSig,
},
after: &StateManagerSingle{
cosigned: types.CosignedTreeHead{
SignedTreeHead: types.SignedTreeHead{
TreeHead: *th0,
- Signature: log.Signature,
+ Signature: logSig,
},
- SigIdent: []*types.SigIdent{wit1, wit2},
+ Cosignature: []types.Signature{wit1Sig, wit2Sig},
+ KeyHash: []types.Hash{*wit1KH, *wit2KH},
},
- tosign: types.SignedTreeHead{
+ toSign: types.SignedTreeHead{
TreeHead: *th1,
- Signature: log.Signature,
+ Signature: logSig,
},
- cosignature: map[[types.HashSize]byte]*types.SigIdent{},
+ cosignatures: map[types.Hash]*types.Signature{},
},
},
{
- description: "tosign tree head did not repeat, it got one witness signature",
+ description: "toSign tree head did not repeat, it got one witness signature",
before: &StateManagerSingle{
cosigned: types.CosignedTreeHead{
SignedTreeHead: types.SignedTreeHead{
TreeHead: *th0,
- Signature: log.Signature,
+ Signature: logSig,
},
- SigIdent: []*types.SigIdent{wit1},
+ Cosignature: []types.Signature{wit1Sig},
+ KeyHash: []types.Hash{*wit1KH},
},
- tosign: types.SignedTreeHead{
+ toSign: types.SignedTreeHead{
TreeHead: *th1,
- Signature: log.Signature,
+ Signature: logSig,
},
- cosignature: map[[types.HashSize]byte]*types.SigIdent{
- *log.KeyHash: wit2,
+ cosignatures: map[types.Hash]*types.Signature{
+ *wit2KH: &wit2Sig,
},
},
next: &types.SignedTreeHead{
TreeHead: *th2,
- Signature: log.Signature,
+ Signature: logSig,
},
after: &StateManagerSingle{
cosigned: types.CosignedTreeHead{
SignedTreeHead: types.SignedTreeHead{
TreeHead: *th1,
- Signature: log.Signature,
+ Signature: logSig,
},
- SigIdent: []*types.SigIdent{wit2},
+ Cosignature: []types.Signature{wit2Sig},
+ KeyHash: []types.Hash{*wit2KH},
},
- tosign: types.SignedTreeHead{
+ toSign: types.SignedTreeHead{
TreeHead: *th2,
- Signature: log.Signature,
+ Signature: logSig,
},
- cosignature: map[[types.HashSize]byte]*types.SigIdent{},
+ cosignatures: map[types.Hash]*types.Signature{},
},
},
} {
@@ -375,31 +375,44 @@ func TestRotate(t *testing.T) {
if got, want := table.before.cosigned.SignedTreeHead, table.after.cosigned.SignedTreeHead; !reflect.DeepEqual(got, want) {
t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
}
- checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent)
- if got, want := table.before.tosign, table.after.tosign; !reflect.DeepEqual(got, want) {
- t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
+ checkWitnessList(t, table.description, table.before.cosigned, table.after.cosigned)
+ if got, want := table.before.toSign, table.after.toSign; !reflect.DeepEqual(got, want) {
+ t.Errorf("got toSign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
}
- if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) {
- t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
+ if got, want := table.before.cosignatures, table.after.cosignatures; !reflect.DeepEqual(got, want) {
+ t.Errorf("got cosignatures map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
}
}
}
-func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) {
+func checkWitnessList(t *testing.T, description string, got, want types.CosignedTreeHead) {
t.Helper()
- for _, si := range got {
+ if got, want := len(got.Cosignature), len(want.Cosignature); got != want {
+ t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, description)
+ return
+ }
+ if got, want := len(got.KeyHash), len(want.KeyHash); got != want {
+ t.Errorf("got %d key hashes but wanted %d in test %q", got, want, description)
+ return
+ }
+ for i := 0; i < len(got.Cosignature); i++ {
found := false
- for _, sj := range want {
- if reflect.DeepEqual(si, sj) {
+ for j := 0; j < len(want.Cosignature); j++ {
+ if bytes.Equal(got.Cosignature[i][:], want.Cosignature[j][:]) && bytes.Equal(got.KeyHash[i][:], want.KeyHash[j][:]) {
found = true
break
}
}
if !found {
- t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:])
+ t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, got.KeyHash[i][:])
}
}
- if len(got) != len(want) {
- t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description)
+}
+
+func mustSign(t *testing.T, s crypto.Signer, th *types.TreeHead, kh *types.Hash) *types.SignedTreeHead {
+ sth, err := th.Sign(s, kh)
+ if err != nil {
+ t.Fatal(err)
}
+ return sth
}
diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go
index 84431be..5aa7609 100644
--- a/pkg/state/state_manager.go
+++ b/pkg/state/state_manager.go
@@ -2,157 +2,15 @@ package state
import (
"context"
- "crypto"
- "crypto/ed25519"
- "fmt"
- "reflect"
- "sync"
- "time"
- "github.com/golang/glog"
- "github.com/google/certificate-transparency-go/schedule"
- "git.sigsum.org/sigsum-log-go/pkg/trillian"
- "git.sigsum.org/sigsum-log-go/pkg/types"
+ "git.sigsum.org/sigsum-lib-go/pkg/types"
)
-// StateManager coordinates access to the log's tree heads and (co)signatures
+// StateManager coordinates access to a log's tree heads and (co)signatures
type StateManager interface {
Latest(context.Context) (*types.SignedTreeHead, error)
ToSign(context.Context) (*types.SignedTreeHead, error)
Cosigned(context.Context) (*types.CosignedTreeHead, error)
- AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error
+ AddCosignature(context.Context, *types.PublicKey, *types.Signature) error
Run(context.Context)
}
-
-// StateManagerSingle implements the StateManager interface. It is assumed that
-// the log server is running on a single-instance machine. So, no coordination.
-type StateManagerSingle struct {
- client trillian.Client
- signer crypto.Signer
- interval time.Duration
- deadline time.Duration
- sync.RWMutex
-
- // cosigned is the current cosigned tree head that is being served
- cosigned types.CosignedTreeHead
-
- // tosign is the current tree head that is being cosigned by witnesses
- tosign types.SignedTreeHead
-
- // cosignature keeps track of all cosignatures for the tosign tree head
- cosignature map[[types.HashSize]byte]*types.SigIdent
-}
-
-func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) {
- sm := &StateManagerSingle{
- client: client,
- signer: signer,
- interval: interval,
- deadline: deadline,
- }
-
- ctx, _ := context.WithTimeout(context.Background(), sm.deadline)
- sth, err := sm.Latest(ctx)
- if err != nil {
- return nil, fmt.Errorf("Latest: %v", err)
- }
-
- sm.cosigned = types.CosignedTreeHead{
- SignedTreeHead: *sth,
- SigIdent: []*types.SigIdent{},
- }
- sm.tosign = *sth
- sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{}
- return sm, nil
-}
-
-func (sm *StateManagerSingle) Run(ctx context.Context) {
- schedule.Every(ctx, sm.interval, func(ctx context.Context) {
- ictx, _ := context.WithTimeout(ctx, sm.deadline)
- nextSTH, err := sm.Latest(ictx)
- if err != nil {
- glog.Warningf("rotate failed: Latest: %v", err)
- return
- }
-
- sm.Lock()
- defer sm.Unlock()
- sm.rotate(nextSTH)
- })
-}
-
-func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) {
- th, err := sm.client.GetTreeHead(ctx)
- if err != nil {
- return nil, fmt.Errorf("LatestTreeHead: %v", err)
- }
- th.KeyHash = types.Hash(sm.signer.Public().(ed25519.PublicKey)[:])
- sth, err := th.Sign(sm.signer)
- if err != nil {
- return nil, fmt.Errorf("sign: %v", err)
- }
- return sth, nil
-}
-
-func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) {
- sm.RLock()
- defer sm.RUnlock()
- return &sm.tosign, nil
-}
-
-func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.CosignedTreeHead, error) {
- sm.RLock()
- defer sm.RUnlock()
- if len(sm.cosigned.SigIdent) == 0 {
- return nil, fmt.Errorf("no witness cosignatures available")
- }
- return &sm.cosigned, nil
-}
-
-func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error {
- sm.Lock()
- defer sm.Unlock()
-
- if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil {
- return fmt.Errorf("Verify: %v", err)
- }
- witness := types.Hash(vk[:])
- if _, ok := sm.cosignature[*witness]; ok {
- return fmt.Errorf("signature-signer pair is a duplicate")
- }
- sm.cosignature[*witness] = &types.SigIdent{
- Signature: sig,
- KeyHash: witness,
- }
-
- glog.V(3).Infof("accepted new cosignature from witness: %x", *witness)
- return nil
-}
-
-// rotate rotates the log's cosigned and stable STH. The caller must aquire the
-// source's read-write lock if there are concurrent reads and/or writes.
-func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) {
- if reflect.DeepEqual(sm.cosigned.SignedTreeHead, sm.tosign) {
- // cosigned and tosign are the same. So, we need to merge all
- // cosignatures that we already had with the new collected ones.
- for _, sigident := range sm.cosigned.SigIdent {
- if _, ok := sm.cosignature[*sigident.KeyHash]; !ok {
- sm.cosignature[*sigident.KeyHash] = sigident
- }
- }
- glog.V(3).Infof("cosigned tree head repeated, merged signatures")
- }
- var cosignatures []*types.SigIdent
- for _, sigident := range sm.cosignature {
- cosignatures = append(cosignatures, sigident)
- }
-
- // Update cosigned tree head
- sm.cosigned.SignedTreeHead = sm.tosign
- sm.cosigned.SigIdent = cosignatures
-
- // Update to-sign tree head
- sm.tosign = *next
- sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{} // TODO: on repeat we might want to not zero this
- glog.V(3).Infof("rotated tree heads")
-}