@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,948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bee Compatibility Test Vectors
|
|
3
|
+
*
|
|
4
|
+
* These tests verify our ACT cryptographic implementation produces identical
|
|
5
|
+
* outputs to Bee's implementation using test vectors extracted from Bee's codebase.
|
|
6
|
+
*
|
|
7
|
+
* Test vectors from:
|
|
8
|
+
* - bee/pkg/accesscontrol/access_test.go (private keys, lookup keys)
|
|
9
|
+
* - bee/pkg/crypto/dh_test.go (ECDH shared secret)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from "vitest"
|
|
13
|
+
import {
|
|
14
|
+
publicKeyFromPrivate,
|
|
15
|
+
ecdhSharedSecret,
|
|
16
|
+
deriveKeys,
|
|
17
|
+
publicKeyFromCompressed,
|
|
18
|
+
compressPublicKey,
|
|
19
|
+
counterModeEncrypt,
|
|
20
|
+
counterModeDecrypt,
|
|
21
|
+
generateRandomKey,
|
|
22
|
+
} from "./crypto"
|
|
23
|
+
import { findEntryByLookupKey, type ActEntry } from "./act"
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Test Vectors from Bee
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
// Fixed private keys from bee/pkg/accesscontrol/access_test.go
|
|
30
|
+
const BEE_PRIVATE_KEYS = {
|
|
31
|
+
KEY_0: "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa",
|
|
32
|
+
KEY_1: "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb",
|
|
33
|
+
KEY_2: "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Expected lookup keys when Key 1 is the publisher (from setupAccessLogic())
|
|
37
|
+
// See bee/pkg/accesscontrol/access_test.go TestAddNewGranteeToContent
|
|
38
|
+
const BEE_EXPECTED_LOOKUP_KEYS = {
|
|
39
|
+
// ECDH(Key1_priv, Key0_pub) - adding Key 0 as grantee
|
|
40
|
+
KEY1_WITH_KEY0_PUB:
|
|
41
|
+
"b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b",
|
|
42
|
+
// ECDH(Key1_priv, Key1_pub) - publisher self-lookup
|
|
43
|
+
KEY1_SELF: "a13678e81f9d939b9401a3ad7e548d2ceb81c50f8c76424296e83a1ad79c0df0",
|
|
44
|
+
// ECDH(Key1_priv, Key2_pub) - adding Key 2 as grantee
|
|
45
|
+
KEY1_WITH_KEY2_PUB:
|
|
46
|
+
"d5e9a6499ca74f5b8b958a4b89b7338045b2baa9420e115443a8050e26986564",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ECDH test vector from bee/pkg/crypto/dh_test.go
|
|
50
|
+
const BEE_ECDH_TEST = {
|
|
51
|
+
PRIVATE_KEY:
|
|
52
|
+
"c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa",
|
|
53
|
+
PUBLIC_KEY_COMPRESSED:
|
|
54
|
+
"0271e574ad8f6a6c998c84c27df18124fddd906aba9d852150da4223edde14044f",
|
|
55
|
+
SALT: "cb7e692f211f8ae4f858ff56ce8a4fc0e40bae1a36f8283f0ceb6bb4be133f1e",
|
|
56
|
+
EXPECTED_SHARED_KEY:
|
|
57
|
+
"9edbd3beeb48c090158ccb82d679c5ea2bcb74850d34fe55c10b32e16b822007",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Test reference from Bee tests (used for encrypt/decrypt verification)
|
|
61
|
+
const BEE_TEST_REFERENCE =
|
|
62
|
+
"39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559"
|
|
63
|
+
|
|
64
|
+
// Values from actual Bee debug logs for CTR verification
|
|
65
|
+
const BEE_CTR_DEBUG = {
|
|
66
|
+
ENCRYPTED_REF:
|
|
67
|
+
"daae5cf4b3978362e7975548de9139ee6cfd5b4422179b23a55eded1b975ea52",
|
|
68
|
+
ACCESS_KEY:
|
|
69
|
+
"f264f1e4a6c0104a295bc650b80dc38635eb56ba047b6c9b41bd7241cf4bbf99",
|
|
70
|
+
DECRYPTED_REF:
|
|
71
|
+
"ffe4a41dcc711ab78148a37c92599c116a75e2ee02a16cd651eee9a077e7af5b",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// Helper Functions
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert hex string to Uint8Array
|
|
80
|
+
*/
|
|
81
|
+
function hexToBytes(hex: string): Uint8Array {
|
|
82
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex
|
|
83
|
+
const bytes = new Uint8Array(cleanHex.length / 2)
|
|
84
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
85
|
+
bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16)
|
|
86
|
+
}
|
|
87
|
+
return bytes
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert Uint8Array to hex string
|
|
92
|
+
*/
|
|
93
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
94
|
+
return Array.from(bytes)
|
|
95
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
96
|
+
.join("")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// Tests
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
describe("Bee Compatibility Tests", () => {
|
|
104
|
+
describe("Public Key Generation from Private Key", () => {
|
|
105
|
+
it("should generate consistent public key for Key 0", () => {
|
|
106
|
+
const privKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
107
|
+
const pubKey = publicKeyFromPrivate(privKey)
|
|
108
|
+
|
|
109
|
+
// Verify the public key is 32 bytes each for x and y
|
|
110
|
+
expect(pubKey.x.length).toBe(32)
|
|
111
|
+
expect(pubKey.y.length).toBe(32)
|
|
112
|
+
|
|
113
|
+
// Verify roundtrip through compression
|
|
114
|
+
const compressed = compressPublicKey(pubKey.x, pubKey.y)
|
|
115
|
+
const decompressed = publicKeyFromCompressed(compressed)
|
|
116
|
+
expect(decompressed.x).toEqual(pubKey.x)
|
|
117
|
+
expect(decompressed.y).toEqual(pubKey.y)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it("should generate different public keys for each private key", () => {
|
|
121
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
122
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
123
|
+
const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
124
|
+
|
|
125
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
126
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
127
|
+
const pubKey2 = publicKeyFromPrivate(privKey2)
|
|
128
|
+
|
|
129
|
+
expect(bytesToHex(pubKey0.x)).not.toBe(bytesToHex(pubKey1.x))
|
|
130
|
+
expect(bytesToHex(pubKey1.x)).not.toBe(bytesToHex(pubKey2.x))
|
|
131
|
+
expect(bytesToHex(pubKey0.x)).not.toBe(bytesToHex(pubKey2.x))
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe("ECDH Shared Secret", () => {
|
|
136
|
+
it("should compute shared secret with correct length from Bee ECDH test vector keys", () => {
|
|
137
|
+
const privKey = hexToBytes(BEE_ECDH_TEST.PRIVATE_KEY)
|
|
138
|
+
const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
|
|
139
|
+
|
|
140
|
+
// Decompress the public key
|
|
141
|
+
const pubKey = publicKeyFromCompressed(compressedPubKey)
|
|
142
|
+
|
|
143
|
+
// Compute ECDH shared secret (x-coordinate only)
|
|
144
|
+
const sharedSecret = ecdhSharedSecret(privKey, pubKey.x, pubKey.y)
|
|
145
|
+
|
|
146
|
+
expect(sharedSecret.length).toBe(32)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("should correctly decompress and use Bee ECDH test public key", () => {
|
|
150
|
+
// Verify the public key from ECDH test can be decompressed and used
|
|
151
|
+
const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
|
|
152
|
+
const pubKey = publicKeyFromCompressed(compressedPubKey)
|
|
153
|
+
|
|
154
|
+
// Verify coordinates are valid 32-byte values
|
|
155
|
+
expect(pubKey.x.length).toBe(32)
|
|
156
|
+
expect(pubKey.y.length).toBe(32)
|
|
157
|
+
|
|
158
|
+
// Verify the x-coordinate matches what's in the compressed key (bytes 1-32)
|
|
159
|
+
expect(bytesToHex(pubKey.x)).toBe(
|
|
160
|
+
BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED.slice(2),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
// Verify re-compression produces original
|
|
164
|
+
const recompressed = compressPublicKey(pubKey.x, pubKey.y)
|
|
165
|
+
expect(bytesToHex(recompressed)).toBe(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it("should produce symmetric ECDH results between two parties", () => {
|
|
169
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
170
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
171
|
+
|
|
172
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
173
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
174
|
+
|
|
175
|
+
// Key0 computes shared secret with Key1's public
|
|
176
|
+
const shared01 = ecdhSharedSecret(privKey0, pubKey1.x, pubKey1.y)
|
|
177
|
+
|
|
178
|
+
// Key1 computes shared secret with Key0's public
|
|
179
|
+
const shared10 = ecdhSharedSecret(privKey1, pubKey0.x, pubKey0.y)
|
|
180
|
+
|
|
181
|
+
// Both should be equal (ECDH symmetry)
|
|
182
|
+
expect(bytesToHex(shared01)).toBe(bytesToHex(shared10))
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe("Key Derivation - Lookup Keys", () => {
|
|
187
|
+
it("should derive correct lookup key for Publisher (Key 1) with Key 0 public", () => {
|
|
188
|
+
// Bee's setupAccessLogic() uses Key 1 as publisher
|
|
189
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
190
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
191
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
192
|
+
|
|
193
|
+
const { lookupKey } = deriveKeys(privKey1, pubKey0.x, pubKey0.y)
|
|
194
|
+
|
|
195
|
+
expect(bytesToHex(lookupKey)).toBe(
|
|
196
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it("should derive correct lookup key for Publisher (Key 1) self-lookup", () => {
|
|
201
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
202
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
203
|
+
|
|
204
|
+
const { lookupKey } = deriveKeys(privKey1, pubKey1.x, pubKey1.y)
|
|
205
|
+
|
|
206
|
+
expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it("should derive correct lookup key for Publisher (Key 1) with Key 2 public", () => {
|
|
210
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
211
|
+
const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
212
|
+
const pubKey2 = publicKeyFromPrivate(privKey2)
|
|
213
|
+
|
|
214
|
+
const { lookupKey } = deriveKeys(privKey1, pubKey2.x, pubKey2.y)
|
|
215
|
+
|
|
216
|
+
expect(bytesToHex(lookupKey)).toBe(
|
|
217
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe("Symmetric ECDH Property", () => {
|
|
223
|
+
it("should produce same lookup key from both directions (Key0 <-> Key1)", () => {
|
|
224
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
225
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
226
|
+
|
|
227
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
228
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
229
|
+
|
|
230
|
+
// Key 1 derives lookup key with Key 0's public key
|
|
231
|
+
const keys10 = deriveKeys(privKey1, pubKey0.x, pubKey0.y)
|
|
232
|
+
|
|
233
|
+
// Key 0 derives lookup key with Key 1's public key
|
|
234
|
+
const keys01 = deriveKeys(privKey0, pubKey1.x, pubKey1.y)
|
|
235
|
+
|
|
236
|
+
// Both should produce the same lookup key (ECDH symmetry)
|
|
237
|
+
expect(bytesToHex(keys10.lookupKey)).toBe(
|
|
238
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
239
|
+
)
|
|
240
|
+
expect(bytesToHex(keys01.lookupKey)).toBe(
|
|
241
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
242
|
+
)
|
|
243
|
+
expect(bytesToHex(keys10.lookupKey)).toBe(bytesToHex(keys01.lookupKey))
|
|
244
|
+
|
|
245
|
+
// Access key decryption keys should also match
|
|
246
|
+
expect(bytesToHex(keys10.accessKeyDecryptionKey)).toBe(
|
|
247
|
+
bytesToHex(keys01.accessKeyDecryptionKey),
|
|
248
|
+
)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it("should produce same lookup key from both directions (Key1 <-> Key2)", () => {
|
|
252
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
253
|
+
const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
254
|
+
|
|
255
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
256
|
+
const pubKey2 = publicKeyFromPrivate(privKey2)
|
|
257
|
+
|
|
258
|
+
// Key 1 derives lookup key with Key 2's public key
|
|
259
|
+
const keys12 = deriveKeys(privKey1, pubKey2.x, pubKey2.y)
|
|
260
|
+
|
|
261
|
+
// Key 2 derives lookup key with Key 1's public key
|
|
262
|
+
const keys21 = deriveKeys(privKey2, pubKey1.x, pubKey1.y)
|
|
263
|
+
|
|
264
|
+
// Both should produce the same lookup key (ECDH symmetry)
|
|
265
|
+
expect(bytesToHex(keys12.lookupKey)).toBe(
|
|
266
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
267
|
+
)
|
|
268
|
+
expect(bytesToHex(keys21.lookupKey)).toBe(
|
|
269
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
270
|
+
)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it("should produce same lookup key from both directions (Key0 <-> Key2)", () => {
|
|
274
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
275
|
+
const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
276
|
+
|
|
277
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
278
|
+
const pubKey2 = publicKeyFromPrivate(privKey2)
|
|
279
|
+
|
|
280
|
+
// Key0 derives lookup key with Key2's public key
|
|
281
|
+
const keys02 = deriveKeys(privKey0, pubKey2.x, pubKey2.y)
|
|
282
|
+
|
|
283
|
+
// Key2 derives lookup key with Key0's public key
|
|
284
|
+
const keys20 = deriveKeys(privKey2, pubKey0.x, pubKey0.y)
|
|
285
|
+
|
|
286
|
+
// Both should produce the same lookup key (ECDH symmetry)
|
|
287
|
+
expect(bytesToHex(keys02.lookupKey)).toBe(bytesToHex(keys20.lookupKey))
|
|
288
|
+
expect(bytesToHex(keys02.accessKeyDecryptionKey)).toBe(
|
|
289
|
+
bytesToHex(keys20.accessKeyDecryptionKey),
|
|
290
|
+
)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
describe("Public Key Compression/Decompression", () => {
|
|
295
|
+
it("should correctly decompress Bee ECDH test public key", () => {
|
|
296
|
+
const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
|
|
297
|
+
|
|
298
|
+
// Decompress
|
|
299
|
+
const decompressed = publicKeyFromCompressed(compressedPubKey)
|
|
300
|
+
|
|
301
|
+
// Verify x and y are 32 bytes each
|
|
302
|
+
expect(decompressed.x.length).toBe(32)
|
|
303
|
+
expect(decompressed.y.length).toBe(32)
|
|
304
|
+
|
|
305
|
+
// Re-compress and verify it matches original
|
|
306
|
+
const recompressed = compressPublicKey(decompressed.x, decompressed.y)
|
|
307
|
+
expect(bytesToHex(recompressed)).toBe(
|
|
308
|
+
bytesToHex(hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)),
|
|
309
|
+
)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it("should roundtrip compress/decompress for all Bee test keys", () => {
|
|
313
|
+
const privateKeys = [
|
|
314
|
+
BEE_PRIVATE_KEYS.KEY_0,
|
|
315
|
+
BEE_PRIVATE_KEYS.KEY_1,
|
|
316
|
+
BEE_PRIVATE_KEYS.KEY_2,
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
for (const privKeyHex of privateKeys) {
|
|
320
|
+
const privKey = hexToBytes(privKeyHex)
|
|
321
|
+
const pubKey = publicKeyFromPrivate(privKey)
|
|
322
|
+
|
|
323
|
+
// Compress
|
|
324
|
+
const compressed = compressPublicKey(pubKey.x, pubKey.y)
|
|
325
|
+
expect(compressed.length).toBe(33)
|
|
326
|
+
expect([0x02, 0x03]).toContain(compressed[0])
|
|
327
|
+
|
|
328
|
+
// Decompress
|
|
329
|
+
const decompressed = publicKeyFromCompressed(compressed)
|
|
330
|
+
|
|
331
|
+
// Verify roundtrip
|
|
332
|
+
expect(bytesToHex(decompressed.x)).toBe(bytesToHex(pubKey.x))
|
|
333
|
+
expect(bytesToHex(decompressed.y)).toBe(bytesToHex(pubKey.y))
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it("should handle both even and odd y coordinates", () => {
|
|
338
|
+
const privateKeys = [
|
|
339
|
+
BEE_PRIVATE_KEYS.KEY_0,
|
|
340
|
+
BEE_PRIVATE_KEYS.KEY_1,
|
|
341
|
+
BEE_PRIVATE_KEYS.KEY_2,
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
const prefixes: number[] = []
|
|
345
|
+
|
|
346
|
+
for (const privKeyHex of privateKeys) {
|
|
347
|
+
const privKey = hexToBytes(privKeyHex)
|
|
348
|
+
const pubKey = publicKeyFromPrivate(privKey)
|
|
349
|
+
const compressed = compressPublicKey(pubKey.x, pubKey.y)
|
|
350
|
+
prefixes.push(compressed[0])
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Verify we have valid prefixes (0x02 or 0x03)
|
|
354
|
+
for (const prefix of prefixes) {
|
|
355
|
+
expect([0x02, 0x03]).toContain(prefix)
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
describe("Key Derivation Algorithm Verification", () => {
|
|
361
|
+
it("should use correct nonces for lookup key (0x00) and akd key (0x01)", () => {
|
|
362
|
+
const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
363
|
+
const pubKey1 = publicKeyFromPrivate(privKey1)
|
|
364
|
+
|
|
365
|
+
const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
|
|
366
|
+
privKey1,
|
|
367
|
+
pubKey1.x,
|
|
368
|
+
pubKey1.y,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
// lookupKey and accessKeyDecryptionKey should be different
|
|
372
|
+
// (same input but different nonces: 0x00 vs 0x01)
|
|
373
|
+
expect(bytesToHex(lookupKey)).not.toBe(bytesToHex(accessKeyDecryptionKey))
|
|
374
|
+
|
|
375
|
+
// Verify the lookup key matches Bee's expected value for Key1 self-lookup
|
|
376
|
+
expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it("should produce 32-byte keys", () => {
|
|
380
|
+
const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
381
|
+
const pubKey0 = publicKeyFromPrivate(privKey0)
|
|
382
|
+
|
|
383
|
+
const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
|
|
384
|
+
privKey0,
|
|
385
|
+
pubKey0.x,
|
|
386
|
+
pubKey0.y,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
expect(lookupKey.length).toBe(32)
|
|
390
|
+
expect(accessKeyDecryptionKey.length).toBe(32)
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
describe("Reference Encryption/Decryption - Publisher (TestDecryptRef_Publisher equivalent)", () => {
|
|
395
|
+
it("should allow publisher to encrypt and decrypt their own reference", () => {
|
|
396
|
+
// Setup: Key 1 is the publisher
|
|
397
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
398
|
+
const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
|
|
399
|
+
|
|
400
|
+
// Test reference to encrypt
|
|
401
|
+
const testReference = hexToBytes(BEE_TEST_REFERENCE)
|
|
402
|
+
|
|
403
|
+
// Step 1: Generate a random access key
|
|
404
|
+
const accessKey = generateRandomKey()
|
|
405
|
+
expect(accessKey.length).toBe(32)
|
|
406
|
+
|
|
407
|
+
// Step 2: Encrypt the reference with the access key
|
|
408
|
+
const encryptedReference = counterModeEncrypt(testReference, accessKey)
|
|
409
|
+
expect(encryptedReference.length).toBe(testReference.length)
|
|
410
|
+
expect(bytesToHex(encryptedReference)).not.toBe(bytesToHex(testReference))
|
|
411
|
+
|
|
412
|
+
// Step 3: Create ACT entry for publisher (self-lookup)
|
|
413
|
+
// Publisher derives keys with their own public key
|
|
414
|
+
const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
|
|
415
|
+
publisherPrivKey,
|
|
416
|
+
publisherPubKey.x,
|
|
417
|
+
publisherPubKey.y,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
// Verify lookup key matches expected value for Key1 self-lookup
|
|
421
|
+
expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
|
|
422
|
+
|
|
423
|
+
// Encrypt the access key with the accessKeyDecryptionKey
|
|
424
|
+
const encryptedAccessKey = counterModeEncrypt(
|
|
425
|
+
accessKey,
|
|
426
|
+
accessKeyDecryptionKey,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
// Create ACT with single entry for publisher
|
|
430
|
+
const actEntries: ActEntry[] = [
|
|
431
|
+
{
|
|
432
|
+
lookupKey,
|
|
433
|
+
encryptedAccessKey,
|
|
434
|
+
},
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
// Step 4: Publisher looks up their entry in ACT (using array directly)
|
|
438
|
+
const {
|
|
439
|
+
lookupKey: derivedLookupKey,
|
|
440
|
+
accessKeyDecryptionKey: derivedAkdKey,
|
|
441
|
+
} = deriveKeys(publisherPrivKey, publisherPubKey.x, publisherPubKey.y)
|
|
442
|
+
|
|
443
|
+
const entry = findEntryByLookupKey(actEntries, derivedLookupKey)
|
|
444
|
+
expect(entry).toBeDefined()
|
|
445
|
+
|
|
446
|
+
// Step 5: Publisher decrypts the access key
|
|
447
|
+
const decryptedAccessKey = counterModeDecrypt(
|
|
448
|
+
entry!.encryptedAccessKey,
|
|
449
|
+
derivedAkdKey,
|
|
450
|
+
)
|
|
451
|
+
expect(bytesToHex(decryptedAccessKey)).toBe(bytesToHex(accessKey))
|
|
452
|
+
|
|
453
|
+
// Step 6: Publisher decrypts the reference
|
|
454
|
+
const decryptedReference = counterModeDecrypt(
|
|
455
|
+
encryptedReference,
|
|
456
|
+
decryptedAccessKey,
|
|
457
|
+
)
|
|
458
|
+
expect(bytesToHex(decryptedReference)).toBe(BEE_TEST_REFERENCE)
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it("should produce consistent encryption across multiple operations", () => {
|
|
462
|
+
const testReference = hexToBytes(BEE_TEST_REFERENCE)
|
|
463
|
+
|
|
464
|
+
// Use a fixed access key for deterministic testing
|
|
465
|
+
const fixedAccessKey = hexToBytes(
|
|
466
|
+
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
// Encrypt twice with same key should produce same result
|
|
470
|
+
const encrypted1 = counterModeEncrypt(testReference, fixedAccessKey)
|
|
471
|
+
const encrypted2 = counterModeEncrypt(testReference, fixedAccessKey)
|
|
472
|
+
expect(bytesToHex(encrypted1)).toBe(bytesToHex(encrypted2))
|
|
473
|
+
|
|
474
|
+
// Decrypt should return original
|
|
475
|
+
const decrypted = counterModeDecrypt(encrypted1, fixedAccessKey)
|
|
476
|
+
expect(bytesToHex(decrypted)).toBe(BEE_TEST_REFERENCE)
|
|
477
|
+
|
|
478
|
+
// Counter mode is symmetric
|
|
479
|
+
const reencrypted = counterModeEncrypt(decrypted, fixedAccessKey)
|
|
480
|
+
expect(bytesToHex(reencrypted)).toBe(bytesToHex(encrypted1))
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
describe("Reference Encryption/Decryption - Grantee (TestDecryptRefWithGrantee_Success equivalent)", () => {
|
|
485
|
+
it("should allow grantee to decrypt reference shared by publisher", () => {
|
|
486
|
+
// Setup: Key 1 is the publisher, Key 2 is the grantee
|
|
487
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
488
|
+
const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
|
|
489
|
+
|
|
490
|
+
const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
491
|
+
const granteePubKey = publicKeyFromPrivate(granteePrivKey)
|
|
492
|
+
|
|
493
|
+
// Test reference to encrypt
|
|
494
|
+
const testReference = hexToBytes(BEE_TEST_REFERENCE)
|
|
495
|
+
|
|
496
|
+
// Step 1: Publisher generates a random access key
|
|
497
|
+
const accessKey = generateRandomKey()
|
|
498
|
+
|
|
499
|
+
// Step 2: Publisher encrypts the reference with the access key
|
|
500
|
+
const encryptedReference = counterModeEncrypt(testReference, accessKey)
|
|
501
|
+
|
|
502
|
+
// Step 3: Publisher creates ACT entries for themselves AND the grantee
|
|
503
|
+
|
|
504
|
+
// Entry for publisher (self-lookup)
|
|
505
|
+
const publisherKeys = deriveKeys(
|
|
506
|
+
publisherPrivKey,
|
|
507
|
+
publisherPubKey.x,
|
|
508
|
+
publisherPubKey.y,
|
|
509
|
+
)
|
|
510
|
+
const encryptedAccessKeyForPublisher = counterModeEncrypt(
|
|
511
|
+
accessKey,
|
|
512
|
+
publisherKeys.accessKeyDecryptionKey,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
// Entry for grantee (publisher derives with grantee's public key)
|
|
516
|
+
const granteeEntryKeys = deriveKeys(
|
|
517
|
+
publisherPrivKey,
|
|
518
|
+
granteePubKey.x,
|
|
519
|
+
granteePubKey.y,
|
|
520
|
+
)
|
|
521
|
+
const encryptedAccessKeyForGrantee = counterModeEncrypt(
|
|
522
|
+
accessKey,
|
|
523
|
+
granteeEntryKeys.accessKeyDecryptionKey,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
// Verify lookup keys match expected values
|
|
527
|
+
expect(bytesToHex(publisherKeys.lookupKey)).toBe(
|
|
528
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
|
|
529
|
+
)
|
|
530
|
+
expect(bytesToHex(granteeEntryKeys.lookupKey)).toBe(
|
|
531
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
// Create ACT with both entries
|
|
535
|
+
const actEntries: ActEntry[] = [
|
|
536
|
+
{
|
|
537
|
+
lookupKey: publisherKeys.lookupKey,
|
|
538
|
+
encryptedAccessKey: encryptedAccessKeyForPublisher,
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
lookupKey: granteeEntryKeys.lookupKey,
|
|
542
|
+
encryptedAccessKey: encryptedAccessKeyForGrantee,
|
|
543
|
+
},
|
|
544
|
+
]
|
|
545
|
+
|
|
546
|
+
// Step 4: Grantee derives keys using their private key + publisher's public key
|
|
547
|
+
const granteeDerivation = deriveKeys(
|
|
548
|
+
granteePrivKey,
|
|
549
|
+
publisherPubKey.x,
|
|
550
|
+
publisherPubKey.y,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
// Due to ECDH symmetry, grantee's derivation should produce same lookup key
|
|
554
|
+
expect(bytesToHex(granteeDerivation.lookupKey)).toBe(
|
|
555
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
// Step 5: Grantee looks up their entry in ACT
|
|
559
|
+
const granteeEntry = findEntryByLookupKey(
|
|
560
|
+
actEntries,
|
|
561
|
+
granteeDerivation.lookupKey,
|
|
562
|
+
)
|
|
563
|
+
expect(granteeEntry).toBeDefined()
|
|
564
|
+
|
|
565
|
+
// Step 6: Grantee decrypts the access key
|
|
566
|
+
const decryptedAccessKey = counterModeDecrypt(
|
|
567
|
+
granteeEntry!.encryptedAccessKey,
|
|
568
|
+
granteeDerivation.accessKeyDecryptionKey,
|
|
569
|
+
)
|
|
570
|
+
expect(bytesToHex(decryptedAccessKey)).toBe(bytesToHex(accessKey))
|
|
571
|
+
|
|
572
|
+
// Step 7: Grantee decrypts the reference
|
|
573
|
+
const decryptedReference = counterModeDecrypt(
|
|
574
|
+
encryptedReference,
|
|
575
|
+
decryptedAccessKey,
|
|
576
|
+
)
|
|
577
|
+
expect(bytesToHex(decryptedReference)).toBe(BEE_TEST_REFERENCE)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
it("should not allow non-grantee to decrypt reference", () => {
|
|
581
|
+
// Setup: Key 1 is the publisher, Key 2 is the grantee, Key 0 is NOT a grantee
|
|
582
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
583
|
+
const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
|
|
584
|
+
|
|
585
|
+
const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
586
|
+
const granteePubKey = publicKeyFromPrivate(granteePrivKey)
|
|
587
|
+
|
|
588
|
+
const nonGranteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
589
|
+
|
|
590
|
+
// Test reference
|
|
591
|
+
const testReference = hexToBytes(BEE_TEST_REFERENCE)
|
|
592
|
+
|
|
593
|
+
// Publisher creates ACT only for themselves and Key 2 (not Key 0)
|
|
594
|
+
const accessKey = generateRandomKey()
|
|
595
|
+
|
|
596
|
+
// Create entries for publisher and grantee only
|
|
597
|
+
const publisherKeys = deriveKeys(
|
|
598
|
+
publisherPrivKey,
|
|
599
|
+
publisherPubKey.x,
|
|
600
|
+
publisherPubKey.y,
|
|
601
|
+
)
|
|
602
|
+
const granteeEntryKeys = deriveKeys(
|
|
603
|
+
publisherPrivKey,
|
|
604
|
+
granteePubKey.x,
|
|
605
|
+
granteePubKey.y,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
const actEntries: ActEntry[] = [
|
|
609
|
+
{
|
|
610
|
+
lookupKey: publisherKeys.lookupKey,
|
|
611
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
612
|
+
accessKey,
|
|
613
|
+
publisherKeys.accessKeyDecryptionKey,
|
|
614
|
+
),
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
lookupKey: granteeEntryKeys.lookupKey,
|
|
618
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
619
|
+
accessKey,
|
|
620
|
+
granteeEntryKeys.accessKeyDecryptionKey,
|
|
621
|
+
),
|
|
622
|
+
},
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
// Non-grantee (Key 0) tries to derive keys with publisher's public key
|
|
626
|
+
const nonGranteeDerivation = deriveKeys(
|
|
627
|
+
nonGranteePrivKey,
|
|
628
|
+
publisherPubKey.x,
|
|
629
|
+
publisherPubKey.y,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
// Non-grantee's lookup key should NOT match any entry in the ACT
|
|
633
|
+
const nonGranteeEntry = findEntryByLookupKey(
|
|
634
|
+
actEntries,
|
|
635
|
+
nonGranteeDerivation.lookupKey,
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
// Entry should not be found (Key 0 was not added as a grantee)
|
|
639
|
+
expect(nonGranteeEntry).toBeUndefined()
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
it("should allow multiple grantees to independently decrypt", () => {
|
|
643
|
+
// Setup: Key 1 is the publisher, Key 0 and Key 2 are grantees
|
|
644
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
645
|
+
const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
|
|
646
|
+
|
|
647
|
+
const grantee0PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
648
|
+
const grantee0PubKey = publicKeyFromPrivate(grantee0PrivKey)
|
|
649
|
+
|
|
650
|
+
const grantee2PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
651
|
+
const grantee2PubKey = publicKeyFromPrivate(grantee2PrivKey)
|
|
652
|
+
|
|
653
|
+
// Test reference
|
|
654
|
+
const testReference = hexToBytes(BEE_TEST_REFERENCE)
|
|
655
|
+
|
|
656
|
+
// Publisher creates ACT for themselves and both grantees
|
|
657
|
+
const accessKey = generateRandomKey()
|
|
658
|
+
const encryptedReference = counterModeEncrypt(testReference, accessKey)
|
|
659
|
+
|
|
660
|
+
// Derive keys for all parties
|
|
661
|
+
const publisherKeys = deriveKeys(
|
|
662
|
+
publisherPrivKey,
|
|
663
|
+
publisherPubKey.x,
|
|
664
|
+
publisherPubKey.y,
|
|
665
|
+
)
|
|
666
|
+
const grantee0EntryKeys = deriveKeys(
|
|
667
|
+
publisherPrivKey,
|
|
668
|
+
grantee0PubKey.x,
|
|
669
|
+
grantee0PubKey.y,
|
|
670
|
+
)
|
|
671
|
+
const grantee2EntryKeys = deriveKeys(
|
|
672
|
+
publisherPrivKey,
|
|
673
|
+
grantee2PubKey.x,
|
|
674
|
+
grantee2PubKey.y,
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
// Verify lookup keys match expected values
|
|
678
|
+
expect(bytesToHex(publisherKeys.lookupKey)).toBe(
|
|
679
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
|
|
680
|
+
)
|
|
681
|
+
expect(bytesToHex(grantee0EntryKeys.lookupKey)).toBe(
|
|
682
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
683
|
+
)
|
|
684
|
+
expect(bytesToHex(grantee2EntryKeys.lookupKey)).toBe(
|
|
685
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
// Create ACT with all entries
|
|
689
|
+
const actEntries: ActEntry[] = [
|
|
690
|
+
{
|
|
691
|
+
lookupKey: publisherKeys.lookupKey,
|
|
692
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
693
|
+
accessKey,
|
|
694
|
+
publisherKeys.accessKeyDecryptionKey,
|
|
695
|
+
),
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
lookupKey: grantee0EntryKeys.lookupKey,
|
|
699
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
700
|
+
accessKey,
|
|
701
|
+
grantee0EntryKeys.accessKeyDecryptionKey,
|
|
702
|
+
),
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
lookupKey: grantee2EntryKeys.lookupKey,
|
|
706
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
707
|
+
accessKey,
|
|
708
|
+
grantee2EntryKeys.accessKeyDecryptionKey,
|
|
709
|
+
),
|
|
710
|
+
},
|
|
711
|
+
]
|
|
712
|
+
|
|
713
|
+
expect(actEntries.length).toBe(3)
|
|
714
|
+
|
|
715
|
+
// Grantee 0 decrypts
|
|
716
|
+
const grantee0Derivation = deriveKeys(
|
|
717
|
+
grantee0PrivKey,
|
|
718
|
+
publisherPubKey.x,
|
|
719
|
+
publisherPubKey.y,
|
|
720
|
+
)
|
|
721
|
+
const grantee0Entry = findEntryByLookupKey(
|
|
722
|
+
actEntries,
|
|
723
|
+
grantee0Derivation.lookupKey,
|
|
724
|
+
)
|
|
725
|
+
expect(grantee0Entry).toBeDefined()
|
|
726
|
+
|
|
727
|
+
const decryptedAccessKey0 = counterModeDecrypt(
|
|
728
|
+
grantee0Entry!.encryptedAccessKey,
|
|
729
|
+
grantee0Derivation.accessKeyDecryptionKey,
|
|
730
|
+
)
|
|
731
|
+
const decryptedRef0 = counterModeDecrypt(
|
|
732
|
+
encryptedReference,
|
|
733
|
+
decryptedAccessKey0,
|
|
734
|
+
)
|
|
735
|
+
expect(bytesToHex(decryptedRef0)).toBe(BEE_TEST_REFERENCE)
|
|
736
|
+
|
|
737
|
+
// Grantee 2 decrypts
|
|
738
|
+
const grantee2Derivation = deriveKeys(
|
|
739
|
+
grantee2PrivKey,
|
|
740
|
+
publisherPubKey.x,
|
|
741
|
+
publisherPubKey.y,
|
|
742
|
+
)
|
|
743
|
+
const grantee2Entry = findEntryByLookupKey(
|
|
744
|
+
actEntries,
|
|
745
|
+
grantee2Derivation.lookupKey,
|
|
746
|
+
)
|
|
747
|
+
expect(grantee2Entry).toBeDefined()
|
|
748
|
+
|
|
749
|
+
const decryptedAccessKey2 = counterModeDecrypt(
|
|
750
|
+
grantee2Entry!.encryptedAccessKey,
|
|
751
|
+
grantee2Derivation.accessKeyDecryptionKey,
|
|
752
|
+
)
|
|
753
|
+
const decryptedRef2 = counterModeDecrypt(
|
|
754
|
+
encryptedReference,
|
|
755
|
+
decryptedAccessKey2,
|
|
756
|
+
)
|
|
757
|
+
expect(bytesToHex(decryptedRef2)).toBe(BEE_TEST_REFERENCE)
|
|
758
|
+
})
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
describe("ACT Entry Creation - TestAddPublisher equivalent", () => {
|
|
762
|
+
// Equivalent to bee/pkg/accesscontrol/access_test.go:137-161 (TestAddPublisher)
|
|
763
|
+
// This test verifies that adding a publisher entry to the ACT produces
|
|
764
|
+
// the correct lookup key and encrypted access key structure.
|
|
765
|
+
|
|
766
|
+
it("should create publisher ACT entry with correct lookup key", () => {
|
|
767
|
+
// Key 1 is the access logic owner (from setupAccessLogic in Bee)
|
|
768
|
+
// Adding Key 0 as a grantee
|
|
769
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
770
|
+
const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
771
|
+
const granteePubKey = publicKeyFromPrivate(granteePrivKey)
|
|
772
|
+
|
|
773
|
+
// Generate a random access key (simulating what Bee does)
|
|
774
|
+
const accessKey = generateRandomKey()
|
|
775
|
+
|
|
776
|
+
// Publisher derives keys for the grantee
|
|
777
|
+
const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
|
|
778
|
+
publisherPrivKey,
|
|
779
|
+
granteePubKey.x,
|
|
780
|
+
granteePubKey.y,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
// Verify lookup key matches expected value from Bee's test
|
|
784
|
+
expect(bytesToHex(lookupKey)).toBe(
|
|
785
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
// Encrypt the access key
|
|
789
|
+
const encryptedAccessKey = counterModeEncrypt(
|
|
790
|
+
accessKey,
|
|
791
|
+
accessKeyDecryptionKey,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
// Verify encrypted access key has correct length (32 bytes)
|
|
795
|
+
const ENCRYPTED_ACCESS_KEY_LENGTH = 32
|
|
796
|
+
expect(encryptedAccessKey.length).toBe(ENCRYPTED_ACCESS_KEY_LENGTH)
|
|
797
|
+
|
|
798
|
+
// Verify the ACT entry can be created
|
|
799
|
+
const actEntry: ActEntry = {
|
|
800
|
+
lookupKey,
|
|
801
|
+
encryptedAccessKey,
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Verify entry can be found by lookup key
|
|
805
|
+
const found = findEntryByLookupKey([actEntry], lookupKey)
|
|
806
|
+
expect(found).toBeDefined()
|
|
807
|
+
expect(bytesToHex(found!.lookupKey)).toBe(
|
|
808
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
809
|
+
)
|
|
810
|
+
expect(found!.encryptedAccessKey.length).toBe(ENCRYPTED_ACCESS_KEY_LENGTH)
|
|
811
|
+
})
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
describe("ACT Entry Creation - TestAddNewGranteeToContent equivalent", () => {
|
|
815
|
+
// Equivalent to bee/pkg/accesscontrol/access_test.go:163-214 (TestAddNewGranteeToContent)
|
|
816
|
+
// This test verifies that adding multiple grantees to the same ACT produces
|
|
817
|
+
// correct lookup keys for each grantee.
|
|
818
|
+
|
|
819
|
+
it("should add multiple grantees with correct lookup keys", () => {
|
|
820
|
+
// Key 1 is the access logic owner (from setupAccessLogic in Bee)
|
|
821
|
+
const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
|
|
822
|
+
const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
|
|
823
|
+
|
|
824
|
+
const grantee0PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
|
|
825
|
+
const grantee0PubKey = publicKeyFromPrivate(grantee0PrivKey)
|
|
826
|
+
|
|
827
|
+
const grantee2PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
|
|
828
|
+
const grantee2PubKey = publicKeyFromPrivate(grantee2PrivKey)
|
|
829
|
+
|
|
830
|
+
// Generate a random access key (same key used for all grantees)
|
|
831
|
+
const accessKey = generateRandomKey()
|
|
832
|
+
const actEntries: ActEntry[] = []
|
|
833
|
+
|
|
834
|
+
// Add Key 0 as grantee (first addition)
|
|
835
|
+
const keys0 = deriveKeys(
|
|
836
|
+
publisherPrivKey,
|
|
837
|
+
grantee0PubKey.x,
|
|
838
|
+
grantee0PubKey.y,
|
|
839
|
+
)
|
|
840
|
+
expect(bytesToHex(keys0.lookupKey)).toBe(
|
|
841
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
|
|
842
|
+
)
|
|
843
|
+
actEntries.push({
|
|
844
|
+
lookupKey: keys0.lookupKey,
|
|
845
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
846
|
+
accessKey,
|
|
847
|
+
keys0.accessKeyDecryptionKey,
|
|
848
|
+
),
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
// Add Key 1 (self) as grantee (second addition)
|
|
852
|
+
const keys1 = deriveKeys(
|
|
853
|
+
publisherPrivKey,
|
|
854
|
+
publisherPubKey.x,
|
|
855
|
+
publisherPubKey.y,
|
|
856
|
+
)
|
|
857
|
+
expect(bytesToHex(keys1.lookupKey)).toBe(
|
|
858
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
|
|
859
|
+
)
|
|
860
|
+
actEntries.push({
|
|
861
|
+
lookupKey: keys1.lookupKey,
|
|
862
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
863
|
+
accessKey,
|
|
864
|
+
keys1.accessKeyDecryptionKey,
|
|
865
|
+
),
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
// Add Key 2 as grantee (third addition)
|
|
869
|
+
const keys2 = deriveKeys(
|
|
870
|
+
publisherPrivKey,
|
|
871
|
+
grantee2PubKey.x,
|
|
872
|
+
grantee2PubKey.y,
|
|
873
|
+
)
|
|
874
|
+
expect(bytesToHex(keys2.lookupKey)).toBe(
|
|
875
|
+
BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
|
|
876
|
+
)
|
|
877
|
+
actEntries.push({
|
|
878
|
+
lookupKey: keys2.lookupKey,
|
|
879
|
+
encryptedAccessKey: counterModeEncrypt(
|
|
880
|
+
accessKey,
|
|
881
|
+
keys2.accessKeyDecryptionKey,
|
|
882
|
+
),
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
// Verify all 3 entries are created
|
|
886
|
+
expect(actEntries.length).toBe(3)
|
|
887
|
+
|
|
888
|
+
// Verify all encrypted access keys have correct length (32 bytes)
|
|
889
|
+
const ENCRYPTED_ACCESS_KEY_LENGTH = 32
|
|
890
|
+
for (const entry of actEntries) {
|
|
891
|
+
expect(entry.encryptedAccessKey.length).toBe(
|
|
892
|
+
ENCRYPTED_ACCESS_KEY_LENGTH,
|
|
893
|
+
)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Verify all 3 lookup keys are unique
|
|
897
|
+
const lookupKeySet = new Set(
|
|
898
|
+
actEntries.map((e) => bytesToHex(e.lookupKey)),
|
|
899
|
+
)
|
|
900
|
+
expect(lookupKeySet.size).toBe(3)
|
|
901
|
+
|
|
902
|
+
// Verify each lookup key can be found in entries
|
|
903
|
+
const entry0 = findEntryByLookupKey(actEntries, keys0.lookupKey)
|
|
904
|
+
const entry1 = findEntryByLookupKey(actEntries, keys1.lookupKey)
|
|
905
|
+
const entry2 = findEntryByLookupKey(actEntries, keys2.lookupKey)
|
|
906
|
+
|
|
907
|
+
expect(entry0).toBeDefined()
|
|
908
|
+
expect(entry1).toBeDefined()
|
|
909
|
+
expect(entry2).toBeDefined()
|
|
910
|
+
|
|
911
|
+
// Verify each grantee can decrypt the access key and it matches
|
|
912
|
+
const decrypted0 = counterModeDecrypt(
|
|
913
|
+
entry0!.encryptedAccessKey,
|
|
914
|
+
keys0.accessKeyDecryptionKey,
|
|
915
|
+
)
|
|
916
|
+
const decrypted1 = counterModeDecrypt(
|
|
917
|
+
entry1!.encryptedAccessKey,
|
|
918
|
+
keys1.accessKeyDecryptionKey,
|
|
919
|
+
)
|
|
920
|
+
const decrypted2 = counterModeDecrypt(
|
|
921
|
+
entry2!.encryptedAccessKey,
|
|
922
|
+
keys2.accessKeyDecryptionKey,
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
expect(bytesToHex(decrypted0)).toBe(bytesToHex(accessKey))
|
|
926
|
+
expect(bytesToHex(decrypted1)).toBe(bytesToHex(accessKey))
|
|
927
|
+
expect(bytesToHex(decrypted2)).toBe(bytesToHex(accessKey))
|
|
928
|
+
})
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
describe("CTR Mode Verification with Bee Debug Logs", () => {
|
|
932
|
+
it("should decrypt to same value as Bee", () => {
|
|
933
|
+
const ourDecrypted = counterModeEncrypt(
|
|
934
|
+
hexToBytes(BEE_CTR_DEBUG.ENCRYPTED_REF),
|
|
935
|
+
hexToBytes(BEE_CTR_DEBUG.ACCESS_KEY),
|
|
936
|
+
)
|
|
937
|
+
expect(bytesToHex(ourDecrypted)).toBe(BEE_CTR_DEBUG.DECRYPTED_REF)
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
it("should encrypt original to same value Bee received", () => {
|
|
941
|
+
const ourEncrypted = counterModeEncrypt(
|
|
942
|
+
hexToBytes(BEE_CTR_DEBUG.DECRYPTED_REF),
|
|
943
|
+
hexToBytes(BEE_CTR_DEBUG.ACCESS_KEY),
|
|
944
|
+
)
|
|
945
|
+
expect(bytesToHex(ourEncrypted)).toBe(BEE_CTR_DEBUG.ENCRYPTED_REF)
|
|
946
|
+
})
|
|
947
|
+
})
|
|
948
|
+
})
|