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  }