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

[viem] signMessage fails when using raw property of SignableMessage #116

Closed
danscan opened this issue Sep 14, 2023 · 3 comments
Closed

[viem] signMessage fails when using raw property of SignableMessage #116

danscan opened this issue Sep 14, 2023 · 3 comments

Comments

@danscan
Copy link

danscan commented Sep 14, 2023

Hey there!

I noticed that using @turnkey/viem's signMessage will always fail if you use the raw form of the SignableMessage type.

// succeeds
await turnkeyViemAccount.signMessage({ message: 'hello' });

// fails
await turnkeyViemAccount.signMessage({ message: { raw: stringToHex('hello') } });
await turnkeyViemAccount.signMessage({ message: { raw: hexToBytes('0x68656c6c6f') } });

Looks like it's due to this line, which assumes message is always a string, and casts it to 0x${string}:

const hashedMessage = keccak256(message as `0x${string}`);

I believe a quick fix is to replace keccak256(message as `0x${string}`) with hashMessage(message). You can import hashMessage from viem itself, and it accepts a SignableMessage, so it should be exactly what's needed here.

FYI Viem has a nice set of typesafe utilities, and also, as I recently found, common types like Hex so you don't need to type `0x${string}` all over :)

@danscan
Copy link
Author

danscan commented Sep 14, 2023

Just confirmed that Viem's hashMessage does what's needed here. Here's my implementation:

import { createActivityPoller, type TurnkeyClient } from "@turnkey/http";
import { hashMessage, signatureToHex, type SignableMessage } from "viem";
import { z } from "zod";

type Options = {
  message: SignableMessage;
  turnkeyClient: TurnkeyClient;
  turnkeyPrivateKeyID: string;
  turnkeySubOrganizationID: string;
}

export async function signMessage({
  message,
  turnkeyClient,
  turnkeyPrivateKeyID,
  turnkeySubOrganizationID,
}: Options) {
  const activityPoller = createActivityPoller({
    client: turnkeyClient,
    requestFn: turnkeyClient.signRawPayload,
  });

  const { result: { signRawPayloadResult } } = CompletedActivitySchema.parse(await activityPoller({
    type: 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD',
    timestampMs: String(Date.now()),
    organizationId: turnkeySubOrganizationID,
    parameters: {
      privateKeyId: turnkeyPrivateKeyID,
      encoding: 'PAYLOAD_ENCODING_HEXADECIMAL',
      hashFunction: 'HASH_FUNCTION_NO_OP',
      payload: hashMessage(message),
    },
  }));

  const signatureHex = signatureToHex({
    r: `0x${signRawPayloadResult.r}`,
    s: `0x${signRawPayloadResult.s}`,
    v: signRawPayloadResult.v === "00" ? 27n : 28n,
  });

  return signatureHex;
}

const CompletedActivitySchema = z.object({
  result: z.object({
    signRawPayloadResult: z.object({
      r: z.string(),
      s: z.string(),
      v: z.string(),
    }),
  }),
});

@andrewkmin
Copy link
Collaborator

Hi @danscan thanks for reporting this! Awesome catch -- we're on it: #117

@andrewkmin
Copy link
Collaborator

Hey @danscan the fix has been merged + released. Will be closing this issue now - please let us know if there are any additional issues and we'd be happy to revisit. Thanks again for filing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants