Skip to content
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
9 changes: 9 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ type ConsensusParams struct {

// EnableStateProofKeyregCheck enables the check for stateProof key on key registration
EnableStateProofKeyregCheck bool

// MaxKeyregValidPeriod defines the longest period (in rounds) allowed for a keyreg transaction.
// This number sets a limit to prevent the number of StateProof keys generated by the user from being too large, and also checked by the WellFormed method.
// The hard-limit for number of StateProof keys is derived from the maximum depth allowed for the merklekeystore tree - 2^16.
// More keys => deeper merkle tree => longer proof required => infeasible for our SNARK.
MaxKeyregValidPeriod uint64
}

// PaysetCommitType enumerates possible ways for the block header to commit to
Expand Down Expand Up @@ -1062,6 +1068,9 @@ func initConsensusProtocols() {
// stat proof key registration
vFuture.EnableStateProofKeyregCheck = true

// Maximum validity period for key registration, to prevent generating too many StateProof keys
vFuture.MaxKeyregValidPeriod = 128*(1<<16) - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the 128 be substituted by the variable representing it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Consensus[protocol.ConsensusFuture] = vFuture
}

Expand Down
11 changes: 11 additions & 0 deletions config/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@ func TestConsensusUpgradeWindow(t *testing.T) {
}
}
}

func TestConsensusCompactCertParams(t *testing.T) {
partitiontest.PartitionTest(t)

for _, params := range Consensus {
if params.CompactCertRounds != 0 {
require.Equal(t, uint64(1<<16), (params.MaxKeyregValidPeriod+1)/params.CompactCertRounds,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the "16" be replaced by an existing variable representing that value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can use merklearray.MaxTreeDepth, but what about the consensus parameters (MaxKeyregValidPeriod for example), should it use that as well? If not there might be some inconsistency in the future.

Copy link
Contributor

@algonautshant algonautshant Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's postpone this parameter issue to a later PR. #3257

"Validity period divided by CompactCertRounds should allow for no more than %d generated keys", 1<<16)
}
}
}
16 changes: 10 additions & 6 deletions crypto/compactcert/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"database/sql"
"fmt"
"github.com/algorand/go-algorand/config"
"strconv"
"testing"

Expand All @@ -35,6 +36,9 @@ import (

type TestMessage string

// TODO: change to CurrentVersion when updated
var CompactCertRounds = config.Consensus[protocol.ConsensusFuture].CompactCertRounds

func (m TestMessage) ToBeHashed() (protocol.HashID, []byte) {
return protocol.Message, []byte(m)
}
Expand Down Expand Up @@ -70,7 +74,7 @@ func createParticipantSliceWithWeight(totalWeight, numberOfParticipant int, key
return parts
}

func generateTestSigner(name string, firstValid uint64, lastValid uint64, interval uint64, a *require.Assertions) (*merklekeystore.Signer, db.Accessor) {
func generateTestSigner(name string, firstValid uint64, lastValid uint64, a *require.Assertions) (*merklekeystore.Signer, db.Accessor) {
store, err := db.MakeAccessor(name, false, true)
a.NoError(err)
a.NotNil(store)
Expand All @@ -84,7 +88,7 @@ func generateTestSigner(name string, firstValid uint64, lastValid uint64, interv
})
a.NoError(err)

signer, err := merklekeystore.New(firstValid, lastValid, interval, crypto.FalconType, store)
signer, err := merklekeystore.New(firstValid, lastValid, CompactCertRounds, crypto.FalconType, store)
a.NoError(err)

err = signer.Persist()
Expand Down Expand Up @@ -117,11 +121,11 @@ func TestBuildVerify(t *testing.T) {
ProvenWeight: uint64(totalWeight / 2),
SigRound: currentRound,
SecKQ: 128,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a sound design to have crypto independent of the consensus parameters.

However, the hard-coded values in the tests which are repeating the consensus values does not look right. And since the compiler-enforced link of these values to the consensus parameters is broken, this can be problematic in the event the consensus values change.

CompactCertRounds is getting its value from go-algorand/config, but SecKQ is not. They need to be uniform.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's postpone this #3257

CompactCertRounds: 128,
CompactCertRounds: CompactCertRounds,
}

// Share the key; we allow the same vote key to appear in multiple accounts..
key, dbAccessor := generateTestSigner(t.Name()+".db", 0, uint64(param.CompactCertRounds)+1, param.CompactCertRounds, a)
key, dbAccessor := generateTestSigner(t.Name()+".db", 0, uint64(param.CompactCertRounds)+1, a)
defer dbAccessor.Close()
require.NotNil(t, dbAccessor, "failed to create signer")
var parts []basics.Participant
Expand Down Expand Up @@ -192,14 +196,14 @@ func BenchmarkBuildVerify(b *testing.B) {
ProvenWeight: uint64(totalWeight / 2),
SigRound: 128,
SecKQ: 128,
CompactCertRounds: 128,
CompactCertRounds: CompactCertRounds,
}

var parts []basics.Participant
var partkeys []*merklekeystore.Signer
var sigs []merklekeystore.Signature
for i := 0; i < npart; i++ {
key, dbAccessor := generateTestSigner(b.Name()+"_"+strconv.Itoa(i)+"_crash.db", 0, uint64(param.CompactCertRounds)+1, param.CompactCertRounds, a)
key, dbAccessor := generateTestSigner(b.Name()+"_"+strconv.Itoa(i)+"_crash.db", 0, uint64(param.CompactCertRounds)+1, a)
defer dbAccessor.Close()
require.NotNil(b, dbAccessor, "failed to create signer")
part := basics.Participant{
Expand Down
4 changes: 2 additions & 2 deletions crypto/merklekeystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package merklekeystore

import (
"errors"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklearray"
"github.com/algorand/go-algorand/protocol"
Expand Down Expand Up @@ -106,14 +105,15 @@ func (k *keysArray) Marshal(pos uint64) ([]byte, error) {

// New Generates a merklekeystore.Signer
// The function allow creation of empty signers, i.e signers without any key to sign with.
// keys can be created between [A,Z], if A == 0, keys created will be in the range (0,Z]
// keys can be created between [firstValid,lastValid], if firstValid == 0, keys created will be in the range (0,lastValid]
func New(firstValid, lastValid, interval uint64, sigAlgoType crypto.AlgorithmType, store db.Accessor) (*Signer, error) {
if firstValid > lastValid {
return nil, errStartBiggerThanEndRound
}
if interval == 0 {
return nil, errDivisorIsZero
}

if firstValid == 0 {
firstValid = 1
}
Expand Down
63 changes: 53 additions & 10 deletions crypto/merklekeystore/keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func TestSignerCreation(t *testing.T) {

testSignerNumKeysLimits := func(t crypto.AlgorithmType, firstValid uint64, lastValid uint64, interval uint64, expectedLen int) {
signer := generateTestSigner(t, firstValid, lastValid, interval, a)
defer signer.keyStore.store.Close()
a.Equal(expectedLen, length(signer, a))
signer.keyStore.store.Close()
}

testSignerNumKeysLimits(crypto.FalconType, 0, 0, 1, 0)
Expand Down Expand Up @@ -114,6 +114,7 @@ func TestEmptySigner(t *testing.T) {

h := genHashableForTest()
signer := generateTestSigner(crypto.FalconType, 8, 9, 5, a)
defer signer.keyStore.store.Close()
a.NoError(err)
a.Equal(0, length(signer, a))

Expand Down Expand Up @@ -153,6 +154,7 @@ func TestDisposableKeysGeneration(t *testing.T) {
a.Error(err)

signer = generateTestSigner(crypto.FalconType, 1000, 1100, 101, a)
defer signer.keyStore.store.Close()
intervalRounds := make([]uint64, 0)
for i := uint64(1000); i <= 1100; i++ {
if i%101 == 0 {
Expand Down Expand Up @@ -441,7 +443,6 @@ func TestSignerTrim(t *testing.T) {
_, err = signer.Trim(55)
a.NoError(err)
a.Equal(0, length(signer, a))

}

func TestKeyDeletion(t *testing.T) {
Expand Down Expand Up @@ -480,6 +481,43 @@ func TestKeyDeletion(t *testing.T) {
}
}

func TestNumberOfGeneratedKeys(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)
interval := uint64(128)
validPeriod := uint64((1<<8)*interval - 1)

store := initTestDB(a)
defer store.Close()
firstValid := uint64(1000)
lastValid := validPeriod + 1000
s, err := New(firstValid, lastValid, interval, crypto.Ed25519Type, *store)
a.NoError(err)
err = s.Persist()
a.NoError(err)
a.Equal(1<<8, length(s, a))

store = initTestDB(a)
defer store.Close()
firstValid = uint64(0)
lastValid = validPeriod
s, err = New(firstValid, lastValid, interval, crypto.Ed25519Type, *store)
a.NoError(err)
err = s.Persist()
a.NoError(err)
a.Equal((1<<8)-1, length(s, a))

store = initTestDB(a)
defer store.Close()
firstValid = uint64(1000)
lastValid = validPeriod + 1000 - (interval * 50)
s, err = New(firstValid, lastValid, interval, crypto.Ed25519Type, *store)
a.NoError(err)
err = s.Persist()
a.NoError(err)
a.Equal((1<<8)-50, length(s, a))
}

//#region Helper Functions
func makeSig(signer *Signer, sigRound uint64, a *require.Assertions) (crypto.Hashable, Signature) {
hashable := genHashableForTest()
Expand All @@ -496,7 +534,18 @@ func generateTestSignerAux(a *require.Assertions) (uint64, uint64, *Signer) {
return start, end, signer
}

func generateTestSigner(t crypto.AlgorithmType, firstValid uint64, lastValid uint64, interval uint64, a *require.Assertions) *Signer {
func generateTestSigner(t crypto.AlgorithmType, firstValid, lastValid, interval uint64, a *require.Assertions) *Signer {
store := initTestDB(a)
signer, err := New(firstValid, lastValid, interval, t, *store)
a.NoError(err)

err = signer.Persist()
a.NoError(err)

return signer
}

func initTestDB(a *require.Assertions) *db.Accessor {
tmpname := uuid.NewV4().String() // could this just be a constant string instead? does it even matter?
store, err := db.MakeAccessor(tmpname, false, true)
a.NoError(err)
Expand All @@ -511,13 +560,7 @@ func generateTestSigner(t crypto.AlgorithmType, firstValid uint64, lastValid uin
})
a.NoError(err)

signer, err := New(firstValid, lastValid, interval, t, store)
a.NoError(err)

err = signer.Persist()
a.NoError(err)

return signer
return &store
}

func length(s *Signer, a *require.Assertions) int {
Expand Down
15 changes: 10 additions & 5 deletions data/account/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ func FillDBWithParticipationKeys(store db.Accessor, address basics.Address, firs
return
}

// TODO: change to ConsensusCurrentVersion when updated
interval := config.Consensus[protocol.ConsensusFuture].CompactCertRounds
maxValidPeriod := config.Consensus[protocol.ConsensusFuture].MaxKeyregValidPeriod

if maxValidPeriod != 0 && uint64(lastValid-firstValid) > maxValidPeriod {
return PersistedParticipation{}, fmt.Errorf("the validity period for merkleKeyStore is too large: the limit is %d", maxValidPeriod)
}

// Compute how many distinct participation keys we should generate
firstID := basics.OneTimeIDForRound(firstValid, keyDilution)
lastID := basics.OneTimeIDForRound(lastValid, keyDilution)
Expand All @@ -235,13 +243,10 @@ func FillDBWithParticipationKeys(store db.Accessor, address basics.Address, firs
// Generate a new VRF key, which lives in the participation keys db
vrf := crypto.GenerateVRFSecrets()

// TODO change this
compactCertRound := config.Consensus[protocol.ConsensusFuture].CompactCertRounds

// Generate a new key which signs the compact certificates
stateProofSecrets, err := merklekeystore.New(uint64(firstValid), uint64(lastValid), compactCertRound, crypto.FalconType, store)
stateProofSecrets, err := merklekeystore.New(uint64(firstValid), uint64(lastValid), interval, crypto.FalconType, store)
if err != nil {
return
return PersistedParticipation{}, err
}

// Construct the Participation containing these keys to be persisted
Expand Down
79 changes: 79 additions & 0 deletions data/account/participation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strings"
"testing"

uuid "github.com/satori/go.uuid"
"github.com/stretchr/testify/require"

"github.com/algorand/go-algorand/config"
Expand Down Expand Up @@ -59,6 +60,7 @@ func setupParticipationKey(t *testing.T, a *require.Assertions) (PersistedPartic
a.Equal(versions[PartTableSchemaName], PartTableSchemaVersion)
return part, rootDB, partDB, err
}

func setupkeyWithNoDBS(t *testing.T, a *require.Assertions) PersistedParticipation {
part, rootDB, partDB, err := setupParticipationKey(t, a)
a.NoError(err)
Expand Down Expand Up @@ -448,3 +450,80 @@ func BenchmarkParticipationKeyRestoration(b *testing.B) {
}
part.Close()
}

func createKeystoreTestDB(a *require.Assertions) *db.Accessor {
tmpname := uuid.NewV4().String() // could this just be a constant string instead? does it even matter?
store, err := db.MakeAccessor(tmpname, false, true)
a.NoError(err)
a.NotNil(store)

return &store
}

func TestKeyregValidityOverLimit(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

// TODO: change to ConsensusCurrentVersion when updated
maxValidPeriod := config.Consensus[protocol.ConsensusFuture].MaxKeyregValidPeriod
dilution := config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution

var address basics.Address
crypto.RandBytes(address[:])

store := createKeystoreTestDB(a)
defer store.Close()
firstValid := basics.Round(0)
lastValid := basics.Round(maxValidPeriod + 1)
_, err := FillDBWithParticipationKeys(*store, address, firstValid, lastValid, dilution)
a.Error(err)
}

func TestFillDBWithParticipationKeys(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

// TODO: change to ConsensusCurrentVersion when updated
dilution := config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution

var address basics.Address
crypto.RandBytes(address[:])

store := createKeystoreTestDB(a)
defer store.Close()
firstValid := basics.Round(0)
lastValid := basics.Round(10000)
_, err := FillDBWithParticipationKeys(*store, address, firstValid, lastValid, dilution)
a.NoError(err)
}

// Long unit test, should only be run nightly
func TestKeyregValidityPeriod(t *testing.T) {
partitiontest.PartitionTest(t)
if testing.Short() {
t.Skip()
}

a := require.New(t)

// TODO: change to ConsensusCurrentVersion when updated
maxValidPeriod := config.Consensus[protocol.ConsensusFuture].MaxKeyregValidPeriod
dilution := config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution

var address basics.Address

store := createKeystoreTestDB(a)
defer store.Close()
firstValid := basics.Round(0)
lastValid := basics.Round(maxValidPeriod)
crypto.RandBytes(address[:])
_, err := FillDBWithParticipationKeys(*store, address, firstValid, lastValid, dilution)
a.NoError(err)

store = createKeystoreTestDB(a)
defer store.Close()
firstValid = basics.Round(0)
lastValid = basics.Round(maxValidPeriod + 1)
_, err = FillDBWithParticipationKeys(*store, address, firstValid, lastValid, dilution)
a.Error(err)
}
Loading