@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.
Files changed (93) hide show
  1. package/dist/account/AbstractKeylessAccount.d.ts.map +1 -1
  2. package/dist/account/AbstractKeylessAccount.js +3 -0
  3. package/dist/account/AbstractKeylessAccount.js.map +1 -1
  4. package/dist/account/EphemeralKeyPair.d.ts +29 -6
  5. package/dist/account/EphemeralKeyPair.d.ts.map +1 -1
  6. package/dist/account/EphemeralKeyPair.js +35 -8
  7. package/dist/account/EphemeralKeyPair.js.map +1 -1
  8. package/dist/bcs/deserializer.d.ts.map +1 -1
  9. package/dist/bcs/deserializer.js +15 -0
  10. package/dist/bcs/deserializer.js.map +1 -1
  11. package/dist/cli/index.d.ts +1 -0
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +1 -0
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/localNode.d.ts.map +1 -1
  16. package/dist/cli/localNode.js +6 -0
  17. package/dist/cli/localNode.js.map +1 -1
  18. package/dist/cli/move.d.ts.map +1 -1
  19. package/dist/cli/move.js +8 -0
  20. package/dist/cli/move.js.map +1 -1
  21. package/dist/cli/spawnArgs.d.ts +12 -0
  22. package/dist/cli/spawnArgs.d.ts.map +1 -0
  23. package/dist/cli/spawnArgs.js +51 -0
  24. package/dist/cli/spawnArgs.js.map +1 -0
  25. package/dist/core/crypto/ed25519.d.ts +117 -4
  26. package/dist/core/crypto/ed25519.d.ts.map +1 -1
  27. package/dist/core/crypto/ed25519.js +128 -14
  28. package/dist/core/crypto/ed25519.js.map +1 -1
  29. package/dist/core/crypto/keyless.d.ts +14 -0
  30. package/dist/core/crypto/keyless.d.ts.map +1 -1
  31. package/dist/core/crypto/keyless.js +22 -3
  32. package/dist/core/crypto/keyless.js.map +1 -1
  33. package/dist/core/crypto/poseidon.js +5 -5
  34. package/dist/core/crypto/poseidon.js.map +1 -1
  35. package/dist/core/crypto/secp256k1.d.ts +123 -5
  36. package/dist/core/crypto/secp256k1.d.ts.map +1 -1
  37. package/dist/core/crypto/secp256k1.js +137 -13
  38. package/dist/core/crypto/secp256k1.js.map +1 -1
  39. package/dist/core/crypto/secp256r1.d.ts +121 -1
  40. package/dist/core/crypto/secp256r1.d.ts.map +1 -1
  41. package/dist/core/crypto/secp256r1.js +156 -9
  42. package/dist/core/crypto/secp256r1.js.map +1 -1
  43. package/dist/core/crypto/utils.d.ts +28 -1
  44. package/dist/core/crypto/utils.d.ts.map +1 -1
  45. package/dist/core/crypto/utils.js +28 -1
  46. package/dist/core/crypto/utils.js.map +1 -1
  47. package/dist/errors/index.d.ts +19 -0
  48. package/dist/errors/index.d.ts.map +1 -1
  49. package/dist/errors/index.js +35 -0
  50. package/dist/errors/index.js.map +1 -1
  51. package/dist/internal/account.d.ts +17 -0
  52. package/dist/internal/account.d.ts.map +1 -1
  53. package/dist/internal/account.js +66 -17
  54. package/dist/internal/account.js.map +1 -1
  55. package/dist/internal/keyless.d.ts.map +1 -1
  56. package/dist/internal/keyless.js +86 -2
  57. package/dist/internal/keyless.js.map +1 -1
  58. package/dist/internal/transaction.d.ts.map +1 -1
  59. package/dist/internal/transaction.js +20 -3
  60. package/dist/internal/transaction.js.map +1 -1
  61. package/dist/transactions/transactionBuilder/encryptPayload.d.ts.map +1 -1
  62. package/dist/transactions/transactionBuilder/encryptPayload.js +42 -41
  63. package/dist/transactions/transactionBuilder/encryptPayload.js.map +1 -1
  64. package/dist/transactions/types.d.ts +17 -13
  65. package/dist/transactions/types.d.ts.map +1 -1
  66. package/dist/utils/helpers.d.ts +16 -0
  67. package/dist/utils/helpers.d.ts.map +1 -1
  68. package/dist/utils/helpers.js +29 -0
  69. package/dist/utils/helpers.js.map +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.js +1 -1
  72. package/package.json +3 -2
  73. package/src/account/AbstractKeylessAccount.ts +3 -0
  74. package/src/account/EphemeralKeyPair.ts +35 -8
  75. package/src/bcs/deserializer.ts +16 -0
  76. package/src/cli/index.ts +1 -0
  77. package/src/cli/localNode.ts +7 -0
  78. package/src/cli/move.ts +9 -0
  79. package/src/cli/spawnArgs.ts +55 -0
  80. package/src/core/crypto/ed25519.ts +132 -15
  81. package/src/core/crypto/keyless.ts +22 -3
  82. package/src/core/crypto/poseidon.ts +5 -5
  83. package/src/core/crypto/secp256k1.ts +141 -13
  84. package/src/core/crypto/secp256r1.ts +164 -11
  85. package/src/core/crypto/utils.ts +28 -1
  86. package/src/errors/index.ts +37 -0
  87. package/src/internal/account.ts +73 -17
  88. package/src/internal/keyless.ts +88 -2
  89. package/src/internal/transaction.ts +22 -3
  90. package/src/transactions/transactionBuilder/encryptPayload.ts +56 -49
  91. package/src/transactions/types.ts +17 -13
  92. package/src/utils/helpers.ts +33 -0
  93. package/src/version.ts +1 -1
@@ -12,6 +12,7 @@ import { PrivateKey } from "./privateKey.js";
12
12
  import { PublicKey } from "./publicKey.js";
13
13
  import { Signature } from "./signature.js";
14
14
  import { convertSigningMessage } from "./utils.js";
15
+ import { TEXT_ENCODER } from "../../utils/const.js";
15
16
  import { AptosConfig } from "../../api/aptosConfig.js";
16
17
 
17
18
  /**
@@ -62,10 +63,54 @@ export class Secp256k1PublicKey extends PublicKey {
62
63
  }
63
64
 
64
65
  // region PublicKey
66
+ /**
67
+ * Verifies a signature against the exact bytes of `message`. This is the
68
+ * unambiguous form — the input is interpreted as raw bytes regardless of
69
+ * what they encode. Pair with {@link Secp256k1PrivateKey.signBytes}.
70
+ *
71
+ * The message is SHA3-256 hashed before verification (matching the
72
+ * Aptos-side Secp256k1 signing convention), and the signature is required
73
+ * to be in canonical low-S form for malleability resistance.
74
+ *
75
+ * @param args - The arguments for verification.
76
+ * @param args.message - The exact bytes that were signed.
77
+ * @param args.signature - The signature to verify.
78
+ * @group Implementation
79
+ * @category Serialization
80
+ */
81
+ verifyBytes(args: { message: Uint8Array; signature: Secp256k1Signature }): boolean {
82
+ const { message, signature } = args;
83
+ const messageSha3Bytes = sha3_256(message);
84
+ return secp256k1.verify(signature.toUint8Array(), messageSha3Bytes, this.key.toUint8Array(), {
85
+ lowS: true,
86
+ prehash: false,
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Verifies a signature against the UTF-8 encoding of `message`. The input
92
+ * is always treated as text — there is no hex/text heuristic. Pair with
93
+ * {@link Secp256k1PrivateKey.signText}.
94
+ *
95
+ * @param args - The arguments for verification.
96
+ * @param args.message - The text that was signed.
97
+ * @param args.signature - The signature to verify.
98
+ * @group Implementation
99
+ * @category Serialization
100
+ */
101
+ verifyText(args: { message: string; signature: Secp256k1Signature }): boolean {
102
+ return this.verifyBytes({ message: TEXT_ENCODER.encode(args.message), signature: args.signature });
103
+ }
104
+
65
105
  /**
66
106
  * Verifies a Secp256k1 signature against the public key.
67
107
  *
68
- * This function checks the validity of a signature for a given message, ensuring that the signature is canonical as a malleability check.
108
+ * @deprecated The polymorphic `message: HexInput` input is ambiguous a
109
+ * bare even-length string of hex characters (e.g., `"cafe"`) is verified
110
+ * against the 2 bytes `[0xCA, 0xFE]`, not 4 UTF-8 text bytes. Use
111
+ * {@link verifyBytes} for `Uint8Array` input or {@link verifyText} for
112
+ * `string` input; both are unambiguous. See
113
+ * {@link convertSigningMessage} for the full legacy rule.
69
114
  *
70
115
  * @param args - The arguments for verifying the signature.
71
116
  * @param args.message - The message that was signed.
@@ -77,9 +122,7 @@ export class Secp256k1PublicKey extends PublicKey {
77
122
  const { message, signature } = args;
78
123
  const messageToVerify = convertSigningMessage(message);
79
124
  const messageBytes = Hex.fromHexInput(messageToVerify).toUint8Array();
80
- const messageSha3Bytes = sha3_256(messageBytes);
81
- const signatureBytes = signature.toUint8Array();
82
- return secp256k1.verify(signatureBytes, messageSha3Bytes, this.key.toUint8Array(), { lowS: true, prehash: false });
125
+ return this.verifyBytes({ message: messageBytes, signature });
83
126
  }
84
127
 
85
128
  /**
@@ -308,11 +351,42 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
308
351
  }
309
352
 
310
353
  /**
311
- * Clears the private key from memory by overwriting it with random bytes.
312
- * After calling this method, the private key can no longer be used for signing or deriving public keys.
354
+ * Overwrites the underlying private-key byte buffer with random bytes and
355
+ * then zeros. After calling this method the key can no longer sign or
356
+ * derive a public key.
357
+ *
358
+ * SECURITY: This is a best-effort window-narrowing tool, NOT a true
359
+ * zeroization guarantee. In JavaScript, four classes of copies cannot be
360
+ * reached from user code and so survive `clear()`:
361
+ *
362
+ * 1. **JS string copies.** Any value previously produced by `toString()`,
363
+ * `toHexString()`, or `bcsToHex().toString()` is an immutable string
364
+ * in the heap. The language provides no API to overwrite string
365
+ * memory; it is reclaimed only when GC collects it.
366
+ * 2. **noble-curves internals.** The sign path inside `@noble/curves`
367
+ * expands the private key into scalar `BigInt` field elements, which
368
+ * are also immutable. Even if noble explicitly zeroed its own byte
369
+ * copies after use, the `BigInt` intermediates persist.
370
+ * 3. **JIT register / stack residue.** The engine may have held key
371
+ * bytes in CPU registers or on the engine stack during a sign call.
372
+ * There is no JS-visible way to scrub those.
373
+ * 4. **GC-relocated copies.** Generational GCs (V8, JSC) copy live
374
+ * objects between heap regions during minor/major collections. The
375
+ * `Uint8Array` we zeroed may have stale copies sitting in survivor
376
+ * space until the next cycle reclaims them.
313
377
  *
314
- * Note: Due to JavaScript's memory management, this cannot guarantee complete removal of
315
- * sensitive data from memory, but it significantly reduces the window of exposure.
378
+ * This method zeros the SDK's own `Uint8Array` (the most reachable
379
+ * copy), but downstream consumers should treat it as a hardening signal,
380
+ * not a guarantee. If you need real key-material hygiene, prefer a
381
+ * WASM-backed secp256k1 implementation that exposes explicit
382
+ * `memzero`, or hardware-backed keys (HSM / TPM / secure enclave).
383
+ * Note that `crypto.subtle` does NOT support secp256k1 on any major
384
+ * runtime, so non-extractable WebCrypto keys are not available for
385
+ * this curve.
386
+ *
387
+ * To minimize the size of the unreachable-copy set, avoid calling
388
+ * `toString()` / `toHexString()` on private keys at all in long-lived
389
+ * processes — the byte form is what gets cleared.
316
390
  *
317
391
  * @group Implementation
318
392
  * @category Serialization
@@ -344,10 +418,54 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
344
418
  return this.cleared;
345
419
  }
346
420
 
421
+ /**
422
+ * Sign exactly the bytes of `message`. The input is interpreted as raw
423
+ * bytes regardless of what they encode. Pair with
424
+ * {@link Secp256k1PublicKey.verifyBytes}.
425
+ *
426
+ * The message is SHA3-256 hashed before signing (matching the Aptos-side
427
+ * Secp256k1 signing convention), and the produced signature is in
428
+ * canonical low-S form for malleability resistance.
429
+ *
430
+ * @param message - The exact bytes to sign.
431
+ * @returns The generated signature for the provided bytes.
432
+ * @throws Error if the private key has been cleared from memory.
433
+ * @group Implementation
434
+ * @category Serialization
435
+ */
436
+ signBytes(message: Uint8Array): Secp256k1Signature {
437
+ this.ensureNotCleared();
438
+ const messageHashBytes = sha3_256(message);
439
+ const signature = secp256k1.sign(messageHashBytes, this.key.toUint8Array(), { lowS: true, prehash: false });
440
+ return new Secp256k1Signature(signature);
441
+ }
442
+
443
+ /**
444
+ * Sign the UTF-8 encoding of `message`. The input is always treated as
445
+ * text — there is no hex/text heuristic. Pair with
446
+ * {@link Secp256k1PublicKey.verifyText}.
447
+ *
448
+ * @param message - The text to sign.
449
+ * @returns The generated signature for the UTF-8 bytes of the provided text.
450
+ * @throws Error if the private key has been cleared from memory.
451
+ * @group Implementation
452
+ * @category Serialization
453
+ */
454
+ signText(message: string): Secp256k1Signature {
455
+ return this.signBytes(TEXT_ENCODER.encode(message));
456
+ }
457
+
347
458
  /**
348
459
  * Sign the given message with the private key.
349
460
  * This function generates a cryptographic signature for the provided message, ensuring the signature is canonical and non-malleable.
350
461
  *
462
+ * @deprecated The polymorphic `message: HexInput` input is ambiguous — a
463
+ * bare even-length string of hex characters (e.g., `"cafe"`) is signed
464
+ * as the 2 bytes `[0xCA, 0xFE]`, not 4 UTF-8 text bytes. Use
465
+ * {@link signBytes} for `Uint8Array` input or {@link signText} for
466
+ * `string` input; both are unambiguous. See
467
+ * {@link convertSigningMessage} for the full legacy rule.
468
+ *
351
469
  * @param message - A message in HexInput format to be signed.
352
470
  * @returns Signature - The generated signature for the provided message.
353
471
  * @throws Error if the private key has been cleared from memory.
@@ -355,12 +473,9 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
355
473
  * @category Serialization
356
474
  */
357
475
  sign(message: HexInput): Secp256k1Signature {
358
- this.ensureNotCleared();
359
476
  const messageToSign = convertSigningMessage(message);
360
- const messageBytes = Hex.fromHexInput(messageToSign);
361
- const messageHashBytes = sha3_256(messageBytes.toUint8Array());
362
- const signature = secp256k1.sign(messageHashBytes, this.key.toUint8Array(), { lowS: true, prehash: false });
363
- return new Secp256k1Signature(signature);
477
+ const messageBytes = Hex.fromHexInput(messageToSign).toUint8Array();
478
+ return this.signBytes(messageBytes);
364
479
  }
365
480
 
366
481
  /**
@@ -393,6 +508,13 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
393
508
  /**
394
509
  * Get the private key as a string representation.
395
510
  *
511
+ * SECURITY: This produces an immutable JS string containing the key
512
+ * material in hex. Strings cannot be zeroed by `clear()` (see the
513
+ * `clear()` JSDoc for the four classes of unreachable copies). Avoid
514
+ * calling this method on long-lived `Secp256k1PrivateKey` instances in
515
+ * processes where memory hygiene matters; prefer `toUint8Array()`,
516
+ * which returns a clearable `Uint8Array`.
517
+ *
396
518
  * @returns string representation of the private key
397
519
  * @throws Error if the private key has been cleared from memory.
398
520
  * @group Implementation
@@ -406,6 +528,9 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
406
528
  /**
407
529
  * Get the private key as a hex string with the 0x prefix.
408
530
  *
531
+ * SECURITY: Same caveat as `toString()` — the returned string is an
532
+ * immutable JS heap allocation that `clear()` cannot zero.
533
+ *
409
534
  * @returns string representation of the private key.
410
535
  * @throws Error if the private key has been cleared from memory.
411
536
  */
@@ -419,6 +544,9 @@ export class Secp256k1PrivateKey extends Serializable implements PrivateKey {
419
544
  *
420
545
  * [Read about AIP-80](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-80.md)
421
546
  *
547
+ * SECURITY: Same caveat as `toString()` — produces an immutable JS string
548
+ * containing the key material; cannot be zeroed by `clear()`.
549
+ *
422
550
  * @returns AIP-80 compliant string representation of the private key.
423
551
  * @throws Error if the private key has been cleared from memory.
424
552
  */
@@ -15,6 +15,8 @@ import { PublicKey, VerifySignatureAsyncArgs } from "./publicKey.js";
15
15
  import { PrivateKey } from "./privateKey.js";
16
16
  import { Signature } from "./signature.js";
17
17
  import { AuthenticationKey } from "../authenticationKey.js";
18
+ import { convertSigningMessage } from "./utils.js";
19
+ import { TEXT_ENCODER } from "../../utils/const.js";
18
20
 
19
21
  /**
20
22
  * Represents a Secp256r1 ECDSA public key.
@@ -101,10 +103,51 @@ export class Secp256r1PublicKey extends PublicKey {
101
103
  return serializer.toUint8Array();
102
104
  }
103
105
 
106
+ /**
107
+ * Verifies a signature against the exact bytes of `message`. This is the
108
+ * unambiguous form — the input is interpreted as raw bytes regardless of
109
+ * what they encode. Pair with {@link Secp256r1PrivateKey.signBytes}.
110
+ *
111
+ * The message is SHA3-256 hashed before verification (matching the
112
+ * Aptos-side Secp256r1 signing convention), and the signature is required
113
+ * to be in canonical low-S form for malleability resistance.
114
+ *
115
+ * @param args - The arguments for verification.
116
+ * @param args.message - The exact bytes that were signed.
117
+ * @param args.signature - The signature to verify.
118
+ * @group Implementation
119
+ * @category Serialization
120
+ */
121
+ verifyBytes(args: { message: Uint8Array; signature: Signature }): boolean {
122
+ const { message, signature } = args;
123
+ const sha3Message = sha3_256(message);
124
+ return p256.verify(signature.toUint8Array(), sha3Message, this.toUint8Array(), { prehash: false, lowS: true });
125
+ }
126
+
127
+ /**
128
+ * Verifies a signature against the UTF-8 encoding of `message`. The input
129
+ * is always treated as text — there is no hex/text heuristic. Pair with
130
+ * {@link Secp256r1PrivateKey.signText}.
131
+ *
132
+ * @param args - The arguments for verification.
133
+ * @param args.message - The text that was signed.
134
+ * @param args.signature - The signature to verify.
135
+ * @group Implementation
136
+ * @category Serialization
137
+ */
138
+ verifyText(args: { message: string; signature: Signature }): boolean {
139
+ return this.verifyBytes({ message: TEXT_ENCODER.encode(args.message), signature: args.signature });
140
+ }
141
+
104
142
  /**
105
143
  * Verifies a Secp256r1 signature against the public key.
106
144
  *
107
- * This function checks the validity of a signature for a given message.
145
+ * @deprecated The polymorphic `message: HexInput` input is ambiguous a
146
+ * bare even-length string of hex characters (e.g., `"cafe"`) is verified
147
+ * against the 2 bytes `[0xCA, 0xFE]`, not 4 UTF-8 text bytes. Use
148
+ * {@link verifyBytes} for `Uint8Array` input or {@link verifyText} for
149
+ * `string` input; both are unambiguous. See
150
+ * {@link convertSigningMessage} for the full legacy rule.
108
151
  *
109
152
  * @param args - The arguments for verifying the signature.
110
153
  * @param args.message - The message that was signed.
@@ -114,12 +157,9 @@ export class Secp256r1PublicKey extends PublicKey {
114
157
  */
115
158
  verifySignature(args: { message: HexInput; signature: Signature }): boolean {
116
159
  const { message, signature } = args;
117
-
118
- const msgHex = Hex.fromHexInput(message).toUint8Array();
119
- const sha3Message = sha3_256(msgHex);
120
- const rawSignature = signature.toUint8Array();
121
-
122
- return p256.verify(rawSignature, sha3Message, this.toUint8Array(), { prehash: false });
160
+ const messageToVerify = convertSigningMessage(message);
161
+ const msgBytes = Hex.fromHexInput(messageToVerify).toUint8Array();
162
+ return this.verifyBytes({ message: msgBytes, signature });
123
163
  }
124
164
 
125
165
  /**
@@ -242,6 +282,12 @@ export class Secp256r1PrivateKey extends PrivateKey {
242
282
  */
243
283
  private readonly key: Hex;
244
284
 
285
+ /**
286
+ * Whether the key has been cleared from memory.
287
+ * @private
288
+ */
289
+ private cleared: boolean = false;
290
+
245
291
  /**
246
292
  * Create a new PrivateKey instance from a Uint8Array or String.
247
293
  *
@@ -268,47 +314,106 @@ export class Secp256r1PrivateKey extends PrivateKey {
268
314
  * Get the private key in bytes (Uint8Array).
269
315
  *
270
316
  * @returns
317
+ * @throws Error if the private key has been cleared from memory.
271
318
  * @group Implementation
272
319
  * @category Serialization
273
320
  */
274
321
  toUint8Array(): Uint8Array {
322
+ this.ensureNotCleared();
275
323
  return this.key.toUint8Array();
276
324
  }
277
325
 
278
326
  /**
279
327
  * Get the private key as a string representation.
280
328
  *
329
+ * SECURITY: This produces an immutable JS string containing the key
330
+ * material. Strings cannot be zeroed by `clear()` (see the `clear()`
331
+ * JSDoc for the four classes of unreachable copies). Avoid calling this
332
+ * method on long-lived `Secp256r1PrivateKey` instances in processes
333
+ * where memory hygiene matters; prefer `toUint8Array()`, which returns
334
+ * a clearable `Uint8Array`.
335
+ *
281
336
  * @returns string representation of the private key
337
+ * @throws Error if the private key has been cleared from memory.
282
338
  * @group Implementation
283
339
  * @category Serialization
284
340
  */
285
341
  toString(): string {
342
+ this.ensureNotCleared();
286
343
  return PrivateKey.formatPrivateKey(this.key.toString(), PrivateKeyVariants.Secp256r1);
287
344
  }
288
345
 
289
346
  /**
290
347
  * Get the private key as a hex string with the 0x prefix.
291
348
  *
349
+ * SECURITY: Same caveat as `toString()` — produces an immutable JS string
350
+ * containing the key material; cannot be zeroed by `clear()`.
351
+ *
292
352
  * @returns string representation of the private key.
353
+ * @throws Error if the private key has been cleared from memory.
293
354
  */
294
355
  toHexString(): string {
356
+ this.ensureNotCleared();
295
357
  return this.key.toString();
296
358
  }
297
359
 
360
+ /**
361
+ * Sign exactly the bytes of `message`. The input is interpreted as raw
362
+ * bytes regardless of what they encode. Pair with
363
+ * {@link Secp256r1PublicKey.verifyBytes}.
364
+ *
365
+ * The message is SHA3-256 hashed before signing (matching the Aptos-side
366
+ * Secp256r1 signing convention).
367
+ *
368
+ * @param message - The exact bytes to sign.
369
+ * @returns The generated signature for the provided bytes.
370
+ * @throws Error if the private key has been cleared from memory.
371
+ * @group Implementation
372
+ * @category Serialization
373
+ */
374
+ signBytes(message: Uint8Array): Secp256r1Signature {
375
+ this.ensureNotCleared();
376
+ const sha3Message = sha3_256(message);
377
+ const signature = p256.sign(sha3Message, this.key.toUint8Array(), { prehash: false });
378
+ return new Secp256r1Signature(signature);
379
+ }
380
+
381
+ /**
382
+ * Sign the UTF-8 encoding of `message`. The input is always treated as
383
+ * text — there is no hex/text heuristic. Pair with
384
+ * {@link Secp256r1PublicKey.verifyText}.
385
+ *
386
+ * @param message - The text to sign.
387
+ * @returns The generated signature for the UTF-8 bytes of the provided text.
388
+ * @throws Error if the private key has been cleared from memory.
389
+ * @group Implementation
390
+ * @category Serialization
391
+ */
392
+ signText(message: string): Secp256r1Signature {
393
+ return this.signBytes(TEXT_ENCODER.encode(message));
394
+ }
395
+
298
396
  /**
299
397
  * Sign the given message with the private key.
300
398
  * This function generates a cryptographic signature for the provided message.
301
399
  *
400
+ * @deprecated The polymorphic `message: HexInput` input is ambiguous — a
401
+ * bare even-length string of hex characters (e.g., `"cafe"`) is signed
402
+ * as the 2 bytes `[0xCA, 0xFE]`, not 4 UTF-8 text bytes. Use
403
+ * {@link signBytes} for `Uint8Array` input or {@link signText} for
404
+ * `string` input; both are unambiguous. See
405
+ * {@link convertSigningMessage} for the full legacy rule.
406
+ *
302
407
  * @param message - A message in HexInput format to be signed.
303
408
  * @returns Signature - The generated signature for the provided message.
409
+ * @throws Error if the private key has been cleared from memory.
304
410
  * @group Implementation
305
411
  * @category Serialization
306
412
  */
307
413
  sign(message: HexInput): Secp256r1Signature {
308
- const msgHex = Hex.fromHexInput(message);
309
- const sha3Message = sha3_256(msgHex.toUint8Array());
310
- const signature = p256.sign(sha3Message, this.key.toUint8Array(), { prehash: false });
311
- return new Secp256r1Signature(signature);
414
+ const messageToSign = convertSigningMessage(message);
415
+ const msgBytes = Hex.fromHexInput(messageToSign).toUint8Array();
416
+ return this.signBytes(msgBytes);
312
417
  }
313
418
 
314
419
  /**
@@ -352,13 +457,61 @@ export class Secp256r1PrivateKey extends PrivateKey {
352
457
  * Derive the Secp256r1PublicKey from this private key.
353
458
  *
354
459
  * @returns Secp256r1PublicKey The derived public key.
460
+ * @throws Error if the private key has been cleared from memory.
355
461
  * @group Implementation
356
462
  * @category Serialization
357
463
  */
358
464
  publicKey(): Secp256r1PublicKey {
465
+ this.ensureNotCleared();
359
466
  const bytes = p256.getPublicKey(this.key.toUint8Array(), false);
360
467
  return new Secp256r1PublicKey(bytes);
361
468
  }
469
+
470
+ /**
471
+ * Throws if the key has already been cleared.
472
+ * @private
473
+ */
474
+ private ensureNotCleared(): void {
475
+ if (this.cleared) {
476
+ throw new Error("Private key has been cleared from memory and can no longer be used");
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Overwrites the underlying private-key byte buffer with random bytes and
482
+ * then zeros. After calling this method the key can no longer sign or
483
+ * derive a public key.
484
+ *
485
+ * SECURITY: This is a best-effort window-narrowing tool, NOT a true
486
+ * zeroization guarantee. See `Ed25519PrivateKey.clear()` for the full
487
+ * enumeration of JavaScript-level limits (immutable string copies, noble
488
+ * `BigInt` intermediates, JIT register/stack residue, GC-relocated
489
+ * copies). For Secp256r1 specifically, non-extractable `crypto.subtle`
490
+ * P-256 keys are universally supported across modern runtimes and are
491
+ * the architecturally-correct path for callers who need real memory
492
+ * hygiene; consider that alternative for new code.
493
+ *
494
+ * @group Implementation
495
+ * @category Serialization
496
+ */
497
+ clear(): void {
498
+ if (!this.cleared) {
499
+ const keyBytes = this.key.toUint8Array();
500
+ // Multiple overwrite passes for consistency with the other private-key classes.
501
+ crypto.getRandomValues(keyBytes);
502
+ keyBytes.fill(0xff);
503
+ crypto.getRandomValues(keyBytes);
504
+ keyBytes.fill(0);
505
+ this.cleared = true;
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Returns whether `clear()` has been called.
511
+ */
512
+ isCleared(): boolean {
513
+ return this.cleared;
514
+ }
362
515
  }
363
516
 
364
517
  export class WebAuthnSignature extends Signature {
@@ -10,7 +10,34 @@ import { BaseAccountPublicKey } from "./types.js";
10
10
  import { detectPublicKeyVariant } from "./anyKeyRegistry.js";
11
11
 
12
12
  /**
13
- * Helper function to convert a message to sign or to verify to a valid message input
13
+ * Normalizes a sign/verify message into a {@link HexInput} that downstream
14
+ * callers can pass to `Hex.fromHexInput()`.
15
+ *
16
+ * Behavior — be aware before passing a string:
17
+ * - `Uint8Array` → returned as-is (used as raw bytes).
18
+ * - String that parses as hex via `Hex.isValid()` (with or without a `0x`
19
+ * prefix) → returned as the original hex string, which downstream
20
+ * `Hex.fromHexInput()` decodes to its byte form.
21
+ * - Any other string → returned as the UTF-8 byte encoding of the string.
22
+ *
23
+ * **AMBIGUITY**: a bare even-length string of hex characters is *always*
24
+ * interpreted as hex, even when the caller intended it as text. For example:
25
+ *
26
+ * ```ts
27
+ * sign("cafe") // signs 2 bytes: [0xCA, 0xFE]
28
+ * sign("decade") // signs 3 bytes: [0xDE, 0xCA, 0xDE]
29
+ * sign("0xcafe") // signs 2 bytes: [0xCA, 0xFE] (explicit hex)
30
+ * sign("hello") // signs 5 bytes: UTF-8 "hello" (not valid hex)
31
+ * sign(new TextEncoder().encode("cafe")) // signs 4 bytes: UTF-8 "cafe"
32
+ * ```
33
+ *
34
+ * If you mean *text*, pass `TextEncoder.encode(text)` or any `Uint8Array`.
35
+ * If you mean *hex bytes*, the most explicit form is also a `Uint8Array`
36
+ * (`Hex.fromHexInput("0x...").toUint8Array()`), or a string prefixed with
37
+ * `0x` for clarity. The heuristic is preserved as-is for backwards
38
+ * compatibility — changing it would silently re-interpret bytes signed by
39
+ * existing dApps and wallets — but new code should treat string inputs to
40
+ * `sign()` / `verifySignature()` as untyped and prefer `Uint8Array`.
14
41
  *
15
42
  * @param message a message as a string or Uint8Array
16
43
  *
@@ -361,6 +361,15 @@ type AptosApiErrorOpts = {
361
361
  * @param statusText - The message associated with the response status.
362
362
  * @param data - The response data returned from the API.
363
363
  * @param request - The original AptosRequest that triggered the error.
364
+ *
365
+ * SECURITY: `Error.message` is sanitized for `AptosApiType.PEPPER` and
366
+ * `AptosApiType.PROVER` so that response bodies (which can contain JWT claims
367
+ * or pepper-derived material) don't leak into default log/crash sinks. The
368
+ * `data` field, however, ALWAYS holds the raw response body — including for
369
+ * those sensitive API types — so callers that log or serialize
370
+ * `AptosApiError.data` (e.g., `JSON.stringify(error)`, Sentry's automatic
371
+ * field capture, custom structured loggers) must treat it accordingly. If
372
+ * you only need a human-readable summary, prefer `error.message`.
364
373
  */
365
374
  export class AptosApiError extends Error {
366
375
  readonly url: string;
@@ -369,6 +378,16 @@ export class AptosApiError extends Error {
369
378
 
370
379
  readonly statusText: string;
371
380
 
381
+ /**
382
+ * The raw response body returned by the API.
383
+ *
384
+ * SECURITY: For `AptosApiType.PEPPER` and `AptosApiType.PROVER`, this can
385
+ * contain sensitive keyless-flow material (JWT claims, pepper-derived
386
+ * state). It is NOT redacted here — only `Error.message` is. Treat
387
+ * `error.data` as sensitive when handling errors from those API types,
388
+ * especially before passing the error to a structured logger or crash
389
+ * reporter.
390
+ */
372
391
  readonly data: any;
373
392
 
374
393
  readonly request: AptosRequest;
@@ -404,6 +423,13 @@ export class AptosApiError extends Error {
404
423
  * @param {AptosRequest} opts.aptosRequest - The original request made to the Aptos API.
405
424
  * @param {AptosResponse} opts.aptosResponse - The response received from the Aptos API.
406
425
  */
426
+ // API types whose response bodies may contain keyless-account material (JWT
427
+ // claims, pepper-derived state). For these we exclude the body from the error
428
+ // message — including from the structured-error branch — so nothing about
429
+ // the response payload reaches the default Error.message sink. Callers that
430
+ // need the full body can still read it from `AptosApiError.data`.
431
+ const SENSITIVE_BODY_API_TYPES: ReadonlySet<AptosApiType> = new Set([AptosApiType.PEPPER, AptosApiType.PROVER]);
432
+
407
433
  function deriveErrorMessage({ apiType, aptosRequest, aptosResponse }: AptosApiErrorOpts): string {
408
434
  // extract the W3C trace_id from the response headers if it exists. Some services set this in the response, and it's useful for debugging.
409
435
  // See https://www.w3.org/TR/trace-context/#relationship-between-the-headers .
@@ -414,6 +440,17 @@ function deriveErrorMessage({ apiType, aptosRequest, aptosResponse }: AptosApiEr
414
440
  aptosResponse.url ?? aptosRequest.url
415
441
  } ${traceIdString}failed with`;
416
442
 
443
+ // For sensitive API types, redact the response body in every branch below.
444
+ // Doing this up-front rather than per-branch ensures a Pepper/Prover
445
+ // response that happens to match the `{ message, error_code }` shape (or
446
+ // some future well-known shape) can't slip a payload through into
447
+ // Error.message. The full body remains on AptosApiError.data for callers
448
+ // that explicitly need it.
449
+ const isSensitive = SENSITIVE_BODY_API_TYPES.has(apiType);
450
+ if (isSensitive) {
451
+ return `${errorPrelude} status: ${aptosResponse.statusText}(code:${aptosResponse.status}) (response body redacted for ${apiType})`;
452
+ }
453
+
417
454
  // handle graphql responses from indexer api and extract the error message of the first error
418
455
  if (apiType === AptosApiType.INDEXER && aptosResponse.data?.errors?.[0]?.message != null) {
419
456
  return `${errorPrelude}: ${aptosResponse.data.errors[0].message}`;