forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
multisig: add type and bitarray (cosmos#6241)
* add multisig and bitarray * remove random message * add changelog entry * bring back the concrete type * move changelog entry * move bitarray to crypto/types * fix build * remove commented code Co-authored-by: Aaron Craelius <[email protected]>
- Loading branch information
1 parent
bf8809e
commit a201967
Showing
11 changed files
with
942 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
package types | ||
|
||
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. | ||
|
||
// 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: uint32(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 == uint32(0) { | ||
return len(bA.Elems) * 8 | ||
} | ||
|
||
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)<<uint8(7-(i%8))) > 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 | ||
} | ||
|
||
// NumTrueBitsBefore returns the number of bits set to true before the | ||
// given index. e.g. if bA = _XX__XX, NumOfTrueBitsBefore(4) = 2, since | ||
// there are two bits set to true before index 4. | ||
func (bA *CompactBitArray) NumTrueBitsBefore(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{<bit-string>}, | ||
// where <bit-string> is a sequence of 'x' (1) and '_' (0). | ||
// The <bit-string> 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 | ||
} | ||
|
||
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{uint32(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]...) | ||
} |
Oops, something went wrong.