@bsv/sdk 1.6.8 → 1.6.10

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 (92) hide show
  1. package/README.md +9 -4
  2. package/dist/cjs/package.json +7 -5
  3. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +11 -3
  4. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  5. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1 -1
  6. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  7. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js +21 -0
  8. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js.map +1 -0
  9. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  10. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +9 -1
  11. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  12. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1 -1
  13. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  14. package/dist/esm/src/wallet/substrates/utils/toOriginHeader.js +17 -0
  15. package/dist/esm/src/wallet/substrates/utils/toOriginHeader.js.map +1 -0
  16. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  17. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
  18. package/dist/types/src/wallet/substrates/utils/toOriginHeader.d.ts +2 -0
  19. package/dist/types/src/wallet/substrates/utils/toOriginHeader.d.ts.map +1 -0
  20. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  21. package/dist/umd/bundle.js +1 -1
  22. package/docs/concepts/beef.md +84 -0
  23. package/docs/concepts/chain-tracking.md +122 -0
  24. package/docs/concepts/decentralized-identity.md +184 -0
  25. package/docs/concepts/fees.md +217 -0
  26. package/docs/concepts/identity-certificates.md +255 -0
  27. package/docs/concepts/index.md +62 -0
  28. package/docs/concepts/key-management.md +176 -0
  29. package/docs/concepts/script-templates.md +163 -0
  30. package/docs/concepts/sdk-philosophy.md +72 -0
  31. package/docs/concepts/signatures.md +179 -0
  32. package/docs/concepts/spv-verification.md +106 -0
  33. package/docs/concepts/transaction-encoding.md +148 -0
  34. package/docs/concepts/transaction-structure.md +63 -0
  35. package/docs/concepts/trust-model.md +123 -0
  36. package/docs/concepts/verification.md +219 -0
  37. package/docs/concepts/wallet-integration.md +95 -0
  38. package/docs/guides/direct-transaction-creation.md +137 -0
  39. package/docs/guides/http-client-configuration.md +414 -0
  40. package/docs/guides/index.md +30 -0
  41. package/docs/guides/transaction-signing-methods.md +268 -0
  42. package/docs/index.md +74 -0
  43. package/docs/reference/arc-config.md +698 -0
  44. package/docs/reference/brc-100.md +33 -0
  45. package/docs/reference/configuration.md +829 -0
  46. package/docs/reference/debugging.md +700 -0
  47. package/docs/reference/errors.md +547 -0
  48. package/docs/reference/index.md +98 -0
  49. package/docs/reference/network-config.md +914 -0
  50. package/docs/reference/op-codes.md +306 -0
  51. package/docs/reference/transaction-signatures.md +94 -0
  52. package/docs/{wallet.md → reference/wallet.md} +9 -0
  53. package/docs/requirements.txt +3 -0
  54. package/docs/tutorials/advanced-transaction.md +575 -0
  55. package/docs/tutorials/aes-encryption.md +947 -0
  56. package/docs/tutorials/authfetch-tutorial.md +957 -0
  57. package/docs/tutorials/ecdh-key-exchange.md +547 -0
  58. package/docs/tutorials/elliptic-curve-fundamentals.md +603 -0
  59. package/docs/tutorials/error-handling.md +1215 -0
  60. package/docs/tutorials/first-transaction-low-level.md +204 -0
  61. package/docs/tutorials/first-transaction.md +278 -0
  62. package/docs/tutorials/hashes-and-hmacs.md +814 -0
  63. package/docs/tutorials/identity-management.md +702 -0
  64. package/docs/tutorials/index.md +182 -0
  65. package/docs/tutorials/key-management.md +536 -0
  66. package/docs/tutorials/protowallet-development.md +716 -0
  67. package/docs/tutorials/script-construction.md +690 -0
  68. package/docs/tutorials/spv-merkle-proofs.md +682 -0
  69. package/docs/tutorials/testnet-transactions-low-level.md +352 -0
  70. package/docs/tutorials/transaction-broadcasting.md +535 -0
  71. package/docs/tutorials/transaction-types.md +419 -0
  72. package/docs/tutorials/type-42.md +582 -0
  73. package/docs/tutorials/uhrp-storage.md +579 -0
  74. package/package.json +7 -5
  75. package/src/transaction/__tests/Transaction.test.ts +1 -1
  76. package/src/wallet/substrates/HTTPWalletJSON.ts +11 -1
  77. package/src/wallet/substrates/WalletWireProcessor.ts +1 -1
  78. package/src/wallet/substrates/__tests/toOriginHeader.test.ts +34 -0
  79. package/src/wallet/substrates/utils/toOriginHeader.ts +15 -0
  80. package/docs/README.md +0 -21
  81. /package/docs/{auth.md → reference/auth.md} +0 -0
  82. /package/docs/{compat.md → reference/compat.md} +0 -0
  83. /package/docs/{identity.md → reference/identity.md} +0 -0
  84. /package/docs/{kvstore.md → reference/kvstore.md} +0 -0
  85. /package/docs/{messages.md → reference/messages.md} +0 -0
  86. /package/docs/{overlay-tools.md → reference/overlay-tools.md} +0 -0
  87. /package/docs/{primitives.md → reference/primitives.md} +0 -0
  88. /package/docs/{registry.md → reference/registry.md} +0 -0
  89. /package/docs/{script.md → reference/script.md} +0 -0
  90. /package/docs/{storage.md → reference/storage.md} +0 -0
  91. /package/docs/{totp.md → reference/totp.md} +0 -0
  92. /package/docs/{transaction.md → reference/transaction.md} +0 -0
@@ -0,0 +1,947 @@
1
+ # AES Symmetric Encryption
2
+
3
+ **Duration**: 60 minutes
4
+ **Prerequisites**: Basic TypeScript knowledge, [First Transaction](./first-transaction.md) tutorial completed
5
+
6
+ ## Learning Goals
7
+
8
+ - Understand AES-GCM symmetric encryption principles
9
+ - Use the `SymmetricKey` class for encryption and decryption
10
+ - Implement secure key generation and management
11
+ - Apply AES encryption in practical Bitcoin applications
12
+ - Combine AES with ECDH for secure communication
13
+ - Handle different data formats and encoding
14
+
15
+ ## Introduction to AES Encryption
16
+
17
+ Advanced Encryption Standard (AES) is a symmetric encryption algorithm where the same key is used for both encryption and decryption. The BSV TypeScript SDK provides the `SymmetricKey` class that implements AES-GCM (Galois/Counter Mode), which provides both confidentiality and authenticity.
18
+
19
+ AES-GCM offers several advantages:
20
+ - **Confidentiality**: Data is encrypted and unreadable without the key
21
+ - **Authenticity**: Built-in authentication prevents tampering
22
+ - **Performance**: Fast encryption/decryption operations
23
+ - **Security**: Resistant to various cryptographic attacks
24
+
25
+ ## Setting Up Your Environment
26
+
27
+ First, let's import the necessary classes from the SDK:
28
+
29
+ ```typescript
30
+ import { SymmetricKey, Utils, Random } from '@bsv/sdk'
31
+
32
+ // Helper function to convert hex string to byte array
33
+ function hexToBytes(hex: string): number[] {
34
+ const bytes = []
35
+ for (let i = 0; i < hex.length; i += 2) {
36
+ bytes.push(parseInt(hex.substr(i, 2), 16))
37
+ }
38
+ return bytes
39
+ }
40
+
41
+ // Helper function to convert byte array to hex string
42
+ function bytesToHex(bytes: number[]): string {
43
+ const hex = []
44
+ for (const byte of bytes) {
45
+ hex.push(byte.toString(16).padStart(2, '0'))
46
+ }
47
+ return hex.join('')
48
+ }
49
+ ```
50
+
51
+ ## Basic AES Encryption
52
+
53
+ ### Generating Encryption Keys
54
+
55
+ The `SymmetricKey` class provides methods to create secure encryption keys:
56
+
57
+ ```typescript
58
+ // Generate a random 256-bit AES key
59
+ const symmetricKey = SymmetricKey.fromRandom()
60
+ console.log('Generated key:', symmetricKey.toHex())
61
+
62
+ // Create a key from existing data (32 bytes)
63
+ const keyData = Random(32)
64
+ const customKey = new SymmetricKey(keyData)
65
+ console.log('Custom key:', customKey.toHex())
66
+
67
+ // Create a key from hex string
68
+ const hexKey = 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456'
69
+ const keyFromHex = new SymmetricKey(hexToBytes(hexKey))
70
+ ```
71
+
72
+ ### Encrypting Data
73
+
74
+ The `encrypt` method supports both string and binary data:
75
+
76
+ ```typescript
77
+ const symmetricKey = SymmetricKey.fromRandom()
78
+ const message = 'Hello, this is a secret message!'
79
+
80
+ // Encrypt a string (UTF-8 encoding)
81
+ const encryptedMessage = symmetricKey.encrypt(message) as number[]
82
+ console.log('Encrypted message length:', encryptedMessage.length)
83
+
84
+ // Encrypt binary data
85
+ const binaryData = Utils.toArray('Binary data example', 'utf8')
86
+ const encryptedBinary = symmetricKey.encrypt(binaryData) as number[]
87
+
88
+ // Encrypt with hex output (using manual conversion for clarity)
89
+ const encryptedBytes = symmetricKey.encrypt(message) as number[]
90
+ const hexEncrypted = bytesToHex(encryptedBytes)
91
+ console.log('Hex encrypted:', hexEncrypted)
92
+
93
+ // Alternative: SDK's built-in hex handling (for hex input data)
94
+ // Note: The 'hex' parameter treats the input as hex, not the output format
95
+ const messageAsHex = Buffer.from(message).toString('hex') // Convert message to hex first
96
+ const sdkHexEncrypted = symmetricKey.encrypt(messageAsHex, 'hex') as string
97
+ console.log('SDK hex encrypted:', sdkHexEncrypted)
98
+ ```
99
+
100
+ ### Decrypting Data
101
+
102
+ The `decrypt` method reverses the encryption process:
103
+
104
+ ```typescript
105
+ // Decrypt to string (UTF-8)
106
+ const decryptedMessage = symmetricKey.decrypt(encryptedMessage, 'utf8') as string
107
+ console.log('Decrypted message:', decryptedMessage)
108
+
109
+ // Decrypt to binary array
110
+ const decryptedBinary = symmetricKey.decrypt(encryptedBinary) as number[]
111
+ console.log('Decrypted binary:', Utils.toUTF8(decryptedBinary))
112
+
113
+ // Decrypt hex-encoded data (manual conversion method)
114
+ const hexBytes = hexToBytes(hexEncrypted)
115
+ const decryptedFromHex = symmetricKey.decrypt(hexBytes, 'utf8') as string
116
+ console.log('Decrypted from hex:', decryptedFromHex)
117
+
118
+ // Alternative: SDK hex handling method
119
+ const sdkHexBytes = hexToBytes(sdkHexEncrypted)
120
+ const sdkDecryptedHex = symmetricKey.decrypt(sdkHexBytes, 'hex') as string
121
+ const sdkDecryptedMessage = Buffer.from(sdkDecryptedHex, 'hex').toString('utf8')
122
+ console.log('SDK decrypted from hex:', sdkDecryptedMessage)
123
+ ```
124
+
125
+ ### Understanding the Hex Parameter
126
+
127
+ The `enc` parameter in the SDK's `encrypt()` and `decrypt()` methods can be confusing. Here's how it actually works:
128
+
129
+ **In `encrypt(data, enc)`:**
130
+ - `enc` specifies how to **interpret the input data**
131
+ - `enc: 'hex'` means the input data is a hex string that should be converted to bytes
132
+ - The output format is determined by the `enc` parameter (hex string if `enc: 'hex'`, byte array otherwise)
133
+
134
+ **In `decrypt(data, enc)`:**
135
+ - `enc` specifies the **output format**
136
+ - `enc: 'hex'` returns the decrypted data as a hex string
137
+ - `enc: 'utf8'` returns the decrypted data as a UTF-8 string
138
+
139
+ ```typescript
140
+ // Example: Encrypting hex data with SDK's built-in hex handling
141
+ const message = 'Hello, World!'
142
+ const messageAsHex = Buffer.from(message).toString('hex') // '48656c6c6f2c20576f726c6421'
143
+
144
+ // Encrypt hex input data
145
+ const encrypted = key.encrypt(messageAsHex, 'hex') as string
146
+
147
+ // Decrypt and get result as hex
148
+ const decryptedHex = key.decrypt(hexToBytes(encrypted), 'hex') as string
149
+ console.log('Decrypted as hex:', decryptedHex) // '48656c6c6f2c20576f726c6421'
150
+
151
+ // Convert hex result back to UTF-8
152
+ const finalMessage = Buffer.from(decryptedHex, 'hex').toString('utf8')
153
+ console.log('Final message:', finalMessage) // 'Hello, World!'
154
+ ```
155
+
156
+
157
+ ## Complete Encryption Example
158
+
159
+ Here's a comprehensive example demonstrating the full encryption workflow:
160
+
161
+ ```typescript
162
+ import { SymmetricKey, Utils } from '@bsv/sdk'
163
+
164
+ function demonstrateAESEncryption() {
165
+ console.log('=== AES Encryption Demonstration ===')
166
+
167
+ // 1. Generate a random encryption key
168
+ const symmetricKey = SymmetricKey.fromRandom()
169
+ console.log('Generated key:', symmetricKey.toHex().substring(0, 16) + '...')
170
+
171
+ // 2. Prepare the message to encrypt
172
+ const originalMessage = 'This is a confidential message that needs protection!'
173
+ console.log('Original message:', originalMessage)
174
+
175
+ // 3. Encrypt the message
176
+ const encryptedData = symmetricKey.encrypt(originalMessage) as number[]
177
+ console.log('Encrypted data length:', encryptedData.length, 'bytes')
178
+ console.log('Encrypted (first 32 bytes):', encryptedData.slice(0, 32))
179
+
180
+ // 4. Decrypt the message
181
+ const decryptedMessage = symmetricKey.decrypt(encryptedData, 'utf8') as string
182
+
183
+ // 5. Verify integrity
184
+ const isValid = originalMessage === decryptedMessage
185
+ console.log('Decryption successful:', isValid)
186
+
187
+ return {
188
+ key: symmetricKey,
189
+ original: originalMessage,
190
+ encrypted: encryptedData,
191
+ decrypted: decryptedMessage,
192
+ valid: isValid
193
+ }
194
+ }
195
+
196
+ // Run the demonstration
197
+ const result = demonstrateAESEncryption()
198
+ ```
199
+
200
+ ## Working with Different Data Types
201
+
202
+ ### Encrypting JSON Data
203
+
204
+ ```typescript
205
+ function encryptJSON(data: any, key: SymmetricKey): number[] {
206
+ const jsonString = JSON.stringify(data)
207
+ return key.encrypt(jsonString) as number[]
208
+ }
209
+
210
+ function decryptJSON(encryptedData: number[], key: SymmetricKey): any {
211
+ const jsonString = key.decrypt(encryptedData, 'utf8') as string
212
+ return JSON.parse(jsonString)
213
+ }
214
+
215
+ // Example usage
216
+ const symmetricKey = SymmetricKey.fromRandom()
217
+ const userData = {
218
+ name: 'Alice',
219
+ email: 'alice@example.com',
220
+ balance: 1000,
221
+ transactions: ['tx1', 'tx2', 'tx3']
222
+ }
223
+
224
+ const encryptedJSON = encryptJSON(userData, symmetricKey)
225
+ const decryptedData = decryptJSON(encryptedJSON, symmetricKey)
226
+ console.log('Original data:', userData)
227
+ console.log('Decrypted data:', decryptedData)
228
+ ```
229
+
230
+ ### Encrypting Files and Large Data
231
+
232
+ ```typescript
233
+ function encryptLargeData(data: string, key: SymmetricKey): {
234
+ encrypted: number[],
235
+ size: number,
236
+ checksum: string
237
+ } {
238
+ // Convert to binary
239
+ const binaryData = Utils.toArray(data, 'utf8')
240
+
241
+ // Encrypt the data
242
+ const encrypted = key.encrypt(binaryData) as number[]
243
+
244
+ // Calculate checksum of original data for verification
245
+ const checksum = Utils.toBase64(binaryData.slice(0, 32))
246
+
247
+ return {
248
+ encrypted,
249
+ size: binaryData.length,
250
+ checksum
251
+ }
252
+ }
253
+
254
+ function decryptLargeData(encryptedData: {
255
+ encrypted: number[],
256
+ size: number,
257
+ checksum: string
258
+ }, key: SymmetricKey): string {
259
+ // Decrypt the data
260
+ const decrypted = key.decrypt(encryptedData.encrypted) as number[]
261
+
262
+ // Verify size
263
+ if (decrypted.length !== encryptedData.size) {
264
+ throw new Error('Decrypted data size mismatch')
265
+ }
266
+
267
+ // Verify checksum
268
+ const checksum = Utils.toBase64(decrypted.slice(0, 32))
269
+ if (checksum !== encryptedData.checksum) {
270
+ console.warn('Checksum mismatch - data may be corrupted')
271
+ }
272
+
273
+ return Utils.toUTF8(decrypted)
274
+ }
275
+ ```
276
+
277
+ ## Key Derivation and Management
278
+
279
+ ### Deriving Keys from Passwords
280
+
281
+ ```typescript
282
+ import { Hash } from '@bsv/sdk'
283
+
284
+ function deriveKeyFromPassword(password: string, salt?: number[]): SymmetricKey {
285
+ // Use provided salt or generate random one
286
+ const keySalt = salt || Random(32)
287
+
288
+ // Create key material by hashing password + salt
289
+ const passwordBytes = Utils.toArray(password, 'utf8')
290
+ const keyMaterial = [...passwordBytes, ...keySalt]
291
+
292
+ // Hash to create 256-bit key
293
+ const keyHash = Hash.sha256(keyMaterial)
294
+
295
+ return new SymmetricKey(keyHash)
296
+ }
297
+
298
+ // Example usage
299
+ const password = 'MySecurePassword123!'
300
+ const salt = Random(32)
301
+ const derivedKey = deriveKeyFromPassword(password, salt)
302
+
303
+ // Store salt separately - needed for key recreation
304
+ console.log('Derived key:', derivedKey.toHex())
305
+ console.log('Salt (store this):', Utils.toBase64(salt))
306
+
307
+ // Recreate the same key later
308
+ const recreatedKey = deriveKeyFromPassword(password, salt)
309
+ console.log('Keys match:', derivedKey.toHex() === recreatedKey.toHex())
310
+ ```
311
+
312
+ ### Key Rotation and Versioning
313
+
314
+ ```typescript
315
+ class KeyManager {
316
+ private keys: Map<number, SymmetricKey> = new Map()
317
+ private currentVersion: number = 1
318
+
319
+ generateNewKey(): number {
320
+ const newKey = SymmetricKey.fromRandom()
321
+ this.keys.set(this.currentVersion, newKey)
322
+ return this.currentVersion++
323
+ }
324
+
325
+ encrypt(data: string, version?: number): {
326
+ encrypted: number[],
327
+ version: number
328
+ } {
329
+ const keyVersion = version || this.currentVersion - 1
330
+ const key = this.keys.get(keyVersion)
331
+
332
+ if (!key) {
333
+ throw new Error(`Key version ${keyVersion} not found`)
334
+ }
335
+
336
+ return {
337
+ encrypted: key.encrypt(data) as number[],
338
+ version: keyVersion
339
+ }
340
+ }
341
+
342
+ decrypt(encryptedData: {
343
+ encrypted: number[],
344
+ version: number
345
+ }): string {
346
+ const key = this.keys.get(encryptedData.version)
347
+
348
+ if (!key) {
349
+ throw new Error(`Key version ${encryptedData.version} not found`)
350
+ }
351
+
352
+ return key.decrypt(encryptedData.encrypted, 'utf8') as string
353
+ }
354
+ }
355
+
356
+ // Example usage
357
+ const keyManager = new KeyManager()
358
+ const v1 = keyManager.generateNewKey()
359
+ const v2 = keyManager.generateNewKey()
360
+
361
+ const message = 'Data encrypted with version 1'
362
+ const encrypted = keyManager.encrypt(message, v1)
363
+ const decrypted = keyManager.decrypt(encrypted)
364
+ console.log('Decrypted:', decrypted)
365
+ ```
366
+
367
+ ## Combining AES with ECDH
368
+
369
+ For secure communication between parties, combine AES encryption with ECDH key exchange:
370
+
371
+ ```typescript
372
+ import { PrivateKey, PublicKey } from '@bsv/sdk'
373
+
374
+ function createSecureChannel(
375
+ senderPrivateKey: PrivateKey,
376
+ recipientPublicKey: PublicKey
377
+ ): SymmetricKey {
378
+ // Derive shared secret using ECDH
379
+ const sharedSecret = senderPrivateKey.deriveSharedSecret(recipientPublicKey)
380
+
381
+ // Use shared secret as AES key material
382
+ const keyMaterial = sharedSecret.encode(true).slice(1) // Remove prefix byte
383
+
384
+ return new SymmetricKey(keyMaterial)
385
+ }
386
+
387
+ function secureMessageExchange() {
388
+ // Generate key pairs for Alice and Bob
389
+ const alicePrivate = PrivateKey.fromRandom()
390
+ const alicePublic = alicePrivate.toPublicKey()
391
+
392
+ const bobPrivate = PrivateKey.fromRandom()
393
+ const bobPublic = bobPrivate.toPublicKey()
394
+
395
+ // Alice creates encryption key using Bob's public key
396
+ const aliceEncryptionKey = createSecureChannel(alicePrivate, bobPublic)
397
+
398
+ // Bob creates the same encryption key using Alice's public key
399
+ const bobDecryptionKey = createSecureChannel(bobPrivate, alicePublic)
400
+
401
+ // Verify both parties have the same key
402
+ console.log('Keys match:',
403
+ aliceEncryptionKey.toHex() === bobDecryptionKey.toHex())
404
+
405
+ // Alice encrypts a message
406
+ const message = 'Hello Bob, this is a secure message from Alice!'
407
+ const encrypted = aliceEncryptionKey.encrypt(message) as number[]
408
+
409
+ // Bob decrypts the message
410
+ const decrypted = bobDecryptionKey.decrypt(encrypted, 'utf8') as string
411
+
412
+ console.log('Original message:', message)
413
+ console.log('Decrypted message:', decrypted)
414
+ console.log('Secure communication successful:', message === decrypted)
415
+
416
+ return { aliceEncryptionKey, bobDecryptionKey, message, decrypted }
417
+ }
418
+
419
+ // Run secure message exchange
420
+ secureMessageExchange()
421
+ ```
422
+
423
+ ## Error Handling and Security
424
+
425
+ ### Robust Encryption with Error Handling
426
+
427
+ ```typescript
428
+ class SecureAESManager {
429
+ private key: SymmetricKey
430
+
431
+ constructor(key?: SymmetricKey) {
432
+ this.key = key || SymmetricKey.fromRandom()
433
+ }
434
+
435
+ safeEncrypt(data: string): {
436
+ success: boolean,
437
+ encrypted?: number[],
438
+ error?: string
439
+ } {
440
+ try {
441
+ if (!data || data.length === 0) {
442
+ return { success: false, error: 'Data cannot be empty' }
443
+ }
444
+
445
+ const encrypted = this.key.encrypt(data) as number[]
446
+
447
+ if (!encrypted || encrypted.length === 0) {
448
+ return { success: false, error: 'Encryption failed' }
449
+ }
450
+
451
+ return { success: true, encrypted }
452
+ } catch (error) {
453
+ return {
454
+ success: false,
455
+ error: `Encryption error: ${error.message}`
456
+ }
457
+ }
458
+ }
459
+
460
+ safeDecrypt(encryptedData: number[]): {
461
+ success: boolean,
462
+ decrypted?: string,
463
+ error?: string
464
+ } {
465
+ try {
466
+ if (!encryptedData || encryptedData.length === 0) {
467
+ return { success: false, error: 'Encrypted data cannot be empty' }
468
+ }
469
+
470
+ const decrypted = this.key.decrypt(encryptedData, 'utf8') as string
471
+
472
+ return { success: true, decrypted }
473
+ } catch (error) {
474
+ return {
475
+ success: false,
476
+ error: `Decryption failed: ${error.message}`
477
+ }
478
+ }
479
+ }
480
+
481
+ rotateKey(): void {
482
+ this.key = SymmetricKey.fromRandom()
483
+ }
484
+
485
+ getKeyFingerprint(): string {
486
+ // Create a fingerprint of the key for identification
487
+ const keyBytes = this.key.toArray()
488
+ const hash = Hash.sha256(keyBytes)
489
+ return Utils.toBase64(hash).substring(0, 16)
490
+ }
491
+ }
492
+
493
+ // Example usage with error handling
494
+ const aesManager = new SecureAESManager()
495
+ console.log('Key fingerprint:', aesManager.getKeyFingerprint())
496
+
497
+ const testData = 'Sensitive information that needs protection'
498
+ const encryptResult = aesManager.safeEncrypt(testData)
499
+
500
+ if (encryptResult.success) {
501
+ console.log('Encryption successful')
502
+
503
+ const decryptResult = aesManager.safeDecrypt(encryptResult.encrypted!)
504
+
505
+ if (decryptResult.success) {
506
+ console.log('Decryption successful:', decryptResult.decrypted)
507
+ } else {
508
+ console.error('Decryption failed:', decryptResult.error)
509
+ }
510
+ } else {
511
+ console.error('Encryption failed:', encryptResult.error)
512
+ }
513
+ ```
514
+
515
+ ## Practical Applications
516
+
517
+ ### Encrypting Transaction Metadata
518
+
519
+ ```typescript
520
+ import { Transaction, PrivateKey } from '@bsv/sdk'
521
+
522
+ function encryptTransactionMetadata(
523
+ transaction: Transaction,
524
+ metadata: any,
525
+ encryptionKey: SymmetricKey
526
+ ): number[] {
527
+ const metadataWithTx = {
528
+ txid: Buffer.from(transaction.id()).toString('hex'),
529
+ timestamp: Date.now(),
530
+ metadata: metadata
531
+ }
532
+
533
+ const jsonString = JSON.stringify(metadataWithTx)
534
+ return encryptionKey.encrypt(jsonString) as number[]
535
+ }
536
+
537
+ function decryptTransactionMetadata(
538
+ encryptedMetadata: number[],
539
+ encryptionKey: SymmetricKey
540
+ ): any {
541
+ const jsonString = encryptionKey.decrypt(encryptedMetadata, 'utf8') as string
542
+ return JSON.parse(jsonString)
543
+ }
544
+
545
+ // Example: Encrypt private notes about a transaction
546
+ const privateKey = PrivateKey.fromRandom()
547
+ const encryptionKey = SymmetricKey.fromRandom()
548
+
549
+ // Create a simple transaction (placeholder)
550
+ const transaction = new Transaction()
551
+ // ... transaction setup ...
552
+
553
+ const privateMetadata = {
554
+ purpose: 'Payment to supplier',
555
+ invoiceNumber: 'INV-2024-001',
556
+ notes: 'Quarterly payment for services',
557
+ category: 'business-expense'
558
+ }
559
+
560
+ const encrypted = encryptTransactionMetadata(transaction, privateMetadata, encryptionKey)
561
+ const decrypted = decryptTransactionMetadata(encrypted, encryptionKey)
562
+
563
+ console.log('Original metadata:', privateMetadata)
564
+ console.log('Decrypted metadata:', decrypted)
565
+ ```
566
+
567
+ ### Secure Configuration Storage
568
+
569
+ ```typescript
570
+ class SecureConfig {
571
+ private encryptionKey: SymmetricKey
572
+ private config: Map<string, number[]> = new Map()
573
+
574
+ constructor(password: string) {
575
+ // Derive encryption key from password
576
+ const salt = Random(32)
577
+ const passwordHash = Hash.sha256([
578
+ ...Utils.toArray(password, 'utf8'),
579
+ ...salt
580
+ ])
581
+ this.encryptionKey = new SymmetricKey(passwordHash)
582
+ }
583
+
584
+ set(key: string, value: any): void {
585
+ const jsonValue = JSON.stringify(value)
586
+ const encrypted = this.encryptionKey.encrypt(jsonValue) as number[]
587
+ this.config.set(key, encrypted)
588
+ }
589
+
590
+ get(key: string): any {
591
+ const encrypted = this.config.get(key)
592
+ if (!encrypted) {
593
+ return undefined
594
+ }
595
+
596
+ try {
597
+ const decrypted = this.encryptionKey.decrypt(encrypted, 'utf8') as string
598
+ return JSON.parse(decrypted)
599
+ } catch (error) {
600
+ console.error('Failed to decrypt config value:', error)
601
+ return undefined
602
+ }
603
+ }
604
+
605
+ has(key: string): boolean {
606
+ return this.config.has(key)
607
+ }
608
+
609
+ delete(key: string): boolean {
610
+ return this.config.delete(key)
611
+ }
612
+
613
+ export(): string {
614
+ const exportData = {}
615
+ for (const [key, encrypted] of this.config.entries()) {
616
+ exportData[key] = Utils.toBase64(encrypted)
617
+ }
618
+ return JSON.stringify(exportData)
619
+ }
620
+
621
+ import(data: string): void {
622
+ const importData = JSON.parse(data)
623
+ for (const [key, base64Value] of Object.entries(importData)) {
624
+ const encrypted = Utils.fromBase64(base64Value as string)
625
+ this.config.set(key, encrypted)
626
+ }
627
+ }
628
+ }
629
+
630
+ // Example usage
631
+ const secureConfig = new SecureConfig('MySecurePassword123!')
632
+
633
+ // Store sensitive configuration
634
+ secureConfig.set('apiKey', 'sk-1234567890abcdef')
635
+ secureConfig.set('databaseUrl', 'postgresql://user:pass@host:5432/db')
636
+ secureConfig.set('walletSeed', 'abandon abandon abandon...')
637
+
638
+ // Retrieve configuration
639
+ console.log('API Key:', secureConfig.get('apiKey'))
640
+ console.log('Has database URL:', secureConfig.has('databaseUrl'))
641
+
642
+ // Export encrypted configuration
643
+ const exportedConfig = secureConfig.export()
644
+ console.log('Exported config length:', exportedConfig.length)
645
+ ```
646
+
647
+ ## Performance Considerations
648
+
649
+ ### Benchmarking AES Operations
650
+
651
+ ```typescript
652
+ function benchmarkAESPerformance() {
653
+ const key = SymmetricKey.fromRandom()
654
+ const testSizes = [100, 1000, 10000, 100000] // bytes
655
+
656
+ console.log('=== AES Performance Benchmark ===')
657
+
658
+ for (const size of testSizes) {
659
+ const testData = 'x'.repeat(size)
660
+
661
+ // Benchmark encryption
662
+ const encryptStart = performance.now()
663
+ const encrypted = key.encrypt(testData) as number[]
664
+ const encryptTime = performance.now() - encryptStart
665
+
666
+ // Benchmark decryption
667
+ const decryptStart = performance.now()
668
+ const decrypted = key.decrypt(encrypted, 'utf8') as string
669
+ const decryptTime = performance.now() - decryptStart
670
+
671
+ console.log(`Size: ${size} bytes`)
672
+ console.log(` Encrypt: ${encryptTime.toFixed(2)}ms`)
673
+ console.log(` Decrypt: ${decryptTime.toFixed(2)}ms`)
674
+ console.log(` Total: ${(encryptTime + decryptTime).toFixed(2)}ms`)
675
+ console.log(` Throughput: ${(size / (encryptTime + decryptTime) * 1000).toFixed(0)} bytes/sec`)
676
+ console.log()
677
+ }
678
+ }
679
+
680
+ // Run benchmark
681
+ benchmarkAESPerformance()
682
+ ```
683
+
684
+ ## Security Best Practices
685
+
686
+ ### Key Security Guidelines
687
+
688
+ ```typescript
689
+ class SecureKeyPractices {
690
+ // Good: Generate random keys
691
+ static generateSecureKey(): SymmetricKey {
692
+ return SymmetricKey.fromRandom()
693
+ }
694
+
695
+ // Good: Derive keys from strong passwords
696
+ static deriveFromPassword(password: string, salt: number[]): SymmetricKey {
697
+ if (password.length < 12) {
698
+ throw new Error('Password must be at least 12 characters')
699
+ }
700
+
701
+ const keyMaterial = Hash.sha256([
702
+ ...Utils.toArray(password, 'utf8'),
703
+ ...salt
704
+ ])
705
+ return new SymmetricKey(keyMaterial)
706
+ }
707
+
708
+ // Good: Secure key comparison
709
+ static keysEqual(key1: SymmetricKey, key2: SymmetricKey): boolean {
710
+ const bytes1 = key1.toArray()
711
+ const bytes2 = key2.toArray()
712
+
713
+ if (bytes1.length !== bytes2.length) {
714
+ return false
715
+ }
716
+
717
+ // Constant-time comparison to prevent timing attacks
718
+ let result = 0
719
+ for (let i = 0; i < bytes1.length; i++) {
720
+ result |= bytes1[i] ^ bytes2[i]
721
+ }
722
+ return result === 0
723
+ }
724
+
725
+ // Good: Secure key destruction
726
+ static destroyKey(key: SymmetricKey): void {
727
+ // Overwrite key material (note: this is conceptual in JavaScript)
728
+ const keyArray = key.toArray()
729
+ for (let i = 0; i < keyArray.length; i++) {
730
+ keyArray[i] = 0
731
+ }
732
+ }
733
+ }
734
+
735
+ // Security validation example
736
+ function validateSecurityPractices() {
737
+ console.log('=== Security Practices Validation ===')
738
+
739
+ // Generate secure keys
740
+ const key1 = SecureKeyPractices.generateSecureKey()
741
+ const key2 = SecureKeyPractices.generateSecureKey()
742
+
743
+ console.log('Keys are different:', !SecureKeyPractices.keysEqual(key1, key2))
744
+
745
+ // Test password-based key derivation
746
+ const password = 'MyVerySecurePassword123!'
747
+ const salt = Random(32)
748
+ const derivedKey1 = SecureKeyPractices.deriveFromPassword(password, salt)
749
+ const derivedKey2 = SecureKeyPractices.deriveFromPassword(password, salt)
750
+
751
+ console.log('Derived keys are identical:',
752
+ SecureKeyPractices.keysEqual(derivedKey1, derivedKey2))
753
+
754
+ // Clean up
755
+ SecureKeyPractices.destroyKey(key1)
756
+ SecureKeyPractices.destroyKey(key2)
757
+
758
+ console.log('Security validation complete')
759
+ }
760
+
761
+ validateSecurityPractices()
762
+ ```
763
+
764
+ ## Troubleshooting Common Issues
765
+
766
+ ### Common Problems and Solutions
767
+
768
+ ```typescript
769
+ function troubleshootAESIssues() {
770
+ console.log('=== AES Troubleshooting Guide ===')
771
+
772
+ // Issue 1: Decryption fails with "Decryption failed!" error
773
+ try {
774
+ const key = SymmetricKey.fromRandom()
775
+ const message = 'Test message'
776
+ const encrypted = key.encrypt(message) as number[]
777
+
778
+ // Corrupt the encrypted data to simulate tampering
779
+ encrypted[10] = encrypted[10] ^ 1
780
+
781
+ const decrypted = key.decrypt(encrypted, 'utf8')
782
+ } catch (error) {
783
+ console.log('Detected tampered data:', error.message)
784
+ console.log(' Solution: Verify data integrity, check for transmission errors')
785
+ }
786
+
787
+ // Issue 2: Wrong key used for decryption
788
+ try {
789
+ const key1 = SymmetricKey.fromRandom()
790
+ const key2 = SymmetricKey.fromRandom()
791
+ const message = 'Test message'
792
+
793
+ const encrypted = key1.encrypt(message) as number[]
794
+ const decrypted = key2.decrypt(encrypted, 'utf8') // Wrong key!
795
+ } catch (error) {
796
+ console.log('Detected wrong key usage:', error.message)
797
+ console.log(' Solution: Ensure same key is used for encryption and decryption')
798
+ }
799
+
800
+ // Issue 3: Empty or invalid data
801
+ try {
802
+ const key = SymmetricKey.fromRandom()
803
+ // Empty strings are actually supported and work fine
804
+ const encrypted = key.encrypt('') as number[]
805
+ const decrypted = key.decrypt(encrypted, 'utf8') as string
806
+ console.log('Empty string encryption works:', decrypted === '')
807
+ console.log(' Note: Empty strings are supported by the SDK')
808
+ } catch (error) {
809
+ console.log('Unexpected error with empty data:', error.message)
810
+ }
811
+
812
+ // Issue 4: Hex string handling
813
+ try {
814
+ const hexKey = 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456'
815
+ // This will fail - constructor expects byte array, not hex string
816
+ // const wrongKey = new SymmetricKey(hexKey)
817
+
818
+ // Correct approach - convert hex to bytes first
819
+ const correctKey = new SymmetricKey(hexToBytes(hexKey))
820
+ console.log('Hex key creation works with helper function')
821
+ console.log(' Solution: Use hexToBytes() helper function for hex strings')
822
+ console.log(' Note: SDK does not provide Utils.fromHex() method')
823
+ } catch (error) {
824
+ console.log('Hex key creation issue:', error.message)
825
+ }
826
+
827
+ console.log('\nTroubleshooting complete')
828
+ }
829
+
830
+ troubleshootAESIssues()
831
+ ```
832
+
833
+ ## Testing Your Implementation
834
+
835
+ ### Comprehensive Test Suite
836
+
837
+ ```typescript
838
+ function runAESTests() {
839
+ console.log('=== AES Implementation Tests ===')
840
+ let passed = 0
841
+ let total = 0
842
+
843
+ function test(name: string, testFn: () => boolean) {
844
+ total++
845
+ try {
846
+ const result = testFn()
847
+ if (result) {
848
+ console.log(` ${name}`)
849
+ passed++
850
+ } else {
851
+ console.log(` ${name}`)
852
+ }
853
+ } catch (error) {
854
+ console.log(` ${name} - Error: ${error.message}`)
855
+ }
856
+ }
857
+
858
+ // Test 1: Basic encryption/decryption
859
+ test('Basic encryption/decryption', () => {
860
+ const key = SymmetricKey.fromRandom()
861
+ const message = 'Hello, World!'
862
+ const encrypted = key.encrypt(message) as number[]
863
+ const decrypted = key.decrypt(encrypted, 'utf8') as string
864
+ return message === decrypted
865
+ })
866
+
867
+ // Test 2: Binary data handling
868
+ test('Binary data encryption', () => {
869
+ const key = SymmetricKey.fromRandom()
870
+ const binaryData = Random(100)
871
+ const encrypted = key.encrypt(binaryData) as number[]
872
+ const decrypted = key.decrypt(encrypted) as number[]
873
+ return JSON.stringify(binaryData) === JSON.stringify(decrypted)
874
+ })
875
+
876
+ // Test 3: Large data encryption
877
+ test('Large data encryption', () => {
878
+ const key = SymmetricKey.fromRandom()
879
+ const largeMessage = 'x'.repeat(10000)
880
+ const encrypted = key.encrypt(largeMessage) as number[]
881
+ const decrypted = key.decrypt(encrypted, 'utf8') as string
882
+ return largeMessage === decrypted
883
+ })
884
+
885
+ // Test 4: Key derivation consistency
886
+ test('Key derivation consistency', () => {
887
+ const password = 'TestPassword123'
888
+ const salt = Random(32)
889
+ const key1 = new SymmetricKey(Hash.sha256([
890
+ ...Utils.toArray(password, 'utf8'),
891
+ ...salt
892
+ ]))
893
+ const key2 = new SymmetricKey(Hash.sha256([
894
+ ...Utils.toArray(password, 'utf8'),
895
+ ...salt
896
+ ]))
897
+ return key1.toHex() === key2.toHex()
898
+ })
899
+
900
+ // Test 5: Hex encoding/decoding
901
+ test('Hex encoding support', () => {
902
+ const key = SymmetricKey.fromRandom()
903
+ const message = 'Test message'
904
+ const encryptedBytes = key.encrypt(message) as number[]
905
+ const encrypted = bytesToHex(encryptedBytes)
906
+ const hexBytes = hexToBytes(encrypted)
907
+ const decrypted = key.decrypt(hexBytes, 'utf8') as string
908
+ return message === decrypted
909
+ })
910
+
911
+ console.log(`\nTests completed: ${passed}/${total} passed`)
912
+ return passed === total
913
+ }
914
+
915
+ // Run the test suite
916
+ const allTestsPassed = runAESTests()
917
+ console.log('\nAll tests passed:', allTestsPassed)
918
+ ```
919
+
920
+ ## Summary
921
+
922
+ In this tutorial, you've learned how to:
923
+
924
+ 1. **Generate secure AES encryption keys** using `SymmetricKey.fromRandom()`
925
+ 2. **Encrypt and decrypt data** with the `encrypt()` and `decrypt()` methods
926
+ 3. **Handle different data formats** including strings, binary data, and JSON
927
+ 4. **Derive keys from passwords** using secure hashing techniques
928
+ 5. **Implement key management** with versioning and rotation
929
+ 6. **Combine AES with ECDH** for secure communication channels
930
+ 7. **Apply security best practices** including error handling and validation
931
+ 8. **Build practical applications** like secure configuration storage
932
+ 9. **Optimize performance** and troubleshoot common issues
933
+
934
+ The BSV TypeScript SDK's `SymmetricKey` class provides a robust, secure implementation of AES-GCM encryption that's suitable for production applications. The built-in authentication prevents tampering, while the straightforward API makes it easy to integrate encryption into your Bitcoin applications.
935
+
936
+ ## Next Steps
937
+
938
+ - Explore the [ECDH Key Exchange](./ecdh-key-exchange.md) tutorial for asymmetric encryption
939
+ - Learn about [Messages Reference](../reference/messages.md) for authentication
940
+ - Study [Advanced Transaction Construction](./advanced-transaction.md) for complex applications
941
+ - Review the [Security Best Practices](../guides/security-best-practices.md) guide
942
+
943
+ ## Additional Resources
944
+
945
+ - [AES Wikipedia](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
946
+ - [GCM Mode Wikipedia](https://en.wikipedia.org/wiki/Galois/Counter_Mode)
947
+ - [BSV SDK Primitives Reference](../reference/primitives.md)