-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbootstrap.ts
More file actions
146 lines (128 loc) 路 5.6 KB
/
Copy pathbootstrap.ts
File metadata and controls
146 lines (128 loc) 路 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { GhostRequestBootstrap, GhostResponse, BootstrapResponseData } from '../params';
import { type GhostContext, Entropy } from '../context';
import { arrayify, keccak256, concat, hexlify } from '@monorepo/core/sandboxed';
/**
* Handle bootstrap request - generates entropy, encrypts it, and signs it
* This initializes the system with a secret that only this Lit Action can decrypt
*
* IMPORTANT: This now calls setEntropy on-chain from within the TEE
*/
export async function handleBootstrap(request: GhostRequestBootstrap, ctx: GhostContext): Promise<GhostResponse<BootstrapResponseData>> {
const accessControlConditions = ctx.litCidAccessControl();
const currentCid = ctx.getCurrentIPFSCid();
const encryptResult = await ctx.makeEntropy(request.pkpEthAddress, request.pkpPublicKey, request.tgApiSecret);
// Get the manager to extract teePublicKey
const manager = await ctx.getManager();
const teeEncPublicKey = hexlify(manager.teePublicKey);
const dataHashBytes = arrayify('0x'+encryptResult.dataToEncryptHash);
const ciphertextBytes = new TextEncoder().encode(encryptResult.ciphertext);
const cidBytes = new TextEncoder().encode(currentCid);
const toSign = arrayify(keccak256(concat([dataHashBytes, ciphertextBytes, cidBytes])));
const signature = ctx.litEcdsaSigToEthSig(await Lit.Actions.signAndCombineEcdsa({
toSign,
publicKey: request.pkpPublicKey,
sigName: 'bootstrap-sig',
}));
// Construct and sign the setEntropy transaction
const txHash = await sendSetEntropyTransaction(
ctx,
request.pkpPublicKey,
request.pkpEthAddress,
{
ciphertext: encryptResult.ciphertext,
digest: '0x' + encryptResult.dataToEncryptHash,
ipfsCid: currentCid,
sig: signature,
teeEncPublicKey: teeEncPublicKey,
}
);
// Clear entropy and reload from chain to verify everything matches
ctx.clearEntropy();
// Fetch on-chain entropy FIRST (before attempting to decrypt)
const onChainEntropy = await ctx.ghost.getEntropy() as Entropy;
// Verify on-chain data matches what we sent (before attempting decrypt)
const ciphertextMatches = onChainEntropy.ciphertext === encryptResult.ciphertext;
const digestMatches = onChainEntropy.digest === ('0x' + encryptResult.dataToEncryptHash);
const cidMatches = onChainEntropy.ipfsCid === currentCid;
const sigVMatches = onChainEntropy.sig.v === signature.v;
const sigRMatches = onChainEntropy.sig.r === signature.r;
const sigSMatches = onChainEntropy.sig.s === signature.s;
const teeEncPublicKeyOnChainMatches = onChainEntropy.teeEncPublicKey === teeEncPublicKey;
if (!ciphertextMatches) {
throw new Error('Verification failed: ciphertext mismatch');
}
if (!digestMatches) {
throw new Error('Verification failed: digest mismatch');
}
if (!cidMatches) {
throw new Error('Verification failed: ipfsCid mismatch');
}
if (!sigVMatches || !sigRMatches || !sigSMatches) {
throw new Error('Verification failed: signature mismatch');
}
if (!teeEncPublicKeyOnChainMatches) {
throw new Error('Verification failed: teeEncPublicKey mismatch');
}
// NOW attempt to decrypt and reload entropy
let reloadedPrivateParams: any;
let reloadedTeeEncPublicKey: string;
try {
reloadedPrivateParams = await ctx.getPrivateParams();
const reloadedManager = await ctx.getManager();
reloadedTeeEncPublicKey = hexlify(reloadedManager.teePublicKey);
// Verify all reloaded values match
const pkpPublicKeyMatches = ctx.pkpPublicKey === request.pkpPublicKey;
const pkpEthAddressMatches = ctx.pkpEthAddress === request.pkpEthAddress;
const teeEncPublicKeyReloadMatches = reloadedTeeEncPublicKey === teeEncPublicKey;
const tgApiSecretMatches = reloadedPrivateParams.tgApiSecret === request.tgApiSecret;
if (!pkpPublicKeyMatches) throw new Error('Verification failed: pkpPublicKey mismatch after reload');
if (!pkpEthAddressMatches) throw new Error('Verification failed: pkpEthAddress mismatch after reload');
if (!teeEncPublicKeyReloadMatches) throw new Error('Verification failed: teeEncPublicKey mismatch after reload');
if (!tgApiSecretMatches) throw new Error('Verification failed: tgApiSecret mismatch after reload');
} catch (error) {
throw new Error('Failed to decrypt and reload entropy: ' + (error as any)?.message);
}
return {
ok: true,
data: {
pkp: request.pkpPublicKey,
accessControlConditions,
dataToEncryptHash: encryptResult.dataToEncryptHash,
ciphertext: encryptResult.ciphertext,
encryptResult: encryptResult,
signature: signature,
currentCid,
teeEncPublicKey,
setEntropyTxHash: txHash,
},
}
}
/**
* Construct, sign, and broadcast setEntropy transaction from within the TEE
*/
async function sendSetEntropyTransaction(
ctx: GhostContext,
pkpPublicKey: string,
pkpEthAddress: string,
entropy: Entropy
): Promise<string> {
// Get current nonce and gas price
const nonce = await ctx.provider.getTransactionCount(pkpEthAddress, 'pending');
const feeData = await ctx.provider.getFeeData();
// Encode the setEntropy call data using the contract's interface
const setEntropyData = ctx.ghost.interface.encodeFunctionData('setEntropy', [entropy]);
// Construct the transaction
const tx = {
to: ctx.ghost.address,
nonce,
gasLimit: hexlify(1000000),
data: setEntropyData,
chainId: (await ctx.provider.getNetwork()).chainId,
// EIP-1559 transaction
maxFeePerGas: feeData.maxFeePerGas || undefined,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas || undefined,
type: 2,
};
// Wait for the tx to be mined
return await ctx.signAndSendTx(pkpPublicKey, tx, true);
}