@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,376 @@
|
|
|
1
|
+
import { Binary } from "cafe-utility"
|
|
2
|
+
|
|
3
|
+
// secp256k1 curve parameters
|
|
4
|
+
const SECP256K1_P = BigInt(
|
|
5
|
+
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
|
|
6
|
+
)
|
|
7
|
+
const SECP256K1_N = BigInt(
|
|
8
|
+
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
|
9
|
+
)
|
|
10
|
+
const SECP256K1_GX = BigInt(
|
|
11
|
+
"0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
|
12
|
+
)
|
|
13
|
+
const SECP256K1_GY = BigInt(
|
|
14
|
+
"0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Key derivation nonces
|
|
18
|
+
const LOOKUP_KEY_NONCE = 0x00
|
|
19
|
+
const ACCESS_KEY_DECRYPTION_NONCE = 0x01
|
|
20
|
+
|
|
21
|
+
// Size constants
|
|
22
|
+
const PRIVATE_KEY_SIZE = 32
|
|
23
|
+
const PUBLIC_KEY_COORD_SIZE = 32
|
|
24
|
+
const COMPRESSED_PUBLIC_KEY_SIZE = 33
|
|
25
|
+
const KEY_SIZE = 32
|
|
26
|
+
const COUNTER_SIZE = 4
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Modular arithmetic: a mod p (always positive)
|
|
30
|
+
*/
|
|
31
|
+
function mod(a: bigint, p: bigint): bigint {
|
|
32
|
+
const result = a % p
|
|
33
|
+
return result >= 0n ? result : result + p
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extended Euclidean algorithm for modular inverse
|
|
38
|
+
*/
|
|
39
|
+
function modInverse(a: bigint, p: bigint): bigint {
|
|
40
|
+
let [oldR, r] = [mod(a, p), p]
|
|
41
|
+
let [oldS, s] = [1n, 0n]
|
|
42
|
+
|
|
43
|
+
while (r !== 0n) {
|
|
44
|
+
const quotient = oldR / r
|
|
45
|
+
;[oldR, r] = [r, oldR - quotient * r]
|
|
46
|
+
;[oldS, s] = [s, oldS - quotient * s]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (oldR !== 1n) {
|
|
50
|
+
throw new Error("Modular inverse does not exist")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return mod(oldS, p)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Elliptic curve point addition on secp256k1
|
|
58
|
+
* Returns null for point at infinity
|
|
59
|
+
*/
|
|
60
|
+
function ellipticAdd(
|
|
61
|
+
x1: bigint,
|
|
62
|
+
y1: bigint,
|
|
63
|
+
x2: bigint,
|
|
64
|
+
y2: bigint,
|
|
65
|
+
): [bigint, bigint] | null {
|
|
66
|
+
// Handle point at infinity cases
|
|
67
|
+
if (x1 === 0n && y1 === 0n) return [x2, y2]
|
|
68
|
+
if (x2 === 0n && y2 === 0n) return [x1, y1]
|
|
69
|
+
|
|
70
|
+
// If points are inverses, return point at infinity
|
|
71
|
+
if (x1 === x2 && mod(y1 + y2, SECP256K1_P) === 0n) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let slope: bigint
|
|
76
|
+
if (x1 === x2 && y1 === y2) {
|
|
77
|
+
// Point doubling
|
|
78
|
+
const numerator = mod(3n * x1 * x1, SECP256K1_P)
|
|
79
|
+
const denominator = mod(2n * y1, SECP256K1_P)
|
|
80
|
+
slope = mod(numerator * modInverse(denominator, SECP256K1_P), SECP256K1_P)
|
|
81
|
+
} else {
|
|
82
|
+
// Point addition
|
|
83
|
+
const numerator = mod(y2 - y1, SECP256K1_P)
|
|
84
|
+
const denominator = mod(x2 - x1, SECP256K1_P)
|
|
85
|
+
slope = mod(numerator * modInverse(denominator, SECP256K1_P), SECP256K1_P)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const x3 = mod(slope * slope - x1 - x2, SECP256K1_P)
|
|
89
|
+
const y3 = mod(slope * (x1 - x3) - y1, SECP256K1_P)
|
|
90
|
+
|
|
91
|
+
return [x3, y3]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Elliptic curve point doubling (special case of addition)
|
|
96
|
+
*/
|
|
97
|
+
function ellipticDouble(x: bigint, y: bigint): [bigint, bigint] | null {
|
|
98
|
+
return ellipticAdd(x, y, x, y)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Scalar multiplication using double-and-add algorithm
|
|
103
|
+
*/
|
|
104
|
+
function scalarMultiply(
|
|
105
|
+
px: bigint,
|
|
106
|
+
py: bigint,
|
|
107
|
+
scalar: bigint,
|
|
108
|
+
): [bigint, bigint] | null {
|
|
109
|
+
if (scalar === 0n) return null
|
|
110
|
+
|
|
111
|
+
let result: [bigint, bigint] | null = null
|
|
112
|
+
let current: [bigint, bigint] | null = [px, py]
|
|
113
|
+
|
|
114
|
+
let s = mod(scalar, SECP256K1_N)
|
|
115
|
+
while (s > 0n) {
|
|
116
|
+
if (s & 1n) {
|
|
117
|
+
if (result === null) {
|
|
118
|
+
result = current
|
|
119
|
+
} else if (current !== null) {
|
|
120
|
+
result = ellipticAdd(result[0], result[1], current[0], current[1])
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (current !== null) {
|
|
124
|
+
current = ellipticDouble(current[0], current[1])
|
|
125
|
+
}
|
|
126
|
+
s >>= 1n
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert Uint8Array to bigint (big-endian)
|
|
134
|
+
*/
|
|
135
|
+
function uint8ArrayToBigInt(bytes: Uint8Array): bigint {
|
|
136
|
+
let result = 0n
|
|
137
|
+
for (const byte of bytes) {
|
|
138
|
+
result = (result << 8n) | BigInt(byte)
|
|
139
|
+
}
|
|
140
|
+
return result
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Convert bigint to Uint8Array (big-endian, fixed size)
|
|
145
|
+
*/
|
|
146
|
+
function bigIntToUint8Array(value: bigint, size: number): Uint8Array {
|
|
147
|
+
const result = new Uint8Array(size)
|
|
148
|
+
let v = value
|
|
149
|
+
for (let i = size - 1; i >= 0; i--) {
|
|
150
|
+
result[i] = Number(v & 0xffn)
|
|
151
|
+
v >>= 8n
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Derive public key from private key
|
|
158
|
+
*/
|
|
159
|
+
export function publicKeyFromPrivate(privKey: Uint8Array): {
|
|
160
|
+
x: Uint8Array
|
|
161
|
+
y: Uint8Array
|
|
162
|
+
} {
|
|
163
|
+
if (privKey.length !== PRIVATE_KEY_SIZE) {
|
|
164
|
+
throw new Error(`Private key must be ${PRIVATE_KEY_SIZE} bytes`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const scalar = uint8ArrayToBigInt(privKey)
|
|
168
|
+
const point = scalarMultiply(SECP256K1_GX, SECP256K1_GY, scalar)
|
|
169
|
+
|
|
170
|
+
if (point === null) {
|
|
171
|
+
throw new Error("Invalid private key")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
x: bigIntToUint8Array(point[0], PUBLIC_KEY_COORD_SIZE),
|
|
176
|
+
y: bigIntToUint8Array(point[1], PUBLIC_KEY_COORD_SIZE),
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Compute ECDH shared secret (x-coordinate of shared point)
|
|
182
|
+
*/
|
|
183
|
+
export function ecdhSharedSecret(
|
|
184
|
+
privKey: Uint8Array,
|
|
185
|
+
pubX: Uint8Array,
|
|
186
|
+
pubY: Uint8Array,
|
|
187
|
+
): Uint8Array {
|
|
188
|
+
if (privKey.length !== PRIVATE_KEY_SIZE) {
|
|
189
|
+
throw new Error(`Private key must be ${PRIVATE_KEY_SIZE} bytes`)
|
|
190
|
+
}
|
|
191
|
+
if (
|
|
192
|
+
pubX.length !== PUBLIC_KEY_COORD_SIZE ||
|
|
193
|
+
pubY.length !== PUBLIC_KEY_COORD_SIZE
|
|
194
|
+
) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Public key coordinates must be ${PUBLIC_KEY_COORD_SIZE} bytes each`,
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const scalar = uint8ArrayToBigInt(privKey)
|
|
201
|
+
const px = uint8ArrayToBigInt(pubX)
|
|
202
|
+
const py = uint8ArrayToBigInt(pubY)
|
|
203
|
+
|
|
204
|
+
const point = scalarMultiply(px, py, scalar)
|
|
205
|
+
if (point === null) {
|
|
206
|
+
throw new Error("ECDH computation resulted in point at infinity")
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return bigIntToUint8Array(point[0], PUBLIC_KEY_COORD_SIZE)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Derive lookup key and access key decryption key from ECDH shared secret
|
|
214
|
+
*/
|
|
215
|
+
export function deriveKeys(
|
|
216
|
+
privKey: Uint8Array,
|
|
217
|
+
pubX: Uint8Array,
|
|
218
|
+
pubY: Uint8Array,
|
|
219
|
+
): { lookupKey: Uint8Array; accessKeyDecryptionKey: Uint8Array } {
|
|
220
|
+
const sharedSecret = ecdhSharedSecret(privKey, pubX, pubY)
|
|
221
|
+
|
|
222
|
+
// lookupKey = keccak256(sharedX || 0x00)
|
|
223
|
+
const lookupKeyInput = new Uint8Array(sharedSecret.length + 1)
|
|
224
|
+
lookupKeyInput.set(sharedSecret)
|
|
225
|
+
lookupKeyInput[sharedSecret.length] = LOOKUP_KEY_NONCE
|
|
226
|
+
const lookupKey = Binary.keccak256(lookupKeyInput)
|
|
227
|
+
|
|
228
|
+
// accessKeyDecryptionKey = keccak256(sharedX || 0x01)
|
|
229
|
+
const akdKeyInput = new Uint8Array(sharedSecret.length + 1)
|
|
230
|
+
akdKeyInput.set(sharedSecret)
|
|
231
|
+
akdKeyInput[sharedSecret.length] = ACCESS_KEY_DECRYPTION_NONCE
|
|
232
|
+
const accessKeyDecryptionKey = Binary.keccak256(akdKeyInput)
|
|
233
|
+
|
|
234
|
+
return { lookupKey, accessKeyDecryptionKey }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Counter-mode encryption/decryption
|
|
239
|
+
* Matches Bee's Go implementation (bee/pkg/encryption/encryption.go:134-168)
|
|
240
|
+
* For each 32-byte block i: data[i] XOR keccak256(keccak256(key || uint32LE(i)))
|
|
241
|
+
*/
|
|
242
|
+
export function counterModeEncrypt(
|
|
243
|
+
data: Uint8Array,
|
|
244
|
+
key: Uint8Array,
|
|
245
|
+
): Uint8Array {
|
|
246
|
+
if (key.length !== KEY_SIZE) {
|
|
247
|
+
throw new Error(`Key must be ${KEY_SIZE} bytes`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const result = new Uint8Array(data.length)
|
|
251
|
+
const numBlocks = Math.ceil(data.length / KEY_SIZE)
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < numBlocks; i++) {
|
|
254
|
+
// Create counter input: key || uint32LE(i)
|
|
255
|
+
// Must match Bee's Go implementation which uses binary.LittleEndian.PutUint32
|
|
256
|
+
const counterInput = new Uint8Array(key.length + COUNTER_SIZE)
|
|
257
|
+
counterInput.set(key)
|
|
258
|
+
// LITTLE ENDIAN counter (matches Bee's binary.LittleEndian.PutUint32)
|
|
259
|
+
counterInput[key.length] = i & 0xff
|
|
260
|
+
counterInput[key.length + 1] = (i >> 8) & 0xff
|
|
261
|
+
counterInput[key.length + 2] = (i >> 16) & 0xff
|
|
262
|
+
counterInput[key.length + 3] = (i >> 24) & 0xff
|
|
263
|
+
|
|
264
|
+
// First hash: keccak256(key || counter)
|
|
265
|
+
const ctrHash = Binary.keccak256(counterInput)
|
|
266
|
+
|
|
267
|
+
// Second hash for "selective disclosure" (matches Bee's implementation)
|
|
268
|
+
const keyStream = Binary.keccak256(ctrHash)
|
|
269
|
+
|
|
270
|
+
// XOR data block with keystream
|
|
271
|
+
const blockStart = i * KEY_SIZE
|
|
272
|
+
const blockEnd = Math.min(blockStart + KEY_SIZE, data.length)
|
|
273
|
+
|
|
274
|
+
for (let j = blockStart; j < blockEnd; j++) {
|
|
275
|
+
result[j] = data[j] ^ keyStream[j - blockStart]
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Counter-mode decryption (symmetric with encryption)
|
|
284
|
+
*/
|
|
285
|
+
export const counterModeDecrypt = counterModeEncrypt
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Parse compressed public key (33 bytes) to uncompressed coordinates
|
|
289
|
+
*/
|
|
290
|
+
export function publicKeyFromCompressed(compressed: Uint8Array): {
|
|
291
|
+
x: Uint8Array
|
|
292
|
+
y: Uint8Array
|
|
293
|
+
} {
|
|
294
|
+
if (compressed.length !== COMPRESSED_PUBLIC_KEY_SIZE) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Compressed public key must be ${COMPRESSED_PUBLIC_KEY_SIZE} bytes`,
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const prefix = compressed[0]
|
|
301
|
+
if (prefix !== 0x02 && prefix !== 0x03) {
|
|
302
|
+
throw new Error("Invalid compressed public key prefix")
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const x = uint8ArrayToBigInt(compressed.slice(1))
|
|
306
|
+
|
|
307
|
+
// y² = x³ + 7 (mod p)
|
|
308
|
+
const ySquared = mod(x * x * x + 7n, SECP256K1_P)
|
|
309
|
+
|
|
310
|
+
// Compute modular square root using Tonelli-Shanks
|
|
311
|
+
// For secp256k1, p ≡ 3 (mod 4), so y = (y²)^((p+1)/4) mod p
|
|
312
|
+
const exponent = (SECP256K1_P + 1n) / 4n
|
|
313
|
+
let y = modPow(ySquared, exponent, SECP256K1_P)
|
|
314
|
+
|
|
315
|
+
// Check parity and adjust if needed
|
|
316
|
+
const isEven = (y & 1n) === 0n
|
|
317
|
+
const shouldBeEven = prefix === 0x02
|
|
318
|
+
if (isEven !== shouldBeEven) {
|
|
319
|
+
y = SECP256K1_P - y
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
x: bigIntToUint8Array(x, PUBLIC_KEY_COORD_SIZE),
|
|
324
|
+
y: bigIntToUint8Array(y, PUBLIC_KEY_COORD_SIZE),
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Compress public key to 33 bytes
|
|
330
|
+
*/
|
|
331
|
+
export function compressPublicKey(x: Uint8Array, y: Uint8Array): Uint8Array {
|
|
332
|
+
if (
|
|
333
|
+
x.length !== PUBLIC_KEY_COORD_SIZE ||
|
|
334
|
+
y.length !== PUBLIC_KEY_COORD_SIZE
|
|
335
|
+
) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Public key coordinates must be ${PUBLIC_KEY_COORD_SIZE} bytes each`,
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const yBigInt = uint8ArrayToBigInt(y)
|
|
342
|
+
const prefix = (yBigInt & 1n) === 0n ? 0x02 : 0x03
|
|
343
|
+
|
|
344
|
+
const result = new Uint8Array(COMPRESSED_PUBLIC_KEY_SIZE)
|
|
345
|
+
result[0] = prefix
|
|
346
|
+
result.set(x, 1)
|
|
347
|
+
|
|
348
|
+
return result
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Modular exponentiation using square-and-multiply
|
|
353
|
+
*/
|
|
354
|
+
function modPow(base: bigint, exponent: bigint, modulus: bigint): bigint {
|
|
355
|
+
let result = 1n
|
|
356
|
+
base = mod(base, modulus)
|
|
357
|
+
|
|
358
|
+
while (exponent > 0n) {
|
|
359
|
+
if (exponent & 1n) {
|
|
360
|
+
result = mod(result * base, modulus)
|
|
361
|
+
}
|
|
362
|
+
exponent >>= 1n
|
|
363
|
+
base = mod(base * base, modulus)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return result
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Generate a random 32-byte key using crypto.getRandomValues
|
|
371
|
+
*/
|
|
372
|
+
export function generateRandomKey(): Uint8Array {
|
|
373
|
+
const key = new Uint8Array(KEY_SIZE)
|
|
374
|
+
crypto.getRandomValues(key)
|
|
375
|
+
return key
|
|
376
|
+
}
|