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

signMessage(...): move encoding and hashing logic to client side #22

Merged
merged 3 commits into from
Apr 8, 2023
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
17 changes: 7 additions & 10 deletions examples/with-ethers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,26 @@ Address:
0x064c0CfDD7C485Eba21988Ded4dbCD9358556842

Balance:
0.094163288735774138 Ether
0.07750465249126655 Ether

Transaction count:
2
14

Turnkey-powered signature:
0xf3db9720b4b2ef8eba3119b04cc9332e4d363a9e3ee8b269375dc2b6b005a97a1503d172e67f4f647ee7a87967fbcff7d46bec0925638b78f6d943dfe5bc26161c
0x97da598ac1ad566e77be7c7d9cc77339730e48c557c5d6f32f93d9fdeeed13472b1faf20f1e457a897a409f31b9e680ad6b02086ac4fb9aa693ce10374976b201c

Recovered address:
0xF8781b03365C82A0BA33f0BC8a0eAc97611e0046

Recovered pubkey:
0x048d99ba65e978bc39ecc55e42afa590cf2893d67aca407b8d66822aa5493d094abdc4710cbee863812631af249a11fefe260895a5d072c8bc0cce53210e8b1a3d
0x064c0CfDD7C485Eba21988Ded4dbCD9358556842

Turnkey-signed transaction:
0x02f8668080808080942ad9ea1e677949a536a270cec812d6e868c881088609184e72a00080c001a09881f59e48500ef8960ae1cb94e0c862e7d613f961c250b6f07b546a1b058b1da06ba1871d7aed5eb8ea8cb211a0e3e22a1c6b54b34b4376d0ef5b1daef4100c8f

Sent 0.00001 Ether to 0x2Ad9eA1E677949a536A270CEC812D6e868C88108:
https://goerli.etherscan.io/tx/0x6b33eaf0b1c01beeb2122baf15c1375807a38610d3983025e8c7c900e9624bf3
https://goerli.etherscan.io/tx/0xe034bdc597766719aef04b1d08998e606e85da1dd73e52fad8586a7d79d659e0

WETH Balance:
0.00001 WETH
0.00007 WETH

Wrapped 0.00001 ETH:
https://goerli.etherscan.io/tx/0x1579554f6803838d7b2a3aa7e66308bbf4059a8442fe3e6fd27d7e98061c6c5b
https://goerli.etherscan.io/tx/0x7f98c1b2c7ff7f8ab876b27fdcd794653d8b7f728dbeec3b1d403789c38bcb71
```
20 changes: 10 additions & 10 deletions examples/with-ethers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,14 @@ async function main() {
print("Balance:", `${ethers.utils.formatEther(balance)} Ether`);
print("Transaction count:", `${transactionCount}`);

// 1. Sign a raw payload
// 1. Sign a raw payload (`eth_sign` style)
const message = "Hello Turnkey";
const msgHash = ethers.utils.hashMessage(message);
const signature = await connectedSigner.signMessage(msgHash);
const msgHashBytes = ethers.utils.arrayify(msgHash);
const recoveredPubKey = ethers.utils.recoverPublicKey(
msgHashBytes,
signature
);
const recoveredAddress = ethers.utils.recoverAddress(msgHashBytes, signature);
const signature = await connectedSigner.signMessage(message);
const recoveredAddress = ethers.utils.verifyMessage(message, signature);

print("Turnkey-powered signature:", `${signature}`);
print("Recovered address:", `${recoveredAddress}`);
print("Recovered pubkey:", `${recoveredPubKey}`);
assertEqual(recoveredAddress, address);

// Create a simple send transaction
const transactionAmount = "0.00001";
Expand Down Expand Up @@ -123,3 +117,9 @@ main().catch((error) => {
function print(header: string, body: string): void {
console.log(`${header}\n\t${body}\n`);
}

function assertEqual<T>(left: T, right: T) {
if (left !== right) {
throw new Error(`${JSON.stringify(left)} !== ${JSON.stringify(right)}`);
}
}
33 changes: 4 additions & 29 deletions packages/ethers/src/__tests__/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,37 +102,12 @@ test("TurnkeySigner", async () => {
`);
}

// Test message (raw payload) signing
// Test message (raw payload) signing, `eth_sign` style
const message = "Hello Turnkey";
const msgHash = ethers.utils.hashMessage(message);
const signedMessage = await signer.signMessage(msgHash);
const sig = await signer.signMessage(message);

expect(signedMessage).toMatch(/^0x/);

try {
await signer.signMessage(message);
} catch (error) {
expect(error).toBeInstanceOf(TurnkeyActivityError);

const { message, cause, activityId, activityStatus, activityType } =
error as TurnkeyActivityError;

expect({
message,
cause,
activityId,
activityStatus,
activityType,
}).toMatchInlineSnapshot(`
{
"activityId": null,
"activityStatus": null,
"activityType": null,
"cause": [Error: 400: Bad Request | Internal error 3: invalid hex payload: Hello Turnkey, signer/app/src/crypto.rs:161],
"message": "Failed to sign",
}
`);
}
expect(sig).toMatch(/^0x/);
expect(ethers.utils.verifyMessage(message, sig)).toEqual(expectedEthAddress);
});

function assertNonEmptyString(input: unknown, name: string): string {
Expand Down
16 changes: 10 additions & 6 deletions packages/ethers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers, type UnsignedTransaction } from "ethers";
import { ethers, type UnsignedTransaction, type Bytes } from "ethers";
import { PublicApiService, init as httpInit } from "@turnkey/http";

type TActivity = PublicApiService.TPostGetActivityResponse["activity"];
Expand Down Expand Up @@ -157,10 +157,14 @@ export class TurnkeySigner extends ethers.Signer {
return `0x${signedTx}`;
}

async signMessage(message: string): Promise<string> {
const nonHexPrefixedSerializedMessage = message.replace(/^0x/, "");
// Returns the signed prefixed-message. Per Ethers spec, this method treats:
// - Bytes as a binary message
// - string as a UTF8-message
// i.e. "0x1234" is a SIX (6) byte string, NOT 2 bytes of data
async signMessage(message: string | Bytes): Promise<string> {
const hashedMessage = ethers.utils.hashMessage(message);
const signedMessage = await this._signMessageWithErrorWrapping(
nonHexPrefixedSerializedMessage
hashedMessage
);
return `${signedMessage}`;
}
Expand Down Expand Up @@ -191,8 +195,8 @@ export class TurnkeySigner extends ethers.Signer {
parameters: {
privateKeyId: this.config.privateKeyId,
payload: message,
encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
hashFunction: "HASH_FUNCTION_KECCAK256",
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NO_OP",
},
timestampMs: String(Date.now()), // millisecond timestamp
},
Expand Down