github.com/consensys/gnark-crypto@v0.14.0/field/generator/internal/templates/element/base.go (about) 1 package element 2 3 const Base = ` 4 5 import ( 6 "math/big" 7 "math/bits" 8 "io" 9 "crypto/rand" 10 "encoding/binary" 11 "strconv" 12 "errors" 13 "reflect" 14 "strings" 15 16 "github.com/consensys/gnark-crypto/field/hash" 17 "github.com/consensys/gnark-crypto/field/pool" 18 "github.com/bits-and-blooms/bitset" 19 ) 20 21 // {{.ElementName}} represents a field element stored on {{.NbWords}} words (uint64) 22 // 23 // {{.ElementName}} are assumed to be in Montgomery form in all methods. 24 // 25 // Modulus q = 26 // 27 // q[base10] = {{.Modulus}} 28 // q[base16] = 0x{{.ModulusHex}} 29 // 30 // Warning 31 // 32 // This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. 33 type {{.ElementName}} [{{.NbWords}}]uint64 34 35 const ( 36 Limbs = {{.NbWords}} // number of 64 bits words needed to represent a {{.ElementName}} 37 Bits = {{.NbBits}} // number of bits needed to represent a {{.ElementName}} 38 Bytes = {{.NbBytes}} // number of bytes needed to represent a {{.ElementName}} 39 ) 40 41 42 // Field modulus q 43 const ( 44 {{- range $i := $.NbWordsIndexesFull}} 45 q{{$i}} uint64 = {{index $.Q $i}} 46 {{- if eq $.NbWords 1}} 47 q uint64 = q0 48 {{- end}} 49 {{- end}} 50 ) 51 52 var q{{.ElementName}} = {{.ElementName}}{ 53 {{- range $i := $.NbWordsIndexesFull}} 54 q{{$i}},{{end}} 55 } 56 57 var _modulus big.Int // q stored as big.Int 58 59 // Modulus returns q as a big.Int 60 // 61 // q[base10] = {{.Modulus}} 62 // q[base16] = 0x{{.ModulusHex}} 63 func Modulus() *big.Int { 64 return new(big.Int).Set(&_modulus) 65 } 66 67 // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r 68 // used for Montgomery reduction 69 const qInvNeg uint64 = {{index .QInverse 0}} 70 71 func init() { 72 _modulus.SetString("{{.ModulusHex}}", 16) 73 } 74 75 // New{{.ElementName}} returns a new {{.ElementName}} from a uint64 value 76 // 77 // it is equivalent to 78 // var v {{.ElementName}} 79 // v.SetUint64(...) 80 func New{{.ElementName}}(v uint64) {{.ElementName}} { 81 z := {{.ElementName}}{v} 82 z.Mul(&z, &rSquare) 83 return z 84 } 85 86 // SetUint64 sets z to v and returns z 87 func (z *{{.ElementName}}) SetUint64(v uint64) *{{.ElementName}} { 88 // sets z LSB to v (non-Montgomery form) and convert z to Montgomery form 89 *z = {{.ElementName}}{v} 90 return z.Mul(z, &rSquare) // z.toMont() 91 } 92 93 // SetInt64 sets z to v and returns z 94 func (z *{{.ElementName}}) SetInt64(v int64) *{{.ElementName}} { 95 96 // absolute value of v 97 m := v >> 63 98 z.SetUint64(uint64((v ^ m) - m)) 99 100 if m != 0 { 101 // v is negative 102 z.Neg(z) 103 } 104 105 return z 106 } 107 108 // Set z = x and returns z 109 func (z *{{.ElementName}}) Set(x *{{.ElementName}}) *{{.ElementName}} { 110 {{- range $i := .NbWordsIndexesFull}} 111 z[{{$i}}] = x[{{$i}}] 112 {{- end}} 113 return z 114 } 115 116 // SetInterface converts provided interface into {{.ElementName}} 117 // returns an error if provided type is not supported 118 // supported types: 119 // {{.ElementName}} 120 // *{{.ElementName}} 121 // uint64 122 // int 123 // string (see SetString for valid formats) 124 // *big.Int 125 // big.Int 126 // []byte 127 func (z *{{.ElementName}}) SetInterface(i1 interface{}) (*{{.ElementName}}, error) { 128 if i1 == nil { 129 return nil, errors.New("can't set {{.PackageName}}.{{.ElementName}} with <nil>") 130 } 131 132 switch c1 := i1.(type) { 133 case {{.ElementName}}: 134 return z.Set(&c1), nil 135 case *{{.ElementName}}: 136 if c1 == nil { 137 return nil, errors.New("can't set {{.PackageName}}.{{.ElementName}} with <nil>") 138 } 139 return z.Set(c1), nil 140 case uint8: 141 return z.SetUint64(uint64(c1)), nil 142 case uint16: 143 return z.SetUint64(uint64(c1)), nil 144 case uint32: 145 return z.SetUint64(uint64(c1)), nil 146 case uint: 147 return z.SetUint64(uint64(c1)), nil 148 case uint64: 149 return z.SetUint64(c1), nil 150 case int8: 151 return z.SetInt64(int64(c1)), nil 152 case int16: 153 return z.SetInt64(int64(c1)), nil 154 case int32: 155 return z.SetInt64(int64(c1)), nil 156 case int64: 157 return z.SetInt64(c1), nil 158 case int: 159 return z.SetInt64(int64(c1)), nil 160 case string: 161 return z.SetString(c1) 162 case *big.Int: 163 if c1 == nil { 164 return nil, errors.New("can't set {{.PackageName}}.{{.ElementName}} with <nil>") 165 } 166 return z.SetBigInt(c1), nil 167 case big.Int: 168 return z.SetBigInt(&c1), nil 169 case []byte: 170 return z.SetBytes(c1), nil 171 default: 172 return nil, errors.New("can't set {{.PackageName}}.{{.ElementName}} from type " + reflect.TypeOf(i1).String()) 173 } 174 } 175 176 // SetZero z = 0 177 func (z *{{.ElementName}}) SetZero() *{{.ElementName}} { 178 {{- range $i := .NbWordsIndexesFull}} 179 z[{{$i}}] = 0 180 {{- end}} 181 return z 182 } 183 184 // SetOne z = 1 (in Montgomery form) 185 func (z *{{.ElementName}}) SetOne() *{{.ElementName}} { 186 {{- range $i := .NbWordsIndexesFull}} 187 z[{{$i}}] = {{index $.One $i}} 188 {{- end}} 189 return z 190 } 191 192 193 // Div z = x*y⁻¹ (mod q) 194 func (z *{{.ElementName}}) Div( x, y *{{.ElementName}}) *{{.ElementName}} { 195 var yInv {{.ElementName}} 196 yInv.Inverse( y) 197 z.Mul( x, &yInv) 198 return z 199 } 200 201 // Equal returns z == x; constant-time 202 func (z *{{.ElementName}}) Equal(x *{{.ElementName}}) bool { 203 return z.NotEqual(x) == 0 204 } 205 206 // NotEqual returns 0 if and only if z == x; constant-time 207 func (z *{{.ElementName}}) NotEqual(x *{{.ElementName}}) uint64 { 208 return {{- range $i := reverse .NbWordsIndexesNoZero}}(z[{{$i}}] ^ x[{{$i}}]) | {{end}}(z[0] ^ x[0]) 209 } 210 211 // IsZero returns z == 0 212 func (z *{{.ElementName}}) IsZero() bool { 213 return ( {{- range $i := reverse .NbWordsIndexesNoZero}} z[{{$i}}] | {{end}}z[0]) == 0 214 } 215 216 // IsOne returns z == 1 217 func (z *{{.ElementName}}) IsOne() bool { 218 {{- if eq .NbWords 1}} 219 return z[0] == {{index $.One 0}} 220 {{- else}} 221 return ( {{- range $i := reverse .NbWordsIndexesNoZero -}}{{if ne (index $.One $i) 0}}(z[{{$i}}] ^ {{index $.One $i}}) | {{else}}z[{{$i}}] | {{end}}{{- end}}(z[0] ^ {{index $.One 0}}) ) == 0 222 {{- end}} 223 } 224 225 // IsUint64 reports whether z can be represented as an uint64. 226 func (z *{{.ElementName}}) IsUint64() bool { 227 {{- if eq .NbWords 1}} 228 return true 229 {{- else}} 230 zz := *z 231 zz.fromMont() 232 return zz.FitsOnOneWord() 233 {{- end}} 234 } 235 236 // Uint64 returns the uint64 representation of x. If x cannot be represented in a uint64, the result is undefined. 237 func (z *{{.ElementName}}) Uint64() uint64 { 238 return z.Bits()[0] 239 } 240 241 // FitsOnOneWord reports whether z words (except the least significant word) are 0 242 // 243 // It is the responsibility of the caller to convert from Montgomery to Regular form if needed. 244 func (z *{{.ElementName}}) FitsOnOneWord() bool { 245 {{- if eq .NbWords 1}} 246 return true 247 {{- else}} 248 return ( {{- range $i := reverse .NbWordsIndexesNoZero}} z[{{$i}}] {{- if ne $i 1}}|{{- end}} {{end}}) == 0 249 {{- end}} 250 } 251 252 // Cmp compares (lexicographic order) z and x and returns: 253 // 254 // -1 if z < x 255 // 0 if z == x 256 // +1 if z > x 257 // 258 func (z *{{.ElementName}}) Cmp(x *{{.ElementName}}) int { 259 _z := z.Bits() 260 _x := x.Bits() 261 {{- range $i := reverse $.NbWordsIndexesFull}} 262 if _z[{{$i}}] > _x[{{$i}}] { 263 return 1 264 } else if _z[{{$i}}] < _x[{{$i}}] { 265 return -1 266 } 267 {{- end}} 268 return 0 269 } 270 271 // LexicographicallyLargest returns true if this element is strictly lexicographically 272 // larger than its negation, false otherwise 273 func (z *{{.ElementName}}) LexicographicallyLargest() bool { 274 // adapted from github.com/zkcrypto/bls12_381 275 // we check if the element is larger than (q-1) / 2 276 // if z - (((q -1) / 2) + 1) have no underflow, then z > (q-1) / 2 277 278 _z := z.Bits() 279 280 var b uint64 281 _, b = bits.Sub64(_z[0], {{index .QMinusOneHalvedP 0}}, 0) 282 {{- range $i := .NbWordsIndexesNoZero}} 283 _, b = bits.Sub64(_z[{{$i}}], {{index $.QMinusOneHalvedP $i}}, b) 284 {{- end}} 285 286 return b == 0 287 } 288 289 // SetRandom sets z to a uniform random value in [0, q). 290 // 291 // This might error only if reading from crypto/rand.Reader errors, 292 // in which case, value of z is undefined. 293 func (z *{{.ElementName}}) SetRandom() (*{{.ElementName}}, error) { 294 // this code is generated for all modulus 295 // and derived from go/src/crypto/rand/util.go 296 297 // l is number of limbs * 8; the number of bytes needed to reconstruct {{.NbWords}} uint64 298 const l = {{mul 8 .NbWords}} 299 300 // bitLen is the maximum bit length needed to encode a value < q. 301 const bitLen = {{.NbBits}} 302 303 // k is the maximum byte length needed to encode a value < q. 304 const k = (bitLen + 7) / 8 305 306 // b is the number of bits in the most significant byte of q-1. 307 b := uint(bitLen % 8) 308 if b == 0 { 309 b = 8 310 } 311 312 var bytes [l]byte 313 314 for { 315 // note that bytes[k:l] is always 0 316 if _, err := io.ReadFull(rand.Reader, bytes[:k]); err != nil { 317 return nil, err 318 } 319 320 // Clear unused bits in in the most significant byte to increase probability 321 // that the candidate is < q. 322 bytes[k-1] &= uint8(int(1<<b) - 1) 323 324 {{- range $i := .NbWordsIndexesFull}} 325 {{- $k := add $i 1}} 326 z[{{$i}}] = binary.LittleEndian.Uint64(bytes[{{mul $i 8}}:{{mul $k 8}}]) 327 {{- end}} 328 329 if !z.smallerThanModulus() { 330 continue // ignore the candidate and re-sample 331 } 332 333 return z, nil 334 } 335 } 336 337 // smallerThanModulus returns true if z < q 338 // This is not constant time 339 func (z *{{.ElementName}}) smallerThanModulus() bool { 340 {{- if eq $.NbWords 1}} 341 return z[0] < q 342 {{- else}} 343 return ({{- range $i := reverse .NbWordsIndexesNoZero}} z[{{$i}}] < q{{$i}} || ( z[{{$i}}] == q{{$i}} && ( 344 {{- end}}z[0] < q0 {{- range $i := .NbWordsIndexesNoZero}} )) {{- end}} ) 345 {{- end }} 346 } 347 348 // One returns 1 349 func One() {{.ElementName}} { 350 var one {{.ElementName}} 351 one.SetOne() 352 return one 353 } 354 355 // Halve sets z to z / 2 (mod q) 356 func (z *{{.ElementName}}) Halve() { 357 {{- if not (and (eq .NbWords 1) (.NoCarry))}} 358 var carry uint64 359 {{- end}} 360 361 if z[0]&1 == 1 { 362 {{- template "add_q" dict "all" . "V1" "z" }} 363 } 364 {{- rsh "z" .NbWords}} 365 366 {{- if not .NoCarry}} 367 if carry != 0 { 368 // when we added q, the result was larger than our available limbs 369 // when we shift right, we need to set the highest bit 370 z[{{.NbWordsLastIndex}}] |= (1 << 63) 371 } 372 {{end}} 373 } 374 375 {{ define "add_q" }} 376 // {{$.V1}} = {{$.V1}} + q 377 {{- range $i := $.all.NbWordsIndexesFull }} 378 {{- $carryIn := ne $i 0}} 379 {{- $carryOut := or (ne $i $.all.NbWordsLastIndex) (and (eq $i $.all.NbWordsLastIndex) (not $.all.NoCarry))}} 380 {{$.V1}}[{{$i}}], {{- if $carryOut}}carry{{- else}}_{{- end}} = bits.Add64({{$.V1}}[{{$i}}], q{{$i}}, {{- if $carryIn}}carry{{- else}}0{{- end}}) 381 {{- end}} 382 {{ end }} 383 384 385 386 // fromMont converts z in place (i.e. mutates) from Montgomery to regular representation 387 // sets and returns z = z * 1 388 func (z *{{.ElementName}}) fromMont() *{{.ElementName}} { 389 fromMont(z) 390 return z 391 } 392 393 // Add z = x + y (mod q) 394 func (z *{{.ElementName}}) Add( x, y *{{.ElementName}}) *{{.ElementName}} { 395 {{ $hasCarry := or (not $.NoCarry) (gt $.NbWords 1)}} 396 {{- if $hasCarry}} 397 var carry uint64 398 {{- end}} 399 {{- range $i := iterate 0 $.NbWords}} 400 {{- $hasCarry := or (not $.NoCarry) (lt $i $.NbWordsLastIndex)}} 401 z[{{$i}}], {{- if $hasCarry}}carry{{- else}}_{{- end}} = bits.Add64(x[{{$i}}], y[{{$i}}], {{- if eq $i 0}}0{{- else}}carry{{- end}}) 402 {{- end}} 403 404 {{- if eq $.NbWords 1}} 405 if {{- if not .NoCarry}} carry != 0 ||{{- end }} z[0] >= q { 406 z[0] -= q 407 } 408 {{- else}} 409 {{- if not .NoCarry}} 410 // if we overflowed the last addition, z >= q 411 // if z >= q, z = z - q 412 if carry != 0 { 413 var b uint64 414 // we overflowed, so z >= q 415 {{- range $i := iterate 0 $.NbWords}} 416 {{- $hasBorrow := lt $i $.NbWordsLastIndex}} 417 z[{{$i}}], {{- if $hasBorrow}}b{{- else}}_{{- end}} = bits.Sub64(z[{{$i}}], q{{$i}}, {{- if eq $i 0}}0{{- else}}b{{- end}}) 418 {{- end}} 419 return z 420 } 421 {{- end}} 422 423 {{ template "reduce" .}} 424 {{- end}} 425 return z 426 } 427 428 // Double z = x + x (mod q), aka Lsh 1 429 func (z *{{.ElementName}}) Double( x *{{.ElementName}}) *{{.ElementName}} { 430 {{- if eq .NbWords 1}} 431 if x[0] & (1 << 63) == (1 << 63) { 432 // if highest bit is set, then we have a carry to x + x, we shift and subtract q 433 z[0] = (x[0] << 1) - q 434 } else { 435 // highest bit is not set, but x + x can still be >= q 436 z[0] = (x[0] << 1) 437 if z[0] >= q { 438 z[0] -= q 439 } 440 } 441 {{- else}} 442 {{ $hasCarry := or (not $.NoCarry) (gt $.NbWords 1)}} 443 {{- if $hasCarry}} 444 var carry uint64 445 {{- end}} 446 {{- range $i := iterate 0 $.NbWords}} 447 {{- $hasCarry := or (not $.NoCarry) (lt $i $.NbWordsLastIndex)}} 448 z[{{$i}}], {{- if $hasCarry}}carry{{- else}}_{{- end}} = bits.Add64(x[{{$i}}], x[{{$i}}], {{- if eq $i 0}}0{{- else}}carry{{- end}}) 449 {{- end}} 450 {{- if not .NoCarry}} 451 // if we overflowed the last addition, z >= q 452 // if z >= q, z = z - q 453 if carry != 0 { 454 var b uint64 455 // we overflowed, so z >= q 456 {{- range $i := iterate 0 $.NbWords}} 457 {{- $hasBorrow := lt $i $.NbWordsLastIndex}} 458 z[{{$i}}], {{- if $hasBorrow}}b{{- else}}_{{- end}} = bits.Sub64(z[{{$i}}], q{{$i}}, {{- if eq $i 0}}0{{- else}}b{{- end}}) 459 {{- end}} 460 return z 461 } 462 {{- end}} 463 464 {{ template "reduce" .}} 465 {{- end}} 466 return z 467 } 468 469 470 // Sub z = x - y (mod q) 471 func (z *{{.ElementName}}) Sub( x, y *{{.ElementName}}) *{{.ElementName}} { 472 var b uint64 473 z[0], b = bits.Sub64(x[0], y[0], 0) 474 {{- range $i := .NbWordsIndexesNoZero}} 475 z[{{$i}}], b = bits.Sub64(x[{{$i}}], y[{{$i}}], b) 476 {{- end}} 477 if b != 0 { 478 {{- if eq .NbWords 1}} 479 z[0] += q 480 {{- else}} 481 var c uint64 482 z[0], c = bits.Add64(z[0], q0, 0) 483 {{- range $i := .NbWordsIndexesNoZero}} 484 {{- if eq $i $.NbWordsLastIndex}} 485 z[{{$i}}], _ = bits.Add64(z[{{$i}}], q{{$i}}, c) 486 {{- else}} 487 z[{{$i}}], c = bits.Add64(z[{{$i}}], q{{$i}}, c) 488 {{- end}} 489 {{- end}} 490 {{- end}} 491 } 492 return z 493 } 494 495 // Neg z = q - x 496 func (z *{{.ElementName}}) Neg( x *{{.ElementName}}) *{{.ElementName}} { 497 if x.IsZero() { 498 z.SetZero() 499 return z 500 } 501 {{- if eq .NbWords 1}} 502 z[0] = q - x[0] 503 {{- else}} 504 var borrow uint64 505 z[0], borrow = bits.Sub64(q0, x[0], 0) 506 {{- range $i := .NbWordsIndexesNoZero}} 507 {{- if eq $i $.NbWordsLastIndex}} 508 z[{{$i}}], _ = bits.Sub64(q{{$i}}, x[{{$i}}], borrow) 509 {{- else}} 510 z[{{$i}}], borrow = bits.Sub64(q{{$i}}, x[{{$i}}], borrow) 511 {{- end}} 512 {{- end}} 513 {{- end}} 514 return z 515 } 516 517 // Select is a constant-time conditional move. 518 // If c=0, z = x0. Else z = x1 519 func (z *{{.ElementName}}) Select(c int, x0 *{{.ElementName}}, x1 *{{.ElementName}}) *{{.ElementName}} { 520 cC := uint64( (int64(c) | -int64(c)) >> 63 ) // "canonicized" into: 0 if c=0, -1 otherwise 521 {{- range $i := .NbWordsIndexesFull }} 522 z[{{$i}}] = x0[{{$i}}] ^ cC & (x0[{{$i}}] ^ x1[{{$i}}]) 523 {{- end}} 524 return z 525 } 526 527 // _mulGeneric is unoptimized textbook CIOS 528 // it is a fallback solution on x86 when ADX instruction set is not available 529 // and is used for testing purposes. 530 func _mulGeneric(z,x,y *{{.ElementName}}) { 531 {{ mul_doc false }} 532 {{ template "mul_cios" dict "all" . "V1" "x" "V2" "y"}} 533 {{ template "reduce" . }} 534 } 535 536 537 func _fromMontGeneric(z *{{.ElementName}}) { 538 // the following lines implement z = z * 1 539 // with a modified CIOS montgomery multiplication 540 // see Mul for algorithm documentation 541 {{- range $j := .NbWordsIndexesFull}} 542 { 543 // m = z[0]n'[0] mod W 544 m := z[0] * qInvNeg 545 C := madd0(m, q0, z[0]) 546 {{- range $i := $.NbWordsIndexesNoZero}} 547 C, z[{{sub $i 1}}] = madd2(m, q{{$i}}, z[{{$i}}], C) 548 {{- end}} 549 z[{{sub $.NbWords 1}}] = C 550 } 551 {{- end}} 552 553 {{ template "reduce" .}} 554 } 555 556 func _reduceGeneric(z *{{.ElementName}}) { 557 {{ template "reduce" . }} 558 } 559 560 // BatchInvert returns a new slice with every element inverted. 561 // Uses Montgomery batch inversion trick 562 func BatchInvert(a []{{.ElementName}}) []{{.ElementName}} { 563 res := make([]{{.ElementName}}, len(a)) 564 if len(a) == 0 { 565 return res 566 } 567 568 zeroes := bitset.New(uint(len(a))) 569 accumulator := One() 570 571 for i:=0; i < len(a); i++ { 572 if a[i].IsZero() { 573 zeroes.Set(uint(i)) 574 continue 575 } 576 res[i] = accumulator 577 accumulator.Mul(&accumulator, &a[i]) 578 } 579 580 accumulator.Inverse(&accumulator) 581 582 for i := len(a) - 1; i >= 0; i-- { 583 if zeroes.Test(uint(i)) { 584 continue 585 } 586 res[i].Mul(&res[i], &accumulator) 587 accumulator.Mul(&accumulator, &a[i]) 588 } 589 590 return res 591 } 592 593 func _butterflyGeneric(a, b *{{.ElementName}}) { 594 t := *a 595 a.Add(a, b) 596 b.Sub(&t, b) 597 } 598 599 // BitLen returns the minimum number of bits needed to represent z 600 // returns 0 if z == 0 601 func (z *{{.ElementName}}) BitLen() int { 602 {{- range $i := reverse .NbWordsIndexesNoZero}} 603 if z[{{$i}}] != 0 { 604 return {{mul $i 64}} + bits.Len64(z[{{$i}}]) 605 } 606 {{- end}} 607 return bits.Len64(z[0]) 608 } 609 610 // Hash msg to count prime field elements. 611 // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 612 func Hash(msg, dst []byte, count int) ([]{{.ElementName}}, error) { 613 // 128 bits of security 614 // L = ceil((ceil(log2(p)) + k) / 8), where k is the security parameter = 128 615 const Bytes = 1 + (Bits-1)/8 616 const L = 16 + Bytes 617 618 lenInBytes := count * L 619 pseudoRandomBytes, err := hash.ExpandMsgXmd(msg, dst, lenInBytes) 620 if err != nil { 621 return nil, err 622 } 623 624 // get temporary big int from the pool 625 vv := pool.BigInt.Get() 626 627 res := make([]{{.ElementName}}, count) 628 for i := 0; i < count; i++ { 629 vv.SetBytes(pseudoRandomBytes[i*L : (i+1)*L]) 630 res[i].SetBigInt(vv) 631 } 632 633 // release object into pool 634 pool.BigInt.Put(vv) 635 636 return res, nil 637 } 638 639 640 {{ define "rsh V nbWords" }} 641 // {{$.V}} = {{$.V}} >> 1 642 {{- $lastIndex := sub .nbWords 1}} 643 {{- range $i := iterate 0 $lastIndex}} 644 {{$.V}}[{{$i}}] = {{$.V}}[{{$i}}] >> 1 | {{$.V}}[{{(add $i 1)}}] << 63 645 {{- end}} 646 {{$.V}}[{{$lastIndex}}] >>= 1 647 {{ end }} 648 649 650 651 `