Skip to content

Commit bfb53cb

Browse files
authored
[SDK] Refactor: Migrate signatures to Ox (#5532)
1 parent e2b2c1e commit bfb53cb

File tree

17 files changed

+456
-273
lines changed

17 files changed

+456
-273
lines changed

packages/thirdweb/package.json

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -127,24 +127,60 @@
127127
},
128128
"typesVersions": {
129129
"*": {
130-
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
131-
"auth": ["./dist/types/exports/auth.d.ts"],
132-
"chains": ["./dist/types/exports/chains.d.ts"],
133-
"contract": ["./dist/types/exports/contract.d.ts"],
134-
"deploys": ["./dist/types/exports/deploys.d.ts"],
135-
"event": ["./dist/types/exports/event.d.ts"],
136-
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
137-
"pay": ["./dist/types/exports/pay.d.ts"],
138-
"react": ["./dist/types/exports/react.d.ts"],
139-
"react-native": ["./dist/types/exports/react-native.d.ts"],
140-
"rpc": ["./dist/types/exports/rpc.d.ts"],
141-
"storage": ["./dist/types/exports/storage.d.ts"],
142-
"transaction": ["./dist/types/exports/transaction.d.ts"],
143-
"utils": ["./dist/types/exports/utils.d.ts"],
144-
"wallets": ["./dist/types/exports/wallets.d.ts"],
145-
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"],
146-
"modules": ["./dist/types/exports/modules.d.ts"],
147-
"social": ["./dist/types/exports/social.d.ts"]
130+
"adapters/*": [
131+
"./dist/types/exports/adapters/*.d.ts"
132+
],
133+
"auth": [
134+
"./dist/types/exports/auth.d.ts"
135+
],
136+
"chains": [
137+
"./dist/types/exports/chains.d.ts"
138+
],
139+
"contract": [
140+
"./dist/types/exports/contract.d.ts"
141+
],
142+
"deploys": [
143+
"./dist/types/exports/deploys.d.ts"
144+
],
145+
"event": [
146+
"./dist/types/exports/event.d.ts"
147+
],
148+
"extensions/*": [
149+
"./dist/types/exports/extensions/*.d.ts"
150+
],
151+
"pay": [
152+
"./dist/types/exports/pay.d.ts"
153+
],
154+
"react": [
155+
"./dist/types/exports/react.d.ts"
156+
],
157+
"react-native": [
158+
"./dist/types/exports/react-native.d.ts"
159+
],
160+
"rpc": [
161+
"./dist/types/exports/rpc.d.ts"
162+
],
163+
"storage": [
164+
"./dist/types/exports/storage.d.ts"
165+
],
166+
"transaction": [
167+
"./dist/types/exports/transaction.d.ts"
168+
],
169+
"utils": [
170+
"./dist/types/exports/utils.d.ts"
171+
],
172+
"wallets": [
173+
"./dist/types/exports/wallets.d.ts"
174+
],
175+
"wallets/*": [
176+
"./dist/types/exports/wallets/*.d.ts"
177+
],
178+
"modules": [
179+
"./dist/types/exports/modules.d.ts"
180+
],
181+
"social": [
182+
"./dist/types/exports/social.d.ts"
183+
]
148184
}
149185
},
150186
"browser": {
@@ -181,7 +217,7 @@
181217
"fuse.js": "7.0.0",
182218
"input-otp": "^1.4.1",
183219
"mipd": "0.0.7",
184-
"ox": "0.3.0",
220+
"ox": "0.4.0",
185221
"uqr": "0.1.2",
186222
"viem": "2.21.51"
187223
},

packages/thirdweb/src/extensions/erc1271/checkContractWalletSignedTypedData.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type TypedData, type TypedDataDefinition, hashTypedData } from "viem";
1+
import * as ox__TypedData from "ox/TypedData";
22
import type { ThirdwebContract } from "../../contract/contract.js";
33
import { isHex } from "../../utils/encoding/hex.js";
44
import { isValidSignature } from "./__generated__/isValidSignature/read/isValidSignature.js";
@@ -7,11 +7,11 @@ import { isValidSignature } from "./__generated__/isValidSignature/read/isValidS
77
* @extension ERC1271
88
*/
99
export type CheckContractWalletSignTypedDataOptions<
10-
typedData extends TypedData | Record<string, unknown>,
10+
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
1111
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
1212
> = {
1313
contract: ThirdwebContract;
14-
data: TypedDataDefinition<typedData, primaryType>;
14+
data: ox__TypedData.Definition<typedData, primaryType>;
1515
signature: string;
1616
};
1717
const MAGIC_VALUE = "0x1626ba7e";
@@ -42,15 +42,19 @@ const MAGIC_VALUE = "0x1626ba7e";
4242
* @returns A promise that resolves with a boolean indicating if the signature is valid.
4343
*/
4444
export async function checkContractWalletSignedTypedData<
45-
typedData extends TypedData | Record<string, unknown>,
45+
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
4646
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
4747
>(options: CheckContractWalletSignTypedDataOptions<typedData, primaryType>) {
4848
if (!isHex(options.signature)) {
4949
throw new Error("The signature must be a valid hex string.");
5050
}
5151
const result = await isValidSignature({
5252
contract: options.contract,
53-
hash: hashTypedData(options.data),
53+
hash: ox__TypedData.hashStruct({
54+
primaryType: options.data.primaryType,
55+
data: options.data.message as Record<string, unknown>,
56+
types: options.data.types as ox__TypedData.Definition["types"],
57+
}),
5458
signature: options.signature,
5559
});
5660
return result === MAGIC_VALUE;
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1-
import type { TypedData } from "abitype";
2-
import type { TypedDataDefinition } from "viem";
3-
import { type Hex, hexToNumber, isHex } from "../../encoding/hex.js";
1+
import * as ox__Hex from "ox/Hex";
2+
import type * as ox__TypedData from "ox/TypedData";
3+
import type { Hex } from "../../encoding/hex.js";
44

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

88
/**
99
* @internal
1010
*/
1111
export function parseTypedData<
12-
typedData extends TypedData | Record<string, unknown> = TypedData,
12+
typedData extends
13+
| ox__TypedData.TypedData
14+
| Record<string, unknown> = ox__TypedData.TypedData,
1315
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
1416
>(
15-
typedData: TypedDataDefinition<typedData, primaryType>,
16-
): TypedDataDefinition<typedData, primaryType> {
17+
typedData: ox__TypedData.Definition<typedData, primaryType>,
18+
): ox__TypedData.Definition<typedData, primaryType> {
1719
const domain = typedData.domain as UnknownDomain;
18-
if (domain?.chainId !== undefined && isHex(domain.chainId)) {
20+
if (domain?.chainId !== undefined && ox__Hex.validate(domain.chainId)) {
1921
typedData.domain = {
2022
...(typedData.domain as HexDomain),
21-
chainId: hexToNumber((typedData.domain as unknown as HexDomain).chainId),
22-
} as unknown as TypedDataDefinition<typedData, primaryType>["domain"];
23+
chainId: ox__Hex.toNumber(
24+
(typedData.domain as unknown as HexDomain).chainId,
25+
),
26+
} as unknown as ox__TypedData.Definition<typedData, primaryType>["domain"];
2327
}
2428
return typedData;
2529
}

packages/thirdweb/src/utils/signatures/sign-message.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import * as ox__Hex from "ox/Hex";
2+
import * as ox__PersonalMessage from "ox/PersonalMessage";
3+
import * as ox__Secp256k1 from "ox/Secp256k1";
4+
import * as ox__Signature from "ox/Signature";
15
import type { Account } from "../../wallets/interfaces/wallet.js";
26
import type { Hex } from "../encoding/hex.js";
3-
import { hashMessage } from "../hashing/hashMessage.js";
47
import type { Prettify } from "../type-utils.js";
5-
import { sign } from "./sign.js";
6-
import { signatureToHex } from "./signature-to-hex.js";
78

89
type Message = Prettify<
910
| string
@@ -59,9 +60,17 @@ export function signMessage(
5960
options: SignMessageOptions | { message: Message; account: Account },
6061
): Hex | Promise<Hex> {
6162
if ("privateKey" in options) {
62-
const { message, privateKey } = options;
63-
const signature = sign({ hash: hashMessage(message), privateKey });
64-
return signatureToHex(signature);
63+
const payload = ox__PersonalMessage.getSignPayload(
64+
typeof options.message === "object"
65+
? options.message.raw
66+
: ox__Hex.fromString(options.message),
67+
);
68+
69+
const signature = ox__Secp256k1.sign({
70+
payload,
71+
privateKey: options.privateKey,
72+
});
73+
return ox__Signature.toHex(signature);
6574
}
6675
if ("account" in options) {
6776
const { message, account } = options;

packages/thirdweb/src/utils/signatures/sign-typed-data.test.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,6 @@ test("domain: chainId", async () => {
8383
);
8484
});
8585

86-
test("domain: chainId hex", async () => {
87-
expect(
88-
signTypedData({
89-
...typedData.complex,
90-
domain: {
91-
chainId: "0x1" as unknown as number,
92-
},
93-
primaryType: "Mail",
94-
privateKey: ANVIL_PKEY_A,
95-
}),
96-
).toMatchInlineSnapshot(
97-
'"0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db81c"',
98-
);
99-
});
100-
10186
test("domain: name", async () => {
10287
expect(
10388
signTypedData({
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import type { TypedData } from "abitype";
2-
import { type TypedDataDefinition, hashTypedData } from "viem";
1+
import * as ox__Secp256k1 from "ox/Secp256k1";
2+
import * as ox__Signature from "ox/Signature";
3+
import * as ox__TypedData from "ox/TypedData";
34
import type { Hex } from "../encoding/hex.js";
4-
import { parseTypedData } from "./helpers/parse-typed-data.js";
5-
import { sign } from "./sign.js";
6-
import { signatureToHex } from "./signature-to-hex.js";
75

86
export type SignTypedDataOptions<
9-
typedData extends TypedData | Record<string, unknown> = TypedData,
7+
typedData extends
8+
| ox__TypedData.TypedData
9+
| Record<string, unknown> = ox__TypedData.TypedData,
1010
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
11-
> = TypedDataDefinition<typedData, primaryType> & {
11+
> = ox__TypedData.Definition<typedData, primaryType> & {
1212
privateKey: Hex;
1313
};
1414

@@ -28,17 +28,18 @@ export type SignTypedDataOptions<
2828
* @utils
2929
*/
3030
export function signTypedData<
31-
const typedData extends TypedData | Record<string, unknown>,
31+
const typedData extends ox__TypedData.TypedData | Record<string, unknown>,
3232
primaryType extends keyof typedData | "EIP712Domain",
3333
>(options: SignTypedDataOptions<typedData, primaryType>): Hex {
3434
const { privateKey, ...typedData } =
3535
options as unknown as SignTypedDataOptions;
3636

37-
const parsedTypeData = parseTypedData(typedData);
37+
const payload = ox__TypedData.getSignPayload(typedData);
3838

39-
const signature = sign({
40-
hash: hashTypedData(parsedTypeData), // TODO: Implement native hashTypedData
39+
const signature = ox__Secp256k1.sign({
40+
payload,
4141
privateKey,
4242
});
43-
return signatureToHex(signature);
43+
44+
return ox__Signature.toHex(signature);
4445
}

packages/thirdweb/src/utils/signatures/sign.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { secp256k1 } from "@noble/curves/secp256k1";
2-
import type { Signature } from "viem";
1+
import * as ox__Secp256k1 from "ox/Secp256k1";
32

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

@@ -28,12 +27,12 @@ export type SignOptions = {
2827
* ```
2928
* @utils
3029
*/
31-
export function sign({ hash, privateKey }: SignOptions): Signature {
32-
const { r, s, recovery } = secp256k1.sign(hash.slice(2), privateKey.slice(2));
30+
export function sign({ hash, privateKey }: SignOptions) {
31+
const { r, s, yParity } = ox__Secp256k1.sign({ payload: hash, privateKey });
3332
return {
3433
r: toHex(r, { size: 32 }),
3534
s: toHex(s, { size: 32 }),
36-
v: recovery ? 28n : 27n,
37-
yParity: recovery,
35+
v: yParity === 1 ? 28n : 27n,
36+
yParity,
3837
};
3938
}

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

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { secp256k1 } from "@noble/curves/secp256k1";
2-
import type { Signature } from "viem";
3-
import { type Hex, hexToBigInt } from "../encoding/hex.js";
1+
import * as ox__Hex from "ox/Hex";
2+
import * as ox__Signature from "ox/Signature";
3+
import type { Hex } from "../encoding/hex.js";
44

55
/**
66
* Converts a signature to a hex string.
@@ -25,15 +25,31 @@ import { type Hex, hexToBigInt } from "../encoding/hex.js";
2525
* ```
2626
* @utils
2727
*/
28-
export function signatureToHex(signature: Signature): Hex {
28+
export function signatureToHex(signature: {
29+
r: Hex;
30+
s: Hex;
31+
v?: bigint | number | Hex | undefined;
32+
yParity?: bigint | number | Hex | undefined;
33+
}): Hex {
2934
const { r, s, v, yParity } = signature;
30-
const yParity_ = (() => {
31-
if (yParity === 0 || yParity === 1) return yParity;
32-
if (v && (v === 27n || v === 28n || v >= 35n)) return v % 2n === 0n ? 1 : 0;
33-
throw new Error("Invalid `v` or `yParity` value");
34-
})();
35-
return `0x${new secp256k1.Signature(
36-
hexToBigInt(r),
37-
hexToBigInt(s),
38-
).toCompactHex()}${yParity_ === 0 ? "1b" : "1c"}`;
35+
return ox__Signature.toHex(
36+
ox__Signature.from(
37+
typeof yParity !== "undefined"
38+
? {
39+
r,
40+
s,
41+
yParity: !ox__Hex.validate(yParity)
42+
? ox__Hex.fromNumber(yParity)
43+
: yParity,
44+
}
45+
: {
46+
r,
47+
s,
48+
v:
49+
!ox__Hex.validate(v) && typeof v !== "undefined"
50+
? ox__Hex.fromNumber(v)
51+
: v,
52+
},
53+
),
54+
);
3955
}

0 commit comments

Comments
 (0)