package multisig import ( "bytes" "encoding/binary" "errors" "fmt" "regexp" "strings" ) // CompactBitArray is an implementation of a space efficient bit array. // This is used to ensure that the encoded data takes up a minimal amount of // space after amino encoding. // This is not thread safe, and is not intended for concurrent usage. type CompactBitArray struct { ExtraBitsStored byte `json:"extra_bits"` // The number of extra bits in elems. Elems []byte `json:"bits"` } // NewCompactBitArray returns a new compact bit array. // It returns nil if the number of bits is zero. func NewCompactBitArray(bits int) *CompactBitArray { if bits <= 0 { return nil } return &CompactBitArray{ ExtraBitsStored: byte(bits % 8), Elems: make([]byte, (bits+7)/8), } } // Size returns the number of bits in the bitarray func (bA *CompactBitArray) Size() int { if bA == nil { return 0 } else if bA.ExtraBitsStored == byte(0) { return len(bA.Elems) * 8 } // num_bits = 8*num_full_bytes + overflow_in_last_byte // num_full_bytes = (len(bA.Elems)-1) return (len(bA.Elems)-1)*8 + int(bA.ExtraBitsStored) } // GetIndex returns the bit at index i within the bit array. // The behavior is undefined if i >= bA.Size() func (bA *CompactBitArray) GetIndex(i int) bool { if bA == nil { return false } if i >= bA.Size() { return false } return bA.Elems[i>>3]&(uint8(1)< 0 } // SetIndex sets the bit at index i within the bit array. // The behavior is undefined if i >= bA.Size() func (bA *CompactBitArray) SetIndex(i int, v bool) bool { if bA == nil { return false } if i >= bA.Size() { return false } if v { bA.Elems[i>>3] |= (uint8(1) << uint8(7-(i%8))) } else { bA.Elems[i>>3] &= ^(uint8(1) << uint8(7-(i%8))) } return true } // NumOfTrueBitsBefore returns the location of the given index, among the // values in the bit array that are set to true. // e.g. if bA = _XX_X_X, NumOfTrueBitsBefore(4) = 2, since // the value at index 4 of the bit array is the third // value that is true. (And it is 0-indexed) func (bA *CompactBitArray) NumOfTrueBitsBefore(index int) int { numTrueValues := 0 for i := 0; i < index; i++ { if bA.GetIndex(i) { numTrueValues++ } } return numTrueValues } // Copy returns a copy of the provided bit array. func (bA *CompactBitArray) Copy() *CompactBitArray { if bA == nil { return nil } c := make([]byte, len(bA.Elems)) copy(c, bA.Elems) return &CompactBitArray{ ExtraBitsStored: bA.ExtraBitsStored, Elems: c, } } // String returns a string representation of CompactBitArray: BA{}, // where is a sequence of 'x' (1) and '_' (0). // The includes spaces and newlines to help people. // For a simple sequence of 'x' and '_' characters with no spaces or newlines, // see the MarshalJSON() method. // Example: "BA{_x_}" or "nil-BitArray" for nil. func (bA *CompactBitArray) String() string { return bA.StringIndented("") } // StringIndented returns the same thing as String(), but applies the indent // at every 10th bit, and twice at every 50th bit. func (bA *CompactBitArray) StringIndented(indent string) string { if bA == nil { return "nil-BitArray" } lines := []string{} bits := "" size := bA.Size() for i := 0; i < size; i++ { if bA.GetIndex(i) { bits += "x" } else { bits += "_" } if i%100 == 99 { lines = append(lines, bits) bits = "" } if i%10 == 9 { bits += indent } if i%50 == 49 { bits += indent } } if len(bits) > 0 { lines = append(lines, bits) } return fmt.Sprintf("BA{%v:%v}", size, strings.Join(lines, indent)) } // MarshalJSON implements json.Marshaler interface by marshaling bit array // using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit. func (bA *CompactBitArray) MarshalJSON() ([]byte, error) { if bA == nil { return []byte("null"), nil } bits := `"` size := bA.Size() for i := 0; i < size; i++ { if bA.GetIndex(i) { bits += `x` } else { bits += `_` } } bits += `"` return []byte(bits), nil } var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`) // UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom // JSON description. func (bA *CompactBitArray) UnmarshalJSON(bz []byte) error { b := string(bz) if b == "null" { // This is required e.g. for encoding/json when decoding // into a pointer with pre-allocated BitArray. bA.ExtraBitsStored = 0 bA.Elems = nil return nil } // Validate 'b'. match := bitArrayJSONRegexp.FindStringSubmatch(b) if match == nil { return fmt.Errorf("BitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b) } bits := match[1] // Construct new CompactBitArray and copy over. numBits := len(bits) bA2 := NewCompactBitArray(numBits) for i := 0; i < numBits; i++ { if bits[i] == 'x' { bA2.SetIndex(i, true) } } *bA = *bA2 return nil } // CompactMarshal is a space efficient encoding for CompactBitArray. // It is not amino compatible. func (bA *CompactBitArray) CompactMarshal() []byte { size := bA.Size() if size <= 0 { return []byte("null") } bz := make([]byte, 0, size/8) // length prefix number of bits, not number of bytes. This difference // takes 3-4 bits in encoding, as opposed to instead encoding the number of // bytes (saving 3-4 bits) and including the offset as a full byte. bz = appendUvarint(bz, uint64(size)) bz = append(bz, bA.Elems...) return bz } // CompactUnmarshal is a space efficient decoding for CompactBitArray. // It is not amino compatible. func CompactUnmarshal(bz []byte) (*CompactBitArray, error) { if len(bz) < 2 { return nil, errors.New("compact bit array: invalid compact unmarshal size") } else if bytes.Equal(bz, []byte("null")) { return NewCompactBitArray(0), nil } size, n := binary.Uvarint(bz) bz = bz[n:] if len(bz) != int(size+7)/8 { return nil, errors.New("compact bit array: invalid compact unmarshal size") } bA := &CompactBitArray{byte(int(size % 8)), bz} return bA, nil } func appendUvarint(b []byte, x uint64) []byte { var a [binary.MaxVarintLen64]byte n := binary.PutUvarint(a[:], x) return append(b, a[:n]...) }