From 26b786d9857db21fdf110eaf9cb6d1d6e4e68ef9 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Mon, 13 Sep 2021 19:53:17 +0200 Subject: updated (co)signed tree head structures - Added key_hash in tree head, see motivation in api.md - Added separate types for (co)signed tree heads - Refactored tree head HTTP APIs to be current, see api.md --- cmd/tmp/cosign/main.go | 13 +++- pkg/instance/endpoint.go | 4 +- pkg/instance/endpoint_test.go | 18 +++-- pkg/mocks/sigsum_state_manager.go | 4 +- pkg/state/state_manager.go | 32 +++++---- pkg/state/state_manager_test.go | 148 ++++++++++++++++++++------------------ pkg/types/ascii.go | 66 +++++++---------- pkg/types/ascii_test.go | 34 ++------- pkg/types/trunnel.go | 7 +- pkg/types/trunnel_test.go | 4 +- pkg/types/types.go | 37 +++------- 11 files changed, 173 insertions(+), 194 deletions(-) diff --git a/cmd/tmp/cosign/main.go b/cmd/tmp/cosign/main.go index 629e7ac..42162e4 100644 --- a/cmd/tmp/cosign/main.go +++ b/cmd/tmp/cosign/main.go @@ -13,11 +13,17 @@ import ( ) var ( - url = flag.String("url", "http://localhost:6965/sigsum/v0", "base url") - sk = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "hex key") + url = flag.String("url", "http://localhost:6965/sigsum/v0", "base url") + sk = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "witness secret key (hex)") + log_vk = flag.String("log_vk", "cc0e7294a9d002c33aaa828efba6622ab1ce8ebdb8a795902555c2813133cfe8", "log public key (hex)") ) func main() { + log_vk, err := hex.DecodeString(*log_vk) + if err != nil { + log.Fatalf("DecodeString: %v", err) + } + priv, err := hex.DecodeString(*sk) if err != nil { log.Fatalf("DecodeString: %v", err) @@ -34,7 +40,8 @@ func main() { if err := sth.UnmarshalASCII(rsp.Body); err != nil { log.Fatalf("UnmarshalASCII: %v", err) } - fmt.Printf("%+v\n", sth) + sth.TreeHead.KeyHash = types.Hash(log_vk) + fmt.Printf("%+v\n\n", sth) msg := sth.TreeHead.Marshal() sig := ed25519.Sign(sk, msg) diff --git a/pkg/instance/endpoint.go b/pkg/instance/endpoint.go index ec87303..2387263 100644 --- a/pkg/instance/endpoint.go +++ b/pkg/instance/endpoint.go @@ -58,11 +58,11 @@ func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter, func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { glog.V(3).Info("handling get-tree-head-cosigned request") - sth, err := i.Stateman.Cosigned(ctx) + cth, err := i.Stateman.Cosigned(ctx) if err != nil { return http.StatusInternalServerError, err } - if err := sth.MarshalASCII(w); err != nil { + if err := cth.MarshalASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/endpoint_test.go index 51ec0f4..95b71d3 100644 --- a/pkg/instance/endpoint_test.go +++ b/pkg/instance/endpoint_test.go @@ -34,10 +34,14 @@ var ( TreeSize: 0, RootHash: types.Hash(nil), }, + Signature: &[types.SignatureSize]byte{}, + } + testCTH = &types.CosignedTreeHead{ + SignedTreeHead: *testSTH, SigIdent: []*types.SigIdent{ &types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, KeyHash: &[types.HashSize]byte{}, + Signature: &[types.SignatureSize]byte{}, }, }, } @@ -137,7 +141,7 @@ func TestAddCosignature(t *testing.T) { buf := func() io.Reader { return bytes.NewBufferString(fmt.Sprintf( "%s%s%x%s"+"%s%s%x%s", - types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, + types.Cosignature, types.Delim, make([]byte, types.SignatureSize), types.EOL, types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, )) } @@ -311,10 +315,10 @@ func TestGetTreeToSign(t *testing.T) { func TestGetTreeCosigned(t *testing.T) { for _, table := range []struct { description string - expect bool // set if a mock answer is expected - rsp *types.SignedTreeHead // signed tree head from Trillian client - err error // error from Trillian client - wantCode int // HTTP status ok + expect bool // set if a mock answer is expected + rsp *types.CosignedTreeHead // cosigned tree head from Trillian client + err error // error from Trillian client + wantCode int // HTTP status ok }{ { description: "invalid: backend failure", @@ -325,7 +329,7 @@ func TestGetTreeCosigned(t *testing.T) { { description: "valid", expect: true, - rsp: testSTH, + rsp: testCTH, wantCode: http.StatusOK, }, } { diff --git a/pkg/mocks/sigsum_state_manager.go b/pkg/mocks/sigsum_state_manager.go index b999677..594d0a1 100644 --- a/pkg/mocks/sigsum_state_manager.go +++ b/pkg/mocks/sigsum_state_manager.go @@ -50,10 +50,10 @@ func (mr *MockStateManagerMockRecorder) AddCosignature(arg0, arg1, arg2 interfac } // Cosigned mocks base method. -func (m *MockStateManager) Cosigned(arg0 context.Context) (*types.SignedTreeHead, error) { +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.SignedTreeHead) + ret0, _ := ret[0].(*types.CosignedTreeHead) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go index 7ddf986..a08fd84 100644 --- a/pkg/state/state_manager.go +++ b/pkg/state/state_manager.go @@ -3,6 +3,7 @@ package state import ( "context" "crypto" + "crypto/ed25519" "fmt" "reflect" "sync" @@ -18,7 +19,7 @@ import ( type StateManager interface { Latest(context.Context) (*types.SignedTreeHead, error) ToSign(context.Context) (*types.SignedTreeHead, error) - Cosigned(context.Context) (*types.SignedTreeHead, error) + Cosigned(context.Context) (*types.CosignedTreeHead, error) AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error Run(context.Context) } @@ -33,7 +34,7 @@ type StateManagerSingle struct { sync.RWMutex // cosigned is the current cosigned tree head that is being served - cosigned types.SignedTreeHead + cosigned types.CosignedTreeHead // tosign is the current tree head that is being cosigned by witnesses tosign types.SignedTreeHead @@ -56,18 +57,19 @@ func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interva return nil, fmt.Errorf("Latest: %v", err) } - sm.cosigned = *sth - sm.tosign = *sth - sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ - *sth.SigIdent[0].KeyHash: sth.SigIdent[0], // log signature + 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) - nextTreeHead, err := sm.Latest(ictx) + nextSTH, err := sm.Latest(ictx) if err != nil { glog.Warningf("rotate failed: Latest: %v", err) return @@ -75,7 +77,7 @@ func (sm *StateManagerSingle) Run(ctx context.Context) { sm.Lock() defer sm.Unlock() - sm.rotate(nextTreeHead) + sm.rotate(nextSTH) }) } @@ -84,6 +86,7 @@ func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead 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) @@ -97,9 +100,12 @@ func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, return &sm.tosign, nil } -func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { +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 } @@ -126,7 +132,7 @@ func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.Verif // 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.TreeHead, sm.tosign.TreeHead) { + 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 { @@ -142,13 +148,11 @@ func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { } // Update cosigned tree head - sm.cosigned.TreeHead = sm.tosign.TreeHead + 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{ - *next.SigIdent[0].KeyHash: next.SigIdent[0], // log signature - } + 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") } diff --git a/pkg/state/state_manager_test.go b/pkg/state/state_manager_test.go index acc2319..6650544 100644 --- a/pkg/state/state_manager_test.go +++ b/pkg/state/state_manager_test.go @@ -23,14 +23,21 @@ var ( Timestamp: 0, TreeSize: 0, RootHash: types.Hash(nil), + KeyHash: types.Hash(testPub[:]), } testSigIdent = &types.SigIdent{ Signature: testSig, KeyHash: types.Hash(testPub[:]), } testSTH = &types.SignedTreeHead{ - TreeHead: *testTH, - SigIdent: []*types.SigIdent{testSigIdent}, + TreeHead: *testTH, + Signature: testSig, + } + testCTH = &types.CosignedTreeHead{ + SignedTreeHead: *testSTH, + SigIdent: []*types.SigIdent{ + testSigIdent, + }, } testSignerOK = &mocks.TestSigner{testPub, testSig, nil} testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} @@ -72,14 +79,14 @@ func TestNewStateManagerSingle(t *testing.T) { if err != nil { return } - if got, want := &sm.cosigned, table.wantSth; !reflect.DeepEqual(got, want) { + 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) } // we only have log signature on startup - if got, want := len(sm.cosignature), 1; got != want { + if got, want := len(sm.cosignature), 0; got != want { t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description) } }() @@ -157,16 +164,23 @@ func TestToSign(t *testing.T) { func TestCosigned(t *testing.T) { description := "valid" sm := StateManagerSingle{ - cosigned: *testSTH, + cosigned: *testCTH, } - sth, err := sm.Cosigned(context.Background()) + cth, err := sm.Cosigned(context.Background()) if err != nil { t.Errorf("Cosigned should not fail with error: %v", err) return } - if got, want := sth, testSTH; !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) + cth, err = sm.Cosigned(context.Background()) + if err == nil { + t.Errorf("Cosigned should fail without witness cosignatures") + return + } } func TestAddCosignature(t *testing.T) { @@ -202,15 +216,15 @@ func TestAddCosignature(t *testing.T) { }, } { sth, _ := table.th.Sign(testSignerOK) - logKeyHash := sth.SigIdent[0].KeyHash - logSigIdent := sth.SigIdent[0] + cth := &types.CosignedTreeHead{ + SignedTreeHead: *sth, + SigIdent: []*types.SigIdent{}, + } sm := &StateManagerSingle{ - signer: testSignerOK, - cosigned: *sth, - tosign: *sth, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *logKeyHash: logSigIdent, - }, + signer: testSignerOK, + cosigned: *cth, + tosign: *sth, + cosignature: map[[types.HashSize]byte]*types.SigIdent{}, } // Prepare witness signature @@ -218,11 +232,13 @@ func TestAddCosignature(t *testing.T) { if err != nil { t.Fatalf("Sign: %v", err) } - witnessKeyHash := sth.SigIdent[0].KeyHash - witnessSigIdent := sth.SigIdent[0] + si := &types.SigIdent{ + KeyHash: types.Hash(table.signer.Public().(ed25519.PublicKey)[:]), + Signature: sth.Signature, + } // Add witness signature - err = sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature) + err = sm.AddCosignature(context.Background(), table.vk, si.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) } @@ -230,33 +246,24 @@ func TestAddCosignature(t *testing.T) { continue } - // We should have two signatures (log + witness) - if got, want := len(sm.cosignature), 2; got != want { + // We should have one witness signature + if got, want := len(sm.cosignature), 1; got != want { t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description) continue } - // check that log signature is there - sigident, ok := sm.cosignature[*logKeyHash] - if !ok { - t.Errorf("log signature is missing") - continue - } - if got, want := sigident, logSigIdent; !reflect.DeepEqual(got, want) { - t.Errorf("got log sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } // check that witness signature is there - sigident, ok = sm.cosignature[*witnessKeyHash] + sigident, ok := sm.cosignature[*si.KeyHash] if !ok { t.Errorf("witness signature is missing") continue } - if got, want := sigident, witnessSigIdent; !reflect.DeepEqual(got, want) { + if got, want := si, sigident; !reflect.DeepEqual(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, witnessSigIdent.Signature); err == nil { + if err := sm.AddCosignature(context.Background(), table.vk, si.Signature); err == nil { t.Errorf("duplicate witness signature accepted as valid") } } @@ -292,81 +299,86 @@ func TestRotate(t *testing.T) { { description: "tosign tree head repated, but got one new witnes signature", before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, + cosigned: types.CosignedTreeHead{ + SignedTreeHead: types.SignedTreeHead{ + TreeHead: *th0, + Signature: log.Signature, + }, + SigIdent: []*types.SigIdent{wit1}, }, tosign: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log}, + TreeHead: *th0, + Signature: log.Signature, }, cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, *wit2.KeyHash: wit2, // the new witness signature }, }, next: &types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, + TreeHead: *th1, + Signature: log.Signature, }, after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1, wit2}, + cosigned: types.CosignedTreeHead{ + SignedTreeHead: types.SignedTreeHead{ + TreeHead: *th0, + Signature: log.Signature, + }, + SigIdent: []*types.SigIdent{wit1, wit2}, }, tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig + TreeHead: *th1, + Signature: log.Signature, }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{}, }, }, { description: "tosign tree head did not repeat, it got one witness signature", before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, + cosigned: types.CosignedTreeHead{ + SignedTreeHead: types.SignedTreeHead{ + TreeHead: *th0, + Signature: log.Signature, + }, + SigIdent: []*types.SigIdent{wit1}, }, tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, + TreeHead: *th1, + Signature: log.Signature, }, cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, - *wit2.KeyHash: wit2, // the only witness that signed tosign + *log.KeyHash: wit2, }, }, next: &types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, + TreeHead: *th2, + Signature: log.Signature, }, after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log, wit2}, + cosigned: types.CosignedTreeHead{ + SignedTreeHead: types.SignedTreeHead{ + TreeHead: *th1, + Signature: log.Signature, + }, + SigIdent: []*types.SigIdent{wit2}, }, tosign: types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig + TreeHead: *th2, + Signature: log.Signature, }, + cosignature: map[[types.HashSize]byte]*types.SigIdent{}, }, }, } { table.before.rotate(table.next) - if got, want := table.before.cosigned.TreeHead, table.after.cosigned.TreeHead; !reflect.DeepEqual(got, want) { + 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.TreeHead, table.after.tosign.TreeHead; !reflect.DeepEqual(got, want) { + 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.tosign.SigIdent, table.after.tosign.SigIdent) 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) } diff --git a/pkg/types/ascii.go b/pkg/types/ascii.go index f0a96ad..72abfcb 100644 --- a/pkg/types/ascii.go +++ b/pkg/types/ascii.go @@ -18,7 +18,7 @@ const ( // NumField* is the number of unique keys in an incoming ASCII message NumFieldLeaf = 4 - NumFieldSignedTreeHead = 5 + NumFieldSignedTreeHead = 4 NumFieldConsistencyProof = 3 NumFieldInclusionProof = 3 NumFieldLeavesRequest = 2 @@ -28,11 +28,11 @@ const ( NumFieldCosignatureRequest = 2 // New leaf keys - ShardHint = "shard_hint" - Checksum = "checksum" - Signature = "signature" - VerificationKey = "verification_key" - DomainHint = "domain_hint" + ShardHint = "shard_hint" + Checksum = "checksum" + Signature = "signature" + VerificationKey = "verification_key" + DomainHint = "domain_hint" // Inclusion proof keys LeafHash = "leaf_hash" @@ -53,8 +53,9 @@ const ( TreeSize = "tree_size" RootHash = "root_hash" - // Signature and signer-identity keys - KeyHash = "key_hash" + // Witness signature-identity keys + KeyHash = "key_hash" + Cosignature = "cosignature" ) // MessageASCI is a wrapper that manages ASCII key-value pairs @@ -219,19 +220,29 @@ func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { if err := writeASCII(w, RootHash, hex.EncodeToString(sth.RootHash[:])); err != nil { return fmt.Errorf("writeASCII: %v", err) } - for _, sigident := range sth.SigIdent { - if err := sigident.MarshalASCII(w); err != nil { - return fmt.Errorf("MarshalASCII: %v", err) + if err := writeASCII(w, Signature, hex.EncodeToString(sth.Signature[:])); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + return nil +} + +func (cth *CosignedTreeHead) MarshalASCII(w io.Writer) error { + if err := cth.SignedTreeHead.MarshalASCII(w); err != nil { + return fmt.Errorf("writeASCII: %v", err) + } + for _, si := range cth.SigIdent { + if err := si.MarshalASCII(w); err != nil { + return fmt.Errorf("writeASCII: %v", err) } } return nil } func (si *SigIdent) MarshalASCII(w io.Writer) error { - if err := writeASCII(w, Signature, hex.EncodeToString(si.Signature[:])); err != nil { + if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { return fmt.Errorf("writeASCII: %v", err) } - if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { + if err := writeASCII(w, Cosignature, hex.EncodeToString(si.Signature[:])); err != nil { return fmt.Errorf("writeASCII: %v", err) } return nil @@ -280,7 +291,6 @@ func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { return fmt.Errorf("NewMessageASCII: %v", err) } - // TreeHead if sth.Timestamp, err = msg.GetUint64(Timestamp); err != nil { return fmt.Errorf("GetUint64(Timestamp): %v", err) } @@ -290,30 +300,8 @@ func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { if sth.RootHash, err = msg.GetHash(RootHash); err != nil { return fmt.Errorf("GetHash(RootHash): %v", err) } - - // SigIdent - signatures := msg.GetStrings(Signature) - if len(signatures) == 0 { - return fmt.Errorf("no signer") - } - keyHashes := msg.GetStrings(KeyHash) - if len(signatures) != len(keyHashes) { - return fmt.Errorf("mismatched signature-signer count") - } - sth.SigIdent = make([]*SigIdent, 0, len(signatures)) - for i, n := 0, len(signatures); i < n; i++ { - var signature [SignatureSize]byte - if err := decodeHex(signatures[i], signature[:]); err != nil { - return fmt.Errorf("decodeHex: %v", err) - } - var hash [HashSize]byte - if err := decodeHex(keyHashes[i], hash[:]); err != nil { - return fmt.Errorf("decodeHex: %v", err) - } - sth.SigIdent = append(sth.SigIdent, &SigIdent{ - Signature: &signature, - KeyHash: &hash, - }) + if sth.Signature, err = msg.GetSignature(Signature); err != nil { + return fmt.Errorf("GetHash(RootHash): %v", err) } return nil } @@ -401,7 +389,7 @@ func (req *CosignatureRequest) UnmarshalASCII(r io.Reader) error { return fmt.Errorf("NewMessageASCII: %v", err) } - if req.Signature, err = msg.GetSignature(Signature); err != nil { + if req.Signature, err = msg.GetSignature(Cosignature); err != nil { return fmt.Errorf("GetSignature: %v", err) } if req.KeyHash, err = msg.GetHash(KeyHash); err != nil { diff --git a/pkg/types/ascii_test.go b/pkg/types/ascii_test.go index c3f9e98..fc3f486 100644 --- a/pkg/types/ascii_test.go +++ b/pkg/types/ascii_test.go @@ -134,26 +134,14 @@ func TestSignedTreeHeadMarshalASCII(t *testing.T) { TreeSize: 456, RootHash: testBuffer32, }, - SigIdent: []*SigIdent{ - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, + Signature: testBuffer64, } wantBuf := bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", Timestamp, Delim, 123, EOL, TreeSize, Delim, 456, EOL, RootHash, Delim, testBuffer32[:], EOL, Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, )) buf := bytes.NewBuffer(nil) if err := sth.MarshalASCII(buf); err != nil { @@ -236,14 +224,11 @@ func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { { description: "valid", buf: bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", + "%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", Timestamp, Delim, 123, EOL, TreeSize, Delim, 456, EOL, RootHash, Delim, testBuffer32[:], EOL, Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, - Signature, Delim, testBuffer64[:], EOL, - KeyHash, Delim, testBuffer32[:], EOL, )), wantSth: &SignedTreeHead{ TreeHead: TreeHead{ @@ -251,16 +236,7 @@ func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { TreeSize: 456, RootHash: testBuffer32, }, - SigIdent: []*SigIdent{ - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - &SigIdent{ - Signature: testBuffer64, - KeyHash: testBuffer32, - }, - }, + Signature: testBuffer64, }, }, } { @@ -436,7 +412,7 @@ func TestCosignatureRequestUnmarshalASCII(t *testing.T) { description: "valid", buf: bytes.NewBufferString(fmt.Sprintf( "%s%s%x%s"+"%s%s%x%s", - Signature, Delim, testBuffer64[:], EOL, + Cosignature, Delim, testBuffer64[:], EOL, KeyHash, Delim, testBuffer32[:], EOL, )), wantReq: &CosignatureRequest{ diff --git a/pkg/types/trunnel.go b/pkg/types/trunnel.go index 268f6f7..5350c5b 100644 --- a/pkg/types/trunnel.go +++ b/pkg/types/trunnel.go @@ -10,6 +10,8 @@ const ( MessageSize = 8 + HashSize // LeafSize is the number of bytes in a Trunnel-encoded leaf LeafSize = MessageSize + SignatureSize + HashSize + // TreeHeadSize is the number of bytes in a Trunnel-encoded tree head + TreeHeadSize = 8 + 8 + HashSize + HashSize ) // Marshal returns a Trunnel-encoded message @@ -30,10 +32,11 @@ func (l *Leaf) Marshal() []byte { // Marshal returns a Trunnel-encoded tree head func (th *TreeHead) Marshal() []byte { - buf := make([]byte, 8+8+HashSize) + buf := make([]byte, TreeHeadSize) binary.BigEndian.PutUint64(buf[0:8], th.Timestamp) binary.BigEndian.PutUint64(buf[8:16], th.TreeSize) - copy(buf[16:], th.RootHash[:]) + copy(buf[16:16+HashSize], th.RootHash[:]) + copy(buf[16+HashSize:], th.KeyHash[:]) return buf } diff --git a/pkg/types/trunnel_test.go b/pkg/types/trunnel_test.go index 297578c..a3ae1ba 100644 --- a/pkg/types/trunnel_test.go +++ b/pkg/types/trunnel_test.go @@ -48,16 +48,18 @@ func TestMarshalLeaf(t *testing.T) { } func TestMarshalTreeHead(t *testing.T) { - description := "valid: timestamp 16909060, tree size 72623859790382856, root hash 0x00,0x01,..." + description := "valid: timestamp 16909060, tree size 72623859790382856, root hash & key hash 0x00,0x01,..." th := &TreeHead{ Timestamp: 16909060, TreeSize: 72623859790382856, RootHash: testBuffer32, + KeyHash: testBuffer32, } want := bytes.Join([][]byte{ []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, testBuffer32[:], + testBuffer32[:], }, nil) if got := th.Marshal(); !bytes.Equal(got, want) { t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) diff --git a/pkg/types/types.go b/pkg/types/types.go index 96e2b18..bc58c98 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -32,78 +32,67 @@ func (e Endpoint) Path(components ...string) string { return strings.Join(append(components, string(e)), "/") } -// Leaf is the log's Merkle tree leaf. type Leaf struct { Message SigIdent } -// Message is composed of a shard hint and a checksum. The submitter selects -// these values to fit the log's shard interval and the opaque data in question. type Message struct { ShardHint uint64 Checksum *[HashSize]byte } -// SigIdent is composed of a signature-signer pair. The signature is computed -// over the Trunnel-serialized leaf message. KeyHash identifies the signer. type SigIdent struct { Signature *[SignatureSize]byte KeyHash *[HashSize]byte } -// SignedTreeHead is composed of a tree head and a list of signature-signer -// pairs. Each signature is computed over the Trunnel-serialized tree head. type SignedTreeHead struct { TreeHead + Signature *[SignatureSize]byte +} + +type CosignedTreeHead struct { + SignedTreeHead SigIdent []*SigIdent } -// TreeHead is the log's tree head. type TreeHead struct { Timestamp uint64 TreeSize uint64 RootHash *[HashSize]byte + KeyHash *[HashSize]byte } -// ConsistencyProof is a consistency proof that proves the log's append-only -// property. type ConsistencyProof struct { NewSize uint64 OldSize uint64 Path []*[HashSize]byte } -// InclusionProof is an inclusion proof that proves a leaf is included in the -// log. type InclusionProof struct { TreeSize uint64 LeafIndex uint64 Path []*[HashSize]byte } -// LeafList is a list of leaves type LeafList []*Leaf -// ConsistencyProofRequest is a get-consistency-proof request type ConsistencyProofRequest struct { NewSize uint64 OldSize uint64 } -// InclusionProofRequest is a get-proof-by-hash request type InclusionProofRequest struct { LeafHash *[HashSize]byte TreeSize uint64 } -// LeavesRequest is a get-leaves request type LeavesRequest struct { StartSize uint64 EndSize uint64 } -// LeafRequest is an add-leaf request type LeafRequest struct { Message Signature *[SignatureSize]byte @@ -111,7 +100,6 @@ type LeafRequest struct { DomainHint string } -// CosignatureRequest is an add-cosignature request type CosignatureRequest struct { SigIdent } @@ -123,17 +111,12 @@ func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { return nil, fmt.Errorf("Sign: %v", err) } - sigident := SigIdent{ - KeyHash: Hash(signer.Public().(ed25519.PublicKey)[:]), + sth := &SignedTreeHead{ + TreeHead: *th, Signature: &[SignatureSize]byte{}, } - copy(sigident.Signature[:], sig) - return &SignedTreeHead{ - TreeHead: *th, - SigIdent: []*SigIdent{ - &sigident, - }, - }, nil + copy(sth.Signature[:], sig) + return sth, nil } // Verify verifies the tree head signature using the log's signature scheme -- cgit v1.2.3