You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
6.1 KiB

  1. package multisig
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "regexp"
  8. "strings"
  9. )
  10. // CompactBitArray is an implementation of a space efficient bit array.
  11. // This is used to ensure that the encoded data takes up a minimal amount of
  12. // space after amino encoding.
  13. // This is not thread safe, and is not intended for concurrent usage.
  14. type CompactBitArray struct {
  15. ExtraBitsStored byte `json:"extra_bits"` // The number of extra bits in elems.
  16. Elems []byte `json:"bits"`
  17. }
  18. // NewCompactBitArray returns a new compact bit array.
  19. // It returns nil if the number of bits is zero.
  20. func NewCompactBitArray(bits int) *CompactBitArray {
  21. if bits <= 0 {
  22. return nil
  23. }
  24. return &CompactBitArray{
  25. ExtraBitsStored: byte(bits % 8),
  26. Elems: make([]byte, (bits+7)/8),
  27. }
  28. }
  29. // Size returns the number of bits in the bitarray
  30. func (bA *CompactBitArray) Size() int {
  31. if bA == nil {
  32. return 0
  33. } else if bA.ExtraBitsStored == byte(0) {
  34. return len(bA.Elems) * 8
  35. }
  36. // num_bits = 8*num_full_bytes + overflow_in_last_byte
  37. // num_full_bytes = (len(bA.Elems)-1)
  38. return (len(bA.Elems)-1)*8 + int(bA.ExtraBitsStored)
  39. }
  40. // GetIndex returns the bit at index i within the bit array.
  41. // The behavior is undefined if i >= bA.Size()
  42. func (bA *CompactBitArray) GetIndex(i int) bool {
  43. if bA == nil {
  44. return false
  45. }
  46. if i >= bA.Size() {
  47. return false
  48. }
  49. return bA.Elems[i>>3]&(uint8(1)<<uint8(7-(i%8))) > 0
  50. }
  51. // SetIndex sets the bit at index i within the bit array.
  52. // The behavior is undefined if i >= bA.Size()
  53. func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
  54. if bA == nil {
  55. return false
  56. }
  57. if i >= bA.Size() {
  58. return false
  59. }
  60. if v {
  61. bA.Elems[i>>3] |= (uint8(1) << uint8(7-(i%8)))
  62. } else {
  63. bA.Elems[i>>3] &= ^(uint8(1) << uint8(7-(i%8)))
  64. }
  65. return true
  66. }
  67. // NumOfTrueBitsBefore returns the location of the given index, among the
  68. // values in the bit array that are set to true.
  69. // e.g. if bA = _XX_X_X, NumOfTrueBitsBefore(4) = 2, since
  70. // the value at index 4 of the bit array is the third
  71. // value that is true. (And it is 0-indexed)
  72. func (bA *CompactBitArray) NumOfTrueBitsBefore(index int) int {
  73. numTrueValues := 0
  74. for i := 0; i < index; i++ {
  75. if bA.GetIndex(i) {
  76. numTrueValues++
  77. }
  78. }
  79. return numTrueValues
  80. }
  81. // Copy returns a copy of the provided bit array.
  82. func (bA *CompactBitArray) Copy() *CompactBitArray {
  83. if bA == nil {
  84. return nil
  85. }
  86. c := make([]byte, len(bA.Elems))
  87. copy(c, bA.Elems)
  88. return &CompactBitArray{
  89. ExtraBitsStored: bA.ExtraBitsStored,
  90. Elems: c,
  91. }
  92. }
  93. // String returns a string representation of CompactBitArray: BA{<bit-string>},
  94. // where <bit-string> is a sequence of 'x' (1) and '_' (0).
  95. // The <bit-string> includes spaces and newlines to help people.
  96. // For a simple sequence of 'x' and '_' characters with no spaces or newlines,
  97. // see the MarshalJSON() method.
  98. // Example: "BA{_x_}" or "nil-BitArray" for nil.
  99. func (bA *CompactBitArray) String() string {
  100. return bA.StringIndented("")
  101. }
  102. // StringIndented returns the same thing as String(), but applies the indent
  103. // at every 10th bit, and twice at every 50th bit.
  104. func (bA *CompactBitArray) StringIndented(indent string) string {
  105. if bA == nil {
  106. return "nil-BitArray"
  107. }
  108. lines := []string{}
  109. bits := ""
  110. size := bA.Size()
  111. for i := 0; i < size; i++ {
  112. if bA.GetIndex(i) {
  113. bits += "x"
  114. } else {
  115. bits += "_"
  116. }
  117. if i%100 == 99 {
  118. lines = append(lines, bits)
  119. bits = ""
  120. }
  121. if i%10 == 9 {
  122. bits += indent
  123. }
  124. if i%50 == 49 {
  125. bits += indent
  126. }
  127. }
  128. if len(bits) > 0 {
  129. lines = append(lines, bits)
  130. }
  131. return fmt.Sprintf("BA{%v:%v}", size, strings.Join(lines, indent))
  132. }
  133. // MarshalJSON implements json.Marshaler interface by marshaling bit array
  134. // using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit.
  135. func (bA *CompactBitArray) MarshalJSON() ([]byte, error) {
  136. if bA == nil {
  137. return []byte("null"), nil
  138. }
  139. bits := `"`
  140. size := bA.Size()
  141. for i := 0; i < size; i++ {
  142. if bA.GetIndex(i) {
  143. bits += `x`
  144. } else {
  145. bits += `_`
  146. }
  147. }
  148. bits += `"`
  149. return []byte(bits), nil
  150. }
  151. var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`)
  152. // UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom
  153. // JSON description.
  154. func (bA *CompactBitArray) UnmarshalJSON(bz []byte) error {
  155. b := string(bz)
  156. if b == "null" {
  157. // This is required e.g. for encoding/json when decoding
  158. // into a pointer with pre-allocated BitArray.
  159. bA.ExtraBitsStored = 0
  160. bA.Elems = nil
  161. return nil
  162. }
  163. // Validate 'b'.
  164. match := bitArrayJSONRegexp.FindStringSubmatch(b)
  165. if match == nil {
  166. return fmt.Errorf("BitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b)
  167. }
  168. bits := match[1]
  169. // Construct new CompactBitArray and copy over.
  170. numBits := len(bits)
  171. bA2 := NewCompactBitArray(numBits)
  172. for i := 0; i < numBits; i++ {
  173. if bits[i] == 'x' {
  174. bA2.SetIndex(i, true)
  175. }
  176. }
  177. *bA = *bA2
  178. return nil
  179. }
  180. // CompactMarshal is a space efficient encoding for CompactBitArray.
  181. // It is not amino compatible.
  182. func (bA *CompactBitArray) CompactMarshal() []byte {
  183. size := bA.Size()
  184. if size <= 0 {
  185. return []byte("null")
  186. }
  187. bz := make([]byte, 0, size/8)
  188. // length prefix number of bits, not number of bytes. This difference
  189. // takes 3-4 bits in encoding, as opposed to instead encoding the number of
  190. // bytes (saving 3-4 bits) and including the offset as a full byte.
  191. bz = appendUvarint(bz, uint64(size))
  192. bz = append(bz, bA.Elems...)
  193. return bz
  194. }
  195. // CompactUnmarshal is a space efficient decoding for CompactBitArray.
  196. // It is not amino compatible.
  197. func CompactUnmarshal(bz []byte) (*CompactBitArray, error) {
  198. if len(bz) < 2 {
  199. return nil, errors.New("compact bit array: invalid compact unmarshal size")
  200. } else if bytes.Equal(bz, []byte("null")) {
  201. return NewCompactBitArray(0), nil
  202. }
  203. size, n := binary.Uvarint(bz)
  204. bz = bz[n:]
  205. if len(bz) != int(size+7)/8 {
  206. return nil, errors.New("compact bit array: invalid compact unmarshal size")
  207. }
  208. bA := &CompactBitArray{byte(int(size % 8)), bz}
  209. return bA, nil
  210. }
  211. func appendUvarint(b []byte, x uint64) []byte {
  212. var a [binary.MaxVarintLen64]byte
  213. n := binary.PutUvarint(a[:], x)
  214. return append(b, a[:n]...)
  215. }