Skip to content

Commit

Permalink
fix: seal format default and bagged consitency check corner case
Browse files Browse the repository at this point in the history
* fix: test helpers now default to producing v1 seals, but the version was
  not set.
* fix: the legacy consistency proof check accounts properly for the
  case where mmrsizeA == mmrsizeB
  • Loading branch information
Robin Bryce committed Oct 30, 2024
1 parent d214f3f commit 1662c25
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 28 deletions.
66 changes: 48 additions & 18 deletions massifs/massifcontextverified.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,7 @@ func (mc *MassifContext) verifyContext(
ctx context.Context, options ReaderOptions,
) (*VerifiedContext, error) {

var ok bool
var err error
var peaksB [][]byte

// This checks that any un-committed data is consistent with the latest seal available for the massif

Expand All @@ -171,6 +169,23 @@ func (mc *MassifContext) verifyContext(
return nil, err
}

if state.Version == int(MMRStateVersion1) {
return mc.verifyContextV1(msg, state, options)
}
return mc.verifyContextV0(msg, state, options)
}

func (mc *MassifContext) verifyContextV1(
msg *cose.CoseSign1Message, state MMRState, options ReaderOptions,
) (*VerifiedContext, error) {
var ok bool
var err error
var peaksB [][]byte

if state.Version != int(MMRStateVersion1) {
return nil, fmt.Errorf("unsupported MMR state version %d", state.Version)
}

// get the peaks from the local store, we are checking the store against the
// latest additions. as we verify the signature below, any changes to the
// store will be caught.
Expand All @@ -179,21 +194,9 @@ func (mc *MassifContext) verifyContext(
return nil, err
}

// NOTICE: The verification uses the public key that is provided on the
// message. If the caller wants to ensure the massif is signed by the
// expected key then they must obtain a copy of the public key from a source
// they trust and supply it as an option.
pubKeyProvider := cose.NewCWTPublicKeyProvider(msg)

if options.trustedSealerPubKey != nil {
var remotePub crypto.PublicKey
remotePub, _, err = pubKeyProvider.PublicKey()
if err != nil {
return nil, err
}
if !options.trustedSealerPubKey.Equal(remotePub) {
return nil, ErrRemoteSealKeyMatchFailed
}
pubKeyProvider, err := mc.sealPublicKeyProvider(msg, options)
if err != nil {
return nil, err
}

// Ensure the peaks we read from the store are the ones that were signed.
Expand All @@ -218,7 +221,7 @@ func (mc *MassifContext) verifyContext(
err, mc.Start.MassifIndex, mc.TenantIdentity)
}
if !ok {
// We don't expect false without error, but we
// We don't expect false without error.
return nil, fmt.Errorf("%w: failed to verify accumulator state massif %d for tenant %s",
mmr.ErrConsistencyCheck, mc.Start.MassifIndex, mc.TenantIdentity)
}
Expand Down Expand Up @@ -252,3 +255,30 @@ func (mc *MassifContext) verifyContext(
ConsistentRoots: peaksB,
}, nil
}

func (mc *MassifContext) sealPublicKeyProvider(
msg *cose.CoseSign1Message, options ReaderOptions,
) (*cose.CWTPublicKeyProvider, error) {

var err error

// NOTICE: The verification uses the public key that is provided on the
// message. If the caller wants to ensure the massif is signed by the
// expected key then they must obtain a copy of the public key from a source
// they trust and supply it as an option.
pubKeyProvider := cose.NewCWTPublicKeyProvider(msg)
if options.trustedSealerPubKey == nil {
return pubKeyProvider, nil
}

var remotePub crypto.PublicKey
remotePub, _, err = pubKeyProvider.PublicKey()
if err != nil {
return nil, err
}
if !options.trustedSealerPubKey.Equal(remotePub) {
return nil, ErrRemoteSealKeyMatchFailed
}
return pubKeyProvider, nil
}

89 changes: 89 additions & 0 deletions massifs/massifcontextverifiedv0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package massifs

import (
"crypto/sha256"
"fmt"

"github.com/datatrails/go-datatrails-common/cose"
"github.com/datatrails/go-datatrails-merklelog/mmr"
)

// verifyContextV0 reads and verifies the V0 seal, which will remain important
// for replicas for a while.
func (mc *MassifContext) verifyContextV0(
msg *cose.CoseSign1Message, state MMRState, options ReaderOptions,
) (*VerifiedContext, error) {

var ok bool
var err error
var rootB []byte

if state.Version != int(MMRStateVersion0) {
return nil, fmt.Errorf("unsupported MMR state version %d", state.Version)
}

state.LegacySealRoot, err = mmr.GetRoot(state.MMRSize, mc, sha256.New())
pubKeyProvider, err := mc.sealPublicKeyProvider(msg, options)
if err != nil {
return nil, err
}

err = VerifySignedCheckPoint(
*options.codec, pubKeyProvider, msg, state, nil,
)
if err != nil {
return nil, fmt.Errorf(
"%w: failed to verify seal for massif %d for tenant %s: %v",
ErrSealVerifyFailed, mc.Start.MassifIndex, mc.TenantIdentity, err)
}
cp, err := mmr.IndexConsistencyProofBagged(
state.MMRSize, mc.RangeCount(), mc, sha256.New())
if err != nil {
return nil, fmt.Errorf(
"%w: error creating bagged consistency proof from %d for massif %d, for tenant %s",
err, state.MMRSize, mc.Start.MassifIndex, mc.TenantIdentity)
}

ok, rootB, err = mmr.CheckConsistencyBagged(mc, sha256.New(), cp, state.LegacySealRoot)
if err != nil {
return nil, fmt.Errorf(
"%w: error verifying bagged consistency proof from %d for massif %d, for tenant %s",
err, state.MMRSize, mc.Start.MassifIndex, mc.TenantIdentity)
}
if !ok {
// We don't expect false without error, but we
return nil, fmt.Errorf("%w: failed to verify bagged state for massif %d for tenant %s",
mmr.ErrConsistencyCheck, mc.Start.MassifIndex, mc.TenantIdentity)
}
// If the caller has provided a trusted base state, also verify against
// that. Typically this is used for 3d party verification, the 3rd party has
// saved a previously verified state in a local store, and they want to
// check the remote log is consistent with the log portion they have locally
// before replicating the new data.
if options.trustedBaseState != nil {

cp, err := mmr.IndexConsistencyProofBagged(
state.MMRSize, mc.RangeCount(), mc, sha256.New())
if err != nil {
return nil, fmt.Errorf(
"%w: error checking consistency with trusted base state from %d for tenant %s",
err, options.trustedBaseState.MMRSize, mc.TenantIdentity)
}

ok, _, err = mmr.CheckConsistencyBagged(mc, sha256.New(), cp, options.trustedBaseState.LegacySealRoot)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf(
"%w: the accumulator produced for the trusted base state doesn't match the root produced for the seal state fetched from the log",
mmr.ErrConsistencyCheck)
}
}
return &VerifiedContext{
MassifContext: *mc,
Sign1Message: *msg,
MMRState: state,
ConsistentRoots: [][]byte{rootB},
}, nil
}
1 change: 1 addition & 0 deletions massifs/testcommitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func (c *TestMinimalCommitter) ContextCommitted(ctx context.Context, tenantIdent
}

state := MMRState{
Version: int(MMRStateVersion1),
MMRSize: mmrSize,
Peaks: peaks,
Timestamp: time.Now().UnixMilli(),
Expand Down
19 changes: 9 additions & 10 deletions mmr/verifyconsistencybagged.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,25 @@ func VerifyConsistencyBagged(
hasher hash.Hash, peakHashesA [][]byte,
proof ConsistencyProof, rootA []byte, rootB []byte) bool {

// A zero length path not valid, even in the case where the mmr's are
// identical (root a == root b)
if len(proof.PathBagged) == 0 {
// There must be something to prove
if len(peakHashesA) == 0 {
return false
}

// There must be something to prove
if len(peakHashesA) == 0 {
// Check the peakHashesA, which will have been retrieved from the updated
// log, recreate rootA. rootA should have come from a previous Merkle
// Signed Root.
if !bytes.Equal(HashPeaksRHS(hasher, peakHashesA), rootA) {
return false
}

// Catch the case where mmr b is exactly mmr a
if bytes.Equal(rootA, rootB) {
if len(proof.PathBagged) == 0 && bytes.Equal(rootA, rootB) {
return true
}

// Check the peakHashesA, which will have been retrieved from the updated
// log, recreate rootA. rootA should have come from a previous Merkle
// Signed Root.
if !bytes.Equal(HashPeaksRHS(hasher, peakHashesA), rootA) {
// Otherwise, s zero length path not valid
if len(proof.PathBagged) == 0 {
return false
}

Expand Down

0 comments on commit 1662c25

Please sign in to comment.