@cofhe/abi 0.6.1 → 0.7.0

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,5 +1,6 @@
1
1
  import {
2
2
  Encryptable,
3
+ type AnyExternalHash,
3
4
  type EncryptableAddress,
4
5
  type EncryptableBool,
5
6
  type EncryptableItem,
@@ -8,54 +9,135 @@ import {
8
9
  type EncryptableUint32,
9
10
  type EncryptableUint64,
10
11
  type EncryptableUint8,
11
- type EncryptedAddressInput,
12
- type EncryptedBoolInput,
13
- type EncryptedItemInput,
14
- type EncryptedUint128Input,
15
- type EncryptedUint16Input,
16
- type EncryptedUint32Input,
17
- type EncryptedUint64Input,
18
- type EncryptedUint8Input,
12
+ type ExternalAddressHash,
13
+ type ExternalBoolHash,
14
+ type ExternalUint128Hash,
15
+ type ExternalUint16Hash,
16
+ type ExternalUint32Hash,
17
+ type ExternalUint64Hash,
18
+ type ExternalUint8Hash,
19
19
  type LiteralToPrimitive,
20
20
  type Primitive,
21
21
  } from '@cofhe/sdk';
22
22
  import type { Abi, AbiFunction, AbiParameter, ExtractAbiFunction } from 'abitype';
23
23
  import type { CofheAbiParametersToPrimitiveTypes } from './fhenixMap';
24
- import { extractArrayParameterType, getAbiFunction, type MaybePartialBy, type ReadonlyWiden } from './utils';
24
+ import {
25
+ extractArrayParameterType,
26
+ getAbiFunction,
27
+ type MaybeExtractArrayParameterType,
28
+ type MaybePartialBy,
29
+ type ReadonlyWiden,
30
+ } from './utils';
25
31
 
26
32
  export type CofheInputArgs<abi extends Abi | readonly unknown[] = Abi, functionName extends string = string> = GetArgs<
27
33
  abi,
28
34
  functionName
29
35
  >['args'];
30
36
 
31
- type EncryptedInputToInputMap<E extends EncryptedItemInput> = E extends EncryptedBoolInput
37
+ type ExternalHashToInputMap<E extends AnyExternalHash> = E extends ExternalBoolHash
32
38
  ? EncryptableBool['data']
33
- : E extends EncryptedUint8Input
39
+ : E extends ExternalUint8Hash
34
40
  ? EncryptableUint8['data']
35
- : E extends EncryptedUint16Input
41
+ : E extends ExternalUint16Hash
36
42
  ? EncryptableUint16['data']
37
- : E extends EncryptedUint32Input
43
+ : E extends ExternalUint32Hash
38
44
  ? EncryptableUint32['data']
39
- : E extends EncryptedUint64Input
45
+ : E extends ExternalUint64Hash
40
46
  ? EncryptableUint64['data']
41
- : E extends EncryptedUint128Input
47
+ : E extends ExternalUint128Hash
42
48
  ? EncryptableUint128['data']
43
- : E extends EncryptedAddressInput
49
+ : E extends ExternalAddressHash
44
50
  ? EncryptableAddress['data']
45
51
  : never;
46
52
 
47
- type EncryptedInputsToInputs<T> = T extends Primitive
48
- ? LiteralToPrimitive<T>
49
- : T extends EncryptedItemInput
50
- ? EncryptedInputToInputMap<T>
53
+ // Note: AnyExternalHash is checked before Primitive - the branded hash types are structurally
54
+ // `0x${string}` (a subtype of `string`, which is itself a Primitive), so Primitive must not be
55
+ // checked first or every hash would be (incorrectly) widened via LiteralToPrimitive instead.
56
+ type EncryptedInputsToInputs<T> = T extends AnyExternalHash
57
+ ? ExternalHashToInputMap<T>
58
+ : T extends Primitive
59
+ ? LiteralToPrimitive<T>
51
60
  : {
52
61
  [K in keyof T]: EncryptedInputsToInputs<T[K]>;
53
62
  };
54
63
 
64
+ /**
65
+ * Drops the batch-signature argument from an args tuple.
66
+ *
67
+ * The signature slot is the first non-`external*` parameter that follows the contiguous run of
68
+ * `external*` parameters - `FHE.asEuintXX(hash, proof)` is a pair, so the proof sits with the
69
+ * hashes it authenticates rather than at the end of the parameter list. Walks the ABI parameters
70
+ * and the args tuple in lockstep and elides the arg at that position.
71
+ */
72
+ type DropSignatureSlotTuple<
73
+ params extends readonly unknown[],
74
+ args extends readonly unknown[],
75
+ afterExternalRun extends boolean = false,
76
+ > = params extends readonly [infer P, ...infer PRest extends readonly unknown[]]
77
+ ? args extends readonly [infer A, ...infer ARest extends readonly unknown[]]
78
+ ? P extends AbiParameter
79
+ ? ParamHasExternalInput<P> extends true
80
+ ? readonly [A, ...DropSignatureSlotTuple<PRest, ARest, true>]
81
+ : afterExternalRun extends true
82
+ ? DropSignatureSlotTuple<PRest, ARest, false>
83
+ : readonly [A, ...DropSignatureSlotTuple<PRest, ARest, false>]
84
+ : readonly [A, ...DropSignatureSlotTuple<PRest, ARest, afterExternalRun>]
85
+ : readonly []
86
+ : args;
87
+
88
+ /** Non-tuple args (e.g. `undefined`) pass through unchanged, as the previous `DropLast` did. */
89
+ type DropSignatureSlot<params extends readonly unknown[], args> = args extends readonly unknown[]
90
+ ? DropSignatureSlotTuple<params, args>
91
+ : args;
92
+
93
+ const EXTERNAL_INPUT_INTERNAL_TYPES = [
94
+ 'externalEbool',
95
+ 'externalEuint8',
96
+ 'externalEuint16',
97
+ 'externalEuint32',
98
+ 'externalEuint64',
99
+ 'externalEuint128',
100
+ 'externalEaddress',
101
+ ] as const;
102
+ type ExternalInputInternalType = (typeof EXTERNAL_INPUT_INTERNAL_TYPES)[number];
103
+
104
+ /** Type-level: does this ABI parameter carry (or, if a tuple, recursively contain) an `external*` input? */
105
+ type ParamHasExternalInput<param extends AbiParameter> =
106
+ MaybeExtractArrayParameterType<param['internalType']> extends [infer head extends string, any]
107
+ ? head extends ExternalInputInternalType
108
+ ? true
109
+ : false
110
+ : param['internalType'] extends ExternalInputInternalType
111
+ ? true
112
+ : param extends { type: 'tuple'; components: infer components extends readonly AbiParameter[] }
113
+ ? ParamsHaveExternalInput<components>
114
+ : false;
115
+
116
+ type ParamsHaveExternalInput<params extends readonly AbiParameter[]> = params extends readonly [
117
+ infer Head extends AbiParameter,
118
+ ...infer Rest extends readonly AbiParameter[],
119
+ ]
120
+ ? ParamHasExternalInput<Head> extends true
121
+ ? true
122
+ : ParamsHaveExternalInput<Rest>
123
+ : false;
124
+
125
+ /**
126
+ * Pre-transform args for a function ABI: the raw plain values a caller supplies before encryption.
127
+ *
128
+ * If the function has one or more `external*` (encrypted) inputs, the plain `bytes` parameter
129
+ * immediately following them is the shared batch signature slot - the caller never supplies it
130
+ * directly (the SDK injects it after encryption), so it's dropped from this type entirely.
131
+ */
55
132
  export type CofheInputArgsPreTransform<
56
133
  abi extends Abi | readonly unknown[] = Abi,
57
134
  functionName extends string = string,
58
- > = EncryptedInputsToInputs<CofheInputArgs<abi, functionName>>;
135
+ abiFunction extends AbiFunction = abi extends Abi ? ExtractAbiFunction<abi, functionName> : AbiFunction,
136
+ > = EncryptedInputsToInputs<
137
+ ParamsHaveExternalInput<abiFunction['inputs']> extends true
138
+ ? DropSignatureSlot<abiFunction['inputs'], CofheInputArgs<abi, functionName>>
139
+ : CofheInputArgs<abi, functionName>
140
+ >;
59
141
 
60
142
  /// GetArgs from abitype (not exported from abitype)
61
143
  type GetArgs<
@@ -78,73 +160,54 @@ type GetArgs<
78
160
  | (Abi extends abi ? readonly unknown[] : never), // fallback if `abi` is declared as `Abi`
79
161
  > = MaybePartialBy<{ args: args_ }, readonly [] extends primitiveTypes ? 'args' : Abi extends abi ? 'args' : string>;
80
162
 
81
- // Runtime helper to check if an internalType is an encrypted input type
82
- const ENCRYPTED_INPUT_INTERNAL_TYPES = [
83
- 'struct InEbool',
84
- 'struct InEuint8',
85
- 'struct InEuint16',
86
- 'struct InEuint32',
87
- 'struct InEuint64',
88
- 'struct InEuint128',
89
- 'struct InEaddress',
90
- ] as const;
91
- type EncryptedInputInternalType = (typeof ENCRYPTED_INPUT_INTERNAL_TYPES)[number];
163
+ function internalTypeIsExternalInput(internalType: string): internalType is ExternalInputInternalType {
164
+ return EXTERNAL_INPUT_INTERNAL_TYPES.includes(internalType as any);
165
+ }
92
166
 
93
- function transformSingleEncryptedInputToEncryptable(
94
- internalType: EncryptedInputInternalType,
95
- data: unknown
96
- ): EncryptableItem {
167
+ function paramHasExternalInput(param: AbiParameter): boolean {
168
+ const [internalTypeHead] = extractArrayParameterType(param.internalType);
169
+ if (internalTypeHead != null && internalTypeIsExternalInput(internalTypeHead)) return true;
170
+ if ('components' in param && Array.isArray(param.components)) {
171
+ return param.components.some((component) => paramHasExternalInput(component));
172
+ }
173
+ return false;
174
+ }
175
+
176
+ function transformSingleExternalToEncryptable(internalType: ExternalInputInternalType, data: unknown): EncryptableItem {
97
177
  switch (internalType) {
98
- case 'struct InEbool':
178
+ case 'externalEbool':
99
179
  return Encryptable.bool(data as boolean);
100
- case 'struct InEuint8':
180
+ case 'externalEuint8':
101
181
  return Encryptable.uint8(data as string | bigint);
102
- case 'struct InEuint16':
182
+ case 'externalEuint16':
103
183
  return Encryptable.uint16(data as string | bigint);
104
- case 'struct InEuint32':
184
+ case 'externalEuint32':
105
185
  return Encryptable.uint32(data as string | bigint);
106
- case 'struct InEuint64':
186
+ case 'externalEuint64':
107
187
  return Encryptable.uint64(data as string | bigint);
108
- case 'struct InEuint128':
188
+ case 'externalEuint128':
109
189
  return Encryptable.uint128(data as string | bigint);
110
- case 'struct InEaddress':
190
+ case 'externalEaddress':
111
191
  return Encryptable.address(data as string | bigint);
112
192
  default:
113
- throw new Error(`Unknown encrypted input type: ${internalType}`);
193
+ throw new Error(`Unknown external input type: ${internalType}`);
114
194
  }
115
195
  }
116
196
 
117
- function transformArrayOfEncryptedInputsToEncryptables(
118
- internalType: EncryptedInputInternalType,
197
+ function transformArrayOfExternalsToEncryptables(
198
+ internalType: ExternalInputInternalType,
119
199
  size: string | undefined,
120
200
  data: unknown
121
201
  ): EncryptableItem[] {
122
- // Ensure data is an array
123
202
  if (!Array.isArray(data)) {
124
203
  throw new Error('Data must be an array');
125
204
  }
126
205
 
127
- // Ensure length of fixed length tuple matches the length of the data array
128
206
  if (size != null && size !== '' && parseInt(size) !== data.length) {
129
207
  throw new Error(`Array size mismatch: ${size} !== ${data.length}`);
130
208
  }
131
209
 
132
- // Transform each item in the data array into an encryptable item
133
- return data.map((item) => transformSingleEncryptedInputToEncryptable(internalType, item));
134
- }
135
-
136
- // Pathways
137
- // - type is a tuple && internalType is exactly an encrypted input type (struct InEbool)
138
- // - - extract the base encrypted type (InEbool) and convert into an encryptable item
139
- // - type is a tuple && internalType is a tuple of encrypted input types (struct InEuint32[2] / struct InEuint32[])
140
- // - - extract the base encrypted type and length, and convert into an array of encryptable items
141
- // - type is a tuple && internalType is not an encrypted input type
142
- // - - iterate components
143
- // - type is not a tuple
144
- // - - throw away
145
-
146
- function internalTypeIsEncryptedInput(internalType: string): internalType is EncryptedInputInternalType {
147
- return ENCRYPTED_INPUT_INTERNAL_TYPES.includes(internalType as any);
210
+ return data.map((item) => transformSingleExternalToEncryptable(internalType, item));
148
211
  }
149
212
 
150
213
  /**
@@ -153,8 +216,9 @@ function internalTypeIsEncryptedInput(internalType: string): internalType is Enc
153
216
  *
154
217
  * @param abi - The ABI containing the function definition
155
218
  * @param functionName - Name of the function
156
- * @param args - Function arguments in the format of CofheInputArgsPreTransform (raw data values)
157
- * @returns Array of EncryptableItem objects ready for encryption
219
+ * @param args - Function arguments in the format of CofheInputArgsPreTransform (raw data values;
220
+ * if the function has `external*` inputs, the trailing batch-signature slot is NOT included)
221
+ * @returns Array of EncryptableItem objects ready for encryption, in ABI parameter order
158
222
  */
159
223
  export function extractEncryptableValues<TAbi extends Abi, TFunctionName extends string>(
160
224
  abi: TAbi,
@@ -171,136 +235,31 @@ export function extractEncryptableValues<TAbi extends Abi, TFunctionName extends
171
235
  throw new Error('Arguments must be an array');
172
236
  }
173
237
 
238
+ const hasExternalInputs = inputs.some((input) => paramHasExternalInput(input));
239
+ const signatureSlotIndex = findSignatureSlotIndex(inputs, hasExternalInputs, functionName);
240
+
174
241
  // Collect encrypted values as EncryptableItem objects in order (flat array)
175
242
  const encryptableItems: EncryptableItem[] = [];
176
243
 
177
- // Process each parameter - transforms raw values into EncryptableItem objects
178
244
  function processParameter(param: AbiParameter, value: unknown): void {
179
245
  const [typeHead, typeSize] = extractArrayParameterType(param.type);
180
246
  const [internalTypeHead, internalTypeSize] = extractArrayParameterType(param.internalType);
181
247
 
182
- /**
183
- {
184
- name: 'inNumber',
185
- type: 'tuple',
186
- internalType: 'struct InEuint32',
187
- components: [
188
- {
189
- name: 'ctHash',
190
- type: 'uint256',
191
- internalType: 'uint256',
192
- },
193
- {
194
- name: 'securityZone',
195
- type: 'uint8',
196
- internalType: 'uint8',
197
- },
198
- {
199
- name: 'utype',
200
- type: 'uint8',
201
- internalType: 'uint8',
202
- },
203
- {
204
- name: 'signature',
205
- type: 'bytes',
206
- internalType: 'bytes',
207
- },
208
- ],
209
- }
210
- */
211
- if (
212
- typeHead === 'tuple' &&
213
- typeSize == null &&
214
- internalTypeHead != null &&
215
- internalTypeIsEncryptedInput(internalTypeHead)
216
- ) {
217
- const encryptable = transformSingleEncryptedInputToEncryptable(internalTypeHead, value);
218
- encryptableItems.push(encryptable);
248
+ // Bare external input (e.g. `externalEuint32`, type: 'bytes32')
249
+ if (typeSize == null && internalTypeHead != null && internalTypeIsExternalInput(internalTypeHead)) {
250
+ encryptableItems.push(transformSingleExternalToEncryptable(internalTypeHead, value));
219
251
  return;
220
252
  }
221
253
 
222
- /**
223
- {
224
- name: 'inEuint32Array',
225
- type: 'tuple[2]',
226
- internalType: 'struct InEuint32[2]',
227
- components: [
228
- {
229
- name: 'ctHash',
230
- type: 'uint256',
231
- internalType: 'uint256',
232
- },
233
- {
234
- name: 'securityZone',
235
- type: 'uint8',
236
- internalType: 'uint8',
237
- },
238
- {
239
- name: 'utype',
240
- type: 'uint8',
241
- internalType: 'uint8',
242
- },
243
- {
244
- name: 'signature',
245
- type: 'bytes',
246
- internalType: 'bytes',
247
- },
248
- ],
249
- }
250
- */
251
- if (
252
- typeHead === 'tuple' &&
253
- typeSize != null &&
254
- internalTypeHead != null &&
255
- internalTypeIsEncryptedInput(internalTypeHead)
256
- ) {
257
- const encryptables = transformArrayOfEncryptedInputsToEncryptables(internalTypeHead, typeSize, value);
254
+ // Array of external inputs (e.g. `externalEuint32[]`/`externalEuint32[2]`, type: 'bytes32[]'/'bytes32[2]')
255
+ if (typeSize != null && internalTypeHead != null && internalTypeIsExternalInput(internalTypeHead)) {
256
+ const encryptables = transformArrayOfExternalsToEncryptables(internalTypeHead, typeSize, value);
258
257
  encryptableItems.push(...encryptables);
259
258
  return;
260
259
  }
261
260
 
262
- /**
263
- {
264
- name: 'containsEncryptedInput',
265
- type: 'tuple',
266
- internalType: 'struct ABITest.ContainsEncryptedInput',
267
- components: [
268
- {
269
- name: 'value',
270
- type: 'uint256',
271
- internalType: 'uint256',
272
- },
273
- {
274
- name: 'encryptedInput',
275
- type: 'tuple',
276
- internalType: 'struct InEuint32',
277
- components: [
278
- {
279
- name: 'ctHash',
280
- type: 'uint256',
281
- internalType: 'uint256',
282
- },
283
- {
284
- name: 'securityZone',
285
- type: 'uint8',
286
- internalType: 'uint8',
287
- },
288
- {
289
- name: 'utype',
290
- type: 'uint8',
291
- internalType: 'uint8',
292
- },
293
- {
294
- name: 'signature',
295
- type: 'bytes',
296
- internalType: 'bytes',
297
- },
298
- ],
299
- },
300
- ],
301
- }
302
- */
303
- if (typeHead === 'tuple' && (internalTypeHead == null || !internalTypeIsEncryptedInput(internalTypeHead))) {
261
+ // Tuple recursive case (struct that may itself contain external inputs)
262
+ if (typeHead === 'tuple') {
304
263
  if ('components' in param && Array.isArray(param.components)) {
305
264
  param.components.forEach((component) => {
306
265
  processParameter(component, (value as Record<string, unknown>)[component.name]);
@@ -309,26 +268,73 @@ export function extractEncryptableValues<TAbi extends Abi, TFunctionName extends
309
268
  return;
310
269
  }
311
270
 
312
- // param.type is not a tuple, so cannot be an encrypted input or contain encrypted inputs
271
+ // Not an external input, and cannot contain one
313
272
  return;
314
273
  }
315
274
 
316
- // Process all inputs
275
+ // Process all inputs, skipping the batch-signature slot (nothing to extract there). `args` is the
276
+ // pre-transform tuple, which omits that slot, so its indices lag by one past the signature.
277
+ let argIndex = 0;
317
278
  inputs.forEach((input, index) => {
318
- const arg = args[index];
279
+ if (index === signatureSlotIndex) return;
280
+ const arg = args[argIndex];
319
281
  if (arg == null) {
320
- throw new Error(`Argument ${index} is undefined`);
282
+ throw new Error(`Argument ${argIndex} is undefined`);
321
283
  }
284
+ argIndex++;
322
285
  processParameter(input, arg);
323
286
  });
324
287
 
325
288
  return encryptableItems;
326
289
  }
327
290
 
291
+ /**
292
+ * Index of the shared batch-signature parameter: the plain `bytes` immediately after the
293
+ * contiguous run of `external*` parameters. Returns -1 when the function has no encrypted inputs.
294
+ *
295
+ * The proof pairs with the hashes it authenticates (`FHE.asEuintXX(hash, proof)`), so it does not
296
+ * have to be the last parameter - anything may follow it.
297
+ */
298
+ function findSignatureSlotIndex(
299
+ inputs: readonly AbiParameter[],
300
+ hasExternalInputs: boolean,
301
+ functionName: string
302
+ ): number {
303
+ if (!hasExternalInputs) return -1;
304
+
305
+ const externalIndices = inputs.reduce<number[]>((acc, input, index) => {
306
+ if (paramHasExternalInput(input)) acc.push(index);
307
+ return acc;
308
+ }, []);
309
+
310
+ const first = externalIndices[0]!;
311
+ const last = externalIndices[externalIndices.length - 1]!;
312
+ if (last - first + 1 !== externalIndices.length) {
313
+ throw new Error(
314
+ `Function ${functionName} has encrypted (external*) inputs at non-adjacent positions ` +
315
+ `(${externalIndices.join(', ')}). They share one batch signature, so they must be contiguous ` +
316
+ `with the 'bytes' signature parameter immediately after them`
317
+ );
318
+ }
319
+
320
+ const slotIndex = last + 1;
321
+ const slot = inputs[slotIndex];
322
+ if (slot?.type !== 'bytes') {
323
+ throw new Error(
324
+ `Function ${functionName} has encrypted (external*) inputs, so the parameter immediately after ` +
325
+ `them must be a plain 'bytes' parameter to receive the shared batch signature - found ` +
326
+ `'${slot?.type ?? 'nothing'}' at position ${slotIndex} instead`
327
+ );
328
+ }
329
+
330
+ return slotIndex;
331
+ }
332
+
328
333
  /**
329
334
  * Re-inserts encrypted values back into function arguments based on the ABI.
330
- * Takes the extracted values (now encrypted) and the original args structure, and replaces
331
- * the encryptable values with their encrypted counterparts.
335
+ * Takes the batch-verified result (per-input hashes, in order, followed by the single shared
336
+ * signature) and the original args structure, and produces the final call args: each `external*`
337
+ * position gets its corresponding hash, and the trailing `bytes` parameter gets the signature.
332
338
  *
333
339
  * This function mirrors the extraction logic in extractEncryptableValues, ensuring values
334
340
  * are inserted in the exact same order and locations where they were extracted.
@@ -336,14 +342,14 @@ export function extractEncryptableValues<TAbi extends Abi, TFunctionName extends
336
342
  * @param abi - The ABI containing the function definition
337
343
  * @param functionName - Name of the function
338
344
  * @param args - Original function arguments in the format of CofheInputArgsPreTransform
339
- * @param encryptedValues - Encrypted values in the same order as returned by extractEncryptableValues
345
+ * @param encryptedResult - `[...hashes, signature]`, as returned by `EncryptInputsBuilder.execute()`
340
346
  * @returns Function arguments with encrypted values inserted (format of CofheInputArgs)
341
347
  */
342
348
  export function insertEncryptedValues<TAbi extends Abi, TFunctionName extends string>(
343
349
  abi: TAbi,
344
350
  functionName: TFunctionName,
345
351
  args: CofheInputArgsPreTransform<TAbi, TFunctionName>,
346
- encryptedValues: readonly EncryptedItemInput[]
352
+ encryptedResult: readonly `0x${string}`[]
347
353
  ): CofheInputArgs<TAbi, TFunctionName> {
348
354
  const abiFunction = getAbiFunction(abi, functionName);
349
355
  const inputs = abiFunction?.inputs;
@@ -355,63 +361,53 @@ export function insertEncryptedValues<TAbi extends Abi, TFunctionName extends st
355
361
  throw new Error('Arguments must be an array');
356
362
  }
357
363
 
358
- // Track position in encrypted values array
359
- let encryptedIndex = 0;
364
+ const hasExternalInputs = inputs.some((input) => paramHasExternalInput(input));
365
+ const signatureSlotIndex = findSignatureSlotIndex(inputs, hasExternalInputs, functionName);
366
+
367
+ const hashes = hasExternalInputs ? encryptedResult.slice(0, -1) : [];
368
+ const signature = hasExternalInputs ? encryptedResult[encryptedResult.length - 1] : undefined;
369
+
370
+ // Track position in the hashes array
371
+ let hashIndex = 0;
360
372
 
361
- // Process each parameter - replaces raw values with encrypted values
362
373
  function processParameter(param: AbiParameter, value: unknown): unknown {
363
374
  const [typeHead, typeSize] = extractArrayParameterType(param.type);
364
- const [internalTypeHead, internalTypeSize] = extractArrayParameterType(param.internalType);
375
+ const [internalTypeHead] = extractArrayParameterType(param.internalType);
365
376
 
366
- // Single encrypted input (tuple with encrypted input type)
367
- if (
368
- typeHead === 'tuple' &&
369
- typeSize == null &&
370
- internalTypeHead != null &&
371
- internalTypeIsEncryptedInput(internalTypeHead)
372
- ) {
373
- if (encryptedIndex >= encryptedValues.length) {
374
- throw new Error(
375
- `Not enough encrypted values: expected at least ${encryptedIndex + 1}, got ${encryptedValues.length}`
376
- );
377
+ // Bare external input - substitute its hash
378
+ if (typeSize == null && internalTypeHead != null && internalTypeIsExternalInput(internalTypeHead)) {
379
+ if (hashIndex >= hashes.length) {
380
+ throw new Error(`Not enough encrypted hashes: expected at least ${hashIndex + 1}, got ${hashes.length}`);
377
381
  }
378
- const encryptedValue = encryptedValues[encryptedIndex];
379
- encryptedIndex++;
380
- return encryptedValue;
382
+ const hash = hashes[hashIndex];
383
+ hashIndex++;
384
+ return hash;
381
385
  }
382
386
 
383
- // Array of encrypted inputs (tuple[2] or tuple[] with encrypted input type)
384
- if (
385
- typeHead === 'tuple' &&
386
- typeSize != null &&
387
- internalTypeHead != null &&
388
- internalTypeIsEncryptedInput(internalTypeHead)
389
- ) {
390
- // Determine how many encrypted values we need
387
+ // Array of external inputs - substitute the corresponding slice of hashes
388
+ if (typeSize != null && internalTypeHead != null && internalTypeIsExternalInput(internalTypeHead)) {
391
389
  const arrayLength = Array.isArray(value) ? value.length : 0;
392
390
  if (arrayLength === 0) {
393
391
  return [];
394
392
  }
395
393
 
396
- if (encryptedIndex + arrayLength > encryptedValues.length) {
394
+ if (hashIndex + arrayLength > hashes.length) {
397
395
  throw new Error(
398
- `Not enough encrypted values: expected at least ${encryptedIndex + arrayLength}, got ${encryptedValues.length}`
396
+ `Not enough encrypted hashes: expected at least ${hashIndex + arrayLength}, got ${hashes.length}`
399
397
  );
400
398
  }
401
399
 
402
- // Extract the required number of encrypted values
403
- const encryptedArray = encryptedValues.slice(encryptedIndex, encryptedIndex + arrayLength);
404
- encryptedIndex += arrayLength;
405
- return encryptedArray;
400
+ const hashArray = hashes.slice(hashIndex, hashIndex + arrayLength);
401
+ hashIndex += arrayLength;
402
+ return hashArray;
406
403
  }
407
404
 
408
- // Tuple recursive case (not encrypted input)
409
- if (typeHead === 'tuple' && (internalTypeHead == null || !internalTypeIsEncryptedInput(internalTypeHead))) {
405
+ // Tuple recursive case (struct that may itself contain external inputs)
406
+ if (typeHead === 'tuple') {
410
407
  if ('components' in param && Array.isArray(param.components)) {
411
408
  const valueObj = value as Record<string, unknown>;
412
409
  const result: Record<string, unknown> = {};
413
410
 
414
- // Process each component in order
415
411
  param.components.forEach((component) => {
416
412
  const componentName = component.name;
417
413
  if (componentName) {
@@ -427,24 +423,28 @@ export function insertEncryptedValues<TAbi extends Abi, TFunctionName extends st
427
423
  return value;
428
424
  }
429
425
 
430
- // Not an encrypted input, return original value
426
+ // Not an external input, return original value
431
427
  return value;
432
428
  }
433
429
 
434
- // Process all inputs in order
430
+ // Rebuild the full arg list; the signature slot is injected directly. `args` omits that slot, so
431
+ // its indices lag by one past the signature position.
432
+ let argIndex = 0;
435
433
  const result = inputs.map((input, index) => {
436
- const arg = args[index];
434
+ if (index === signatureSlotIndex) {
435
+ return signature;
436
+ }
437
+ const arg = args[argIndex];
437
438
  if (arg == null) {
438
- throw new Error(`Argument ${index} is undefined`);
439
+ throw new Error(`Argument ${argIndex} is undefined`);
439
440
  }
441
+ argIndex++;
440
442
  return processParameter(input, arg);
441
443
  });
442
444
 
443
- // Verify we used all encrypted values
444
- if (encryptedIndex !== encryptedValues.length) {
445
- throw new Error(
446
- `Mismatch in encrypted values count: used ${encryptedIndex}, but provided ${encryptedValues.length}`
447
- );
445
+ // Verify we used all encrypted hashes
446
+ if (hashIndex !== hashes.length) {
447
+ throw new Error(`Mismatch in encrypted hashes count: used ${hashIndex}, but provided ${hashes.length}`);
448
448
  }
449
449
 
450
450
  return result as CofheInputArgs<TAbi, TFunctionName>;