Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multisig: add type and bitarray #6241

Merged
merged 10 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa

### Features

* (crypto/multisig) [\#6241](https://github.com/cosmos/cosmos-sdk/pull/6241) Add Multisig type directly to the repo. Previously this was in tendermint.
* (rest) [\#6167](https://github.com/cosmos/cosmos-sdk/pull/6167) Support `max-body-bytes` CLI flag for the REST service.
* (x/ibc) [\#5588](https://github.com/cosmos/cosmos-sdk/pull/5588) Add [ICS 024 - Host State Machine Requirements](https://github.com/cosmos/ics/tree/master/spec/ics-024-host-requirements) subpackage to `x/ibc` module.
* (x/ibc) [\#5277](https://github.com/cosmos/cosmos-sdk/pull/5277) `x/ibc` changes from IBC alpha. For more details check the the [`x/ibc/spec`](https://github.com/cosmos/tree/master/x/ibc/spec) directory:
Expand Down
248 changes: 248 additions & 0 deletions crypto/types/compact_bit_array.go
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]...)
}
Loading