Skip to content

Commit

Permalink
multisig: add type and bitarray (#6241)
Browse files Browse the repository at this point in the history
* 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 <aaron@regen.network>
  • Loading branch information
tac0turtle and aaronc committed May 20, 2020
1 parent bf8809e commit a201967
Show file tree
Hide file tree
Showing 11 changed files with 942 additions and 120 deletions.
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

0 comments on commit a201967

Please sign in to comment.