From 69be6bb687a479cd569e0b5394ac56a230a34a74 Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Thu, 14 Nov 2024 15:03:08 -0500 Subject: [PATCH] [multikey] Allow variable length bitmaps --- CHANGELOG.md | 2 + src/core/crypto/multiKey.ts | 2 +- .../transaction/transactionSubmission.test.ts | 110 ++++++++++++++++++ tests/unit/multiKey.test.ts | 14 ++- 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab226399f..274395207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T # Unreleased +- [`fix`] Allow variable length bitmaps in Multikey accounts, allowing for compatibility between SDKs properly + # 1.33.0 (2024-11-13) - Allow optional provision of public keys in transaction simulation - Update the multisig v2 example to demonstrate a new way to pre-check a multisig payload before it is created on-chain diff --git a/src/core/crypto/multiKey.ts b/src/core/crypto/multiKey.ts index e14578eef..d51fc604b 100644 --- a/src/core/crypto/multiKey.ts +++ b/src/core/crypto/multiKey.ts @@ -171,7 +171,7 @@ export class MultiKey extends AccountPublicKey { // Extend by required number of bytes if (bitmap.length < byteOffset) { for (let i = bitmap.length; i < byteOffset; i += 1) { - bitmap.push(0) + bitmap.push(0); } } diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index d9536c9ed..b35755fba 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -12,6 +12,8 @@ import { TransactionPayloadEntryFunction, Bool, MoveString, + Ed25519PublicKey, + AnyPublicKey, } from "../../../src"; import { MAX_U64_BIG_INT } from "../../../src/bcs/consts"; import { longTestTimeout } from "../../unit/helper"; @@ -662,6 +664,114 @@ describe("transaction submission", () => { }); expect(response.signature?.type).toBe("single_sender"); }); + test("it submits a multi key transaction with lots of signers", async () => { + const subAccounts = []; + for (let i = 0; i < 32; i += 1) { + switch (i % 3) { + case 0: + subAccounts.push(Account.generate({ scheme: SigningSchemeInput.Ed25519, legacy: false })); + break; + case 1: + subAccounts.push(Account.generate({ scheme: SigningSchemeInput.Ed25519, legacy: true })); + break; + case 2: + subAccounts.push(Account.generate({ scheme: SigningSchemeInput.Secp256k1Ecdsa })); + break; + default: + break; + } + } + const publicKeys = subAccounts.map((account) => { + if (account.publicKey instanceof Ed25519PublicKey) { + return new AnyPublicKey(account.publicKey); + } + return account.publicKey; + }); + + const multiKey = new MultiKey({ + publicKeys, + signaturesRequired: 1, + }); + + const account = new MultiKeyAccount({ + multiKey, + signers: subAccounts, + }); + + await aptos.fundAccount({ accountAddress: account.accountAddress, amount: 100_000_000 }); + + const transaction = await aptos.transaction.build.simple({ + sender: account.accountAddress, + data: { + function: `0x${contractPublisherAccount.accountAddress.toStringWithoutPrefix()}::transfer::transfer`, + functionArguments: [1, receiverAccounts[0].accountAddress], + }, + }); + + const senderAuthenticator = aptos.transaction.sign({ signer: account, transaction }); + + const response = await aptos.transaction.submit.simple({ transaction, senderAuthenticator }); + await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + expect(response.signature?.type).toBe("single_sender"); + + // Sign with only one of them now + const account2 = new MultiKeyAccount({ + multiKey, + signers: [subAccounts[0]], + }); + expect(account2.accountAddress).toEqual(account.accountAddress); + + await aptos.fundAccount({ accountAddress: account2.accountAddress, amount: 100_000_000 }); + + const transaction2 = await aptos.transaction.build.simple({ + sender: account2.accountAddress, + data: { + function: `0x${contractPublisherAccount.accountAddress.toStringWithoutPrefix()}::transfer::transfer`, + functionArguments: [1, receiverAccounts[0].accountAddress], + }, + }); + + const senderAuthenticator2 = aptos.transaction.sign({ signer: account2, transaction: transaction2 }); + + const response2 = await aptos.transaction.submit.simple({ + transaction: transaction2, + senderAuthenticator: senderAuthenticator2, + }); + await aptos.waitForTransaction({ + transactionHash: response2.hash, + }); + expect(response2.signature?.type).toBe("single_sender"); + + // Sign with the last one now + const account3 = new MultiKeyAccount({ + multiKey, + signers: [subAccounts[31]], + }); + expect(account3.accountAddress).toEqual(account.accountAddress); + + await aptos.fundAccount({ accountAddress: account3.accountAddress, amount: 100_000_000 }); + + const transaction3 = await aptos.transaction.build.simple({ + sender: account3.accountAddress, + data: { + function: `0x${contractPublisherAccount.accountAddress.toStringWithoutPrefix()}::transfer::transfer`, + functionArguments: [1, receiverAccounts[0].accountAddress], + }, + }); + + const senderAuthenticator3 = aptos.transaction.sign({ signer: account3, transaction: transaction3 }); + + const response3 = await aptos.transaction.submit.simple({ + transaction: transaction3, + senderAuthenticator: senderAuthenticator3, + }); + await aptos.waitForTransaction({ + transactionHash: response3.hash, + }); + expect(response3.signature?.type).toBe("single_sender"); + }); }); describe("publish move module", () => { const account = Account.generate(); diff --git a/tests/unit/multiKey.test.ts b/tests/unit/multiKey.test.ts index b06834e46..3aef3be1a 100644 --- a/tests/unit/multiKey.test.ts +++ b/tests/unit/multiKey.test.ts @@ -1,7 +1,15 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Deserializer, Ed25519PublicKey, Secp256k1PublicKey, MultiKey, Hex, MultiKeySignature, Serializer } from "../../src"; +import { + Deserializer, + Ed25519PublicKey, + Secp256k1PublicKey, + MultiKey, + Hex, + MultiKeySignature, + Serializer, +} from "../../src"; import { multiKeyTestObject } from "./helper"; describe("MultiKey", () => { @@ -125,9 +133,9 @@ describe("MultiKey", () => { ).toUint8Array(); const deserializer = new Deserializer(serializedBytes); const multiKeySig = MultiKeySignature.deserialize(deserializer); - const serializer = new Serializer() + const serializer = new Serializer(); multiKeySig.serialize(serializer); const outBytes = serializer.toUint8Array(); - expect(outBytes).toEqual(serializedBytes) + expect(outBytes).toEqual(serializedBytes); }); });