|
| 1 | +// Copyright (c) 2021 Electric Coin Company |
| 2 | + |
1 | 3 | package equihash
|
2 | 4 |
|
3 |
| -type Config struct { |
4 |
| - n uint32 |
5 |
| - k uint32 |
6 |
| - personal []byte |
| 5 | +import ( |
| 6 | + "bytes" |
| 7 | + "encoding/binary" |
| 8 | + "fmt" |
| 9 | + |
| 10 | + "github.com/sencha-dev/powkit/internal/common/convutil" |
| 11 | + "github.com/sencha-dev/powkit/internal/crypto" |
| 12 | +) |
| 13 | + |
| 14 | +const ( |
| 15 | + uint32Size = 4 |
| 16 | + wordSize = 32 |
| 17 | + wordMask = (1 << wordSize) - 1 |
| 18 | +) |
| 19 | + |
| 20 | +func collisionBitLength(n, k uint32) uint32 { |
| 21 | + return n / (k + 1) |
| 22 | +} |
| 23 | + |
| 24 | +func collisionByteLength(n, k uint32) uint32 { |
| 25 | + return (collisionBitLength(n, k) + 7) / 8 |
| 26 | +} |
| 27 | + |
| 28 | +func indicesPerHashOutput(n uint32) uint32 { |
| 29 | + return 512 / n |
| 30 | +} |
| 31 | + |
| 32 | +func hashOutput(n uint32) uint32 { |
| 33 | + return indicesPerHashOutput(n) * ((n + 7) / 8) |
| 34 | +} |
| 35 | + |
| 36 | +func hashLength(n, k uint32) uint32 { |
| 37 | + return (k + 1) * collisionByteLength(n, k) |
| 38 | +} |
| 39 | + |
| 40 | +func blakePersonal(personal []byte, n, k uint32) []byte { |
| 41 | + nBytes := make([]byte, 4) |
| 42 | + binary.LittleEndian.PutUint32(nBytes, n) |
| 43 | + |
| 44 | + kBytes := make([]byte, 4) |
| 45 | + binary.LittleEndian.PutUint32(kBytes, k) |
| 46 | + |
| 47 | + personalBytes := bytes.Join([][]byte{ |
| 48 | + personal, |
| 49 | + nBytes, |
| 50 | + kBytes, |
| 51 | + }, nil) |
| 52 | + |
| 53 | + return personalBytes |
7 | 54 | }
|
8 | 55 |
|
9 |
| -func New(n, k uint32, personal string) *Config { |
10 |
| - cfg := &Config{ |
11 |
| - n: n, |
12 |
| - k: k, |
13 |
| - personal: []byte(personal), |
| 56 | +func expandArray(input []byte, bitLen, bytePad uint32) ([]byte, error) { |
| 57 | + if bitLen < 8 { |
| 58 | + return nil, fmt.Errorf("bitLen must be no less than 8") |
| 59 | + } else if wordSize < bitLen { |
| 60 | + return nil, fmt.Errorf("bitLen must be no greater than %d", wordSize-7) |
14 | 61 | }
|
15 | 62 |
|
16 |
| - return cfg |
| 63 | + inputLen := uint32(len(input)) |
| 64 | + outputWidth := (bitLen+7)/8 + bytePad |
| 65 | + outputLen := 8 * outputWidth * inputLen / bitLen |
| 66 | + |
| 67 | + if outputLen == inputLen { |
| 68 | + return input, nil |
| 69 | + } |
| 70 | + |
| 71 | + output := make([]byte, outputLen) |
| 72 | + var bitLenMask uint32 = (1 << bitLen) - 1 |
| 73 | + |
| 74 | + var accBits, accValue, j uint32 |
| 75 | + for i := range input { |
| 76 | + accValue = (accValue << 8) | uint32(input[i]) |
| 77 | + accBits += 8 |
| 78 | + |
| 79 | + if accBits >= bitLen { |
| 80 | + accBits -= bitLen |
| 81 | + for x := bytePad; x < outputWidth; x++ { |
| 82 | + p1 := accValue >> (accBits + (8 * (outputWidth - x - 1))) |
| 83 | + p2 := (bitLenMask >> (8 * (outputWidth - x - 1))) & 0xFF |
| 84 | + output[j+x] = uint8(p1 & p2) |
| 85 | + } |
| 86 | + |
| 87 | + j += outputWidth |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + return output, nil |
17 | 92 | }
|
18 | 93 |
|
19 |
| -func (cfg *Config) TraditionalVerify(seed, input []byte, nonce uint32) bool { |
20 |
| - return TraditionalVerify(cfg.n, cfg.k, cfg.personal, seed, input, nonce) |
| 94 | +func indicesFromMinimal(n, k uint32, minimal []byte) ([]uint32, error) { |
| 95 | + cBitLen := collisionBitLength(n, k) |
| 96 | + minimalLen := uint32(len(minimal)) |
| 97 | + |
| 98 | + if minimalLen != ((1<<k)*(cBitLen+1))/8 { |
| 99 | + return nil, fmt.Errorf("invalid minimal for parameters") |
| 100 | + } |
| 101 | + |
| 102 | + if (((cBitLen + 1) + 7) / 8) > 4 { |
| 103 | + return nil, fmt.Errorf("invalid n, k parameters") |
| 104 | + } |
| 105 | + |
| 106 | + bytePad := uint32Size - ((cBitLen+1)+7)/8 |
| 107 | + indices, err := expandArray(minimal, cBitLen+1, bytePad) |
| 108 | + if err != nil { |
| 109 | + return nil, err |
| 110 | + } |
| 111 | + |
| 112 | + return convutil.BytesToUint32Array(indices, binary.BigEndian), nil |
21 | 113 | }
|
22 | 114 |
|
23 |
| -func (cfg *Config) ZCashVerify(header, soln []byte) (bool, error) { |
24 |
| - return ZCashVerify(cfg.n, cfg.k, cfg.personal, header, soln) |
| 115 | +func hasCollision(a, b *node, len uint32) bool { |
| 116 | + for i := uint32(0); i < len; i++ { |
| 117 | + if a.hash[i] != b.hash[i] { |
| 118 | + return false |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + return true |
| 123 | +} |
| 124 | + |
| 125 | +func distinctIndices(a, b *node) bool { |
| 126 | + for _, i := range a.indices { |
| 127 | + for _, j := range b.indices { |
| 128 | + if i == j { |
| 129 | + return false |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + return true |
| 135 | +} |
| 136 | + |
| 137 | +func validateSubtrees(n, k uint32, a, b *node) error { |
| 138 | + if !hasCollision(a, b, collisionByteLength(n, k)) { |
| 139 | + return fmt.Errorf("collision") |
| 140 | + } else if b.indicesBefore(a) { |
| 141 | + return fmt.Errorf("out of order") |
| 142 | + } else if !distinctIndices(a, b) { |
| 143 | + return fmt.Errorf("duplicate indices") |
| 144 | + } |
| 145 | + |
| 146 | + return nil |
| 147 | +} |
| 148 | + |
| 149 | +type node struct { |
| 150 | + hash []byte |
| 151 | + indices []uint32 |
| 152 | +} |
| 153 | + |
| 154 | +func (n *node) indicesBefore(b *node) bool { |
| 155 | + return n.indices[0] < b.indices[0] |
| 156 | +} |
| 157 | + |
| 158 | +func hashBlakeWithOffset(initialState, personalState []byte, offset, hashLength uint32) []byte { |
| 159 | + newState := make([]byte, len(initialState)+4) |
| 160 | + copy(newState, initialState) |
| 161 | + binary.LittleEndian.PutUint32(newState[len(initialState):], offset) |
| 162 | + |
| 163 | + return crypto.Blake2b(newState, personalState, int(hashLength)) |
| 164 | +} |
| 165 | + |
| 166 | +func generateHash(initialState, personalState []byte, g, hashLength uint32, twist bool) []byte { |
| 167 | + if !twist { |
| 168 | + return hashBlakeWithOffset(initialState, personalState, g, hashLength) |
| 169 | + } |
| 170 | + |
| 171 | + myHash := make([]uint32, 16) |
| 172 | + startIndex := g & 0xFFFFFFF0 |
| 173 | + for g2 := startIndex; g2 <= g; g2++ { |
| 174 | + tmpHash := hashBlakeWithOffset(initialState, personalState, g2, hashLength) |
| 175 | + for i := 0; i < 16; i++ { |
| 176 | + myHash[i] += binary.LittleEndian.Uint32(tmpHash[i*4 : (i+1)*4]) |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + hash := convutil.Uint32ArrayToBytes(myHash, binary.LittleEndian) |
| 181 | + for j := 15; j < int(hashLength); j += 16 { |
| 182 | + hash[j] &= 0xF8 |
| 183 | + } |
| 184 | + |
| 185 | + return hash |
| 186 | +} |
| 187 | + |
| 188 | +func newNode(n, k uint32, personal []byte, twist bool, state []byte, i uint32) (*node, error) { |
| 189 | + g := i / indicesPerHashOutput(n) |
| 190 | + hashLength := hashOutput(n) |
| 191 | + personalState := blakePersonal(personal, n, k) |
| 192 | + hash := generateHash(state, personalState, g, hashLength, twist) |
| 193 | + |
| 194 | + start := (i % indicesPerHashOutput(n)) * ((n + 7) / 8) |
| 195 | + end := start + ((n + 7) / 8) |
| 196 | + |
| 197 | + minimalHash, err := expandArray(hash[start:end], collisionBitLength(n, k), 0) |
| 198 | + if err != nil { |
| 199 | + return nil, err |
| 200 | + } |
| 201 | + |
| 202 | + return &node{ |
| 203 | + hash: minimalHash, |
| 204 | + indices: []uint32{i}, |
| 205 | + }, nil |
| 206 | +} |
| 207 | + |
| 208 | +func newNodeFromChildrenRef(a, b *node, trim uint32) *node { |
| 209 | + len := uint32(len(a.hash)) |
| 210 | + hash := make([]byte, len-trim) |
| 211 | + for i := trim; i < len; i++ { |
| 212 | + hash[i-trim] = a.hash[i] ^ b.hash[i] |
| 213 | + } |
| 214 | + |
| 215 | + indices := make([]uint32, 0) |
| 216 | + if a.indicesBefore(b) { |
| 217 | + indices = append(indices, a.indices...) |
| 218 | + indices = append(indices, b.indices...) |
| 219 | + } else { |
| 220 | + indices = append(indices, b.indices...) |
| 221 | + indices = append(indices, a.indices...) |
| 222 | + } |
| 223 | + |
| 224 | + n := &node{ |
| 225 | + hash: hash, |
| 226 | + indices: indices, |
| 227 | + } |
| 228 | + |
| 229 | + return n |
| 230 | +} |
| 231 | + |
| 232 | +func isValidSolutionIterative(n, k uint32, personal []byte, twist bool, state []byte, indices []uint32) (bool, error) { |
| 233 | + var err error |
| 234 | + rows := make([]*node, len(indices)) |
| 235 | + for i := range indices { |
| 236 | + rows[i], err = newNode(n, k, personal, twist, state, indices[i]) |
| 237 | + if err != nil { |
| 238 | + return false, err |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + hashLen := hashLength(n, k) |
| 243 | + for len(rows) > 1 { |
| 244 | + curRows := make([]*node, 0) |
| 245 | + for i := 0; i < len(rows); i += 2 { |
| 246 | + a := rows[i] |
| 247 | + b := rows[i+1] |
| 248 | + err := validateSubtrees(n, k, a, b) |
| 249 | + if err != nil { |
| 250 | + return false, err |
| 251 | + } |
| 252 | + |
| 253 | + row := newNodeFromChildrenRef(a, b, collisionByteLength(n, k)) |
| 254 | + curRows = append(curRows, row) |
| 255 | + } |
| 256 | + |
| 257 | + rows = curRows |
| 258 | + hashLen -= collisionByteLength(n, k) |
| 259 | + } |
| 260 | + |
| 261 | + for i := uint32(0); i < hashLen; i++ { |
| 262 | + if rows[0].hash[i] != 0 { |
| 263 | + return false, nil |
| 264 | + } |
| 265 | + } |
| 266 | + |
| 267 | + return true, nil |
| 268 | +} |
| 269 | + |
| 270 | +func verify(n, k uint32, personal []byte, twist bool, header, soln []byte) (bool, error) { |
| 271 | + indices, err := indicesFromMinimal(n, k, soln) |
| 272 | + if err != nil { |
| 273 | + return false, err |
| 274 | + } |
| 275 | + |
| 276 | + return isValidSolutionIterative(n, k, personal, twist, header, indices) |
25 | 277 | }
|
0 commit comments