Skip to content

Commit e063168

Browse files
committed
fix(network/auth): respect handshake responders when preparing keysets and pricing
- limit JIT keyset creation to nodes that actually returned server keys and filter price data to match, avoiding encryption attempts against missing nodes (packages/networks/src/networks/vNaga/shared/factories/BaseModuleFactory.ts) - adjust PKP and custom auth adapters to derive node pricing only from responding nodes and fail fast if they can’t satisfy the threshold, preventing session-sign requests from targeting unreachable URLs (packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts, getCustomAuthContextAdapter.ts)
1 parent 3d1494a commit e063168

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

packages/auth/src/lib/AuthManager/authAdapters/getCustomAuthContextAdapter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,19 @@ export async function getCustomAuthContextAdapter(
5656
const threshold = handshakeResult.threshold;
5757

5858
// TODO: ❗️THIS IS NOT TYPED - we have to fix this! (This can only be in Naga)
59+
const respondingUrlSet = new Set(Object.keys(handshakeResult.serverKeys));
60+
const respondingNodePrices = nodePrices.filter((item: { url: string }) =>
61+
respondingUrlSet.has(item.url)
62+
);
63+
64+
if (respondingNodePrices.length < threshold) {
65+
throw new Error(
66+
`Not enough handshake nodes to satisfy threshold. Threshold: ${threshold}, responding nodes: ${respondingNodePrices.length}`
67+
);
68+
}
69+
5970
const nodeUrls = litClientCtx.getMaxPricesForNodeProduct({
60-
nodePrices: nodePrices,
71+
nodePrices: respondingNodePrices,
6172
userMaxPrice: litClientCtx.getUserMaxPrice({
6273
product: 'LIT_ACTION',
6374
}),

packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,19 @@ export async function getPkpAuthContextAdapter(
133133
const threshold = handshakeResult.threshold;
134134

135135
// TODO: ❗️THIS IS NOT TYPED - we have to fix this! (This can only be in Naga)
136+
const respondingUrlSet = new Set(Object.keys(handshakeResult.serverKeys));
137+
const respondingNodePrices = nodePrices.filter((item: { url: string }) =>
138+
respondingUrlSet.has(item.url)
139+
);
140+
141+
if (respondingNodePrices.length < threshold) {
142+
throw new Error(
143+
`Not enough handshake nodes to satisfy threshold. Threshold: ${threshold}, responding nodes: ${respondingNodePrices.length}`
144+
);
145+
}
146+
136147
const nodeUrls = litClientCtx.getMaxPricesForNodeProduct({
137-
nodePrices: nodePrices,
148+
nodePrices: respondingNodePrices,
138149
userMaxPrice: litClientCtx.getUserMaxPrice({
139150
product: 'LIT_ACTION',
140151
}),

packages/networks/src/networks/vNaga/shared/factories/BaseModuleFactory.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,17 +450,49 @@ export function createBaseModule<T, M>(config: BaseModuleConfig<T, M>) {
450450
): Promise<NagaJitContext> => {
451451
const keySet: KeySet = {};
452452

453-
for (const url of connectionInfo.bootstrapUrls) {
453+
const respondingUrls = Object.keys(handshakeResult.serverKeys);
454+
const respondingUrlSet = new Set(respondingUrls);
455+
456+
if (respondingUrls.length === 0) {
457+
throw new Error(
458+
`Handshake response did not include any node identity keys. Received handshake result: ${JSON.stringify(
459+
handshakeResult
460+
)}`
461+
);
462+
}
463+
464+
for (const url of respondingUrls) {
465+
const serverKey = handshakeResult.serverKeys[url];
466+
467+
if (!serverKey || !serverKey.nodeIdentityKey) {
468+
throw new Error(
469+
`Handshake response missing node identity key for node ${url}. Received handshake result: ${JSON.stringify(
470+
handshakeResult
471+
)}`
472+
);
473+
}
474+
454475
keySet[url] = {
455476
publicKey: hexToBytes(
456477
HexPrefixedSchema.parse(
457-
handshakeResult.serverKeys[url].nodeIdentityKey
478+
serverKey.nodeIdentityKey
458479
) as `0x${string}`
459480
),
460481
secretKey: nacl.box.keyPair().secretKey,
461482
};
462483
}
463484

485+
const missingUrls = connectionInfo.bootstrapUrls.filter(
486+
(url) => !keySet[url]
487+
);
488+
489+
if (missingUrls.length > 0) {
490+
_logger.warn(
491+
{ missingUrls },
492+
'Some bootstrap URLs did not complete the handshake; proceeding with responding nodes only'
493+
);
494+
}
495+
464496
// Use read-only account for viewing PKPs
465497
const account = privateKeyToAccount(DEV_PRIVATE_KEY);
466498

@@ -473,7 +505,25 @@ export function createBaseModule<T, M>(config: BaseModuleConfig<T, M>) {
473505
account
474506
);
475507

476-
return { keySet, nodePrices };
508+
const filteredNodePrices = nodePrices.filter((price) =>
509+
respondingUrlSet.has(price.url)
510+
);
511+
512+
if (filteredNodePrices.length === 0) {
513+
throw new Error('Unable to resolve price data for responding handshake nodes');
514+
}
515+
516+
if (filteredNodePrices.length !== nodePrices.length) {
517+
const excludedByPriceFeed = nodePrices
518+
.filter((price) => !respondingUrlSet.has(price.url))
519+
.map((price) => price.url);
520+
_logger.warn(
521+
{ excludedByPriceFeed },
522+
'Price feed included nodes that did not complete the handshake; excluding them from pricing context'
523+
);
524+
}
525+
526+
return { keySet, nodePrices: filteredNodePrices };
477527
},
478528

479529
/**

0 commit comments

Comments
 (0)