github.com/dominant-strategies/go-quai@v0.28.2/consensus/progpow/algorithm_progpow.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package progpow 18 19 import ( 20 "encoding/binary" 21 "math/bits" 22 23 "golang.org/x/crypto/sha3" 24 ) 25 26 const ( 27 progpowCacheBytes = 16 * 1024 // Total size 16*1024 bytes 28 progpowCacheWords = progpowCacheBytes / 4 // Total size 16*1024 bytes 29 progpowLanes = 16 // The number of parallel lanes that coordinate to calculate a single hash instance. 30 progpowRegs = 32 // The register file usage size 31 progpowDagLoads = 4 // Number of uint32 loads from the DAG per lane 32 progpowCntCache = 11 33 progpowCntMath = 18 34 progpowPeriodLength = 2147483647 // int32 max defined explicitly for other architectures 35 progpowCntDag = 64 36 progpowMixBytes = 256 37 ) 38 39 func progpowLight(size uint64, cache []uint32, hash []byte, nonce uint64, 40 blockNumber uint64, cDag []uint32) ([]byte, []byte) { 41 keccak512 := makeHasher(sha3.NewLegacyKeccak512()) 42 lookup := func(index uint32) []byte { 43 return generateDatasetItem(cache, index/16, keccak512) 44 } 45 return progpow(hash, nonce, size, blockNumber, cDag, lookup) 46 } 47 48 func progpowFull(dataset []uint32, hash []byte, nonce uint64, blockNumber uint64) ([]byte, []byte) { 49 lookup := func(index uint32) []byte { 50 mix := make([]byte, hashBytes) 51 for i := uint32(0); i < hashWords; i++ { 52 binary.LittleEndian.PutUint32(mix[i*4:], dataset[index+i]) 53 } 54 return mix 55 } 56 cDag := make([]uint32, progpowCacheBytes/4) 57 for i := uint32(0); i < progpowCacheBytes/4; i += 2 { 58 cDag[i+0] = dataset[i+0] 59 cDag[i+1] = dataset[i+1] 60 } 61 return progpow(hash, nonce, uint64(len(dataset))*4, blockNumber, cDag, lookup) 62 } 63 64 func rotl32(x uint32, n uint32) uint32 { 65 return ((x) << (n % 32)) | ((x) >> (32 - (n % 32))) 66 } 67 68 func rotr32(x uint32, n uint32) uint32 { 69 return ((x) >> (n % 32)) | ((x) << (32 - (n % 32))) 70 } 71 72 func lower32(in uint64) uint32 { 73 return uint32(in) 74 } 75 76 func higher32(in uint64) uint32 { 77 return uint32(in >> 32) 78 } 79 80 var keccakfRNDC = [24]uint32{ 81 0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001, 82 0x80008081, 0x00008009, 0x0000008a, 0x00000088, 0x80008009, 0x8000000a, 83 0x8000808b, 0x0000008b, 0x00008089, 0x00008003, 0x00008002, 0x00000080, 84 0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008} 85 86 func keccakF800Round(st *[25]uint32, r int) { 87 var keccakfROTC = [24]uint32{1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 88 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 89 20, 44} 90 var keccakfPILN = [24]uint32{10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 91 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 92 6, 1} 93 bc := make([]uint32, 5) 94 // Theta 95 for i := 0; i < 5; i++ { 96 bc[i] = st[i] ^ st[i+5] ^ st[i+10] ^ st[i+15] ^ st[i+20] 97 } 98 99 for i := 0; i < 5; i++ { 100 t := bc[(i+4)%5] ^ rotl32(bc[(i+1)%5], 1) 101 for j := 0; j < 25; j += 5 { 102 st[j+i] ^= t 103 } 104 } 105 106 // Rho Pi 107 t := st[1] 108 for i, j := range keccakfPILN { 109 bc[0] = st[j] 110 st[j] = rotl32(t, keccakfROTC[i]) 111 t = bc[0] 112 } 113 114 // Chi 115 for j := 0; j < 25; j += 5 { 116 bc[0] = st[j+0] 117 bc[1] = st[j+1] 118 bc[2] = st[j+2] 119 bc[3] = st[j+3] 120 bc[4] = st[j+4] 121 st[j+0] ^= ^bc[1] & bc[2] 122 st[j+1] ^= ^bc[2] & bc[3] 123 st[j+2] ^= ^bc[3] & bc[4] 124 st[j+3] ^= ^bc[4] & bc[0] 125 st[j+4] ^= ^bc[0] & bc[1] 126 } 127 128 // Iota 129 st[0] ^= keccakfRNDC[r] 130 //return st 131 } 132 133 func keccakF800Short(headerHash []byte, nonce uint64, result []uint32) uint64 { 134 var st [25]uint32 135 136 for i := 0; i < 8; i++ { 137 st[i] = (uint32(headerHash[4*i])) + 138 (uint32(headerHash[4*i+1]) << 8) + 139 (uint32(headerHash[4*i+2]) << 16) + 140 (uint32(headerHash[4*i+3]) << 24) 141 } 142 143 st[8] = lower32(nonce) 144 st[9] = higher32(nonce) 145 for i := 0; i < 8; i++ { 146 st[10+i] = result[i] 147 } 148 149 for r := 0; r < 21; r++ { 150 keccakF800Round(&st, r) 151 } 152 keccakF800Round(&st, 21) 153 ret := make([]byte, 8) 154 binary.BigEndian.PutUint32(ret[4:], st[0]) 155 binary.BigEndian.PutUint32(ret, st[1]) 156 return binary.LittleEndian.Uint64(ret) 157 } 158 159 func keccakF800Long(headerHash []byte, nonce uint64, result []uint32) []byte { 160 var st [25]uint32 161 162 for i := 0; i < 8; i++ { 163 st[i] = (uint32(headerHash[4*i])) + 164 (uint32(headerHash[4*i+1]) << 8) + 165 (uint32(headerHash[4*i+2]) << 16) + 166 (uint32(headerHash[4*i+3]) << 24) 167 } 168 169 st[8] = lower32(nonce) 170 st[9] = higher32(nonce) 171 for i := 0; i < 8; i++ { 172 st[10+i] = result[i] 173 } 174 175 for r := 0; r <= 21; r++ { 176 keccakF800Round(&st, r) 177 } 178 ret := make([]byte, 32) 179 for i := 0; i < 8; i++ { 180 binary.LittleEndian.PutUint32(ret[i*4:], st[i]) 181 } 182 return ret 183 } 184 185 func fnv1a(h *uint32, d uint32) uint32 { 186 *h = (*h ^ d) * uint32(0x1000193) 187 return *h 188 } 189 190 type kiss99State struct { 191 z uint32 192 w uint32 193 jsr uint32 194 jcong uint32 195 } 196 197 func kiss99(st *kiss99State) uint32 { 198 var MWC uint32 199 st.z = 36969*(st.z&65535) + (st.z >> 16) 200 st.w = 18000*(st.w&65535) + (st.w >> 16) 201 MWC = ((st.z << 16) + st.w) 202 st.jsr ^= (st.jsr << 17) 203 st.jsr ^= (st.jsr >> 13) 204 st.jsr ^= (st.jsr << 5) 205 st.jcong = 69069*st.jcong + 1234567 206 return ((MWC ^ st.jcong) + st.jsr) 207 } 208 209 func fillMix(seed uint64, laneId uint32) [progpowRegs]uint32 { 210 var st kiss99State 211 var mix [progpowRegs]uint32 212 213 fnvHash := uint32(0x811c9dc5) 214 215 st.z = fnv1a(&fnvHash, lower32(seed)) 216 st.w = fnv1a(&fnvHash, higher32(seed)) 217 st.jsr = fnv1a(&fnvHash, laneId) 218 st.jcong = fnv1a(&fnvHash, laneId) 219 220 for i := 0; i < progpowRegs; i++ { 221 mix[i] = kiss99(&st) 222 } 223 return mix 224 } 225 226 // Merge new data from b into the value in a 227 // Assuming A has high entropy only do ops that retain entropy 228 // even if B is low entropy 229 // (IE don't do A&B) 230 func merge(a *uint32, b uint32, r uint32) { 231 switch r % 4 { 232 case 0: 233 *a = (*a * 33) + b 234 case 1: 235 *a = (*a ^ b) * 33 236 case 2: 237 *a = rotl32(*a, ((r>>16)%31)+1) ^ b 238 default: 239 *a = rotr32(*a, ((r>>16)%31)+1) ^ b 240 } 241 } 242 243 func progpowInit(seed uint64) (kiss99State, [progpowRegs]uint32, [progpowRegs]uint32) { 244 var randState kiss99State 245 246 fnvHash := uint32(0x811c9dc5) 247 248 randState.z = fnv1a(&fnvHash, lower32(seed)) 249 randState.w = fnv1a(&fnvHash, higher32(seed)) 250 randState.jsr = fnv1a(&fnvHash, lower32(seed)) 251 randState.jcong = fnv1a(&fnvHash, higher32(seed)) 252 253 // Create a random sequence of mix destinations for merge() 254 // and mix sources for cache reads 255 // guarantees every destination merged once 256 // guarantees no duplicate cache reads, which could be optimized away 257 // Uses Fisher-Yates shuffle 258 var dstSeq = [32]uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} 259 var srcSeq = [32]uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} 260 261 for i := uint32(progpowRegs - 1); i > 0; i-- { 262 j := kiss99(&randState) % (i + 1) 263 dstSeq[i], dstSeq[j] = dstSeq[j], dstSeq[i] 264 j = kiss99(&randState) % (i + 1) 265 srcSeq[i], srcSeq[j] = srcSeq[j], srcSeq[i] 266 } 267 return randState, dstSeq, srcSeq 268 } 269 270 // Random math between two input values 271 func progpowMath(a uint32, b uint32, r uint32) uint32 { 272 switch r % 11 { 273 case 0: 274 return a + b 275 case 1: 276 return a * b 277 case 2: 278 return higher32(uint64(a) * uint64(b)) 279 case 3: 280 if a < b { 281 return a 282 } 283 return b 284 case 4: 285 return rotl32(a, b) 286 case 5: 287 return rotr32(a, b) 288 case 6: 289 return a & b 290 case 7: 291 return a | b 292 case 8: 293 return a ^ b 294 case 9: 295 return uint32(bits.LeadingZeros32(a) + bits.LeadingZeros32(b)) 296 case 10: 297 return uint32(bits.OnesCount32(a) + bits.OnesCount32(b)) 298 299 default: 300 return 0 301 } 302 } 303 304 func progpowLoop(seed uint64, loop uint32, mix *[progpowLanes][progpowRegs]uint32, 305 lookup func(index uint32) []byte, 306 cDag []uint32, datasetSize uint32) { 307 // All lanes share a base address for the global load 308 // Global offset uses mix[0] to guarantee it depends on the load result 309 gOffset := mix[loop%progpowLanes][0] % (64 * datasetSize / (progpowLanes * progpowDagLoads)) 310 311 var ( 312 srcCounter uint32 313 dstCounter uint32 314 randState kiss99State 315 srcSeq [32]uint32 316 dstSeq [32]uint32 317 rnd = kiss99 318 index uint32 319 data_g []uint32 = make([]uint32, progpowDagLoads) 320 ) 321 // 256 bytes of dag data 322 dag_item := make([]byte, 256) 323 // The lookup returns 64, so we'll fetch four items 324 copy(dag_item, lookup((gOffset*progpowLanes)*progpowDagLoads)) 325 copy(dag_item[64:], lookup((gOffset*progpowLanes)*progpowDagLoads+16)) 326 copy(dag_item[128:], lookup((gOffset*progpowLanes)*progpowDagLoads+32)) 327 copy(dag_item[192:], lookup((gOffset*progpowLanes)*progpowDagLoads+48)) 328 329 // Lanes can execute in parallel and will be convergent 330 for l := uint32(0); l < progpowLanes; l++ { 331 332 // initialize the seed and mix destination sequence 333 randState, dstSeq, srcSeq = progpowInit(seed) 334 srcCounter = uint32(0) 335 dstCounter = uint32(0) 336 337 for i := uint32(0); i < progpowCntMath; i++ { 338 if i < progpowCntCache { 339 // Cached memory access 340 // lanes access random location 341 342 src := srcSeq[(srcCounter)%progpowRegs] 343 srcCounter++ 344 345 offset := mix[l][src] % progpowCacheWords 346 data32 := cDag[offset] 347 348 dst := dstSeq[(dstCounter)%progpowRegs] 349 dstCounter++ 350 351 r := kiss99(&randState) 352 merge(&mix[l][dst], data32, r) 353 } 354 355 // Random Math 356 srcRnd := rnd(&randState) % (progpowRegs * (progpowRegs - 1)) 357 src1 := srcRnd % progpowRegs 358 src2 := srcRnd / progpowRegs 359 if src2 >= src1 { 360 src2++ 361 } 362 data32 := progpowMath(mix[l][src1], mix[l][src2], rnd(&randState)) 363 364 dst := dstSeq[(dstCounter)%progpowRegs] 365 dstCounter++ 366 367 merge(&mix[l][dst], data32, rnd(&randState)) 368 } 369 index = ((l ^ loop) % progpowLanes) * progpowDagLoads 370 371 data_g[0] = binary.LittleEndian.Uint32(dag_item[4*index:]) 372 data_g[1] = binary.LittleEndian.Uint32(dag_item[4*(index+1):]) 373 data_g[2] = binary.LittleEndian.Uint32(dag_item[4*(index+2):]) 374 data_g[3] = binary.LittleEndian.Uint32(dag_item[4*(index+3):]) 375 376 merge(&mix[l][0], data_g[0], rnd(&randState)) 377 378 for i := 1; i < progpowDagLoads; i++ { 379 dst := dstSeq[(dstCounter)%progpowRegs] 380 dstCounter++ 381 merge(&mix[l][dst], data_g[i], rnd(&randState)) 382 } 383 } 384 } 385 386 func progpow(hash []byte, nonce uint64, size uint64, blockNumber uint64, cDag []uint32, 387 lookup func(index uint32) []byte) ([]byte, []byte) { 388 var ( 389 mix [progpowLanes][progpowRegs]uint32 390 laneResults [progpowLanes]uint32 391 ) 392 result := make([]uint32, 8) 393 seed := keccakF800Short(hash, nonce, result) 394 for lane := uint32(0); lane < progpowLanes; lane++ { 395 mix[lane] = fillMix(seed, lane) 396 } 397 period := (blockNumber / progpowPeriodLength) 398 for l := uint32(0); l < progpowCntDag; l++ { 399 progpowLoop(period, l, &mix, lookup, cDag, uint32(size/progpowMixBytes)) 400 } 401 402 // Reduce mix data to a single per-lane result 403 for lane := uint32(0); lane < progpowLanes; lane++ { 404 laneResults[lane] = 0x811c9dc5 405 for i := uint32(0); i < progpowRegs; i++ { 406 fnv1a(&laneResults[lane], mix[lane][i]) 407 } 408 } 409 for i := uint32(0); i < 8; i++ { 410 result[i] = 0x811c9dc5 411 } 412 for lane := uint32(0); lane < progpowLanes; lane++ { 413 fnv1a(&result[lane%8], laneResults[lane]) 414 } 415 finalHash := keccakF800Long(hash, seed, result[:]) 416 mixHash := make([]byte, 8*4) 417 for i := 0; i < 8; i++ { 418 binary.LittleEndian.PutUint32(mixHash[i*4:], result[i]) 419 } 420 return mixHash[:], finalHash[:] 421 }