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
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,25 @@ export const SignMessageDrawer: React.FC = () => {
className={styles.customTextArea}
/>
</div>
<div className={styles.inputGroup}>
<Flex justifyContent="space-between" alignItems="center">
<Text.Body.Normal weight="$medium" data-testid={'result-message-raw-key-label'}>
{t('core.signMessage.rawKey')}
</Text.Body.Normal>
{renderCopyToClipboard({
text: signatureObject.rawKey,
handleCopy,
t,
testId: 'raw-public-key-copy-to-clipboard-button'
})}
</Flex>
<TextArea
value={signatureObject.rawKey}
dataTestId="sign-message-raw-key"
rows={4}
className={styles.customTextArea}
/>
</div>
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import { useAnalyticsContext } from '@providers';
import { useSecrets } from '@lace/core';
import { parseError } from '@src/utils/parse-error';

type SignatureObject = Cip30DataSignature & { rawKey: Wallet.Crypto.Ed25519PublicKeyHex };

interface SignMessageState {
usedAddresses: { address: string; id: number }[];
usedAddresses: { address: string; id: number; type: 'payment' | 'stake' }[];
isSigningInProgress: boolean;
signatureObject: Cip30DataSignature | undefined;
signatureObject: SignatureObject | undefined;
error: string;
errorObj?: Error;
hardwareWalletError: string;
Expand All @@ -31,19 +33,20 @@ export const useSignMessageState = (): SignMessageState => {

const { inMemoryWallet, isHardwareWallet } = useWalletStore();
const [isSigningInProgress, setIsSigningInProgress] = useState(false);
const [signature, setSignature] = useState<Cip30DataSignature>();
const [signatureObject, setSignatureObject] = useState<SignatureObject | undefined>();
const [error, setError] = useState<string>('');
const [errorObj, setErrorObj] = useState<Error | undefined>();
const [hardwareWalletError, setHardwareWalletError] = useState<string>('');

const addresses = useObservable(inMemoryWallet?.addresses$);
const rewardAccounts = useObservable(inMemoryWallet?.delegation.rewardAccounts$);

const resetSigningState = useCallback(() => {
setIsSigningInProgress(false);
setError('');
setHardwareWalletError('');
setErrorObj(undefined);
setSignature(undefined);
setSignatureObject(undefined);
}, []);

const performSigning = useCallback(
Expand All @@ -57,17 +60,26 @@ export const useSignMessageState = (): SignMessageState => {
isHardwareWallet
? analytics.sendEventToPostHog(PostHogAction.SignMessageAskingHardwareWalletInteraction)
: analytics.sendEventToPostHog(PostHogAction.SignMessageAskingForPassword);

// Determine if the address is a payment address or reward account
const isRewardAccount = Wallet.Cardano.isRewardAccount(address);

const signatureGenerated = await withSignDataConfirmation(
async () =>
await inMemoryWallet.signData({
signWith: address as Wallet.Cardano.PaymentAddress,
signWith: isRewardAccount
? (address as Wallet.Cardano.RewardAccount)
: (address as Wallet.Cardano.PaymentAddress),
payload
}),
!isHardwareWallet ? password : {},
clearSecrets
);

setSignature(signatureGenerated);
setSignatureObject({
...signatureGenerated,
rawKey: Wallet.util.coseKeyToRaw(signatureGenerated.key)
});
} catch (signingError: unknown) {
logger.error('Error signing message:', signingError);
setErrorObj(parseError(signingError));
Expand All @@ -92,16 +104,25 @@ export const useSignMessageState = (): SignMessageState => {
[isHardwareWallet, analytics, clearSecrets, inMemoryWallet, t]
);

const usedAddresses =
addresses?.map((address, index) => ({
const usedAddresses = [
// Payment addresses
...(addresses?.map((address, index) => ({
address: address.address.toString(),
id: index
})) || [];
id: index,
type: 'payment' as const
})) || []),
// Reward accounts (stake addresses)
...(rewardAccounts?.map((rewardAccount, index) => ({
address: rewardAccount.address,
id: addresses?.length ? addresses.length + index : index,
type: 'stake' as const
})) || [])
];

return {
usedAddresses,
isSigningInProgress,
signatureObject: signature,
signatureObject,
error,
errorObj,
hardwareWalletError,
Expand Down
19 changes: 19 additions & 0 deletions packages/cardano/src/wallet/util/cose-key-to-raw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { COSEKey, Label, Int } from '@emurgo/cardano-message-signing-nodejs';
import type { Cip30DataSignature } from '@cardano-sdk/dapp-connector';
import { Ed25519PublicKeyHex } from '@cardano-sdk/crypto';

export const coseKeyToRaw = (coseKey: Cip30DataSignature['key']): Ed25519PublicKeyHex => {
const parsedKey = COSEKey.from_bytes(Buffer.from(coseKey, 'hex'));
// eslint-disable-next-line no-magic-numbers
const rawKeyLabelX = Label.new_int(Int.new_i32(-2));
const keyCbor = parsedKey.header(rawKeyLabelX);
const keyBytes = keyCbor?.as_bytes();
keyCbor?.free();
rawKeyLabelX.free();
parsedKey.free();
if (!keyBytes) {
throw new Error('expected COSE key with label "x" (-2)');
}
// eslint-disable-next-line new-cap
return Ed25519PublicKeyHex(Buffer.from(keyBytes).toString('hex'));
};
1 change: 1 addition & 0 deletions packages/cardano/src/wallet/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './drep';
export * from './voter';
export * from './derive-ed25519-key-hash-from-bip32-public-key';
export * from './is-nft';
export * from './cose-key-to-raw';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import '../../styles/theme.scss';
@import '../../../../../common/src/ui/styles/abstracts/mixins';

.overlay {
z-index: 10 !important;
Expand Down Expand Up @@ -36,6 +37,12 @@
color: var(--text-color-primary, #000000) !important;
}

.addressDropdownMenu {
@include scroll-bar-style;
max-height: 40vh;
overflow: auto;
}

:global(.ant-dropdown-menu) {
background-color: var(--bg-color-body, #ffffff) !important;
border: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid-grey, #333333)) !important;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
export interface AddressSchema {
id: number;
address: string;
type?: 'payment' | 'stake';
}

export type WalletOwnAddressDropdownProps = {
Expand Down Expand Up @@ -40,10 +41,25 @@ export const WalletOwnAddressDropdown = ({
}
};

const items: MenuProps['items'] = addresses.map((address) => ({
key: address.id.toString(),
label: addEllipsis(address.address, FIRST_PART_ADDRESS_LENGTH, LAST_PART_ADDRESS_LENGTH)
}));
const getAddressTypeDisplay = (addressType?: 'payment' | 'stake'): string => {
if (addressType === 'stake') {
return ` (${t('core.signMessage.addressTypeStake')})`;
}
if (addressType === 'payment') {
return ` (${t('core.signMessage.addressTypePayment')})`;
}
return '';
};

const items: MenuProps['items'] = addresses.map((address) => {
const shortenedAddress = addEllipsis(address.address, FIRST_PART_ADDRESS_LENGTH, LAST_PART_ADDRESS_LENGTH);
const addressType = getAddressTypeDisplay(address.type);

return {
key: address.id.toString(),
label: `${shortenedAddress}${addressType}`
};
});

const menuProps: MenuProps = {
items,
Expand All @@ -53,7 +69,11 @@ export const WalletOwnAddressDropdown = ({
return (
<Dropdown
menu={menuProps}
dropdownRender={(menus) => <div data-testid="address-dropdown-menu">{menus}</div>}
dropdownRender={(menus) => (
<div data-testid="address-dropdown-menu" className={styles.addressDropdownMenu}>
{menus}
</div>
)}
trigger={['click']}
data-testid="address-menu"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class MessageSigningInputDrawerAssert {
async assertSeeSelectedAddress(expectedAddress: string) {
await MessageSigningInputDrawer.selectAddressButton.waitForDisplayed();
const actualAddress = await MessageSigningInputDrawer.selectAddressButton.getText();
expect(actualAddress).to.equal(expectedAddress);
const normalizedExpectedAddress = expectedAddress.replace(/\s*\((Payment|Stake)\)$/, '');
expect(actualAddress).to.equal(normalizedExpectedAddress);
}
}

Expand Down
7 changes: 5 additions & 2 deletions packages/translation/src/lib/translations/core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@
"core.authorizeDapp.seeWalletUtxo": "See your wallet UTXOs",
"core.authorizeDapp.title": "Allow this site to",
"core.authorizeDapp.warning": "Only connect to trusted DApps",
"core.signMessage.title": "Message signing",
"core.signMessage.title": "Sign message",
"core.signMessage.instructions": "Type or paste the message to be signed",
"core.signMessage.subtitle": "Your message will be encrypted and can be used after that",
"core.signMessage.subtitle": "Your message will be cryptographically signed to prove authenticity",
"core.signMessage.addressLabel": "Address",
"core.signMessage.selectAddress": "Select an address to use",
"core.signMessage.messageLabel": "Message to sign",
Expand All @@ -231,12 +231,15 @@
"core.signMessage.successTitle": "All done!",
"core.signMessage.signature": "Signature",
"core.signMessage.key": "Public Key",
"core.signMessage.rawKey": "Public Key (Glacier Drop Compatible)",
"core.signMessage.nextButton": "Next",
"core.signMessage.closeButton": "Close",
"core.signMessage.cancelButton": "Cancel",
"core.signMessage.signButton": "Sign message",
"core.signMessage.signWithHardwareWalletButton": "Sign message with hardware wallet",
"core.signMessage.hardwareWalletNotConnected": "Make sure your hardware wallet is connected and unlocked.",
"core.signMessage.addressTypePayment": "Payment",
"core.signMessage.addressTypeStake": "Stake",
"core.Burn.title": "Confirm transaction",
"core.coinInputSelection.assetSelection": "Select tokens or NFTs",
"core.coinInputSelection.nfts": "NFTs",
Expand Down
Loading