@opendatalabs/vana-sdk 3.21.0 → 3.22.0-pr.207.e47cd30
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 +64 -0
- package/dist/index.browser.d.ts +3 -1
- package/dist/index.browser.js +320 -4
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +364 -34
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +3 -1
- package/dist/index.node.js +323 -7
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/derivative-questions.cjs +22 -0
- package/dist/protocol/derivative-questions.cjs.map +1 -1
- package/dist/protocol/derivative-questions.d.ts +53 -0
- package/dist/protocol/derivative-questions.js +19 -0
- package/dist/protocol/derivative-questions.js.map +1 -1
- package/dist/protocol/derivative-status.cjs +209 -0
- package/dist/protocol/derivative-status.cjs.map +1 -0
- package/dist/protocol/derivative-status.d.ts +196 -0
- package/dist/protocol/derivative-status.js +190 -0
- package/dist/protocol/derivative-status.js.map +1 -0
- package/dist/protocol/derivative-status.test.d.ts +1 -0
- package/dist/protocol/identity.cjs +169 -0
- package/dist/protocol/identity.cjs.map +1 -0
- package/dist/protocol/identity.d.ts +151 -0
- package/dist/protocol/identity.js +132 -0
- package/dist/protocol/identity.js.map +1 -0
- package/dist/protocol/identity.test.d.ts +1 -0
- package/dist/tests/mock-personal-server.d.ts +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enclave identity and consent primitives for Personal Servers.
|
|
3
|
+
*
|
|
4
|
+
* Owner/Web -> Gateway -> Agent(CVM): derive wallet and quote
|
|
5
|
+
* Web verifies evidence; Account encrypts the EIP-191 master signature to the
|
|
6
|
+
* enclave public key; Gateway relays it blind; Agent decrypts and seals it.
|
|
7
|
+
*
|
|
8
|
+
* @category Protocol
|
|
9
|
+
*/
|
|
10
|
+
import { type Address, type Hex } from "viem";
|
|
11
|
+
import { type ECIESProvider } from "../crypto/ecies/interface.js";
|
|
12
|
+
import type { ServerRegistrationMessage } from "./eip712.js";
|
|
13
|
+
export declare const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;
|
|
14
|
+
export declare const USER_PS_ID_DOMAIN = "vana.ps-enclave.v1";
|
|
15
|
+
export declare const ENCLAVE_WALLET_PURPOSE = "vana.ps-enclave.wallet.v1";
|
|
16
|
+
export declare const MASTER_SIGNATURE_DELIVERY_VERSION = "vana.ps-enclave.delivery.v1";
|
|
17
|
+
export declare const SEALED_ENVELOPE_VERSION = 1;
|
|
18
|
+
export type UserPsId = Hex;
|
|
19
|
+
export interface EnclaveIdentityEvidence {
|
|
20
|
+
v: typeof ENCLAVE_IDENTITY_EVIDENCE_VERSION;
|
|
21
|
+
userPsId: UserPsId;
|
|
22
|
+
chainId: number;
|
|
23
|
+
ownerAddress: Address;
|
|
24
|
+
epoch: number;
|
|
25
|
+
address: Address;
|
|
26
|
+
publicKey: Hex;
|
|
27
|
+
appId: Hex;
|
|
28
|
+
composeHash: Hex;
|
|
29
|
+
osImageHash?: Hex;
|
|
30
|
+
purpose: string;
|
|
31
|
+
signatureChain: [Hex, Hex];
|
|
32
|
+
quote: Hex;
|
|
33
|
+
eventLog?: string;
|
|
34
|
+
kmsRootFingerprint: Hex;
|
|
35
|
+
}
|
|
36
|
+
export interface IdentityRequest {
|
|
37
|
+
ownerAddress: Address;
|
|
38
|
+
chainId: number;
|
|
39
|
+
}
|
|
40
|
+
export type IdentityState = "prepared" | "registered" | "sealed" | "retired";
|
|
41
|
+
export interface IdentityResponse {
|
|
42
|
+
identity: EnclaveIdentityEvidence;
|
|
43
|
+
state: IdentityState;
|
|
44
|
+
created: boolean;
|
|
45
|
+
serverUrl: string;
|
|
46
|
+
serverId?: Hex;
|
|
47
|
+
serverStatus?: "pending" | "submitting" | "confirmed" | "finalized" | "failed";
|
|
48
|
+
sealed: boolean;
|
|
49
|
+
}
|
|
50
|
+
export type IdentityRegistrationRequest = {
|
|
51
|
+
version: "v2";
|
|
52
|
+
message: ServerRegistrationMessage;
|
|
53
|
+
} | {
|
|
54
|
+
version: "v3";
|
|
55
|
+
message: ServerRegistrationMessage & {
|
|
56
|
+
nonce: string;
|
|
57
|
+
deadline: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export interface IdentityRegistrationResponse {
|
|
61
|
+
serverId: Hex;
|
|
62
|
+
state: "registered";
|
|
63
|
+
serverStatus: "pending";
|
|
64
|
+
}
|
|
65
|
+
/** Inner plaintext of the ECIES box. */
|
|
66
|
+
export interface MasterSignatureDelivery {
|
|
67
|
+
v: typeof MASTER_SIGNATURE_DELIVERY_VERSION;
|
|
68
|
+
userPsId: UserPsId;
|
|
69
|
+
epoch: number;
|
|
70
|
+
enclaveAddress: Address;
|
|
71
|
+
ownerAddress: Address;
|
|
72
|
+
masterSignature: Hex;
|
|
73
|
+
issuedAt: number;
|
|
74
|
+
}
|
|
75
|
+
export interface SealedSecretSubmission {
|
|
76
|
+
userPsId: UserPsId;
|
|
77
|
+
epoch: number;
|
|
78
|
+
enclaveAddress: Address;
|
|
79
|
+
ciphertext: Hex;
|
|
80
|
+
}
|
|
81
|
+
export interface SealedSecretResponse {
|
|
82
|
+
sealed: true;
|
|
83
|
+
secretHash: Hex;
|
|
84
|
+
sealedAt: string;
|
|
85
|
+
}
|
|
86
|
+
export interface AesGcmBox {
|
|
87
|
+
iv: string;
|
|
88
|
+
ciphertext: string;
|
|
89
|
+
tag: string;
|
|
90
|
+
}
|
|
91
|
+
/** Persisted enclave envelope; the Gateway treats it as opaque text. */
|
|
92
|
+
export interface SealedEnvelope extends AesGcmBox {
|
|
93
|
+
v: typeof SEALED_ENVELOPE_VERSION;
|
|
94
|
+
wrappedContentKey: AesGcmBox;
|
|
95
|
+
}
|
|
96
|
+
export interface EnclaveTrustAnchors {
|
|
97
|
+
kmsRootPubkey: Hex;
|
|
98
|
+
appIds: Hex[];
|
|
99
|
+
}
|
|
100
|
+
/** Fleet-provisioned trust anchors keyed by Vana chain ID. */
|
|
101
|
+
export declare const ENCLAVE_TRUST_ANCHORS: Record<number, EnclaveTrustAnchors>;
|
|
102
|
+
/**
|
|
103
|
+
* Derives the deterministic Personal Server ID used by enclave paths.
|
|
104
|
+
*
|
|
105
|
+
* @param chainId - Vana chain ID.
|
|
106
|
+
* @param ownerAddress - Checksummed owner address.
|
|
107
|
+
* @returns The path-compatible keccak256 digest.
|
|
108
|
+
*/
|
|
109
|
+
export declare function userPsId(chainId: number, ownerAddress: Address): UserPsId;
|
|
110
|
+
/**
|
|
111
|
+
* Builds keccak256(purpose || ":" || hex(pubkey)) for signature-chain link 0.
|
|
112
|
+
*
|
|
113
|
+
* ASSUMPTION: hex(pubkey) is lowercase, without 0x, for the 65-byte key.
|
|
114
|
+
* TODO(verify-against-dstack-vector): pin with a captured CVM chain
|
|
115
|
+
* (dstack kms/src/crypto.rs).
|
|
116
|
+
*/
|
|
117
|
+
export declare function appRootPreimage(purpose: string, publicKey: Hex): Hex;
|
|
118
|
+
/**
|
|
119
|
+
* Builds the dstack KMS issuance digest for signature-chain link 1.
|
|
120
|
+
*
|
|
121
|
+
* ASSUMPTION: appId is lowercase hex without 0x encoded as UTF-8; the app-root
|
|
122
|
+
* key is appended as 33 raw SEC1-compressed bytes.
|
|
123
|
+
* TODO(verify-against-dstack-vector): pin with a captured CVM chain.
|
|
124
|
+
*/
|
|
125
|
+
export declare function kmsIssuedPreimage(appId: Hex, appRootPublicKey: Hex): Hex;
|
|
126
|
+
/**
|
|
127
|
+
* Verifies the enclave wallet binding against fleet-provisioned trust anchors.
|
|
128
|
+
*
|
|
129
|
+
* @param evidence - Identity evidence returned by the enclave agent.
|
|
130
|
+
* @param anchors - Pinned KMS root and allowed dstack app IDs.
|
|
131
|
+
* @throws When any identity binding or trust check fails.
|
|
132
|
+
*/
|
|
133
|
+
export declare function verifyEnclaveIdentityEvidence(evidence: EnclaveIdentityEvidence, anchors: EnclaveTrustAnchors): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Builds the authenticated master-signature plaintext delivered to an enclave.
|
|
136
|
+
*
|
|
137
|
+
* @param evidence - Verified enclave identity evidence.
|
|
138
|
+
* @param masterSignature - EIP-191 signature over the master-key message.
|
|
139
|
+
* @param now - Issuance time in Unix seconds.
|
|
140
|
+
* @returns The plaintext delivery object.
|
|
141
|
+
*/
|
|
142
|
+
export declare function buildMasterSignatureDelivery(evidence: EnclaveIdentityEvidence, masterSignature: Hex, now?: number): MasterSignatureDelivery;
|
|
143
|
+
/**
|
|
144
|
+
* Encrypts a master-signature delivery in the SDK's ECIES wire format.
|
|
145
|
+
*
|
|
146
|
+
* @param delivery - Plaintext delivery object.
|
|
147
|
+
* @param publicKey - Enclave 65-byte uncompressed public key.
|
|
148
|
+
* @param ecies - Platform-specific ECIES provider.
|
|
149
|
+
* @returns The 0x-prefixed serialized ciphertext.
|
|
150
|
+
*/
|
|
151
|
+
export declare function encryptMasterSignatureDelivery(delivery: MasterSignatureDelivery, publicKey: Hex, ecies: ECIESProvider): Promise<Hex>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as secp256k1 from "@noble/secp256k1";
|
|
2
|
+
import {
|
|
3
|
+
concat,
|
|
4
|
+
encodePacked,
|
|
5
|
+
fromHex,
|
|
6
|
+
getAddress,
|
|
7
|
+
keccak256,
|
|
8
|
+
recoverPublicKey,
|
|
9
|
+
toBytes
|
|
10
|
+
} from "viem";
|
|
11
|
+
import { publicKeyToAddress } from "viem/accounts";
|
|
12
|
+
import { serializeECIES } from "../crypto/ecies/interface.js";
|
|
13
|
+
import { deriveMasterKey } from "../crypto/keys/derive.js";
|
|
14
|
+
const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;
|
|
15
|
+
const USER_PS_ID_DOMAIN = "vana.ps-enclave.v1";
|
|
16
|
+
const ENCLAVE_WALLET_PURPOSE = "vana.ps-enclave.wallet.v1";
|
|
17
|
+
const MASTER_SIGNATURE_DELIVERY_VERSION = "vana.ps-enclave.delivery.v1";
|
|
18
|
+
const SEALED_ENVELOPE_VERSION = 1;
|
|
19
|
+
const VANA_MAINNET_CHAIN_ID = 1480;
|
|
20
|
+
const MOKSHA_CHAIN_ID = 14800;
|
|
21
|
+
const KMS_ISSUED_PREFIX = "dstack-kms-issued";
|
|
22
|
+
const PREIMAGE_SEPARATOR = ":";
|
|
23
|
+
const UNCOMPRESSED_PUBLIC_KEY_BYTES = 65;
|
|
24
|
+
const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04";
|
|
25
|
+
const ENCLAVE_TRUST_ANCHORS = {
|
|
26
|
+
// filled at fleet provisioning; verify fails closed while empty
|
|
27
|
+
[VANA_MAINNET_CHAIN_ID]: { kmsRootPubkey: "0x", appIds: [] },
|
|
28
|
+
// filled at fleet provisioning; verify fails closed while empty
|
|
29
|
+
[MOKSHA_CHAIN_ID]: { kmsRootPubkey: "0x", appIds: [] }
|
|
30
|
+
};
|
|
31
|
+
function userPsId(chainId, ownerAddress) {
|
|
32
|
+
const packed = encodePacked(
|
|
33
|
+
["string", "uint256", "address"],
|
|
34
|
+
[USER_PS_ID_DOMAIN, BigInt(chainId), getAddress(ownerAddress)]
|
|
35
|
+
);
|
|
36
|
+
return keccak256(packed);
|
|
37
|
+
}
|
|
38
|
+
function appRootPreimage(purpose, publicKey) {
|
|
39
|
+
const keyHex = publicKey.slice(2).toLowerCase();
|
|
40
|
+
return keccak256(toBytes(`${purpose}${PREIMAGE_SEPARATOR}${keyHex}`));
|
|
41
|
+
}
|
|
42
|
+
function kmsIssuedPreimage(appId, appRootPublicKey) {
|
|
43
|
+
const appIdHex = appId.slice(2).toLowerCase();
|
|
44
|
+
const prefix = toBytes(
|
|
45
|
+
`${KMS_ISSUED_PREFIX}${PREIMAGE_SEPARATOR}${appIdHex}`
|
|
46
|
+
);
|
|
47
|
+
const compressed = secp256k1.ProjectivePoint.fromHex(
|
|
48
|
+
fromHex(appRootPublicKey, "bytes")
|
|
49
|
+
).toRawBytes(true);
|
|
50
|
+
return keccak256(concat([prefix, compressed]));
|
|
51
|
+
}
|
|
52
|
+
async function recoverChainKey(hash, signature, link) {
|
|
53
|
+
try {
|
|
54
|
+
return (await recoverPublicKey({ hash, signature })).toLowerCase();
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error(`Invalid enclave signature chain link ${link}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function verifyEnclaveIdentityEvidence(evidence, anchors) {
|
|
60
|
+
if (evidence.v !== ENCLAVE_IDENTITY_EVIDENCE_VERSION) {
|
|
61
|
+
throw new Error("Unsupported enclave identity evidence version");
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isInteger(evidence.epoch) || evidence.epoch < 1) {
|
|
64
|
+
throw new Error("Invalid enclave identity epoch");
|
|
65
|
+
}
|
|
66
|
+
const derivedAddress = publicKeyToAddress(evidence.publicKey);
|
|
67
|
+
if (getAddress(derivedAddress) !== getAddress(evidence.address)) {
|
|
68
|
+
throw new Error("Enclave public key does not match its address");
|
|
69
|
+
}
|
|
70
|
+
const appRootPublicKey = await recoverChainKey(
|
|
71
|
+
appRootPreimage(evidence.purpose, evidence.publicKey),
|
|
72
|
+
evidence.signatureChain[0],
|
|
73
|
+
0
|
|
74
|
+
);
|
|
75
|
+
const kmsRootPublicKey = await recoverChainKey(
|
|
76
|
+
kmsIssuedPreimage(evidence.appId, appRootPublicKey),
|
|
77
|
+
evidence.signatureChain[1],
|
|
78
|
+
1
|
|
79
|
+
);
|
|
80
|
+
if (anchors.kmsRootPubkey === "0x") {
|
|
81
|
+
throw new Error("KMS root trust anchor is not provisioned");
|
|
82
|
+
}
|
|
83
|
+
if (kmsRootPublicKey !== anchors.kmsRootPubkey.toLowerCase()) {
|
|
84
|
+
throw new Error("KMS root public key does not match the trust anchor");
|
|
85
|
+
}
|
|
86
|
+
if (keccak256(kmsRootPublicKey) !== evidence.kmsRootFingerprint.toLowerCase()) {
|
|
87
|
+
throw new Error("KMS root fingerprint does not match the evidence");
|
|
88
|
+
}
|
|
89
|
+
const appId = evidence.appId.toLowerCase();
|
|
90
|
+
if (!anchors.appIds.some((allowedAppId) => allowedAppId.toLowerCase() === appId)) {
|
|
91
|
+
throw new Error("Enclave app ID is not trusted");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function buildMasterSignatureDelivery(evidence, masterSignature, now = Math.floor(Date.now() / 1e3)) {
|
|
95
|
+
deriveMasterKey(masterSignature);
|
|
96
|
+
return {
|
|
97
|
+
v: MASTER_SIGNATURE_DELIVERY_VERSION,
|
|
98
|
+
userPsId: evidence.userPsId,
|
|
99
|
+
epoch: evidence.epoch,
|
|
100
|
+
enclaveAddress: evidence.address,
|
|
101
|
+
ownerAddress: evidence.ownerAddress,
|
|
102
|
+
masterSignature,
|
|
103
|
+
issuedAt: now
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function assertUncompressedKey(publicKey) {
|
|
107
|
+
const hex = publicKey.slice(2);
|
|
108
|
+
if (hex.length !== UNCOMPRESSED_PUBLIC_KEY_BYTES * 2 || !hex.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
109
|
+
throw new Error("Public key must be a 65-byte uncompressed secp256k1 key");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function encryptMasterSignatureDelivery(delivery, publicKey, ecies) {
|
|
113
|
+
assertUncompressedKey(publicKey);
|
|
114
|
+
const plaintext = toBytes(JSON.stringify(delivery));
|
|
115
|
+
const encrypted = await ecies.encrypt(fromHex(publicKey, "bytes"), plaintext);
|
|
116
|
+
return `0x${serializeECIES(encrypted)}`;
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
ENCLAVE_IDENTITY_EVIDENCE_VERSION,
|
|
120
|
+
ENCLAVE_TRUST_ANCHORS,
|
|
121
|
+
ENCLAVE_WALLET_PURPOSE,
|
|
122
|
+
MASTER_SIGNATURE_DELIVERY_VERSION,
|
|
123
|
+
SEALED_ENVELOPE_VERSION,
|
|
124
|
+
USER_PS_ID_DOMAIN,
|
|
125
|
+
appRootPreimage,
|
|
126
|
+
buildMasterSignatureDelivery,
|
|
127
|
+
encryptMasterSignatureDelivery,
|
|
128
|
+
kmsIssuedPreimage,
|
|
129
|
+
userPsId,
|
|
130
|
+
verifyEnclaveIdentityEvidence
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/identity.ts"],"sourcesContent":["/**\n * Enclave identity and consent primitives for Personal Servers.\n *\n * Owner/Web -> Gateway -> Agent(CVM): derive wallet and quote\n * Web verifies evidence; Account encrypts the EIP-191 master signature to the\n * enclave public key; Gateway relays it blind; Agent decrypts and seals it.\n *\n * @category Protocol\n */\n\nimport * as secp256k1 from \"@noble/secp256k1\";\nimport {\n concat,\n encodePacked,\n fromHex,\n getAddress,\n keccak256,\n recoverPublicKey,\n toBytes,\n type Address,\n type Hex,\n} from \"viem\";\nimport { publicKeyToAddress } from \"viem/accounts\";\nimport { serializeECIES, type ECIESProvider } from \"../crypto/ecies/interface\";\nimport { deriveMasterKey } from \"../crypto/keys/derive\";\nimport type { ServerRegistrationMessage } from \"./eip712\";\n\nexport const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;\nexport const USER_PS_ID_DOMAIN = \"vana.ps-enclave.v1\";\nexport const ENCLAVE_WALLET_PURPOSE = \"vana.ps-enclave.wallet.v1\";\nexport const MASTER_SIGNATURE_DELIVERY_VERSION = \"vana.ps-enclave.delivery.v1\";\nexport const SEALED_ENVELOPE_VERSION = 1;\n\nconst VANA_MAINNET_CHAIN_ID = 1480;\nconst MOKSHA_CHAIN_ID = 14800;\nconst KMS_ISSUED_PREFIX = \"dstack-kms-issued\";\nconst PREIMAGE_SEPARATOR = \":\";\nconst UNCOMPRESSED_PUBLIC_KEY_BYTES = 65;\nconst UNCOMPRESSED_PUBLIC_KEY_PREFIX = \"04\";\n\nexport type UserPsId = Hex;\n\nexport interface EnclaveIdentityEvidence {\n v: typeof ENCLAVE_IDENTITY_EVIDENCE_VERSION;\n userPsId: UserPsId;\n chainId: number;\n ownerAddress: Address;\n epoch: number;\n address: Address;\n publicKey: Hex;\n appId: Hex;\n composeHash: Hex;\n osImageHash?: Hex;\n purpose: string;\n signatureChain: [Hex, Hex];\n quote: Hex;\n eventLog?: string;\n kmsRootFingerprint: Hex;\n}\n\nexport interface IdentityRequest {\n ownerAddress: Address;\n chainId: number;\n}\n\nexport type IdentityState = \"prepared\" | \"registered\" | \"sealed\" | \"retired\";\n\nexport interface IdentityResponse {\n identity: EnclaveIdentityEvidence;\n state: IdentityState;\n created: boolean;\n serverUrl: string;\n serverId?: Hex;\n serverStatus?:\n | \"pending\"\n | \"submitting\"\n | \"confirmed\"\n | \"finalized\"\n | \"failed\";\n sealed: boolean;\n}\n\nexport type IdentityRegistrationRequest =\n | { version: \"v2\"; message: ServerRegistrationMessage }\n | {\n version: \"v3\";\n message: ServerRegistrationMessage & { nonce: string; deadline: string };\n };\n\nexport interface IdentityRegistrationResponse {\n serverId: Hex;\n state: \"registered\";\n serverStatus: \"pending\";\n}\n\n/** Inner plaintext of the ECIES box. */\nexport interface MasterSignatureDelivery {\n v: typeof MASTER_SIGNATURE_DELIVERY_VERSION;\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n ownerAddress: Address;\n masterSignature: Hex;\n issuedAt: number;\n}\n\nexport interface SealedSecretSubmission {\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n ciphertext: Hex;\n}\n\nexport interface SealedSecretResponse {\n sealed: true;\n secretHash: Hex;\n sealedAt: string;\n}\n\nexport interface AesGcmBox {\n iv: string;\n ciphertext: string;\n tag: string;\n}\n\n/** Persisted enclave envelope; the Gateway treats it as opaque text. */\nexport interface SealedEnvelope extends AesGcmBox {\n v: typeof SEALED_ENVELOPE_VERSION;\n wrappedContentKey: AesGcmBox;\n}\n\nexport interface EnclaveTrustAnchors {\n kmsRootPubkey: Hex;\n appIds: Hex[];\n}\n\n/** Fleet-provisioned trust anchors keyed by Vana chain ID. */\nexport const ENCLAVE_TRUST_ANCHORS: Record<number, EnclaveTrustAnchors> = {\n // filled at fleet provisioning; verify fails closed while empty\n [VANA_MAINNET_CHAIN_ID]: { kmsRootPubkey: \"0x\", appIds: [] },\n // filled at fleet provisioning; verify fails closed while empty\n [MOKSHA_CHAIN_ID]: { kmsRootPubkey: \"0x\", appIds: [] },\n};\n\n/**\n * Derives the deterministic Personal Server ID used by enclave paths.\n *\n * @param chainId - Vana chain ID.\n * @param ownerAddress - Checksummed owner address.\n * @returns The path-compatible keccak256 digest.\n */\nexport function userPsId(chainId: number, ownerAddress: Address): UserPsId {\n const packed = encodePacked(\n [\"string\", \"uint256\", \"address\"],\n [USER_PS_ID_DOMAIN, BigInt(chainId), getAddress(ownerAddress)],\n );\n\n return keccak256(packed);\n}\n\n/**\n * Builds keccak256(purpose || \":\" || hex(pubkey)) for signature-chain link 0.\n *\n * ASSUMPTION: hex(pubkey) is lowercase, without 0x, for the 65-byte key.\n * TODO(verify-against-dstack-vector): pin with a captured CVM chain\n * (dstack kms/src/crypto.rs).\n */\nexport function appRootPreimage(purpose: string, publicKey: Hex): Hex {\n const keyHex = publicKey.slice(2).toLowerCase();\n return keccak256(toBytes(`${purpose}${PREIMAGE_SEPARATOR}${keyHex}`));\n}\n\n/**\n * Builds the dstack KMS issuance digest for signature-chain link 1.\n *\n * ASSUMPTION: appId is lowercase hex without 0x encoded as UTF-8; the app-root\n * key is appended as 33 raw SEC1-compressed bytes.\n * TODO(verify-against-dstack-vector): pin with a captured CVM chain.\n */\nexport function kmsIssuedPreimage(appId: Hex, appRootPublicKey: Hex): Hex {\n const appIdHex = appId.slice(2).toLowerCase();\n const prefix = toBytes(\n `${KMS_ISSUED_PREFIX}${PREIMAGE_SEPARATOR}${appIdHex}`,\n );\n const compressed = secp256k1.ProjectivePoint.fromHex(\n fromHex(appRootPublicKey, \"bytes\"),\n ).toRawBytes(true);\n\n return keccak256(concat([prefix, compressed]));\n}\n\nasync function recoverChainKey(\n hash: Hex,\n signature: Hex,\n link: number,\n): Promise<Hex> {\n try {\n return (await recoverPublicKey({ hash, signature })).toLowerCase() as Hex;\n } catch {\n throw new Error(`Invalid enclave signature chain link ${link}`);\n }\n}\n\n/**\n * Verifies the enclave wallet binding against fleet-provisioned trust anchors.\n *\n * @param evidence - Identity evidence returned by the enclave agent.\n * @param anchors - Pinned KMS root and allowed dstack app IDs.\n * @throws When any identity binding or trust check fails.\n */\nexport async function verifyEnclaveIdentityEvidence(\n evidence: EnclaveIdentityEvidence,\n anchors: EnclaveTrustAnchors,\n): Promise<void> {\n if (evidence.v !== ENCLAVE_IDENTITY_EVIDENCE_VERSION) {\n throw new Error(\"Unsupported enclave identity evidence version\");\n }\n\n if (!Number.isInteger(evidence.epoch) || evidence.epoch < 1) {\n throw new Error(\"Invalid enclave identity epoch\");\n }\n\n const derivedAddress = publicKeyToAddress(evidence.publicKey);\n\n if (getAddress(derivedAddress) !== getAddress(evidence.address)) {\n throw new Error(\"Enclave public key does not match its address\");\n }\n\n const appRootPublicKey = await recoverChainKey(\n appRootPreimage(evidence.purpose, evidence.publicKey),\n evidence.signatureChain[0],\n 0,\n );\n const kmsRootPublicKey = await recoverChainKey(\n kmsIssuedPreimage(evidence.appId, appRootPublicKey),\n evidence.signatureChain[1],\n 1,\n );\n\n if (anchors.kmsRootPubkey === \"0x\") {\n throw new Error(\"KMS root trust anchor is not provisioned\");\n }\n\n if (kmsRootPublicKey !== anchors.kmsRootPubkey.toLowerCase()) {\n throw new Error(\"KMS root public key does not match the trust anchor\");\n }\n\n if (\n keccak256(kmsRootPublicKey) !== evidence.kmsRootFingerprint.toLowerCase()\n ) {\n throw new Error(\"KMS root fingerprint does not match the evidence\");\n }\n\n const appId = evidence.appId.toLowerCase();\n if (\n !anchors.appIds.some((allowedAppId) => allowedAppId.toLowerCase() === appId)\n ) {\n throw new Error(\"Enclave app ID is not trusted\");\n }\n}\n\n/**\n * Builds the authenticated master-signature plaintext delivered to an enclave.\n *\n * @param evidence - Verified enclave identity evidence.\n * @param masterSignature - EIP-191 signature over the master-key message.\n * @param now - Issuance time in Unix seconds.\n * @returns The plaintext delivery object.\n */\nexport function buildMasterSignatureDelivery(\n evidence: EnclaveIdentityEvidence,\n masterSignature: Hex,\n now = Math.floor(Date.now() / 1000),\n): MasterSignatureDelivery {\n deriveMasterKey(masterSignature);\n\n return {\n v: MASTER_SIGNATURE_DELIVERY_VERSION,\n userPsId: evidence.userPsId,\n epoch: evidence.epoch,\n enclaveAddress: evidence.address,\n ownerAddress: evidence.ownerAddress,\n masterSignature,\n issuedAt: now,\n };\n}\n\nfunction assertUncompressedKey(publicKey: Hex): void {\n const hex = publicKey.slice(2);\n if (\n hex.length !== UNCOMPRESSED_PUBLIC_KEY_BYTES * 2 ||\n !hex.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) ||\n !/^[0-9a-fA-F]+$/.test(hex)\n ) {\n throw new Error(\"Public key must be a 65-byte uncompressed secp256k1 key\");\n }\n}\n\n/**\n * Encrypts a master-signature delivery in the SDK's ECIES wire format.\n *\n * @param delivery - Plaintext delivery object.\n * @param publicKey - Enclave 65-byte uncompressed public key.\n * @param ecies - Platform-specific ECIES provider.\n * @returns The 0x-prefixed serialized ciphertext.\n */\nexport async function encryptMasterSignatureDelivery(\n delivery: MasterSignatureDelivery,\n publicKey: Hex,\n ecies: ECIESProvider,\n): Promise<Hex> {\n assertUncompressedKey(publicKey);\n\n const plaintext = toBytes(JSON.stringify(delivery));\n const encrypted = await ecies.encrypt(fromHex(publicKey, \"bytes\"), plaintext);\n\n return `0x${serializeECIES(encrypted)}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -84,6 +84,13 @@ export interface MockQuestion {
|
|
|
84
84
|
};
|
|
85
85
|
status: "pending" | "ready" | "failed" | "stale";
|
|
86
86
|
error: string | null;
|
|
87
|
+
/** The coarse failure class the status route serves; null unless failed. */
|
|
88
|
+
errorCode: "inference_unavailable" | "source_missing" | "grant_invalid" | "internal" | null;
|
|
89
|
+
/**
|
|
90
|
+
* What the status route reports as the next automatic retry. The mock has
|
|
91
|
+
* no scheduler, so a test sets it through `settleQuestion`.
|
|
92
|
+
*/
|
|
93
|
+
retryAfterSeconds: number | null;
|
|
87
94
|
createdAt: string;
|
|
88
95
|
updatedAt: string;
|
|
89
96
|
lastComputedAt: string | null;
|
|
@@ -126,6 +133,8 @@ export interface MockPersonalServer {
|
|
|
126
133
|
} | {
|
|
127
134
|
status: "failed";
|
|
128
135
|
error: string;
|
|
136
|
+
errorCode?: MockQuestion["errorCode"];
|
|
137
|
+
retryAfterSeconds?: number | null;
|
|
129
138
|
} | {
|
|
130
139
|
status: "stale" | "pending";
|
|
131
140
|
}): void;
|