Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: seal format default and bagged consitency check corner case #30

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion massifs/heightindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TreeSize(height uint8) uint64 {
return TreeCount(height) * LogEntryBytes
}

// TreeCount returns the node count
// TreeCount returns the node count
func TreeCount(height uint8) uint64 {
return ((1 << height) - 1)
}
65 changes: 47 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,29 @@ 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
}
93 changes: 93 additions & 0 deletions massifs/massifcontextverifiedv0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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())
if err != nil {
return nil, err
}

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
Loading