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

Remove dead upgrades through v7 #101

Merged
merged 7 commits into from Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 1 addition & 9 deletions agreement/cryptoVerifier_test.go
Expand Up @@ -70,15 +70,7 @@ func makeUnauthenticatedVote(l Ledger, sender basics.Address, selection *crypto.
m, _ := membership(l, rv.Sender, rv.Round, rv.Period, rv.Step)
cred := committee.MakeCredential(&selection.SK, m.Selector)
ephID := basics.OneTimeIDForRound(rv.Round, voting.KeyDilution(config.Consensus[protocol.ConsensusCurrentVersion]))
var sig crypto.OneTimeSignature

proto, err := l.ConsensusParams(ParamsRound(rv.Round))
// If we can't figure out the protocol params, the ledger has moved forward
// well ahead of rv.Round, so our vote is irrelevant. Generate an empty
// signature in that case.
if err == nil {
sig = voting.Sign(ephID, proto.FineGrainedEphemeralKeys, rv)
}
sig := voting.Sign(ephID, rv)

return unauthenticatedVote{
R: rv,
Expand Down
2 changes: 1 addition & 1 deletion agreement/message_test.go
Expand Up @@ -63,7 +63,7 @@ func BenchmarkVoteDecoding(b *testing.B) {
Cred: committee.UnauthenticatedCredential{
Proof: vrfProof,
},
Sig: oneTimeSecrets.Sign(id, false, proposal),
Sig: oneTimeSecrets.Sign(id, proposal),
}

msgBytes := protocol.Encode(&uv)
Expand Down
220 changes: 57 additions & 163 deletions agreement/proposal.go
Expand Up @@ -132,110 +132,42 @@ func deriveNewSeed(address basics.Address, vrf *crypto.VRFSecrets, rnd round, pe
err = fmt.Errorf("failed to obtain consensus parameters in round %v: %v", ParamsRound(rnd), err)
return
}
if cparams.TwinSeeds {
var alpha crypto.Digest
prevSeed, err := ledger.Seed(seedRound(rnd, cparams))
if err != nil {
reterr = fmt.Errorf("failed read seed of round %v: %v", seedRound(rnd, cparams), err)
return
}

if period == 0 {
seedProof, ok = vrf.SK.Prove(prevSeed)
if !ok {
reterr = fmt.Errorf("could not make seed proof")
return
}
vrfOut, ok = seedProof.Hash()
if !ok {
// If proof2hash fails on a proof we produced with VRF Prove, this indicates our VRF code has a dangerous bug.
// Panicking is the only safe thing to do.
logging.Base().Panicf("VrfProof.Hash() failed on a proof we ourselves generated; this indicates a bug in the VRF code: %v", seedProof)
}
alpha = crypto.HashObj(proposerSeed{Addr: address, VRF: vrfOut})
} else {
alpha = crypto.HashObj(prevSeed)
}

input := seedInput{Alpha: alpha}
rerand := rnd % basics.Round(cparams.SeedLookback*cparams.SeedRefreshInterval)
if rerand < basics.Round(cparams.SeedLookback) {
digrnd := rnd.SubSaturate(basics.Round(cparams.SeedLookback * cparams.SeedRefreshInterval))
oldDigest, err := ledger.LookupDigest(digrnd)
if err != nil {
reterr = fmt.Errorf("could not lookup old entry digest (for seed) from round %v: %v", digrnd, err)
return
}
input.History = oldDigest
}
newSeed = committee.Seed(crypto.HashObj(input))
return
}

// Compute the new seed
prevSeed, err := ledger.Seed(rnd.SubSaturate(1))
var alpha crypto.Digest
prevSeed, err := ledger.Seed(seedRound(rnd, cparams))
if err != nil {
reterr = fmt.Errorf("failed read seed of round %v: %v", seedRound(rnd, cparams), err)
return
}
if (rnd % basics.Round(cparams.SeedLookback)) != 0 {
// In odd rounds, the seed is just the seed from the previous round, unchanged.
// This simplifies the analysis now that our seed lookback parameter is 2. (In the original paper it was 1.)
newSeed = prevSeed
} else {
// In even rounds, we evolve the seed
var q1 crypto.Digest
var ok bool
var vrfOut crypto.VrfOutput
if period == 0 {
// For period 0, the proposer runs the previous seed through their VRF.
// To an adversary trying to predict (or influence) future seeds, as soon as there's an honest proposer the seed becomes completely rerandomized.
// This is because a VRF output is pseudorandom to anyone without the secret key or the corresponding proof.
// The adversary's ability to influence the seed is also limited because of the uniqueness property of the VRF.
seedProof, ok = vrf.SK.Prove(prevSeed)
if !ok {
reterr = fmt.Errorf("Could not make seed proof")
return
}
vrfOut, ok = seedProof.Hash()
if !ok {
// If proof2hash fails on a proof we produced with VRF Prove, this indicates our VRF code has a dangerous bug.
// Panicking is the only safe thing to do.
logging.Base().Panicf("VrfProof.Hash() failed on a proof we ourselves generated; this indicates a bug in the VRF code: %v", seedProof)
}
// Hashing in the proposer's address is not strictly speaking necessary.
// We do it here to be consistent with Credentials, where hashing the address in with the VRF output is necessary to prevent a specific attack.
q1 = crypto.Hash(append(vrfOut[:], address[:]...))
} else {
// For periods > 0, we don't use the proposer's VRF or address.
// This limits an adversary's ability to influence the seed.
// In particular, some of the adversary's accounts may be likely to be selected in period 0, others in period 1, and so on.
// If the adversary doesn't like any of the seeds from any of their period-0 possible proposers, they might try causing the network to move on to the next period until they reach a period where one of their likely proposers gives them a good seed.
// By making periods > 0 give only one possible seed, this limits the number of new seeds the adversary can choose between.
q1 = crypto.Hash(prevSeed[:])

if period == 0 {
seedProof, ok = vrf.SK.Prove(prevSeed)
if !ok {
reterr = fmt.Errorf("could not make seed proof")
return
}
vrfOut, ok = seedProof.Hash()
if !ok {
// If proof2hash fails on a proof we produced with VRF Prove, this indicates our VRF code has a dangerous bug.
// Panicking is the only safe thing to do.
logging.Base().Panicf("VrfProof.Hash() failed on a proof we ourselves generated; this indicates a bug in the VRF code: %v", seedProof)
}
alpha = crypto.HashObj(proposerSeed{Addr: address, VRF: vrfOut})
} else {
alpha = crypto.HashObj(prevSeed)
}

// Periodically mix an older block hash (which either implicitly or explicitly commits to the balances) into the seed.
// This prevents a specific attack wherein an attacker during a long partition can cause the network to permanently stall even after the partition has healed.
// In particular, during a partition, the adversary can (by dropping other proposals) propose every block.
// Thus they can predict (and to some extent influence) seed values for future rounds that are during the partition.
// Say the partition is going to end just before round R. In round R, proposers are selected using the seed from round (R-SeedLookback)
// and the balances / VRF keys from round (R-BalLookback), both of which are during the partition.
// Say we're before (R-BalLookback). Because the adversary knows what the seed will be at round R-(SeedLookback), they can find
// (by brute force) and register VRF public keys that give extremely good credentials (disproportionate to stake) for being round R proposer in period 0.
// Likewise they can register VRF public keys that will make them be proposer in round R period 1, and period 2, and so on for all periods.
// Then even after the partition has healed, the adversary can permanently stall the network because they will be selected in every period of round R and can keep proposing bad blocks.
// Periodically mixing the block hash into the seed defeats this attack: any change to the balances / VRF keys registered in round (R-BalLookback) will cause the seed in round (R-SeedLookback) to change. So by brute force the adversary may be able to make themselves leader in a few periods of round R but certainly not all of them, and they won't be able to stall the network after the partition has healed.
if rnd%basics.Round(cparams.SeedRefreshInterval) == 0 {
oldDigest, err := ledger.LookupDigest(rnd.SubSaturate(basics.Round(cparams.SeedRefreshInterval)))
if err != nil {
reterr = fmt.Errorf("Could not lookup old entry digest (for seed): %v", err)
return
}
q1 = crypto.Hash(append(q1[:], oldDigest[:]...))
input := seedInput{Alpha: alpha}
rerand := rnd % basics.Round(cparams.SeedLookback*cparams.SeedRefreshInterval)
if rerand < basics.Round(cparams.SeedLookback) {
digrnd := rnd.SubSaturate(basics.Round(cparams.SeedLookback * cparams.SeedRefreshInterval))
oldDigest, err := ledger.LookupDigest(digrnd)
if err != nil {
reterr = fmt.Errorf("could not lookup old entry digest (for seed) from round %v: %v", digrnd, err)
return
}
newSeed = committee.Seed(q1)
input.History = oldDigest
}
newSeed = committee.Seed(crypto.HashObj(input))
return
}

Expand All @@ -253,79 +185,41 @@ func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error {
return fmt.Errorf("failed to obtain balance record for address %v in round %v: %v", value.OriginalProposer, balanceRound, err)
}

if cparams.TwinSeeds {
var alpha crypto.Digest
prevSeed, err := ledger.Seed(seedRound(rnd, cparams))
if err != nil {
return fmt.Errorf("failed read seed of round %v: %v", seedRound(rnd, cparams), err)
}

if value.OriginalPeriod == 0 {
verifier := proposerRecord.SelectionID
ok, vrfOut := verifier.Verify(p.SeedProof, prevSeed)
if !ok {
return fmt.Errorf("payload seed proof malformed (%v, %v)", prevSeed, p.SeedProof)
}
vrfOut, ok = p.SeedProof.Hash()
if !ok {
// If proof2hash fails on a proof we produced with VRF Prove, this indicates our VRF code has a dangerous bug.
// Panicking is the only safe thing to do.
logging.Base().Panicf("VrfProof.Hash() failed on a proof we ourselves generated; this indicates a bug in the VRF code: %v", p.SeedProof)
}
alpha = crypto.HashObj(proposerSeed{Addr: proposerRecord.Addr, VRF: vrfOut})
} else {
alpha = crypto.HashObj(prevSeed)
}
var alpha crypto.Digest
prevSeed, err := ledger.Seed(seedRound(rnd, cparams))
if err != nil {
return fmt.Errorf("failed read seed of round %v: %v", seedRound(rnd, cparams), err)
}

input := seedInput{Alpha: alpha}
rerand := rnd % basics.Round(cparams.SeedLookback*cparams.SeedRefreshInterval)
if rerand < basics.Round(cparams.SeedLookback) {
digrnd := rnd.SubSaturate(basics.Round(cparams.SeedLookback * cparams.SeedRefreshInterval))
oldDigest, err := ledger.LookupDigest(digrnd)
if err != nil {
return fmt.Errorf("could not lookup old entry digest (for seed) from round %v: %v", digrnd, err)
}
input.History = oldDigest
if value.OriginalPeriod == 0 {
verifier := proposerRecord.SelectionID
ok, vrfOut := verifier.Verify(p.SeedProof, prevSeed)
if !ok {
return fmt.Errorf("payload seed proof malformed (%v, %v)", prevSeed, p.SeedProof)
}
if p.Seed() != committee.Seed(crypto.HashObj(input)) {
return fmt.Errorf("payload seed malformed (%v != %v)", committee.Seed(crypto.HashObj(input)), p.Seed())
vrfOut, ok = p.SeedProof.Hash()
if !ok {
// If proof2hash fails on a proof we produced with VRF Prove, this indicates our VRF code has a dangerous bug.
// Panicking is the only safe thing to do.
logging.Base().Panicf("VrfProof.Hash() failed on a proof we ourselves generated; this indicates a bug in the VRF code: %v", p.SeedProof)
}
alpha = crypto.HashObj(proposerSeed{Addr: proposerRecord.Addr, VRF: vrfOut})
} else {
prevSeed, err := ledger.Seed(p.Round().SubSaturate(1))
if err != nil {
return fmt.Errorf("could not perform ledger read for prevSeed: %v", err)
}
alpha = crypto.HashObj(prevSeed)
}

// Check the seed is computed correctly. See comments in proposalForBlock() for details.
if p.Round()%basics.Round(cparams.SeedLookback) != 0 {
if p.Seed() != prevSeed {
return fmt.Errorf("payload seed malformed")
}
} else {
var q1 crypto.Digest
if value.OriginalPeriod == 0 {
verifier := proposerRecord.SelectionID
ok, vrfOut := verifier.Verify(p.SeedProof, prevSeed)
if !ok {
return fmt.Errorf("payload seed proof malformed (%v, %v)", prevSeed, p.SeedProof)
}
q1 = crypto.Hash(append(vrfOut[:], proposerRecord.Addr[:]...))
} else {
q1 = crypto.Hash(prevSeed[:])
}

if p.Round()%basics.Round(cparams.SeedRefreshInterval) == 0 {
oldDigest, err := ledger.LookupDigest(p.Round().SubSaturate(basics.Round(cparams.SeedRefreshInterval)))
if err != nil {
return fmt.Errorf("could not perform ledger read for oldDigest: %v", err)
}
q1 = crypto.Hash(append(q1[:], oldDigest[:]...))
}

if p.Seed() != committee.Seed(q1) {
return fmt.Errorf("payload seed malformed (%v != %v)", committee.Seed(q1), p.Seed())
}
input := seedInput{Alpha: alpha}
rerand := rnd % basics.Round(cparams.SeedLookback*cparams.SeedRefreshInterval)
if rerand < basics.Round(cparams.SeedLookback) {
digrnd := rnd.SubSaturate(basics.Round(cparams.SeedLookback * cparams.SeedRefreshInterval))
oldDigest, err := ledger.LookupDigest(digrnd)
if err != nil {
return fmt.Errorf("could not lookup old entry digest (for seed) from round %v: %v", digrnd, err)
}
input.History = oldDigest
}
if p.Seed() != committee.Seed(crypto.HashObj(input)) {
return fmt.Errorf("payload seed malformed (%v != %v)", committee.Seed(crypto.HashObj(input)), p.Seed())
}
return nil
}
Expand Down
10 changes: 1 addition & 9 deletions agreement/selector.go
Expand Up @@ -46,15 +46,7 @@ func (sel selector) CommitteeSize(proto config.ConsensusParams) uint64 {
}

func balanceRound(r basics.Round, cparams config.ConsensusParams) basics.Round {
if cparams.TwinSeeds {
return r.SubSaturate(basics.Round(2 * cparams.SeedRefreshInterval * cparams.SeedLookback))
}

lookback := basics.Round(2*cparams.SeedRefreshInterval + cparams.SeedLookback + 1)
if cparams.IncorrectBalLookback {
return (r + 2).SubSaturate(lookback)
}
return r.SubSaturate(lookback)
return r.SubSaturate(basics.Round(2 * cparams.SeedRefreshInterval * cparams.SeedLookback))
}

func seedRound(r basics.Round, cparams config.ConsensusParams) basics.Round {
Expand Down
4 changes: 2 additions & 2 deletions agreement/vote.go
Expand Up @@ -126,7 +126,7 @@ func (uv unauthenticatedVote) verify(l LedgerReader) (vote, error) {

ephID := basics.OneTimeIDForRound(rv.Round, m.Record.KeyDilution(proto))
voteID := m.Record.VoteID
if !voteID.Verify(ephID, proto.FineGrainedEphemeralKeys, rv, uv.Sig) {
if !voteID.Verify(ephID, rv, uv.Sig) {
return vote{}, fmt.Errorf("unauthenticatedVote.verify: could not verify FS signature on vote by %v given %v: %+v", rv.Sender, voteID, uv)
}

Expand Down Expand Up @@ -173,7 +173,7 @@ func makeVote(rv rawVote, voting crypto.OneTimeSigner, selection *crypto.VRFSecr
}

ephID := basics.OneTimeIDForRound(rv.Round, voting.KeyDilution(proto))
sig := voting.Sign(ephID, proto.FineGrainedEphemeralKeys, rv)
sig := voting.Sign(ephID, rv)
if (sig == crypto.OneTimeSignature{}) {
return unauthenticatedVote{}, fmt.Errorf("makeVote: got back empty signature for vote")
}
Expand Down