block512.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package hashx provides a concrete implementation of [hash.Hash]
  4. // that operates on a particular block size.
  5. package hashx
  6. import (
  7. "encoding/binary"
  8. "fmt"
  9. "hash"
  10. "unsafe"
  11. )
  12. var _ hash.Hash = (*Block512)(nil)
  13. // Block512 wraps a [hash.Hash] for functions that operate on 512-bit block sizes.
  14. // It has efficient methods for hashing fixed-width integers.
  15. //
  16. // A hashing algorithm that operates on 512-bit block sizes should be used.
  17. // The hash still operates correctly even with misaligned block sizes,
  18. // but operates less efficiently.
  19. //
  20. // Example algorithms with 512-bit block sizes include:
  21. // - MD4 (https://golang.org/x/crypto/md4)
  22. // - MD5 (https://golang.org/pkg/crypto/md5)
  23. // - BLAKE2s (https://golang.org/x/crypto/blake2s)
  24. // - BLAKE3
  25. // - RIPEMD (https://golang.org/x/crypto/ripemd160)
  26. // - SHA-0
  27. // - SHA-1 (https://golang.org/pkg/crypto/sha1)
  28. // - SHA-2 (https://golang.org/pkg/crypto/sha256)
  29. // - Whirlpool
  30. //
  31. // See https://en.wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions#Parameters
  32. // for a list of hash functions and their block sizes.
  33. //
  34. // Block512 assumes that [hash.Hash.Write] never fails and
  35. // never allows the provided buffer to escape.
  36. type Block512 struct {
  37. hash.Hash
  38. x [512 / 8]byte
  39. nx int
  40. }
  41. // New512 constructs a new Block512 that wraps h.
  42. //
  43. // It reports an error if the block sizes do not match.
  44. // Misaligned block sizes perform poorly, but execute correctly.
  45. // The error may be ignored if performance is not a concern.
  46. func New512(h hash.Hash) (*Block512, error) {
  47. b := &Block512{Hash: h}
  48. if len(b.x)%h.BlockSize() != 0 {
  49. return b, fmt.Errorf("hashx.Block512: inefficient use of hash.Hash with %d-bit block size", 8*h.BlockSize())
  50. }
  51. return b, nil
  52. }
  53. // Write hashes the contents of b.
  54. func (h *Block512) Write(b []byte) (int, error) {
  55. h.HashBytes(b)
  56. return len(b), nil
  57. }
  58. // Sum appends the current hash to b and returns the resulting slice.
  59. //
  60. // It flushes any partially completed blocks to the underlying [hash.Hash],
  61. // which may cause future operations to be misaligned and less efficient
  62. // until [Block512.Reset] is called.
  63. func (h *Block512) Sum(b []byte) []byte {
  64. if h.nx > 0 {
  65. h.Hash.Write(h.x[:h.nx])
  66. h.nx = 0
  67. }
  68. // Unfortunately hash.Hash.Sum always causes the input to escape since
  69. // escape analysis cannot prove anything past an interface method call.
  70. // Assuming h already escapes, we call Sum with h.x first,
  71. // and then copy the result to b.
  72. sum := h.Hash.Sum(h.x[:0])
  73. return append(b, sum...)
  74. }
  75. // Reset resets Block512 to its initial state.
  76. // It recursively resets the underlying [hash.Hash].
  77. func (h *Block512) Reset() {
  78. h.Hash.Reset()
  79. h.nx = 0
  80. }
  81. // HashUint8 hashes n as a 1-byte integer.
  82. func (h *Block512) HashUint8(n uint8) {
  83. // NOTE: This method is carefully written to be inlineable.
  84. if h.nx <= len(h.x)-1 {
  85. h.x[h.nx] = n
  86. h.nx += 1
  87. } else {
  88. h.hashUint8Slow(n) // mark "noinline" to keep this within inline budget
  89. }
  90. }
  91. //go:noinline
  92. func (h *Block512) hashUint8Slow(n uint8) { h.hashUint(uint64(n), 1) }
  93. // HashUint16 hashes n as a 2-byte little-endian integer.
  94. func (h *Block512) HashUint16(n uint16) {
  95. // NOTE: This method is carefully written to be inlineable.
  96. if h.nx <= len(h.x)-2 {
  97. binary.LittleEndian.PutUint16(h.x[h.nx:], n)
  98. h.nx += 2
  99. } else {
  100. h.hashUint16Slow(n) // mark "noinline" to keep this within inline budget
  101. }
  102. }
  103. //go:noinline
  104. func (h *Block512) hashUint16Slow(n uint16) { h.hashUint(uint64(n), 2) }
  105. // HashUint32 hashes n as a 4-byte little-endian integer.
  106. func (h *Block512) HashUint32(n uint32) {
  107. // NOTE: This method is carefully written to be inlineable.
  108. if h.nx <= len(h.x)-4 {
  109. binary.LittleEndian.PutUint32(h.x[h.nx:], n)
  110. h.nx += 4
  111. } else {
  112. h.hashUint32Slow(n) // mark "noinline" to keep this within inline budget
  113. }
  114. }
  115. //go:noinline
  116. func (h *Block512) hashUint32Slow(n uint32) { h.hashUint(uint64(n), 4) }
  117. // HashUint64 hashes n as a 8-byte little-endian integer.
  118. func (h *Block512) HashUint64(n uint64) {
  119. // NOTE: This method is carefully written to be inlineable.
  120. if h.nx <= len(h.x)-8 {
  121. binary.LittleEndian.PutUint64(h.x[h.nx:], n)
  122. h.nx += 8
  123. } else {
  124. h.hashUint64Slow(n) // mark "noinline" to keep this within inline budget
  125. }
  126. }
  127. //go:noinline
  128. func (h *Block512) hashUint64Slow(n uint64) { h.hashUint(uint64(n), 8) }
  129. func (h *Block512) hashUint(n uint64, i int) {
  130. for ; i > 0; i-- {
  131. if h.nx == len(h.x) {
  132. h.Hash.Write(h.x[:])
  133. h.nx = 0
  134. }
  135. h.x[h.nx] = byte(n)
  136. h.nx += 1
  137. n >>= 8
  138. }
  139. }
  140. // HashBytes hashes the contents of b.
  141. // It does not explicitly hash the length separately.
  142. func (h *Block512) HashBytes(b []byte) {
  143. // Nearly identical to sha256.digest.Write.
  144. if h.nx > 0 {
  145. n := copy(h.x[h.nx:], b)
  146. h.nx += n
  147. if h.nx == len(h.x) {
  148. h.Hash.Write(h.x[:])
  149. h.nx = 0
  150. }
  151. b = b[n:]
  152. }
  153. if len(b) >= len(h.x) {
  154. n := len(b) &^ (len(h.x) - 1) // n is a multiple of len(h.x)
  155. h.Hash.Write(b[:n])
  156. b = b[n:]
  157. }
  158. if len(b) > 0 {
  159. h.nx = copy(h.x[:], b)
  160. }
  161. }
  162. // HashString hashes the contents of s.
  163. // It does not explicitly hash the length separately.
  164. func (h *Block512) HashString(s string) {
  165. // TODO: Avoid unsafe when standard hashers implement io.StringWriter.
  166. // See https://go.dev/issue/38776.
  167. type stringHeader struct {
  168. p unsafe.Pointer
  169. n int
  170. }
  171. p := (*stringHeader)(unsafe.Pointer(&s))
  172. b := unsafe.Slice((*byte)(p.p), p.n)
  173. h.HashBytes(b)
  174. }
  175. // TODO: Add Hash.MarshalBinary and Hash.UnmarshalBinary?