@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.
- package/CHANGELOG.md +22 -0
- package/README.md +36 -25
- package/dist/index.cjs +93 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -17
- package/dist/index.d.ts +48 -17
- package/dist/index.js +93 -54
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/encryptedInputs.ts +234 -234
- package/src/fhenixMap.ts +16 -15
- package/src/mockEncrypt.ts +19 -21
package/src/encryptedInputs.ts
CHANGED
|
@@ -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
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type
|
|
17
|
-
type
|
|
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 {
|
|
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
|
|
37
|
+
type ExternalHashToInputMap<E extends AnyExternalHash> = E extends ExternalBoolHash
|
|
32
38
|
? EncryptableBool['data']
|
|
33
|
-
: E extends
|
|
39
|
+
: E extends ExternalUint8Hash
|
|
34
40
|
? EncryptableUint8['data']
|
|
35
|
-
: E extends
|
|
41
|
+
: E extends ExternalUint16Hash
|
|
36
42
|
? EncryptableUint16['data']
|
|
37
|
-
: E extends
|
|
43
|
+
: E extends ExternalUint32Hash
|
|
38
44
|
? EncryptableUint32['data']
|
|
39
|
-
: E extends
|
|
45
|
+
: E extends ExternalUint64Hash
|
|
40
46
|
? EncryptableUint64['data']
|
|
41
|
-
: E extends
|
|
47
|
+
: E extends ExternalUint128Hash
|
|
42
48
|
? EncryptableUint128['data']
|
|
43
|
-
: E extends
|
|
49
|
+
: E extends ExternalAddressHash
|
|
44
50
|
? EncryptableAddress['data']
|
|
45
51
|
: never;
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
94
|
-
internalType
|
|
95
|
-
|
|
96
|
-
)
|
|
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 '
|
|
178
|
+
case 'externalEbool':
|
|
99
179
|
return Encryptable.bool(data as boolean);
|
|
100
|
-
case '
|
|
180
|
+
case 'externalEuint8':
|
|
101
181
|
return Encryptable.uint8(data as string | bigint);
|
|
102
|
-
case '
|
|
182
|
+
case 'externalEuint16':
|
|
103
183
|
return Encryptable.uint16(data as string | bigint);
|
|
104
|
-
case '
|
|
184
|
+
case 'externalEuint32':
|
|
105
185
|
return Encryptable.uint32(data as string | bigint);
|
|
106
|
-
case '
|
|
186
|
+
case 'externalEuint64':
|
|
107
187
|
return Encryptable.uint64(data as string | bigint);
|
|
108
|
-
case '
|
|
188
|
+
case 'externalEuint128':
|
|
109
189
|
return Encryptable.uint128(data as string | bigint);
|
|
110
|
-
case '
|
|
190
|
+
case 'externalEaddress':
|
|
111
191
|
return Encryptable.address(data as string | bigint);
|
|
112
192
|
default:
|
|
113
|
-
throw new Error(`Unknown
|
|
193
|
+
throw new Error(`Unknown external input type: ${internalType}`);
|
|
114
194
|
}
|
|
115
195
|
}
|
|
116
196
|
|
|
117
|
-
function
|
|
118
|
-
internalType:
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
279
|
+
if (index === signatureSlotIndex) return;
|
|
280
|
+
const arg = args[argIndex];
|
|
319
281
|
if (arg == null) {
|
|
320
|
-
throw new Error(`Argument ${
|
|
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
|
|
331
|
-
* the
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
|
|
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
|
|
375
|
+
const [internalTypeHead] = extractArrayParameterType(param.internalType);
|
|
365
376
|
|
|
366
|
-
//
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
return
|
|
382
|
+
const hash = hashes[hashIndex];
|
|
383
|
+
hashIndex++;
|
|
384
|
+
return hash;
|
|
381
385
|
}
|
|
382
386
|
|
|
383
|
-
// Array of
|
|
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 (
|
|
394
|
+
if (hashIndex + arrayLength > hashes.length) {
|
|
397
395
|
throw new Error(
|
|
398
|
-
`Not enough encrypted
|
|
396
|
+
`Not enough encrypted hashes: expected at least ${hashIndex + arrayLength}, got ${hashes.length}`
|
|
399
397
|
);
|
|
400
398
|
}
|
|
401
399
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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 (
|
|
409
|
-
if (typeHead === 'tuple'
|
|
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
|
|
426
|
+
// Not an external input, return original value
|
|
431
427
|
return value;
|
|
432
428
|
}
|
|
433
429
|
|
|
434
|
-
//
|
|
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
|
-
|
|
434
|
+
if (index === signatureSlotIndex) {
|
|
435
|
+
return signature;
|
|
436
|
+
}
|
|
437
|
+
const arg = args[argIndex];
|
|
437
438
|
if (arg == null) {
|
|
438
|
-
throw new Error(`Argument ${
|
|
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
|
|
444
|
-
if (
|
|
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>;
|