forked from crypto-org-chain/chainlibpy
-
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.
add multi sign support(fix crypto-org-chain#13)
- Loading branch information
1 parent
c4f7c12
commit 4b36eca
Showing
12 changed files
with
727 additions
and
11 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import base64 | ||
import hashlib | ||
import json | ||
from typing import List | ||
|
||
import ecdsa | ||
|
||
from chainlibpy.amino import StdFee, StdSignDoc, SyncMode | ||
from chainlibpy.amino.message import Msg | ||
from chainlibpy.amino.tx import Pubkey, Signature, StdTx | ||
from chainlibpy.wallet import Wallet | ||
|
||
|
||
class Transaction: | ||
"""A Cosmos transaction. | ||
After initialization, one or more token transfers can be added by | ||
calling the `add_transfer()` method. Finally, call `get_pushable()` | ||
to get a signed transaction that can be pushed to the `POST /txs` | ||
endpoint of the Cosmos REST API. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
wallet: Wallet, | ||
account_num: int, | ||
sequence: int, | ||
fee: StdFee = StdFee.default(), | ||
memo: str = "", | ||
chain_id: str = "crypto-org-chain-mainnet-1", | ||
sync_mode: SyncMode = "sync", | ||
timeout_height: int = 0, | ||
multi_sign_address: str = None, | ||
) -> None: | ||
self._multi_sign_address = multi_sign_address | ||
self._wallet = wallet | ||
self._account_num = str(account_num) | ||
self._sequence = str(sequence) | ||
self._fee = fee | ||
self._memo = memo | ||
self._chain_id = chain_id | ||
self._sync_mode = sync_mode | ||
self._msgs: List[Msg] = [] | ||
self._timeout_height = str(timeout_height) | ||
|
||
def add_msg(self, msg: Msg): | ||
self._msgs.append(msg) | ||
|
||
def get_pushable(self) -> dict: | ||
"""get the request post to the /txs.""" | ||
std_tx = StdTx(self._msgs, self._fee, self._memo, self._timeout_height, [self.signature]) | ||
|
||
pushable_tx = { | ||
"tx": std_tx.to_dict(), | ||
"mode": self._sync_mode, | ||
} | ||
return pushable_tx | ||
|
||
@property | ||
def signature(self) -> Signature: | ||
pubkey = self._wallet.public_key | ||
base64_pubkey = base64.b64encode(pubkey).decode("utf-8") | ||
pubkey = Pubkey(value=base64_pubkey) | ||
raw_signature = self.sign() | ||
sig_str = base64.b64encode(raw_signature).decode("utf-8") | ||
signature = Signature(sig_str, pubkey, self._account_num, self._sequence) | ||
return signature | ||
|
||
def sign(self) -> bytes: | ||
sign_doc = self._get_sign_doc() | ||
message_str = json.dumps( | ||
sign_doc.to_dict(), separators=(",", ":"), sort_keys=True | ||
) | ||
message_bytes = message_str.encode("utf-8") | ||
|
||
privkey = ecdsa.SigningKey.from_string( | ||
self._wallet.private_key, curve=ecdsa.SECP256k1 | ||
) | ||
signature_compact = privkey.sign_deterministic( | ||
message_bytes, | ||
hashfunc=hashlib.sha256, | ||
sigencode=ecdsa.util.sigencode_string_canonize, | ||
) | ||
return signature_compact | ||
|
||
def _get_sign_doc(self) -> StdSignDoc: | ||
sign_doc = StdSignDoc( | ||
account_number=self._account_num, | ||
sequence=self._sequence, | ||
chain_id=self._chain_id, | ||
memo=self._memo, | ||
fee=self._fee.to_dict(), | ||
msgs=self._msgs, | ||
timeout_height=self._timeout_height, | ||
) | ||
return sign_doc |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,4 @@ | ||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! | ||
"""Client and server classes corresponding to protobuf-defined services.""" | ||
import grpc | ||
|
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
Empty file.
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,74 @@ | ||
import base64 | ||
|
||
from chainlibpy.amino.basic import BasicObj | ||
from chainlibpy.generated.cosmos.crypto.multisig.v1beta1.multisig_pb2 import ( | ||
CompactBitArray as ProtoCompactBitArray, | ||
) | ||
from chainlibpy.multisign.bits import Bits | ||
|
||
|
||
class CompactBitArray(BasicObj): | ||
MaxInt32 = 2147483647 | ||
|
||
def __init__(self, bits: int): | ||
self.extra_bits_stored = 0 | ||
self.elems = bytearray() | ||
if bits <= 0: | ||
raise Exception(f"invalid bits {bits}") | ||
n_elems = (bits + 7) // 8 | ||
if n_elems <= 0 or n_elems > self.MaxInt32: | ||
raise Exception(f"invalid bits {bits}") | ||
self.extra_bits_stored = bits % 8 | ||
self.elems = bytearray([0] * n_elems) | ||
|
||
def __repr__(self): | ||
elems = base64.b64encode(self.elems).decode('utf-8') | ||
return f"extra_bits_stored:{self.extra_bits_stored}, elems:{elems}" | ||
|
||
def bit_array(self) -> ProtoCompactBitArray: | ||
return ProtoCompactBitArray( | ||
extra_bits_stored=self.extra_bits_stored, | ||
elems=bytes(self.elems) | ||
) | ||
|
||
def count(self) -> int: | ||
"""returns the number of bits in the bitarray.""" | ||
if self.extra_bits_stored == 0: | ||
return len(self.elems) * 8 | ||
return (len(self.elems) - 1) * 8 + int(self.extra_bits_stored) | ||
|
||
def get_index(self, index: int) -> bool: | ||
"""returns the bit at index i within the bit array. | ||
The behavior is undefined if i >= self.count() | ||
""" | ||
if index < 0 or index >= self.count(): | ||
return False | ||
return (self.elems[index >> 3] & (1 << (7 - (index % 8)))) > 0 | ||
|
||
def set_index(self, i: int, v: bool) -> bool: | ||
"""set_index sets the bit at index i within the bit array. | ||
Returns true if and only if the operation succeeded. The | ||
behavior is undefined if i >= self.count() | ||
""" | ||
if i < 0 or i >= self.count(): | ||
return False | ||
|
||
if v: | ||
self.elems[i >> 3] |= (1 << (7 - (i % 8))) | ||
else: | ||
self.elems[i >> 3] &= ~(1 << (7 - (i % 8))) | ||
return True | ||
|
||
def num_true_bits_before(self, index: int) -> int: | ||
ones_count = 0 | ||
max_ = self.count() | ||
index = min(index, max_) | ||
elem = 0 | ||
while True: | ||
if elem*8+7 >= index: | ||
ones_count += Bits.ones_count8(self.elems[elem]) >> (7 - (index % 8) + 1) | ||
return ones_count | ||
ones_count += Bits.ones_count8(self.elems[elem]) | ||
elem += 1 |
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,27 @@ | ||
class Bits(object): | ||
pop8tab = [ | ||
0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, | ||
0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, | ||
0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, | ||
0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, | ||
0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, | ||
0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, | ||
0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, | ||
0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 0x05, 0x06, 0x06, 0x07, 0x06, 0x07, 0x07, 0x08 | ||
] | ||
|
||
@classmethod | ||
def ones_count8(cls, x: int) -> int: | ||
"""returns the number of one bits ("population count") in x. | ||
from: go/match/bits/bits.go | ||
""" | ||
return int(cls.pop8tab[x]) |
Oops, something went wrong.