@snaha/swarm-id 0.0.1
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/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Copyright 2024 The Swarm Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style
|
|
3
|
+
// license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import { Binary } from "cafe-utility"
|
|
6
|
+
import { Bytes, Reference, Span } from "@ethersphere/bee-js"
|
|
7
|
+
import { calculateChunkAddress } from "./bmt"
|
|
8
|
+
import { MAX_PAYLOAD_SIZE, MIN_PAYLOAD_SIZE } from "./constants"
|
|
9
|
+
import { newChunkEncrypter, decryptChunkData, type Key } from "./encryption"
|
|
10
|
+
|
|
11
|
+
const ENCODER = new TextEncoder()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Encrypted chunk interface
|
|
15
|
+
*
|
|
16
|
+
* The reference includes both the chunk address and the encryption key (64 bytes total)
|
|
17
|
+
*/
|
|
18
|
+
export interface EncryptedChunk {
|
|
19
|
+
readonly data: Uint8Array // encrypted span + encrypted data
|
|
20
|
+
readonly encryptionKey: Key // 32 bytes
|
|
21
|
+
readonly span: Span // original (unencrypted) span
|
|
22
|
+
readonly payload: Bytes // encrypted payload
|
|
23
|
+
readonly address: Reference // BMT hash of encrypted data
|
|
24
|
+
readonly reference: Reference // 64 bytes: address (32) + encryption key (32)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates an encrypted content addressed chunk
|
|
29
|
+
*
|
|
30
|
+
* Process:
|
|
31
|
+
* 1. Create chunk with span + payload
|
|
32
|
+
* 2. Encrypt the chunk data
|
|
33
|
+
* 3. Calculate BMT hash on encrypted data
|
|
34
|
+
* 4. Return reference = address + encryption key (64 bytes)
|
|
35
|
+
*
|
|
36
|
+
* @param payloadBytes the data to be stored in the chunk
|
|
37
|
+
* @param encryptionKey optional encryption key (if not provided, a random key will be generated)
|
|
38
|
+
*/
|
|
39
|
+
export function makeEncryptedContentAddressedChunk(
|
|
40
|
+
payloadBytes: Uint8Array | string,
|
|
41
|
+
encryptionKey?: Key,
|
|
42
|
+
): EncryptedChunk {
|
|
43
|
+
if (!(payloadBytes instanceof Uint8Array)) {
|
|
44
|
+
payloadBytes = ENCODER.encode(payloadBytes)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
payloadBytes.length < MIN_PAYLOAD_SIZE ||
|
|
49
|
+
payloadBytes.length > MAX_PAYLOAD_SIZE
|
|
50
|
+
) {
|
|
51
|
+
throw new RangeError(
|
|
52
|
+
`payload size ${payloadBytes.length} exceeds limits [${MIN_PAYLOAD_SIZE}, ${MAX_PAYLOAD_SIZE}]`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create the original chunk data (span + payload)
|
|
57
|
+
const span = Span.fromBigInt(BigInt(payloadBytes.length))
|
|
58
|
+
const chunkData = Binary.concatBytes(span.toUint8Array(), payloadBytes)
|
|
59
|
+
|
|
60
|
+
// Encrypt the chunk
|
|
61
|
+
const encrypter = newChunkEncrypter()
|
|
62
|
+
const { key, encryptedSpan, encryptedData } = encrypter.encryptChunk(
|
|
63
|
+
chunkData,
|
|
64
|
+
encryptionKey,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Concatenate encrypted span and data
|
|
68
|
+
const encryptedChunkData = Binary.concatBytes(encryptedSpan, encryptedData)
|
|
69
|
+
|
|
70
|
+
// Calculate BMT address on encrypted data
|
|
71
|
+
const address = calculateChunkAddress(encryptedChunkData)
|
|
72
|
+
|
|
73
|
+
// Create 64-byte reference: address (32 bytes) + encryption key (32 bytes)
|
|
74
|
+
const reference = new Reference(
|
|
75
|
+
Binary.concatBytes(address.toUint8Array(), key),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
data: encryptedChunkData,
|
|
80
|
+
encryptionKey: key,
|
|
81
|
+
span,
|
|
82
|
+
payload: new Bytes(encryptedChunkData.slice(Span.LENGTH)),
|
|
83
|
+
address,
|
|
84
|
+
reference,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Decrypts an encrypted chunk given the encryption key
|
|
90
|
+
*
|
|
91
|
+
* @param encryptedChunkData The encrypted chunk data (span + payload)
|
|
92
|
+
* @param encryptionKey The 32-byte encryption key
|
|
93
|
+
*/
|
|
94
|
+
export function decryptEncryptedChunk(
|
|
95
|
+
encryptedChunkData: Uint8Array,
|
|
96
|
+
encryptionKey: Key,
|
|
97
|
+
): Uint8Array {
|
|
98
|
+
return decryptChunkData(encryptionKey, encryptedChunkData)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Extracts encryption key from a 64-byte encrypted reference
|
|
103
|
+
*
|
|
104
|
+
* @param reference 64-byte reference (address + key)
|
|
105
|
+
*/
|
|
106
|
+
export function extractEncryptionKey(reference: Reference): Key {
|
|
107
|
+
const refBytes = reference.toUint8Array()
|
|
108
|
+
if (refBytes.length !== 64) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Invalid encrypted reference length: ${refBytes.length}, expected 64`,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return refBytes.slice(32, 64)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Extracts the chunk address from a 64-byte encrypted reference
|
|
119
|
+
*
|
|
120
|
+
* @param reference 64-byte reference (address + key)
|
|
121
|
+
*/
|
|
122
|
+
export function extractChunkAddress(reference: Reference): Reference {
|
|
123
|
+
const refBytes = reference.toUint8Array()
|
|
124
|
+
if (refBytes.length !== 64) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Invalid encrypted reference length: ${refBytes.length}, expected 64`,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new Reference(refBytes.slice(0, 32))
|
|
131
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for chunk encryption primitives
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest"
|
|
6
|
+
import {
|
|
7
|
+
Encryption,
|
|
8
|
+
generateRandomKey,
|
|
9
|
+
newSpanEncryption,
|
|
10
|
+
newDataEncryption,
|
|
11
|
+
newChunkEncrypter,
|
|
12
|
+
decryptChunkData,
|
|
13
|
+
KEY_LENGTH,
|
|
14
|
+
} from "./encryption"
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Encryption Class Tests
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
describe("Encryption class", () => {
|
|
21
|
+
describe("round-trip encrypt/decrypt", () => {
|
|
22
|
+
it("should round-trip encrypt/decrypt for various data sizes", () => {
|
|
23
|
+
const key = generateRandomKey()
|
|
24
|
+
const padding = 64
|
|
25
|
+
|
|
26
|
+
// Test multiple sizes smaller than padding
|
|
27
|
+
const sizes = [1, 8, 16, 31, 32, 33, 48, 63, 64]
|
|
28
|
+
for (const size of sizes) {
|
|
29
|
+
const data = new Uint8Array(size)
|
|
30
|
+
for (let i = 0; i < size; i++) {
|
|
31
|
+
data[i] = i % 256
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const encrypter = new Encryption(key, padding, 0)
|
|
35
|
+
const encrypted = encrypter.encrypt(data)
|
|
36
|
+
|
|
37
|
+
encrypter.reset()
|
|
38
|
+
const decrypted = encrypter.decrypt(encrypted)
|
|
39
|
+
|
|
40
|
+
// Decrypted data has padding, so compare only original length
|
|
41
|
+
expect(decrypted.slice(0, size)).toEqual(data)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("should pad data smaller than padding size", () => {
|
|
46
|
+
const key = generateRandomKey()
|
|
47
|
+
const padding = 64
|
|
48
|
+
const data = new Uint8Array([1, 2, 3, 4, 5])
|
|
49
|
+
|
|
50
|
+
const encrypter = new Encryption(key, padding, 0)
|
|
51
|
+
const encrypted = encrypter.encrypt(data)
|
|
52
|
+
|
|
53
|
+
expect(encrypted.length).toBe(padding)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should throw error on data larger than padding", () => {
|
|
57
|
+
const key = generateRandomKey()
|
|
58
|
+
const padding = 32
|
|
59
|
+
const data = new Uint8Array(64)
|
|
60
|
+
|
|
61
|
+
const encrypter = new Encryption(key, padding, 0)
|
|
62
|
+
|
|
63
|
+
expect(() => encrypter.encrypt(data)).toThrow(
|
|
64
|
+
/data length .* longer than padding/,
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("should be deterministic (same key + data = same ciphertext)", () => {
|
|
69
|
+
const key = generateRandomKey()
|
|
70
|
+
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
|
71
|
+
|
|
72
|
+
const encrypter1 = new Encryption(key, 32, 0)
|
|
73
|
+
const encrypter2 = new Encryption(key, 32, 0)
|
|
74
|
+
|
|
75
|
+
const encrypted1 = encrypter1.encrypt(data)
|
|
76
|
+
const encrypted2 = encrypter2.encrypt(data)
|
|
77
|
+
|
|
78
|
+
expect(encrypted1).toEqual(encrypted2)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("should produce different ciphertext with different keys", () => {
|
|
82
|
+
const key1 = generateRandomKey()
|
|
83
|
+
const key2 = generateRandomKey()
|
|
84
|
+
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
|
85
|
+
|
|
86
|
+
const encrypter1 = new Encryption(key1, 32, 0)
|
|
87
|
+
const encrypter2 = new Encryption(key2, 32, 0)
|
|
88
|
+
|
|
89
|
+
const encrypted1 = encrypter1.encrypt(data)
|
|
90
|
+
const encrypted2 = encrypter2.encrypt(data)
|
|
91
|
+
|
|
92
|
+
expect(encrypted1).not.toEqual(encrypted2)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("should reset counter correctly", () => {
|
|
96
|
+
const key = generateRandomKey()
|
|
97
|
+
const data = new Uint8Array([1, 2, 3, 4])
|
|
98
|
+
const padding = 32
|
|
99
|
+
|
|
100
|
+
const encrypter = new Encryption(key, padding, 0)
|
|
101
|
+
|
|
102
|
+
// First encryption
|
|
103
|
+
const encrypted1 = encrypter.encrypt(data)
|
|
104
|
+
|
|
105
|
+
// Reset and encrypt again
|
|
106
|
+
encrypter.reset()
|
|
107
|
+
const encrypted2 = encrypter.encrypt(data)
|
|
108
|
+
|
|
109
|
+
// Should produce same result after reset
|
|
110
|
+
expect(encrypted1).toEqual(encrypted2)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("should encrypt without padding when padding is 0", () => {
|
|
114
|
+
const key = generateRandomKey()
|
|
115
|
+
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
|
116
|
+
|
|
117
|
+
const encrypter = new Encryption(key, 0, 0)
|
|
118
|
+
const encrypted = encrypter.encrypt(data)
|
|
119
|
+
|
|
120
|
+
// Without padding, output length equals input length
|
|
121
|
+
expect(encrypted.length).toBe(data.length)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe("key() method", () => {
|
|
126
|
+
it("should return the encryption key", () => {
|
|
127
|
+
const key = generateRandomKey()
|
|
128
|
+
const encrypter = new Encryption(key, 0, 0)
|
|
129
|
+
|
|
130
|
+
expect(encrypter.key()).toBe(key)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe("decrypt validation", () => {
|
|
135
|
+
it("should throw error when decrypt data length differs from padding", () => {
|
|
136
|
+
const key = generateRandomKey()
|
|
137
|
+
const padding = 64
|
|
138
|
+
const wrongSizeData = new Uint8Array(32)
|
|
139
|
+
|
|
140
|
+
const decrypter = new Encryption(key, padding, 0)
|
|
141
|
+
|
|
142
|
+
expect(() => decrypter.decrypt(wrongSizeData)).toThrow(
|
|
143
|
+
/data length .* different than padding/,
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// generateRandomKey Tests
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
describe("generateRandomKey", () => {
|
|
154
|
+
it("should return correct default length (32 bytes)", () => {
|
|
155
|
+
const key = generateRandomKey()
|
|
156
|
+
expect(key.length).toBe(KEY_LENGTH)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("should support custom length", () => {
|
|
160
|
+
const key16 = generateRandomKey(16)
|
|
161
|
+
const key64 = generateRandomKey(64)
|
|
162
|
+
|
|
163
|
+
expect(key16.length).toBe(16)
|
|
164
|
+
expect(key64.length).toBe(64)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("should generate different keys on each call", () => {
|
|
168
|
+
const key1 = generateRandomKey()
|
|
169
|
+
const key2 = generateRandomKey()
|
|
170
|
+
const key3 = generateRandomKey()
|
|
171
|
+
|
|
172
|
+
expect(key1).not.toEqual(key2)
|
|
173
|
+
expect(key2).not.toEqual(key3)
|
|
174
|
+
expect(key1).not.toEqual(key3)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it("should return Uint8Array", () => {
|
|
178
|
+
const key = generateRandomKey()
|
|
179
|
+
expect(key).toBeInstanceOf(Uint8Array)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// newSpanEncryption / newDataEncryption Tests
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
describe("newSpanEncryption", () => {
|
|
188
|
+
it("should return valid encrypter", () => {
|
|
189
|
+
const key = generateRandomKey()
|
|
190
|
+
const encrypter = newSpanEncryption(key)
|
|
191
|
+
|
|
192
|
+
expect(encrypter.key()).toBe(key)
|
|
193
|
+
expect(typeof encrypter.encrypt).toBe("function")
|
|
194
|
+
expect(typeof encrypter.decrypt).toBe("function")
|
|
195
|
+
expect(typeof encrypter.reset).toBe("function")
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it("should encrypt span data (8 bytes)", () => {
|
|
199
|
+
const key = generateRandomKey()
|
|
200
|
+
const span = new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0]) // length 1 in little-endian
|
|
201
|
+
|
|
202
|
+
const encrypter = newSpanEncryption(key)
|
|
203
|
+
const encrypted = encrypter.encrypt(span)
|
|
204
|
+
|
|
205
|
+
// Span encryption has no padding (padding = 0)
|
|
206
|
+
expect(encrypted.length).toBe(8)
|
|
207
|
+
expect(encrypted).not.toEqual(span)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("newDataEncryption", () => {
|
|
212
|
+
it("should return valid encrypter", () => {
|
|
213
|
+
const key = generateRandomKey()
|
|
214
|
+
const encrypter = newDataEncryption(key)
|
|
215
|
+
|
|
216
|
+
expect(encrypter.key()).toBe(key)
|
|
217
|
+
expect(typeof encrypter.encrypt).toBe("function")
|
|
218
|
+
expect(typeof encrypter.decrypt).toBe("function")
|
|
219
|
+
expect(typeof encrypter.reset).toBe("function")
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it("should pad data to 4096 bytes", () => {
|
|
223
|
+
const key = generateRandomKey()
|
|
224
|
+
const data = new Uint8Array([1, 2, 3, 4, 5])
|
|
225
|
+
|
|
226
|
+
const encrypter = newDataEncryption(key)
|
|
227
|
+
const encrypted = encrypter.encrypt(data)
|
|
228
|
+
|
|
229
|
+
expect(encrypted.length).toBe(4096)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// ============================================================================
|
|
234
|
+
// newChunkEncrypter + decryptChunkData Tests
|
|
235
|
+
// ============================================================================
|
|
236
|
+
|
|
237
|
+
describe("newChunkEncrypter + decryptChunkData", () => {
|
|
238
|
+
it("should round-trip chunk encryption/decryption", () => {
|
|
239
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
240
|
+
|
|
241
|
+
// Create chunk data: 8-byte span + payload
|
|
242
|
+
const span = new Uint8Array([5, 0, 0, 0, 0, 0, 0, 0])
|
|
243
|
+
const payload = new Uint8Array([1, 2, 3, 4, 5])
|
|
244
|
+
const chunkData = new Uint8Array(8 + payload.length)
|
|
245
|
+
chunkData.set(span)
|
|
246
|
+
chunkData.set(payload, 8)
|
|
247
|
+
|
|
248
|
+
const { key, encryptedSpan, encryptedData } =
|
|
249
|
+
chunkEncrypter.encryptChunk(chunkData)
|
|
250
|
+
|
|
251
|
+
// Verify encrypted parts
|
|
252
|
+
expect(encryptedSpan.length).toBe(8)
|
|
253
|
+
expect(encryptedData.length).toBe(4096)
|
|
254
|
+
|
|
255
|
+
// Decrypt
|
|
256
|
+
const encryptedChunkData = new Uint8Array(8 + 4096)
|
|
257
|
+
encryptedChunkData.set(encryptedSpan)
|
|
258
|
+
encryptedChunkData.set(encryptedData, 8)
|
|
259
|
+
|
|
260
|
+
const decrypted = decryptChunkData(key, encryptedChunkData)
|
|
261
|
+
|
|
262
|
+
// Verify decryption recovers original data (span + payload portion)
|
|
263
|
+
expect(decrypted.slice(0, 8)).toEqual(span)
|
|
264
|
+
expect(decrypted.slice(8, 8 + payload.length)).toEqual(payload)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it("should produce encrypted span of 8 bytes", () => {
|
|
268
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
269
|
+
const chunkData = new Uint8Array(8 + 100) // span + 100 bytes payload
|
|
270
|
+
|
|
271
|
+
const { encryptedSpan } = chunkEncrypter.encryptChunk(chunkData)
|
|
272
|
+
|
|
273
|
+
expect(encryptedSpan.length).toBe(8)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it("should produce encrypted data padded to 4096 bytes", () => {
|
|
277
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
278
|
+
const chunkData = new Uint8Array(8 + 100) // span + 100 bytes payload
|
|
279
|
+
|
|
280
|
+
const { encryptedData } = chunkEncrypter.encryptChunk(chunkData)
|
|
281
|
+
|
|
282
|
+
expect(encryptedData.length).toBe(4096)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it("should produce total encrypted chunk of 4104 bytes (8 + 4096)", () => {
|
|
286
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
287
|
+
const chunkData = new Uint8Array(8 + 100)
|
|
288
|
+
|
|
289
|
+
const { encryptedSpan, encryptedData } =
|
|
290
|
+
chunkEncrypter.encryptChunk(chunkData)
|
|
291
|
+
|
|
292
|
+
const totalLength = encryptedSpan.length + encryptedData.length
|
|
293
|
+
expect(totalLength).toBe(4104)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it("should use provided encryption key", () => {
|
|
297
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
298
|
+
const customKey = generateRandomKey()
|
|
299
|
+
const chunkData = new Uint8Array(8 + 50)
|
|
300
|
+
|
|
301
|
+
const { key } = chunkEncrypter.encryptChunk(chunkData, customKey)
|
|
302
|
+
|
|
303
|
+
expect(key).toBe(customKey)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it("should generate random key when not provided", () => {
|
|
307
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
308
|
+
const chunkData = new Uint8Array(8 + 50)
|
|
309
|
+
|
|
310
|
+
const result1 = chunkEncrypter.encryptChunk(chunkData)
|
|
311
|
+
const result2 = chunkEncrypter.encryptChunk(chunkData)
|
|
312
|
+
|
|
313
|
+
expect(result1.key).not.toEqual(result2.key)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it("should produce different encrypted output with different keys", () => {
|
|
317
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
318
|
+
const chunkData = new Uint8Array(8 + 50)
|
|
319
|
+
chunkData.fill(42)
|
|
320
|
+
|
|
321
|
+
const key1 = generateRandomKey()
|
|
322
|
+
const key2 = generateRandomKey()
|
|
323
|
+
|
|
324
|
+
const result1 = chunkEncrypter.encryptChunk(chunkData, key1)
|
|
325
|
+
const result2 = chunkEncrypter.encryptChunk(chunkData, key2)
|
|
326
|
+
|
|
327
|
+
expect(result1.encryptedSpan).not.toEqual(result2.encryptedSpan)
|
|
328
|
+
expect(result1.encryptedData).not.toEqual(result2.encryptedData)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it("should handle maximum payload size (4096 bytes data)", () => {
|
|
332
|
+
const chunkEncrypter = newChunkEncrypter()
|
|
333
|
+
const chunkData = new Uint8Array(8 + 4096) // span + max payload
|
|
334
|
+
for (let i = 0; i < chunkData.length; i++) {
|
|
335
|
+
chunkData[i] = i % 256
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const { key, encryptedSpan, encryptedData } =
|
|
339
|
+
chunkEncrypter.encryptChunk(chunkData)
|
|
340
|
+
|
|
341
|
+
expect(encryptedSpan.length).toBe(8)
|
|
342
|
+
expect(encryptedData.length).toBe(4096)
|
|
343
|
+
|
|
344
|
+
// Verify round-trip
|
|
345
|
+
const encryptedChunkData = new Uint8Array(8 + 4096)
|
|
346
|
+
encryptedChunkData.set(encryptedSpan)
|
|
347
|
+
encryptedChunkData.set(encryptedData, 8)
|
|
348
|
+
|
|
349
|
+
const decrypted = decryptChunkData(key, encryptedChunkData)
|
|
350
|
+
expect(decrypted).toEqual(chunkData)
|
|
351
|
+
})
|
|
352
|
+
})
|