github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/formats/checkpoints/combine_signatures.go (about)

     1  // Copyright 2021 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package checkpoints provides functionality for handling checkpoints.
    16  package checkpoints
    17  
    18  import (
    19  	"fmt"
    20  	"sort"
    21  
    22  	"golang.org/x/mod/sumdb/note"
    23  )
    24  
    25  // Combine returns a checkpoint with the union of all signatures on the provided checkpoints from known witnesses.
    26  // Signatures from unknown witnesses are discarded.
    27  //
    28  // Combined signatures will always be ordered with the log signature first, followed by
    29  // witness signatures ordered by their key hash (ascending).
    30  //
    31  // All cps:
    32  //   - MUST contain identical checkpoint bodies
    33  //   - MUST be signed by the log whose verifier is provided.
    34  //   - MAY be signed by one or more witnesses.
    35  //
    36  // if this isn't the case an error is returned.
    37  func Combine(cps [][]byte, logSigV note.Verifier, witSigVs note.Verifiers) ([]byte, error) {
    38  	var ret *note.Note
    39  	sigs := make(map[uint32]note.Signature)
    40  
    41  	for i, cp := range cps {
    42  		// Ensure the Checkpoint is for the specific log
    43  		candN, err := note.Open(cp, note.VerifierList(logSigV))
    44  		if err != nil {
    45  			return nil, fmt.Errorf("checkpoint %d is not signed by log: %v", i, err)
    46  		}
    47  		// if this is the first CP, then just take it.
    48  		if i == 0 {
    49  			ret = candN
    50  			// Save the log sig so it's always first in the list when we serialise
    51  			ret.Sigs = []note.Signature{candN.Sigs[0]}
    52  			// But remove all other sigs
    53  			ret.UnverifiedSigs = nil
    54  		}
    55  
    56  		// Now gather witness sigs.
    57  		// It's easier to just re-open with the verifiers we're interested in rather than trying to
    58  		// dig through note.Sigs separating the log sig from the witnesses.
    59  		candN, err = note.Open(cp, witSigVs)
    60  		if err != nil {
    61  			nErr, ok := err.(*note.UnverifiedNoteError)
    62  			if !ok {
    63  				return nil, fmt.Errorf("failed to open checkpoint %d: %v", i, err)
    64  			}
    65  			// Continue running
    66  			candN = nErr.Note
    67  		}
    68  
    69  		if candN.Text != ret.Text {
    70  			return nil, fmt.Errorf("checkpoint %d has differing content", i)
    71  		}
    72  
    73  		for _, s := range candN.Sigs {
    74  			sigs[s.Hash] = s
    75  		}
    76  	}
    77  
    78  	for _, s := range sigs {
    79  		ret.Sigs = append(ret.Sigs, s)
    80  	}
    81  	sort.Slice(ret.Sigs, func(i, j int) bool {
    82  		// The log key is always first
    83  		if ret.Sigs[i].Hash == logSigV.KeyHash() {
    84  			return true
    85  		}
    86  		if ret.Sigs[j].Hash == logSigV.KeyHash() {
    87  			return false
    88  		}
    89  
    90  		return ret.Sigs[i].Hash < ret.Sigs[j].Hash
    91  	})
    92  	return note.Sign(ret)
    93  }