@inco/shield-js 0.0.0-bootstrap.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.
- package/README.md +52 -308
- package/dist/contracts/abi.d.ts +2000 -4513
- package/dist/contracts/abi.js +2400 -5651
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +2 -2
- package/dist/contracts/utils.d.ts +5 -24
- package/dist/contracts/utils.js +13 -24
- package/dist/errors.d.ts +16 -47
- package/dist/errors.js +6 -53
- package/dist/generated/inco/shield/v2/conductor_pb.d.ts +77 -0
- package/dist/generated/inco/shield/v2/conductor_pb.js +6 -0
- package/dist/generated/inco/shield/v2/deposit_pb.d.ts +12 -103
- package/dist/generated/inco/shield/v2/deposit_pb.js +4 -141
- package/dist/generated/inco/shield/v2/drafting_pb.d.ts +45 -269
- package/dist/generated/inco/shield/v2/drafting_pb.js +8 -361
- package/dist/generated/inco/shield/v2/types_pb.d.ts +47 -166
- package/dist/generated/inco/shield/v2/types_pb.js +12 -261
- package/dist/index.d.ts +1 -7
- package/dist/index.js +1 -14
- package/dist/shield/abi.d.ts +5 -0
- package/dist/shield/abi.js +28 -0
- package/dist/shield/client.d.ts +27 -51
- package/dist/shield/client.js +73 -65
- package/dist/shield/convert.d.ts +2 -14
- package/dist/shield/convert.js +39 -115
- package/dist/shield/encryption.d.ts +0 -67
- package/dist/shield/encryption.js +0 -67
- package/dist/shield/envelope.d.ts +10 -11
- package/dist/shield/envelope.js +15 -14
- package/dist/shield/fee.d.ts +24 -0
- package/dist/shield/fee.js +151 -0
- package/dist/shield/index.d.ts +9 -3
- package/dist/shield/index.js +7 -2
- package/dist/shield/intent.d.ts +20 -933
- package/dist/shield/intent.js +92 -112
- package/dist/shield/permission-status.d.ts +9 -0
- package/dist/shield/permission-status.js +8 -0
- package/dist/shield/rpc.d.ts +50 -49
- package/dist/shield/rpc.js +207 -354
- package/dist/shield/shield.eip712.gen.d.ts +1096 -43
- package/dist/shield/shield.eip712.gen.js +572 -62
- package/dist/shield/types.d.ts +7 -102
- package/dist/shield/types.js +1 -4
- package/dist/shield/userop/gas-estimation.d.ts +8 -23
- package/dist/shield/userop/gas-estimation.js +100 -137
- package/dist/shield/userop/paymaster.d.ts +15 -0
- package/dist/shield/userop/paymaster.js +17 -0
- package/dist/shield/userop/token-exchange.d.ts +5 -52
- package/dist/shield/userop/token-exchange.js +41 -149
- package/dist/shield/userop/types.d.ts +3 -28
- package/dist/shield/userop/types.js +0 -3
- package/dist/shield/userop/userOp.d.ts +0 -19
- package/dist/shield/userop/userOp.js +4 -22
- package/dist/shield/utils/chain.d.ts +1 -0
- package/dist/shield/utils/chain.js +3 -0
- package/dist/shield/validate-withdrawal-split.d.ts +13 -0
- package/dist/shield/validate-withdrawal-split.js +93 -0
- package/package.json +18 -27
- package/dist/generated/inco/shield/v2/conductor_connect.d.ts +0 -137
- package/dist/generated/inco/shield/v2/conductor_connect.js +0 -141
- package/dist/generated/inco/shield/v2/permission_pb.d.ts +0 -443
- package/dist/generated/inco/shield/v2/permission_pb.js +0 -639
- package/dist/generated/inco/shield/v2/query_pb.d.ts +0 -103
- package/dist/generated/inco/shield/v2/query_pb.js +0 -141
- package/dist/shield/userop/index.d.ts +0 -1
- package/dist/shield/userop/index.js +0 -1
- package/dist/uniswap-adapter/config.d.ts +0 -34
- package/dist/uniswap-adapter/config.js +0 -45
- package/dist/uniswap-adapter/find-pools.d.ts +0 -91
- package/dist/uniswap-adapter/find-pools.js +0 -108
- package/dist/uniswap-adapter/index.d.ts +0 -10
- package/dist/uniswap-adapter/index.js +0 -13
- package/dist/uniswap-adapter/swap.d.ts +0 -344
- package/dist/uniswap-adapter/swap.js +0 -309
package/dist/shield/convert.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/shield/convert.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
export declare function openSignedResponse(resp: EncryptedResponse, responseKey: Uint8Array): Promise<{
|
|
11
|
+
body: Uint8Array;
|
|
12
|
+
teeSignature: Uint8Array;
|
|
13
|
+
}>;
|
package/dist/shield/envelope.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
+
import { create, fromBinary, toBinary } from '@bufbuild/protobuf';
|
|
1
2
|
import { toHex } from 'viem';
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|