diff --git a/keyserver/src/responders/landing-handler.js b/keyserver/src/responders/landing-handler.js --- a/keyserver/src/responders/landing-handler.js +++ b/keyserver/src/responders/landing-handler.js @@ -13,6 +13,7 @@ isValidPrimaryIdentityPublicKey, isValidSIWENonce, isValidSIWEMessageType, + isValidSIWEMessageString, } from 'lib/utils/siwe-utils.js'; import { getMessageForException } from './utils.js'; @@ -152,6 +153,25 @@ return; } const siweMessageType = ((siweMessageTypeRawString: any): SIWEMessageType); + const siweMessageToSign = req.header('siwe-message-to-sign'); + + if ( + siweMessageToSign !== null && + siweMessageToSign !== undefined && + !isValidSIWEMessageString(decodeURIComponent(siweMessageToSign)) + ) { + res.status(400).send({ + message: 'Invalid siwe message to sign.', + }); + return; + } + + if (siweMessageToSign && (siweNonce || siwePrimaryIdentityPublicKey)) { + res.status(400).send({ + message: + 'Nonce and complete message to sign cannot be provided at the same time.', + }); + } const [{ jsURL, fontURLs, cssInclude }, LandingSSR] = await Promise.all([ getAssetInfo(), @@ -215,6 +235,7 @@ siweNonce={siweNonce} siwePrimaryIdentityPublicKey={siwePrimaryIdentityPublicKey} siweMessageType={siweMessageType} + siweMessageToSign={siweMessageToSign} />, ); reactStream.pipe(res, { end: false }); @@ -227,12 +248,16 @@ const siweMessageTypeString = siweMessageType ? `"${siweMessageType}"` : 'null'; + const siweMessageToSignString = siweMessageToSign + ? `"${siweMessageToSign}"` + : 'null'; // prettier-ignore res.end(html` + diff --git a/landing/landing-ssr.react.js b/landing/landing-ssr.react.js --- a/landing/landing-ssr.react.js +++ b/landing/landing-ssr.react.js @@ -14,6 +14,7 @@ +siweNonce: ?string, +siwePrimaryIdentityPublicKey: ?string, +siweMessageType: ?SIWEMessageType, + +siweMessageToSign: ?string, }; function LandingSSR(props: LandingSSRProps): React.Node { const { @@ -22,6 +23,7 @@ siweNonce, siwePrimaryIdentityPublicKey, siweMessageType, + siweMessageToSign, } = props; const siweContextValue = React.useMemo( @@ -29,8 +31,14 @@ siweNonce, siwePrimaryIdentityPublicKey, siweMessageType, + siweMessageToSign, }), - [siweNonce, siwePrimaryIdentityPublicKey, siweMessageType], + [ + siweNonce, + siwePrimaryIdentityPublicKey, + siweMessageType, + siweMessageToSign, + ], ); const routerContext = React.useMemo(() => ({}), []); return ( diff --git a/landing/root.js b/landing/root.js --- a/landing/root.js +++ b/landing/root.js @@ -12,6 +12,7 @@ declare var siweNonce: ?string; declare var siwePrimaryIdentityPublicKey: ?string; declare var siweMessageType: ?SIWEMessageType; +declare var siweMessageToSign: ?string; function RootComponent(): React.Node { const siweContextValue = React.useMemo( @@ -19,6 +20,7 @@ siweNonce, siwePrimaryIdentityPublicKey, siweMessageType, + siweMessageToSign, }), [], ); diff --git a/landing/siwe-context.js b/landing/siwe-context.js --- a/landing/siwe-context.js +++ b/landing/siwe-context.js @@ -8,12 +8,14 @@ +siweNonce: ?string, +siwePrimaryIdentityPublicKey: ?string, +siweMessageType: ?SIWEMessageType, + +siweMessageToSign: ?string, }; const SIWEContext: React.Context = React.createContext({ siweNonce: null, siwePrimaryIdentityPublicKey: null, siweMessageType: null, + siweMessageToSign: null, }); export { SIWEContext }; diff --git a/landing/siwe.react.js b/landing/siwe.react.js --- a/landing/siwe.react.js +++ b/landing/siwe.react.js @@ -63,40 +63,72 @@ }); } +async function signCompleteMessageWithEthereum( + address: string, + message: string, + signer: Signer, +) { + const decodedMessage = decodeURIComponent(message); + const signature = await signer.signMessage({ message: decodedMessage }); + postMessageToNativeWebView({ + type: 'siwe_success', + address, + message: decodedMessage, + signature, + }); +} + const queryClient = new QueryClient(); function SIWE(): React.Node { const { address } = useAccount(); const { data: signer } = useWalletClient(); - const { siweNonce, siwePrimaryIdentityPublicKey, siweMessageType } = - React.useContext(SIWEContext); + const { + siweNonce, + siwePrimaryIdentityPublicKey, + siweMessageType, + siweMessageToSign, + } = React.useContext(SIWEContext); + const onClick = React.useCallback(() => { - invariant(siweNonce, 'nonce must be present during SIWE attempt'); invariant(siweMessageType, 'message type must be set during SIWE attempt'); invariant( - siwePrimaryIdentityPublicKey, - 'primaryIdentityPublicKey must be present during SIWE attempt', + siweMessageToSign || (siwePrimaryIdentityPublicKey && siweNonce), + 'nonce with primaryIdentityPublicKey or complete message to sign must be ' + + 'present during SIWE attempt', ); - const statement = getSIWEStatementForPublicKey( - siwePrimaryIdentityPublicKey, - siweMessageType, - ); - void signInWithEthereum(address, signer, siweNonce, statement); + + if (siweMessageToSign) { + void signCompleteMessageWithEthereum(address, siweMessageToSign, signer); + return; + } + + if (siweNonce && siwePrimaryIdentityPublicKey) { + const statement = getSIWEStatementForPublicKey( + siwePrimaryIdentityPublicKey, + siweMessageType, + ); + void signInWithEthereum(address, signer, siweNonce, statement); + } }, [ address, signer, siweNonce, siwePrimaryIdentityPublicKey, siweMessageType, + siweMessageToSign, ]); const { openConnectModal, connectModalOpen } = useConnectModal(); - const hasNonce = siweNonce !== null && siweNonce !== undefined; + const hasNonceOrCompleteMessage = + (siweNonce !== null && siweNonce !== undefined) || + (siweMessageToSign !== null && siweMessageToSign !== undefined); + React.useEffect(() => { - if (hasNonce && openConnectModal) { + if (hasNonceOrCompleteMessage && openConnectModal) { openConnectModal(); } - }, [hasNonce, openConnectModal]); + }, [hasNonceOrCompleteMessage, openConnectModal]); const [wcModalOpen, setWCModalOpen] = React.useState(false); @@ -135,10 +167,12 @@ ); } - if (!hasNonce) { + if (!hasNonceOrCompleteMessage) { return (
-

Unable to proceed: nonce not found.

+

+ Unable to proceed: neither nonce nor complete message found. +

); } else if (!signer) { diff --git a/lib/types/siwe-types.js b/lib/types/siwe-types.js --- a/lib/types/siwe-types.js +++ b/lib/types/siwe-types.js @@ -141,10 +141,18 @@ export const SIWEMessageTypes = Object.freeze({ MSG_AUTH: 'msg_auth', MSG_BACKUP: 'msg_backup', + MSG_BACKUP_RESTORE: 'msg_backup_restore', }); export type SIWEMessageType = $Values; +export type SIWESignatureRequestData = + | { +messageType: SIWEMessageType } + | { + +messageType: 'msg_backup_restore', + +messageToSign: string, + }; + export type SIWEBackupSecrets = { +message: string, +signature: string, diff --git a/lib/utils/siwe-utils.js b/lib/utils/siwe-utils.js --- a/lib/utils/siwe-utils.js +++ b/lib/utils/siwe-utils.js @@ -82,6 +82,11 @@ ); } +function isValidSIWEMessageString(candidate: string): boolean { + const candidateMessage = new SiweMessage(candidate); + return isValidSIWEMessage(candidateMessage); +} + function getSIWEStatementForPublicKey( publicKey: string, messageType: SIWEMessageType, @@ -134,7 +139,12 @@ `Your signature on this message will be used to derive ` + `a secret key that will encrypt your Comm backup.`; +const siweBackupRestoreMessageSigningExplanationStatements: string = + `Your signature on this message will be used to derive ` + + `a secret key that will decrypt you Comm backup.`; + const siweBackupMessageSigningButtonStatement = 'Create a backup key'; +const siweBackupRestoreMessageSigningButtonStatement = 'Retrieve a backup key'; const userTextsForSIWEMessageTypes: { +[signatureRequestType: string]: { @@ -153,6 +163,11 @@ showTermsAgreement: false, buttonStatement: siweBackupMessageSigningButtonStatement, }, + [SIWEMessageTypes.MSG_BACKUP_RESTORE]: { + explanationStatement: siweBackupRestoreMessageSigningExplanationStatements, + showTermsAgreement: false, + buttonStatement: siweBackupRestoreMessageSigningButtonStatement, + }, }; export { @@ -163,6 +178,7 @@ isValidPrimaryIdentityPublicKey, createSIWEMessage, isValidSIWEMessage, + isValidSIWEMessageString, getSIWEStatementForPublicKey, isValidSIWEStatementWithPublicKey, getPublicKeyFromSIWEStatement,