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

fix: local account signers were not correctly handling hex inputs #42

Merged
merged 2 commits into from
Jun 30, 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
32 changes: 10 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
SimpleSmartContractAccount,
SmartAccountProvider,
type SimpleSmartAccountOwner,
LocalAccountSigner,
} from "@alchemy/aa-core";
import { mnemonicToAccount } from "viem/accounts";
import { polygonMumbai } from "viem/chains";
Expand All @@ -64,17 +65,10 @@ const SIMPLE_ACCOUNT_FACTORY_ADDRESS =
"0x9406Cc6185a346906296840746125a0E44976454";

// 1. define the EOA owner of the Smart Account
// This is just one exapmle of how to interact with EOAs, feel free to use any other interface
const ownerAccount = mnemonicToAccount(MNEMONIC);
// All that is important for defining an owner is that it provides a `signMessage` and `getAddress` function
const owner: SimpleSmartAccountOwner = {
// this should sign a message according to ERC-191
signMessage: async (msg) =>
ownerAccount.signMessage({
message: toHex(msg),
}),
getAddress: async () => ownerAccount.address,
};
// this uses a utility method for creating an account signer using mnemonic
// we also have a utility for creating an account signer from a private key
const owner: SimpleSmartAccountOwner =
LocalAccountSigner.mnemonicToAccountSigner(MNEMONIC);

// 2. initialize the provider and connect it to the account
const provider = new SmartAccountProvider(
Expand Down Expand Up @@ -109,6 +103,7 @@ const { hash } = provider.sendUserOperation({
import {
SimpleSmartContractAccount,
type SimpleSmartAccountOwner,
LocalAccountSigner,
} from "@alchemy/aa-core";
import { toHex } from "viem";
import { mnemonicToAccount } from "viem/accounts";
Expand All @@ -119,17 +114,10 @@ const SIMPLE_ACCOUNT_FACTORY_ADDRESS =
"0x9406Cc6185a346906296840746125a0E44976454";

// 1. define the EOA owner of the Smart Account
// This is just one exapmle of how to interact with EOAs, feel free to use any other interface
const ownerAccount = mnemonicToAccount(MNEMONIC);
// All that is important for defining an owner is that it provides a `signMessage` and `getAddress` function
const owner: SimpleSmartAccountOwner = {
// this should sign a message according to ERC-191
signMessage: async (msg) =>
ownerAccount.signMessage({
message: toHex(msg),
}),
getAddress: async () => ownerAccount.address,
};
// this uses a utility method for creating an account signer using mnemonic
// we also have a utility for creating an account signer from a private key
const owner: SimpleSmartAccountOwner =
LocalAccountSigner.mnemonicToAccountSigner(MNEMONIC);

// 2. initialize the provider and connect it to the account
let provider = new AlchemyProvider({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type Address,
type Hex,
} from "viem";
import { generatePrivateKey } from "viem/accounts";
import { polygonMumbai } from "viem/chains";
import {
KernelSmartContractAccount,
Expand All @@ -17,8 +18,7 @@ import { MockSigner } from "./mocks/mock-signer";
describe("Kernel Account Tests", () => {
//any wallet should work
const config = {
privateKey: process.env.OWNER_KEY as Hex,
ownerWallet: process.env.OWNER_WALLET,
privateKey: generatePrivateKey(),
mockWallet: "0x48D4d3536cDe7A257087206870c6B6E76e3D4ff4",
chain: polygonMumbai,
rpcProvider: `${polygonMumbai.rpcUrls.alchemy.http[0]}/${[
Expand Down Expand Up @@ -52,7 +52,7 @@ describe("Kernel Account Tests", () => {
);

function connect(index: bigint, owner = mockOwner) {
return provider.connect((provider) => account(index, owner));
return provider.connect((_provider) => account(index, owner));
}

function account(index: bigint, owner = mockOwner) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Address, Hex, SmartAccountSigner } from "@alchemy/aa-core";
import type { Address, Hex, SmartAccountSigner } from "@alchemy/aa-core";

export class MockSignerValidator implements SmartAccountSigner {
getAddress(): Promise<Address> {
return Promise.resolve("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc");
}

signMessage(msg: Uint8Array | Hex | string): Promise<Hex> {
async signMessage(msg: Uint8Array | Hex | string): Promise<Hex> {
switch (msg) {
case "0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc":
return "0x64e29e4786b3740ceffc2c1a932124ee74c29b552957ea3bde8913753d1763af4f03362e387d2badb33932e8fc4f7b3411a0a5ade32a5b708aa48c171632a6211b";
case "icanbreakthistestcase":
return "0xabd26de022c2785a7d86c5c388f4adef5d93358b39fbb757463bc9edc78b7b86566cb1ab8c7ff3a52b10d98de6398aacc7b48aec92a3e280065a47b9698209541b";
case "0xbc7299170f076afcbafe11da04482e72e3beccabcd82de0cd2797500e81b76c4":
return "0x6c21c7271c8403452c5a812c9ba776b33b12733953f154d36d989d379c92ec632b7a1997ca16203a7ef5fcd639bcaa3d5420b65e0774a8bca7fad6d1437024661c";
default:
throw new Error("Invalid message");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Address, Hex, SmartAccountSigner } from "@alchemy/aa-core";
import type { Address, Hex, SmartAccountSigner } from "@alchemy/aa-core";

export class MockSigner implements SmartAccountSigner {
getAddress(): Promise<Address> {
return Promise.resolve("0x48D4d3536cDe7A257087206870c6B6E76e3D4ff4");
}

signMessage(msg: Uint8Array | Hex | string): Promise<Hex> {
signMessage(_msg: Uint8Array | Hex | string): Promise<Hex> {
return Promise.resolve(
"0x4d61c5c27fb64b207cbf3bcf60d78e725659cff5f93db9a1316162117dff72aa631761619d93d4d97dfb761ba00b61f9274c6a4a76e494df644d968dd84ddcdb1c"
);
Expand Down
36 changes: 0 additions & 36 deletions packages/core/src/__tests__/private-key-signer.test.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the tests in this file were moved to local-account.test.ts

This file was deleted.

18 changes: 6 additions & 12 deletions packages/core/src/__tests__/simple-account.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isAddress, toHex } from "viem";
import { generatePrivateKey, mnemonicToAccount } from "viem/accounts";
import { isAddress } from "viem";
import { generatePrivateKey } from "viem/accounts";
import { polygonMumbai } from "viem/chains";
import {
SimpleSmartContractAccount,
type SimpleSmartAccountOwner,
} from "../account/simple.js";
import { SmartAccountProvider } from "../provider/base.js";
import { PrivateKeySigner } from "../signer/private-key.js";
import { LocalAccountSigner } from "../signer/local-account.js";
import type { BatchUserOperationCallData } from "../types.js";

const ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
Expand All @@ -16,14 +16,8 @@ const SIMPLE_ACCOUNT_FACTORY_ADDRESS =
"0x9406Cc6185a346906296840746125a0E44976454";

describe("Simple Account Tests", () => {
const ownerAccount = mnemonicToAccount(OWNER_MNEMONIC);
const owner: SimpleSmartAccountOwner = {
signMessage: async (msg) =>
ownerAccount.signMessage({
message: { raw: toHex(msg) },
}),
getAddress: async () => ownerAccount.address,
};
const owner: SimpleSmartAccountOwner =
LocalAccountSigner.mnemonicToAccountSigner(OWNER_MNEMONIC);
const chain = polygonMumbai;
const signer = new SmartAccountProvider(
`${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`,
Expand Down Expand Up @@ -111,7 +105,7 @@ describe("Simple Account Tests", () => {
});

it("should get counterfactual for undeployed account", async () => {
const owner = PrivateKeySigner.privateKeyToAccountSigner(
const owner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
);
const provider = new SmartAccountProvider(
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type {
} from "./account/simple.js";
export type * from "./account/types.js";
export { HdAccountSigner } from "./signer/hd-account.js";
export { LocalAccountSigner } from "./signer/local-account.js";
export { PrivateKeySigner } from "./signer/private-key.js";
export type { SmartAccountSigner } from "./signer/types.js";

Expand Down
40 changes: 6 additions & 34 deletions packages/core/src/signer/hd-account.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,7 @@
import type { Address } from "abitype";
import type { HDAccount, Hex } from "viem";
import { mnemonicToAccount } from "viem/accounts";
import type { SmartAccountSigner } from "./types";
import type { HDAccount } from "viem";
import { LocalAccountSigner } from "./local-account";

export class HdAccountSigner implements SmartAccountSigner {
owner: HDAccount;

constructor(owner: HDAccount) {
this.owner = owner;
}

signMessage(msg: Uint8Array | Hex | string): Promise<Hex> {
if (typeof msg === "string") {
return this.owner.signMessage({
message: msg,
});
} else {
return this.owner.signMessage({
message: {
raw: msg,
},
});
}
}

getAddress(): Promise<Address> {
return Promise.resolve(this.owner.address);
}

static mnemonicToAccountSigner(key: Hex): HdAccountSigner {
const owner = mnemonicToAccount(key);
return new HdAccountSigner(owner);
}
}
/**
* @deprecated use LocalAccountSigner instead
*/
export class HdAccountSigner extends LocalAccountSigner<HDAccount> {}
72 changes: 72 additions & 0 deletions packages/core/src/signer/local-account.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { LocalAccountSigner } from "./local-account";

describe("Local Account Signer Tests", () => {
describe("Using HD Account", () => {
const dummyMnemonic =
"test test test test test test test test test test test test";
const signer = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic);

it("should sign a hex message properly", async () => {
expect(
await signer.signMessage("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc")
).toMatchInlineSnapshot(
'"0x35761512143ffd8da07c93c5a0136424fe935b48e77076f501a57745c16268bf0d9a5d6209b12d5f8b62f96f0991372e046092fd6b1e3bfa610eb51607a28f7e1b"'
);
});

it("should sign a string message properly", async () => {
expect(
await signer.signMessage("icanbreakthistestcase")
).toMatchInlineSnapshot(
'"0x6b2efa82d72558efad294b727741e1eb9ff4800e2d66ce5f0373454ebdd13573630985612c1d618f49f173f65cf83fc131f798cb65ae66f90f8f0b9d4fdbca881b"'
);

expect(
await signer.signMessage("i will definately break this test case")
).toMatchInlineSnapshot(
'"0x1d9a20cfd63c179daab241d9e64a2549e1a47c1ca7c465df6f5b8c5720cee7266050b89b3874b384c88cd8325601572e79b3b626dc756a6aec5fff05d4ce5efc1b"'
);
});

it("should sign a byte array correctly", async () => {
expect(
await signer.signMessage(new TextEncoder().encode("hello, I'm moldy"))
).toEqual(await signer.signMessage("hello, I'm moldy"));
});
});

describe("Using Private Key Account", () => {
const dummyPrivateKey =
"0x022430a80f723d8789f0d4fb346bdd013b546e4b96fcacf8aceca2b1a65a19dc";
const signer =
LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey);

it("should sign a hex message properly", async () => {
expect(
await signer.signMessage("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc")
).toMatchInlineSnapshot(
'"0x91b6680c8f442f46ca71fee15cdd8c9e25693baeb4006d1908a453fd145315ce21a5e7f2ce9760fc993d65e8450fa5225d8dee12972886bdacbb989ca0b09c6c1b"'
);
});

it("should sign a string message properly", async () => {
expect(
await signer.signMessage("icanbreakthistestcase")
).toMatchInlineSnapshot(
'"0xabd26de022c2785a7d86c5c388f4adef5d93358b39fbb757463bc9edc78b7b86566cb1ab8c7ff3a52b10d98de6398aacc7b48aec92a3e280065a47b9698209541b"'
);

expect(
await signer.signMessage("i will definately break this test case")
).toMatchInlineSnapshot(
'"0x907004e990bb1bca76d9fed6bf4a6b614a8d11b6430657cb99ae83fae55c9bc60571876d8c01832e4661dd4312fa08b799b6f59c9b9abddd67e181abc6aef17e1c"'
);
});

it("should sign a byte array correctly", async () => {
expect(
await signer.signMessage(new TextEncoder().encode("hello, I'm moldy"))
).toEqual(await signer.signMessage("hello, I'm moldy"));
});
});
});
44 changes: 44 additions & 0 deletions packages/core/src/signer/local-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { isHex, type HDAccount, type Hex, type PrivateKeyAccount } from "viem";
import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts";
import type { SmartAccountSigner } from "./types.js";

export class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount>
implements SmartAccountSigner
{
private owner: T;
constructor(owner: T) {
this.owner = owner;
}

readonly signMessage: (msg: string | Uint8Array) => Promise<`0x${string}`> = (
msg
) => {
if (typeof msg === "string" && !isHex(msg)) {
return this.owner.signMessage({
message: msg,
});
} else {
return this.owner.signMessage({
message: {
raw: msg,
},
});
}
};

readonly getAddress: () => Promise<`0x${string}`> = async () => {
return this.owner.address;
};

static mnemonicToAccountSigner(key: string): LocalAccountSigner<HDAccount> {
const owner = mnemonicToAccount(key);
return new LocalAccountSigner(owner);
}

static privateKeyToAccountSigner(
key: Hex
): LocalAccountSigner<PrivateKeyAccount> {
const owner = privateKeyToAccount(key);
return new LocalAccountSigner(owner);
}
}
Loading