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
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const deferPackages = [
'@metamask/eth-sig-util',
'@masknet/gun-utils',
'web3-eth',
'web3-eth-accounts',
'twitter-text',
'web3-utils',
'@solana/web3.js',
Expand Down
63 changes: 30 additions & 33 deletions packages/mask/background/services/identity/persona/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,50 @@ import {
fromBase64URL,
PopupRoutes,
type ECKeyIdentifier,
type SignType,
MaskMessages,
type SignMessage,
} from '@masknet/shared-base'
import { queryPersonasWithPrivateKey } from '../../../database/persona/web.js'
import { openPopupWindow } from '../../helper/popup-opener.js'

async function getIdentifier(message: unknown, identifier?: ECKeyIdentifier, source?: string, silent = false) {
if (!identifier || !silent) {
const requestID = crypto.randomUUID()
await openPopupWindow(PopupRoutes.PersonaSignRequest, {
message: JSON.stringify(message),
requestID,
identifier: identifier?.toText(),
source,
})

return timeout(
new Promise<PersonaIdentifier>((resolve, reject) => {
MaskMessages.events.personaSignRequest.on((approval) => {
if (approval.requestID !== requestID) return
if (!approval.selectedPersona) reject(new Error('The user refused to sign message with persona.'))
resolve(approval.selectedPersona!)
})
}),
60 * 1000,
'Timeout of signing with persona.',
)
}
return identifier
}
/**
* Generate a signature w or w/o confirmation from user
*/
export async function signWithPersona(
type: SignType,
message: unknown,
message: SignMessage,
identifier?: ECKeyIdentifier,
source?: string,
origin?: string,
silent = false,
): Promise<string> {
const getIdentifier = async () => {
if (!identifier || !silent) {
const requestID = crypto.randomUUID()
await openPopupWindow(PopupRoutes.PersonaSignRequest, {
message: JSON.stringify(message),
requestID,
identifier: identifier?.toText(),
source,
})

return timeout(
new Promise<PersonaIdentifier>((resolve, reject) => {
MaskMessages.events.personaSignRequest.on((approval) => {
if (approval.requestID !== requestID) return
if (!approval.selectedPersona)
reject(new Error('The user refused to sign message with persona.'))
resolve(approval.selectedPersona!)
})
}),
60 * 1000,
'Timeout of signing with persona.',
)
}
return identifier
}

const identifier_ = await getIdentifier()
identifier = await getIdentifier(message.data, identifier, origin, silent)

// find the persona with the signer's identifier
const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier_)
const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier)
if (!persona?.privateKey.d) throw new Error('Persona not found')

return Signer.sign(type, Buffer.from(fromBase64URL(persona.privateKey.d)), message)
return Signer.sign(message, Buffer.from(fromBase64URL(persona.privateKey.d)))
}
40 changes: 25 additions & 15 deletions packages/mask/background/services/wallet/services/send.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JsonRpcRequest } from 'web3-types'
import { ECKeyIdentifier, type SignType } from '@masknet/shared-base'
import { ECKeyIdentifier, SignType } from '@masknet/shared-base'
import { EVMRequestReadonly, EVMWeb3Readonly } from '@masknet/web3-providers'
import {
ChainId,
Expand All @@ -8,10 +8,10 @@ import {
EthereumMethodType,
PayloadEditor,
type TransactionOptions,
Signer,
} from '@masknet/web3-shared-evm'
import { signWithWallet } from './wallet/index.js'
import { signWithPersona } from '../../identity/persona/sign.js'
import type { TransactionSerializable } from 'viem'

/**
* The entrance of all RPC requests to MaskWallet.
Expand All @@ -23,25 +23,35 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions
from,
chainId = options?.chainId ?? ChainId.Mainnet,
signableMessage,
signableConfig,
signableTransaction,
} = PayloadEditor.fromPayload(payload, options)
const identifier = ECKeyIdentifier.from(options?.identifier).unwrapOr(undefined)
const signer =
identifier ?
new Signer(identifier, <T>(type: SignType, message: T, identifier?: ECKeyIdentifier) =>
signWithPersona(type, message, identifier, undefined, true),
)
: new Signer(owner || from!, signWithWallet)
const signTransaction = async (transaction: TransactionSerializable) => {
const message = { type: SignType.Transaction as const, data: transaction }
if (identifier) {
return signWithPersona(message, identifier)
} else {
return signWithWallet(message, owner || from!)
}
}
const signMessageOrTypedData = async (type: SignType.Message | SignType.TypedData, message: string) => {
const msg = { type, data: message }
if (identifier) {
return signWithPersona(msg, identifier)
} else {
return signWithWallet(msg, owner || from!)
}
}

switch (payload.method) {
case EthereumMethodType.eth_sendTransaction:
case EthereumMethodType.MASK_REPLACE_TRANSACTION:
if (!signableConfig) throw new Error('No transaction to be sent.')
if (!signableTransaction) throw new Error('No transaction to be sent.')

try {
return createJsonRpcResponse(
pid,
await EVMWeb3Readonly.sendSignedTransaction(await signer.signTransaction(signableConfig), {
await EVMWeb3Readonly.sendSignedTransaction(await signTransaction(signableTransaction), {
chainId,
providerURL,
}),
Expand All @@ -53,21 +63,21 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions
case EthereumMethodType.personal_sign:
try {
if (!signableMessage) throw new Error('No message to be signed.')
return createJsonRpcResponse(pid, await signer.signMessage(signableMessage))
return createJsonRpcResponse(pid, await signMessageOrTypedData(SignType.Message, signableMessage))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign message.').error
}
case EthereumMethodType.eth_signTypedData_v4:
try {
if (!signableMessage) throw new Error('No typed data to be signed.')
return createJsonRpcResponse(pid, await signer.signTypedData(signableMessage))
return createJsonRpcResponse(pid, await signMessageOrTypedData(SignType.TypedData, signableMessage))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign typed data.').error
}
case EthereumMethodType.eth_signTransaction:
try {
if (!signableConfig) throw new Error('No transaction to be signed.')
return createJsonRpcResponse(pid, await signer.signTransaction(signableConfig))
if (!signableTransaction) throw new Error('No transaction to be signed.')
return createJsonRpcResponse(pid, await signTransaction(signableTransaction))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign transaction.').error
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import defer * as web3_utils from 'web3-utils'
import { toBytes } from '@ethereumjs/util'
import { api } from '@dimensiondev/mask-wallet-core/proto'
import { Signer } from '@masknet/web3-providers'
import { ImportSource, type SignType, type Wallet } from '@masknet/shared-base'
import { ImportSource, type SignMessage, type Wallet } from '@masknet/shared-base'
import { HD_PATH_WITHOUT_INDEX_ETHEREUM } from '@masknet/web3-shared-base'
import * as Mask from '../maskwallet/index.js'
import * as database from './database/index.js'
Expand Down Expand Up @@ -249,8 +249,8 @@ export async function resetAllWallets() {
await database.resetAllWallets()
}

export async function signWithWallet<T>(type: SignType, message: T, address: string) {
return Signer.sign(type, Buffer.from(toBytes(`0x${await exportPrivateKey(address)}`)), message)
export async function signWithWallet(message: SignMessage, address: string) {
return Signer.sign(message, Buffer.from(toBytes(`0x${await exportPrivateKey(address)}`)))
}

export async function exportMnemonicWords(address: string, unverifiedPassword?: string) {
Expand Down
3 changes: 1 addition & 2 deletions packages/mask/popups/components/ConnectedWallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ export const ConnectedWallet = memo(function ConnectedWallet() {
if (!result) return

const signature = await Services.Identity.signWithPersona(
SignType.Message,
result.signPayload,
{ type: SignType.Message, data: result.signPayload },
persona.identifier,
location.origin,
true,
Expand Down
3 changes: 1 addition & 2 deletions packages/mask/popups/pages/Personas/AccountDetail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ export const Component = memo(() => {
if (!result) return

const signature = await Service.Identity.signWithPersona(
SignType.Message,
result.signPayload,
{ type: SignType.Message, data: result.signPayload },
currentPersona.identifier,
location.origin,
true,
Expand Down
3 changes: 1 addition & 2 deletions packages/mask/popups/pages/Personas/ConnectWallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ export const Component = memo(function ConnectWalletPage() {

if (!payload) return
const personaSignature = await Services.Identity.signWithPersona(
SignType.Message,
payload.signPayload,
{ type: SignType.Message, data: payload.signPayload },
currentPersona.identifier,
location.origin,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ const PersonaAvatarSetting = memo(function PersonaAvatar() {
// Verify Wallet sign with persona
if (bindingWallets.some((x) => isSameAddress(x.identity, account))) {
sign = await Services.Identity.signWithPersona(
SignType.Message,
JSON.stringify(data),
{ type: SignType.Message, data: JSON.stringify(data) },
currentPersona.identifier,
location.origin,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ export const Component = memo(function PersonaSignRequest() {
case MethodAfterPersonaSign.DISCONNECT_NEXT_ID:
if (!message) break
const signature = await Services.Identity.signWithPersona(
SignType.Message,
message,
{ type: SignType.Message, data: message },
selectedPersona,
location.origin,
true,
Expand Down
2 changes: 1 addition & 1 deletion packages/mask/shared-ui/initUIContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function setupUIContext() {
queryPersonaByProfile: Services.Identity.queryPersonaByProfile,
openDashboard: Services.Helper.openDashboard,
openPopupWindow,
signWithPersona: (a, b, c, d) => Services.Identity.signWithPersona(a, b, c, location.origin, d),
signWithPersona: (a, b, c) => Services.Identity.signWithPersona(a, b, location.origin, c),
hasPaymentPassword: Services.Wallet.hasPassword,
createPersona: () => Services.Helper.openDashboard(DashboardRoutes.SignUpPersona),
attachProfile: Services.Identity.attachProfile,
Expand Down
1 change: 0 additions & 1 deletion packages/mask/shared-ui/initialization/walletSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { delay } from '@masknet/kit'
import { openPopupWindow } from '../utils/openPopup.js'

await initWallet({
signWithPersona: (a, b, c, d) => Services.Identity.signWithPersona(a, b, c, location.origin, d),
WalletConnectContext: {
openWalletConnectDialog: async (uri: string) => {
if (Sniffings.is_popup_page) {
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-infra/src/dom/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
PopupRoutes,
PopupRoutesParamsMap,
ProfileIdentifier,
SignType,
SignMessage,
SocialIdentity,
} from '@masknet/shared-base'
import type { Subscription } from 'use-subscription'
Expand Down Expand Up @@ -41,7 +41,7 @@ export interface __UIContext__ {
params: T extends keyof PopupRoutesParamsMap ? PopupRoutesParamsMap[T] : undefined,
): Promise<void>
/** Sign a message with persona (w or w/o popups) */
signWithPersona(type: SignType, message: unknown, identifier?: ECKeyIdentifier, silent?: boolean): Promise<string>
signWithPersona(message: SignMessage, identifier?: ECKeyIdentifier, silent?: boolean): Promise<string>
hasPaymentPassword(): Promise<boolean>
createPersona: () => void
setCurrentPersonaIdentifier: ((x?: PersonaIdentifier) => Promise<void>) | undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/NextID/src/hooks/usePersonaSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function usePersonaSign(message?: string, currentIdentifier?: ECKeyIdenti
return useAsyncFn(async () => {
if (!message || !currentIdentifier) return
try {
return await signWithPersona?.(SignType.Message, message, currentIdentifier)
return await signWithPersona?.({ type: SignType.Message, data: message }, currentIdentifier)
} catch {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EVMWeb3 } from '@masknet/web3-providers'
import { useNftRedPacketContract } from './useNftRedPacketContract.js'
import type { RedPacketNftJSONPayload } from '@masknet/web3-providers/types'
import { useSignedMessage } from './useSignedMessage.js'
import { useMemo } from 'react'
import type { Hex } from 'viem'

const EXTRA_GAS_PER_NFT = 335

Expand All @@ -16,12 +16,10 @@ export function useClaimNftRedpacketCallback(payload: RedPacketNftJSONPayload, t
const nftRedPacketContract = useNftRedPacketContract(chainId)
const { refetch } = useSignedMessage(account, payload)
const id = payload.id
const signedMsg = useMemo(() => {
return signMessage(account, payload.privateKey).signature ?? ''
}, [account, payload.privateKey])
return useAsyncFn(async () => {
if (!nftRedPacketContract || !id || !account || !totalAmount || !signedMsg) return
if (!nftRedPacketContract || !id || !account || !totalAmount || !payload.privateKey) return

const signedMsg = await signMessage(account, payload.privateKey as Hex)
const transaction = nftRedPacketContract.methods.claim(id, signedMsg, account)
const estimatedGas = await transaction.estimateGas({ from: account })
const tx = await new ContractTransaction(nftRedPacketContract).fillAll(transaction, {
Expand All @@ -30,5 +28,5 @@ export function useClaimNftRedpacketCallback(payload: RedPacketNftJSONPayload, t
chainId,
})
return EVMWeb3.sendTransaction(tx, { chainId })
}, [id, refetch, account, chainId, totalAmount, signedMsg])
}, [id, refetch, account, chainId, totalAmount, account, payload.privateKey])
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type RedPacketJSONPayload, type RedPacketNftJSONPayload } from '@maskne
import { signMessage } from '@masknet/web3-shared-evm'
import { useQuery } from '@tanstack/react-query'
import { usePlatformType } from './usePlatformType.js'
import type { Hex } from 'viem'

// TODO NFT redpacket is not supported by the API yet.
export function useSignedMessage(
Expand Down Expand Up @@ -34,7 +35,7 @@ export function useSignedMessage(
queryKey: ['red-packet', 'signed-message', rpid, version, password, account, profile, isTokenRedPacket],
queryFn: async () => {
if (isTokenRedPacket && version <= 3) return password ?? null
if (password) return signMessage(account, password).signature
if (password) return signMessage(account, password as Hex)
if (!profile) return ''
return (
(await FireflyRedPacket.createClaimSignature({
Expand Down
13 changes: 13 additions & 0 deletions packages/shared-base/src/types/Account.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TransactionSerializable } from 'viem'
export interface Account<ChainId> {
account: string
chainId: ChainId
Expand All @@ -10,3 +11,15 @@ export enum SignType {
TypedData = 'typedData',
Transaction = 'transaction',
}

export type SignMessage = SignMessage_String | SignMessage_Transaction

export interface SignMessage_String {
type: SignType.Message | SignType.TypedData
data: string
}

export interface SignMessage_Transaction {
type: SignType.Transaction
data: TransactionSerializable
}
6 changes: 5 additions & 1 deletion packages/shared/src/hooks/useVerifyContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export function useVerifyContent(personaIdentifier: PersonaIdentifier | undefine
)
if (!payload) throw new Error('Failed to create persona payload.')

const signature = await signWithPersona(SignType.Message, payload.signPayload, personaIdentifier, true)
const signature = await signWithPersona(
{ type: SignType.Message, data: payload.signPayload },
personaIdentifier,
true,
)
const post = payload.postContent.replace('%SIG_BASE64%', toBase64(fromHex(signature)))
return {
post,
Expand Down
6 changes: 5 additions & 1 deletion packages/shared/src/hooks/useVerifyNextID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ async function createAndSignMessage(platform: NextIDPlatform, persona: PersonaIn
)
if (!payload) throw new Error('Failed to create persona payload.')

const signature = await signWithPersona(SignType.Message, payload.signPayload, persona.identifier, true)
const signature = await signWithPersona(
{ type: SignType.Message, data: payload.signPayload },
persona.identifier,
true,
)
if (!signature) throw new Error('Failed to sign by persona.')
return { payload, signature }
}
Expand Down
Loading