Skip to content
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
74 changes: 55 additions & 19 deletions packages/thirdweb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,24 +127,60 @@
},
"typesVersions": {
"*": {
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
"auth": ["./dist/types/exports/auth.d.ts"],
"chains": ["./dist/types/exports/chains.d.ts"],
"contract": ["./dist/types/exports/contract.d.ts"],
"deploys": ["./dist/types/exports/deploys.d.ts"],
"event": ["./dist/types/exports/event.d.ts"],
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
"pay": ["./dist/types/exports/pay.d.ts"],
"react": ["./dist/types/exports/react.d.ts"],
"react-native": ["./dist/types/exports/react-native.d.ts"],
"rpc": ["./dist/types/exports/rpc.d.ts"],
"storage": ["./dist/types/exports/storage.d.ts"],
"transaction": ["./dist/types/exports/transaction.d.ts"],
"utils": ["./dist/types/exports/utils.d.ts"],
"wallets": ["./dist/types/exports/wallets.d.ts"],
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"],
"modules": ["./dist/types/exports/modules.d.ts"],
"social": ["./dist/types/exports/social.d.ts"]
"adapters/*": [
"./dist/types/exports/adapters/*.d.ts"
],
"auth": [
"./dist/types/exports/auth.d.ts"
],
"chains": [
"./dist/types/exports/chains.d.ts"
],
"contract": [
"./dist/types/exports/contract.d.ts"
],
"deploys": [
"./dist/types/exports/deploys.d.ts"
],
"event": [
"./dist/types/exports/event.d.ts"
],
"extensions/*": [
"./dist/types/exports/extensions/*.d.ts"
],
"pay": [
"./dist/types/exports/pay.d.ts"
],
"react": [
"./dist/types/exports/react.d.ts"
],
"react-native": [
"./dist/types/exports/react-native.d.ts"
],
"rpc": [
"./dist/types/exports/rpc.d.ts"
],
"storage": [
"./dist/types/exports/storage.d.ts"
],
"transaction": [
"./dist/types/exports/transaction.d.ts"
],
"utils": [
"./dist/types/exports/utils.d.ts"
],
"wallets": [
"./dist/types/exports/wallets.d.ts"
],
"wallets/*": [
"./dist/types/exports/wallets/*.d.ts"
],
"modules": [
"./dist/types/exports/modules.d.ts"
],
"social": [
"./dist/types/exports/social.d.ts"
]
}
},
"browser": {
Expand Down Expand Up @@ -181,7 +217,7 @@
"fuse.js": "7.0.0",
"input-otp": "^1.4.1",
"mipd": "0.0.7",
"ox": "0.3.0",
"ox": "0.4.0",
"uqr": "0.1.2",
"viem": "2.21.51"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type TypedData, type TypedDataDefinition, hashTypedData } from "viem";
import * as ox__TypedData from "ox/TypedData";
import type { ThirdwebContract } from "../../contract/contract.js";
import { isHex } from "../../utils/encoding/hex.js";
import { isValidSignature } from "./__generated__/isValidSignature/read/isValidSignature.js";
Expand All @@ -7,11 +7,11 @@ import { isValidSignature } from "./__generated__/isValidSignature/read/isValidS
* @extension ERC1271
*/
export type CheckContractWalletSignTypedDataOptions<
typedData extends TypedData | Record<string, unknown>,
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
> = {
contract: ThirdwebContract;
data: TypedDataDefinition<typedData, primaryType>;
data: ox__TypedData.Definition<typedData, primaryType>;
signature: string;
};
const MAGIC_VALUE = "0x1626ba7e";
Expand Down Expand Up @@ -42,15 +42,19 @@ const MAGIC_VALUE = "0x1626ba7e";
* @returns A promise that resolves with a boolean indicating if the signature is valid.
*/
export async function checkContractWalletSignedTypedData<
typedData extends TypedData | Record<string, unknown>,
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(options: CheckContractWalletSignTypedDataOptions<typedData, primaryType>) {
if (!isHex(options.signature)) {
throw new Error("The signature must be a valid hex string.");
}
const result = await isValidSignature({
contract: options.contract,
hash: hashTypedData(options.data),
hash: ox__TypedData.hashStruct({
primaryType: options.data.primaryType,
data: options.data.message as Record<string, unknown>,
types: options.data.types as ox__TypedData.Definition["types"],
}),
signature: options.signature,
});
return result === MAGIC_VALUE;
Expand Down
26 changes: 15 additions & 11 deletions packages/thirdweb/src/utils/signatures/helpers/parse-typed-data.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import type { TypedData } from "abitype";
import type { TypedDataDefinition } from "viem";
import { type Hex, hexToNumber, isHex } from "../../encoding/hex.js";
import * as ox__Hex from "ox/Hex";
import type * as ox__TypedData from "ox/TypedData";
import type { Hex } from "../../encoding/hex.js";

type UnknownDomain = unknown & { chainId?: unknown }; // TODO: create our own typed data types so this is cleaner
type HexDomain = unknown & { chainId: Hex }; // TODO: create our own typed data types so this is cleaner
type UnknownDomain = unknown & { chainId?: unknown };
type HexDomain = unknown & { chainId: Hex };

/**
* @internal
*/
export function parseTypedData<
typedData extends TypedData | Record<string, unknown> = TypedData,
typedData extends
| ox__TypedData.TypedData
| Record<string, unknown> = ox__TypedData.TypedData,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(
typedData: TypedDataDefinition<typedData, primaryType>,
): TypedDataDefinition<typedData, primaryType> {
typedData: ox__TypedData.Definition<typedData, primaryType>,
): ox__TypedData.Definition<typedData, primaryType> {
const domain = typedData.domain as UnknownDomain;
if (domain?.chainId !== undefined && isHex(domain.chainId)) {
if (domain?.chainId !== undefined && ox__Hex.validate(domain.chainId)) {
typedData.domain = {
...(typedData.domain as HexDomain),
chainId: hexToNumber((typedData.domain as unknown as HexDomain).chainId),
} as unknown as TypedDataDefinition<typedData, primaryType>["domain"];
chainId: ox__Hex.toNumber(
(typedData.domain as unknown as HexDomain).chainId,
),
} as unknown as ox__TypedData.Definition<typedData, primaryType>["domain"];
}
return typedData;
}
21 changes: 15 additions & 6 deletions packages/thirdweb/src/utils/signatures/sign-message.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as ox__Hex from "ox/Hex";
import * as ox__PersonalMessage from "ox/PersonalMessage";
import * as ox__Secp256k1 from "ox/Secp256k1";
import * as ox__Signature from "ox/Signature";
import type { Account } from "../../wallets/interfaces/wallet.js";
import type { Hex } from "../encoding/hex.js";
import { hashMessage } from "../hashing/hashMessage.js";
import type { Prettify } from "../type-utils.js";
import { sign } from "./sign.js";
import { signatureToHex } from "./signature-to-hex.js";

type Message = Prettify<
| string
Expand Down Expand Up @@ -59,9 +60,17 @@ export function signMessage(
options: SignMessageOptions | { message: Message; account: Account },
): Hex | Promise<Hex> {
if ("privateKey" in options) {
const { message, privateKey } = options;
const signature = sign({ hash: hashMessage(message), privateKey });
return signatureToHex(signature);
const payload = ox__PersonalMessage.getSignPayload(
typeof options.message === "object"
? options.message.raw
: ox__Hex.fromString(options.message),
);

const signature = ox__Secp256k1.sign({
payload,
privateKey: options.privateKey,
});
return ox__Signature.toHex(signature);
}
if ("account" in options) {
const { message, account } = options;
Expand Down
15 changes: 0 additions & 15 deletions packages/thirdweb/src/utils/signatures/sign-typed-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,6 @@ test("domain: chainId", async () => {
);
});

test("domain: chainId hex", async () => {
expect(
signTypedData({
...typedData.complex,
domain: {
chainId: "0x1" as unknown as number,
},
primaryType: "Mail",
privateKey: ANVIL_PKEY_A,
}),
).toMatchInlineSnapshot(
'"0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db81c"',
);
});

test("domain: name", async () => {
expect(
signTypedData({
Expand Down
25 changes: 13 additions & 12 deletions packages/thirdweb/src/utils/signatures/sign-typed-data.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { TypedData } from "abitype";
import { type TypedDataDefinition, hashTypedData } from "viem";
import * as ox__Secp256k1 from "ox/Secp256k1";
import * as ox__Signature from "ox/Signature";
import * as ox__TypedData from "ox/TypedData";
import type { Hex } from "../encoding/hex.js";
import { parseTypedData } from "./helpers/parse-typed-data.js";
import { sign } from "./sign.js";
import { signatureToHex } from "./signature-to-hex.js";

export type SignTypedDataOptions<
typedData extends TypedData | Record<string, unknown> = TypedData,
typedData extends
| ox__TypedData.TypedData
| Record<string, unknown> = ox__TypedData.TypedData,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
> = TypedDataDefinition<typedData, primaryType> & {
> = ox__TypedData.Definition<typedData, primaryType> & {
privateKey: Hex;
};

Expand All @@ -28,17 +28,18 @@ export type SignTypedDataOptions<
* @utils
*/
export function signTypedData<
const typedData extends TypedData | Record<string, unknown>,
const typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain",
>(options: SignTypedDataOptions<typedData, primaryType>): Hex {
const { privateKey, ...typedData } =
options as unknown as SignTypedDataOptions;

const parsedTypeData = parseTypedData(typedData);
const payload = ox__TypedData.getSignPayload(typedData);

const signature = sign({
hash: hashTypedData(parsedTypeData), // TODO: Implement native hashTypedData
const signature = ox__Secp256k1.sign({
payload,
privateKey,
});
return signatureToHex(signature);

return ox__Signature.toHex(signature);
}
11 changes: 5 additions & 6 deletions packages/thirdweb/src/utils/signatures/sign.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { secp256k1 } from "@noble/curves/secp256k1";
import type { Signature } from "viem";
import * as ox__Secp256k1 from "ox/Secp256k1";

import { type Hex, toHex } from "../encoding/hex.js";

Expand Down Expand Up @@ -28,12 +27,12 @@ export type SignOptions = {
* ```
* @utils
*/
export function sign({ hash, privateKey }: SignOptions): Signature {
const { r, s, recovery } = secp256k1.sign(hash.slice(2), privateKey.slice(2));
export function sign({ hash, privateKey }: SignOptions) {
const { r, s, yParity } = ox__Secp256k1.sign({ payload: hash, privateKey });
return {
r: toHex(r, { size: 32 }),
s: toHex(s, { size: 32 }),
v: recovery ? 28n : 27n,
yParity: recovery,
v: yParity === 1 ? 28n : 27n,
yParity,
};
}
42 changes: 29 additions & 13 deletions packages/thirdweb/src/utils/signatures/signature-to-hex.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { secp256k1 } from "@noble/curves/secp256k1";
import type { Signature } from "viem";
import { type Hex, hexToBigInt } from "../encoding/hex.js";
import * as ox__Hex from "ox/Hex";
import * as ox__Signature from "ox/Signature";
import type { Hex } from "../encoding/hex.js";

/**
* Converts a signature to a hex string.
Expand All @@ -25,15 +25,31 @@
* ```
* @utils
*/
export function signatureToHex(signature: Signature): Hex {
export function signatureToHex(signature: {
r: Hex;
s: Hex;
v?: bigint | number | Hex | undefined;
yParity?: bigint | number | Hex | undefined;
}): Hex {
const { r, s, v, yParity } = signature;
const yParity_ = (() => {
if (yParity === 0 || yParity === 1) return yParity;
if (v && (v === 27n || v === 28n || v >= 35n)) return v % 2n === 0n ? 1 : 0;
throw new Error("Invalid `v` or `yParity` value");
})();
return `0x${new secp256k1.Signature(
hexToBigInt(r),
hexToBigInt(s),
).toCompactHex()}${yParity_ === 0 ? "1b" : "1c"}`;
return ox__Signature.toHex(
ox__Signature.from(
typeof yParity !== "undefined"
? {
r,
s,
yParity: !ox__Hex.validate(yParity)
? ox__Hex.fromNumber(yParity)
: yParity,

Check warning on line 43 in packages/thirdweb/src/utils/signatures/signature-to-hex.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/utils/signatures/signature-to-hex.ts#L43

Added line #L43 was not covered by tests
}
: {
r,
s,
v:
!ox__Hex.validate(v) && typeof v !== "undefined"
? ox__Hex.fromNumber(v)
: v,

Check warning on line 51 in packages/thirdweb/src/utils/signatures/signature-to-hex.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/utils/signatures/signature-to-hex.ts#L51

Added line #L51 was not covered by tests
},
),
);
}
Loading
Loading