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

Make MultiKeyAuthenicator work #34

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to the Aptos Python SDK will be captured in this file. This

## Unreleased

## 0.9.2
- Fix MultiKeyAuthenicator serialization and deserialization with tests

## 0.9.1
- For `account_sequence_number`, return 0 if account has yet to be created to better support sponsored transactions create account if not exists
- For `account_balance`, Use `0x1::coin::balance` instead of reading the resource
Expand Down
18 changes: 8 additions & 10 deletions aptos_sdk/asymmetric_crypto_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,14 @@ def serialize(self, serializer: Serializer):

class MultiSignature(asymmetric_crypto.Signature):
signatures: List[Tuple[int, Signature]]
BITMAP_NUM_OF_BYTES: int = 4
MAX_SIGNATURES: int = 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find this 16 signature limit? Is this just for the SDK?

As far as I can tell, the limit is 255

https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L794-L798

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L811

distinct keys is 16... I think we kinda messed up here, but I think we can make this bigger without any impact since we use the bitvec.


def __init__(self, signatures: List[Tuple[int, asymmetric_crypto.Signature]]):
# Sort first to ensure no issues in order
# signatures.sort(key=lambda x: x[0])
self.signatures = []
for index, signature in signatures:
assert (
index < self.BITMAP_NUM_OF_BYTES * 8
), "bitmap value exceeds maximum value"
assert index < self.MAX_SIGNATURES, "bitmap value exceeds maximum value"
if isinstance(signature, Signature):
self.signatures.append((index, signature))
else:
Expand All @@ -189,9 +187,9 @@ def __str__(self) -> str:
@staticmethod
def deserialize(deserializer: Deserializer) -> MultiSignature:
signatures = deserializer.sequence(Signature.deserialize)
deserializer.uleb128()
bitmap = deserializer.u32()
num_bits = MultiSignature.BITMAP_NUM_OF_BYTES * 8
bitmap_raw = deserializer.to_bytes()
bitmap = int.from_bytes(bitmap_raw, "little")
num_bits = len(bitmap_raw) * 8
sig_index = 0
indexed_signatures = []

Expand All @@ -201,7 +199,7 @@ def deserialize(deserializer: Deserializer) -> MultiSignature:
indexed_signatures.append((i, signatures[sig_index]))
sig_index += 1

return MultiSignature(signatures)
return MultiSignature(indexed_signatures)

def serialize(self, serializer: Serializer):
actual_sigs = []
Expand All @@ -212,8 +210,8 @@ def serialize(self, serializer: Serializer):
actual_sigs.append(signature)

serializer.sequence(actual_sigs, Serializer.struct)
serializer.uleb128(self.BITMAP_NUM_OF_BYTES)
serializer.u32(bitmap)
count = 1 if bitmap < 256 else 2
serializer.to_bytes(bitmap.to_bytes(count, "little"))
Comment on lines +213 to +214
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature limit accepted on the other side only goes up to 255? https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L794-L798

Is there a reason it's doing custom byte sequence serialization? I noticed this is really not simple to do in the Python SDK

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how the bitvec works...
length + data



def index_to_bitmap_value(i: int) -> int:
Expand Down
41 changes: 40 additions & 1 deletion aptos_sdk/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from __future__ import annotations

import typing
import unittest
from typing import List

from . import asymmetric_crypto, asymmetric_crypto_wrapper, ed25519
from . import asymmetric_crypto, asymmetric_crypto_wrapper, ed25519, secp256k1_ecdsa
from .account_address import AccountAddress
from .bcs import Deserializer, Serializer

Expand Down Expand Up @@ -136,6 +137,8 @@ def deserialize(deserializer: Deserializer) -> AccountAuthenticator:
authenticator = MultiEd25519Authenticator.deserialize(deserializer)
elif variant == AccountAuthenticator.SINGLE_KEY:
authenticator = SingleKeyAuthenticator.deserialize(deserializer)
elif variant == AccountAuthenticator.MULTI_KEY:
authenticator = MultiKeyAuthenticator.deserialize(deserializer)
else:
raise Exception(f"Invalid type: {variant}")

Expand Down Expand Up @@ -388,3 +391,39 @@ def deserialize(deserializer: Deserializer) -> MultiKeyAuthenticator:
def serialize(self, serializer: Serializer):
serializer.struct(self.public_key)
serializer.struct(self.signature)


class Test(unittest.TestCase):
def test_multi_key_auth(self):
expected_output = bytes.fromhex(
"040303002020fdbac9b10b7587bba7b5bc163bce69e796d71e4ed44c10fcb4488689f7a1440141049b8327d929a0e45285c04d19c9fffbee065c266b701972922d807228120e43f34ad68ac77f6ec0205fe39f7c5b6055dad973a03464a3a743302de0feaf6ec6d90141049b8327d929a0e45285c04d19c9fffbee065c266b701972922d807228120e43f34ad68ac77f6ec0205fe39f7c5b6055dad973a03464a3a743302de0feaf6ec6d902020040a9839b56be99b48c285ec252cf9bf779e42d3b62eb8664c31b18c1fdb29b574b1bfde0b89aedddb9fb8304ca5913c9feefea75d332d8f72ac3ab4598a884ea0801402bd50683abe6332a496121f8ec7db7be351f49b0087fa0dfb258c469822bd52e59fc9344944a1f338b0f0a61c7173453e0cd09cf961e45cb9396808fa67eeef301c0"
)
der = Deserializer(expected_output)
der.struct(Authenticator)

pk0 = ed25519.PublicKey.from_str(
"20FDBAC9B10B7587BBA7B5BC163BCE69E796D71E4ED44C10FCB4488689F7A144"
)
pk1 = secp256k1_ecdsa.PublicKey.from_str(
"049B8327D929A0E45285C04D19C9FFFBEE065C266B701972922D807228120E43F34AD68AC77F6EC0205FE39F7C5B6055DAD973A03464A3A743302DE0FEAF6EC6D9"
)
pk2 = secp256k1_ecdsa.PublicKey.from_str(
"049B8327D929A0E45285C04D19C9FFFBEE065C266B701972922D807228120E43F34AD68AC77F6EC0205FE39F7C5B6055DAD973A03464A3A743302DE0FEAF6EC6D9"
)
sig0 = ed25519.Signature.from_str(
"a9839b56be99b48c285ec252cf9bf779e42d3b62eb8664c31b18c1fdb29b574b1bfde0b89aedddb9fb8304ca5913c9feefea75d332d8f72ac3ab4598a884ea08"
)
sig1 = secp256k1_ecdsa.Signature.from_str(
"2bd50683abe6332a496121f8ec7db7be351f49b0087fa0dfb258c469822bd52e59fc9344944a1f338b0f0a61c7173453e0cd09cf961e45cb9396808fa67eeef3"
)

multi_key = asymmetric_crypto_wrapper.MultiPublicKey([pk0, pk1, pk2], 2)
multi_sig = asymmetric_crypto_wrapper.MultiSignature([(0, sig0), (1, sig1)])
multi_key_auth = MultiKeyAuthenticator(multi_key, multi_sig)
single_sender_auth = SingleSenderAuthenticator(
AccountAuthenticator(multi_key_auth)
)
txn_auth = Authenticator(single_sender_auth)
ser = Serializer()
txn_auth.serialize(ser)
self.assertEqual(expected_output, ser.output())
12 changes: 12 additions & 0 deletions aptos_sdk/ed25519.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ def __eq__(self, other: object):
def __str__(self) -> str:
return f"0x{self.key.encode().hex()}"

@staticmethod
def from_str(value: str) -> PublicKey:
if value[0:2] == "0x":
value = value[2:]
return PublicKey(VerifyKey(bytes.fromhex(value)))

def verify(self, data: bytes, signature: asymmetric_crypto.Signature) -> bool:
try:
signature = cast(Signature, signature)
Expand Down Expand Up @@ -192,6 +198,12 @@ def deserialize(deserializer: Deserializer) -> Signature:

return Signature(signature)

@staticmethod
def from_str(value: str) -> Signature:
if value[0:2] == "0x":
value = value[2:]
return Signature(bytes.fromhex(value))

def serialize(self, serializer: Serializer):
serializer.to_bytes(self.signature)

Expand Down
5 changes: 5 additions & 0 deletions examples/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ async def test_large_package_publisher(self):
)
await large_package_publisher.main(large_package_example_dir, module_addr)

async def test_multikey(self):
from . import multikey

await multikey.main()

async def test_multisig(self):
from . import multisig

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aptos-sdk"
version = "0.9.1"
version = "0.9.2"
description = "Aptos SDK"
authors = ["Aptos Labs <[email protected]>"]
license = "Apache-2.0"
Expand Down
Loading