Skip to content

Commit

Permalink
feat: detect payload signing type for WalletConnect
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Jan 13, 2025
1 parent 433206b commit d1d1a3c
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 20 deletions.
15 changes: 11 additions & 4 deletions apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -95,14 +100,16 @@ 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],
payload: request.params.payload,
isScam: event.verifyContext.verified.isScam,
validationStatus: event.verifyContext.verified.validation,
signer: signer,
signingType: SigningType.RAW,
signer,
signingType,
requestId: { sdkType: "walletconnect", id: id, topic },
};

Expand Down
26 changes: 20 additions & 6 deletions packages/core/src/decodeBeaconPayload.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { SigningType } from "@airgap/beacon-wallet";

import { decodeBeaconPayload } from "./decodeBeaconPayload";
import { decodeBeaconPayload, getSigningTypeFromPayload } from "./decodeBeaconPayload";

describe("decodeBeaconPayload", () => {
const emptyPayload = "";

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",
Expand All @@ -18,6 +21,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 });
});

Expand All @@ -41,6 +45,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);
});

Expand All @@ -63,6 +68,7 @@ describe("decodeBeaconPayload", () => {
],
});

expect(getSigningTypeFromPayload(raw)).toEqual(SigningType.RAW);
expect(decodeBeaconPayload(raw, SigningType.RAW)).toEqual({ result });
});

Expand All @@ -82,17 +88,25 @@ 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", () => {
expect(getSigningTypeFromPayload(emptyPayload)).toEqual(SigningType.RAW);
expect(decodeBeaconPayload(emptyPayload, SigningType.RAW)).toEqual({
result: emptyPayload,
});
});
});
41 changes: 31 additions & 10 deletions packages/core/src/decodeBeaconPayload.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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;
}
Expand All @@ -54,7 +67,8 @@ export const decodeBeaconPayload = (
}

return { result };
} catch {
} catch (error) {
console.error(error);
return { result: payload, error: "Cannot parse Beacon payload" };
}
};
Expand All @@ -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;

1 comment on commit d1d1a3c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title Lines Statements Branches Functions
apps/desktop Coverage: 83%
83.81% (1787/2132) 79.58% (850/1068) 78.27% (454/580)
apps/web Coverage: 83%
83.81% (1787/2132) 79.58% (850/1068) 78.27% (454/580)
packages/components Coverage: 97%
97.51% (196/201) 95.91% (94/98) 88.13% (52/59)
packages/core Coverage: 81%
82.37% (215/261) 72.51% (95/131) 81.66% (49/60)
packages/crypto Coverage: 100%
100% (43/43) 90.9% (10/11) 100% (7/7)
packages/data-polling Coverage: 97%
95.27% (141/148) 87.5% (21/24) 92.85% (39/42)
packages/multisig Coverage: 98%
98.47% (129/131) 85.71% (18/21) 100% (36/36)
packages/social-auth Coverage: 100%
100% (21/21) 100% (11/11) 100% (3/3)
packages/state Coverage: 85%
84.39% (822/974) 81.03% (188/232) 77.77% (301/387)
packages/tezos Coverage: 89%
88.72% (118/133) 94.59% (35/37) 86.84% (33/38)
packages/tzkt Coverage: 89%
87.32% (62/71) 87.5% (14/16) 80.48% (33/41)

Please sign in to comment.