Skip to content
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
- Add MultiKey (K-of-N mixed key types) transfer example (`examples/typescript/multikey_transfer.ts`)
- Add MultiEd25519 (K-of-N Ed25519) transfer example (`examples/typescript/multi_ed25519_transfer.ts`)

## Changed

- **BREAKING**: Dynamically load `poseidon-lite` to reduce initial bundle size by ~421KB (minified+gzipped) for applications that don't use Keyless accounts
- `poseidon-lite` is now loaded on-demand via dynamic `import()`, enabling bundlers to code-split it into a separate chunk
- `poseidonHash()`, `hashStrToField()`, `KeylessPublicKey.create()`, `KeylessPublicKey.fromJwtAndPepper()`, `FederatedKeylessPublicKey.create()`, `FederatedKeylessPublicKey.fromJwtAndPepper()`, `verifyKeylessSignatureWithJwkAndConfig()`, `KeylessPublicKey.verifySignature()`, `FederatedKeylessPublicKey.verifySignature()`, and `MoveJWK.toScalar()` are now async and auto-load poseidon on first call
- Sync variants (`poseidonHashSync`, `hashStrToFieldSync`, `KeylessPublicKey.createSync`, `FederatedKeylessPublicKey.createSync`) are available for constructors/deserialization after poseidon has been loaded
- `EphemeralKeyPair.generate()` is now async (returns `Promise<EphemeralKeyPair>`)
- `ensurePoseidonLoaded()` is exported for explicit pre-loading if needed

# 6.2.0 (2026-03-22)

## Fixed
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/federated_keyless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const example = async () => {
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const ephemeralKeyPair = EphemeralKeyPair.generate();
const ephemeralKeyPair = await EphemeralKeyPair.generate();

console.log("\n=== Federated Keyless Account Example ===\n");

Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/jwk_update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const example = async () => {
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const ephemeralKeyPair = EphemeralKeyPair.generate();
const ephemeralKeyPair = await EphemeralKeyPair.generate();

console.log("\n=== Federated Keyless JWK Installation ===\n");

Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/keyless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const example = async () => {
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const aliceEphem = EphemeralKeyPair.generate();
const aliceEphem = await EphemeralKeyPair.generate();

console.log("\n=== Keyless Account Example ===\n");

Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/keyless_mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const example = async () => {
const aptos = new Aptos(config);

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const aliceEphem = EphemeralKeyPair.generate();
const aliceEphem = await EphemeralKeyPair.generate();

console.log("\n=== Keyless Account Example (Mainnet) ===\n");

Expand Down
26 changes: 21 additions & 5 deletions src/account/EphemeralKeyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { randomBytes } from "@noble/hashes/utils";
import {
bytesToBigIntLE,
padAndPackBytesWithLen,
poseidonHash,
poseidonHashSync,
ensurePoseidonLoaded,
Ed25519PrivateKey,
EphemeralPublicKey,
EphemeralSignature,
Expand Down Expand Up @@ -101,8 +102,16 @@ export class EphemeralKeyPair extends Serializable {
const fields = padAndPackBytesWithLen(this.publicKey.bcsToBytes(), 93);
fields.push(BigInt(this.expiryDateSecs));
fields.push(bytesToBigIntLE(this.blinder));
const nonceHash = poseidonHash(fields);
this.nonce = nonceHash.toString();
try {
const nonceHash = poseidonHashSync(fields);
this.nonce = nonceHash.toString();
} catch (error) {
const message =
"Failed to compute EphemeralKeyPair nonce. Ensure Poseidon has been initialized via ensurePoseidonLoaded() " +
"before constructing or deserializing EphemeralKeyPair instances. Original error: " +
(error instanceof Error ? error.message : String(error));
throw new Error(message);
}
}

/**
Expand Down Expand Up @@ -223,14 +232,21 @@ export class EphemeralKeyPair extends Serializable {
* Generates a new ephemeral key pair with an optional expiry date.
* This function allows you to create a temporary key pair for secure operations.
*
* This method is async because it ensures the poseidon-lite module is loaded before computing
* the nonce. This allows bundlers to code-split poseidon-lite out of the main bundle.
*
* @param args - Optional parameters for key pair generation.
* @param args.scheme - The type of key pair to use for the EphemeralKeyPair. Only Ed25519 is supported for now.
* @param args.expiryDateSecs - The date of expiry for the key pair in seconds.
* @returns An instance of EphemeralKeyPair containing the generated private key and expiry date.
* @returns A Promise that resolves to an EphemeralKeyPair containing the generated private key and expiry date.
* @group Implementation
* @category Account (On-Chain Model)
*/
static generate(args?: { scheme?: EphemeralPublicKeyVariant; expiryDateSecs?: number }): EphemeralKeyPair {
static async generate(args?: {
scheme?: EphemeralPublicKeyVariant;
expiryDateSecs?: number;
}): Promise<EphemeralKeyPair> {
await ensurePoseidonLoaded();
let privateKey: PrivateKey;

switch (args?.scheme) {
Expand Down
6 changes: 5 additions & 1 deletion src/account/FederatedKeylessAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
* Creates a KeylessAccount instance using the provided parameters.
* This function allows you to set up a KeylessAccount with specific attributes such as address, proof, and JWT.
*
* **Important**: This constructor uses `FederatedKeylessPublicKey.createSync()` which requires
* poseidon-lite to already be loaded. Use `FederatedKeylessAccount.create()` (async) for the
* standard flow, or call `await ensurePoseidonLoaded()` before constructing/deserializing directly.
*
* @param args - The parameters for creating a KeylessAccount.
* @param args.address - Optional account address associated with the KeylessAccount.
* @param args.proof - A Zero Knowledge Signature or a promise that resolves to one.
Expand All @@ -62,7 +66,7 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
verificationKeyHash?: HexInput;
audless?: boolean;
}) {
const publicKey = FederatedKeylessPublicKey.create(args);
const publicKey = FederatedKeylessPublicKey.createSync(args);
super({ publicKey, ...args });
this.publicKey = publicKey;
this.audless = args.audless ?? false;
Comment on lines 68 to 72
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

Using FederatedKeylessPublicKey.createSync() in the constructor means FederatedKeylessAccount.deserialize() (and any direct construction) will now fail unless poseidon-lite has already been loaded. Consider adding explicit docs on this, and/or an async deserialization/factory API that preloads poseidon via ensurePoseidonLoaded() before constructing.

Copilot uses AI. Check for mistakes.
Expand Down
6 changes: 5 additions & 1 deletion src/account/KeylessAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export class KeylessAccount extends AbstractKeylessAccount {
* Use the static generator `create(...)` instead.
* Creates an instance of the KeylessAccount with an optional proof.
*
* **Important**: This constructor uses `KeylessPublicKey.createSync()` which requires
* poseidon-lite to already be loaded. Use `KeylessAccount.create()` (async) for the
* standard flow, or call `await ensurePoseidonLoaded()` before constructing/deserializing directly.
*
* @param args - The parameters for creating a KeylessAccount.
* @param args.address - Optional account address associated with the KeylessAccount.
* @param args.ephemeralKeyPair - The ephemeral key pair used in the account creation.
Expand Down Expand Up @@ -64,7 +68,7 @@ export class KeylessAccount extends AbstractKeylessAccount {
jwt: string;
verificationKeyHash?: HexInput;
}) {
const publicKey = KeylessPublicKey.create(args);
const publicKey = KeylessPublicKey.createSync(args);
super({ publicKey, ...args });
this.publicKey = publicKey;
Comment on lines 70 to 73
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

Switching the constructor to KeylessPublicKey.createSync() means KeylessAccount.deserialize() (and any direct new KeylessAccount(...)) will now throw unless poseidon-lite has already been preloaded. Consider documenting this requirement prominently on the constructor/deserialize path, and/or providing an async deserializeAsync/fromBytesAsync that calls ensurePoseidonLoaded() before constructing.

Copilot uses AI. Check for mistakes.
}
Expand Down
46 changes: 27 additions & 19 deletions src/core/crypto/federatedKeyless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@ import { Deserializer, Serializer } from "../../bcs";
import { HexInput, AnyPublicKeyVariant, SigningScheme } from "../../types";
import { AuthenticationKey } from "../authenticationKey";
import { AccountAddress, AccountAddressInput } from "../accountAddress";
import {
KeylessConfiguration,
KeylessPublicKey,
KeylessSignature,
MoveJWK,
verifyKeylessSignature,
verifyKeylessSignatureWithJwkAndConfig,
} from "./keyless";
import { KeylessConfiguration, KeylessPublicKey, KeylessSignature, MoveJWK, verifyKeylessSignature } from "./keyless";
import { AptosConfig } from "../../api";
import { Signature } from "..";

Expand Down Expand Up @@ -75,18 +68,22 @@ export class FederatedKeylessPublicKey extends AccountPublicKey {
* @group Implementation
* @category Serialization
*/
/**
* @deprecated This method always throws. Use {@link verifySignatureAsync} or
* {@link verifyKeylessSignatureWithJwkAndConfig} (now async) instead.
* @group Implementation
* @category Serialization
*/
verifySignature(args: {
message: HexInput;
signature: Signature;
jwk: MoveJWK;
keylessConfig: KeylessConfiguration;
}): boolean {
try {
verifyKeylessSignatureWithJwkAndConfig({ ...args, publicKey: this });
return true;
} catch {
return false;
}
throw new Error(
"FederatedKeylessPublicKey.verifySignature is no longer synchronous. Use verifySignatureAsync() instead, " +
"or call verifyKeylessSignatureWithJwkAndConfig() directly (which is now async).",
);
}

serialize(serializer: Serializer): void {
Expand Down Expand Up @@ -138,24 +135,35 @@ export class FederatedKeylessPublicKey extends AccountPublicKey {
* @group Implementation
* @category Serialization
*/
static create(args: {
static async create(args: {
iss: string;
uidKey: string;
uidVal: string;
aud: string;
pepper: HexInput;
jwkAddress: AccountAddressInput;
}): Promise<FederatedKeylessPublicKey> {
return new FederatedKeylessPublicKey(args.jwkAddress, await KeylessPublicKey.create(args));
}

static createSync(args: {
iss: string;
uidKey: string;
uidVal: string;
aud: string;
pepper: HexInput;
jwkAddress: AccountAddressInput;
}): FederatedKeylessPublicKey {
return new FederatedKeylessPublicKey(args.jwkAddress, KeylessPublicKey.create(args));
return new FederatedKeylessPublicKey(args.jwkAddress, KeylessPublicKey.createSync(args));
}

static fromJwtAndPepper(args: {
static async fromJwtAndPepper(args: {
jwt: string;
pepper: HexInput;
jwkAddress: AccountAddressInput;
uidKey?: string;
}): FederatedKeylessPublicKey {
return new FederatedKeylessPublicKey(args.jwkAddress, KeylessPublicKey.fromJwtAndPepper(args));
}): Promise<FederatedKeylessPublicKey> {
return new FederatedKeylessPublicKey(args.jwkAddress, await KeylessPublicKey.fromJwtAndPepper(args));
}

static isInstance(publicKey: PublicKey) {
Expand Down
Loading
Loading