From e17bef57d567c4088d09c2610a6f2a0631c1397b Mon Sep 17 00:00:00 2001 From: Diana Savatina Date: Thu, 2 Jan 2025 16:09:38 +0000 Subject: [PATCH] feat: detect payload signing type for WalletConnect --- .../WalletConnect/useHandleWcRequest.tsx | 13 ++++-- packages/core/src/decodeBeaconPayload.test.ts | 24 ++++++++--- packages/core/src/decodeBeaconPayload.ts | 41 ++++++++++++++----- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx index 8ccc3389b0..3a666d0290 100644 --- a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx +++ b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx @@ -1,7 +1,12 @@ -import { SigningType } from "@airgap/beacon-wallet"; +import { type SigningType } from "@airgap/beacon-wallet"; import { useToast } from "@chakra-ui/react"; import { useDynamicModalContext } from "@umami/components"; -import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core"; +import { + type ImplicitAccount, + estimate, + getSigningTypeFromPayload, + toAccountOperations, +} from "@umami/core"; import { useAsyncActionHandler, useFindNetwork, @@ -95,6 +100,8 @@ export const useHandleWcRequest = () => { session ); } + + const signingType: SigningType = getSigningTypeFromPayload(request.params.payload); const signPayloadProps: SignPayloadProps = { appName: session.peer.metadata.name, appIcon: session.peer.metadata.icons[0], @@ -102,7 +109,7 @@ export const useHandleWcRequest = () => { isScam: event.verifyContext.verified.isScam, validationStatus: event.verifyContext.verified.validation, signer: signer, - signingType: SigningType.RAW, + signingType: signingType, requestId: { sdkType: "walletconnect", id: id, topic }, }; diff --git a/packages/core/src/decodeBeaconPayload.test.ts b/packages/core/src/decodeBeaconPayload.test.ts index 5ad39dcef2..d8835fb8b1 100644 --- a/packages/core/src/decodeBeaconPayload.test.ts +++ b/packages/core/src/decodeBeaconPayload.test.ts @@ -1,12 +1,13 @@ import { SigningType } from "@airgap/beacon-wallet"; -import { decodeBeaconPayload } from "./decodeBeaconPayload"; +import { decodeBeaconPayload, getSigningTypeFromPayload } from "./decodeBeaconPayload"; describe("decodeBeaconPayload", () => { it("returns an error message if the payload is not a valid utf8", () => { const payload = "05010000004354657a6f73205369676e6564204d6573736167653a20496e76616c696420555446383a20c380c2afc3bfc3bec3bd3b204e6f6e7072696e7461626c653a20000115073b"; + expect(getSigningTypeFromPayload(payload)).toEqual(SigningType.MICHELINE); expect(decodeBeaconPayload(payload, SigningType.MICHELINE)).toEqual({ result: payload, error: "Cannot parse Beacon payload", @@ -18,6 +19,7 @@ describe("decodeBeaconPayload", () => { "05010000004254657a6f73205369676e6564204d6573736167653a206d79646170702e636f6d20323032312d30312d31345431353a31363a30345a2048656c6c6f20776f726c6421"; const expected = "Tezos Signed Message: mydapp.com 2021-01-14T15:16:04Z Hello world!"; + expect(getSigningTypeFromPayload(payload)).toEqual(SigningType.MICHELINE); expect(decodeBeaconPayload(payload, SigningType.MICHELINE)).toEqual({ result: expected }); }); @@ -41,6 +43,7 @@ describe("decodeBeaconPayload", () => { const expected = { result: "Tezos Signed Message: mydapp.com 2021-01-14T15:16:04Z Hello world!", }; + expect(getSigningTypeFromPayload(payload)).toEqual(SigningType.RAW); expect(decodeBeaconPayload(payload, SigningType.RAW)).toEqual(expected); }); @@ -63,6 +66,7 @@ describe("decodeBeaconPayload", () => { ], }); + expect(getSigningTypeFromPayload(raw)).toEqual(SigningType.RAW); expect(decodeBeaconPayload(raw, SigningType.RAW)).toEqual({ result }); }); @@ -83,16 +87,26 @@ describe("decodeBeaconPayload", () => { it("handles an empty payload", () => { const emptyPayload = ""; + expect(getSigningTypeFromPayload(emptyPayload)).toEqual(SigningType.RAW); expect(decodeBeaconPayload(emptyPayload, SigningType.RAW)).toEqual({ - result: "", + result: emptyPayload, }); }); it("handles a payload with non-hex characters", () => { - const nonHexPayload = "0501ZZZZ"; - expect(decodeBeaconPayload(nonHexPayload, SigningType.RAW)).toEqual({ + const nonHexPayload = "0301ZZZZ"; + expect(getSigningTypeFromPayload(nonHexPayload)).toEqual(SigningType.OPERATION); + expect(decodeBeaconPayload(nonHexPayload, SigningType.OPERATION)).toEqual({ error: "Cannot parse Beacon payload", - result: "0501ZZZZ", + result: nonHexPayload, + }); + }); + + it("handles OPERATION payload", () => { + const emptyPayload = ""; + expect(getSigningTypeFromPayload(emptyPayload)).toEqual(SigningType.RAW); + expect(decodeBeaconPayload(emptyPayload, SigningType.RAW)).toEqual({ + result: "", }); }); }); diff --git a/packages/core/src/decodeBeaconPayload.ts b/packages/core/src/decodeBeaconPayload.ts index a2e63f0827..a0ce87915a 100644 --- a/packages/core/src/decodeBeaconPayload.ts +++ b/packages/core/src/decodeBeaconPayload.ts @@ -1,7 +1,7 @@ import { SigningType } from "@airgap/beacon-wallet"; import { CODEC, type ProtocolsHash, Uint8ArrayConsumer, getCodec } from "@taquito/local-forging"; -import { DefaultProtocol, unpackData } from "@taquito/michel-codec"; -import { hex2buf } from "@taquito/utils"; +import { DefaultProtocol, type MichelsonData, unpackData } from "@taquito/michel-codec"; +import { bytesToString, hex2buf } from "@taquito/utils"; import { CustomError } from "../../utils/src/ErrorContext"; @@ -25,14 +25,27 @@ export const decodeBeaconPayload = ( switch (signingType) { case SigningType.MICHELINE: case SigningType.OPERATION: { - const consumer = Uint8ArrayConsumer.fromHexString(payload); - const uint8array = consumer.consume(consumer.length()); - const parsed = unpackData(uint8array); + if (getSigningTypeFromPayload(payload) !== signingType) { + throw new CustomError("Invalid prefix for signing type"); + } - if ("string" in parsed && Object.keys(parsed).length === 1) { - result = parsed.string; - } else { - result = JSON.stringify(parsed); + try { + const consumer = Uint8ArrayConsumer.fromHexString(payload); + const uint8array = consumer.consume(consumer.length()); + const parsed: MichelsonData = unpackData(uint8array); + if ("string" in parsed && Object.keys(parsed).length === 1) { + result = parsed.string; + } else { + result = JSON.stringify(parsed); + } + } catch { + // "03" for operation + // "05" + "01" + 8-padded-length for micheline + const prefixLen = signingType === SigningType.MICHELINE ? 12 : 2; + result = bytesToString(payload.slice(prefixLen)); + if (result.length === 0) { + throw new CustomError("Invalid payload. Failed to decode."); + } } break; } @@ -54,7 +67,8 @@ export const decodeBeaconPayload = ( } return { result }; - } catch { + } catch (error) { + console.error(error); return { result: payload, error: "Cannot parse Beacon payload" }; } }; @@ -63,3 +77,10 @@ const isValidASCII = (str: string) => str.split("").every(char => char.charCodeA const parseOperationMicheline = (payload: string) => getCodec(CODEC.MANAGER, DefaultProtocol as string as ProtocolsHash).decoder(payload); + +export const getSigningTypeFromPayload = (payload: string): SigningType => + payload.startsWith("05") + ? SigningType.MICHELINE + : payload.startsWith("03") + ? SigningType.OPERATION + : SigningType.RAW;