Skip to content
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

Implement EIP-7524 (PLUME Signatures) #3638

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
77 changes: 76 additions & 1 deletion background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,16 @@ import {
signingSliceEmitter,
typedDataRequest,
signDataRequest,
getPLUMESignatureRequest,
signedPLUME,
} from "./redux-slices/signing"

import { SignTypedDataRequest, MessageSigningRequest } from "./utils/signing"
import {
SignTypedDataRequest,
MessageSigningRequest,
PLUMESigningRequest,
PLUMESigningResponse,
} from "./utils/signing"
import {
emitter as earnSliceEmitter,
setVaultsAsStale,
Expand All @@ -132,6 +139,7 @@ import { ETHEREUM, FLASHBOTS_RPC_URL, OPTIMISM, USD } from "./constants"
import { clearApprovalInProgress, clearSwapQuote } from "./redux-slices/0x-swap"
import {
AccountSigner,
PLUMESignatureResponse,
SignatureResponse,
TXSignatureResponse,
} from "./services/signing"
Expand Down Expand Up @@ -984,6 +992,18 @@ export default class Main extends BaseService<never> {
this.store.dispatch(signedDataAction(signedData))
},
)
signingSliceEmitter.on(
"requestSignPLUME",
async ({ rawSigningData, account, accountSigner, plumeVersion }) => {
const signedData = await this.signingService.signPLUME(
account,
rawSigningData,
accountSigner,
plumeVersion,
)
this.store.dispatch(signedPLUME(signedData))
},
)

this.chainService.emitter.on(
"blockPrices",
Expand Down Expand Up @@ -1342,6 +1362,61 @@ export default class Main extends BaseService<never> {
signingSliceEmitter.on("signatureRejected", rejectAndClear)
},
)
this.internalEthereumProviderService.emitter.on(
"getPLUMESignatureRequest",
async ({
payload,
resolver,
rejecter,
}: {
payload: PLUMESigningRequest
resolver: (
result: PLUMESigningResponse | PromiseLike<PLUMESigningResponse>,
) => void
rejecter: () => void
}) => {
await this.signingService.prepareForSigningRequest()

this.store.dispatch(getPLUMESignatureRequest(payload))

const clear = () => {
this.signingService.emitter.off(
"PLUMESigningResponse",
// Mutual dependency to handleAndClear.
// eslint-disable-next-line @typescript-eslint/no-use-before-define
handleAndClear,
)

signingSliceEmitter.off(
"signatureRejected",
// Mutual dependency to rejectAndClear.
// eslint-disable-next-line @typescript-eslint/no-use-before-define
rejectAndClear,
)
}

const handleAndClear = (response: PLUMESignatureResponse) => {
clear()
switch (response.type) {
case "success-data":
resolver(response.plume)
break
default:
rejecter()
break
}
}

const rejectAndClear = () => {
clear()
rejecter()
}

this.signingService.emitter.on("PLUMESigningResponse", handleAndClear)

signingSliceEmitter.on("signatureRejected", rejectAndClear)
},
)
this.internalEthereumProviderService.emitter.on(
"signDataRequest",
async ({
Expand Down
1 change: 1 addition & 0 deletions background/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"immer": "^9.0.1",
"jsondiffpatch": "^0.4.1",
"lodash": "^4.17.21",
"plume-sig": "^2.0.2",
"sinon": "^14.0.0",
"siwe": "^1.1.0",
"util": "^0.12.4",
Expand Down
44 changes: 43 additions & 1 deletion background/redux-slices/signing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { createSelector, createSlice } from "@reduxjs/toolkit"
import Emittery from "emittery"
import { MessageSigningRequest, SignTypedDataRequest } from "../utils/signing"
import {
MessageSigningRequest,
PLUMESigningRequest,
PLUMESigningResponse,
SignTypedDataRequest,
} from "../utils/signing"
import { createBackgroundAsyncThunk } from "./utils"
import { EnrichedSignTypedDataRequest } from "../services/enrichment"
import { EIP712TypedData } from "../types"
Expand All @@ -20,6 +25,7 @@ export type SignOperationType =
| SignTypedDataRequest
| EIP1559TransactionRequest
| LegacyEVMTransactionRequest
| PLUMESigningRequest

/**
* A request for a signing operation carrying the AccountSigner whose signature
Expand All @@ -39,6 +45,9 @@ type Events = {
requestSignData: MessageSigningRequest & {
accountSigner: AccountSigner
}
requestSignPLUME: PLUMESigningRequest & {
accountSigner: AccountSigner
}
signatureRejected: never
}

Expand All @@ -50,6 +59,7 @@ type SigningState = {

signedData: string | undefined
signDataRequest: MessageSigningRequest | undefined
getPLUMESignatureRequest: PLUMESigningRequest | undefined

additionalSigningStatus: "editing" | undefined
}
Expand All @@ -60,6 +70,7 @@ export const initialState: SigningState = {

signedData: undefined,
signDataRequest: undefined,
getPLUMESignatureRequest: undefined,

additionalSigningStatus: undefined,
}
Expand Down Expand Up @@ -91,6 +102,17 @@ export const signData = createBackgroundAsyncThunk(
},
)

export const signPLUME = createBackgroundAsyncThunk(
"signing/signPLUME",
async (data: SignOperation<PLUMESigningRequest>) => {
const { request, accountSigner } = data
await signingSliceEmitter.emit("requestSignPLUME", {
...request,
accountSigner,
})
},
)

const signingSlice = createSlice({
name: "signing",
initialState,
Expand All @@ -114,6 +136,18 @@ const signingSlice = createSlice({
...state,
signDataRequest: payload,
}),
getPLUMESignatureRequest: (
state,
{ payload }: { payload: PLUMESigningRequest },
) => ({
...state,
getPLUMESignatureRequest: payload,
}),
signedPLUME: (state, { payload }: { payload: PLUMESigningResponse }) => ({
...state,
signedPLUME: payload,
getPLUMESignatureRequest: undefined,
}),
signedData: (state, { payload }: { payload: string }) => ({
...state,
signedData: payload,
Expand All @@ -124,6 +158,7 @@ const signingSlice = createSlice({
typedDataRequest: undefined,
signDataRequest: undefined,
additionalSigningStatus: undefined,
getPLUMESignatureRequest: undefined,
}),
updateAdditionalSigningStatus: (
state,
Expand All @@ -142,6 +177,8 @@ export const {
signDataRequest,
clearSigningState,
updateAdditionalSigningStatus,
getPLUMESignatureRequest,
signedPLUME,
} = signingSlice.actions

export default signingSlice.reducer
Expand All @@ -156,6 +193,11 @@ export const selectSigningData = createSelector(
(signTypes) => signTypes,
)

export const selectPLUMESigningData = createSelector(
(state: { signing: SigningState }) => state.signing.getPLUMESignatureRequest,
(signTypes) => signTypes,
)

export const selectAdditionalSigningStatus = createSelector(
(state: { signing: SigningState }) => state.signing.additionalSigningStatus,
(additionalSigningStatus) => additionalSigningStatus,
Expand Down
51 changes: 51 additions & 0 deletions background/services/internal-ethereum-provider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
SignTypedDataRequest,
MessageSigningRequest,
parseSigningData,
PLUMESigningResponse,
PLUMESigningRequest,
} from "../../utils/signing"
import { getOrCreateDB, InternalEthereumProviderDatabase } from "./db"
import { TAHO_INTERNAL_ORIGIN } from "./constants"
Expand Down Expand Up @@ -114,6 +116,10 @@ type Events = ServiceLifecycleEvents & {
>
signTypedDataRequest: DAppRequestEvent<SignTypedDataRequest, string>
signDataRequest: DAppRequestEvent<MessageSigningRequest, string>
getPLUMESignatureRequest: DAppRequestEvent<
PLUMESigningRequest,
PLUMESigningResponse
>
selectedNetwork: EVMNetwork
watchAssetRequest: { contractAddress: string; network: EVMNetwork }
// connect
Expand Down Expand Up @@ -182,6 +188,15 @@ export default class InternalEthereumProviderService extends BaseService<Events>
},
typedData: JSON.parse(params[1] as string),
})
case "eth_getPlumeSignature":
return this.getPlumeSignature(
{
input: params[0] as string,
account: params[1] as string,
version: params[2] as string,
},
origin,
)
case "eth_chainId":
// TODO Decide on a better way to track whether a particular chain is
// allowed to have an RPC call made to it. Ideally this would be based
Expand Down Expand Up @@ -571,4 +586,40 @@ export default class InternalEthereumProviderService extends BaseService<Events>
})
})
}

private async getPlumeSignature(
{
input,
account,
version,
}: {
input: string
account: string
version?: string
},
origin: string,
) {
const currentNetwork =
await this.getCurrentOrDefaultNetworkForOrigin(origin)
const plumeVersion = version !== undefined ? Number(version) : 2

if (plumeVersion < 1 || plumeVersion > 2) {
throw new Error("Unsupported PLUME version.")
}

return new Promise<PLUMESigningResponse>((resolve, reject) => {
this.emitter.emit("getPLUMESignatureRequest", {
payload: {
account: {
address: account,
network: currentNetwork,
},
rawSigningData: input,
plumeVersion,
},
resolver: resolve,
rejecter: reject,
})
})
}
}
60 changes: 60 additions & 0 deletions background/services/internal-signer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HDKeyring, { SerializedHDKeyring } from "@tallyho/hd-keyring"

import { arrayify } from "ethers/lib/utils"
import { Wallet } from "ethers"
import { computeAllInputs } from "plume-sig"
import { normalizeEVMAddress, sameEVMAddress } from "../../lib/utils"
import { ServiceCreatorFunction, ServiceLifecycleEvents } from "../types"
import {
Expand All @@ -29,6 +30,7 @@ import { AddressOnNetwork } from "../../accounts"
import logger from "../../lib/logger"
import PreferenceService from "../preferences"
import { DEFAULT_AUTOLOCK_INTERVAL } from "../preferences/defaults"
import { PLUMESigningResponse } from "../../utils/signing"

export enum SignerInternalTypes {
mnemonicBIP39S128 = "mnemonic#bip39:128",
Expand Down Expand Up @@ -1020,6 +1022,64 @@ export default class InternalSignerService extends BaseService<Events> {
}
}

/**
* Generate a Pseudonymously Linked Unique Message Entity based on EIP-7524 with the usage of eth_getPlumeSignature method,
* more information about the EIP can be found at https://eips.ethereum.org/EIPS/eip-7524
*
* @param message - the message to generate a PLUME for
* @param account - signers account address
*/
async generatePLUME({
message,
account,
version,
}: {
message: string
account: HexString
version?: number
}): Promise<PLUMESigningResponse> {
this.requireUnlocked()
const signerWithType = this.#findSigner(account)

if (!signerWithType) {
throw new Error(
`PLUME generation failed. Signer for address ${account} was not found.`,
)
}

try {
let privateKey
if (isPrivateKey(signerWithType)) {
privateKey = signerWithType.signer.privateKey
} else {
privateKey = await signerWithType.signer.exportPrivateKey(
account,
"I solemnly swear that I am treating this private key material with great care.",
)
}
if (!privateKey) {
throw new Error("Private key unavailable")
}
if (privateKey.startsWith("0x")) {
privateKey = privateKey.substring(2)
}

const { plume, s, publicKey, c, gPowR, hashMPKPowR } =
await computeAllInputs(message, privateKey, undefined, version)

return {
plume: `0x${plume.toHex(true)}`,
publicKey: `0x${Buffer.from(publicKey).toString("hex")}`,
hashMPKPowR: `0x${hashMPKPowR.toHex(true)}`,
gPowR: `0x${gPowR.toHex(true)}`,
c: `0x${c}`,
s: `0x${s}`,
}
} catch (error) {
throw new Error("Signing data failed")
}
}

#emitInternalSigners(): void {
if (this.locked()) {
this.emitter.emit("internalSigners", {
Expand Down
Loading