-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reject Swap
approvals with foreign claim addresses
#821
Reject Swap
approvals with foreign claim addresses
#821
Conversation
7e91a09
to
60a2982
Compare
@@ -3,17 +3,10 @@ import { SwapView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/ | |||
import { fromBaseUnitAmount, joinLoHiAmount } from '@penumbra-zone/types/src/amount'; | |||
import { uint8ArrayToBase64 } from '@penumbra-zone/types/src/base64'; | |||
import { ActionDetails } from './action-details'; | |||
import { AddressView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; | |||
import { AddressViewComponent } from './address-view'; | |||
|
|||
export const SwapViewComponent = ({ value }: { value: SwapView }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I'm not sure we should hide it. The claiming part of a swap seems so integral to penumbra. I don't think it hurts to show it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm OK. I was basing it on Henry's comment in #548:
Adding this check means that we don't need to display the claim address to the user in the approval view.
That said, I think the main point of that ticket was to ensure the claim address belonged to the current user, so I'll add it back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then up to you! 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd prefer to hide it. The user shouldn't ever have to know about the claim address, it should just do the right thing automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a follow-up to this PR, we should also change the swap rendering to show formatted values, so that as a user I see something like "Swap 1234 UM => TestUsd"
@@ -35,7 +35,7 @@ export interface ShieldedPoolMock { | |||
} | |||
|
|||
export interface ViewServerMock { | |||
fullViewingKey?: Mock; | |||
fullViewingKey?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have always been a string
Swap
approvals with foreign claim addressesSwap
approvals with foreign claim addresses
minor nit; to maintain consistency, can we deactivate the swap button until the user enters a non-zero amount, similar to how we handle send transactions? |
packages/router/src/grpc/custody/authorize/assert-valid-swaps.ts
Outdated
Show resolved
Hide resolved
we might want to consider displaying a more stateful error message describing why the swap transaction was canceled down the line. |
@jessepinho this is out of scope for this PR, but maybe we should separately consider adding a minor delay between swap and swap claim toasts, or slight offset in the UI? I'm not sure this is the most intuitive UX, because the view changes so quickly between transactions that I find myself missing the display of the first transaction. Curious what you think. |
throw new ConnectError( | ||
"Tried to initiate a swap with a claim address belonging to a different user. This means that, when the swap is claimed, the funds would go to someone else's address, not yours. This should never happen. The website you are using may be trying to steal your funds!", | ||
Code.PermissionDenied, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like quite a helpful error message. Can we show this in our tooltip instead of it being swallowed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is related to @TalDerei 's comment above:
we might want to consider displaying a more stateful error message describing why the swap transaction was canceled down the line.
Here's the issue: minifront will never try to steal the user's funds, so we never need to show this message to minifront users.
We should show this message to users on other sites who try to steal the user's funds, but obviously, a malicious website isn't going to proactively show this error to people. But if they're trying to steal people's funds, they'll presumably test the stealing-people's-funds functionality first, and they'll realize that this error shows up, at which point they'll either A) give up, or B) at least suppress this error.
So I'm not actually sure how useful this error is?
The only way it could be useful is if we could show this error in the transaction approval popup, which the malicious website has no control over. Not sure if this is possible, though, since this error is thrown inside an RPC method implementation. What if we merge this as-is for now, and then revisit this question in a separate ticket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created separate ticket to limit scope
packages/router/src/grpc/custody/authorize/assert-valid-swaps.ts
Outdated
Show resolved
Hide resolved
@@ -3,17 +3,10 @@ import { SwapView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/ | |||
import { fromBaseUnitAmount, joinLoHiAmount } from '@penumbra-zone/types/src/amount'; | |||
import { uint8ArrayToBase64 } from '@penumbra-zone/types/src/base64'; | |||
import { ActionDetails } from './action-details'; | |||
import { AddressView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; | |||
import { AddressViewComponent } from './address-view'; | |||
|
|||
export const SwapViewComponent = ({ value }: { value: SwapView }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I'm not sure we should hide it. The claiming part of a swap seems so integral to penumbra. I don't think it hurts to show it.
): AddressIndex | undefined => { | ||
const res = is_controlled_address(fullViewingKey, address) as JsonValue; | ||
const res = is_controlled_address(fullViewingKey, bech32Address(address)) as JsonValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we should consider changing the rust api as well.
is_controlled_address()
should return a boolean
get_address_index_by_address()
should return Result (throws if not able to)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm less comfortable with the Rust side of things — would you be OK with making that change in a follow-on PR? (For that matter, we probably only need get_address_index_by_address
in Rust, since is_controlled_address
would just be calling get_address_index_by_address
anyway.)
And in the meantime, could you give this PR a 👍🏻 since the other items are resolved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
packages/router/src/grpc/custody/authorize/assert-valid-swaps.ts
Outdated
Show resolved
Hide resolved
f5a720d
to
e113b86
Compare
Could you please create separate tickets for these? |
assertSwapClaimAddressesBelongToCurrentUser(req.plan, address => | ||
isControlledAddress(fullViewingKey, address), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get a sense we will be doing many more of these guards in the future. I wonder if there is a more general pattern we can have for this 🤔
Maybe not for this PR, unless you're feeling inspired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to think of these as policies, and have one function per policy that approves or denies, and call each function in sequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can dig it 👌🏻
): AddressIndex | undefined => { | ||
const res = is_controlled_address(fullViewingKey, address) as JsonValue; | ||
const res = is_controlled_address(fullViewingKey, bech32Address(address)) as JsonValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
From #548:
In this PR
CustodyService
'sauthorize
implementation, assert that transaction swaps (if there are any) contain claim addresses controlled by the current user.authorize
implementation.isControlledAddress
helper togetAddressIndexByAddress
to better represent what it does.isControlledAddress
helper that actually returns a boolean.<SwapViewComponent />
.Manual testing
The code:
The result:
Screen.Recording.2024-03-22.at.7.19.09.PM.mov
Note that it just says "Transaction canceled," rather than showing the error message about the website stealing your funds. That's because it throws a
ConnectError
withCode.PermissionDenied
, which minifront catches and displays as "Transaction canceled."Closes #548