-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes #1051
- Loading branch information
Showing
32 changed files
with
2,172 additions
and
1,358 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ExternalMethods, MESSAGE_SOURCE, SignatureResponseMessage } from '@shared/message-types'; | ||
import { logger } from '@shared/logger'; | ||
import { SignatureData } from '@shared/crypto/sign-message'; | ||
|
||
export const finalizeMessageSignature = ( | ||
requestPayload: string, | ||
tabId: number, | ||
data: SignatureData | string | ||
) => { | ||
try { | ||
const responseMessage: SignatureResponseMessage = { | ||
source: MESSAGE_SOURCE, | ||
method: ExternalMethods.signatureResponse, | ||
payload: { | ||
signatureRequest: requestPayload, | ||
signatureResponse: data, | ||
}, | ||
}; | ||
chrome.tabs.sendMessage(tabId, responseMessage); | ||
window.close(); | ||
} catch (error) { | ||
logger.debug('Failed to get Tab ID for message signature request:', requestPayload); | ||
throw new Error( | ||
'Your message was signed, but we lost communication with the app you started with.' | ||
); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { Account, getAppPrivateKey } from '@stacks/wallet-sdk'; | ||
import { SignaturePayload } from '@stacks/connect'; | ||
import { decodeToken, TokenVerifier } from 'jsontokens'; | ||
import { getPublicKeyFromPrivate } from '@stacks/encryption'; | ||
import { getAddressFromPrivateKey, TransactionVersion } from '@stacks/transactions'; | ||
|
||
export function getPayloadFromToken(requestToken: string) { | ||
const token = decodeToken(requestToken); | ||
return token.payload as unknown as SignaturePayload; | ||
} | ||
|
||
function getTransactionVersionFromRequest(signature: SignaturePayload) { | ||
const { network } = signature; | ||
if (!network) return TransactionVersion.Mainnet; | ||
if (![TransactionVersion.Mainnet, TransactionVersion.Testnet].includes(network.version)) { | ||
throw new Error('Invalid network version provided'); | ||
} | ||
return network.version; | ||
} | ||
|
||
const UNAUTHORIZED_SIGNATURE_REQUEST = | ||
'The signature request provided is not signed by this wallet.'; | ||
/** | ||
* Verify a transaction request. | ||
* A transaction request is a signed JWT that is created on an app, | ||
* via `@stacks/connect`. The private key used to sign this JWT is an | ||
* `appPrivateKey`, which an app can get from authentication. | ||
* | ||
* The payload in this JWT can include an `stxAddress`. This indicates the | ||
* 'default' STX address that should be used to sign this transaction. This allows | ||
* the wallet to use the same account to sign a transaction as it used to sign | ||
* in to the app. | ||
* | ||
* This JWT is invalidated if: | ||
* - The JWT is not signed properly | ||
* - The public key used to sign this tx request does not match an `appPrivateKey` | ||
* for any of the accounts in this wallet. | ||
* - The `stxAddress` provided in the payload does not match an STX address | ||
* for any of the accounts in this wallet. | ||
* | ||
* @returns The decoded and validated `SignaturePayload` | ||
* @throws if the transaction request is invalid | ||
*/ | ||
interface VerifySignatureRequestArgs { | ||
requestToken: string; | ||
accounts: Account[]; | ||
appDomain: string; | ||
} | ||
export async function verifySignatureRequest({ | ||
requestToken, | ||
accounts, | ||
appDomain, | ||
}: VerifySignatureRequestArgs): Promise<SignaturePayload> { | ||
const token = decodeToken(requestToken); | ||
const signature = token.payload as unknown as SignaturePayload; | ||
const { publicKey, stxAddress } = signature; | ||
const txVersion = getTransactionVersionFromRequest(signature); | ||
const verifier = new TokenVerifier('ES256k', publicKey); | ||
const isSigned = await verifier.verifyAsync(requestToken); | ||
if (!isSigned) { | ||
throw new Error('Signature request is not signed'); | ||
} | ||
const foundAccount = accounts.find(account => { | ||
const appPrivateKey = getAppPrivateKey({ | ||
account, | ||
appDomain, | ||
}); | ||
const appPublicKey = getPublicKeyFromPrivate(appPrivateKey); | ||
if (appPublicKey !== publicKey) return false; | ||
if (!stxAddress) return true; | ||
const accountStxAddress = getAddressFromPrivateKey(account.stxPrivateKey, txVersion); | ||
if (stxAddress !== accountStxAddress) return false; | ||
return true; | ||
}); | ||
if (!foundAccount) { | ||
throw new Error(UNAUTHORIZED_SIGNATURE_REQUEST); | ||
} | ||
return signature; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
src/app/pages/signature-request/components/hash-drawer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Stack, Flex, Box, Text } from '@stacks/ui'; | ||
import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; | ||
import { useState } from 'react'; | ||
|
||
interface ShowHashButtonProps { | ||
expanded: boolean; | ||
} | ||
const ShowHashButton = (props: ShowHashButtonProps) => { | ||
const { expanded } = props; | ||
return <Box as={expanded ? FiChevronDown : FiChevronUp} mr="tight" size="20px" />; | ||
}; | ||
|
||
interface HashDrawerProps { | ||
hash: string; | ||
} | ||
|
||
export function HashDrawer(props: HashDrawerProps): JSX.Element | null { | ||
const { hash } = props; | ||
const [showHash, setShowHash] = useState(false); | ||
const [displayHash, setDisplayHash] = useState(hash); | ||
return ( | ||
<Stack py="tight" px="tight" spacing="loose"> | ||
<Flex marginBottom="0px !important"> | ||
<Text display="block" fontSize={0}> | ||
{showHash ? 'Hide hash' : 'Show hash'} | ||
</Text> | ||
<Box | ||
_hover={{ cursor: 'pointer' }} | ||
style={{ marginLeft: 'auto' }} | ||
onClick={() => { | ||
setDisplayHash(showHash ? '' : hash); | ||
setShowHash(!showHash); | ||
}} | ||
> | ||
<ShowHashButton expanded={showHash} /> | ||
</Box> | ||
</Flex> | ||
<Box | ||
transition="all 0.65s cubic-bezier(0.23, 1, 0.32, 1)" | ||
py={showHash ? 'tight' : 'none'} | ||
height={showHash ? '100%' : '0'} | ||
visibility={showHash ? 'visible' : 'hidden'} | ||
> | ||
<Stack spacing="base-tight"> | ||
<Text | ||
display="block" | ||
color="#74777D" | ||
fontSize={2} | ||
fontWeight={500} | ||
lineHeight="1.6" | ||
wordBreak="break-all" | ||
fontFamily={'Fira Code'} | ||
> | ||
{displayHash} | ||
</Text> | ||
</Stack> | ||
</Box> | ||
</Stack> | ||
); | ||
} |
51 changes: 51 additions & 0 deletions
51
src/app/pages/signature-request/components/message-box.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { color, Stack, Text } from '@stacks/ui'; | ||
import { sha256 } from 'sha.js'; | ||
import { HashDrawer } from './hash-drawer'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
interface MessageBoxProps { | ||
message: string; | ||
} | ||
export function MessageBox(props: MessageBoxProps): JSX.Element | null { | ||
const { message } = props; | ||
const [hash, setHash] = useState<string | undefined>(); | ||
useEffect(() => { | ||
setHash(new sha256().update(message).digest('hex')); | ||
}, [message]); | ||
|
||
if (!message) return null; | ||
|
||
return ( | ||
<> | ||
<Stack minHeight={'260px'}> | ||
<Stack | ||
border="4px solid" | ||
borderColor={color('border')} | ||
borderRadius="12px" | ||
backgroundColor={color('border')} | ||
> | ||
<Stack | ||
py="base-tight" | ||
px="base-loose" | ||
spacing="loose" | ||
borderRadius="12px" | ||
backgroundColor={'white'} | ||
> | ||
<Stack spacing="base-tight"> | ||
<Text | ||
display="block" | ||
fontSize={2} | ||
fontWeight={500} | ||
lineHeight="1.6" | ||
wordBreak="break-all" | ||
> | ||
{message} | ||
</Text> | ||
</Stack> | ||
</Stack> | ||
{hash ? <HashDrawer hash={hash} /> : null} | ||
</Stack> | ||
</Stack> | ||
</> | ||
); | ||
} |
31 changes: 31 additions & 0 deletions
31
src/app/pages/signature-request/components/network-row.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { whenChainId } from '@app/common/transactions/transaction-utils'; | ||
import { SpaceBetween } from '@app/components/space-between'; | ||
import { Caption } from '@app/components/typography'; | ||
import { StacksNetwork } from '@stacks/network'; | ||
import { ChainID } from '@stacks/transactions'; | ||
import { Box } from '@stacks/ui'; | ||
|
||
interface NetworkRowProps { | ||
network: StacksNetwork; | ||
} | ||
export function NetworkRow(props: NetworkRowProps): JSX.Element | null { | ||
const { network } = props; | ||
|
||
return ( | ||
<Box spacing="base"> | ||
<SpaceBetween position="relative"> | ||
<Box alignItems="center" isInline> | ||
<Caption>No fees will be incured</Caption> | ||
</Box> | ||
<Caption> | ||
<span> | ||
{whenChainId(network.chainId)({ | ||
[ChainID.Testnet]: 'Testnet', | ||
[ChainID.Mainnet]: 'Mainnet', | ||
})} | ||
</span> | ||
</Caption> | ||
</SpaceBetween> | ||
</Box> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { memo } from 'react'; | ||
import { Stack } from '@stacks/ui'; | ||
|
||
import { useCurrentNetwork } from '@app/common/hooks/use-current-network'; | ||
import { addPortSuffix, getUrlHostname } from '@app/common/utils'; | ||
import { Caption, Title } from '@app/components/typography'; | ||
import { getPayloadFromToken } from '@app/common/signature/requests'; | ||
import { useSignatureRequestSearchParams } from '@app/store/signatures/requests.hooks'; | ||
|
||
function PageTopBase(): JSX.Element | null { | ||
const network = useCurrentNetwork(); | ||
const { origin, requestToken } = useSignatureRequestSearchParams(); | ||
if (!requestToken) return null; | ||
const signatureRequest = getPayloadFromToken(requestToken); | ||
if (!signatureRequest) return null; | ||
|
||
const appName = signatureRequest?.appDetails?.name; | ||
const originAddition = origin ? ` (${getUrlHostname(origin)})` : ''; | ||
const testnetAddition = network.isTestnet | ||
? ` using ${getUrlHostname(network.url)}${addPortSuffix(network.url)}` | ||
: ''; | ||
const caption = appName ? `Requested by "${appName}"${originAddition}${testnetAddition}` : null; | ||
|
||
return ( | ||
<Stack pt="extra-loose" spacing="base"> | ||
<Title fontWeight="bold" as="h1"> | ||
Sign Message | ||
</Title> | ||
{caption && <Caption wordBreak="break-word">{caption}</Caption>} | ||
</Stack> | ||
); | ||
} | ||
|
||
export const PageTop = memo(PageTopBase); |
Oops, something went wrong.