@aptos-labs/ts-sdk 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account/AbstractKeylessAccount.d.ts.map +1 -1
- package/dist/account/AbstractKeylessAccount.js +3 -0
- package/dist/account/AbstractKeylessAccount.js.map +1 -1
- package/dist/account/EphemeralKeyPair.d.ts +29 -6
- package/dist/account/EphemeralKeyPair.d.ts.map +1 -1
- package/dist/account/EphemeralKeyPair.js +35 -8
- package/dist/account/EphemeralKeyPair.js.map +1 -1
- package/dist/bcs/deserializer.d.ts.map +1 -1
- package/dist/bcs/deserializer.js +15 -0
- package/dist/bcs/deserializer.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/localNode.d.ts.map +1 -1
- package/dist/cli/localNode.js +6 -0
- package/dist/cli/localNode.js.map +1 -1
- package/dist/cli/move.d.ts.map +1 -1
- package/dist/cli/move.js +8 -0
- package/dist/cli/move.js.map +1 -1
- package/dist/cli/spawnArgs.d.ts +12 -0
- package/dist/cli/spawnArgs.d.ts.map +1 -0
- package/dist/cli/spawnArgs.js +51 -0
- package/dist/cli/spawnArgs.js.map +1 -0
- package/dist/core/crypto/ed25519.d.ts +117 -4
- package/dist/core/crypto/ed25519.d.ts.map +1 -1
- package/dist/core/crypto/ed25519.js +128 -14
- package/dist/core/crypto/ed25519.js.map +1 -1
- package/dist/core/crypto/keyless.d.ts +14 -0
- package/dist/core/crypto/keyless.d.ts.map +1 -1
- package/dist/core/crypto/keyless.js +22 -3
- package/dist/core/crypto/keyless.js.map +1 -1
- package/dist/core/crypto/poseidon.js +5 -5
- package/dist/core/crypto/poseidon.js.map +1 -1
- package/dist/core/crypto/secp256k1.d.ts +123 -5
- package/dist/core/crypto/secp256k1.d.ts.map +1 -1
- package/dist/core/crypto/secp256k1.js +137 -13
- package/dist/core/crypto/secp256k1.js.map +1 -1
- package/dist/core/crypto/secp256r1.d.ts +121 -1
- package/dist/core/crypto/secp256r1.d.ts.map +1 -1
- package/dist/core/crypto/secp256r1.js +156 -9
- package/dist/core/crypto/secp256r1.js.map +1 -1
- package/dist/core/crypto/utils.d.ts +28 -1
- package/dist/core/crypto/utils.d.ts.map +1 -1
- package/dist/core/crypto/utils.js +28 -1
- package/dist/core/crypto/utils.js.map +1 -1
- package/dist/errors/index.d.ts +19 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +35 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/internal/account.d.ts +17 -0
- package/dist/internal/account.d.ts.map +1 -1
- package/dist/internal/account.js +66 -17
- package/dist/internal/account.js.map +1 -1
- package/dist/internal/keyless.d.ts.map +1 -1
- package/dist/internal/keyless.js +86 -2
- package/dist/internal/keyless.js.map +1 -1
- package/dist/internal/transaction.d.ts.map +1 -1
- package/dist/internal/transaction.js +20 -3
- package/dist/internal/transaction.js.map +1 -1
- package/dist/transactions/transactionBuilder/encryptPayload.d.ts.map +1 -1
- package/dist/transactions/transactionBuilder/encryptPayload.js +42 -41
- package/dist/transactions/transactionBuilder/encryptPayload.js.map +1 -1
- package/dist/transactions/types.d.ts +17 -13
- package/dist/transactions/types.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts +16 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +29 -0
- package/dist/utils/helpers.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/account/AbstractKeylessAccount.ts +3 -0
- package/src/account/EphemeralKeyPair.ts +35 -8
- package/src/bcs/deserializer.ts +16 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/localNode.ts +7 -0
- package/src/cli/move.ts +9 -0
- package/src/cli/spawnArgs.ts +55 -0
- package/src/core/crypto/ed25519.ts +132 -15
- package/src/core/crypto/keyless.ts +22 -3
- package/src/core/crypto/poseidon.ts +5 -5
- package/src/core/crypto/secp256k1.ts +141 -13
- package/src/core/crypto/secp256r1.ts +164 -11
- package/src/core/crypto/utils.ts +28 -1
- package/src/errors/index.ts +37 -0
- package/src/internal/account.ts +73 -17
- package/src/internal/keyless.ts +88 -2
- package/src/internal/transaction.ts +22 -3
- package/src/transactions/transactionBuilder/encryptPayload.ts +56 -49
- package/src/transactions/types.ts +17 -13
- package/src/utils/helpers.ts +33 -0
- package/src/version.ts +1 -1
package/src/internal/account.ts
CHANGED
|
@@ -83,6 +83,7 @@ import { Hex } from "../core/hex.js";
|
|
|
83
83
|
import { CurrentFungibleAssetBalancesBoolExp } from "../types/generated/types.js";
|
|
84
84
|
import { getTableItem } from "./table.js";
|
|
85
85
|
import { APTOS_COIN } from "../utils/index.js";
|
|
86
|
+
import { memoizeAsync } from "../utils/memoize.js";
|
|
86
87
|
import { AptosApiError } from "../errors/index.js";
|
|
87
88
|
import { Deserializer, U8, MoveVector } from "../bcs/index.js";
|
|
88
89
|
import { generateTransaction } from "./transactionSubmission.js";
|
|
@@ -381,6 +382,45 @@ export async function lookupOriginalAccountAddress(args: {
|
|
|
381
382
|
}
|
|
382
383
|
}
|
|
383
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Fetches the on-chain `authentication_key` for an account address and memoizes it for ~1 hour,
|
|
387
|
+
* keyed by `(network or fullnode URL, address)`. Used by the encrypted-transaction builder to
|
|
388
|
+
* derive auth keys when the caller does not pass them explicitly. Callers that just rotated their
|
|
389
|
+
* key and need an immediate fresh read should pass the auth key explicitly instead of relying on
|
|
390
|
+
* this cache.
|
|
391
|
+
*
|
|
392
|
+
* If the address has no `0x1::account::Account` resource on chain (a brand-new account, or a light
|
|
393
|
+
* account with balance/objects but no explicit resource), returns the address bytes as the
|
|
394
|
+
* authentication key — matching the chain's account-creation convention (see
|
|
395
|
+
* [`doesAccountExistAtAddress`]). This makes encrypted-transaction builds work for not-yet-created
|
|
396
|
+
* signers (e.g., fee-payer sponsorship of an uncreated sender).
|
|
397
|
+
*/
|
|
398
|
+
export async function fetchAndCacheAuthKeyForAddress(args: {
|
|
399
|
+
aptosConfig: AptosConfig;
|
|
400
|
+
accountAddress: AccountAddressInput;
|
|
401
|
+
}): Promise<AuthenticationKey> {
|
|
402
|
+
const { aptosConfig, accountAddress } = args;
|
|
403
|
+
const address = AccountAddress.from(accountAddress);
|
|
404
|
+
const addr = address.toString();
|
|
405
|
+
const cacheKey = `auth-key-${aptosConfig.fullnode ?? aptosConfig.network}-${addr}`;
|
|
406
|
+
return memoizeAsync(
|
|
407
|
+
async () => {
|
|
408
|
+
try {
|
|
409
|
+
const info = await getInfoUtil({ aptosConfig, accountAddress: addr });
|
|
410
|
+
return new AuthenticationKey({ data: info.authentication_key });
|
|
411
|
+
} catch (err) {
|
|
412
|
+
if (err instanceof AptosApiError && err.data?.error_code === "account_not_found") {
|
|
413
|
+
// Chain convention: with no Account resource the auth key is the address itself.
|
|
414
|
+
return new AuthenticationKey({ data: address.toUint8Array() });
|
|
415
|
+
}
|
|
416
|
+
throw err;
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
cacheKey,
|
|
420
|
+
60 * 60 * 1000,
|
|
421
|
+
)();
|
|
422
|
+
}
|
|
423
|
+
|
|
384
424
|
/**
|
|
385
425
|
* Retrieves the count of tokens owned by a specific account address.
|
|
386
426
|
*
|
|
@@ -958,17 +998,27 @@ async function doesAccountExistAtAddress(args: {
|
|
|
958
998
|
}
|
|
959
999
|
}
|
|
960
1000
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1001
|
+
// Lazy: instantiating TypeTagU8 at module-init time creates an ESM circular
|
|
1002
|
+
// import (structEnumParser → internal/account → transactions/index → typeTag)
|
|
1003
|
+
// where TypeTagU8 may not yet be a constructor when this file evaluates.
|
|
1004
|
+
// Building the ABI on first use sidesteps the order dependency.
|
|
1005
|
+
let _rotateAuthKeyAbi: EntryFunctionABI | undefined;
|
|
1006
|
+
function rotateAuthKeyAbi(): EntryFunctionABI {
|
|
1007
|
+
if (!_rotateAuthKeyAbi) {
|
|
1008
|
+
_rotateAuthKeyAbi = {
|
|
1009
|
+
typeParameters: [],
|
|
1010
|
+
parameters: [
|
|
1011
|
+
new TypeTagU8(),
|
|
1012
|
+
TypeTagVector.u8(),
|
|
1013
|
+
new TypeTagU8(),
|
|
1014
|
+
TypeTagVector.u8(),
|
|
1015
|
+
TypeTagVector.u8(),
|
|
1016
|
+
TypeTagVector.u8(),
|
|
1017
|
+
],
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
return _rotateAuthKeyAbi;
|
|
1021
|
+
}
|
|
972
1022
|
|
|
973
1023
|
/**
|
|
974
1024
|
* Rotates the authentication key for a given account.
|
|
@@ -1065,16 +1115,22 @@ async function rotateAuthKeyWithChallenge(
|
|
|
1065
1115
|
MoveVector.U8(proofSignedByCurrentKey.toUint8Array()),
|
|
1066
1116
|
MoveVector.U8(proofSignedByNewKey.toUint8Array()),
|
|
1067
1117
|
],
|
|
1068
|
-
abi: rotateAuthKeyAbi,
|
|
1118
|
+
abi: rotateAuthKeyAbi(),
|
|
1069
1119
|
},
|
|
1070
1120
|
options,
|
|
1071
1121
|
});
|
|
1072
1122
|
}
|
|
1073
1123
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1124
|
+
let _rotateAuthKeyUnverifiedAbi: EntryFunctionABI | undefined;
|
|
1125
|
+
function rotateAuthKeyUnverifiedAbi(): EntryFunctionABI {
|
|
1126
|
+
if (!_rotateAuthKeyUnverifiedAbi) {
|
|
1127
|
+
_rotateAuthKeyUnverifiedAbi = {
|
|
1128
|
+
typeParameters: [],
|
|
1129
|
+
parameters: [new TypeTagU8(), TypeTagVector.u8()],
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
return _rotateAuthKeyUnverifiedAbi;
|
|
1133
|
+
}
|
|
1078
1134
|
|
|
1079
1135
|
/**
|
|
1080
1136
|
* Rotates the authentication key for a given account without verifying the new key.
|
|
@@ -1105,7 +1161,7 @@ export async function rotateAuthKeyUnverified(args: {
|
|
|
1105
1161
|
new U8(accountPublicKeyToSigningScheme(toNewPublicKey)), // to scheme
|
|
1106
1162
|
MoveVector.U8(accountPublicKeyToBaseAccountPublicKey(toNewPublicKey).toUint8Array()),
|
|
1107
1163
|
],
|
|
1108
|
-
abi: rotateAuthKeyUnverifiedAbi,
|
|
1164
|
+
abi: rotateAuthKeyUnverifiedAbi(),
|
|
1109
1165
|
},
|
|
1110
1166
|
options,
|
|
1111
1167
|
});
|
package/src/internal/keyless.ts
CHANGED
|
@@ -108,6 +108,12 @@ export async function getProof(args: {
|
|
|
108
108
|
if (Hex.fromHexInput(pepper).toUint8Array().length !== KeylessAccount.PEPPER_LENGTH) {
|
|
109
109
|
throw new Error(`Pepper needs to be ${KeylessAccount.PEPPER_LENGTH} bytes`);
|
|
110
110
|
}
|
|
111
|
+
// SECURITY: jwtDecode does NOT verify the JWT signature. The prover service
|
|
112
|
+
// is the next hop and will reject a tampered JWT, and the on-chain keyless
|
|
113
|
+
// verifier validates the signature against the JWK set published on-chain.
|
|
114
|
+
// Callers must still source `jwt` from a trusted IdP redirect flow — accepting
|
|
115
|
+
// a user-supplied JWT here will produce a useless proof, not a forged one,
|
|
116
|
+
// but it also leaks the (unverified) claims to the prover.
|
|
111
117
|
const decodedJwt = jwtDecode<JwtPayload>(jwt);
|
|
112
118
|
if (typeof decodedJwt.iat !== "number") {
|
|
113
119
|
throw new Error("iat was not found");
|
|
@@ -261,6 +267,28 @@ export async function updateFederatedKeylessJwkSetTransaction(args: {
|
|
|
261
267
|
}
|
|
262
268
|
}
|
|
263
269
|
|
|
270
|
+
// SSRF guard: require HTTPS. Without this check a caller-supplied `iss` or
|
|
271
|
+
// `jwksUrl` could redirect the fetch to plaintext HTTP, cloud-metadata
|
|
272
|
+
// endpoints (e.g., `http://169.254.169.254/...`), internal services, or
|
|
273
|
+
// non-network schemes like `file:` / `data:`. The on-chain JWKS update is
|
|
274
|
+
// a privileged operation, so we refuse to source key material over an
|
|
275
|
+
// untrusted transport.
|
|
276
|
+
let parsedJwksUrl: URL;
|
|
277
|
+
try {
|
|
278
|
+
parsedJwksUrl = new URL(jwksUrl);
|
|
279
|
+
} catch {
|
|
280
|
+
throw KeylessError.fromErrorType({
|
|
281
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
282
|
+
details: "JWKS URL is not a valid URL",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (parsedJwksUrl.protocol !== "https:") {
|
|
286
|
+
throw KeylessError.fromErrorType({
|
|
287
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
288
|
+
details: `JWKS URL must use https: (got ${parsedJwksUrl.protocol})`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
264
292
|
let response: Response;
|
|
265
293
|
|
|
266
294
|
try {
|
|
@@ -275,13 +303,19 @@ export async function updateFederatedKeylessJwkSetTransaction(args: {
|
|
|
275
303
|
} else {
|
|
276
304
|
errorMessage = `error unknown - ${error}`;
|
|
277
305
|
}
|
|
306
|
+
// Surface only the origin (scheme + host + port) of the JWKS URL in the
|
|
307
|
+
// user-facing error. The full URL, which may include `iss`-derived path
|
|
308
|
+
// segments or tenant identifiers from enterprise IdPs, is intentionally
|
|
309
|
+
// omitted to avoid leaking infrastructure details into logs / crash
|
|
310
|
+
// reporters.
|
|
278
311
|
throw KeylessError.fromErrorType({
|
|
279
312
|
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
280
|
-
details: `Failed to fetch JWKS
|
|
313
|
+
details: `Failed to fetch JWKS from ${parsedJwksUrl.origin}: ${errorMessage}`,
|
|
281
314
|
});
|
|
282
315
|
}
|
|
283
316
|
|
|
284
|
-
const
|
|
317
|
+
const rawJwks: unknown = await response.json();
|
|
318
|
+
const jwks = validateJwksResponse(rawJwks, parsedJwksUrl.origin);
|
|
285
319
|
return generateTransaction({
|
|
286
320
|
aptosConfig,
|
|
287
321
|
sender: sender.accountAddress,
|
|
@@ -298,3 +332,55 @@ export async function updateFederatedKeylessJwkSetTransaction(args: {
|
|
|
298
332
|
options,
|
|
299
333
|
});
|
|
300
334
|
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Caller can supply any IdP URL, so the JWKS response is untrusted. The shape
|
|
338
|
+
* isn't enforced by the TS cast above, and `jwks.keys.map(...)` would throw a
|
|
339
|
+
* confusing `TypeError: Cannot read properties of ... 'map'` on malformed
|
|
340
|
+
* payloads. Worse, a hostile/buggy IdP could return an unboundedly large
|
|
341
|
+
* `keys` array and we'd pack the whole thing into the on-chain transaction.
|
|
342
|
+
*
|
|
343
|
+
* Validate the four fields we actually use (kid, alg, e, n), cap the key
|
|
344
|
+
* count, and surface a single descriptive error when anything is off.
|
|
345
|
+
*/
|
|
346
|
+
const MAX_FEDERATED_JWKS_KEYS = 32;
|
|
347
|
+
|
|
348
|
+
function validateJwksResponse(raw: unknown, originForError: string): JWKS {
|
|
349
|
+
if (raw === null || typeof raw !== "object" || !Array.isArray((raw as { keys?: unknown }).keys)) {
|
|
350
|
+
throw KeylessError.fromErrorType({
|
|
351
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
352
|
+
details: `JWKS response from ${originForError} is missing a 'keys' array`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const keys = (raw as { keys: unknown[] }).keys;
|
|
356
|
+
if (keys.length === 0) {
|
|
357
|
+
throw KeylessError.fromErrorType({
|
|
358
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
359
|
+
details: `JWKS response from ${originForError} has an empty 'keys' array`,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (keys.length > MAX_FEDERATED_JWKS_KEYS) {
|
|
363
|
+
throw KeylessError.fromErrorType({
|
|
364
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
365
|
+
details: `JWKS response from ${originForError} has ${keys.length} keys (max ${MAX_FEDERATED_JWKS_KEYS})`,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
369
|
+
const key = keys[i];
|
|
370
|
+
if (key === null || typeof key !== "object") {
|
|
371
|
+
throw KeylessError.fromErrorType({
|
|
372
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
373
|
+
details: `JWKS response from ${originForError}: key at index ${i} is not an object`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
for (const field of ["kid", "alg", "e", "n"] as const) {
|
|
377
|
+
if (typeof (key as Record<string, unknown>)[field] !== "string") {
|
|
378
|
+
throw KeylessError.fromErrorType({
|
|
379
|
+
type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED,
|
|
380
|
+
details: `JWKS response from ${originForError}: key at index ${i} is missing string field '${field}'`,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return raw as JWKS;
|
|
386
|
+
}
|
|
@@ -200,6 +200,16 @@ export async function waitForTransaction(args: {
|
|
|
200
200
|
let backoffIntervalMs = 200;
|
|
201
201
|
const backoffMultiplier = 1.5;
|
|
202
202
|
|
|
203
|
+
// A response is "settled" when the fullnode has populated the execution result.
|
|
204
|
+
// There is a window where `type` flips to a committed variant (e.g. User) before
|
|
205
|
+
// `success`/`vm_status` are filled in — during that window we must keep polling,
|
|
206
|
+
// not treat the partial response as a failure.
|
|
207
|
+
function isUnsettled(txn: TransactionResponse | undefined): boolean {
|
|
208
|
+
if (txn === undefined) return true;
|
|
209
|
+
if (txn.type === TransactionResponseType.Pending) return true;
|
|
210
|
+
return (txn as { success?: boolean }).success === undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
203
213
|
/**
|
|
204
214
|
* Handles API errors by throwing the last error or a timeout error for a failed transaction.
|
|
205
215
|
*
|
|
@@ -223,7 +233,7 @@ export async function waitForTransaction(args: {
|
|
|
223
233
|
// check to see if the txn is already on the blockchain
|
|
224
234
|
try {
|
|
225
235
|
lastTxn = await getTransactionByHash({ aptosConfig, transactionHash });
|
|
226
|
-
isPending = lastTxn
|
|
236
|
+
isPending = isUnsettled(lastTxn);
|
|
227
237
|
} catch (e) {
|
|
228
238
|
handleAPIError(e);
|
|
229
239
|
}
|
|
@@ -233,7 +243,7 @@ export async function waitForTransaction(args: {
|
|
|
233
243
|
const startTime = Date.now();
|
|
234
244
|
try {
|
|
235
245
|
lastTxn = await longWaitForTransaction({ aptosConfig, transactionHash });
|
|
236
|
-
isPending = lastTxn
|
|
246
|
+
isPending = isUnsettled(lastTxn);
|
|
237
247
|
} catch (e) {
|
|
238
248
|
handleAPIError(e);
|
|
239
249
|
}
|
|
@@ -248,7 +258,7 @@ export async function waitForTransaction(args: {
|
|
|
248
258
|
try {
|
|
249
259
|
lastTxn = await getTransactionByHash({ aptosConfig, transactionHash });
|
|
250
260
|
|
|
251
|
-
isPending = lastTxn
|
|
261
|
+
isPending = isUnsettled(lastTxn);
|
|
252
262
|
|
|
253
263
|
if (!isPending) {
|
|
254
264
|
break;
|
|
@@ -280,6 +290,15 @@ export async function waitForTransaction(args: {
|
|
|
280
290
|
lastTxn,
|
|
281
291
|
);
|
|
282
292
|
}
|
|
293
|
+
// If we exited the loop with a committed-shaped response that hasn't been
|
|
294
|
+
// fully populated yet (success/vm_status still undefined), this is the
|
|
295
|
+
// indexer-lag race — surface it as a timeout, not as a failed transaction.
|
|
296
|
+
if ((lastTxn as { success?: boolean }).success === undefined) {
|
|
297
|
+
throw new WaitForTransactionError(
|
|
298
|
+
`Transaction ${transactionHash} did not finish indexing within ${timeoutSecs} seconds`,
|
|
299
|
+
lastTxn,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
283
302
|
if (!checkSuccess) {
|
|
284
303
|
return lastTxn;
|
|
285
304
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { AptosConfig } from "../../api/aptosConfig.js";
|
|
5
5
|
import { AccountAddress, AccountAddressInput } from "../../core/index.js";
|
|
6
6
|
import { AuthenticationKey } from "../../core/authenticationKey.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchAndCacheAuthKeyForAddress } from "../../internal/account.js";
|
|
8
8
|
import { fetchAndCacheEncryptionKey } from "../../internal/encryptionKey.js";
|
|
9
9
|
import {
|
|
10
10
|
ClaimedEntryFunction,
|
|
@@ -83,10 +83,11 @@ function resolveClaimedEntryFun(args: {
|
|
|
83
83
|
options: InputGenerateTransactionOptions;
|
|
84
84
|
}): ClaimedEntryFunction | undefined {
|
|
85
85
|
const { payload, feePayerAddress, options } = args;
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
// Unlike buildSignerAuthKeys, we treat a zero feePayerAddress (deferred gas-station sponsor) the
|
|
87
|
+
// same as a real one: a fee payer *will* sign, so include claimed_entry_fun so they can inspect
|
|
88
|
+
// the payload without decrypting it. The zero check in buildSignerAuthKeys is different — adding
|
|
89
|
+
// a placeholder zero auth key to the cryptographic AAD would corrupt it.
|
|
90
|
+
const hasFeePayer = feePayerAddress !== undefined;
|
|
90
91
|
if (!hasFeePayer && !payloadHasMultisigAddress(payload)) {
|
|
91
92
|
return undefined;
|
|
92
93
|
}
|
|
@@ -102,76 +103,81 @@ function resolveClaimedEntryFun(args: {
|
|
|
102
103
|
return undefined;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
function resolveAuthKey(input:
|
|
106
|
-
if (input instanceof
|
|
107
|
-
return input
|
|
106
|
+
function resolveAuthKey(input: AuthenticationKey | HexInput): AuthenticationKey {
|
|
107
|
+
if (input instanceof AuthenticationKey) {
|
|
108
|
+
return input;
|
|
108
109
|
}
|
|
109
110
|
return new AuthenticationKey({ data: input });
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
114
|
+
* Assembles `(address, authenticationKey)` pairs in `TransactionAuthenticator::all_signer_auth_keys` order
|
|
115
|
+
* (sender, secondaries, fee payer last). Auth keys not supplied in `options` are fetched from chain via
|
|
116
|
+
* `fetchAndCacheAuthKeyForAddress`, which caches per `(network, address)` for ~1 hour.
|
|
115
117
|
*/
|
|
116
|
-
function buildSignerAuthKeys(args: {
|
|
118
|
+
async function buildSignerAuthKeys(args: {
|
|
119
|
+
aptosConfig: AptosConfig;
|
|
117
120
|
sender: AccountAddress;
|
|
118
121
|
options: InputGenerateTransactionOptions;
|
|
119
122
|
feePayerAddress?: AccountAddressInput;
|
|
120
123
|
secondarySignerAddresses?: AccountAddressInput[];
|
|
121
|
-
}): { sender: SignerAuthKeyPair; additional: SignerAuthKeyPair[] | undefined } {
|
|
122
|
-
const { sender, options, feePayerAddress, secondarySignerAddresses } = args;
|
|
124
|
+
}): Promise<{ sender: SignerAuthKeyPair; additional: SignerAuthKeyPair[] | undefined }> {
|
|
125
|
+
const { aptosConfig, sender, options, feePayerAddress, secondarySignerAddresses } = args;
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
const secondaryAddrs = secondarySignerAddresses ?? [];
|
|
128
|
+
const secondaryAuthInputs = options.secondarySignerAuthenticationKeys;
|
|
129
|
+
if (secondaryAddrs.length === 0 && secondaryAuthInputs !== undefined && secondaryAuthInputs.length > 0) {
|
|
125
130
|
throw new Error(
|
|
126
|
-
"options.
|
|
127
|
-
"Pass the sender's AccountPublicKey or a raw 32-byte auth key hex string.",
|
|
131
|
+
"options.secondarySignerAuthenticationKeys was set but no secondarySignerAddresses were provided to generateRawTransaction.",
|
|
128
132
|
);
|
|
129
133
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"Encrypted multi-agent transactions require options.secondarySignerAuthenticationKeys with one entry per secondarySignerAddresses entry, in the same order. " +
|
|
136
|
-
"Each entry may be an AccountPublicKey or a raw 32-byte auth key hex string.",
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
} else if (secondaryAuthHex !== undefined && secondaryAuthHex.length > 0) {
|
|
134
|
+
if (
|
|
135
|
+
secondaryAddrs.length > 0 &&
|
|
136
|
+
secondaryAuthInputs !== undefined &&
|
|
137
|
+
secondaryAuthInputs.length !== secondaryAddrs.length
|
|
138
|
+
) {
|
|
140
139
|
throw new Error(
|
|
141
|
-
"options.secondarySignerAuthenticationKeys
|
|
140
|
+
"Encrypted multi-agent transactions require options.secondarySignerAuthenticationKeys (when provided) to have one entry per secondarySignerAddresses entry, in the same order. " +
|
|
141
|
+
"Leave individual entries undefined to fetch them from chain.",
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const feePayerAddr = feePayerAddress !== undefined ? AccountAddress.from(feePayerAddress) : undefined;
|
|
146
146
|
const hasNonZeroFeePayer = feePayerAddr !== undefined && !feePayerAddr.equals(AccountAddress.ZERO);
|
|
147
|
-
if (hasNonZeroFeePayer && options.feePayerAuthenticationKey === undefined) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
"options.feePayerAuthenticationKey is required when options.encrypted is true and feePayerAddress is a non-zero sponsor. " +
|
|
150
|
-
"Must match the fee payer authenticator; AAD order is sender, then secondaries, then fee payer (aptos-core `all_signer_auth_keys`).",
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
147
|
if (options.feePayerAuthenticationKey !== undefined && !hasNonZeroFeePayer) {
|
|
154
148
|
throw new Error(
|
|
155
149
|
"options.feePayerAuthenticationKey was set but feePayerAddress is missing or the zero address (no on-chain fee payer for AAD).",
|
|
156
150
|
);
|
|
157
151
|
}
|
|
158
152
|
|
|
159
|
-
const
|
|
160
|
-
address:
|
|
161
|
-
|
|
153
|
+
const resolveFor = async (
|
|
154
|
+
address: AccountAddress,
|
|
155
|
+
input: AuthenticationKey | HexInput | undefined,
|
|
156
|
+
): Promise<AuthenticationKey> => {
|
|
157
|
+
if (input !== undefined) {
|
|
158
|
+
return resolveAuthKey(input);
|
|
159
|
+
}
|
|
160
|
+
return fetchAndCacheAuthKeyForAddress({ aptosConfig, accountAddress: address });
|
|
162
161
|
};
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
162
|
+
|
|
163
|
+
const secondaryPairsPromise = Promise.all(
|
|
164
|
+
secondaryAddrs.map(async (addr, i) => {
|
|
165
|
+
const address = AccountAddress.from(addr);
|
|
166
|
+
const authenticationKey = await resolveFor(address, secondaryAuthInputs?.[i]);
|
|
167
|
+
return { address, authenticationKey };
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const [senderAuthKey, secondaryPairs, feePayerAuthKey] = await Promise.all([
|
|
172
|
+
resolveFor(sender, options.senderAuthenticationKey),
|
|
173
|
+
secondaryPairsPromise,
|
|
174
|
+
hasNonZeroFeePayer ? resolveFor(feePayerAddr, options.feePayerAuthenticationKey) : Promise.resolve(undefined),
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
const senderPair: SignerAuthKeyPair = { address: sender, authenticationKey: senderAuthKey };
|
|
178
|
+
const additional: SignerAuthKeyPair[] = [...secondaryPairs];
|
|
179
|
+
if (hasNonZeroFeePayer && feePayerAuthKey !== undefined) {
|
|
180
|
+
additional.push({ address: feePayerAddr, authenticationKey: feePayerAuthKey });
|
|
175
181
|
}
|
|
176
182
|
return { sender: senderPair, additional: additional.length > 0 ? additional : undefined };
|
|
177
183
|
}
|
|
@@ -196,7 +202,8 @@ export async function buildEncryptedPayload(args: {
|
|
|
196
202
|
args;
|
|
197
203
|
|
|
198
204
|
const senderAddr = AccountAddress.from(sender);
|
|
199
|
-
const { sender: senderPair, additional } = buildSignerAuthKeys({
|
|
205
|
+
const { sender: senderPair, additional } = await buildSignerAuthKeys({
|
|
206
|
+
aptosConfig,
|
|
200
207
|
sender: senderAddr,
|
|
201
208
|
options,
|
|
202
209
|
feePayerAddress,
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
U8,
|
|
20
20
|
} from "../bcs/serializable/movePrimitives.js";
|
|
21
21
|
import { FixedBytes } from "../bcs/serializable/fixedBytes.js";
|
|
22
|
-
import { AccountAddress, AccountAddressInput } from "../core/index.js";
|
|
23
|
-
import {
|
|
22
|
+
import { AccountAddress, AccountAddressInput, AuthenticationKey } from "../core/index.js";
|
|
23
|
+
import { PublicKey } from "../core/crypto/index.js";
|
|
24
24
|
import {
|
|
25
25
|
MultiAgentRawTransaction,
|
|
26
26
|
FeePayerRawTransaction,
|
|
@@ -182,23 +182,27 @@ export type InputEncryptedTransactionBuildOptions = {
|
|
|
182
182
|
*/
|
|
183
183
|
encrypted?: boolean;
|
|
184
184
|
/**
|
|
185
|
-
* Authentication key for the primary sender.
|
|
186
|
-
*
|
|
187
|
-
*
|
|
185
|
+
* Authentication key for the primary sender. Optional: when omitted (and `encrypted` is true), the SDK fetches
|
|
186
|
+
* the sender's `authentication_key` from the fullnode and caches it for ~1 hour. Pass it explicitly to skip the
|
|
187
|
+
* lookup (useful right after a key rotation). Accepts an `AuthenticationKey` or a raw 32-byte hex string /
|
|
188
|
+
* `Uint8Array`. Must match the on-chain authenticator identity (aptos-core
|
|
189
|
+
* `PayloadAssociatedData::V1.signer_auth_keys`).
|
|
188
190
|
*/
|
|
189
|
-
|
|
191
|
+
senderAuthenticationKey?: AuthenticationKey | HexInput;
|
|
190
192
|
/**
|
|
191
193
|
* For encrypted **multi-agent** transactions: each secondary signer's authentication key, in the same order
|
|
192
|
-
* as `secondarySignerAddresses` on the transaction build input.
|
|
194
|
+
* as `secondarySignerAddresses` on the transaction build input. Any entry left undefined (or the entire array
|
|
195
|
+
* omitted) will be fetched from chain and cached. Accepts `AuthenticationKey` or a raw 32-byte hex string /
|
|
196
|
+
* `Uint8Array`.
|
|
193
197
|
*/
|
|
194
|
-
secondarySignerAuthenticationKeys?: (HexInput |
|
|
198
|
+
secondarySignerAuthenticationKeys?: (AuthenticationKey | HexInput | undefined)[];
|
|
195
199
|
/**
|
|
196
|
-
* For encrypted **fee-payer** transactions: the fee payer's authentication key.
|
|
197
|
-
*
|
|
198
|
-
* matching aptos-core `TransactionAuthenticator::all_signer_auth_keys` (after sender and
|
|
199
|
-
* Accepts `
|
|
200
|
+
* For encrypted **fee-payer** transactions: the fee payer's authentication key. Optional when `feePayerAddress`
|
|
201
|
+
* is a **non-zero** sponsor — omitted values are fetched from chain and cached. Appended **last** in AAD
|
|
202
|
+
* `signer_auth_keys`, matching aptos-core `TransactionAuthenticator::all_signer_auth_keys` (after sender and
|
|
203
|
+
* secondaries). Accepts `AuthenticationKey` or a raw 32-byte hex string / `Uint8Array`.
|
|
200
204
|
*/
|
|
201
|
-
feePayerAuthenticationKey?:
|
|
205
|
+
feePayerAuthenticationKey?: AuthenticationKey | HexInput;
|
|
202
206
|
/**
|
|
203
207
|
* Overrides `claimed_entry_fun` for encrypted transactions when a fee payer is set, the payload is multisig, or the
|
|
204
208
|
* payload is `TransactionInnerPayload` with a multisig address in `TransactionExtraConfigV1`.
|
package/src/utils/helpers.ts
CHANGED
|
@@ -6,6 +6,39 @@ import { AccountAddress } from "../core/accountAddress.js";
|
|
|
6
6
|
import { createObjectAddress } from "../core/account/utils/address.js";
|
|
7
7
|
import { TEXT_ENCODER } from "./const.js";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Maximum bigint value that can be losslessly converted to a JS `number`.
|
|
11
|
+
* Equal to `BigInt(Number.MAX_SAFE_INTEGER)` (2^53 - 1).
|
|
12
|
+
*/
|
|
13
|
+
const MAX_SAFE_U64 = BigInt(Number.MAX_SAFE_INTEGER);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Narrows a u64 (`bigint`) into a JS `number` with an explicit safety check.
|
|
17
|
+
*
|
|
18
|
+
* The `deserializeU64` reader returns `bigint`, but several keyless code
|
|
19
|
+
* paths historically narrowed the value with `Number(value)` to fit existing
|
|
20
|
+
* `number`-typed fields (expiry timestamps, expiry horizons). For values
|
|
21
|
+
* larger than `Number.MAX_SAFE_INTEGER` (~9 × 10^15), `Number(bigint)`
|
|
22
|
+
* silently loses precision — comparisons against `Date.now() / 1000` then
|
|
23
|
+
* return wrong results.
|
|
24
|
+
*
|
|
25
|
+
* Real-world expiry values are far below the unsafe range (year ~285 million
|
|
26
|
+
* AD as a Unix timestamp), so this check is effectively a guard against
|
|
27
|
+
* corrupted or malicious BCS data rather than a precision concern in normal
|
|
28
|
+
* operation. Throwing is correct behavior at the BCS/JSON boundary.
|
|
29
|
+
*/
|
|
30
|
+
export function u64ToNumberSafe(value: bigint, fieldName: string): number {
|
|
31
|
+
if (value < 0n) {
|
|
32
|
+
throw new RangeError(`${fieldName} is negative (${value}); expected an unsigned u64`);
|
|
33
|
+
}
|
|
34
|
+
if (value > MAX_SAFE_U64) {
|
|
35
|
+
throw new RangeError(
|
|
36
|
+
`${fieldName} (${value}) exceeds Number.MAX_SAFE_INTEGER (${MAX_SAFE_U64}); refusing to silently lose precision`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return Number(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
9
42
|
/**
|
|
10
43
|
* Checks if the current runtime environment is Bun.
|
|
11
44
|
* This is useful for detecting Bun-specific compatibility issues.
|
package/src/version.ts
CHANGED