diff --git a/core/consensus/consensus.go b/core/consensus/consensus.go index cfc74d1..0aa336a 100644 --- a/core/consensus/consensus.go +++ b/core/consensus/consensus.go @@ -125,22 +125,49 @@ const FloonetFirstHardFork uint64 = 185040 // FloonetSecondHardFork is the Floonet second hard fork height, set to happen around 2019-12-19 const FloonetSecondHardFork uint64 = 298080 +// FloonetThirdHardFork is the Floonet second hard fork height, set to happen around 2020-06-20 +const FloonetThirdHardFork uint64 = 552960 + +// TestingFirstHardFork is the AutomatedTesting and UserTesting HF1 height +const TestingFirstHardFork uint64 = 3 + +// TestingSecondHardFork is the AutomatedTesting and UserTesting HF2 height +const TestingSecondHardFork uint64 = 6 + +// TestingThirdHardFork is the AutomatedTesting and UserTesting HF3 height +const TestingThirdHardFork uint64 = 9 + // HeaderVersion compute possible block version at a given height, implements // 6 months interval scheduled hard forks for the first 2 years. func HeaderVersion(chainType ChainType, height uint64) uint16 { hfInterval := uint16(1 + height/HardForkInterval) switch chainType { + case Mainnet: + return hfInterval case Floonet: if height < FloonetFirstHardFork { return 1 } else if height < FloonetSecondHardFork { return 2 - } else if height < 3*HardForkInterval { + } else if height < FloonetThirdHardFork { return 3 + } else if height < 3*HardForkInterval { + return 4 } else { return hfInterval } - // everything else just like mainnet + case AutomatedTesting, UserTesting: + if height < TestingFirstHardFork { + return 1 + } else if height < TestingSecondHardFork { + return 2 + } else if height < TestingThirdHardFork { + return 3 + } else if height < 4*HardForkInterval { + return 4 + } else { + return 5 + } default: return hfInterval } @@ -149,7 +176,7 @@ func HeaderVersion(chainType ChainType, height uint64) uint16 { // ValidHeaderVersion check whether the block version is valid at a given height, implements // 6 months interval scheduled hard forks for the first 2 years. func ValidHeaderVersion(chainType ChainType, height uint64, version uint16) bool { - return height < 3*HardForkInterval && version == HeaderVersion(chainType, height) + return height < 4*HardForkInterval && version == HeaderVersion(chainType, height) } // DifficultyAdjustWindow is the number of blocks used to calculate difficulty adjustments @@ -169,8 +196,7 @@ const DifficultyDampFactor uint64 = 3 const ARScaleDampFactor uint64 = 13 // GraphWeight compute weight of a graph as number of siphash bits defining the graph -// Must be made dependent on height to phase out C31 in early 2020 -// Later phase outs are on hold for now +// The height dependence allows a 30-week linear transition from C31+ to C32+ starting after 1 year func GraphWeight(chainType ChainType, height uint64, edgeBits uint8) uint64 { xprEdgeBits := uint64(edgeBits) expiryHeight := YearHeight diff --git a/core/consensus/consensus_test.go b/core/consensus/consensus_test.go index 94d0cb0..1ac0d94 100644 --- a/core/consensus/consensus_test.go +++ b/core/consensus/consensus_test.go @@ -98,28 +98,34 @@ func TestSecondaryPoWRatio(t *testing.T) { assert.Equal(t, SecondaryPoWRatio(twoYears+1), uint64(0)) } -func TestValidHeaderVersion(t *testing.T) { +func TestHardForks(t *testing.T) { // Tests for Mainnet { - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2, 2)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight/2, 1)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight, 1)) - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2+1, 2)) - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2-1, 1)) - - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight-1, 2)) - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight, 3)) - assert.True(t, ValidHeaderVersion(Mainnet, YearHeight+1, 3)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight, 2)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 2)) - - // v4 not active yet - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 4)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 3)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 2)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 1)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*2, 3)) - assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2+1, 3)) + assert.True(t, ValidHeaderVersion(Mainnet, 0, 1)) + assert.True(t, ValidHeaderVersion(Mainnet, 10, 1)) + assert.False(t, ValidHeaderVersion(Mainnet, 10, 2)) + + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval-1, 1)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval, 1)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval, 2)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval+1, 2)) + + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2-1, 2)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*2, 2)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2, 3)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2+1, 3)) + + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3-1, 3)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*3, 3)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3, 4)) + assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3+1, 4)) + + // v5 not active yet + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 5)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 4)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 3)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 2)) + assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 1)) } // Tests for Floonet { @@ -132,25 +138,30 @@ func TestValidHeaderVersion(t *testing.T) { assert.True(t, ValidHeaderVersion(Floonet, FloonetFirstHardFork+1, 2)) assert.False(t, ValidHeaderVersion(Floonet, FloonetFirstHardFork, 1)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight, 1)) - assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork-1, 2)) assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 3)) assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork+1, 3)) assert.False(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 2)) assert.False(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 1)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight-1, 2)) - assert.True(t, ValidHeaderVersion(Floonet, YearHeight-1, 3)) - assert.True(t, ValidHeaderVersion(Floonet, YearHeight, 3)) - assert.True(t, ValidHeaderVersion(Floonet, YearHeight+1, 3)) - - // v4 not active yet - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 4)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 3)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 2)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 1)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*2, 3)) - assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2+1, 3)) + assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork-1, 3)) + assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 4)) + assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork+1, 4)) + assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 3)) + assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 2)) + assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 1)) + + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 1)) + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 2)) + assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 3)) + assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2, 3)) + assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2+1, 3)) + + // v5 not active yet + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 5)) + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 4)) + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 3)) + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 2)) + assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 1)) } } diff --git a/core/pow.go b/core/pow.go index bbeee2b..f414197 100644 --- a/core/pow.go +++ b/core/pow.go @@ -26,6 +26,8 @@ func createPoWContext(chainType consensus.ChainType, height uint64, edgeBits uin // Mainnet has Cuckaroo29 for AR and Cuckatoo30+ for AF case consensus.Mainnet <= chainType && edgeBits > 29: return pow.NewCuckatooCtx(chainType, edgeBits, proofSize, maxSols) + case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 4): + return pow.NewCuckaroozCtx(chainType, edgeBits, proofSize) case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 3): return pow.NewCuckaroomCtx(chainType, edgeBits, proofSize) case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 2): @@ -34,6 +36,8 @@ func createPoWContext(chainType consensus.ChainType, height uint64, edgeBits uin return pow.NewCuckarooCtx(chainType, edgeBits, proofSize) case consensus.Floonet <= chainType && edgeBits > 29: return pow.NewCuckatooCtx(chainType, edgeBits, proofSize, maxSols) + case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 4): + return pow.NewCuckaroozCtx(chainType, edgeBits, proofSize) case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 3): return pow.NewCuckaroomCtx(chainType, edgeBits, proofSize) case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 2): diff --git a/core/pow/cuckaroo.go b/core/pow/cuckaroo.go index b0d5e11..79688f5 100644 --- a/core/pow/cuckaroo.go +++ b/core/pow/cuckaroo.go @@ -48,6 +48,7 @@ func (c *CuckarooContext) Verify(proof Proof) error { nonces := proof.Nonces uvs := make([]uint64, 2*proof.proofSize()) var xor0, xor1 uint64 + nodeMask := c.params.edgeMask for n := 0; n < proof.proofSize(); n++ { if nonces[n] > c.params.edgeMask { @@ -58,9 +59,9 @@ func (c *CuckarooContext) Verify(proof Proof) error { } // 21 is standard siphash rotation constant edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, false) - uvs[2*n] = edge & c.params.edgeMask - uvs[2*n+1] = (edge >> 32) & c.params.edgeMask + uvs[2*n] = edge & nodeMask xor0 ^= uvs[2*n] + uvs[2*n+1] = (edge >> 32) & nodeMask xor1 ^= uvs[2*n+1] } diff --git a/core/pow/cuckarood.go b/core/pow/cuckarood.go index 1bbd09c..4d3fe01 100644 --- a/core/pow/cuckarood.go +++ b/core/pow/cuckarood.go @@ -49,7 +49,7 @@ func (c *CuckaroodContext) Verify(proof Proof) error { uvs := make([]uint64, 2*proof.proofSize()) ndir := make([]uint64, 2) var xor0, xor1 uint64 - nodemask := c.params.edgeMask >> 1 + nodeMask := c.params.edgeMask >> 1 for n := 0; n < proof.proofSize(); n++ { dir := uint(nonces[n] & 1) @@ -62,11 +62,12 @@ func (c *CuckaroodContext) Verify(proof Proof) error { if n > 0 && nonces[n] <= nonces[n-1] { return errors.New("edges not ascending") } + // cuckarood uses a non-standard siphash rotation constant 25 as anti-ASIC tweak edge := SipHashBlock(c.params.siphashKeys, nonces[n], 25, false) idx := 4*ndir[dir] + 2*uint64(dir) - uvs[idx] = edge & nodemask - uvs[idx+1] = (edge >> 32) & nodemask + uvs[idx] = edge & nodeMask xor0 ^= uvs[idx] + uvs[idx+1] = (edge >> 32) & nodeMask xor1 ^= uvs[idx+1] ndir[dir]++ } diff --git a/core/pow/cuckaroom.go b/core/pow/cuckaroom.go index e74e0b9..4631d4a 100644 --- a/core/pow/cuckaroom.go +++ b/core/pow/cuckaroom.go @@ -46,11 +46,11 @@ func (c *CuckaroomContext) Verify(proof Proof) error { return errors.New("wrong cycle length") } nonces := proof.Nonces - from := make([]uint32, proof.proofSize()) - to := make([]uint32, proof.proofSize()) - var xorFrom uint32 = 0 - var xorTo uint32 = 0 - nodemask := c.params.edgeMask >> 1 + from := make([]uint64, proof.proofSize()) + to := make([]uint64, proof.proofSize()) + var xorFrom uint64 = 0 + var xorTo uint64 = 0 + nodeMask := c.params.edgeMask >> 1 for n := 0; n < proof.proofSize(); n++ { if nonces[n] > c.params.edgeMask { @@ -59,10 +59,11 @@ func (c *CuckaroomContext) Verify(proof Proof) error { if n > 0 && nonces[n] <= nonces[n-1] { return errors.New("edges not ascending") } + // 21 is standard siphash rotation constant edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, true) - from[n] = uint32(edge & nodemask) + from[n] = edge & nodeMask xorFrom ^= from[n] - to[n] = uint32((edge >> 32) & nodemask) + to[n] = (edge >> 32) & nodeMask xorTo ^= to[n] } if xorFrom != xorTo { diff --git a/core/pow/cuckarooz.go b/core/pow/cuckarooz.go new file mode 100644 index 0000000..6eed01a --- /dev/null +++ b/core/pow/cuckarooz.go @@ -0,0 +1,104 @@ +// Copyright 2020 BlockCypher +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pow + +import ( + "errors" + + "github.com/blockcypher/libgrin/core/consensus" +) + +// NewCuckaroozCtx instantiates a new CuckaroozContext as a PowContext. Note that this can't +/// be moved in the PoWContext trait as this particular trait needs to be +/// convertible to an object trait. +func NewCuckaroozCtx(chainType consensus.ChainType, edgeBits uint8, proofSize int) *CuckaroomContext { + cp := new(CuckooParams) + params := cp.new(edgeBits, proofSize) + return &CuckaroomContext{chainType, params} +} + +// CuckaroozContext is a Cuckarooz cycle context. Only includes the verifier for now. +type CuckaroozContext struct { + chainType consensus.ChainType + params CuckooParams +} + +// SetHeaderNonce sets the header nonce. +func (c *CuckaroozContext) SetHeaderNonce(header []uint8, nonce *uint32) { + c.params.resetHeaderNonce(header, nonce) +} + +// Verify verifies the Cuckaroom context. +func (c *CuckaroozContext) Verify(proof Proof) error { + if proof.proofSize() != consensus.ChainTypeProofSize(c.chainType) { + return errors.New("wrong cycle length") + } + nonces := proof.Nonces + uvs := make([]uint64, 2*proof.proofSize()) + var xoruv uint64 = 0 + nodeMask := c.params.edgeMask<<1 | 1 + + for n := 0; n < proof.proofSize(); n++ { + if nonces[n] > c.params.edgeMask { + return errors.New("edge too big") + } + if n > 0 && nonces[n] <= nonces[n-1] { + return errors.New("edges not ascending") + } + // 21 is standard siphash rotation constant + edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, true) + uvs[2*n] = edge & nodeMask + uvs[2*n+1] = edge >> 32 & nodeMask + xoruv ^= uvs[2*n] ^ uvs[2*n+1] + } + if xoruv != 0 { + return errors.New("endpoints don't match up") + } + + n := 0 + i := 0 + j := 0 + for { + // follow cycle + j = i + k := j + for { + k = (j + 1) % (2 * c.params.proofSize) + if k == i { + break + } + } + if uvs[k] == uvs[i] { + // find other edge endpoint matching one at i + if j != i { + return errors.New("branch in cycle") + } + j = k + } + if j == i { + return errors.New("cycle dead ends") + } + i = j ^ 1 + n++ + if i == 0 { + break + } + } + if n == c.params.proofSize { + return nil + } + return errors.New("cycle too short") + +} diff --git a/core/pow/cuckarooz_test.go b/core/pow/cuckarooz_test.go new file mode 100644 index 0000000..7f05ac9 --- /dev/null +++ b/core/pow/cuckarooz_test.go @@ -0,0 +1,75 @@ +// Copyright 2020 BlockCypher +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pow + +import ( + "testing" + + "github.com/blockcypher/libgrin/core/consensus" + "github.com/stretchr/testify/assert" +) + +var v1_19HashCuckarooz = [4]uint64{ + 0xd129f63fba4d9a85, + 0x457dcb3666c5e09c, + 0x045247a2e2ee75f7, + 0x1a0f2e1bcb9d93ff, +} + +var v1_19SolCuckarooz = []uint64{ + 0x33b6, 0x487b, 0x88b7, 0x10bf6, 0x15144, 0x17cb7, 0x22621, 0x2358e, 0x23775, 0x24fb3, + 0x26b8a, 0x2876c, 0x2973e, 0x2f4ba, 0x30a62, 0x3a36b, 0x3ba5d, 0x3be67, 0x3ec56, 0x43141, + 0x4b9c5, 0x4fa06, 0x51a5c, 0x523e5, 0x53d08, 0x57d34, 0x5c2de, 0x60bba, 0x62509, 0x64d69, + 0x6803f, 0x68af4, 0x6bd52, 0x6f041, 0x6f900, 0x70051, 0x7097d, 0x735e8, 0x742c2, 0x79ae5, + 0x7f64d, 0x7fd49, +} + +var v2_29HashCuckarooz = [4]uint64{ + 0x34bb4c75c929a2f5, + 0x21df13263aa81235, + 0x37d00939eae4be06, + 0x473251cbf6941553, +} + +var v2_29SolCuckarooz = []uint64{ + 0x49733a, 0x1d49107, 0x253d2ca, 0x5ad5e59, 0x5b671bd, 0x5dcae1c, 0x5f9a589, 0x65e9afc, + 0x6a59a45, 0x7d9c6d3, 0x7df96e4, 0x8b26174, 0xa17b430, 0xa1c8c0d, 0xa8a0327, 0xabd7402, + 0xacb7c77, 0xb67524f, 0xc1c15a6, 0xc7e2c26, 0xc7f5d8d, 0xcae478a, 0xdea9229, 0xe1ab49e, + 0xf57c7db, 0xfb4e8c5, 0xff314aa, 0x110ccc12, 0x143e546f, 0x17007af8, 0x17140ea2, + 0x173d7c5d, 0x175cd13f, 0x178b8880, 0x1801edc5, 0x18c8f56b, 0x18c8fe6d, 0x19f1a31a, + 0x1bb028d1, 0x1caaa65a, 0x1cf29bc2, 0x1dbde27d, +} + +func TestCuckarooz19Vectors(t *testing.T) { + proof := new(Proof) + ctx := newCuckaroomImpl(consensus.Mainnet, 19, 42) + ctx.params.siphashKeys = v1_19HashCuckaroom + assert.Nil(t, ctx.Verify(proof.new(v1_19SolCuckaroom))) + assert.NotNil(t, ctx.Verify(proof.zero(42))) +} + +func TestCuckarooz29Vectors(t *testing.T) { + proof := new(Proof) + ctx := newCuckaroomImpl(consensus.Mainnet, 29, 42) + ctx.params.siphashKeys = v2_29HashCuckaroom + assert.Nil(t, ctx.Verify(proof.new(v2_29SolCuckaroom))) + assert.NotNil(t, ctx.Verify(proof.zero(42))) +} + +func newCuckaroozImpl(chainType consensus.ChainType, edgeBits uint8, proofSize int) *CuckaroomContext { + cp := new(CuckooParams) + params := cp.new(edgeBits, proofSize) + return &CuckaroomContext{chainType, params} +}