@cofhe/sdk 0.6.0 → 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 +49 -0
- package/acps/acp.ts +411 -0
- package/acps/index.ts +70 -0
- package/{permits → acps}/onchain-utils.ts +49 -24
- package/acps/sealing.ts +90 -0
- package/acps/signature.ts +89 -0
- package/acps/store.ts +172 -0
- package/acps/test/acp.test.ts +615 -0
- package/acps/test/localstorage.test.ts +105 -0
- package/acps/test/sealing.test.ts +77 -0
- package/acps/test/store.test.ts +88 -0
- package/acps/test/validation.test.ts +361 -0
- package/acps/test-utils.ts +32 -0
- package/acps/types.ts +252 -0
- package/acps/validation.ts +392 -0
- package/adapters/test/ethers5.test.ts +4 -1
- package/adapters/test/ethers6.test.ts +4 -1
- package/adapters/test/wagmi.test.ts +5 -2
- package/chains/chains/stagingCofhe.ts +21 -0
- package/chains/index.ts +3 -1
- package/chains/test/chains.test.ts +2 -1
- package/core/acps.ts +625 -0
- package/core/client.ts +136 -39
- package/core/clientTypes.ts +52 -41
- package/core/config.ts +66 -5
- package/core/debug.ts +72 -0
- package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
- package/core/decrypt/apiError.ts +104 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
- package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
- package/core/decrypt/decryptForTxBuilder.ts +102 -102
- package/core/decrypt/decryptForViewBuilder.ts +90 -90
- package/core/decrypt/submitRetry.ts +38 -30
- package/core/decrypt/tnDecryptV1.ts +5 -5
- package/core/decrypt/tnDecryptV2.ts +45 -34
- package/core/decrypt/tnSealOutputV1.ts +4 -3
- package/core/decrypt/tnSealOutputV2.ts +44 -31
- package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
- package/core/encrypt/encryptInputsBuilder.ts +86 -52
- package/core/encrypt/zkPackProveVerify.ts +25 -18
- package/core/error.ts +34 -6
- package/core/index.ts +14 -14
- package/core/test/acpDefaults.test.ts +52 -0
- package/core/test/acps.test.ts +596 -0
- package/core/test/apiError.test.ts +130 -0
- package/core/test/client.test.ts +22 -19
- package/core/test/config.test.ts +25 -5
- package/core/test/decrypt.test.ts +40 -35
- package/core/test/decryptBuilders.test.ts +68 -68
- package/core/test/decryptErrorCodes.test.ts +217 -0
- package/core/test/encryptInputsBuilder.test.ts +72 -41
- package/core/test/pollCallbacks.test.ts +89 -18
- package/core/test/stagingRedirect.ts +18 -0
- package/core/test/submitRetry.test.ts +182 -0
- package/core/types.ts +9 -69
- package/dist/acp-Wi6isVQI.d.cts +407 -0
- package/dist/acp-Wi6isVQI.d.ts +407 -0
- package/dist/acps.cjs +1081 -0
- package/dist/acps.d.cts +480 -0
- package/dist/acps.d.ts +480 -0
- package/dist/acps.js +2 -0
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +30 -1
- package/dist/chains.d.ts +30 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
- package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
- package/dist/chunk-Q7CBWGQX.js +1029 -0
- package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
- package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
- package/dist/core.cjs +1431 -1026
- package/dist/core.d.cts +55 -10
- package/dist/core.d.ts +55 -10
- package/dist/core.js +3 -3
- package/dist/node.cjs +1356 -976
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/web.cjs +1356 -976
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/node/test/inherited.test.ts +75 -65
- package/node/test/tfheinit.test.ts +23 -8
- package/package.json +6 -6
- package/web/test/client.web.test.ts +5 -1
- package/web/test/inherited.web.test.ts +75 -65
- package/web/test/tfheinit.web.test.ts +14 -5
- package/web/test/worker.config.web.test.ts +38 -23
- package/web/test/worker.output.web.test.ts +25 -24
- package/core/encrypt/encryptUtils.ts +0 -67
- package/core/permits.ts +0 -206
- package/core/test/permits.test.ts +0 -534
- package/dist/chunk-VB62WYPL.js +0 -978
- package/dist/permit-DnVMDT5h.d.cts +0 -376
- package/dist/permit-DnVMDT5h.d.ts +0 -376
- package/dist/permits.cjs +0 -1026
- package/dist/permits.d.cts +0 -353
- package/dist/permits.d.ts +0 -353
- package/dist/permits.js +0 -2
- package/permits/index.ts +0 -68
- package/permits/permit.ts +0 -385
- package/permits/sealing.ts +0 -131
- package/permits/signature.ts +0 -79
- package/permits/store.ts +0 -157
- package/permits/test/localstorage.test.ts +0 -113
- package/permits/test/permit.test.ts +0 -557
- package/permits/test/sealing.test.ts +0 -84
- package/permits/test/store.test.ts +0 -88
- package/permits/test/validation.test.ts +0 -361
- package/permits/test-utils.ts +0 -28
- package/permits/types.ts +0 -204
- package/permits/validation.ts +0 -327
- /package/{permits → acps}/utils.ts +0 -0
|
@@ -39,13 +39,16 @@ export const MockThresholdNetworkAbi = [
|
|
|
39
39
|
{
|
|
40
40
|
name: 'permission',
|
|
41
41
|
type: 'tuple',
|
|
42
|
-
internalType: 'struct
|
|
42
|
+
internalType: 'struct ACPermission',
|
|
43
43
|
components: [
|
|
44
44
|
{ name: 'issuer', type: 'address', internalType: 'address' },
|
|
45
45
|
{ name: 'expiration', type: 'uint64', internalType: 'uint64' },
|
|
46
46
|
{ name: 'recipient', type: 'address', internalType: 'address' },
|
|
47
|
-
{ name: '
|
|
48
|
-
{ name: '
|
|
47
|
+
{ name: 'revokerData', type: 'uint256', internalType: 'uint256' },
|
|
48
|
+
{ name: 'revokerContract', type: 'address', internalType: 'address' },
|
|
49
|
+
{ name: 'scope', type: 'uint8', internalType: 'uint8' },
|
|
50
|
+
{ name: 'contracts', type: 'address[]', internalType: 'address[]' },
|
|
51
|
+
{ name: 'handles', type: 'bytes32[]', internalType: 'bytes32[]' },
|
|
49
52
|
{ name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
|
|
50
53
|
{ name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
|
|
51
54
|
{ name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
|
|
@@ -68,13 +71,16 @@ export const MockThresholdNetworkAbi = [
|
|
|
68
71
|
{
|
|
69
72
|
name: 'permission',
|
|
70
73
|
type: 'tuple',
|
|
71
|
-
internalType: 'struct
|
|
74
|
+
internalType: 'struct ACPermission',
|
|
72
75
|
components: [
|
|
73
76
|
{ name: 'issuer', type: 'address', internalType: 'address' },
|
|
74
77
|
{ name: 'expiration', type: 'uint64', internalType: 'uint64' },
|
|
75
78
|
{ name: 'recipient', type: 'address', internalType: 'address' },
|
|
76
|
-
{ name: '
|
|
77
|
-
{ name: '
|
|
79
|
+
{ name: 'revokerData', type: 'uint256', internalType: 'uint256' },
|
|
80
|
+
{ name: 'revokerContract', type: 'address', internalType: 'address' },
|
|
81
|
+
{ name: 'scope', type: 'uint8', internalType: 'uint8' },
|
|
82
|
+
{ name: 'contracts', type: 'address[]', internalType: 'address[]' },
|
|
83
|
+
{ name: 'handles', type: 'bytes32[]', internalType: 'bytes32[]' },
|
|
78
84
|
{ name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
|
|
79
85
|
{ name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
|
|
80
86
|
{ name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
|
|
@@ -139,19 +145,22 @@ export const MockThresholdNetworkAbi = [
|
|
|
139
145
|
},
|
|
140
146
|
{
|
|
141
147
|
type: 'function',
|
|
142
|
-
name: '
|
|
148
|
+
name: 'decryptForTxWithACP',
|
|
143
149
|
inputs: [
|
|
144
150
|
{ name: 'ctHash', type: 'uint256', internalType: 'uint256' },
|
|
145
151
|
{
|
|
146
152
|
name: 'permission',
|
|
147
153
|
type: 'tuple',
|
|
148
|
-
internalType: 'struct
|
|
154
|
+
internalType: 'struct ACPermission',
|
|
149
155
|
components: [
|
|
150
156
|
{ name: 'issuer', type: 'address', internalType: 'address' },
|
|
151
157
|
{ name: 'expiration', type: 'uint64', internalType: 'uint64' },
|
|
152
158
|
{ name: 'recipient', type: 'address', internalType: 'address' },
|
|
153
|
-
{ name: '
|
|
154
|
-
{ name: '
|
|
159
|
+
{ name: 'revokerData', type: 'uint256', internalType: 'uint256' },
|
|
160
|
+
{ name: 'revokerContract', type: 'address', internalType: 'address' },
|
|
161
|
+
{ name: 'scope', type: 'uint8', internalType: 'uint8' },
|
|
162
|
+
{ name: 'contracts', type: 'address[]', internalType: 'address[]' },
|
|
163
|
+
{ name: 'handles', type: 'bytes32[]', internalType: 'bytes32[]' },
|
|
155
164
|
{ name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
|
|
156
165
|
{ name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
|
|
157
166
|
{ name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
|
|
@@ -167,7 +176,7 @@ export const MockThresholdNetworkAbi = [
|
|
|
167
176
|
},
|
|
168
177
|
{
|
|
169
178
|
type: 'function',
|
|
170
|
-
name: '
|
|
179
|
+
name: 'decryptForTxWithoutACP',
|
|
171
180
|
inputs: [{ name: 'ctHash', type: 'uint256', internalType: 'uint256' }],
|
|
172
181
|
outputs: [
|
|
173
182
|
{ name: 'allowed', type: 'bool', internalType: 'bool' },
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CofheErrorCode } from '../error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable backend error codes returned by the threshold-network server's
|
|
5
|
+
* `{ "error": "<stable_code>", "error_message": "<detail>" }` payload, see API-RESPONSES.md.
|
|
6
|
+
* `ct_not_ready`/`overloaded` are excluded: v2 always represents those as a bodyless 204, so
|
|
7
|
+
* they never reach this type.
|
|
8
|
+
*/
|
|
9
|
+
export type BackendApiErrorCode =
|
|
10
|
+
| 'bad_request'
|
|
11
|
+
| 'unknown_chain'
|
|
12
|
+
| 'acp_malformed'
|
|
13
|
+
| 'acp_denied'
|
|
14
|
+
| 'acp_expired'
|
|
15
|
+
| 'acp_invalid'
|
|
16
|
+
| 'acp_required'
|
|
17
|
+
| 'acp_verifier_error'
|
|
18
|
+
| 'acp_verifier_timeout'
|
|
19
|
+
| 'not_publicly_allowed'
|
|
20
|
+
| 'ct_not_found'
|
|
21
|
+
| 'unsupported_security_zone'
|
|
22
|
+
| 'unsupported_type'
|
|
23
|
+
| 'internal_error'
|
|
24
|
+
| 'signing_failed'
|
|
25
|
+
| 'ct_source_error'
|
|
26
|
+
| 'ct_source_timeout'
|
|
27
|
+
| 'seal_failed';
|
|
28
|
+
|
|
29
|
+
const BACKEND_ERROR_CODE_TO_COFHE_ERROR_CODE: Record<BackendApiErrorCode, CofheErrorCode> = {
|
|
30
|
+
bad_request: CofheErrorCode.BadRequest,
|
|
31
|
+
unknown_chain: CofheErrorCode.UnknownChain,
|
|
32
|
+
acp_malformed: CofheErrorCode.ACPMalformed,
|
|
33
|
+
acp_denied: CofheErrorCode.ACPDenied, // ACP-era backends fold revoked into denied
|
|
34
|
+
acp_expired: CofheErrorCode.ACPExpired,
|
|
35
|
+
acp_invalid: CofheErrorCode.ACPInvalid,
|
|
36
|
+
acp_required: CofheErrorCode.ACPRequired,
|
|
37
|
+
acp_verifier_error: CofheErrorCode.ACPVerifierError,
|
|
38
|
+
acp_verifier_timeout: CofheErrorCode.ACPVerifierTimeout,
|
|
39
|
+
not_publicly_allowed: CofheErrorCode.NotPubliclyAllowed,
|
|
40
|
+
ct_not_found: CofheErrorCode.CtNotFound,
|
|
41
|
+
unsupported_security_zone: CofheErrorCode.UnsupportedSecurityZone,
|
|
42
|
+
unsupported_type: CofheErrorCode.UnsupportedType,
|
|
43
|
+
internal_error: CofheErrorCode.InternalError,
|
|
44
|
+
signing_failed: CofheErrorCode.SigningFailed,
|
|
45
|
+
ct_source_error: CofheErrorCode.CtSourceError,
|
|
46
|
+
ct_source_timeout: CofheErrorCode.CtSourceTimeout,
|
|
47
|
+
seal_failed: CofheErrorCode.SealFailed,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function isBackendApiErrorCode(value: string): value is BackendApiErrorCode {
|
|
51
|
+
return value in BACKEND_ERROR_CODE_TO_COFHE_ERROR_CODE;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ParsedApiError = {
|
|
55
|
+
/** Raw `error` field from the backend body, verbatim, even if unrecognized. */
|
|
56
|
+
apiErrorCode?: string;
|
|
57
|
+
/** `error_message`, else `message`, else `HTTP <status>` / statusText. */
|
|
58
|
+
errorMessage: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reads and parses a fetch Response's JSON error body per the shared `{ error, error_message }`
|
|
63
|
+
* contract. Never throws — falls back to a status/statusText-derived message if the body isn't
|
|
64
|
+
* JSON (e.g. axum framework rejections, which are plain text).
|
|
65
|
+
*/
|
|
66
|
+
export async function parseApiErrorResponseBody(response: Response): Promise<ParsedApiError> {
|
|
67
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
68
|
+
let apiErrorCode: string | undefined;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const body = (await response.json()) as unknown;
|
|
72
|
+
if (body && typeof body === 'object') {
|
|
73
|
+
const record = body as Record<string, unknown>;
|
|
74
|
+
if (typeof record.error === 'string' && record.error.length > 0) {
|
|
75
|
+
apiErrorCode = record.error;
|
|
76
|
+
}
|
|
77
|
+
if (typeof record.error_message === 'string' && record.error_message.length > 0) {
|
|
78
|
+
errorMessage = record.error_message;
|
|
79
|
+
} else if (typeof record.message === 'string' && record.message.length > 0) {
|
|
80
|
+
errorMessage = record.message;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
errorMessage = response.statusText || errorMessage;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { apiErrorCode, errorMessage };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Maps a raw backend `error` string to a CofheErrorCode. Unrecognized or undefined codes fall
|
|
92
|
+
* back to `fallback` so future/undocumented backend codes degrade gracefully instead of
|
|
93
|
+
* throwing — the raw string is still preserved via `CofheError.apiErrorCode` on the resulting
|
|
94
|
+
* error.
|
|
95
|
+
*/
|
|
96
|
+
export function mapApiErrorCodeToCofheErrorCode(
|
|
97
|
+
apiErrorCode: string | undefined,
|
|
98
|
+
fallback: CofheErrorCode
|
|
99
|
+
): CofheErrorCode {
|
|
100
|
+
if (apiErrorCode && isBackendApiErrorCode(apiErrorCode)) {
|
|
101
|
+
return BACKEND_ERROR_CODE_TO_COFHE_ERROR_CODE[apiErrorCode];
|
|
102
|
+
}
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ACP, ACPUtils } from '@/acps';
|
|
2
2
|
|
|
3
3
|
import { encodePacked, keccak256, type PublicClient } from 'viem';
|
|
4
4
|
import { sign } from 'viem/accounts';
|
|
@@ -22,34 +22,34 @@ export type DecryptForTxMocksResult = {
|
|
|
22
22
|
export async function cofheMocksDecryptForTx(
|
|
23
23
|
ctHash: bigint | string,
|
|
24
24
|
utype: FheTypes,
|
|
25
|
-
|
|
25
|
+
acp: ACP | null,
|
|
26
26
|
publicClient: PublicClient
|
|
27
27
|
): Promise<DecryptForTxMocksResult> {
|
|
28
28
|
let allowed: boolean;
|
|
29
29
|
let error: string;
|
|
30
30
|
let decryptedValue: bigint;
|
|
31
31
|
|
|
32
|
-
// With
|
|
33
|
-
if (
|
|
34
|
-
|
|
32
|
+
// With acp
|
|
33
|
+
if (acp !== null) {
|
|
34
|
+
const wireAcp = ACPUtils.getPublic(acp, true);
|
|
35
35
|
const permissionWithBigInts = {
|
|
36
|
-
...
|
|
37
|
-
expiration: BigInt(
|
|
38
|
-
|
|
36
|
+
...wireAcp,
|
|
37
|
+
expiration: BigInt(wireAcp.expiration),
|
|
38
|
+
revokerData: BigInt(wireAcp.revokerData),
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
[allowed, error, decryptedValue] = await publicClient.readContract({
|
|
42
42
|
address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
|
|
43
43
|
abi: MockThresholdNetworkAbi,
|
|
44
|
-
functionName: '
|
|
44
|
+
functionName: 'decryptForTxWithACP',
|
|
45
45
|
args: [BigInt(ctHash), permissionWithBigInts],
|
|
46
46
|
});
|
|
47
47
|
} else {
|
|
48
|
-
// Without
|
|
48
|
+
// Without acp (global allowance)
|
|
49
49
|
[allowed, error, decryptedValue] = await publicClient.readContract({
|
|
50
50
|
address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
|
|
51
51
|
abi: MockThresholdNetworkAbi,
|
|
52
|
-
functionName: '
|
|
52
|
+
functionName: 'decryptForTxWithoutACP',
|
|
53
53
|
args: [BigInt(ctHash)],
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ACP, ACPUtils } from '@/acps';
|
|
2
2
|
|
|
3
3
|
import { type PublicClient } from 'viem';
|
|
4
4
|
import { MockThresholdNetworkAbi } from './MockThresholdNetworkAbi.js';
|
|
@@ -9,14 +9,14 @@ import { MOCKS_THRESHOLD_NETWORK_ADDRESS } from '../consts.js';
|
|
|
9
9
|
export async function cofheMocksDecryptForView(
|
|
10
10
|
ctHash: bigint | string,
|
|
11
11
|
utype: FheTypes,
|
|
12
|
-
|
|
12
|
+
acp: ACP,
|
|
13
13
|
publicClient: PublicClient
|
|
14
14
|
): Promise<bigint> {
|
|
15
|
-
const
|
|
15
|
+
const wireAcp = ACPUtils.getPublic(acp, true);
|
|
16
16
|
const permissionWithBigInts = {
|
|
17
|
-
...
|
|
18
|
-
expiration: BigInt(
|
|
19
|
-
|
|
17
|
+
...wireAcp,
|
|
18
|
+
expiration: BigInt(wireAcp.expiration),
|
|
19
|
+
revokerData: BigInt(wireAcp.revokerData),
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const [allowed, error, result] = await publicClient.readContract({
|
|
@@ -41,7 +41,7 @@ export async function cofheMocksDecryptForView(
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const sealedBigInt = BigInt(result);
|
|
44
|
-
const sealingKeyBigInt = BigInt(
|
|
44
|
+
const sealingKeyBigInt = BigInt(acp.sealingKey);
|
|
45
45
|
const unsealed = sealedBigInt ^ sealingKeyBigInt;
|
|
46
46
|
|
|
47
47
|
return unsealed;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-dupe-class-members */
|
|
2
2
|
import { hardhat } from '@/chains';
|
|
3
|
-
import { type
|
|
3
|
+
import { type ACP, type ACPPublic, ACPUtils } from '@/acps';
|
|
4
4
|
|
|
5
5
|
import { FheTypes } from '../types';
|
|
6
6
|
import { getThresholdNetworkUrlOrThrow } from '../config';
|
|
7
7
|
import { CofheError, CofheErrorCode } from '../error';
|
|
8
|
-
import {
|
|
8
|
+
import { acps } from '../acps';
|
|
9
9
|
import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder';
|
|
10
10
|
import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx';
|
|
11
11
|
import { getPublicClientChainID, sleep } from '../utils';
|
|
@@ -20,23 +20,23 @@ const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
|
|
|
20
20
|
* await client.decryptForTx(ctHash)
|
|
21
21
|
* .setChainId(chainId)
|
|
22
22
|
* .setAccount(account)
|
|
23
|
-
* .
|
|
24
|
-
* // or .
|
|
23
|
+
* .withACP(acp | acpHash | undefined)
|
|
24
|
+
* // or .withoutACP()
|
|
25
25
|
* .execute()
|
|
26
26
|
*
|
|
27
27
|
* If chainId not set, uses client's chainId
|
|
28
28
|
* If account not set, uses client's account
|
|
29
|
-
* You MUST choose one
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
29
|
+
* You MUST choose one acp mode before calling execute():
|
|
30
|
+
* - withACP(...) to decrypt using an ACP
|
|
31
|
+
* - withoutACP() to decrypt via global allowance (no acp)
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* withACP() (no args / undefined) uses the active acp for chainId + account.
|
|
34
|
+
* withoutACP() uses global allowance (no acp required).
|
|
35
35
|
*
|
|
36
36
|
* Returns the decrypted value + proof ready for tx.
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
type
|
|
39
|
+
type DecryptForTxACPSelection = 'unset' | 'with-acp' | 'without-acp';
|
|
40
40
|
|
|
41
41
|
type DecryptForTxBuilderParams = BaseBuilderParams & {
|
|
42
42
|
ctHash: bigint | string;
|
|
@@ -51,18 +51,18 @@ export type DecryptForTxResult = {
|
|
|
51
51
|
/**
|
|
52
52
|
* Type-level gating:
|
|
53
53
|
* - The initial builder returned from `client.decryptForTx(...)` intentionally does not expose `execute()`.
|
|
54
|
-
* - Calling `
|
|
55
|
-
* exposes `
|
|
54
|
+
* - Calling `withACP(...)` or `withoutACP()` returns a builder that *does* expose `execute()`, but no longer
|
|
55
|
+
* exposes `withACP/withoutACP` (so you can't select twice, or switch modes).
|
|
56
56
|
*/
|
|
57
57
|
export type DecryptForTxBuilderUnset = Omit<DecryptForTxBuilder, 'execute'>;
|
|
58
58
|
|
|
59
|
-
export type DecryptForTxBuilderSelected = Omit<DecryptForTxBuilder, '
|
|
59
|
+
export type DecryptForTxBuilderSelected = Omit<DecryptForTxBuilder, 'withACP' | 'withoutACP'>;
|
|
60
60
|
|
|
61
61
|
export class DecryptForTxBuilder extends BaseBuilder {
|
|
62
62
|
private ctHash: bigint | string;
|
|
63
|
-
private
|
|
64
|
-
private
|
|
65
|
-
private
|
|
63
|
+
private acpHash?: string;
|
|
64
|
+
private acp?: ACP;
|
|
65
|
+
private acpSelection: DecryptForTxACPSelection = 'unset';
|
|
66
66
|
private pollCallback?: DecryptPollCallbackFunction;
|
|
67
67
|
private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
|
|
68
68
|
|
|
@@ -80,7 +80,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct
|
|
83
|
+
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
|
|
84
84
|
*
|
|
85
85
|
* If not provided, the chainId will be fetched from the connected publicClient.
|
|
86
86
|
*
|
|
@@ -105,7 +105,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* @param account - Account to decrypt values from. Used to fetch the correct
|
|
108
|
+
* @param account - Account to decrypt values from. Used to fetch the correct acp.
|
|
109
109
|
*
|
|
110
110
|
* If not provided, the account will be fetched from the connected walletClient.
|
|
111
111
|
*
|
|
@@ -154,86 +154,86 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Select "use
|
|
157
|
+
* Select "use acp" mode.
|
|
158
158
|
*
|
|
159
|
-
* - `
|
|
160
|
-
* - `
|
|
161
|
-
* - `
|
|
159
|
+
* - `withACP(acp)` uses the provided acp.
|
|
160
|
+
* - `withACP(acpHash)` fetches that acp.
|
|
161
|
+
* - `withACP()` uses the active acp for the resolved `chainId + account`.
|
|
162
162
|
*
|
|
163
|
-
* Note: "global allowance" (no
|
|
163
|
+
* Note: "global allowance" (no acp) is ONLY available via `withoutACP()`.
|
|
164
164
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this.
|
|
165
|
+
withACP(): DecryptForTxBuilderSelected;
|
|
166
|
+
withACP(acpHash: string): DecryptForTxBuilderSelected;
|
|
167
|
+
withACP(acp: ACP): DecryptForTxBuilderSelected;
|
|
168
|
+
withACP(acpOrACPHash?: ACP | string): DecryptForTxBuilderSelected {
|
|
169
|
+
if (this.acpSelection === 'with-acp') {
|
|
170
170
|
throw new CofheError({
|
|
171
171
|
code: CofheErrorCode.InternalError,
|
|
172
|
-
message: 'decryptForTx:
|
|
173
|
-
hint: 'Choose the
|
|
172
|
+
message: 'decryptForTx: withACP() can only be selected once.',
|
|
173
|
+
hint: 'Choose the acp mode once. If you need a different acp, start a new decryptForTx() builder chain.',
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
if (this.
|
|
177
|
+
if (this.acpSelection === 'without-acp') {
|
|
178
178
|
throw new CofheError({
|
|
179
179
|
code: CofheErrorCode.InternalError,
|
|
180
|
-
message: 'decryptForTx: cannot call
|
|
181
|
-
hint: 'Choose exactly one
|
|
180
|
+
message: 'decryptForTx: cannot call withACP() after withoutACP() has been selected.',
|
|
181
|
+
hint: 'Choose exactly one acp mode: either call .withACP(...) or .withoutACP(), but not both.',
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
this.
|
|
185
|
+
this.acpSelection = 'with-acp';
|
|
186
186
|
|
|
187
|
-
if (typeof
|
|
188
|
-
this.
|
|
189
|
-
this.
|
|
190
|
-
} else if (
|
|
191
|
-
// Explicitly choose "active
|
|
192
|
-
this.
|
|
193
|
-
this.
|
|
187
|
+
if (typeof acpOrACPHash === 'string') {
|
|
188
|
+
this.acpHash = acpOrACPHash;
|
|
189
|
+
this.acp = undefined;
|
|
190
|
+
} else if (acpOrACPHash === undefined) {
|
|
191
|
+
// Explicitly choose "active acp" resolution at execute()
|
|
192
|
+
this.acpHash = undefined;
|
|
193
|
+
this.acp = undefined;
|
|
194
194
|
} else {
|
|
195
|
-
//
|
|
196
|
-
this.
|
|
197
|
-
this.
|
|
195
|
+
// ACP object
|
|
196
|
+
this.acp = acpOrACPHash;
|
|
197
|
+
this.acpHash = undefined;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
return this as unknown as DecryptForTxBuilderSelected;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Select "no
|
|
204
|
+
* Select "no acp" mode.
|
|
205
205
|
*
|
|
206
|
-
* This uses global allowance (no
|
|
206
|
+
* This uses global allowance (no acp required) and sends an empty permission payload to `/decrypt`.
|
|
207
207
|
*/
|
|
208
|
-
|
|
209
|
-
if (this.
|
|
208
|
+
withoutACP(): DecryptForTxBuilderSelected {
|
|
209
|
+
if (this.acpSelection === 'without-acp') {
|
|
210
210
|
throw new CofheError({
|
|
211
211
|
code: CofheErrorCode.InternalError,
|
|
212
|
-
message: 'decryptForTx:
|
|
213
|
-
hint: 'Choose the
|
|
212
|
+
message: 'decryptForTx: withoutACP() can only be selected once.',
|
|
213
|
+
hint: 'Choose the acp mode once. If you need a different mode, start a new decryptForTx() builder chain.',
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
if (this.
|
|
217
|
+
if (this.acpSelection === 'with-acp') {
|
|
218
218
|
throw new CofheError({
|
|
219
219
|
code: CofheErrorCode.InternalError,
|
|
220
|
-
message: 'decryptForTx: cannot call
|
|
221
|
-
hint: 'Choose exactly one
|
|
220
|
+
message: 'decryptForTx: cannot call withoutACP() after withACP() has been selected.',
|
|
221
|
+
hint: 'Choose exactly one acp mode: either call .withACP(...) or .withoutACP(), but not both.',
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
this.
|
|
226
|
-
this.
|
|
227
|
-
this.
|
|
225
|
+
this.acpSelection = 'without-acp';
|
|
226
|
+
this.acpHash = undefined;
|
|
227
|
+
this.acp = undefined;
|
|
228
228
|
return this as unknown as DecryptForTxBuilderSelected;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
return this.
|
|
231
|
+
getACP(): ACP | undefined {
|
|
232
|
+
return this.acp;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
return this.
|
|
235
|
+
getACPHash(): string | undefined {
|
|
236
|
+
return this.acpHash;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
private async getThresholdNetworkUrl(): Promise<string> {
|
|
@@ -241,63 +241,63 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
241
241
|
return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
private async
|
|
245
|
-
if (this.
|
|
244
|
+
private async getResolvedACP(): Promise<ACP | null> {
|
|
245
|
+
if (this.acpSelection === 'unset') {
|
|
246
246
|
throw new CofheError({
|
|
247
247
|
code: CofheErrorCode.InternalError,
|
|
248
|
-
message: 'decryptForTx: missing
|
|
249
|
-
hint: 'Call .
|
|
248
|
+
message: 'decryptForTx: missing acp selection; call withACP(...) or withoutACP() before execute().',
|
|
249
|
+
hint: 'Call .withACP() to use the active acp, or .withoutACP() for global allowance.',
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
if (this.
|
|
253
|
+
if (this.acpSelection === 'without-acp') {
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
// with-
|
|
258
|
-
if (this.
|
|
257
|
+
// with-acp mode
|
|
258
|
+
if (this.acp) return this.acp;
|
|
259
259
|
|
|
260
260
|
this.assertChainId();
|
|
261
261
|
this.assertAccount();
|
|
262
262
|
|
|
263
|
-
// Fetch with
|
|
264
|
-
if (this.
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
263
|
+
// Fetch with acp hash
|
|
264
|
+
if (this.acpHash) {
|
|
265
|
+
const acp = await acps.getACP(this.chainId, this.account, this.acpHash);
|
|
266
|
+
if (!acp) {
|
|
267
267
|
throw new CofheError({
|
|
268
|
-
code: CofheErrorCode.
|
|
269
|
-
message: `
|
|
270
|
-
hint: 'Ensure the
|
|
268
|
+
code: CofheErrorCode.ACPNotFound,
|
|
269
|
+
message: `ACP with hash <${this.acpHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
|
|
270
|
+
hint: 'Ensure the acp exists and is valid.',
|
|
271
271
|
context: {
|
|
272
272
|
chainId: this.chainId,
|
|
273
273
|
account: this.account,
|
|
274
|
-
|
|
274
|
+
acpHash: this.acpHash,
|
|
275
275
|
},
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
|
-
return
|
|
278
|
+
return acp;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
// Fetch active
|
|
282
|
-
const
|
|
283
|
-
if (!
|
|
281
|
+
// Fetch active acp (default for withACP() with no args)
|
|
282
|
+
const acp = await acps.getActiveACP(this.chainId, this.account);
|
|
283
|
+
if (!acp) {
|
|
284
284
|
throw new CofheError({
|
|
285
|
-
code: CofheErrorCode.
|
|
286
|
-
message: `Active
|
|
287
|
-
hint: 'Create
|
|
285
|
+
code: CofheErrorCode.ACPNotFound,
|
|
286
|
+
message: `Active acp not found for chainId <${this.chainId}> and account <${this.account}>`,
|
|
287
|
+
hint: 'Create an ACP (e.g. client.acp.createSelf(...)) and/or set it active (client.acp.selectActiveACP(hash)).',
|
|
288
288
|
context: {
|
|
289
289
|
chainId: this.chainId,
|
|
290
290
|
account: this.account,
|
|
291
291
|
},
|
|
292
292
|
});
|
|
293
293
|
}
|
|
294
|
-
return
|
|
294
|
+
return acp;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
298
|
* On hardhat, interact with MockThresholdNetwork contract
|
|
299
299
|
*/
|
|
300
|
-
private async mocksDecryptForTx(
|
|
300
|
+
private async mocksDecryptForTx(acp: ACP | null): Promise<DecryptForTxResult> {
|
|
301
301
|
this.assertPublicClient();
|
|
302
302
|
|
|
303
303
|
// Configurable delay before decrypting to simulate the CoFHE decrypt processing time
|
|
@@ -306,24 +306,24 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
306
306
|
const delay = this.config.mocks.decryptDelay;
|
|
307
307
|
if (delay > 0) await sleep(delay);
|
|
308
308
|
|
|
309
|
-
const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes,
|
|
309
|
+
const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes, acp, this.publicClient);
|
|
310
310
|
return result;
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
/**
|
|
314
314
|
* In the production context, perform a true decryption with the CoFHE coprocessor.
|
|
315
315
|
*/
|
|
316
|
-
private async productionDecryptForTx(
|
|
316
|
+
private async productionDecryptForTx(acp: ACP | null): Promise<DecryptForTxResult> {
|
|
317
317
|
this.assertChainId();
|
|
318
318
|
this.assertPublicClient();
|
|
319
319
|
|
|
320
320
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
321
321
|
|
|
322
|
-
const
|
|
322
|
+
const wireAcp = acp ? ACPUtils.getPublic(acp, true) : null;
|
|
323
323
|
const { decryptedValue, signature } = await tnDecryptV2({
|
|
324
324
|
ctHash: this.ctHash,
|
|
325
325
|
chainId: this.chainId,
|
|
326
|
-
|
|
326
|
+
acp: wireAcp,
|
|
327
327
|
thresholdNetworkUrl,
|
|
328
328
|
retry404TimeoutMs: this.retry404TimeoutMs,
|
|
329
329
|
onPoll: this.pollCallback,
|
|
@@ -339,29 +339,29 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
339
339
|
/**
|
|
340
340
|
* Final step of the decryptForTx process. MUST BE CALLED LAST IN THE CHAIN.
|
|
341
341
|
*
|
|
342
|
-
* You must explicitly choose one
|
|
343
|
-
* - `
|
|
344
|
-
* - `
|
|
342
|
+
* You must explicitly choose one acp mode before calling `execute()`:
|
|
343
|
+
* - `withACP(acp)` / `withACP(acpHash)` / `withACP()` (active acp)
|
|
344
|
+
* - `withoutACP()` (global allowance)
|
|
345
345
|
*/
|
|
346
346
|
async execute(): Promise<DecryptForTxResult> {
|
|
347
|
-
// Resolve
|
|
348
|
-
const
|
|
347
|
+
// Resolve acp (can be ACP object or null for global allowance)
|
|
348
|
+
const acp = await this.getResolvedACP();
|
|
349
349
|
|
|
350
|
-
// If
|
|
351
|
-
if (
|
|
352
|
-
// Ensure
|
|
353
|
-
|
|
350
|
+
// If acp is provided, validate it
|
|
351
|
+
if (acp !== null) {
|
|
352
|
+
// Ensure acp validity
|
|
353
|
+
ACPUtils.validate(acp);
|
|
354
354
|
|
|
355
|
-
// Extract chainId from signed
|
|
356
|
-
const chainId =
|
|
355
|
+
// Extract chainId from signed acp
|
|
356
|
+
const chainId = acp._signedDomain!.chainId;
|
|
357
357
|
|
|
358
358
|
if (chainId === hardhat.id) {
|
|
359
|
-
return await this.mocksDecryptForTx(
|
|
359
|
+
return await this.mocksDecryptForTx(acp);
|
|
360
360
|
} else {
|
|
361
|
-
return await this.productionDecryptForTx(
|
|
361
|
+
return await this.productionDecryptForTx(acp);
|
|
362
362
|
}
|
|
363
363
|
} else {
|
|
364
|
-
// Global allowance - no
|
|
364
|
+
// Global allowance - no acp
|
|
365
365
|
// If chainId not set, try to get it from publicClient
|
|
366
366
|
if (!this.chainId) {
|
|
367
367
|
this.assertPublicClient();
|