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

feat: support multiple multihash algorithms in protocol #526

Merged
merged 1 commit into from
Dec 16, 2020
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
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