@inco/shield-js 0.0.0-bootstrap.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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +359 -0
  3. package/dist/binary.d.ts +3 -0
  4. package/dist/binary.js +16 -0
  5. package/dist/contracts/abi.d.ts +6800 -0
  6. package/dist/contracts/abi.js +8836 -0
  7. package/dist/contracts/index.d.ts +2 -0
  8. package/dist/contracts/index.js +2 -0
  9. package/dist/contracts/utils.d.ts +25 -0
  10. package/dist/contracts/utils.js +28 -0
  11. package/dist/errors.d.ts +125 -0
  12. package/dist/errors.js +91 -0
  13. package/dist/generated/inco/shield/v2/conductor_connect.d.ts +137 -0
  14. package/dist/generated/inco/shield/v2/conductor_connect.js +141 -0
  15. package/dist/generated/inco/shield/v2/deposit_pb.d.ts +103 -0
  16. package/dist/generated/inco/shield/v2/deposit_pb.js +141 -0
  17. package/dist/generated/inco/shield/v2/drafting_pb.d.ts +279 -0
  18. package/dist/generated/inco/shield/v2/drafting_pb.js +372 -0
  19. package/dist/generated/inco/shield/v2/permission_pb.d.ts +443 -0
  20. package/dist/generated/inco/shield/v2/permission_pb.js +639 -0
  21. package/dist/generated/inco/shield/v2/query_pb.d.ts +103 -0
  22. package/dist/generated/inco/shield/v2/query_pb.js +141 -0
  23. package/dist/generated/inco/shield/v2/types_pb.d.ts +166 -0
  24. package/dist/generated/inco/shield/v2/types_pb.js +261 -0
  25. package/dist/index.d.ts +12 -0
  26. package/dist/index.js +19 -0
  27. package/dist/shield/client.d.ts +72 -0
  28. package/dist/shield/client.js +136 -0
  29. package/dist/shield/convert.d.ts +18 -0
  30. package/dist/shield/convert.js +152 -0
  31. package/dist/shield/encryption.d.ts +86 -0
  32. package/dist/shield/encryption.js +180 -0
  33. package/dist/shield/envelope.d.ts +14 -0
  34. package/dist/shield/envelope.js +29 -0
  35. package/dist/shield/index.d.ts +13 -0
  36. package/dist/shield/index.js +11 -0
  37. package/dist/shield/intent.d.ts +977 -0
  38. package/dist/shield/intent.js +188 -0
  39. package/dist/shield/rpc.d.ts +72 -0
  40. package/dist/shield/rpc.js +423 -0
  41. package/dist/shield/shield.eip712.gen.d.ts +396 -0
  42. package/dist/shield/shield.eip712.gen.js +318 -0
  43. package/dist/shield/types.d.ts +119 -0
  44. package/dist/shield/types.js +4 -0
  45. package/dist/shield/userop/gas-estimation.d.ts +36 -0
  46. package/dist/shield/userop/gas-estimation.js +284 -0
  47. package/dist/shield/userop/index.d.ts +1 -0
  48. package/dist/shield/userop/index.js +1 -0
  49. package/dist/shield/userop/token-exchange.d.ts +54 -0
  50. package/dist/shield/userop/token-exchange.js +165 -0
  51. package/dist/shield/userop/types.d.ts +91 -0
  52. package/dist/shield/userop/types.js +4 -0
  53. package/dist/shield/userop/userOp.d.ts +26 -0
  54. package/dist/shield/userop/userOp.js +98 -0
  55. package/dist/shield/utils/chain.d.ts +2 -0
  56. package/dist/shield/utils/chain.js +8 -0
  57. package/dist/uniswap-adapter/config.d.ts +34 -0
  58. package/dist/uniswap-adapter/config.js +45 -0
  59. package/dist/uniswap-adapter/find-pools.d.ts +91 -0
  60. package/dist/uniswap-adapter/find-pools.js +108 -0
  61. package/dist/uniswap-adapter/index.d.ts +10 -0
  62. package/dist/uniswap-adapter/index.js +13 -0
  63. package/dist/uniswap-adapter/swap.d.ts +344 -0
  64. package/dist/uniswap-adapter/swap.js +309 -0
  65. package/package.json +72 -0
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * IncoShield SDK
3
+ *
4
+ * TypeScript SDK for interacting with IncoShield contracts.
5
+ * Works in both browser and Node.js environments.
6
+ */
7
+ // Binary helpers
8
+ export { bytesToHex, hexToBytes } from './binary.js';
9
+ // On-chain contract interactions
10
+ export * as contracts from './contracts/index.js';
11
+ export { computeErc20AssetId } from './contracts/utils.js';
12
+ // Tagged error types
13
+ export * from './errors.js';
14
+ // Shield covalidator client
15
+ export * from './shield/index.js';
16
+ // Generated EIP-712 ABI structs and digest helpers
17
+ export * as abi from './shield/shield.eip712.gen.js';
18
+ // The UniswapV4 adapter is exposed via the `@inco/shield-js/uniswap` subpath so
19
+ // its ethers/@uniswap peer dependencies stay optional for core consumers.
@@ -0,0 +1,72 @@
1
+ import { type Hex, type TransactionReceipt } from 'viem';
2
+ import { type BundlerClient, type UserOperation } from 'viem/account-abstraction';
3
+ import type { PermissionAttestation, PermissionAttestationRequest, TransferIntentOp, WithdrawalIntentOp } from './shield.eip712.gen.js';
4
+ import type { Balance, DepositAttestation, GetBalanceParams, GetPermissionDetailsParams, GetPermissionsByOwnerParams, GetPermissionsBySpenderParams, PermissionDetails, PermissionInfo, RequestDepositAttestationParams, ShieldConfig } from './types.js';
5
+ import type { ENTRYPOINT_VERSION } from './userop/types.js';
6
+ export declare function computeDomainSeparator(chainId: bigint, verifyingContract: Hex): Hex;
7
+ /** Client interface for interacting with the shield conductor. */
8
+ export interface ShieldClient {
9
+ issueTransfer(intentOp: TransferIntentOp): Promise<{
10
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
11
+ }>;
12
+ issueWithdraw(intentOp: WithdrawalIntentOp): Promise<{
13
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
14
+ }>;
15
+ /**
16
+ * Owner-side issuance: sign a `PermissionIntent` and exchange it for a
17
+ * TEE-signed `PermissionAttestation` the spender presents on every draft.
18
+ */
19
+ requestPermissionAttestation(req: PermissionAttestationRequest): Promise<PermissionAttestation>;
20
+ issueDelegatedTransfer(intentOp: TransferIntentOp, attestation: PermissionAttestation): Promise<{
21
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
22
+ }>;
23
+ /** Settles byte-identically to a direct withdraw on-chain. */
24
+ issueDelegatedWithdraw(intentOp: WithdrawalIntentOp, attestation: PermissionAttestation): Promise<{
25
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
26
+ }>;
27
+ getBalance(params: GetBalanceParams): Promise<Balance>;
28
+ revokeOne(params: {
29
+ permissionId: Hex;
30
+ }): Promise<{
31
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
32
+ }>;
33
+ revokeAll(): Promise<{
34
+ userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
35
+ }>;
36
+ requestDepositAttestation(params: RequestDepositAttestationParams): Promise<DepositAttestation>;
37
+ getPermissionsByOwner(params: GetPermissionsByOwnerParams): Promise<PermissionInfo[]>;
38
+ getPermissionsBySpender(params: GetPermissionsBySpenderParams): Promise<PermissionInfo[]>;
39
+ getPermissionDetails(params: GetPermissionDetailsParams): Promise<PermissionDetails>;
40
+ getOwnerGeneration(): Promise<bigint>;
41
+ /** Submit a UserOp through a bundler */
42
+ submitUserOp(userOp: UserOperation<typeof ENTRYPOINT_VERSION>, params: SubmitUserOpParams): Promise<{
43
+ receipt: TransactionReceipt;
44
+ }>;
45
+ }
46
+ /**
47
+ * Create a client for the shield covalidator.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { createShieldClient } from "@inco/shield-js";
52
+ * import { createConnectTransport } from "@connectrpc/connect-web";
53
+ * import { privateKeyToAccount } from "viem/accounts";
54
+ *
55
+ * const transport = createConnectTransport({ baseUrl: "http://localhost:50051" });
56
+ * const signer = privateKeyToAccount("0x...");
57
+ *
58
+ * const client = createShieldClient({
59
+ * transport,
60
+ * chainId: BigInt("<your-chain-id>"),
61
+ * shieldAddress: "0x...",
62
+ * signer,
63
+ * });
64
+ *
65
+ * const balance = await client.getBalance({ address: signer.address, assetId: "0x..." });
66
+ * ```
67
+ */
68
+ export declare function createShieldClient(config: ShieldConfig): Promise<ShieldClient>;
69
+ type SubmitUserOpParams = {
70
+ bundlerClient: BundlerClient;
71
+ };
72
+ export {};
@@ -0,0 +1,136 @@
1
+ import { createClient } from '@connectrpc/connect';
2
+ import { createPublicClient, encodeAbiParameters, http, keccak256, toBytes, } from 'viem';
3
+ import { createBundlerClient, } from 'viem/account-abstraction';
4
+ import { MissingWalletAccount, ShieldError } from '../errors.js';
5
+ import { ConductorService } from '../generated/inco/shield/v2/conductor_connect.js';
6
+ import { deserializePublicKey } from './encryption.js';
7
+ import { getBalance, getOwnerGeneration, getPermissionDetails, getPermissionsByOwner, getPermissionsBySpender, issueDelegatedTransfer, issueDelegatedWithdraw, issueTransfer, issueWithdraw, requestDepositAttestation, requestPermissionAttestation, revokeAll, revokeOne, } from './rpc.js';
8
+ import { getChainFromId } from './utils/chain.js';
9
+ /**
10
+ * Default bundler URL.
11
+ * From: https://docs.pimlico.io/references/bundler/public-endpoint#public-endpoint
12
+ */
13
+ function defaultBundlerUrl(chainId) {
14
+ return `https://public.pimlico.io/v2/${chainId}/rpc`;
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // EIP-712 domain utilities
18
+ // ---------------------------------------------------------------------------
19
+ const EIP712_DOMAIN_TYPEHASH = keccak256(toBytes('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'));
20
+ export function computeDomainSeparator(chainId, verifyingContract) {
21
+ return keccak256(encodeAbiParameters([
22
+ { type: 'bytes32' },
23
+ { type: 'bytes32' },
24
+ { type: 'bytes32' },
25
+ { type: 'uint256' },
26
+ { type: 'address' },
27
+ ], [
28
+ EIP712_DOMAIN_TYPEHASH,
29
+ keccak256(toBytes('IncoShield')),
30
+ keccak256(toBytes('1')),
31
+ chainId,
32
+ verifyingContract,
33
+ ]));
34
+ }
35
+ function isShieldSigner(signer) {
36
+ return ('address' in signer &&
37
+ typeof signer.address === 'string' &&
38
+ 'sign' in signer &&
39
+ typeof signer.sign === 'function' &&
40
+ !('account' in signer));
41
+ }
42
+ function resolveShieldSigner(signer) {
43
+ if (isShieldSigner(signer))
44
+ return signer;
45
+ const account = signer.account;
46
+ if (!account)
47
+ throw new MissingWalletAccount({
48
+ message: 'WalletClient must have an account',
49
+ });
50
+ return {
51
+ address: account.address,
52
+ sign: async ({ hash }) => {
53
+ if ('sign' in account && typeof account.sign === 'function') {
54
+ return account.sign({ hash });
55
+ }
56
+ return signer.signMessage({
57
+ account,
58
+ message: { raw: hash },
59
+ });
60
+ },
61
+ };
62
+ }
63
+ /**
64
+ * Create a client for the shield covalidator.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { createShieldClient } from "@inco/shield-js";
69
+ * import { createConnectTransport } from "@connectrpc/connect-web";
70
+ * import { privateKeyToAccount } from "viem/accounts";
71
+ *
72
+ * const transport = createConnectTransport({ baseUrl: "http://localhost:50051" });
73
+ * const signer = privateKeyToAccount("0x...");
74
+ *
75
+ * const client = createShieldClient({
76
+ * transport,
77
+ * chainId: BigInt("<your-chain-id>"),
78
+ * shieldAddress: "0x...",
79
+ * signer,
80
+ * });
81
+ *
82
+ * const balance = await client.getBalance({ address: signer.address, assetId: "0x..." });
83
+ * ```
84
+ */
85
+ export async function createShieldClient(config) {
86
+ const chain = getChainFromId(Number(config.chainId));
87
+ const conductorClient = createClient(ConductorService, config.grpcTransport);
88
+ const domainSeparator = computeDomainSeparator(config.chainId, config.shieldAddress);
89
+ let teePublicKey;
90
+ try {
91
+ teePublicKey = await deserializePublicKey(config.teePublicKey);
92
+ }
93
+ catch (err) {
94
+ throw new ShieldError({
95
+ message: `Failed to deserialize TEE public key: ${err instanceof Error ? err.message : String(err)}`,
96
+ });
97
+ }
98
+ const signer = resolveShieldSigner(config.signer);
99
+ const bundlerUrl = config.bundlerUrl ?? defaultBundlerUrl(Number(config.chainId));
100
+ const bundlerClient = createBundlerClient({
101
+ client: createPublicClient({
102
+ chain,
103
+ transport: config.viemTransport ?? http(chain.rpcUrls.default.http[0]),
104
+ }),
105
+ transport: http(bundlerUrl),
106
+ });
107
+ const ctx = { rpc: conductorClient, domainSeparator, signer, teePublicKey };
108
+ return {
109
+ issueTransfer: (intentOp) => issueTransfer(ctx, intentOp),
110
+ issueWithdraw: (intentOp) => issueWithdraw(ctx, intentOp),
111
+ requestPermissionAttestation: (params) => requestPermissionAttestation(ctx, params),
112
+ issueDelegatedTransfer: (intentOp, attestation) => issueDelegatedTransfer(ctx, intentOp, attestation),
113
+ issueDelegatedWithdraw: (intentOp, attestation) => issueDelegatedWithdraw(ctx, intentOp, attestation),
114
+ getBalance: (params) => getBalance(ctx, params),
115
+ revokeOne: (params) => revokeOne(ctx, params),
116
+ revokeAll: () => revokeAll(ctx),
117
+ requestDepositAttestation: (params) => requestDepositAttestation(ctx, params),
118
+ getPermissionsByOwner: (params) => getPermissionsByOwner(ctx, params),
119
+ getPermissionsBySpender: (params) => getPermissionsBySpender(ctx, params),
120
+ getPermissionDetails: (params) => getPermissionDetails(ctx, params),
121
+ getOwnerGeneration: () => getOwnerGeneration(ctx),
122
+ submitUserOp: async (userOp) => {
123
+ return submitUserOp(userOp, {
124
+ bundlerClient,
125
+ });
126
+ },
127
+ };
128
+ }
129
+ async function submitUserOp(userOp, params) {
130
+ const { bundlerClient } = params;
131
+ const userOpHash = await bundlerClient.sendUserOperation(userOp);
132
+ const { receipt } = await bundlerClient.waitForUserOperationReceipt({
133
+ hash: userOpHash,
134
+ });
135
+ return { receipt };
136
+ }
@@ -0,0 +1,18 @@
1
+ import type { UserOperation } from 'viem/account-abstraction';
2
+ import type { UserOp07 as ProtoUserOp07 } from '../generated/inco/shield/v2/drafting_pb.js';
3
+ import { type GetOwnerGenerationResponse, type GetPermissionDetailsResponse, type GetPermissionsResponse, type PermissionInfo as ProtoPermissionInfo } from '../generated/inco/shield/v2/permission_pb.js';
4
+ import type { GetBalanceResponse } from '../generated/inco/shield/v2/query_pb.js';
5
+ import { Uint256 as ProtoUint256 } from '../generated/inco/shield/v2/types_pb.js';
6
+ import type { Balance, PermissionDetails, PermissionInfo } from './types.js';
7
+ import type { ENTRYPOINT_VERSION } from './userop/types.js';
8
+ export declare function bigintToProtoUint256(value: bigint): ProtoUint256;
9
+ export declare function parseBalance(proto: GetBalanceResponse): Balance;
10
+ export declare function parsePermissionInfo(proto: ProtoPermissionInfo): PermissionInfo;
11
+ export declare function parsePermissions(proto: GetPermissionsResponse): PermissionInfo[];
12
+ export declare function parsePermissionDetails(proto: GetPermissionDetailsResponse): PermissionDetails;
13
+ export declare function parseOwnerGeneration(proto: GetOwnerGenerationResponse): bigint;
14
+ /**
15
+ * Transfer a proto UserOp07 to a viem UserOperation<'0.7'>.
16
+ * This function throws if the proto UserOp07 is malformed.
17
+ */
18
+ export declare function parseUserOp07(proto?: ProtoUserOp07): UserOperation<typeof ENTRYPOINT_VERSION>;
@@ -0,0 +1,152 @@
1
+ import { bytesToHex } from '../binary.js';
2
+ import { MalformedProto } from '../errors.js';
3
+ import { PermissionStatus as ProtoPermissionStatus, } from '../generated/inco/shield/v2/permission_pb.js';
4
+ import { Uint256 as ProtoUint256 } from '../generated/inco/shield/v2/types_pb.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Utilities
7
+ // ---------------------------------------------------------------------------
8
+ function requireField(value, name) {
9
+ if (value === undefined || value === null) {
10
+ throw new MalformedProto({
11
+ message: `Missing required proto field: ${name}`,
12
+ field: name,
13
+ });
14
+ }
15
+ return value;
16
+ }
17
+ function requireFixedBytes(bytes, name, expected) {
18
+ if (!bytes)
19
+ throw new Error(`Missing required proto field: ${name}`);
20
+ if (bytes.length !== expected) {
21
+ throw new Error(`Malformed proto field ${name}: expected ${expected} bytes, got ${bytes.length}`);
22
+ }
23
+ return bytes;
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Uint256 codec — 32-byte big-endian unsigned. Wire shape is a `Uint256`
27
+ // message wrapping exactly 32 bytes; matches Solidity's `uint256` ABI
28
+ // encoding.
29
+ // ---------------------------------------------------------------------------
30
+ const UINT256_BYTES = 32;
31
+ const UINT256_MAX = 1n << 256n;
32
+ function bigintToUint256BEBytes(value) {
33
+ if (value < 0n || value >= UINT256_MAX) {
34
+ throw new Error(`Value out of uint256 range: ${value}`);
35
+ }
36
+ const buf = new Uint8Array(UINT256_BYTES);
37
+ let remaining = value;
38
+ for (let i = UINT256_BYTES - 1; i >= 0; i--) {
39
+ buf[i] = Number(remaining & 0xffn);
40
+ remaining >>= 8n;
41
+ }
42
+ return buf;
43
+ }
44
+ function uint256BEBytesToBigint(bytes) {
45
+ let result = 0n;
46
+ for (const byte of bytes) {
47
+ result = (result << 8n) | BigInt(byte);
48
+ }
49
+ return result;
50
+ }
51
+ export function bigintToProtoUint256(value) {
52
+ return new ProtoUint256({
53
+ value: new Uint8Array(bigintToUint256BEBytes(value)),
54
+ });
55
+ }
56
+ function requireUint256(proto, name) {
57
+ const bytes = requireFixedBytes(proto?.value, name, UINT256_BYTES);
58
+ return uint256BEBytesToBigint(bytes);
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Parsers (proto → SDK types)
62
+ // ---------------------------------------------------------------------------
63
+ export function parseBalance(proto) {
64
+ return {
65
+ confirmed: requireUint256(proto.confirmedBalance, 'GetBalanceResponse.confirmed_balance'),
66
+ pending: requireUint256(proto.pendingBalance, 'GetBalanceResponse.pending_balance'),
67
+ available: requireUint256(proto.availableBalance, 'GetBalanceResponse.available_balance'),
68
+ };
69
+ }
70
+ function parseProtoPermissionStatus(proto) {
71
+ switch (proto) {
72
+ case ProtoPermissionStatus.ACTIVE:
73
+ return 'active';
74
+ case ProtoPermissionStatus.NOT_YET_ACTIVE:
75
+ return 'not_yet_active';
76
+ case ProtoPermissionStatus.EXPIRED:
77
+ return 'expired';
78
+ case ProtoPermissionStatus.REVOKED:
79
+ return 'revoked';
80
+ case ProtoPermissionStatus.GENERATION_REVOKED:
81
+ return 'generation_revoked';
82
+ default:
83
+ return 'unspecified';
84
+ }
85
+ }
86
+ export function parsePermissionInfo(proto) {
87
+ return {
88
+ permissionId: bytesToHex(requireFixedBytes(proto.permissionId?.value, 'permission.permission_id', 32)),
89
+ owner: bytesToHex(requireFixedBytes(proto.owner?.value, 'permission.owner', 20)),
90
+ spender: bytesToHex(requireFixedBytes(proto.spender?.value, 'permission.spender', 20)),
91
+ assetId: bytesToHex(requireFixedBytes(proto.assetId?.value, 'permission.asset_id', 32)),
92
+ allowance: requireUint256(proto.allowance, 'permission.allowance'),
93
+ start: BigInt(proto.start),
94
+ end: BigInt(proto.end),
95
+ period: BigInt(proto.period),
96
+ agreementHash: bytesToHex(requireFixedBytes(proto.agreementHash?.value, 'permission.agreement_hash', 32)),
97
+ spentInCurrentPeriod: requireUint256(proto.spentInCurrentPeriod, 'permission.spent_in_current_period'),
98
+ availableAllowance: requireUint256(proto.availableAllowance, 'permission.available_allowance'),
99
+ status: parseProtoPermissionStatus(proto.status),
100
+ };
101
+ }
102
+ export function parsePermissions(proto) {
103
+ return proto.permissions.map(parsePermissionInfo);
104
+ }
105
+ export function parsePermissionDetails(proto) {
106
+ return {
107
+ permission: parsePermissionInfo(requireField(proto.permission, 'GetPermissionDetailsResponse.permission')),
108
+ found: proto.found,
109
+ };
110
+ }
111
+ export function parseOwnerGeneration(proto) {
112
+ return BigInt(proto.generation);
113
+ }
114
+ /**
115
+ * Transfer a proto UserOp07 to a viem UserOperation<'0.7'>.
116
+ * This function throws if the proto UserOp07 is malformed.
117
+ */
118
+ export function parseUserOp07(proto) {
119
+ if (!proto) {
120
+ throw new MalformedProto({
121
+ message: 'Missing required proto field: user_op',
122
+ field: 'user_op',
123
+ });
124
+ }
125
+ return {
126
+ sender: bytesToHex(requireFixedBytes(proto.sender?.value, 'user_op.sender', 20)),
127
+ nonce: requireUint256(proto.nonce, 'user_op.nonce'),
128
+ callData: bytesToHex(proto.callData),
129
+ callGasLimit: requireUint256(proto.callGasLimit, 'user_op.call_gas_limit'),
130
+ verificationGasLimit: requireUint256(proto.verificationGasLimit, 'user_op.verification_gas_limit'),
131
+ preVerificationGas: requireUint256(proto.preVerificationGas, 'user_op.pre_verification_gas'),
132
+ maxFeePerGas: requireUint256(proto.maxFeePerGas, 'user_op.max_fee_per_gas'),
133
+ maxPriorityFeePerGas: requireUint256(proto.maxPriorityFeePerGas, 'user_op.max_priority_fee_per_gas'),
134
+ signature: bytesToHex(proto.signature),
135
+ factory: proto.factory
136
+ ? bytesToHex(requireFixedBytes(proto.factory.value, 'user_op.factory', 20))
137
+ : undefined,
138
+ factoryData: proto.factoryData ? bytesToHex(proto.factoryData) : undefined,
139
+ paymaster: proto.paymaster
140
+ ? bytesToHex(requireFixedBytes(proto.paymaster.value, 'user_op.paymaster', 20))
141
+ : undefined,
142
+ paymasterData: proto.paymasterData
143
+ ? bytesToHex(proto.paymasterData)
144
+ : undefined,
145
+ paymasterVerificationGasLimit: proto.paymasterVerificationGasLimit
146
+ ? requireUint256(proto.paymasterVerificationGasLimit, 'user_op.paymaster_verification_gas_limit')
147
+ : undefined,
148
+ paymasterPostOpGasLimit: proto.paymasterPostOpGasLimit
149
+ ? requireUint256(proto.paymasterPostOpGasLimit, 'user_op.paymaster_post_op_gas_limit')
150
+ : undefined,
151
+ };
152
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Encryption primitives for the Shield SDK:
3
+ * - HPKE envelope sealing/opening (X-Wing KEM + ChaCha20Poly1305) for messages to the TEE.
4
+ * - Standalone symmetric AEAD (AES-GCM-SIV) seal/open for keys negotiated
5
+ * out-of-band (e.g. `response_key` from authenticated queries) or for
6
+ * deterministic, quorum-shared encryption derived from a trace hash.
7
+ *
8
+ * HPKE wire format: [encappedKey || ciphertext (ChaCha20Poly1305)]
9
+ */
10
+ /** AEAD key size in bytes, sourced from the cipher suite. */
11
+ export declare const AEAD_KEY_SIZE: number;
12
+ /** AEAD nonce size in bytes, sourced from the cipher suite. */
13
+ export declare const AEAD_NONCE_SIZE: number;
14
+ /** AEAD authentication tag size in bytes (appended to ciphertext). */
15
+ export declare const AEAD_TAG_SIZE: number;
16
+ /**
17
+ * Wire size of an `encryptCheque` blob: `[nonce || aead(plaintext)]`,
18
+ * where `aead` appends an authentication tag.
19
+ */
20
+ export declare function chequeBlobSize(plaintextByteLength: number): number;
21
+ /** Raw byte representation of a keypair for persistence or transport. */
22
+ export interface SerializedKeyPair {
23
+ publicKey: Uint8Array;
24
+ privateKey: Uint8Array;
25
+ }
26
+ /** Generate a fresh X-Wing keypair. */
27
+ export declare function generateKeyPair(): Promise<CryptoKeyPair>;
28
+ /** X-Wing IKM size in bytes — required input length for {@link deriveKeyPair}. */
29
+ export declare const HPKE_IKM_SIZE = 32;
30
+ /**
31
+ * Derive an X-Wing keypair deterministically from a 32-byte IKM. Used by
32
+ * the enclave to derive its long-lived HPKE keypair from
33
+ * `sharedEnclaveSecretSeed` so all quorum members agree on the recipient
34
+ * public key.
35
+ *
36
+ * Throws {@link HpkeKeyPairDerivationFailure} on wrong-length IKM or
37
+ * underlying KEM failure. IKM length is validated up front (matching the
38
+ * length guards in {@link sealAead} / {@link openAead}) so the diagnostic
39
+ * doesn't depend on the upstream library's exception shape.
40
+ */
41
+ export declare function deriveKeyPair(ikm: Uint8Array): Promise<CryptoKeyPair>;
42
+ /** Serialize a {@link CryptoKeyPair} to raw bytes. */
43
+ export declare function serializeKeyPair(kp: CryptoKeyPair): Promise<SerializedKeyPair>;
44
+ /** Deserialize raw bytes back into a {@link CryptoKeyPair}. */
45
+ export declare function deserializeKeyPair(serialized: SerializedKeyPair): Promise<CryptoKeyPair>;
46
+ /** Serialize a public key to raw bytes. */
47
+ export declare function serializePublicKey(key: CryptoKey): Promise<Uint8Array>;
48
+ /** Deserialize a public key from raw bytes. */
49
+ export declare function deserializePublicKey(bytes: Uint8Array): Promise<CryptoKey>;
50
+ /**
51
+ * Encrypt plaintext to the recipient's public key using HPKE.
52
+ *
53
+ * @returns A single buffer in wire format: `[encappedKey || ciphertext]`.
54
+ */
55
+ export declare function sealEnvelope(recipientPublicKey: CryptoKey, plaintext: Uint8Array): Promise<Uint8Array>;
56
+ /**
57
+ * Decrypt an envelope using the recipient's private key.
58
+ *
59
+ * @param envelope - Wire format `[encappedKey || ciphertext]` as returned by {@link sealEnvelope}.
60
+ * @throws If the envelope is too short or decryption fails.
61
+ */
62
+ export declare function openEnvelope(recipientPrivateKey: CryptoKey, envelope: Uint8Array): Promise<Uint8Array>;
63
+ /**
64
+ * Encrypt plaintext with a 32-byte symmetric key using AES-GCM-SIV.
65
+ *
66
+ * The nonce is caller-provided and required. Callers that need
67
+ * deterministic ciphertext (e.g. cheque-internal encryption that must match
68
+ * across TEEs in a quorum) derive the nonce deterministically from the
69
+ * relevant context (trace hash + slot label). Callers that don't (e.g. the
70
+ * TEE→client response path) generate a fresh random nonce.
71
+ *
72
+ * AES-GCM-SIV is misuse-resistant: nonce reuse with the same plaintext
73
+ * leaks plaintext equality but does not catastrophically break the cipher.
74
+ *
75
+ * @param key - 32-byte symmetric key.
76
+ * @param nonce - 12-byte nonce.
77
+ * @param plaintext - Bytes to encrypt.
78
+ * @returns ciphertext (with appended GCM authentication tag).
79
+ */
80
+ export declare function sealAead(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>;
81
+ /**
82
+ * Decrypt ciphertext with a 32-byte symmetric key using AES-GCM-SIV.
83
+ *
84
+ * @throws If authentication fails or key/nonce sizes are wrong.
85
+ */
86
+ export declare function openAead(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Encryption primitives for the Shield SDK:
3
+ * - HPKE envelope sealing/opening (X-Wing KEM + ChaCha20Poly1305) for messages to the TEE.
4
+ * - Standalone symmetric AEAD (AES-GCM-SIV) seal/open for keys negotiated
5
+ * out-of-band (e.g. `response_key` from authenticated queries) or for
6
+ * deterministic, quorum-shared encryption derived from a trace hash.
7
+ *
8
+ * HPKE wire format: [encappedKey || ciphertext (ChaCha20Poly1305)]
9
+ */
10
+ import { Chacha20Poly1305 } from '@hpke/chacha20poly1305';
11
+ import { CipherSuite, HkdfSha256 } from '@hpke/core';
12
+ import { XWing } from '@hpke/hybridkem-x-wing';
13
+ import { gcmsiv } from '@noble/ciphers/aes';
14
+ import { EncryptionError, HpkeKeyPairDerivationFailure } from '../errors.js';
15
+ const kem = new XWing();
16
+ const chachaPoly = new Chacha20Poly1305();
17
+ const suite = new CipherSuite({
18
+ kem,
19
+ kdf: new HkdfSha256(),
20
+ aead: chachaPoly,
21
+ });
22
+ /** X-Wing KEM encapsulated key size in bytes. */
23
+ const ENCAPPED_KEY_SIZE = kem.encSize;
24
+ /** AEAD key size in bytes, sourced from the cipher suite. */
25
+ export const AEAD_KEY_SIZE = chachaPoly.keySize;
26
+ /** AEAD nonce size in bytes, sourced from the cipher suite. */
27
+ export const AEAD_NONCE_SIZE = chachaPoly.nonceSize;
28
+ /** AEAD authentication tag size in bytes (appended to ciphertext). */
29
+ export const AEAD_TAG_SIZE = chachaPoly.tagSize;
30
+ /**
31
+ * Wire size of an `encryptCheque` blob: `[nonce || aead(plaintext)]`,
32
+ * where `aead` appends an authentication tag.
33
+ */
34
+ export function chequeBlobSize(plaintextByteLength) {
35
+ return AEAD_NONCE_SIZE + plaintextByteLength + AEAD_TAG_SIZE;
36
+ }
37
+ /** Generate a fresh X-Wing keypair. */
38
+ export async function generateKeyPair() {
39
+ return suite.kem.generateKeyPair();
40
+ }
41
+ /** X-Wing IKM size in bytes — required input length for {@link deriveKeyPair}. */
42
+ export const HPKE_IKM_SIZE = 32;
43
+ /**
44
+ * Derive an X-Wing keypair deterministically from a 32-byte IKM. Used by
45
+ * the enclave to derive its long-lived HPKE keypair from
46
+ * `sharedEnclaveSecretSeed` so all quorum members agree on the recipient
47
+ * public key.
48
+ *
49
+ * Throws {@link HpkeKeyPairDerivationFailure} on wrong-length IKM or
50
+ * underlying KEM failure. IKM length is validated up front (matching the
51
+ * length guards in {@link sealAead} / {@link openAead}) so the diagnostic
52
+ * doesn't depend on the upstream library's exception shape.
53
+ */
54
+ export async function deriveKeyPair(ikm) {
55
+ if (ikm.byteLength !== HPKE_IKM_SIZE) {
56
+ throw new HpkeKeyPairDerivationFailure({
57
+ message: `deriveKeyPair: ikm must be ${HPKE_IKM_SIZE} bytes, got ${ikm.byteLength}`,
58
+ });
59
+ }
60
+ try {
61
+ return await suite.kem.deriveKeyPair(ikm);
62
+ }
63
+ catch (err) {
64
+ throw new HpkeKeyPairDerivationFailure({
65
+ message: `X-Wing keypair derivation failed: ${err instanceof Error ? err.message : String(err)}`,
66
+ });
67
+ }
68
+ }
69
+ /** Serialize a {@link CryptoKeyPair} to raw bytes. */
70
+ export async function serializeKeyPair(kp) {
71
+ const [pub, priv] = await Promise.all([
72
+ suite.kem.serializePublicKey(kp.publicKey),
73
+ suite.kem.serializePrivateKey(kp.privateKey),
74
+ ]);
75
+ return {
76
+ publicKey: new Uint8Array(pub),
77
+ privateKey: new Uint8Array(priv),
78
+ };
79
+ }
80
+ /** Deserialize raw bytes back into a {@link CryptoKeyPair}. */
81
+ export async function deserializeKeyPair(serialized) {
82
+ const [publicKey, privateKey] = await Promise.all([
83
+ suite.kem.deserializePublicKey(serialized.publicKey),
84
+ suite.kem.deserializePrivateKey(serialized.privateKey),
85
+ ]);
86
+ return { publicKey, privateKey };
87
+ }
88
+ /** Serialize a public key to raw bytes. */
89
+ export async function serializePublicKey(key) {
90
+ return new Uint8Array(await suite.kem.serializePublicKey(key));
91
+ }
92
+ /** Deserialize a public key from raw bytes. */
93
+ export async function deserializePublicKey(bytes) {
94
+ return suite.kem.deserializePublicKey(bytes);
95
+ }
96
+ /**
97
+ * Encrypt plaintext to the recipient's public key using HPKE.
98
+ *
99
+ * @returns A single buffer in wire format: `[encappedKey || ciphertext]`.
100
+ */
101
+ export async function sealEnvelope(recipientPublicKey, plaintext) {
102
+ const sender = await suite.createSenderContext({ recipientPublicKey });
103
+ const ciphertext = await sender.seal(plaintext);
104
+ const ct = new Uint8Array(ciphertext);
105
+ const result = new Uint8Array(sender.enc.byteLength + ct.byteLength);
106
+ result.set(new Uint8Array(sender.enc), 0);
107
+ result.set(ct, sender.enc.byteLength);
108
+ return result;
109
+ }
110
+ /**
111
+ * Decrypt an envelope using the recipient's private key.
112
+ *
113
+ * @param envelope - Wire format `[encappedKey || ciphertext]` as returned by {@link sealEnvelope}.
114
+ * @throws If the envelope is too short or decryption fails.
115
+ */
116
+ export async function openEnvelope(recipientPrivateKey, envelope) {
117
+ if (envelope.byteLength <= ENCAPPED_KEY_SIZE) {
118
+ throw new EncryptionError({
119
+ message: `Envelope too short: expected > ${ENCAPPED_KEY_SIZE} bytes, got ${envelope.byteLength}`,
120
+ });
121
+ }
122
+ const encappedKey = envelope.slice(0, ENCAPPED_KEY_SIZE);
123
+ const ciphertext = envelope.slice(ENCAPPED_KEY_SIZE);
124
+ const recipient = await suite.createRecipientContext({
125
+ recipientKey: recipientPrivateKey,
126
+ enc: encappedKey,
127
+ });
128
+ const plaintext = await recipient.open(ciphertext);
129
+ return new Uint8Array(plaintext);
130
+ }
131
+ /**
132
+ * Encrypt plaintext with a 32-byte symmetric key using AES-GCM-SIV.
133
+ *
134
+ * The nonce is caller-provided and required. Callers that need
135
+ * deterministic ciphertext (e.g. cheque-internal encryption that must match
136
+ * across TEEs in a quorum) derive the nonce deterministically from the
137
+ * relevant context (trace hash + slot label). Callers that don't (e.g. the
138
+ * TEE→client response path) generate a fresh random nonce.
139
+ *
140
+ * AES-GCM-SIV is misuse-resistant: nonce reuse with the same plaintext
141
+ * leaks plaintext equality but does not catastrophically break the cipher.
142
+ *
143
+ * @param key - 32-byte symmetric key.
144
+ * @param nonce - 12-byte nonce.
145
+ * @param plaintext - Bytes to encrypt.
146
+ * @returns ciphertext (with appended GCM authentication tag).
147
+ */
148
+ export async function sealAead(key, nonce, plaintext) {
149
+ if (key.byteLength !== AEAD_KEY_SIZE) {
150
+ throw new EncryptionError({
151
+ message: `sealAead: key must be ${AEAD_KEY_SIZE} bytes, got ${key.byteLength}`,
152
+ });
153
+ }
154
+ if (nonce.byteLength !== AEAD_NONCE_SIZE) {
155
+ throw new EncryptionError({
156
+ message: `sealAead: nonce must be ${AEAD_NONCE_SIZE} bytes, got ${nonce.byteLength}`,
157
+ });
158
+ }
159
+ const cipher = gcmsiv(key, nonce);
160
+ return cipher.encrypt(plaintext);
161
+ }
162
+ /**
163
+ * Decrypt ciphertext with a 32-byte symmetric key using AES-GCM-SIV.
164
+ *
165
+ * @throws If authentication fails or key/nonce sizes are wrong.
166
+ */
167
+ export async function openAead(key, nonce, ciphertext) {
168
+ if (key.byteLength !== AEAD_KEY_SIZE) {
169
+ throw new EncryptionError({
170
+ message: `openAead: key must be ${AEAD_KEY_SIZE} bytes, got ${key.byteLength}`,
171
+ });
172
+ }
173
+ if (nonce.byteLength !== AEAD_NONCE_SIZE) {
174
+ throw new EncryptionError({
175
+ message: `openAead: nonce must be ${AEAD_NONCE_SIZE} bytes, got ${nonce.byteLength}`,
176
+ });
177
+ }
178
+ const cipher = gcmsiv(key, nonce);
179
+ return cipher.decrypt(ciphertext);
180
+ }