@1upmonster/duel 0.2.2 → 0.2.3
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 +15 -5
- package/dist/admin.js +32 -3
- package/dist/generated/duel/instructions/delegateQueue.js +3 -3
- package/dist/generated/duel/instructions/delegateTicket.js +3 -3
- package/dist/generated/duel/instructions/index.d.ts +1 -0
- package/dist/generated/duel/instructions/index.js +2 -1
- package/dist/generated/duel/instructions/setupQueuePermission.d.ts +51 -0
- package/dist/generated/duel/instructions/setupQueuePermission.js +63 -0
- package/dist/generated/duel/programs/duel.d.ts +6 -2
- package/dist/generated/duel/programs/duel.js +12 -4
- package/dist/player.d.ts +2 -1
- package/dist/player.js +37 -6
- package/dist/tee.d.ts +4 -3
- package/dist/tee.js +17 -23
- package/dist/transaction.d.ts +2 -0
- package/dist/transaction.js +30 -19
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +28 -1
- package/package.json +1 -1
- package/src/admin.ts +36 -0
- package/src/duel.json +57 -0
- package/src/generated/duel/instructions/delegateQueue.ts +2 -2
- package/src/generated/duel/instructions/delegateTicket.ts +2 -2
- package/src/generated/duel/instructions/index.ts +1 -0
- package/src/generated/duel/instructions/setupQueuePermission.ts +112 -0
- package/src/generated/duel/programs/duel.ts +8 -4
- package/src/player.ts +41 -3
- package/src/tee.ts +18 -22
- package/src/transaction.ts +33 -18
- package/src/utils.ts +34 -0
- package/src/encryption.ts +0 -154
package/src/player.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSolanaRpc,
|
|
3
|
+
AccountRole,
|
|
3
4
|
type Address,
|
|
4
5
|
type TransactionSigner,
|
|
5
6
|
type Rpc,
|
|
6
7
|
type SolanaRpcApi,
|
|
8
|
+
type Instruction,
|
|
7
9
|
} from "@solana/kit";
|
|
8
10
|
import {
|
|
9
11
|
getCreateTicketInstructionAsync,
|
|
10
12
|
getDelegateTicketInstructionAsync,
|
|
13
|
+
getSetupTicketPermissionInstructionAsync,
|
|
11
14
|
getJoinQueueInstructionAsync,
|
|
12
15
|
getCancelTicketInstructionAsync,
|
|
13
16
|
getCloseTicketInstructionAsync,
|
|
@@ -15,7 +18,6 @@ import {
|
|
|
15
18
|
accountType,
|
|
16
19
|
} from "./generated/duel/index.js";
|
|
17
20
|
import { sendInstruction } from "./transaction.js";
|
|
18
|
-
import { waitUntilPermissionActive } from "./tee.js";
|
|
19
21
|
import * as utils from "./utils.js";
|
|
20
22
|
|
|
21
23
|
const DUEL_PROGRAM_ID = "EdZzUwKd1X2ZWjxLPpz1cpEzMF7RUZC43Pq64v1VcK5X" as Address;
|
|
@@ -130,7 +132,8 @@ export class MatchmakingPlayer {
|
|
|
130
132
|
|
|
131
133
|
/**
|
|
132
134
|
* High-level: full matchmaking TEE entry flow.
|
|
133
|
-
* Creates ticket on L1,
|
|
135
|
+
* Creates ticket on L1, sets up permission PDA (so only player + queue authority can read it),
|
|
136
|
+
* delegates the permission PDA and ticket to TEE, then joins the queue.
|
|
134
137
|
* Use individual methods (createTicket, delegateTicket, joinQueue) as escape hatches if needed.
|
|
135
138
|
*/
|
|
136
139
|
async enterQueue(
|
|
@@ -145,10 +148,45 @@ export class MatchmakingPlayer {
|
|
|
145
148
|
const player = this.signer.address as Address;
|
|
146
149
|
const ticketPda = await this.getTicketPda(player, tenant);
|
|
147
150
|
|
|
151
|
+
// 1. Create ticket on L1
|
|
148
152
|
await this.createTicket(tenant);
|
|
153
|
+
|
|
154
|
+
// 2. Create Permission PDA for the ticket (CPI from duel program with invoke_signed)
|
|
155
|
+
const permissionPda = await utils.derivePermissionPda(ticketPda);
|
|
156
|
+
const setupIx = await getSetupTicketPermissionInstructionAsync({
|
|
157
|
+
player: this.signer,
|
|
158
|
+
tenant,
|
|
159
|
+
permission: permissionPda,
|
|
160
|
+
permissionProgram: utils.PERMISSION_PROGRAM,
|
|
161
|
+
}, { programAddress: this.programId });
|
|
162
|
+
await sendInstruction(this.rpc, setupIx, this.signer);
|
|
163
|
+
|
|
164
|
+
// 3. Delegate Permission PDA to TEE (called on permission program, player signs)
|
|
165
|
+
const { delegationBuffer, delegationRecord, delegationMetadata } =
|
|
166
|
+
await utils.derivePermissionDelegationPdas(permissionPda);
|
|
167
|
+
const delegatePermIx: Instruction = {
|
|
168
|
+
programAddress: utils.PERMISSION_PROGRAM,
|
|
169
|
+
data: new Uint8Array([3, 0, 0, 0, 0, 0, 0, 0]),
|
|
170
|
+
accounts: [
|
|
171
|
+
{ address: this.signer.address, role: AccountRole.WRITABLE_SIGNER },
|
|
172
|
+
{ address: this.signer.address, role: AccountRole.READONLY_SIGNER },
|
|
173
|
+
{ address: ticketPda, role: AccountRole.READONLY },
|
|
174
|
+
{ address: permissionPda, role: AccountRole.WRITABLE },
|
|
175
|
+
{ address: "11111111111111111111111111111111" as Address, role: AccountRole.READONLY },
|
|
176
|
+
{ address: utils.PERMISSION_PROGRAM, role: AccountRole.READONLY },
|
|
177
|
+
{ address: delegationBuffer, role: AccountRole.WRITABLE },
|
|
178
|
+
{ address: delegationRecord, role: AccountRole.WRITABLE },
|
|
179
|
+
{ address: delegationMetadata, role: AccountRole.WRITABLE },
|
|
180
|
+
{ address: utils.DELEGATION_PROGRAM, role: AccountRole.READONLY },
|
|
181
|
+
...(validator ? [{ address: validator, role: AccountRole.READONLY }] : []),
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
await sendInstruction(this.rpc, delegatePermIx, this.signer);
|
|
185
|
+
|
|
186
|
+
// 4. Delegate ticket to TEE
|
|
149
187
|
await this.delegateTicket(player, tenant, validator);
|
|
150
|
-
await waitUntilPermissionActive(teeUrlWithToken, ticketPda);
|
|
151
188
|
|
|
189
|
+
// 5. Join the queue on TEE
|
|
152
190
|
const teeClient = new MatchmakingPlayer(teeRpc, this.signer, this.programId);
|
|
153
191
|
await teeClient.joinQueue(queue, tenant, playerData, callbackProgram);
|
|
154
192
|
|
package/src/tee.ts
CHANGED
|
@@ -45,35 +45,31 @@ export async function getAuthToken(
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Poll the TEE
|
|
49
|
-
*
|
|
48
|
+
* Poll the TEE until the Permission PDA for the given account is active (authorizedUsers non-empty).
|
|
49
|
+
* This confirms the TEE has picked up the delegated Permission PDA and is enforcing access control.
|
|
50
|
+
* Returns true if active before timeout, false otherwise.
|
|
50
51
|
*/
|
|
51
|
-
export async function
|
|
52
|
+
export async function waitForPermission(
|
|
52
53
|
teeUrlWithToken: string,
|
|
53
|
-
|
|
54
|
-
timeoutMs =
|
|
54
|
+
accountAddress: Address,
|
|
55
|
+
timeoutMs = 10000,
|
|
55
56
|
): Promise<boolean> {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
permissionUrl = `${baseUrl}/permission?${tokenParam}&pubkey=${pda}`;
|
|
61
|
-
} else {
|
|
62
|
-
permissionUrl = `${baseUrl}/permission?pubkey=${pda}`;
|
|
63
|
-
}
|
|
57
|
+
const [baseUrl, token] = teeUrlWithToken.replace("/?", "?").split("?");
|
|
58
|
+
const permUrl = token
|
|
59
|
+
? `${baseUrl}/permission?${token}&pubkey=${accountAddress}`
|
|
60
|
+
: `${baseUrl}/permission?pubkey=${accountAddress}`;
|
|
64
61
|
|
|
65
|
-
const
|
|
66
|
-
while (Date.now()
|
|
62
|
+
const deadline = Date.now() + timeoutMs;
|
|
63
|
+
while (Date.now() < deadline) {
|
|
67
64
|
try {
|
|
68
|
-
const res = await fetch(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (authorizedUsers && authorizedUsers.length > 0) return true;
|
|
72
|
-
}
|
|
65
|
+
const res = await fetch(permUrl);
|
|
66
|
+
const json = (await res.json()) as { authorizedUsers?: unknown[] | null };
|
|
67
|
+
if (json.authorizedUsers && json.authorizedUsers.length > 0) return true;
|
|
73
68
|
} catch {
|
|
74
|
-
// ignore transient errors
|
|
69
|
+
// ignore transient errors
|
|
75
70
|
}
|
|
76
|
-
await new Promise((r) => setTimeout(r,
|
|
71
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
77
72
|
}
|
|
78
73
|
return false;
|
|
79
74
|
}
|
|
75
|
+
|
package/src/transaction.ts
CHANGED
|
@@ -15,8 +15,34 @@ import {
|
|
|
15
15
|
|
|
16
16
|
type SolanaRpc = Rpc<SolanaRpcApi>;
|
|
17
17
|
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const bigIntReplacer = (_: string, v: unknown) => typeof v === 'bigint' ? v.toString() : v;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Poll for transaction confirmation and throw if it failed.
|
|
23
|
+
* This ensures callers always know immediately when a transaction is rejected.
|
|
24
|
+
*/
|
|
25
|
+
async function confirmTransaction(rpc: SolanaRpc, sig: string): Promise<void> {
|
|
26
|
+
for (let i = 0; i < 8; i++) {
|
|
27
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
const statuses = await (rpc as any).getSignatureStatuses([sig], { searchTransactionHistory: false }).send().catch(() => null);
|
|
30
|
+
const status = statuses?.value?.[0];
|
|
31
|
+
if (status) {
|
|
32
|
+
if (status.err) {
|
|
33
|
+
throw new Error(`[TX] ${sig.slice(0, 16)}... FAILED: ${JSON.stringify(status.err, bigIntReplacer)}`);
|
|
34
|
+
}
|
|
35
|
+
console.log(`[TX] ${sig.slice(0, 16)}... ${status.confirmationStatus}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// No status after 8s — treat as timeout rather than silently succeeding
|
|
40
|
+
throw new Error(`[TX] ${sig.slice(0, 16)}... confirmation timeout (no status after 8s)`);
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
/**
|
|
19
44
|
* Build, sign with a Kit keypair signer, and send a single instruction.
|
|
45
|
+
* Throws if the transaction is rejected or times out.
|
|
20
46
|
*/
|
|
21
47
|
export async function sendInstruction(
|
|
22
48
|
rpc: SolanaRpc,
|
|
@@ -42,27 +68,13 @@ export async function sendInstruction(
|
|
|
42
68
|
skipPreflight: true,
|
|
43
69
|
}).send() as Promise<string>);
|
|
44
70
|
|
|
45
|
-
|
|
46
|
-
for (let i = 0; i < 8; i++) {
|
|
47
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
48
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
-
const statuses = await (rpc as any).getSignatureStatuses([sig], { searchTransactionHistory: false }).send().catch(() => null);
|
|
50
|
-
const status = statuses?.value?.[0];
|
|
51
|
-
if (status) {
|
|
52
|
-
if (status.err) {
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
console.error(`[TX] ${sig.slice(0, 16)}... FAILED:`, JSON.stringify(status.err, (_, v) => typeof v === 'bigint' ? v.toString() : v));
|
|
55
|
-
} else if (status.confirmationStatus) {
|
|
56
|
-
console.log(`[TX] ${sig.slice(0, 16)}... ${status.confirmationStatus}`);
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
71
|
+
await confirmTransaction(rpc, sig);
|
|
61
72
|
return sig;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
/**
|
|
65
76
|
* Build, sign, and send multiple instructions in a single transaction.
|
|
77
|
+
* Throws if the transaction is rejected or times out.
|
|
66
78
|
*/
|
|
67
79
|
export async function sendInstructions(
|
|
68
80
|
rpc: SolanaRpc,
|
|
@@ -83,8 +95,11 @@ export async function sendInstructions(
|
|
|
83
95
|
const encoded = getBase64EncodedWireTransaction(signedTx);
|
|
84
96
|
|
|
85
97
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
-
|
|
98
|
+
const sig = await (rpc.sendTransaction(encoded as any, {
|
|
87
99
|
encoding: "base64",
|
|
88
100
|
skipPreflight: true,
|
|
89
|
-
}).send() as Promise<string
|
|
101
|
+
}).send() as Promise<string>);
|
|
102
|
+
|
|
103
|
+
await confirmTransaction(rpc, sig);
|
|
104
|
+
return sig;
|
|
90
105
|
}
|
package/src/utils.ts
CHANGED
|
@@ -8,6 +8,40 @@ import {
|
|
|
8
8
|
const addressEncoder = getAddressEncoder();
|
|
9
9
|
const utf8Encoder = getUtf8Encoder();
|
|
10
10
|
|
|
11
|
+
export const PERMISSION_PROGRAM = "ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1" as Address;
|
|
12
|
+
export const DELEGATION_PROGRAM = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" as Address;
|
|
13
|
+
|
|
14
|
+
/** Derives the Permission PDA for a given permissioned account. */
|
|
15
|
+
export async function derivePermissionPda(accountAddress: Address): Promise<Address> {
|
|
16
|
+
const [pda] = await getProgramDerivedAddress({
|
|
17
|
+
programAddress: PERMISSION_PROGRAM,
|
|
18
|
+
seeds: [utf8Encoder.encode("permission:"), addressEncoder.encode(accountAddress)],
|
|
19
|
+
});
|
|
20
|
+
return pda;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Derives the DELeGG delegation PDAs for the Permission PDA (used in DelegatePermission ix). */
|
|
24
|
+
export async function derivePermissionDelegationPdas(permissionPda: Address): Promise<{
|
|
25
|
+
delegationBuffer: Address;
|
|
26
|
+
delegationRecord: Address;
|
|
27
|
+
delegationMetadata: Address;
|
|
28
|
+
}> {
|
|
29
|
+
const [delegationRecord] = await getProgramDerivedAddress({
|
|
30
|
+
programAddress: DELEGATION_PROGRAM,
|
|
31
|
+
seeds: [utf8Encoder.encode("delegation"), addressEncoder.encode(permissionPda)],
|
|
32
|
+
});
|
|
33
|
+
const [delegationMetadata] = await getProgramDerivedAddress({
|
|
34
|
+
programAddress: DELEGATION_PROGRAM,
|
|
35
|
+
seeds: [utf8Encoder.encode("delegation-metadata"), addressEncoder.encode(permissionPda)],
|
|
36
|
+
});
|
|
37
|
+
// Buffer PDA is under the ownerProgram (PERMISSION_PROGRAM for the Permission PDA)
|
|
38
|
+
const [delegationBuffer] = await getProgramDerivedAddress({
|
|
39
|
+
programAddress: PERMISSION_PROGRAM,
|
|
40
|
+
seeds: [utf8Encoder.encode("buffer"), addressEncoder.encode(permissionPda)],
|
|
41
|
+
});
|
|
42
|
+
return { delegationBuffer, delegationRecord, delegationMetadata };
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
export const QUEUE_SEED = "queue";
|
|
12
46
|
export const TENANT_SEED = "tenant";
|
|
13
47
|
export const TICKET_SEED = "ticket";
|
package/src/encryption.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
export class EncryptionProvider {
|
|
2
|
-
private keyPair: CryptoKeyPair | null = null;
|
|
3
|
-
|
|
4
|
-
// Check for crypto availability
|
|
5
|
-
private get crypto(): Crypto {
|
|
6
|
-
if (typeof globalThis.crypto !== 'undefined') {
|
|
7
|
-
return globalThis.crypto;
|
|
8
|
-
}
|
|
9
|
-
// Fallback for Node < 19 if global crypto not set (though SDK likely targets modern envs)
|
|
10
|
-
try {
|
|
11
|
-
return require('crypto').webcrypto;
|
|
12
|
-
} catch (e) {
|
|
13
|
-
throw new Error("WebCrypto API not available.");
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Generate a fresh ephemeral keypair for the session (X25519/P-256).
|
|
19
|
-
*/
|
|
20
|
-
async generateSessionKey(): Promise<CryptoKey> {
|
|
21
|
-
this.keyPair = await this.crypto.subtle.generateKey(
|
|
22
|
-
{
|
|
23
|
-
name: "ECDH",
|
|
24
|
-
namedCurve: "P-256",
|
|
25
|
-
},
|
|
26
|
-
true,
|
|
27
|
-
["deriveKey", "deriveBits"]
|
|
28
|
-
) as CryptoKeyPair;
|
|
29
|
-
|
|
30
|
-
return this.keyPair.publicKey;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Derive shared secret and encrypt payload.
|
|
35
|
-
*/
|
|
36
|
-
async encryptPayload(
|
|
37
|
-
data: Uint8Array,
|
|
38
|
-
teePublicKeyBytes: Uint8Array
|
|
39
|
-
): Promise<{ encrypted: Uint8Array, clientPublicKey: Uint8Array }> {
|
|
40
|
-
if (!this.keyPair) {
|
|
41
|
-
await this.generateSessionKey();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Import TEE Public Key
|
|
45
|
-
// Fix: Explicitly cast to BufferSource/any because TS gets confused with SharedArrayBuffer in some envs
|
|
46
|
-
const teeKey = await this.crypto.subtle.importKey(
|
|
47
|
-
"raw",
|
|
48
|
-
teePublicKeyBytes as unknown as BufferSource,
|
|
49
|
-
{ name: "ECDH", namedCurve: "P-256" },
|
|
50
|
-
false,
|
|
51
|
-
[]
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// Derive Shared Secret (AES-GCM Key)
|
|
55
|
-
const sharedKey = await this.crypto.subtle.deriveKey(
|
|
56
|
-
{
|
|
57
|
-
name: "ECDH",
|
|
58
|
-
public: teeKey,
|
|
59
|
-
},
|
|
60
|
-
this.keyPair!.privateKey,
|
|
61
|
-
{
|
|
62
|
-
name: "AES-GCM",
|
|
63
|
-
length: 256,
|
|
64
|
-
},
|
|
65
|
-
false,
|
|
66
|
-
["encrypt", "decrypt"]
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Encrypt Data
|
|
70
|
-
const iv = this.crypto.getRandomValues(new Uint8Array(12));
|
|
71
|
-
const encryptedBuffer = await this.crypto.subtle.encrypt(
|
|
72
|
-
{
|
|
73
|
-
name: "AES-GCM",
|
|
74
|
-
iv: iv,
|
|
75
|
-
},
|
|
76
|
-
sharedKey,
|
|
77
|
-
data as unknown as BufferSource
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
// Concatenate IV + CipherText
|
|
81
|
-
const encrypted = new Uint8Array(iv.length + encryptedBuffer.byteLength);
|
|
82
|
-
encrypted.set(iv);
|
|
83
|
-
encrypted.set(new Uint8Array(encryptedBuffer), iv.length);
|
|
84
|
-
|
|
85
|
-
// Export Client Public Key
|
|
86
|
-
const clientPubRaw = await this.crypto.subtle.exportKey("raw", this.keyPair!.publicKey);
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
encrypted,
|
|
90
|
-
clientPublicKey: new Uint8Array(clientPubRaw)
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Decrypt a response from the TEE.
|
|
96
|
-
*/
|
|
97
|
-
async decryptResponse(
|
|
98
|
-
encryptedData: Uint8Array,
|
|
99
|
-
teePublicKeyBytes: Uint8Array
|
|
100
|
-
): Promise<Uint8Array> {
|
|
101
|
-
if (!this.keyPair) throw new Error("No session key");
|
|
102
|
-
|
|
103
|
-
// Import TEE Public Key
|
|
104
|
-
const teeKey = await this.crypto.subtle.importKey(
|
|
105
|
-
"raw",
|
|
106
|
-
teePublicKeyBytes as unknown as BufferSource,
|
|
107
|
-
{ name: "ECDH", namedCurve: "P-256" },
|
|
108
|
-
false,
|
|
109
|
-
[]
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// Derive Shared Secret
|
|
113
|
-
const sharedKey = await this.crypto.subtle.deriveKey(
|
|
114
|
-
{
|
|
115
|
-
name: "ECDH",
|
|
116
|
-
public: teeKey,
|
|
117
|
-
},
|
|
118
|
-
this.keyPair!.privateKey,
|
|
119
|
-
{
|
|
120
|
-
name: "AES-GCM",
|
|
121
|
-
length: 256,
|
|
122
|
-
},
|
|
123
|
-
false,
|
|
124
|
-
["encrypt", "decrypt"]
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
const iv = encryptedData.slice(0, 12);
|
|
128
|
-
const ciphertext = encryptedData.slice(12);
|
|
129
|
-
|
|
130
|
-
const decrypted = await this.crypto.subtle.decrypt(
|
|
131
|
-
{
|
|
132
|
-
name: "AES-GCM",
|
|
133
|
-
iv: iv,
|
|
134
|
-
},
|
|
135
|
-
sharedKey,
|
|
136
|
-
ciphertext as unknown as BufferSource
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return new Uint8Array(decrypted);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Helper to generate a valid random P-256 Public Key (65 bytes) for testing.
|
|
144
|
-
*/
|
|
145
|
-
async createMockValidatorKey(): Promise<Uint8Array> {
|
|
146
|
-
const pair = await this.crypto.subtle.generateKey(
|
|
147
|
-
{ name: "ECDH", namedCurve: "P-256" },
|
|
148
|
-
true,
|
|
149
|
-
["deriveKey"]
|
|
150
|
-
) as CryptoKeyPair;
|
|
151
|
-
const raw = await this.crypto.subtle.exportKey("raw", pair.publicKey);
|
|
152
|
-
return new Uint8Array(raw);
|
|
153
|
-
}
|
|
154
|
-
}
|