diff --git a/src/api/aptos.ts b/src/api/aptos.ts index 92dc0e106..a9464f637 100644 --- a/src/api/aptos.ts +++ b/src/api/aptos.ts @@ -12,6 +12,7 @@ import { General } from "./general"; import { ANS } from "./ans"; import { Staking } from "./staking"; import { Transaction } from "./transaction"; +import { ProofChallenge } from "./proofChallenge"; /** * This class is the main entry point into Aptos's @@ -43,6 +44,8 @@ export class Aptos { readonly transaction: Transaction; + readonly proofChallenge: ProofChallenge; + constructor(settings?: AptosConfig) { this.config = new AptosConfig(settings); this.account = new Account(this.config); @@ -55,6 +58,7 @@ export class Aptos { this.general = new General(this.config); this.staking = new Staking(this.config); this.transaction = new Transaction(this.config); + this.proofChallenge = new ProofChallenge(this.config); } } @@ -70,6 +74,7 @@ export interface Aptos FungibleAsset, General, Staking, + ProofChallenge, Omit {} /** @@ -103,3 +108,4 @@ applyMixin(Aptos, FungibleAsset, "fungibleAsset"); applyMixin(Aptos, General, "general"); applyMixin(Aptos, Staking, "staking"); applyMixin(Aptos, Transaction, "transaction"); +applyMixin(Aptos, ProofChallenge, "proofChallenge"); diff --git a/src/api/general.ts b/src/api/general.ts index 5360c2b3c..9e7f7ae1e 100644 --- a/src/api/general.ts +++ b/src/api/general.ts @@ -3,7 +3,6 @@ import { AptosConfig } from "./aptosConfig"; import { - createProofChallenge, getBlockByHeight, getBlockByVersion, getChainTopUserTransactions, @@ -22,12 +21,11 @@ import { GraphqlQuery, LedgerInfo, LedgerVersionArg, - MoveFunctionId, MoveValue, TableItemRequest, } from "../types"; import { ProcessorType } from "../utils/const"; -import { InputViewFunctionData, ProofChallenge } from "../transactions"; +import { InputViewFunctionData } from "../transactions"; /** * A class to query all `General` Aptos related queries @@ -203,11 +201,4 @@ export class General { async getProcessorStatus(processorType: ProcessorType): Promise { return getProcessorStatus({ aptosConfig: this.config, processorType }); } - - async createProofChallenge(args: { struct: MoveFunctionId; data: Array }): Promise { - return createProofChallenge({ - config: this.config, - ...args, - }); - } } diff --git a/src/api/proofChallenge.ts b/src/api/proofChallenge.ts new file mode 100644 index 000000000..325f922fb --- /dev/null +++ b/src/api/proofChallenge.ts @@ -0,0 +1,37 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Account, Signature } from "../core"; +import { createProofChallenge, signProofChallenge } from "../internal/proofChallenge"; +import { MoveFunctionId } from "../types"; +import { AptosConfig } from "./aptosConfig"; +import { ProofChallenge as ProofChallengeInstance } from "../transactions/instances/proofChallenge"; +import { EntryFunctionArgumentTypes, SimpleEntryFunctionArgumentTypes } from "../transactions/types"; + +/** + * A class to query all `General` Aptos related queries + */ +export class ProofChallenge { + readonly config: AptosConfig; + + constructor(config: AptosConfig) { + this.config = config; + } + + async createProofChallenge(args: { + struct: MoveFunctionId; + data: Array; + }): Promise { + return createProofChallenge({ + config: this.config, + ...args, + }); + } + + // eslint-disable-next-line class-methods-use-this + signProofChallenge(args: { challenge: ProofChallengeInstance; signer: Account }): Signature { + return signProofChallenge({ + ...args, + }); + } +} diff --git a/src/api/transaction.ts b/src/api/transaction.ts index b12d23fe3..8c0e60391 100644 --- a/src/api/transaction.ts +++ b/src/api/transaction.ts @@ -25,7 +25,6 @@ import { publicPackageTransaction, rotateAuthKey, signAndSubmitTransaction, - signProofChallenge, signTransaction, } from "../internal/transactionSubmission"; import { @@ -33,9 +32,8 @@ import { AnyRawTransaction, InputGenerateTransactionOptions, InputGenerateTransactionPayloadData, - ProofChallenge, } from "../transactions"; -import { AccountAddressInput, Account, PrivateKey, Signature } from "../core"; +import { AccountAddressInput, Account, PrivateKey } from "../core"; import { Build } from "./transactionSubmission/build"; import { Simulate } from "./transactionSubmission/simulate"; import { Submit } from "./transactionSubmission/submit"; @@ -265,13 +263,6 @@ export class Transaction { }); } - // eslint-disable-next-line class-methods-use-this - signProofChallenge(args: { challenge: ProofChallenge; signer: Account }): Signature { - return signProofChallenge({ - ...args, - }); - } - // TRANSACTION SUBMISSION // /** diff --git a/src/internal/general.ts b/src/internal/general.ts index 2f3de87c9..673c4a377 100644 --- a/src/internal/general.ts +++ b/src/internal/general.ts @@ -9,13 +9,7 @@ */ import { AptosConfig } from "../api/aptosConfig"; -import { MoveString } from "../bcs"; import { getAptosFullNode, postAptosFullNode, postAptosIndexer } from "../client"; -import { AccountAddress } from "../core"; -import { getFunctionParts } from "../transactions/transactionBuilder/helpers"; -import { fetchStructFieldsAbi, convertArgument } from "../transactions/transactionBuilder/remoteAbi"; -import { ProofChallenge } from "../transactions/instances"; -import { EntryFunctionArgumentTypes } from "../transactions/types"; import { AnyNumber, Block, @@ -24,7 +18,6 @@ import { GraphqlQuery, LedgerInfo, LedgerVersionArg, - MoveFunctionId, TableItemRequest, } from "../types"; import { GetChainTopUserTransactionsQuery, GetProcessorStatusQuery } from "../types/generated/operations"; @@ -169,33 +162,3 @@ export async function getProcessorStatus(args: { return data.processor_status[0]; } - -export async function createProofChallenge(args: { - config: AptosConfig; - struct: MoveFunctionId; - data: Array; -}): Promise { - const { config, struct, data } = args; - const { moduleAddress, moduleName, functionName } = getFunctionParts(struct); - const structFieldsAbi = await fetchStructFieldsAbi(moduleAddress, moduleName, functionName, config); - - // Check all BCS types, and convert any non-BCS types - const functionArguments: Array = - data.map((arg, i) => convertArgument(functionName, structFieldsAbi, arg, i, structFieldsAbi.parameters)) ?? []; - - // Check that all arguments are accounted for - if (functionArguments.length !== structFieldsAbi.parameters.length) { - throw new Error( - // eslint-disable-next-line max-len - `Too few arguments for '${moduleAddress}::${moduleName}::${functionName}', expected ${structFieldsAbi.parameters.length} but got ${functionArguments.length}`, - ); - } - - const challenge = new ProofChallenge([ - AccountAddress.from(moduleAddress), - new MoveString(moduleName), - new MoveString(functionName), - ...functionArguments, - ]); - return challenge; -} diff --git a/src/internal/proofChallenge.ts b/src/internal/proofChallenge.ts new file mode 100644 index 000000000..b21d3399c --- /dev/null +++ b/src/internal/proofChallenge.ts @@ -0,0 +1,21 @@ +import { AptosConfig } from "../api/aptosConfig"; +import { Account, Signature } from "../core"; +import { EntryFunctionArgumentTypes, SimpleEntryFunctionArgumentTypes, generateProofChallenge } from "../transactions"; +import { ProofChallenge } from "../transactions/instances/proofChallenge"; +import { MoveFunctionId } from "../types"; + +export async function createProofChallenge(args: { + config: AptosConfig; + struct: MoveFunctionId; + data: Array; +}): Promise { + const challenge = generateProofChallenge({ ...args }); + return challenge; +} + +export function signProofChallenge(args: { challenge: ProofChallenge; signer: Account }): Signature { + const { challenge, signer } = args; + const challengeHex = challenge.bcsToBytes(); + const signature = signer.sign(challengeHex); + return signature; +} diff --git a/src/internal/transactionSubmission.ts b/src/internal/transactionSubmission.ts index 5d80a175c..1fd91b517 100644 --- a/src/internal/transactionSubmission.ts +++ b/src/internal/transactionSubmission.ts @@ -10,9 +10,9 @@ import { MoveVector, U8 } from "../bcs"; import { postAptosFullNode } from "../client"; import { Account } from "../core/account"; import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; -import { PrivateKey, Signature } from "../core/crypto"; +import { PrivateKey } from "../core/crypto"; import { AccountAuthenticator } from "../transactions/authenticator/account"; -import { ProofChallenge, RotationProofChallenge } from "../transactions/instances/rotationProofChallenge"; +import { RotationProofChallenge } from "../transactions/instances/rotationProofChallenge"; import { buildTransaction, generateTransactionPayload, @@ -206,13 +206,6 @@ export function signTransaction(args: { signer: Account; transaction: AnyRawTran return accountAuthenticator; } -export function signProofChallenge(args: { challenge: ProofChallenge; signer: Account }): Signature { - const { challenge, signer } = args; - const challengeHex = challenge.bcsToBytes(); - const signature = signer.sign(challengeHex); - return signature; -} - /** * Simulates a transaction before singing it. * @@ -355,7 +348,6 @@ export async function rotateAuthKey(args: { // Sign the challenge const challengeHex = challenge.bcsToBytes(); - const proofSignedByCurrentPrivateKey = fromAccount.sign(challengeHex); const proofSignedByNewPrivateKey = newAccount.sign(challengeHex); diff --git a/src/transactions/instances/proofChallenge.ts b/src/transactions/instances/proofChallenge.ts new file mode 100644 index 000000000..cfb0044af --- /dev/null +++ b/src/transactions/instances/proofChallenge.ts @@ -0,0 +1,16 @@ +import { Serializable, Serializer } from "../../bcs"; + +export class ProofChallenge extends Serializable { + public readonly data: Serializable[]; + + constructor(data: Serializable[]) { + super(); + this.data = data; + } + + serialize(serializer: Serializer): void { + this.data.forEach((data) => { + serializer.serialize(data); + }); + } +} diff --git a/src/transactions/instances/rotationProofChallenge.ts b/src/transactions/instances/rotationProofChallenge.ts index 8c899db1b..dcc2e6dc2 100644 --- a/src/transactions/instances/rotationProofChallenge.ts +++ b/src/transactions/instances/rotationProofChallenge.ts @@ -7,21 +7,6 @@ import { AnyNumber } from "../../types"; import { PublicKey } from "../../core/crypto"; import { MoveString, MoveVector, U64, U8 } from "../../bcs"; -export class ProofChallenge extends Serializable { - public readonly data: Serializable[]; - - constructor(data: Serializable[]) { - super(); - this.data = data; - } - - serialize(serializer: Serializer): void { - this.data.forEach((data) => { - serializer.serialize(data); - }); - } -} - /** * Representation of the challenge which is needed to sign by owner of the account * to rotate the authentication key. diff --git a/src/transactions/transactionBuilder/transactionBuilder.ts b/src/transactions/transactionBuilder/transactionBuilder.ts index 27078b31f..fe245a157 100644 --- a/src/transactions/transactionBuilder/transactionBuilder.ts +++ b/src/transactions/transactionBuilder/transactionBuilder.ts @@ -70,13 +70,22 @@ import { InputViewFunctionDataWithRemoteABI, InputViewFunctionDataWithABI, FunctionABI, + SimpleEntryFunctionArgumentTypes, } from "../types"; -import { convertArgument, fetchEntryFunctionAbi, fetchViewFunctionAbi, standardizeTypeTags } from "./remoteAbi"; +import { + convertArgument, + fetchEntryFunctionAbi, + fetchStructFieldsAbi, + fetchViewFunctionAbi, + standardizeTypeTags, +} from "./remoteAbi"; import { memoizeAsync } from "../../utils/memoize"; -import { AnyNumber } from "../../types"; +import { AnyNumber, MoveFunctionId } from "../../types"; import { getFunctionParts, isScriptDataInput } from "./helpers"; import { SimpleTransaction } from "../instances/simpleTransaction"; import { MultiAgentTransaction } from "../instances/multiAgentTransaction"; +import { ProofChallenge } from "../instances/proofChallenge"; +import { MoveString } from "../../bcs"; /** * We are defining function signatures, each with its specific input and output. @@ -688,3 +697,34 @@ async function fetchAbi({ 1000 * 60 * 5, // 5 minutes )(); } + +export async function generateProofChallenge(args: { + config: AptosConfig; + struct: MoveFunctionId; + data: Array; +}) { + const { config, struct, data } = args; + const { moduleAddress, moduleName, functionName } = getFunctionParts(struct); + const structFieldsAbi = await fetchStructFieldsAbi(moduleAddress, moduleName, functionName, config); + + // Check all BCS types, and convert any non-BCS types + // TODO repeated code, move to a central place + const functionArguments: Array = + data.map((arg, i) => convertArgument(functionName, structFieldsAbi, arg, i, structFieldsAbi.parameters)) ?? []; + + // Check that all arguments are accounted for + if (functionArguments.length !== structFieldsAbi.parameters.length) { + throw new Error( + // eslint-disable-next-line max-len + `Too few arguments for '${moduleAddress}::${moduleName}::${functionName}', expected ${structFieldsAbi.parameters.length} but got ${functionArguments.length}`, + ); + } + + const challenge = new ProofChallenge([ + AccountAddress.from(moduleAddress), + new MoveString(moduleName), + new MoveString(functionName), + ...functionArguments, + ]); + return challenge; +} diff --git a/tests/e2e/api/proofChallenge.test.ts b/tests/e2e/api/proofChallenge.test.ts new file mode 100644 index 000000000..2635bf83e --- /dev/null +++ b/tests/e2e/api/proofChallenge.test.ts @@ -0,0 +1,50 @@ +import { Account, U8, MoveVector } from "../../../src"; +import { getAptosClient } from "../helper"; + +const { aptos } = getAptosClient(); + +describe("proof challenge", () => { + test("generic challenge", async () => { + const fromAccount = Account.generate(); + const newAccount = Account.generate(); + + await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); + await aptos.fundAccount({ accountAddress: newAccount.accountAddress, amount: 1_000_000_000 }); + + const accountInfo = await aptos.getAccountInfo({ + accountAddress: fromAccount.accountAddress, + }); + + const challenge = await aptos.createProofChallenge({ + struct: "0x1::account::RotationProofChallenge", + data: [ + BigInt(accountInfo.sequence_number), + fromAccount.accountAddress, + accountInfo.authentication_key, + newAccount.publicKey.toUint8Array(), + ], + }); + + const proofSignedByCurrentPrivateKey = aptos.signProofChallenge({ challenge, signer: fromAccount }); + const proofSignedByNewPrivateKey = aptos.signProofChallenge({ challenge, signer: newAccount }); + + const transaction = await aptos.transaction.build.simple({ + sender: fromAccount.accountAddress, + data: { + function: "0x1::account::rotate_authentication_key", + functionArguments: [ + new U8(fromAccount.signingScheme), // from scheme + MoveVector.U8(fromAccount.publicKey.toUint8Array()), + new U8(newAccount.signingScheme), // to scheme + MoveVector.U8(newAccount.publicKey.toUint8Array()), + MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()), + MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()), + ], + }, + }); + + const response = await aptos.signAndSubmitTransaction({ signer: fromAccount, transaction }); + const executedTransaction = await aptos.waitForTransaction({ transactionHash: response.hash }); + expect(executedTransaction.success).toBeTruthy(); + }); +}); diff --git a/tests/e2e/transaction/signTransaction.test.ts b/tests/e2e/transaction/signTransaction.test.ts index 236a64e64..a1b7dbf0b 100644 --- a/tests/e2e/transaction/signTransaction.test.ts +++ b/tests/e2e/transaction/signTransaction.test.ts @@ -6,8 +6,6 @@ import { AccountAuthenticator, AccountAuthenticatorEd25519, AccountAuthenticatorSingleKey, - MoveVector, - U8, } from "../../../src"; import { longTestTimeout } from "../../unit/helper"; import { getAptosClient } from "../helper"; @@ -218,46 +216,3 @@ describe("sign transaction", () => { }); }); }); - -test.only("test", async () => { - const fromAccount = Account.generate(); - const newAccount = Account.generate(); - - await aptos.fundAccount({ accountAddress: fromAccount.accountAddress, amount: 1_000_000_000 }); - await aptos.fundAccount({ accountAddress: newAccount.accountAddress, amount: 1_000_000_000 }); - - const accountInfo = await aptos.getAccountInfo({ - accountAddress: fromAccount.accountAddress, - }); - - const challenge = await aptos.createProofChallenge({ - struct: "0x1::account::RotationProofChallenge", - data: [ - BigInt(accountInfo.sequence_number), - fromAccount.accountAddress, - accountInfo.authentication_key, - newAccount.publicKey.toUint8Array(), - ], - }); - - const proofSignedByCurrentPrivateKey = aptos.signProofChallenge({ challenge, signer: fromAccount }); - const proofSignedByNewPrivateKey = aptos.signProofChallenge({ challenge, signer: newAccount }); - - const transaction = await aptos.transaction.build.simple({ - sender: fromAccount.accountAddress, - data: { - function: "0x1::account::rotate_authentication_key", - functionArguments: [ - new U8(fromAccount.signingScheme), // from scheme - MoveVector.U8(fromAccount.publicKey.toUint8Array()), - new U8(newAccount.signingScheme), // to scheme - MoveVector.U8(newAccount.publicKey.toUint8Array()), - MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()), - MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()), - ], - }, - }); - - const response = await aptos.signAndSubmitTransaction({ signer: fromAccount, transaction }); - console.log("response", response); -});