Skip to content

Commit

Permalink
chore: support multiple multihash algorithms in protocol
Browse files Browse the repository at this point in the history
Closes #522

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask committed Dec 15, 2020
1 parent 188eb39 commit 1c3e1f1
Show file tree
Hide file tree
Showing 20 changed files with 245 additions and 73 deletions.
4 changes: 2 additions & 2 deletions pkg/api/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type Protocol struct {
// GenesisTime is inclusive starting logical blockchain time that this protocol applies to.
// (e.g. block number in a blockchain)
GenesisTime uint64 `json:"genesisTime"`
// MultihashAlgorithm is multihash algorithm code.
MultihashAlgorithm uint `json:"multihashAlgorithm"`
// MultihashAlgorithms are supported multihash algorithm codes
MultihashAlgorithms []uint `json:"multihashAlgorithms"`
// MaxOperationCount defines maximum number of operations per batch.
MaxOperationCount uint `json:"maxOperationCount"`
// MaxOperationSize is maximum operation size in bytes (used to reject operations before parsing them)
Expand Down
12 changes: 9 additions & 3 deletions pkg/hashing/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,20 @@ func IsSupportedMultihash(encodedMultihash string) bool {
return multihash.ValidCode(code)
}

// IsComputedUsingMultihashAlgorithm checks to see if the given encoded hash has been hashed using multihash code.
func IsComputedUsingMultihashAlgorithm(encodedMultihash string, code uint64) bool {
// IsComputedUsingMultihashAlgorithms checks to see if the given encoded hash has been hashed using one of supplied code.
func IsComputedUsingMultihashAlgorithms(encodedMultihash string, codes []uint) bool {
mhCode, err := GetMultihashCode(encodedMultihash)
if err != nil {
return false
}

return mhCode == code
for _, supported := range codes {
if mhCode == uint64(supported) {
return true
}
}

return false
}

// GetMultihashCode returns multihash code from encoded multihash.
Expand Down
6 changes: 3 additions & 3 deletions pkg/hashing/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ func TestIsComputedUsingHashAlgorithm(t *testing.T) {
require.NotNil(t, hash)

key := encoder.EncodeToString(hash)
ok := IsComputedUsingMultihashAlgorithm(key, sha2_256)
ok := IsComputedUsingMultihashAlgorithms(key, []uint{sha2_256})
require.True(t, ok)

// use random code to fail
ok = IsComputedUsingMultihashAlgorithm(key, 55)
ok = IsComputedUsingMultihashAlgorithms(key, []uint{55})
require.False(t, ok)

ok = IsComputedUsingMultihashAlgorithm("invalid", sha2_256)
ok = IsComputedUsingMultihashAlgorithms("invalid", []uint{sha2_256})
require.False(t, ok)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/mocks/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func GetDefaultProtocolParameters() protocol.Protocol {
//nolint:gomnd
return protocol.Protocol{
GenesisTime: 0,
MultihashAlgorithm: sha2_256,
MultihashAlgorithms: []uint{sha2_256},
MaxOperationCount: 2,
MaxOperationSize: MaxOperationByteSize,
MaxOperationHashLength: 100,
Expand Down
26 changes: 13 additions & 13 deletions pkg/processor/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func TestUpdateDocument(t *testing.T) {
pubJWK, err := pubkey.GetPublicKeyJWK(&updateKey.PublicKey)
require.NoError(t, err)

rv, err := commitment.GetRevealValue(pubJWK, getProtocol(1).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(pubJWK, getProtocol(1).MultihashAlgorithms[0])
require.NoError(t, err)

// protocol value for hashing algorithm changed at block 100
Expand Down Expand Up @@ -210,7 +210,7 @@ func TestUpdateDocument(t *testing.T) {
require.NoError(t, err)

// previous operation commit value was calculated with protocol value at block 50
rv, err := commitment.GetRevealValue(pubJWK, getProtocol(50).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(pubJWK, getProtocol(50).MultihashAlgorithms[0])
require.NoError(t, err)

// protocol value for hashing algorithm changed at block 100
Expand Down Expand Up @@ -478,7 +478,7 @@ func TestRecover(t *testing.T) {
pubJWK, err := pubkey.GetPublicKeyJWK(&recoveryKey.PublicKey)
require.NoError(t, err)

rv, err := commitment.GetRevealValue(pubJWK, getProtocol(1).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(pubJWK, getProtocol(1).MultihashAlgorithms[0])
require.NoError(t, err)

// hashing algorithm changed at block 100
Expand Down Expand Up @@ -538,7 +538,7 @@ func TestRecover(t *testing.T) {
pubJWK, err := pubkey.GetPublicKeyJWK(&nextRecoveryKey.PublicKey)
require.NoError(t, err)

rv, err := commitment.GetRevealValue(pubJWK, getProtocol(50).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(pubJWK, getProtocol(50).MultihashAlgorithms[0])
require.NoError(t, err)

// hashing algorithm changed at block 100
Expand Down Expand Up @@ -906,7 +906,7 @@ func getUpdateOperationWithSigner(s client.Signer, privateKey *ecdsa.PrivateKey,
Patches: []patch.Patch{jsonPatch},
}

deltaHash, err := hashing.CalculateModelMultihash(delta, getProtocol(blockNumber).MultihashAlgorithm)
deltaHash, err := hashing.CalculateModelMultihash(delta, getProtocol(blockNumber).MultihashAlgorithms[0])
if err != nil {
return nil, nil, err
}
Expand All @@ -926,7 +926,7 @@ func getUpdateOperationWithSigner(s client.Signer, privateKey *ecdsa.PrivateKey,
return nil, nil, err
}

rv, err := commitment.GetRevealValue(updatePubKey, getProtocol(blockNumber).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(updatePubKey, getProtocol(blockNumber).MultihashAlgorithms[0])
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -955,7 +955,7 @@ func generateKeyAndCommitment(p protocol.Protocol) (*ecdsa.PrivateKey, string, e
return nil, "", err
}

c, err := commitment.GetCommitment(pubKey, p.MultihashAlgorithm)
c, err := commitment.GetCommitment(pubKey, p.MultihashAlgorithms[0])
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -1046,7 +1046,7 @@ func getRecoverOperationWithSigner(signer client.Signer, recoveryKey, updateKey
}

func getRecoverRequest(signer client.Signer, deltaModel *model.DeltaModel, signedDataModel *model.RecoverSignedDataModel, blockNum uint64) (*model.RecoverRequest, error) {
deltaHash, err := hashing.CalculateModelMultihash(deltaModel, getProtocol(blockNum).MultihashAlgorithm)
deltaHash, err := hashing.CalculateModelMultihash(deltaModel, getProtocol(blockNum).MultihashAlgorithms[0])
if err != nil {
return nil, err
}
Expand All @@ -1058,7 +1058,7 @@ func getRecoverRequest(signer client.Signer, deltaModel *model.DeltaModel, signe
return nil, err
}

rv, err := commitment.GetRevealValue(signedDataModel.RecoveryKey, getProtocol(blockNum).MultihashAlgorithm)
rv, err := commitment.GetRevealValue(signedDataModel.RecoveryKey, getProtocol(blockNum).MultihashAlgorithms[0])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1087,7 +1087,7 @@ func getDefaultRecoverRequest(signer client.Signer, recoveryKey, updateKey *ecds
return nil, nil, err
}

deltaHash, err := hashing.CalculateModelMultihash(delta, p.MultihashAlgorithm)
deltaHash, err := hashing.CalculateModelMultihash(delta, p.MultihashAlgorithms[0])
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -1253,7 +1253,7 @@ func getCommitment(key *ecdsa.PrivateKey, p protocol.Protocol) (string, error) {
return "", err
}

return commitment.GetCommitment(pubKey, p.MultihashAlgorithm)
return commitment.GetCommitment(pubKey, p.MultihashAlgorithms[0])
}

func getSuffixData(privateKey *ecdsa.PrivateKey, delta *model.DeltaModel, p protocol.Protocol) (*model.SuffixDataModel, error) {
Expand All @@ -1262,7 +1262,7 @@ func getSuffixData(privateKey *ecdsa.PrivateKey, delta *model.DeltaModel, p prot
return nil, err
}

deltaHash, err := hashing.CalculateModelMultihash(delta, p.MultihashAlgorithm)
deltaHash, err := hashing.CalculateModelMultihash(delta, p.MultihashAlgorithms[0])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1319,7 +1319,7 @@ func newMockProtocolClient() *mocks.MockProtocolClient {
//nolint:gomnd
latest := protocol.Protocol{
GenesisTime: 100,
MultihashAlgorithm: sha2_512,
MultihashAlgorithms: []uint{sha2_512, sha2_256},
MaxOperationCount: 2,
MaxOperationSize: mocks.MaxOperationByteSize,
MaxOperationHashLength: 100,
Expand Down
4 changes: 2 additions & 2 deletions pkg/versions/0_1/client/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ func validateCreateRequest(info *CreateRequestInfo) error {
return fmt.Errorf("multihash[%d] not supported", info.MultihashCode)
}

if !hashing.IsComputedUsingMultihashAlgorithm(info.RecoveryCommitment, uint64(info.MultihashCode)) {
if !hashing.IsComputedUsingMultihashAlgorithms(info.RecoveryCommitment, []uint{info.MultihashCode}) {
return errors.New("next recovery commitment is not computed with the specified hash algorithm")
}

if !hashing.IsComputedUsingMultihashAlgorithm(info.UpdateCommitment, uint64(info.MultihashCode)) {
if !hashing.IsComputedUsingMultihashAlgorithms(info.UpdateCommitment, []uint{info.MultihashCode}) {
return errors.New("next update commitment is not computed with the specified hash algorithm")
}

Expand Down
20 changes: 20 additions & 0 deletions pkg/versions/0_1/model/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ SPDX-License-Identifier: Apache-2.0
package model

import (
"errors"
"fmt"

"github.com/trustbloc/sidetree-core-go/pkg/api/operation"
"github.com/trustbloc/sidetree-core-go/pkg/canonicalizer"
"github.com/trustbloc/sidetree-core-go/pkg/hashing"
)

// GetAnchoredOperation is utility method for converting operation model into anchored operation.
Expand Down Expand Up @@ -65,3 +67,21 @@ func GetAnchoredOperation(op *Operation) (*operation.AnchoredOperation, error) {
OperationBuffer: operationBuffer,
}, nil
}

// GetUniqueSuffix calculates unique suffix from suffix data and multihash algorithms.
func GetUniqueSuffix(model *SuffixDataModel, algs []uint) (string, error) {
if len(algs) == 0 {
return "", errors.New("failed to calculate unique suffix: algorithm not provided")
}

// Even though protocol supports the list of multihashing algorithms in this protocol version (v1) we can have
// only one multihashing algorithm. Later versions may have multiple values for backward compatibility.
// At that point (version 2) the spec will hopefully better define how to handle this scenarios:
// https://github.com/decentralized-identity/sidetree/issues/965
encodedComputedMultihash, err := hashing.CalculateModelMultihash(model, algs[0])
if err != nil {
return "", fmt.Errorf("failed to calculate unique suffix: %s", err.Error())
}

return encodedComputedMultihash, nil
}
141 changes: 141 additions & 0 deletions pkg/versions/0_1/model/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package model

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/trustbloc/sidetree-core-go/pkg/api/operation"
)

const suffix = "suffix"

func TestGetAnchoredOperation(t *testing.T) {
t.Run("success - create", func(t *testing.T) {
op := &Operation{
Type: operation.TypeCreate,
UniqueSuffix: suffix,
SuffixData: &SuffixDataModel{
RecoveryCommitment: "rc",
DeltaHash: "dh",
},
Delta: &DeltaModel{
UpdateCommitment: "uc",
},
}

opBuffer := `{"delta":{"updateCommitment":"uc"},"suffixData":{"deltaHash":"dh","recoveryCommitment":"rc"},"type":"create"}`

anchored, err := GetAnchoredOperation(op)
require.NoError(t, err)
require.NotNil(t, anchored)

require.Equal(t, op.Type, anchored.Type)
require.Equal(t, opBuffer, string(anchored.OperationBuffer))
require.Equal(t, suffix, anchored.UniqueSuffix)
})

t.Run("success - deactivate", func(t *testing.T) {
op := &Operation{
Type: operation.TypeDeactivate,
UniqueSuffix: suffix,
RevealValue: "rv",
SignedData: "jws",
}

opBuffer := `{"didSuffix":"suffix","revealValue":"rv","signedData":"jws","type":"deactivate"}`

anchored, err := GetAnchoredOperation(op)
require.NoError(t, err)
require.NotNil(t, anchored)

require.Equal(t, op.Type, anchored.Type)
require.Equal(t, opBuffer, string(anchored.OperationBuffer))
require.Equal(t, suffix, anchored.UniqueSuffix)
})

t.Run("success - recover", func(t *testing.T) {
op := &Operation{
Type: operation.TypeRecover,
UniqueSuffix: suffix,
RevealValue: "rv",
SignedData: "jws",
Delta: &DeltaModel{
UpdateCommitment: "uc",
},
}

opBuffer := `{"delta":{"updateCommitment":"uc"},"didSuffix":"suffix","revealValue":"rv","signedData":"jws","type":"recover"}`

anchored, err := GetAnchoredOperation(op)
require.NoError(t, err)
require.NotNil(t, anchored)
require.Equal(t, op.Type, anchored.Type)

require.Equal(t, opBuffer, string(anchored.OperationBuffer))
require.Equal(t, suffix, anchored.UniqueSuffix)
})

t.Run("success - update", func(t *testing.T) {
op := &Operation{
Type: operation.TypeUpdate,
UniqueSuffix: suffix,
RevealValue: "rv",
SignedData: "jws",
Delta: &DeltaModel{
UpdateCommitment: "uc",
},
}

opBuffer := `{"delta":{"updateCommitment":"uc"},"didSuffix":"suffix","revealValue":"rv","signedData":"jws","type":"update"}`
anchored, err := GetAnchoredOperation(op)
require.NoError(t, err)
require.NotNil(t, anchored)
require.Equal(t, anchored.Type, op.Type)

require.Equal(t, opBuffer, string(anchored.OperationBuffer))
require.Equal(t, suffix, anchored.UniqueSuffix)
})

t.Run("error - type not supported", func(t *testing.T) {
op := &Operation{Type: "other"}

anchored, err := GetAnchoredOperation(op)
require.Error(t, err)
require.Nil(t, anchored)
require.Contains(t, err.Error(), "operation type other not supported for anchored operation")
})
}

func TestGetUniqueSuffix(t *testing.T) {
s := &SuffixDataModel{
RecoveryCommitment: "rc",
DeltaHash: "dh",
}

t.Run("success", func(t *testing.T) {
uniqueSuffix, err := GetUniqueSuffix(s, []uint{18})
require.NoError(t, err)
require.NotEmpty(t, uniqueSuffix)
})

t.Run("error - algorithm not provided", func(t *testing.T) {
uniqueSuffix, err := GetUniqueSuffix(s, []uint{})
require.Error(t, err)
require.Empty(t, uniqueSuffix)
require.Contains(t, err.Error(), "failed to calculate unique suffix: algorithm not provided")
})

t.Run("error - algorithm not supported", func(t *testing.T) {
uniqueSuffix, err := GetUniqueSuffix(s, []uint{55})
require.Error(t, err)
require.Empty(t, uniqueSuffix)
require.Contains(t, err.Error(), "failed to calculate unique suffix: algorithm not supported")
})
}
2 changes: 1 addition & 1 deletion pkg/versions/0_1/operationapplier/operationapplier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const (
var (
p = protocol.Protocol{
GenesisTime: 0,
MultihashAlgorithm: sha2_256,
MultihashAlgorithms: []uint{sha2_256},
MaxOperationCount: 2,
MaxOperationSize: 2000,
MaxOperationHashLength: 100,
Expand Down
Loading

0 comments on commit 1c3e1f1

Please sign in to comment.