@inco/shield-js 0.1.0 → 0.2.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 (74) hide show
  1. package/README.md +52 -308
  2. package/dist/contracts/abi.d.ts +2000 -4513
  3. package/dist/contracts/abi.js +2400 -5651
  4. package/dist/contracts/index.d.ts +2 -2
  5. package/dist/contracts/index.js +2 -2
  6. package/dist/contracts/utils.d.ts +5 -24
  7. package/dist/contracts/utils.js +13 -24
  8. package/dist/errors.d.ts +16 -47
  9. package/dist/errors.js +6 -53
  10. package/dist/generated/inco/shield/v2/conductor_pb.d.ts +77 -0
  11. package/dist/generated/inco/shield/v2/conductor_pb.js +6 -0
  12. package/dist/generated/inco/shield/v2/deposit_pb.d.ts +12 -103
  13. package/dist/generated/inco/shield/v2/deposit_pb.js +4 -141
  14. package/dist/generated/inco/shield/v2/drafting_pb.d.ts +45 -269
  15. package/dist/generated/inco/shield/v2/drafting_pb.js +8 -361
  16. package/dist/generated/inco/shield/v2/types_pb.d.ts +47 -166
  17. package/dist/generated/inco/shield/v2/types_pb.js +12 -261
  18. package/dist/index.d.ts +1 -7
  19. package/dist/index.js +1 -14
  20. package/dist/shield/abi.d.ts +5 -0
  21. package/dist/shield/abi.js +28 -0
  22. package/dist/shield/client.d.ts +27 -51
  23. package/dist/shield/client.js +73 -65
  24. package/dist/shield/convert.d.ts +2 -14
  25. package/dist/shield/convert.js +39 -115
  26. package/dist/shield/encryption.d.ts +0 -67
  27. package/dist/shield/encryption.js +0 -67
  28. package/dist/shield/envelope.d.ts +10 -11
  29. package/dist/shield/envelope.js +15 -14
  30. package/dist/shield/fee.d.ts +24 -0
  31. package/dist/shield/fee.js +151 -0
  32. package/dist/shield/index.d.ts +9 -3
  33. package/dist/shield/index.js +7 -2
  34. package/dist/shield/intent.d.ts +20 -933
  35. package/dist/shield/intent.js +92 -112
  36. package/dist/shield/permission-status.d.ts +9 -0
  37. package/dist/shield/permission-status.js +8 -0
  38. package/dist/shield/rpc.d.ts +50 -49
  39. package/dist/shield/rpc.js +207 -354
  40. package/dist/shield/shield.eip712.gen.d.ts +1096 -43
  41. package/dist/shield/shield.eip712.gen.js +572 -62
  42. package/dist/shield/types.d.ts +7 -102
  43. package/dist/shield/types.js +1 -4
  44. package/dist/shield/userop/gas-estimation.d.ts +7 -22
  45. package/dist/shield/userop/gas-estimation.js +100 -137
  46. package/dist/shield/userop/paymaster.d.ts +15 -0
  47. package/dist/shield/userop/paymaster.js +17 -0
  48. package/dist/shield/userop/token-exchange.d.ts +5 -52
  49. package/dist/shield/userop/token-exchange.js +41 -149
  50. package/dist/shield/userop/types.d.ts +2 -27
  51. package/dist/shield/userop/types.js +0 -3
  52. package/dist/shield/userop/userOp.d.ts +0 -19
  53. package/dist/shield/userop/userOp.js +4 -22
  54. package/dist/shield/utils/chain.d.ts +1 -0
  55. package/dist/shield/utils/chain.js +3 -0
  56. package/dist/shield/validate-withdrawal-split.d.ts +13 -0
  57. package/dist/shield/validate-withdrawal-split.js +93 -0
  58. package/package.json +18 -27
  59. package/dist/generated/inco/shield/v2/conductor_connect.d.ts +0 -137
  60. package/dist/generated/inco/shield/v2/conductor_connect.js +0 -141
  61. package/dist/generated/inco/shield/v2/permission_pb.d.ts +0 -443
  62. package/dist/generated/inco/shield/v2/permission_pb.js +0 -639
  63. package/dist/generated/inco/shield/v2/query_pb.d.ts +0 -103
  64. package/dist/generated/inco/shield/v2/query_pb.js +0 -141
  65. package/dist/shield/userop/index.d.ts +0 -1
  66. package/dist/shield/userop/index.js +0 -1
  67. package/dist/uniswap-adapter/config.d.ts +0 -34
  68. package/dist/uniswap-adapter/config.js +0 -45
  69. package/dist/uniswap-adapter/find-pools.d.ts +0 -91
  70. package/dist/uniswap-adapter/find-pools.js +0 -108
  71. package/dist/uniswap-adapter/index.d.ts +0 -10
  72. package/dist/uniswap-adapter/index.js +0 -13
  73. package/dist/uniswap-adapter/swap.d.ts +0 -344
  74. package/dist/uniswap-adapter/swap.js +0 -309
@@ -1,18 +1,6 @@
1
1
  import type { UserOperation } from 'viem/account-abstraction';
2
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';
3
+ import { type Uint256 as ProtoUint256 } from '../generated/inco/shield/v2/types_pb.js';
7
4
  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
5
  export declare function parseUserOp07(proto?: ProtoUserOp07): UserOperation<typeof ENTRYPOINT_VERSION>;
6
+ export declare function bigintToProtoUint256(value: bigint): ProtoUint256;
@@ -1,120 +1,7 @@
1
+ import { create } from '@bufbuild/protobuf';
1
2
  import { bytesToHex } from '../binary.js';
2
3
  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
- */
4
+ import { Uint256Schema as ProtoUint256Desc, } from '../generated/inco/shield/v2/types_pb.js';
118
5
  export function parseUserOp07(proto) {
119
6
  if (!proto) {
120
7
  throw new MalformedProto({
@@ -150,3 +37,40 @@ export function parseUserOp07(proto) {
150
37
  : undefined,
151
38
  };
152
39
  }
40
+ const UINT256_BYTES = 32;
41
+ const UINT256_MAX = 1n << 256n;
42
+ export function bigintToProtoUint256(value) {
43
+ return create(ProtoUint256Desc, {
44
+ value: new Uint8Array(bigintToUint256BEBytes(value)),
45
+ });
46
+ }
47
+ function bigintToUint256BEBytes(value) {
48
+ if (value < 0n || value >= UINT256_MAX) {
49
+ throw new Error(`Value out of uint256 range: ${value}`);
50
+ }
51
+ const buf = new Uint8Array(UINT256_BYTES);
52
+ let remaining = value;
53
+ for (let i = UINT256_BYTES - 1; i >= 0; i--) {
54
+ buf[i] = Number(remaining & 0xffn);
55
+ remaining >>= 8n;
56
+ }
57
+ return buf;
58
+ }
59
+ function uint256BEBytesToBigint(bytes) {
60
+ let result = 0n;
61
+ for (const byte of bytes) {
62
+ result = (result << 8n) | BigInt(byte);
63
+ }
64
+ return result;
65
+ }
66
+ function requireUint256(proto, name) {
67
+ return uint256BEBytesToBigint(requireFixedBytes(proto?.value, name, UINT256_BYTES));
68
+ }
69
+ function requireFixedBytes(bytes, name, expected) {
70
+ if (!bytes)
71
+ throw new Error(`Missing required proto field: ${name}`);
72
+ if (bytes.length !== expected) {
73
+ throw new Error(`Malformed proto field ${name}: expected ${expected} bytes, got ${bytes.length}`);
74
+ }
75
+ return bytes;
76
+ }
@@ -1,86 +1,19 @@
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
1
  export declare const AEAD_KEY_SIZE: number;
12
- /** AEAD nonce size in bytes, sourced from the cipher suite. */
13
2
  export declare const AEAD_NONCE_SIZE: number;
14
- /** AEAD authentication tag size in bytes (appended to ciphertext). */
15
3
  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
4
  export declare function chequeBlobSize(plaintextByteLength: number): number;
21
- /** Raw byte representation of a keypair for persistence or transport. */
22
5
  export interface SerializedKeyPair {
23
6
  publicKey: Uint8Array;
24
7
  privateKey: Uint8Array;
25
8
  }
26
- /** Generate a fresh X-Wing keypair. */
27
9
  export declare function generateKeyPair(): Promise<CryptoKeyPair>;
28
- /** X-Wing IKM size in bytes — required input length for {@link deriveKeyPair}. */
29
10
  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
11
  export declare function deriveKeyPair(ikm: Uint8Array): Promise<CryptoKeyPair>;
42
- /** Serialize a {@link CryptoKeyPair} to raw bytes. */
43
12
  export declare function serializeKeyPair(kp: CryptoKeyPair): Promise<SerializedKeyPair>;
44
- /** Deserialize raw bytes back into a {@link CryptoKeyPair}. */
45
13
  export declare function deserializeKeyPair(serialized: SerializedKeyPair): Promise<CryptoKeyPair>;
46
- /** Serialize a public key to raw bytes. */
47
14
  export declare function serializePublicKey(key: CryptoKey): Promise<Uint8Array>;
48
- /** Deserialize a public key from raw bytes. */
49
15
  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
16
  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
17
  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
18
  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
19
  export declare function openAead(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
@@ -1,12 +1,3 @@
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
1
  import { Chacha20Poly1305 } from '@hpke/chacha20poly1305';
11
2
  import { CipherSuite, HkdfSha256 } from '@hpke/core';
12
3
  import { XWing } from '@hpke/hybridkem-x-wing';
@@ -19,38 +10,17 @@ const suite = new CipherSuite({
19
10
  kdf: new HkdfSha256(),
20
11
  aead: chachaPoly,
21
12
  });
22
- /** X-Wing KEM encapsulated key size in bytes. */
23
13
  const ENCAPPED_KEY_SIZE = kem.encSize;
24
- /** AEAD key size in bytes, sourced from the cipher suite. */
25
14
  export const AEAD_KEY_SIZE = chachaPoly.keySize;
26
- /** AEAD nonce size in bytes, sourced from the cipher suite. */
27
15
  export const AEAD_NONCE_SIZE = chachaPoly.nonceSize;
28
- /** AEAD authentication tag size in bytes (appended to ciphertext). */
29
16
  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
17
  export function chequeBlobSize(plaintextByteLength) {
35
18
  return AEAD_NONCE_SIZE + plaintextByteLength + AEAD_TAG_SIZE;
36
19
  }
37
- /** Generate a fresh X-Wing keypair. */
38
20
  export async function generateKeyPair() {
39
21
  return suite.kem.generateKeyPair();
40
22
  }
41
- /** X-Wing IKM size in bytes — required input length for {@link deriveKeyPair}. */
42
23
  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
24
  export async function deriveKeyPair(ikm) {
55
25
  if (ikm.byteLength !== HPKE_IKM_SIZE) {
56
26
  throw new HpkeKeyPairDerivationFailure({
@@ -66,7 +36,6 @@ export async function deriveKeyPair(ikm) {
66
36
  });
67
37
  }
68
38
  }
69
- /** Serialize a {@link CryptoKeyPair} to raw bytes. */
70
39
  export async function serializeKeyPair(kp) {
71
40
  const [pub, priv] = await Promise.all([
72
41
  suite.kem.serializePublicKey(kp.publicKey),
@@ -77,7 +46,6 @@ export async function serializeKeyPair(kp) {
77
46
  privateKey: new Uint8Array(priv),
78
47
  };
79
48
  }
80
- /** Deserialize raw bytes back into a {@link CryptoKeyPair}. */
81
49
  export async function deserializeKeyPair(serialized) {
82
50
  const [publicKey, privateKey] = await Promise.all([
83
51
  suite.kem.deserializePublicKey(serialized.publicKey),
@@ -85,19 +53,12 @@ export async function deserializeKeyPair(serialized) {
85
53
  ]);
86
54
  return { publicKey, privateKey };
87
55
  }
88
- /** Serialize a public key to raw bytes. */
89
56
  export async function serializePublicKey(key) {
90
57
  return new Uint8Array(await suite.kem.serializePublicKey(key));
91
58
  }
92
- /** Deserialize a public key from raw bytes. */
93
59
  export async function deserializePublicKey(bytes) {
94
60
  return suite.kem.deserializePublicKey(bytes);
95
61
  }
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
62
  export async function sealEnvelope(recipientPublicKey, plaintext) {
102
63
  const sender = await suite.createSenderContext({ recipientPublicKey });
103
64
  const ciphertext = await sender.seal(plaintext);
@@ -107,12 +68,6 @@ export async function sealEnvelope(recipientPublicKey, plaintext) {
107
68
  result.set(ct, sender.enc.byteLength);
108
69
  return result;
109
70
  }
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
71
  export async function openEnvelope(recipientPrivateKey, envelope) {
117
72
  if (envelope.byteLength <= ENCAPPED_KEY_SIZE) {
118
73
  throw new EncryptionError({
@@ -128,23 +83,6 @@ export async function openEnvelope(recipientPrivateKey, envelope) {
128
83
  const plaintext = await recipient.open(ciphertext);
129
84
  return new Uint8Array(plaintext);
130
85
  }
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
86
  export async function sealAead(key, nonce, plaintext) {
149
87
  if (key.byteLength !== AEAD_KEY_SIZE) {
150
88
  throw new EncryptionError({
@@ -159,11 +97,6 @@ export async function sealAead(key, nonce, plaintext) {
159
97
  const cipher = gcmsiv(key, nonce);
160
98
  return cipher.encrypt(plaintext);
161
99
  }
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
100
  export async function openAead(key, nonce, ciphertext) {
168
101
  if (key.byteLength !== AEAD_KEY_SIZE) {
169
102
  throw new EncryptionError({
@@ -1,14 +1,13 @@
1
1
  import { type Hex } from 'viem';
2
- import { EncryptedEnvelope, type EncryptedResponse } from '../generated/inco/shield/v2/types_pb.js';
3
- /** HPKE-encrypt a serialized Authenticated* proto into an EncryptedEnvelope. */
4
- export declare function makeEnvelope(teePublicKey: CryptoKey, inner: {
5
- toBinary(): Uint8Array<ArrayBufferLike>;
6
- }): Promise<EncryptedEnvelope>;
7
- /** Generate a random 32-byte AEAD response key. */
2
+ import { type EncryptedResponse } from '../generated/inco/shield/v2/types_pb.js';
3
+ export type RequestAuthInput = {
4
+ eip712Signature?: Uint8Array;
5
+ responseKey: Uint8Array;
6
+ };
7
+ export declare function makeEnvelope(teePublicKey: CryptoKey, auth: RequestAuthInput, body: Uint8Array): Promise<import("../generated/inco/shield/v2/types_pb.js").EncryptedEnvelope>;
8
8
  export declare function generateResponseKey(): Uint8Array<ArrayBuffer>;
9
- /** Generate a fresh 32-byte replay-protection salt using the platform CSPRNG. */
10
9
  export declare function generateRandomSalt(): Hex;
11
- /** AEAD-decrypt an EncryptedResponse using the response key, then decode as a typed proto. */
12
- export declare function decryptResponse<T>(resp: EncryptedResponse, responseKey: Uint8Array, Type: {
13
- fromBinary(bytes: Uint8Array): T;
14
- }): Promise<T>;
10
+ export declare function openSignedResponse(resp: EncryptedResponse, responseKey: Uint8Array): Promise<{
11
+ body: Uint8Array;
12
+ teeSignature: Uint8Array;
13
+ }>;
@@ -1,29 +1,30 @@
1
+ import { create, fromBinary, toBinary } from '@bufbuild/protobuf';
1
2
  import { toHex } from 'viem';
2
- import { EncryptedEnvelope, } from '../generated/inco/shield/v2/types_pb.js';
3
+ import { EncryptedEnvelopeSchema, RequestAuthSchema, SignedRequestSchema, SignedResponseSchema, } from '../generated/inco/shield/v2/types_pb.js';
3
4
  import { AEAD_KEY_SIZE, openAead, sealEnvelope } from './encryption.js';
4
- // ---------------------------------------------------------------------------
5
- // Envelope helpers
6
- // ---------------------------------------------------------------------------
7
- /** HPKE-encrypt a serialized Authenticated* proto into an EncryptedEnvelope. */
8
- export async function makeEnvelope(teePublicKey, inner) {
9
- const bytes = inner.toBinary();
10
- const sealed = await sealEnvelope(teePublicKey, bytes);
11
- return new EncryptedEnvelope({ payload: new Uint8Array(sealed) });
5
+ export async function makeEnvelope(teePublicKey, auth, body) {
6
+ const signedRequest = create(SignedRequestSchema, {
7
+ auth: create(RequestAuthSchema, {
8
+ eip712Signature: auth.eip712Signature,
9
+ responseKey: auth.responseKey,
10
+ }),
11
+ body,
12
+ });
13
+ const sealed = await sealEnvelope(teePublicKey, toBinary(SignedRequestSchema, signedRequest));
14
+ return create(EncryptedEnvelopeSchema, { payload: new Uint8Array(sealed) });
12
15
  }
13
- /** Generate a random 32-byte AEAD response key. */
14
16
  export function generateResponseKey() {
15
17
  const key = new Uint8Array(AEAD_KEY_SIZE);
16
18
  crypto.getRandomValues(key);
17
19
  return key;
18
20
  }
19
- /** Generate a fresh 32-byte replay-protection salt using the platform CSPRNG. */
20
21
  export function generateRandomSalt() {
21
22
  const bytes = new Uint8Array(32);
22
23
  crypto.getRandomValues(bytes);
23
24
  return toHex(bytes);
24
25
  }
25
- /** AEAD-decrypt an EncryptedResponse using the response key, then decode as a typed proto. */
26
- export async function decryptResponse(resp, responseKey, Type) {
26
+ export async function openSignedResponse(resp, responseKey) {
27
27
  const plaintext = await openAead(responseKey, resp.nonce, resp.ciphertext);
28
- return Type.fromBinary(plaintext);
28
+ const signed = fromBinary(SignedResponseSchema, plaintext);
29
+ return { body: signed.body, teeSignature: signed.teeSignature };
29
30
  }
@@ -0,0 +1,24 @@
1
+ import { type Hex } from 'viem';
2
+ import type { AssetMovement, WithdrawalIntentOp } from './shield.eip712.gen.js';
3
+ export declare const WITHDRAW_FEE_BPS = 25n;
4
+ export declare function protocolFee(amount: bigint): bigint;
5
+ export type FeeContext = {
6
+ readonly adapter: Hex;
7
+ readonly treasury: Hex;
8
+ };
9
+ export type DeliveryQuote = {
10
+ readonly feeBps: bigint;
11
+ readonly fee: bigint;
12
+ readonly debit: bigint;
13
+ };
14
+ export declare function quoteByDelivery(delivered: bigint): DeliveryQuote;
15
+ export type DebitQuote = {
16
+ readonly feeBps: bigint;
17
+ readonly fee: bigint;
18
+ readonly delivered: bigint;
19
+ };
20
+ export declare function quoteByDebit(debit: bigint): DebitQuote;
21
+ export declare function encodeMovementAmount(amount: bigint): Hex;
22
+ export declare function decodeMovementAmount(movementArgs: Hex): bigint;
23
+ export declare function checkProtocolFeeSplit(movements: readonly AssetMovement[], feeContext: FeeContext): string | null;
24
+ export declare function withProtocolFeeLeg(intentOp: WithdrawalIntentOp, feeContext: FeeContext): WithdrawalIntentOp;
@@ -0,0 +1,151 @@
1
+ import { decodeAbiParameters, encodeAbiParameters } from 'viem';
2
+ import { InvalidWithdrawalIntentOp } from '../errors.js';
3
+ export const WITHDRAW_FEE_BPS = 25n;
4
+ const BPS_DENOMINATOR = 10000n;
5
+ export function protocolFee(amount) {
6
+ return (amount * WITHDRAW_FEE_BPS) / BPS_DENOMINATOR;
7
+ }
8
+ export function quoteByDelivery(delivered) {
9
+ const fee = protocolFee(delivered);
10
+ return { feeBps: WITHDRAW_FEE_BPS, fee, debit: delivered + fee };
11
+ }
12
+ export function quoteByDebit(debit) {
13
+ const delivered = (BPS_DENOMINATOR * (debit + 1n) - 1n) /
14
+ (BPS_DENOMINATOR + WITHDRAW_FEE_BPS);
15
+ return { feeBps: WITHDRAW_FEE_BPS, fee: protocolFee(delivered), delivered };
16
+ }
17
+ const MOVEMENT_AMOUNT_ABI = [{ type: 'uint256' }];
18
+ export function encodeMovementAmount(amount) {
19
+ return encodeAbiParameters(MOVEMENT_AMOUNT_ABI, [amount]);
20
+ }
21
+ export function decodeMovementAmount(movementArgs) {
22
+ return decodeAbiParameters(MOVEMENT_AMOUNT_ABI, movementArgs)[0];
23
+ }
24
+ export function checkProtocolFeeSplit(movements, feeContext) {
25
+ const violation = checkOnTopProtocolFeeSplit(movements, feeContext);
26
+ return violation !== null &&
27
+ matchesLegacyProtocolFeeSplit(movements, feeContext)
28
+ ? null
29
+ : violation;
30
+ }
31
+ export function withProtocolFeeLeg(intentOp, feeContext) {
32
+ const recipients = intentOp.core.assetMovements;
33
+ if (recipients.length === 0) {
34
+ throw new InvalidWithdrawalIntentOp({
35
+ message: 'withdrawal intent op must carry at least one recipient movement',
36
+ });
37
+ }
38
+ let recipientTotal = 0n;
39
+ for (const recipient of recipients) {
40
+ if (addressEq(recipient.account, feeContext.treasury) ||
41
+ addressEq(recipient.assetAdapter, feeContext.adapter)) {
42
+ throw new InvalidWithdrawalIntentOp({
43
+ message: 'recipient movement must not target the protocol treasury or route through the fee adapter',
44
+ });
45
+ }
46
+ const amount = tryDecodeAmount(recipient.movementArgs);
47
+ if (amount === null) {
48
+ throw new InvalidWithdrawalIntentOp({
49
+ message: `recipient movementArgs must be abi.encode(uint256): ${recipient.movementArgs}`,
50
+ });
51
+ }
52
+ recipientTotal += amount;
53
+ }
54
+ const fee = protocolFee(recipientTotal);
55
+ if (fee === 0n) {
56
+ return intentOp;
57
+ }
58
+ const feeLeg = {
59
+ account: feeContext.treasury,
60
+ assetAdapter: feeContext.adapter,
61
+ movementArgs: encodeMovementAmount(fee),
62
+ };
63
+ return {
64
+ ...intentOp,
65
+ core: {
66
+ ...intentOp.core,
67
+ assetMovements: [...recipients, feeLeg],
68
+ },
69
+ };
70
+ }
71
+ function checkOnTopProtocolFeeSplit(movements, feeContext) {
72
+ if (movements.length === 0) {
73
+ return 'withdrawal must carry at least one movement';
74
+ }
75
+ const feeLegs = movements.filter((m) => addressEq(m.account, feeContext.treasury));
76
+ const recipients = movements.filter((m) => !addressEq(m.account, feeContext.treasury));
77
+ if (recipients.length === 0) {
78
+ return 'withdrawal must carry at least one recipient movement';
79
+ }
80
+ let recipientTotal = 0n;
81
+ for (const recipient of recipients) {
82
+ if (addressEq(recipient.assetAdapter, feeContext.adapter)) {
83
+ return 'recipient movement must not route through the fee-accrual adapter';
84
+ }
85
+ const amount = tryDecodeAmount(recipient.movementArgs);
86
+ if (amount === null) {
87
+ return `recipient movementArgs must be abi.encode(uint256): ${recipient.movementArgs}`;
88
+ }
89
+ recipientTotal += amount;
90
+ }
91
+ const expected = protocolFee(recipientTotal);
92
+ if (feeLegs.length === 0) {
93
+ return expected === 0n
94
+ ? null
95
+ : 'fee-bearing withdrawal must carry exactly one fee leg, got 0';
96
+ }
97
+ if (feeLegs.length !== 1) {
98
+ return `fee-bearing withdrawal must carry exactly one fee leg, got ${feeLegs.length}`;
99
+ }
100
+ const feeLeg = feeLegs[0];
101
+ if (movements.at(-1) !== feeLeg) {
102
+ return 'protocol fee leg must be the final movement';
103
+ }
104
+ if (!addressEq(feeLeg.assetAdapter, feeContext.adapter)) {
105
+ return `fee leg must route through the canonical fee-accrual adapter ${feeContext.adapter}, got ${feeLeg.assetAdapter}`;
106
+ }
107
+ const feeAmount = tryDecodeAmount(feeLeg.movementArgs);
108
+ if (feeAmount === null) {
109
+ return `fee leg movementArgs must be abi.encode(uint256): ${feeLeg.movementArgs}`;
110
+ }
111
+ return feeAmount === expected && expected > 0n
112
+ ? null
113
+ : expected === 0n
114
+ ? 'dust withdrawal must not carry a fee leg'
115
+ : `fee leg carries ${feeAmount}, expected ${expected} for delivered ${recipientTotal}`;
116
+ }
117
+ function matchesLegacyProtocolFeeSplit(movements, feeContext) {
118
+ let gross = 0n;
119
+ for (const movement of movements) {
120
+ const amount = tryDecodeAmount(movement.movementArgs);
121
+ if (amount === null) {
122
+ return false;
123
+ }
124
+ gross += amount;
125
+ }
126
+ const expected = protocolFee(gross);
127
+ if (expected === 0n) {
128
+ return movements.length === 1;
129
+ }
130
+ if (movements.length !== 2) {
131
+ return false;
132
+ }
133
+ const [recipient, feeLeg] = movements;
134
+ if (addressEq(recipient.assetAdapter, feeContext.adapter) ||
135
+ !addressEq(feeLeg.assetAdapter, feeContext.adapter) ||
136
+ !addressEq(feeLeg.account, feeContext.treasury)) {
137
+ return false;
138
+ }
139
+ return tryDecodeAmount(feeLeg.movementArgs) === expected;
140
+ }
141
+ function addressEq(a, b) {
142
+ return a.toLowerCase() === b.toLowerCase();
143
+ }
144
+ function tryDecodeAmount(movementArgs) {
145
+ try {
146
+ return decodeMovementAmount(movementArgs);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }