@interest-protocol/vortex-sdk 11.3.0 → 12.0.3

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.
@@ -1,9 +1,9 @@
1
- import { PaginatedEvents } from '@mysten/sui/client';
2
- import { Commitment } from '../vortex-api.types';
3
- import { VortexKeypair } from '../entities/keypair';
1
+ import type { PaginatedEvents } from '@mysten/sui/client';
2
+ import type { Commitment } from '../vortex-api.types';
3
+ import type { VortexKeypair } from '../entities/keypair';
4
+ import type { Vortex } from '../vortex';
5
+ import type { VortexPool } from '../vortex.types';
4
6
  import { Utxo } from '../entities/utxo';
5
- import { Vortex } from '../vortex';
6
- import { VortexPool } from '../vortex.types';
7
7
  interface GetUnspentUtxosArgs {
8
8
  commitmentEvents: PaginatedEvents;
9
9
  vortexKeypair: VortexKeypair;
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/utils/decrypt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,UAAU,mBAAmB;IAC3B,gBAAgB,EAAE,eAAe,CAAC;IAClC,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,eAAO,MAAM,eAAe,GAAU,6DAKnC,mBAAmB,oBAkCrB,CAAC;AAEF,UAAU,0BAA0B;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,UAAU,wCAAwC;IAChD,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,iBAAiB,CAAC,EAAE,CAAC;IAChE,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,eAAO,MAAM,sBAAsB,GAAU,wDAK1C,0BAA0B,oBA2C5B,CAAC;AAEF,eAAO,MAAM,oCAAoC,GAAU,wDAKxD,wCAAwC;;;EAmD1C,CAAC"}
1
+ {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/utils/decrypt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAIxC,UAAU,mBAAmB;IAC3B,gBAAgB,EAAE,eAAe,CAAC;IAClC,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,eAAO,MAAM,eAAe,GAAU,6DAKnC,mBAAmB,oBAkCrB,CAAC;AAEF,UAAU,0BAA0B;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,UAAU,wCAAwC;IAChD,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,iBAAiB,CAAC,EAAE,CAAC;IAChE,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CACjC;AAED,eAAO,MAAM,sBAAsB,GAAU,wDAK1C,0BAA0B,oBA2C5B,CAAC;AAEF,eAAO,MAAM,oCAAoC,GAAU,wDAKxD,wCAAwC;;;EAgD1C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"vortex-api.d.ts","sourceRoot":"","sources":["../src/vortex-api.ts"],"names":[],"mappings":"AACA,OAAO,EACL,wBAAwB,EAExB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,eAAe,EAGhB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,SAAS;;gBAGR,EAAE,MAAuB,EAAE,GAAE,wBAA6B;IAIhE,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAQjC,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiB7D,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAenE,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAsBtE,QAAQ,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyBzD,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA4BtE,iBAAiB,CACrB,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5D,OAAO,CAAC,UAAU,EAAE,CAAC;IAoBlB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBnE,kBAAkB,CACtB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,mBAAmB,CAAC;IAczB,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;CAuD7C;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
1
+ {"version":3,"file":"vortex-api.d.ts","sourceRoot":"","sources":["../src/vortex-api.ts"],"names":[],"mappings":"AACA,OAAO,EACL,wBAAwB,EAExB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,eAAe,EAGhB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,SAAS;;gBAGR,EAAE,MAAuB,EAAE,GAAE,wBAA6B;IAIhE,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAQjC,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiB7D,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAenE,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAsBtE,QAAQ,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyBzD,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA4BtE,iBAAiB,CACrB,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5D,OAAO,CAAC,UAAU,EAAE,CAAC;IAwBlB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBnE,kBAAkB,CACtB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,mBAAmB,CAAC;IAczB,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;CAuD7C;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interest-protocol/vortex-sdk",
3
- "version": "11.3.0",
3
+ "version": "12.0.3",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "module": "./dist/index.mjs",
@@ -17,8 +17,8 @@
17
17
  "author": "",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "@mysten/sui": "^1.36.2",
21
- "@noble/ciphers": "^2.0.1",
20
+ "@mysten/sui": "^1.45.2",
21
+ "@noble/ciphers": "^2.1.1",
22
22
  "@noble/curves": "^2.0.1",
23
23
  "@noble/hashes": "^2.0.1",
24
24
  "@polymedia/suitcase-core": "^0.0.67",
@@ -29,16 +29,16 @@
29
29
  "@interest-protocol/sui-core-sdk": "1.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/bn.js": "^5.1.6",
33
- "@types/jest": "^29.5.5",
32
+ "@types/bn.js": "^5.2.0",
33
+ "@types/jest": "^29.5.14",
34
34
  "@types/ramda": "^0.30.2",
35
35
  "jest": "^29.7.0",
36
36
  "jest-environment-node": "^29.7.0",
37
- "rimraf": "^6.0.1",
38
- "ts-jest": "^29.1.1",
39
- "tsup": "^8.3.5",
40
- "@interest-protocol/prettier-config": "1.0.0",
41
- "@interest-protocol/typescript-config": "1.0.0"
37
+ "rimraf": "^6.1.2",
38
+ "ts-jest": "^29.4.6",
39
+ "tsup": "^8.5.1",
40
+ "@interest-protocol/typescript-config": "1.0.0",
41
+ "@interest-protocol/prettier-config": "1.0.0"
42
42
  },
43
43
  "publishConfig": {
44
44
  "access": "public",
@@ -108,8 +108,165 @@ describe(VortexKeypair.name, () => {
108
108
  keypair1.encryptionKey
109
109
  );
110
110
 
111
- // Attempting to decrypt with wrong keypair should throw or produce garbage
112
- expect(() => keypair2.decryptUtxo(encrypted)).toThrow();
111
+ // Attempting to decrypt with wrong keypair should throw
112
+ expect(() => keypair2.decryptUtxo(encrypted)).toThrow(
113
+ 'Decryption failed: HMAC verification failed'
114
+ );
115
+ });
116
+
117
+ it('should ALWAYS fail with wrong keypair - 100 iterations', () => {
118
+ // This test verifies the key-committing property of HMAC
119
+ // With the old Poly1305, this could sometimes succeed with garbage data
120
+ // With HMAC-SHA256, it should ALWAYS fail
121
+
122
+ const correctKeypair = VortexKeypair.generate();
123
+ const utxo = {
124
+ amount: 1000000n,
125
+ blinding: 123456789n,
126
+ index: 42n,
127
+ vortexPool:
128
+ '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
129
+ };
130
+
131
+ const encrypted = VortexKeypair.encryptUtxoFor(
132
+ utxo,
133
+ correctKeypair.encryptionKey
134
+ );
135
+
136
+ let failureCount = 0;
137
+ const iterations = 100;
138
+
139
+ for (let i = 0; i < iterations; i++) {
140
+ const wrongKeypair = VortexKeypair.generate();
141
+ try {
142
+ wrongKeypair.decryptUtxo(encrypted);
143
+ // If we get here, decryption "succeeded" (BUG!)
144
+ } catch {
145
+ failureCount++;
146
+ }
147
+ }
148
+
149
+ // ALL attempts must fail - this proves the key-committing property
150
+ expect(failureCount).toBe(iterations);
151
+ });
152
+
153
+ it('should throw specific HMAC error message on wrong key', () => {
154
+ const keypair1 = VortexKeypair.generate();
155
+ const keypair2 = VortexKeypair.generate();
156
+
157
+ const utxo = {
158
+ amount: 500n,
159
+ blinding: 12345n,
160
+ index: 1n,
161
+ vortexPool: '0xabc',
162
+ };
163
+
164
+ const encrypted = VortexKeypair.encryptUtxoFor(
165
+ utxo,
166
+ keypair1.encryptionKey
167
+ );
168
+
169
+ expect(() => keypair2.decryptUtxo(encrypted)).toThrow(
170
+ 'HMAC verification failed'
171
+ );
172
+ });
173
+
174
+ it('should decrypt correctly with the right keypair after failed attempts', () => {
175
+ const correctKeypair = VortexKeypair.generate();
176
+ const utxo = {
177
+ amount: 9999n,
178
+ blinding: 8888n,
179
+ index: 7n,
180
+ vortexPool: '0xdef',
181
+ };
182
+
183
+ const encrypted = VortexKeypair.encryptUtxoFor(
184
+ utxo,
185
+ correctKeypair.encryptionKey
186
+ );
187
+
188
+ // Try 10 wrong keypairs first
189
+ for (let i = 0; i < 10; i++) {
190
+ const wrongKeypair = VortexKeypair.generate();
191
+ expect(() => wrongKeypair.decryptUtxo(encrypted)).toThrow();
192
+ }
193
+
194
+ // Now decrypt with correct keypair - should still work
195
+ const decrypted = correctKeypair.decryptUtxo(encrypted);
196
+ expect(decrypted.amount).toBe(utxo.amount);
197
+ expect(decrypted.blinding).toBe(utxo.blinding);
198
+ expect(decrypted.index).toBe(utxo.index);
199
+ });
200
+
201
+ it('should handle large amounts correctly', () => {
202
+ const keypair = VortexKeypair.generate();
203
+ const utxo = {
204
+ amount: BigInt('999999999999999999999999999'),
205
+ blinding: BigInt('888888888888888888888888888'),
206
+ index: BigInt('77777777777'),
207
+ vortexPool:
208
+ '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
209
+ };
210
+
211
+ const encrypted = VortexKeypair.encryptUtxoFor(
212
+ utxo,
213
+ keypair.encryptionKey
214
+ );
215
+ const decrypted = keypair.decryptUtxo(encrypted);
216
+
217
+ expect(decrypted.amount).toBe(utxo.amount);
218
+ expect(decrypted.blinding).toBe(utxo.blinding);
219
+ expect(decrypted.index).toBe(utxo.index);
220
+ });
221
+
222
+ it('should handle zero values correctly', () => {
223
+ const keypair = VortexKeypair.generate();
224
+ const utxo = {
225
+ amount: 0n,
226
+ blinding: 0n,
227
+ index: 0n,
228
+ vortexPool: '0x0',
229
+ };
230
+
231
+ const encrypted = VortexKeypair.encryptUtxoFor(
232
+ utxo,
233
+ keypair.encryptionKey
234
+ );
235
+ const decrypted = keypair.decryptUtxo(encrypted);
236
+
237
+ expect(decrypted.amount).toBe(utxo.amount);
238
+ expect(decrypted.blinding).toBe(utxo.blinding);
239
+ expect(decrypted.index).toBe(utxo.index);
240
+ });
241
+
242
+ it('should produce different ciphertexts for same data (random nonce)', () => {
243
+ const keypair = VortexKeypair.generate();
244
+ const utxo = {
245
+ amount: 1000n,
246
+ blinding: 999n,
247
+ index: 5n,
248
+ vortexPool: '0x123',
249
+ };
250
+
251
+ const encrypted1 = VortexKeypair.encryptUtxoFor(
252
+ utxo,
253
+ keypair.encryptionKey
254
+ );
255
+ const encrypted2 = VortexKeypair.encryptUtxoFor(
256
+ utxo,
257
+ keypair.encryptionKey
258
+ );
259
+
260
+ // Different ciphertexts due to random nonce
261
+ expect(encrypted1).not.toBe(encrypted2);
262
+
263
+ // But both decrypt to same data
264
+ const decrypted1 = keypair.decryptUtxo(encrypted1);
265
+ const decrypted2 = keypair.decryptUtxo(encrypted2);
266
+
267
+ expect(decrypted1.amount).toBe(decrypted2.amount);
268
+ expect(decrypted1.blinding).toBe(decrypted2.blinding);
269
+ expect(decrypted1.index).toBe(decrypted2.index);
113
270
  });
114
271
  });
115
272
 
package/src/constants.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { SUI_TYPE_ARG } from '@mysten/sui/utils';
2
-
3
1
  export const BN254_FIELD_MODULUS =
4
2
  21888242871839275222246405745257275088548364400416034343698204186575808495617n;
5
3
 
@@ -73,24 +71,24 @@ export const ERROR_CODES = {
73
71
  };
74
72
 
75
73
  export const VORTEX_PACKAGE_ID =
76
- '0xcf81b96e392f82b776ee980108357426b726c4043c838822545a307e12c5ded6';
74
+ '0xd9d3b65c318e7d7dd208050a28e113a45256765b4c45acd119626d8a228d7555';
77
75
 
78
76
  export const VORTEX_UPGRADE_CAP =
79
- '0xc2d1925fd45559e09c51f5491ec96d61f9e9108c967d34fe22a053c1b307ddfc';
77
+ '0xa0d5c1dec2ad7c732cf8d01378eba8fc1451841b051fa0292d3ad07ce92aad80';
80
78
 
81
79
  export const REGISTRY_OBJECT_ID =
82
- '0xf2c11c297e0581e0279714f6ba47e26d03d9a70756036fab5882ebc0f1d2b3b1';
80
+ '0x6cec550fb43435462f34b15aee9f8b8030e0433e16c8104e966013f0443b2e36';
83
81
 
84
82
  export const VORTEX_SWAP_PACKAGE_ID =
85
- '0x2ddd33debbac3e0461b3551bb00bd40d3055ea5cd441b4fad8624dcbb095e8fb';
83
+ '0x325610fc0c15c91682f8bf263ef0991c28fe7ff77fd98266821c3e696e459cdf';
86
84
 
87
85
  export const VORTEX_SWAP_UPGRADE_CAP =
88
- '0xec0beaf1453b0e09d92f8addf25abcfb4bc6ce43ead828914836cadc8df249a0';
86
+ '0x3f535b61b6baa2174dc301179bd2bc392563086ddca23b8eec69da505d54e3c1';
89
87
 
90
88
  export const SECRET_PACKAGE_ID =
91
89
  '0x2d57ed0dd0d5f44d91f865fee3bc33d13ef3ce97c7daf88ad5f5fbb32468ccd6';
92
90
 
93
- export const INITIAL_SHARED_VERSION = '692442863';
91
+ export const INITIAL_SHARED_VERSION = '738926997';
94
92
 
95
93
  export const LSK_FETCH_OFFSET = 'fetch_offset';
96
94
 
@@ -106,8 +104,3 @@ export const TREASURY_ADDRESS =
106
104
  export const DEPOSIT_FEE_IN_BASIS_POINTS = 50n;
107
105
 
108
106
  export const BASIS_POINTS = 10_000n;
109
-
110
- export const VORTEX_POOL_IDS = {
111
- [SUI_TYPE_ARG]:
112
- '0x1e3672f35853fccded923505434b5138543829231f025120d57fda95b86b504c',
113
- };
@@ -1,5 +1,6 @@
1
+ /* eslint-disable */
1
2
  import * as Scalar from './scalar';
2
- import { getRandomBytes } from './random';
3
+ import { randomBytes } from '@noble/ciphers/utils.js';
3
4
 
4
5
  export class F1Field {
5
6
  type: string;
@@ -288,7 +289,7 @@ export class F1Field {
288
289
  const nBytes = (this.bitLength * 2) / 8;
289
290
  let res = this.zero;
290
291
  for (let i = 0; i < nBytes; i++) {
291
- res = (res << BigInt(8)) + BigInt(getRandomBytes(1)[0]!);
292
+ res = (res << BigInt(8)) + BigInt(randomBytes(1)[0]!);
292
293
  }
293
294
  return res % this.p;
294
295
  }
@@ -1,6 +1,6 @@
1
+ /* eslint-disable */
1
2
  import * as utils from './utils';
2
3
  import * as Scalar from './scalar';
3
4
  import { F1Field } from './f1field';
4
- import { getRandomBytes } from './random';
5
5
 
6
- export { utils, Scalar, F1Field, getRandomBytes };
6
+ export { utils, Scalar, F1Field };
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  const hexLen = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
2
3
 
3
4
  export const fromString = (s: string, radix?: number): bigint => {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  import * as Scalar from './scalar';
2
3
  import { fromBase64 } from '@mysten/sui/utils';
3
4
  export function unStringifyBigInts(o: unknown): unknown {
@@ -6,10 +6,22 @@ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
6
6
  import invariant from 'tiny-invariant';
7
7
  import { randomBytes } from '@noble/ciphers/utils.js';
8
8
  import { x25519 } from '@noble/curves/ed25519.js';
9
- import { xsalsa20poly1305 } from '@noble/ciphers/salsa.js';
9
+ import { xchacha20 } from '@noble/ciphers/chacha.js';
10
+ import { hmac } from '@noble/hashes/hmac.js';
11
+ import { sha256 } from '@noble/hashes/sha2.js';
10
12
  import { blake2b } from '@noble/hashes/blake2.js';
11
13
  import { normalizeSuiAddress } from '@mysten/sui/utils';
12
14
 
15
+ // Constant-time comparison to prevent timing attacks
16
+ function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
17
+ if (a.length !== b.length) return false;
18
+ let diff = 0;
19
+ for (let i = 0; i < a.length; i++) {
20
+ diff |= a[i] ^ b[i];
21
+ }
22
+ return diff === 0;
23
+ }
24
+
13
25
  export interface UtxoPayload {
14
26
  amount: bigint;
15
27
  blinding: bigint;
@@ -21,6 +33,7 @@ interface EncryptedMessage {
21
33
  version: string;
22
34
  nonce: string; // base64
23
35
  ephemPublicKey: string; // base64
36
+ hmacTag: string; // base64 - HMAC-SHA256 tag for key-committing authentication
24
37
  ciphertext: string; // base64
25
38
  }
26
39
 
@@ -30,12 +43,14 @@ type SignMessageFn = (message: Uint8Array) => Promise<{
30
43
  bytes: string;
31
44
  }>;
32
45
 
46
+ // Message format: nonce (24) | ephemPublicKey (32) | hmacTag (32) | ciphertext (variable)
33
47
  function packEncryptedMessage(encryptedMessage: EncryptedMessage): string {
34
48
  const nonceBuf = Buffer.from(encryptedMessage.nonce, 'base64');
35
49
  const ephemPublicKeyBuf = Buffer.from(
36
50
  encryptedMessage.ephemPublicKey,
37
51
  'base64'
38
52
  );
53
+ const hmacTagBuf = Buffer.from(encryptedMessage.hmacTag, 'base64');
39
54
  const ciphertextBuf = Buffer.from(encryptedMessage.ciphertext, 'base64');
40
55
 
41
56
  const messageBuff = Buffer.concat([
@@ -43,6 +58,8 @@ function packEncryptedMessage(encryptedMessage: EncryptedMessage): string {
43
58
  nonceBuf,
44
59
  Buffer.alloc(32 - ephemPublicKeyBuf.length),
45
60
  ephemPublicKeyBuf,
61
+ Buffer.alloc(32 - hmacTagBuf.length),
62
+ hmacTagBuf,
46
63
  ciphertextBuf,
47
64
  ]);
48
65
 
@@ -58,12 +75,14 @@ function unpackEncryptedMessage(encryptedMessage: string): EncryptedMessage {
58
75
 
59
76
  const nonceBuf = messageBuff.subarray(0, 24);
60
77
  const ephemPublicKeyBuf = messageBuff.subarray(24, 56);
61
- const ciphertextBuf = messageBuff.subarray(56);
78
+ const hmacTagBuf = messageBuff.subarray(56, 88);
79
+ const ciphertextBuf = messageBuff.subarray(88);
62
80
 
63
81
  return {
64
- version: 'x25519-xsalsa20-poly1305',
82
+ version: 'x25519-xchacha20-hmac-sha256',
65
83
  nonce: nonceBuf.toString('base64'),
66
84
  ephemPublicKey: ephemPublicKeyBuf.toString('base64'),
85
+ hmacTag: hmacTagBuf.toString('base64'),
67
86
  ciphertext: ciphertextBuf.toString('base64'),
68
87
  };
69
88
  }
@@ -159,14 +178,19 @@ export class VortexKeypair {
159
178
  recipientPublicKey
160
179
  );
161
180
 
181
+ // Encrypt with XChaCha20
162
182
  const nonce = randomBytes(24);
163
- const cipher = xsalsa20poly1305(sharedSecret, nonce);
164
- const ciphertext = cipher.encrypt(bytes);
183
+ const ciphertext = xchacha20(sharedSecret, nonce, bytes);
184
+
185
+ // Compute HMAC-SHA256 over ciphertext (Encrypt-then-MAC)
186
+ // This provides key-committing authentication
187
+ const hmacTag = hmac(sha256, sharedSecret, ciphertext);
165
188
 
166
189
  const encryptedMessage: EncryptedMessage = {
167
- version: 'x25519-xsalsa20-poly1305',
190
+ version: 'x25519-xchacha20-hmac-sha256',
168
191
  nonce: Buffer.from(nonce).toString('base64'),
169
192
  ephemPublicKey: Buffer.from(ephemeralPublicKey).toString('base64'),
193
+ hmacTag: Buffer.from(hmacTag).toString('base64'),
170
194
  ciphertext: Buffer.from(ciphertext).toString('base64'),
171
195
  };
172
196
 
@@ -189,10 +213,28 @@ export class VortexKeypair {
189
213
 
190
214
  invariant(parts.length === 4, 'Invalid UTXO format after decryption');
191
215
 
216
+ const amount = BigInt(parts[0]);
217
+ const blinding = BigInt(parts[1]);
218
+ const index = BigInt(parts[2]);
219
+
220
+ // Validate values are within BN254 field to prevent proof failures
221
+ invariant(
222
+ amount >= 0n && amount < BN254_FIELD_MODULUS,
223
+ 'Amount exceeds field modulus'
224
+ );
225
+ invariant(
226
+ blinding >= 0n && blinding < BN254_FIELD_MODULUS,
227
+ 'Blinding exceeds field modulus'
228
+ );
229
+ invariant(
230
+ index >= 0n && index < BN254_FIELD_MODULUS,
231
+ 'Index exceeds field modulus'
232
+ );
233
+
192
234
  return {
193
- amount: BigInt(parts[0]),
194
- blinding: BigInt(parts[1]),
195
- index: BigInt(parts[2]),
235
+ amount,
236
+ blinding,
237
+ index,
196
238
  vortexPool: normalizeSuiAddress(parts[3]),
197
239
  };
198
240
  }
@@ -254,12 +296,20 @@ export class VortexKeypair {
254
296
  ephemeralPublicKey
255
297
  );
256
298
 
257
- // Decrypt using XSalsa20-Poly1305
258
- const nonce = Buffer.from(encryptedMessage.nonce, 'base64');
259
299
  const ciphertext = Buffer.from(encryptedMessage.ciphertext, 'base64');
300
+ const receivedHmacTag = Buffer.from(encryptedMessage.hmacTag, 'base64');
260
301
 
261
- const cipher = xsalsa20poly1305(sharedSecret, nonce);
262
- const decrypted = cipher.decrypt(ciphertext);
302
+ // Verify HMAC-SHA256 first (Encrypt-then-MAC verification)
303
+ // This is key-committing: wrong key = wrong HMAC = guaranteed failure
304
+ const expectedHmacTag = hmac(sha256, sharedSecret, ciphertext);
305
+
306
+ if (!constantTimeEqual(receivedHmacTag, expectedHmacTag)) {
307
+ throw new Error('Decryption failed: HMAC verification failed');
308
+ }
309
+
310
+ // Decrypt using XChaCha20
311
+ const nonce = Buffer.from(encryptedMessage.nonce, 'base64');
312
+ const decrypted = xchacha20(sharedSecret, nonce, ciphertext);
263
313
 
264
314
  return Buffer.from(decrypted);
265
315
  }
@@ -1,6 +1,8 @@
1
1
  import { VortexKeypair } from './keypair';
2
2
  import { poseidon3, poseidon4 } from '../crypto';
3
- import { normalizeSuiAddress } from '@mysten/sui/utils';
3
+ import { normalizeSuiAddress, toHex } from '@mysten/sui/utils';
4
+ import { randomBytes } from '@noble/ciphers/utils.js';
5
+ import { BN254_FIELD_MODULUS } from '../constants';
4
6
 
5
7
  interface UtxoConstructorArgs {
6
8
  amount: bigint;
@@ -53,7 +55,10 @@ export class Utxo {
53
55
  }
54
56
 
55
57
  static blinding() {
56
- return BigInt(Math.floor(Math.random() * 1_000_000_000));
58
+ // Use cryptographically secure randomness (works on web + Node.js)
59
+ // 31 bytes = 248 bits, safely below BN254 field modulus (~254 bits)
60
+ const bytes = randomBytes(31);
61
+ return BigInt('0x' + toHex(bytes)) % BN254_FIELD_MODULUS;
57
62
  }
58
63
 
59
64
  commitment() {
@@ -1,12 +1,12 @@
1
- import { PaginatedEvents } from '@mysten/sui/client';
1
+ import type { PaginatedEvents } from '@mysten/sui/client';
2
+ import type { Commitment } from '../vortex-api.types';
3
+ import type { UtxoPayload, VortexKeypair } from '../entities/keypair';
4
+ import type { Vortex } from '../vortex';
5
+ import type { VortexPool } from '../vortex.types';
6
+
2
7
  import { parseNewCommitmentEvent } from './events';
3
- import { Commitment } from '../vortex-api.types';
4
- import { UtxoPayload } from '../entities/keypair';
5
- import { VortexKeypair } from '../entities/keypair';
6
8
  import { Utxo } from '../entities/utxo';
7
9
  import { normalizeStructTag, toHex } from '@mysten/sui/utils';
8
- import { Vortex } from '../vortex';
9
- import { VortexPool } from '../vortex.types';
10
10
  import invariant from 'tiny-invariant';
11
11
 
12
12
  interface GetUnspentUtxosArgs {
@@ -26,18 +26,18 @@ export const getUnspentUtxos = async ({
26
26
 
27
27
  const allUtxos = [] as UtxoPayload[];
28
28
 
29
+ const vortexObjectId =
30
+ typeof vortexPool === 'string' ? vortexPool : vortexPool.objectId;
31
+
29
32
  commitments.forEach((commitment) => {
30
33
  try {
31
34
  const utxo = vortexKeypair.decryptUtxo(commitment.encryptedOutput);
32
35
  allUtxos.push(utxo);
33
36
  } catch {
34
- // Do nothing
37
+ // HMAC verification failed - wrong keypair
35
38
  }
36
39
  });
37
40
 
38
- const vortexObjectId =
39
- typeof vortexPool === 'string' ? vortexPool : vortexPool.objectId;
40
-
41
41
  const utxos = allUtxos.map(
42
42
  (utxo) =>
43
43
  new Utxo({ ...utxo, keypair: vortexKeypair, vortexPool: vortexObjectId })
@@ -94,7 +94,7 @@ export const getUnspentUtxosWithApi = async ({
94
94
  const utxo = vortexKeypair.decryptUtxo(encryptedOutputHex);
95
95
  allUtxos.push(utxo);
96
96
  } catch {
97
- // Do nothing
97
+ // HMAC verification failed - wrong keypair
98
98
  }
99
99
  });
100
100
 
@@ -128,10 +128,7 @@ export const getUnspentUtxosWithApiAndCommitments = async ({
128
128
  vortexPool,
129
129
  }: GetUnspentUtxosWithApiAndCommitmentsArgs) => {
130
130
  const allUtxos = [] as UtxoPayload[];
131
- const userCommitments = [] as Pick<
132
- Commitment,
133
- 'coinType' | 'encryptedOutput'
134
- >[];
131
+ const userCommitments = [] as Pick<Commitment, 'coinType' | 'encryptedOutput'>[];
135
132
 
136
133
  const vortexObject = await vortexSdk.resolveVortexPool(vortexPool);
137
134
 
@@ -152,7 +149,7 @@ export const getUnspentUtxosWithApiAndCommitments = async ({
152
149
  });
153
150
  allUtxos.push(utxo);
154
151
  } catch {
155
- // Do nothing
152
+ // HMAC verification failed - wrong keypair
156
153
  }
157
154
  });
158
155
 
package/src/vortex-api.ts CHANGED
@@ -154,7 +154,11 @@ export class VortexAPI {
154
154
  let hasNext = true;
155
155
 
156
156
  while (hasNext) {
157
- const response = await this.getCommitments({ ...args, page, apiKey: args.apiKey });
157
+ const response = await this.getCommitments({
158
+ ...args,
159
+ page,
160
+ apiKey: args.apiKey,
161
+ });
158
162
  allCommitments.push(...response.data.items);
159
163
  hasNext = response.data.pagination.hasNext;
160
164
  page++;
@@ -1,2 +0,0 @@
1
- export declare function getRandomBytes(length: number): Uint8Array;
2
- //# sourceMappingURL=random.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../../../src/crypto/ff/random.ts"],"names":[],"mappings":"AAUA,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAqBzD"}
@@ -1,32 +0,0 @@
1
- /**
2
- * Type definition for the browser crypto object.
3
- */
4
- type BrowserCrypto = {
5
- crypto?: { getRandomValues?: (arg0: Uint8Array) => void };
6
- };
7
-
8
- // Type declaration for Node.js require (used in Node.js environment fallback)
9
- declare const require: (module: string) => any;
10
-
11
- export function getRandomBytes(length: number): Uint8Array {
12
- if (length <= 0) {
13
- throw new Error('Length must be greater than 0');
14
- }
15
- const global = globalThis as BrowserCrypto;
16
- if (global.crypto?.getRandomValues) {
17
- const randomValues = new Uint8Array(length);
18
- global.crypto.getRandomValues(randomValues);
19
- return randomValues;
20
- }
21
- // eslint-disable-next-line no-unused-labels
22
- NODE: {
23
- // eslint-disable-next-line @typescript-eslint/no-require-imports
24
- // eslint-disable-next-line @typescript-eslint/no-require-imports
25
- const crypto = require('crypto');
26
- return crypto.randomBytes(length);
27
- }
28
-
29
- throw new Error(
30
- 'Random byte generation is not supported in this environment'
31
- );
32
- }