Skip to content

Commit

Permalink
Make MultiKeyAuthenicator work
Browse files Browse the repository at this point in the history
  • Loading branch information
davidiw committed Oct 21, 2024
1 parent f6110c7 commit 46682d8
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 12 deletions.
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

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"))


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

0 comments on commit 46682d8

Please sign in to comment.