Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/veria-305-session-voucher-chain-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Bound session voucher verification to stored channel chain metadata.
30 changes: 30 additions & 0 deletions src/tempo/server/Session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,36 @@ describe.runIf(isLocalnet)('session', () => {
expect(ch!.highestVoucherAmount).toBe(2000000n)
})

test('rejects voucher signed for route escrow instead of stored channel escrow', async () => {
const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
const server = createServer()
await openServerChannel(server, channelId, serializedTransaction)

const routeEscrow = '0x0000000000000000000000000000000000000999' as Address
const signature = await signVoucher(
client,
payer,
{ channelId, cumulativeAmount: 2000000n },
routeEscrow,
chain.id,
)

await expect(
server.verify({
credential: {
challenge: makeChallenge({ id: 'route-escrow-replay', channelId }),
payload: {
action: 'voucher' as const,
channelId,
cumulativeAmount: '2000000',
signature,
},
},
request: makeRequest({ escrowContract: routeEscrow }),
}),
).rejects.toThrow(InvalidSignatureError)
})

test('rejects non-increasing voucher replay', async () => {
const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n)
const server = createServer()
Expand Down
23 changes: 8 additions & 15 deletions src/tempo/server/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,8 @@ async function verifyAndAcceptVoucher(parameters: {
channelId: Hex
voucher: SignedVoucher
onChain: OnChainChannel
methodDetails: SessionMethodDetails
}): Promise<SessionReceipt> {
const { store, minVoucherDelta, challenge, channel, channelId, voucher, onChain, methodDetails } =
parameters
const { store, minVoucherDelta, challenge, channel, channelId, voucher, onChain } = parameters

if (onChain.finalized) {
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
Expand Down Expand Up @@ -568,8 +566,8 @@ async function verifyAndAcceptVoucher(parameters: {
}

const isValid = await verifyVoucher(
methodDetails.escrowContract,
methodDetails.chainId,
channel.escrowContract,
channel.chainId,
voucher,
channel.authorizedSigner,
)
Expand Down Expand Up @@ -848,11 +846,7 @@ async function handleVoucher(

const onChain = await (async () => {
if (isStale) {
const onChainChannel = await getOnChainChannel(
client,
methodDetails.escrowContract,
channelId,
)
const onChainChannel = await getOnChainChannel(client, channel.escrowContract, channelId)
lastOnChainVerified.set(channelId, Date.now())
// Persist closeRequestedAt so the cached path detects force-close
// between re-queries.
Expand Down Expand Up @@ -883,7 +877,6 @@ async function handleVoucher(
channelId,
voucher,
onChain,
methodDetails,
})
}

Expand All @@ -910,7 +903,7 @@ async function handleClose(

const voucher = parseVoucherFromPayload(channelId, payload.cumulativeAmount, payload.signature)

const onChain = await getOnChainChannel(client, methodDetails.escrowContract, channelId)
const onChain = await getOnChainChannel(client, channel.escrowContract, channelId)

if (onChain.finalized) {
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
Expand All @@ -936,8 +929,8 @@ async function handleClose(
}

const isValid = await verifyVoucher(
methodDetails.escrowContract,
methodDetails.chainId,
channel.escrowContract,
channel.chainId,
voucher,
channel.authorizedSigner,
)
Expand Down Expand Up @@ -972,7 +965,7 @@ async function handleClose(
sender: account?.address ?? client.account?.address,
})

txHash = await closeOnChain(client, methodDetails.escrowContract, voucher, {
txHash = await closeOnChain(client, channel.escrowContract, voucher, {
...(feePayer && account ? { feePayer, account } : { account }),
candidateFeeTokens: [channel.token],
})
Expand Down
Loading