Skip to content

Commit

Permalink
Add hash verification
Browse files Browse the repository at this point in the history
  • Loading branch information
raducristianpopa committed Feb 20, 2024
1 parent 4920fb8 commit f60ef44
Showing 1 changed file with 34 additions and 17 deletions.
51 changes: 34 additions & 17 deletions src/background/paymentFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ interface KeyInformation {
keyId: string
}

interface InteractionParams {
interactRef: string
hash: string
}

type Headers = SignatureHeaders & Partial<ContentHeaders>

export class PaymentFlowService {
Expand All @@ -50,10 +55,9 @@ export class PaymentFlowService {
incomingPaymentUrlId: string
quoteUrlId: string
token: string
interactRef: string
manageUrl: string
amount: string | number
nonce: string | null
clientNonce: string | null

constructor(
sendingPaymentPointerUrl: string,
Expand Down Expand Up @@ -113,7 +117,7 @@ export class PaymentFlowService {
const { headers } = await signMessage(
{
name: 'sig1',
params: ['alg', 'keyid', 'created'],
params: ['keyid', 'created'],
fields: components,
key: signingKey,
},
Expand Down Expand Up @@ -144,7 +148,7 @@ export class PaymentFlowService {
const { privateKey, keyId } = await this.getPrivateKeyInformation()
this.client = await createAuthenticatedClient({
walletAddressUrl: this.sendingWalletAddress.id,
requestInterceptor: async config => {
authenticatedRequestInterceptor: async config => {
if (!config.method || !config.url) {
throw new Error('Cannot intercept request: url or method missing')
}
Expand Down Expand Up @@ -215,7 +219,7 @@ export class PaymentFlowService {
)

this.incomingPaymentUrlId = incomingPayment.id
this.nonce = crypto.randomUUID()
this.clientNonce = crypto.randomUUID()

// Revoke grant to avoid leaving users with unused, dangling grants.
await this.client.grant.cancel({
Expand Down Expand Up @@ -253,7 +257,7 @@ export class PaymentFlowService {
finish: {
method: 'redirect',
uri: 'http://localhost:3344',
nonce: this.nonce,
nonce: this.clientNonce,
},
},
},
Expand All @@ -265,10 +269,9 @@ export class PaymentFlowService {

// Q: Should this be moved to continuation polling?
// https://github.com/interledger/open-payments/issues/385
const interactRef = await this.confirmPayment(quoteAndOPGrant.interact.redirect)
const { interactRef, hash } = await this.confirmPayment(quoteAndOPGrant.interact.redirect)

// verify hash
//
await this.verifyInteractionHash(interactRef, quoteAndOPGrant.interact.finish, hash)

const continuation = await this.client.grant.continue(
{
Expand Down Expand Up @@ -378,30 +381,44 @@ export class PaymentFlowService {
return activeTabs[0].id
}

// TODO: Finish verify hash
async verifyHash(interactRef: string, interactNonce: string, hash: string): Promise<void> {
const url = this.sendingWalletAddress.authServer
// Fourth item- https://rafiki.dev/concepts/open-payments/grant-interaction/#endpoints
async verifyInteractionHash(
interactRef: string,
interactNonce: string,
hash: string,
): Promise<void> {
// TODO: Update based on Rafiki Call discussion
// The interaction hash is not correctly calculated within Rafiki at the momenet in certain scenarios
const grantEndpoint = new URL(this.sendingWalletAddress.authServer).origin + '/'
const data = new TextEncoder().encode(
`${this.nonce}\n${interactNonce}\n${interactRef}\n${url}/`,
`${this.clientNonce}\n${interactNonce}\n${interactRef}\n${grantEndpoint}`,
)

// Reset client nonce
this.clientNonce = null

const digest = await crypto.subtle.digest('SHA-256', data)
const calculatedHash = btoa(String.fromCharCode.apply(null, new Uint8Array(digest)))
if (calculatedHash !== hash) throw new Error('Invalid interaction hash')
}

private async confirmPayment(url: string): Promise<string> {
private async confirmPayment(url: string): Promise<InteractionParams> {
const currentTabId = await this.getCurrentActiveTabId()

return await new Promise<string>(res => {
return await new Promise(res => {
if (url) {
tabs.create({ url }).then(tab => {
if (tab.id) {
tabs.onUpdated.addListener((tabId, changeInfo) => {
try {
const tabUrl = new URL(changeInfo.url || '')
const interactRef = tabUrl.searchParams.get('interact_ref')
const hash = tabUrl.searchParams.get('hash')

if (tabId === tab.id && interactRef) {
if (tabId === tab.id && interactRef && hash) {
tabs.update(currentTabId, { active: true })
tabs.remove(tab.id)
res(interactRef)
res({ interactRef, hash })
}
} catch (e) {
// do nothing
Expand Down

0 comments on commit f60ef44

Please sign in to comment.