Skip to content

Commit d106336

Browse files
authored
Add new account derivation APIs (#635)
* Add account discovery APIs * update * add sindling for single sender * remove test script * fix lint * remove unused func * fmt * Update account recovery API * remove signatures query * remove pubkey for account address * Update account api options * update CL * lower coverage threshold for functions * update test * Add e2e tests and txnoptions for rotateAuthKey * revert jest * Add comments * Update funcdoc * fix origin method * add testcases * fmt * fmt * update API to have lastTxnVersion * update error message * reomve space * skip tests * use object look up to check account existance
1 parent 1792657 commit d106336

22 files changed

+1816
-1326
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
All notable changes to the Aptos TypeScript SDK will be captured in this file. This changelog is written by hand for now. It adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
## Unreleased
6+
- Add account derivation APIs, `getAccountsForPublicKey` and `deriveOwnedAccountsFromSigner` which handle multi-key accounts and key rotations
7+
- Update the deprecated function deriveAccountFromPrivateKey to use the new account derivation API
68

79
# 3.0.0 (2025-06-26)
810
- Make the default max gas amount and exipry time from now values for transaction generation configurable.

src/account/FederatedKeylessAccount.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
140140
uidKey?: string;
141141
proofFetchCallback?: ProofFetchCallback;
142142
verificationKey?: Groth16VerificationKey;
143+
verificationKeyHash?: Uint8Array;
143144
}): FederatedKeylessAccount {
144145
const {
145146
address,
@@ -151,8 +152,13 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
151152
uidKey = "sub",
152153
proofFetchCallback,
153154
verificationKey,
155+
verificationKeyHash,
154156
} = args;
155157

158+
if (verificationKeyHash && verificationKey) {
159+
throw new Error("Cannot provide both verificationKey and verificationKeyHash");
160+
}
161+
156162
const { iss, aud, uidVal } = getIssAudAndUidVal({ jwt, uidKey });
157163
return new FederatedKeylessAccount({
158164
address,
@@ -166,7 +172,7 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
166172
jwkAddress: AccountAddress.from(jwkAddress),
167173
jwt,
168174
proofFetchCallback,
169-
verificationKeyHash: verificationKey ? verificationKey.hash() : undefined,
175+
verificationKeyHash: verificationKeyHash ?? (verificationKey ? verificationKey.hash() : undefined),
170176
});
171177
}
172178
}

src/account/KeylessAccount.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,23 @@ export class KeylessAccount extends AbstractKeylessAccount {
143143
uidKey?: string;
144144
proofFetchCallback?: ProofFetchCallback;
145145
verificationKey?: Groth16VerificationKey;
146+
verificationKeyHash?: Uint8Array;
146147
}): KeylessAccount {
147-
const { address, proof, jwt, ephemeralKeyPair, pepper, uidKey = "sub", proofFetchCallback, verificationKey } = args;
148+
const {
149+
address,
150+
proof,
151+
jwt,
152+
ephemeralKeyPair,
153+
pepper,
154+
uidKey = "sub",
155+
proofFetchCallback,
156+
verificationKey,
157+
verificationKeyHash,
158+
} = args;
159+
160+
if (verificationKeyHash && verificationKey) {
161+
throw new Error("Cannot provide both verificationKey and verificationKeyHash");
162+
}
148163

149164
const { iss, aud, uidVal } = getIssAudAndUidVal({ jwt, uidKey });
150165
return new KeylessAccount({
@@ -158,7 +173,7 @@ export class KeylessAccount extends AbstractKeylessAccount {
158173
pepper,
159174
jwt,
160175
proofFetchCallback,
161-
verificationKeyHash: verificationKey ? verificationKey.hash() : undefined,
176+
verificationKeyHash: verificationKeyHash ?? (verificationKey ? verificationKey.hash() : undefined),
162177
});
163178
}
164179
}

src/api/account.ts

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { Account as AccountModule } from "../account";
5-
import { AccountAddress, PrivateKey, AccountAddressInput, createObjectAddress } from "../core";
5+
import {
6+
AccountAddress,
7+
AccountAddressInput,
8+
createObjectAddress,
9+
BaseAccountPublicKey,
10+
PrivateKeyInput,
11+
} from "../core";
612
import {
713
AccountData,
814
AnyNumber,
@@ -31,6 +37,7 @@ import {
3137
getAccountOwnedObjects,
3238
getAccountOwnedTokens,
3339
getAccountOwnedTokensFromCollectionAddress,
40+
getAccountsForPublicKey,
3441
getAccountTokensCount,
3542
getAccountTransactionsCount,
3643
getInfo,
@@ -42,6 +49,8 @@ import {
4249
getResourcesPage,
4350
getTransactions,
4451
lookupOriginalAccountAddress,
52+
deriveOwnedAccountsFromSigner,
53+
AccountInfo,
4554
} from "../internal/account";
4655
import { APTOS_COIN, APTOS_FA, ProcessorType } from "../utils/const";
4756
import { AptosConfig } from "./aptosConfig";
@@ -977,7 +986,131 @@ export class Account {
977986
* @group Account
978987
* @deprecated Note that more inspection is needed by the user to determine which account exists on-chain
979988
*/
980-
async deriveAccountFromPrivateKey(args: { privateKey: PrivateKey }): Promise<AccountModule> {
989+
async deriveAccountFromPrivateKey(args: {
990+
privateKey: PrivateKeyInput;
991+
minimumLedgerVersion?: AnyNumber;
992+
options?: {
993+
throwIfNoAccountFound?: boolean;
994+
};
995+
}): Promise<AccountModule> {
996+
await waitForIndexerOnVersion({
997+
config: this.config,
998+
minimumLedgerVersion: args.minimumLedgerVersion,
999+
processorType: ProcessorType.ACCOUNT_RESTORATION_PROCESSOR,
1000+
});
1001+
await waitForIndexerOnVersion({
1002+
config: this.config,
1003+
minimumLedgerVersion: args.minimumLedgerVersion,
1004+
processorType: ProcessorType.OBJECT_PROCESSOR,
1005+
});
9811006
return deriveAccountFromPrivateKey({ aptosConfig: this.config, ...args });
9821007
}
1008+
1009+
/**
1010+
* Derives all accounts owned by a signer. This function takes a signer (either an Account or PrivateKey)
1011+
* and returns all accounts that can be derived from it, ordered by the most recently used account first.
1012+
*
1013+
* Note, this function will not return accounts that require more than one signer to be used.
1014+
*
1015+
* @param args - The arguments for deriving owned accounts
1016+
* @param args.signer - The signer to derive accounts from (Account or PrivateKey)
1017+
* @param args.minimumLedgerVersion - The minimum ledger version to wait for before querying
1018+
* @param args.options.includeUnverified - Whether to include unverified accounts in the results. Unverified accounts
1019+
* are accounts that can be authenticated with the signer, but there is no history of the signer using the account.
1020+
* Default is false.
1021+
* @param args.options.noMultiKey - If true, do not include multi-key accounts in the results. Default is false.
1022+
* @returns Promise resolving to an array of derived Account objects
1023+
*
1024+
* @example
1025+
* ```typescript
1026+
* import { Aptos, AptosConfig, Network, Ed25519Account } from "@aptos-labs/ts-sdk";
1027+
*
1028+
* const config = new AptosConfig({ network: Network.TESTNET });
1029+
* const aptos = new Aptos(config);
1030+
*
1031+
* async function getOwnedAccounts() {
1032+
* const signer = Ed25519Account.generate();
1033+
* const accounts = await aptos.deriveOwnedAccountsFromSigner({
1034+
* signer
1035+
* });
1036+
* const account = accounts[0];
1037+
* console.log(account);
1038+
* }
1039+
* ```
1040+
* @group Account
1041+
*/
1042+
async deriveOwnedAccountsFromSigner(args: {
1043+
signer: AccountModule | PrivateKeyInput;
1044+
minimumLedgerVersion?: AnyNumber;
1045+
options?: { includeUnverified?: boolean; noMultiKey?: boolean };
1046+
}): Promise<AccountModule[]> {
1047+
await waitForIndexerOnVersion({
1048+
config: this.config,
1049+
minimumLedgerVersion: args.minimumLedgerVersion,
1050+
processorType: ProcessorType.ACCOUNT_RESTORATION_PROCESSOR,
1051+
});
1052+
await waitForIndexerOnVersion({
1053+
config: this.config,
1054+
minimumLedgerVersion: args.minimumLedgerVersion,
1055+
processorType: ProcessorType.OBJECT_PROCESSOR,
1056+
});
1057+
return deriveOwnedAccountsFromSigner({ aptosConfig: this.config, ...args });
1058+
}
1059+
1060+
/**
1061+
* Gets all account info (address, account public key, last transaction version) that have are associated with a public key and **related public keys**
1062+
*
1063+
* For a given public key, it will query all multikeys that the public key is part of. Then for the provided public key and
1064+
* any multikeys found in the previous step, it will query for any accounts that have an auth key that matches any of the
1065+
* public keys.
1066+
*
1067+
* Note: If an Ed25519PublicKey or an AnyPublicKey that wraps Ed25519PublicKey is passed in, it will query for both legacy and single singer cases.
1068+
*
1069+
* @param args - The arguments for getting accounts for a public key
1070+
* @param args.publicKey - The public key to look up accounts for
1071+
* @param args.minimumLedgerVersion - The minimum ledger version to wait for before querying
1072+
* @param args.options.includeUnverified - Whether to include unverified accounts in the results. Unverified accounts
1073+
* are accounts that can be authenticated with the signer, but there is no history of the signer using the account. Default
1074+
* is false.
1075+
* @param args.options.noMultiKey - Whether to exclude multi-key accounts in the results. Default is false.
1076+
* @returns Promise resolving to an array of account addresses and their associated public keys
1077+
*
1078+
* @example
1079+
* ```typescript
1080+
* import { Aptos, AptosConfig, Network, Ed25519PrivateKey } from "@aptos-labs/ts-sdk";
1081+
*
1082+
* const config = new AptosConfig({ network: Network.TESTNET });
1083+
* const aptos = new Aptos(config);
1084+
*
1085+
* async function getAccounts() {
1086+
* const privateKey = Ed25519PrivateKey.generate();
1087+
* const publicKey = privateKey.publicKey();
1088+
* const accounts = await aptos.getAccountsForPublicKey({
1089+
* publicKey
1090+
* });
1091+
* console.log(accounts);
1092+
* }
1093+
* ```
1094+
* @group Account
1095+
*/
1096+
async getAccountsForPublicKey(args: {
1097+
publicKey: BaseAccountPublicKey;
1098+
minimumLedgerVersion?: AnyNumber;
1099+
options?: { includeUnverified?: boolean; noMultiKey?: boolean };
1100+
}): Promise<AccountInfo[]> {
1101+
await waitForIndexerOnVersion({
1102+
config: this.config,
1103+
minimumLedgerVersion: args.minimumLedgerVersion,
1104+
processorType: ProcessorType.ACCOUNT_RESTORATION_PROCESSOR,
1105+
});
1106+
await waitForIndexerOnVersion({
1107+
config: this.config,
1108+
minimumLedgerVersion: args.minimumLedgerVersion,
1109+
processorType: ProcessorType.OBJECT_PROCESSOR,
1110+
});
1111+
return getAccountsForPublicKey({
1112+
aptosConfig: this.config,
1113+
...args,
1114+
});
1115+
}
9831116
}

src/api/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ export class Transaction {
523523
async rotateAuthKey(
524524
args: {
525525
fromAccount: Account;
526+
options?: InputGenerateTransactionOptions;
526527
} & (
527528
| { toAccount: Account; dangerouslySkipVerification?: never }
528529
| { toNewPrivateKey: Ed25519PrivateKey; dangerouslySkipVerification?: never }

src/core/crypto/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export * from "./publicKey";
1515
export * from "./secp256k1";
1616
export * from "./signature";
1717
export * from "./singleKey";
18+
export * from "./types";
1819
export * from "./deserializationUtils";

src/core/crypto/multiEd25519.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ export class MultiEd25519PublicKey extends AbstractMultiKey {
9292
this.threshold = threshold;
9393
}
9494

95+
getSignaturesRequired(): number {
96+
return this.threshold;
97+
}
98+
9599
// region AccountPublicKey
96100

97101
/**
@@ -217,6 +221,28 @@ export class MultiEd25519PublicKey extends AbstractMultiKey {
217221
return new MultiEd25519PublicKey({ publicKeys: keys, threshold });
218222
}
219223

224+
/**
225+
* Deserializes a MultiEd25519Signature from the provided deserializer.
226+
* This function helps in reconstructing a MultiEd25519Signature object from its serialized byte representation.
227+
*
228+
* @param deserializer - The deserializer instance used to read the serialized data.
229+
* @group Implementation
230+
* @category Serialization
231+
*/
232+
static deserializeWithoutLength(deserializer: Deserializer): MultiEd25519PublicKey {
233+
const length = deserializer.remaining();
234+
const bytes = deserializer.deserializeFixedBytes(length);
235+
const threshold = bytes[bytes.length - 1];
236+
237+
const keys: Ed25519PublicKey[] = [];
238+
239+
for (let i = 0; i < bytes.length - 1; i += Ed25519PublicKey.LENGTH) {
240+
const begin = i;
241+
keys.push(new Ed25519PublicKey(bytes.subarray(begin, begin + Ed25519PublicKey.LENGTH)));
242+
}
243+
return new MultiEd25519PublicKey({ publicKeys: keys, threshold });
244+
}
245+
220246
// endregion
221247

222248
/**

src/core/crypto/multiKey.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export abstract class AbstractMultiKey extends AccountPublicKey {
9696
}
9797
throw new Error(`Public key ${publicKey} not found in multi key set ${this.publicKeys}`);
9898
}
99+
100+
abstract getSignaturesRequired(): number;
99101
}
100102

101103
/**
@@ -175,6 +177,10 @@ export class MultiKey extends AbstractMultiKey {
175177
this.signaturesRequired = signaturesRequired;
176178
}
177179

180+
getSignaturesRequired(): number {
181+
return this.signaturesRequired;
182+
}
183+
178184
// endregion
179185

180186
// region AccountPublicKey

src/core/crypto/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { AnyPublicKey } from "./singleKey";
2+
import { Ed25519PublicKey } from "./ed25519";
3+
import { MultiKey } from "./multiKey";
4+
import { MultiEd25519PublicKey } from "./multiEd25519";
5+
6+
// This type is used to represent the base from of an account's public key.
7+
// These are the types of public keys that can be used to derive an account's address by appending
8+
// the signing scheme to the public key as bytes and hashing it.
9+
export type BaseAccountPublicKey = Ed25519PublicKey | AnyPublicKey | MultiKey | MultiEd25519PublicKey;

0 commit comments

Comments
 (0)