@oleary-labs/signet-sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,18 +5,60 @@
5
5
  * structured payload that the node verifies against the key's scope
6
6
  * before computing the hash and signing.
7
7
  */
8
- import { signKeygenRequest } from "./request";
9
- // ---------------------------------------------------------------------------
10
- // Scope construction
11
- // ---------------------------------------------------------------------------
8
+ import { hashTypedData, keccak256, stringToBytes } from "viem";
9
+ import { signSignRequest } from "./request";
10
+ import { hexToBytes } from "./session";
11
+ /**
12
+ * Encode an EIP-712 type per the spec: the primary type's definition
13
+ * followed by its referenced struct types in alphabetical order, e.g.
14
+ * `TransferWithAuthorization(address from,address to,uint256 value,...)`.
15
+ * Matches go-ethereum's `apitypes.TypeHash` encoding used by the node, so
16
+ * the resulting typeHash byte-matches what the node verifies.
17
+ */
18
+ function encodeEIP712Type(primaryType, types) {
19
+ const deps = new Set();
20
+ const visit = (t) => {
21
+ const base = t.replace(/(\[\d*\])+$/, ""); // strip array suffixes
22
+ if (!types[base] || deps.has(base))
23
+ return;
24
+ deps.add(base);
25
+ for (const f of types[base])
26
+ visit(f.type);
27
+ };
28
+ for (const f of types[primaryType] ?? [])
29
+ visit(f.type);
30
+ const sorted = [...deps].filter((d) => d !== primaryType).sort();
31
+ const encodeOne = (t) => `${t}(${types[t].map((f) => `${f.type} ${f.name}`).join(",")})`;
32
+ return [primaryType, ...sorted].map(encodeOne).join("");
33
+ }
34
+ /**
35
+ * EIP-712 typeHash: keccak256(encodeType(primaryType)). This is the value a
36
+ * 0x03 scope binds, and the same value the verifying contract uses — so a
37
+ * key scoped to one method (e.g. TransferWithAuthorization) cannot sign a
38
+ * different method on the same contract (e.g. permit).
39
+ */
40
+ export function eip712TypeHash(primaryType, types) {
41
+ if (!types[primaryType]) {
42
+ throw new Error(`primaryType "${primaryType}" not declared in types`);
43
+ }
44
+ return keccak256(stringToBytes(encodeEIP712Type(primaryType, types)));
45
+ }
12
46
  /**
13
- * Build an EIP-712 domain scope (scheme 0x03).
47
+ * Build an EIP-712 domain+type scope (scheme 0x03).
14
48
  *
15
49
  * Format: 0x03 | chainId (8 bytes, uint64 BE) | verifyingContract (20 bytes)
16
- * Total: 29 bytes.
50
+ * | typeHash (32 bytes). Total: 61 bytes.
51
+ *
52
+ * `typeHash` is keccak256(encodeType(primaryType)) — see {@link eip712TypeHash}.
53
+ * Binding the type (not just the domain) prevents a key authorized for one
54
+ * typed-data method from signing a different method on the same contract.
17
55
  */
18
- export function buildEIP712Scope(chainId, verifyingContract) {
19
- const buf = new Uint8Array(29);
56
+ export function buildEIP712Scope(chainId, verifyingContract, typeHash) {
57
+ const th = typeHash.startsWith("0x") ? typeHash.slice(2) : typeHash;
58
+ if (th.length !== 64) {
59
+ throw new Error(`typeHash must be 32 bytes (64 hex chars), got ${th.length}`);
60
+ }
61
+ const buf = new Uint8Array(61);
20
62
  buf[0] = 0x03;
21
63
  // chainId as 8-byte big-endian
22
64
  const view = new DataView(buf.buffer);
@@ -28,8 +70,22 @@ export function buildEIP712Scope(chainId, verifyingContract) {
28
70
  for (let i = 0; i < 20; i++) {
29
71
  buf[9 + i] = parseInt(addr.slice(i * 2, i * 2 + 2), 16);
30
72
  }
73
+ // typeHash as 32 bytes
74
+ for (let i = 0; i < 32; i++) {
75
+ buf[29 + i] = parseInt(th.slice(i * 2, i * 2 + 2), 16);
76
+ }
31
77
  return "0x" + Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
32
78
  }
79
+ /**
80
+ * Convenience: build a 0x03 scope directly from an EIP-712 typed-data sample,
81
+ * deriving chainId, verifyingContract, and typeHash from it. Any sample with
82
+ * the intended domain + primary type works (message values are irrelevant to
83
+ * the scope). This is the recommended way to scope a key for a given method.
84
+ */
85
+ export function buildEIP712ScopeForTypedData(typedData) {
86
+ const typeHash = eip712TypeHash(typedData.primaryType, typedData.types);
87
+ return buildEIP712Scope(typedData.domain.chainId, typedData.domain.verifyingContract, typeHash);
88
+ }
33
89
  // ---------------------------------------------------------------------------
34
90
  // Structured signing
35
91
  // ---------------------------------------------------------------------------
@@ -50,13 +106,17 @@ export function buildEIP712Scope(chainId, verifyingContract) {
50
106
  * @param identity - For auth key cert sessions
51
107
  */
52
108
  export async function signTypedData(nodeUrl, proxyEndpoint, groupId, keyId, curve, typedData, sessionKeypair, claims, identity) {
53
- // Build session-authenticated request (no message hash payload is sent separately)
54
- // The canonical hash must use the full sub-key ID (identity + suffix).
109
+ // The canonical request hash must use the full sub-key ID (identity + suffix).
55
110
  // Extract suffix from keyId: "oauth:iss:sub:suffix" → suffix is last segment
56
111
  // The identity param is "iss:sub", so we need to add the suffix.
57
112
  const keyParts = keyId.split(":");
58
113
  const keySuffix = keyParts.length > 1 ? keyParts[keyParts.length - 1] : undefined;
59
- const signReq = await signKeygenRequest(sessionKeypair, claims, groupId, keySuffix, identity);
114
+ // Compute the EIP-712 hash locally and bind it into the session request
115
+ // signature. The nodes recompute hashTypedData from the payload and verify
116
+ // the request signature against it — without this binding, a malicious
117
+ // initiator could substitute any payload matching the key's scope.
118
+ const payloadHash = hexToBytes(hashTypedData(typedData).slice(2));
119
+ const signReq = await signSignRequest(sessionKeypair, claims, groupId, payloadHash, keySuffix, identity);
60
120
  const res = await fetch(proxyEndpoint, {
61
121
  method: "POST",
62
122
  headers: {
@@ -1 +1 @@
1
- {"version":3,"file":"scopedSign.js","sourceRoot":"","sources":["../src/scopedSign.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scopedSign.js","sourceRoot":"","sources":["../src/scopedSign.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ZK proof witness construction for the jwt_auth noir circuit.
3
+ *
4
+ * Uses generateInputs from noir-jwt for the core RSA/JWT witness,
5
+ * then adds the claim assertions and session_pub binding that our
6
+ * circuit requires.
7
+ */
8
+ import type { IdTokenClaims } from "./types";
9
+ /** Full witness for the jwt_auth circuit (Prover.toml format). */
10
+ export interface FullCircuitWitness {
11
+ data: {
12
+ storage: number[];
13
+ len: number;
14
+ };
15
+ base64_decode_offset: number;
16
+ pubkey_modulus_limbs: string[];
17
+ redc_params_limbs: string[];
18
+ signature_limbs: string[];
19
+ expected_iss: {
20
+ storage: number[];
21
+ len: number;
22
+ };
23
+ expected_sub: {
24
+ storage: number[];
25
+ len: number;
26
+ };
27
+ expected_exp: number;
28
+ expected_aud: {
29
+ storage: number[];
30
+ len: number;
31
+ };
32
+ expected_azp: {
33
+ storage: number[];
34
+ len: number;
35
+ };
36
+ _session_pub: number[];
37
+ }
38
+ /**
39
+ * Build full circuit witness from a JWT, JWKS key, and session public key.
40
+ *
41
+ * @param jwt — raw JWT string
42
+ * @param jwksKey — the RSA public key from Google JWKS (as JsonWebKey)
43
+ * @param claims — decoded JWT claims
44
+ * @param sessionPubBytes — 33-byte compressed secp256k1 session public key
45
+ */
46
+ export declare function buildFullWitness(jwt: string, jwksKey: JsonWebKey, claims: IdTokenClaims, sessionPubBytes: number[]): Promise<FullCircuitWitness>;
47
+ /**
48
+ * Serialize a FullCircuitWitness to Prover.toml format for nargo.
49
+ */
50
+ export declare function witnessToProverToml(w: FullCircuitWitness): string;
51
+ //# sourceMappingURL=witness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"witness.d.ts","sourceRoot":"","sources":["../src/witness.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IAEjC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAG1B,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAGjD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,kBAAkB,CAAC,CA8B7B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,kBAAkB,GAAG,MAAM,CA8BjE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * ZK proof witness construction for the jwt_auth noir circuit.
3
+ *
4
+ * Uses generateInputs from noir-jwt for the core RSA/JWT witness,
5
+ * then adds the claim assertions and session_pub binding that our
6
+ * circuit requires.
7
+ */
8
+ import { generateInputs } from "./generate-inputs";
9
+ /**
10
+ * Build full circuit witness from a JWT, JWKS key, and session public key.
11
+ *
12
+ * @param jwt — raw JWT string
13
+ * @param jwksKey — the RSA public key from Google JWKS (as JsonWebKey)
14
+ * @param claims — decoded JWT claims
15
+ * @param sessionPubBytes — 33-byte compressed secp256k1 session public key
16
+ */
17
+ export async function buildFullWitness(jwt, jwksKey, claims, sessionPubBytes) {
18
+ // Generate core JWT/RSA inputs using noir-jwt library
19
+ const inputs = await generateInputs({
20
+ jwt,
21
+ pubkey: jwksKey,
22
+ maxSignedDataLength: 1024,
23
+ });
24
+ if (!inputs.data) {
25
+ throw new Error("Expected full data mode (no partial SHA)");
26
+ }
27
+ return {
28
+ // Core JWT/RSA witness
29
+ data: inputs.data,
30
+ base64_decode_offset: inputs.base64_decode_offset,
31
+ pubkey_modulus_limbs: inputs.pubkey_modulus_limbs,
32
+ redc_params_limbs: inputs.redc_params_limbs,
33
+ signature_limbs: inputs.signature_limbs,
34
+ // Claim assertions
35
+ expected_iss: toBoundedVec(claims.iss, 128),
36
+ expected_sub: toBoundedVec(claims.sub, 128),
37
+ expected_exp: claims.exp,
38
+ expected_aud: toBoundedVec(claims.aud, 128),
39
+ expected_azp: toBoundedVec(claims.azp, 128),
40
+ // Session binding
41
+ _session_pub: sessionPubBytes,
42
+ };
43
+ }
44
+ /**
45
+ * Serialize a FullCircuitWitness to Prover.toml format for nargo.
46
+ */
47
+ export function witnessToProverToml(w) {
48
+ const lines = [];
49
+ // Bare keys must come before [table] sections in TOML
50
+ lines.push(`base64_decode_offset = ${w.base64_decode_offset}`);
51
+ lines.push(`expected_exp = ${w.expected_exp}`);
52
+ lines.push(`redc_params_limbs = [${w.redc_params_limbs.map((l) => `"${l}"`).join(", ")}]`);
53
+ lines.push(`signature_limbs = [${w.signature_limbs.map((l) => `"${l}"`).join(", ")}]`);
54
+ lines.push(`pubkey_modulus_limbs = [${w.pubkey_modulus_limbs.map((l) => `"${l}"`).join(", ")}]`);
55
+ lines.push(`_session_pub = [${w._session_pub.join(", ")}]`);
56
+ lines.push("");
57
+ // BoundedVec tables
58
+ lines.push("[data]");
59
+ lines.push(`storage = [${w.data.storage.join(", ")}]`);
60
+ lines.push(`len = ${w.data.len}`);
61
+ lines.push("");
62
+ writeBoundedVecToml(lines, "expected_iss", w.expected_iss);
63
+ writeBoundedVecToml(lines, "expected_sub", w.expected_sub);
64
+ writeBoundedVecToml(lines, "expected_aud", w.expected_aud);
65
+ writeBoundedVecToml(lines, "expected_azp", w.expected_azp);
66
+ return lines.join("\n");
67
+ }
68
+ function toBoundedVec(value, maxLen) {
69
+ const storage = new Array(maxLen).fill(0);
70
+ for (let i = 0; i < value.length; i++) {
71
+ storage[i] = value.charCodeAt(i);
72
+ }
73
+ return { storage, len: value.length };
74
+ }
75
+ function writeBoundedVecToml(lines, name, vec) {
76
+ lines.push(`[${name}]`);
77
+ lines.push(`storage = [${vec.storage.join(", ")}]`);
78
+ lines.push(`len = ${vec.len}`);
79
+ lines.push("");
80
+ }
81
+ //# sourceMappingURL=witness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"witness.js","sourceRoot":"","sources":["../src/witness.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,31 +1,91 @@
1
1
  {
2
2
  "name": "@oleary-labs/signet-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Signet DKMS SDK — threshold signing, key management, delegation, and x402 payments",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
- ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
10
- "./session": { "import": "./dist/session.js", "types": "./dist/session.d.ts" },
11
- "./request": { "import": "./dist/request.js", "types": "./dist/request.d.ts" },
12
- "./keygen": { "import": "./dist/keygen.js", "types": "./dist/keygen.d.ts" },
13
- "./types": { "import": "./dist/types.js", "types": "./dist/types.d.ts" },
14
- "./jwks": { "import": "./dist/jwks.js", "types": "./dist/jwks.d.ts" },
15
- "./oauth": { "import": "./dist/oauth.js", "types": "./dist/oauth.d.ts" },
16
- "./bootstrap": { "import": "./dist/bootstrap.js", "types": "./dist/bootstrap.d.ts" },
17
- "./authkey-session": { "import": "./dist/authkey-session.js", "types": "./dist/authkey-session.d.ts" },
18
- "./proof": { "import": "./dist/proof.js", "types": "./dist/proof.d.ts" },
19
- "./server-prover": { "import": "./dist/server-prover.js", "types": "./dist/server-prover.d.ts" },
20
- "./witness": { "import": "./dist/witness.js", "types": "./dist/witness.d.ts" },
21
- "./jwt": { "import": "./dist/jwt.js", "types": "./dist/jwt.d.ts" },
22
- "./admin": { "import": "./dist/admin.js", "types": "./dist/admin.d.ts" },
23
- "./delegate": { "import": "./dist/delegate.js", "types": "./dist/delegate.d.ts" },
24
- "./scopedSign": { "import": "./dist/scopedSign.js", "types": "./dist/scopedSign.d.ts" },
25
- "./frostVerify": { "import": "./dist/frostVerify.js", "types": "./dist/frostVerify.d.ts" },
26
- "./x402": { "import": "./dist/x402.js", "types": "./dist/x402.d.ts" },
27
- "./userop": { "import": "./dist/userop.js", "types": "./dist/userop.d.ts" },
28
- "./bundler": { "import": "./dist/bundler.js", "types": "./dist/bundler.d.ts" }
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./session": {
14
+ "import": "./dist/session.js",
15
+ "types": "./dist/session.d.ts"
16
+ },
17
+ "./request": {
18
+ "import": "./dist/request.js",
19
+ "types": "./dist/request.d.ts"
20
+ },
21
+ "./keygen": {
22
+ "import": "./dist/keygen.js",
23
+ "types": "./dist/keygen.d.ts"
24
+ },
25
+ "./types": {
26
+ "import": "./dist/types.js",
27
+ "types": "./dist/types.d.ts"
28
+ },
29
+ "./jwks": {
30
+ "import": "./dist/jwks.js",
31
+ "types": "./dist/jwks.d.ts"
32
+ },
33
+ "./oauth": {
34
+ "import": "./dist/oauth.js",
35
+ "types": "./dist/oauth.d.ts"
36
+ },
37
+ "./bootstrap": {
38
+ "import": "./dist/bootstrap.js",
39
+ "types": "./dist/bootstrap.d.ts"
40
+ },
41
+ "./authkey-session": {
42
+ "import": "./dist/authkey-session.js",
43
+ "types": "./dist/authkey-session.d.ts"
44
+ },
45
+ "./proof": {
46
+ "import": "./dist/proof.js",
47
+ "types": "./dist/proof.d.ts"
48
+ },
49
+ "./server-prover": {
50
+ "import": "./dist/server-prover.js",
51
+ "types": "./dist/server-prover.d.ts"
52
+ },
53
+ "./witness": {
54
+ "import": "./dist/witness.js",
55
+ "types": "./dist/witness.d.ts"
56
+ },
57
+ "./jwt": {
58
+ "import": "./dist/jwt.js",
59
+ "types": "./dist/jwt.d.ts"
60
+ },
61
+ "./admin": {
62
+ "import": "./dist/admin.js",
63
+ "types": "./dist/admin.d.ts"
64
+ },
65
+ "./delegate": {
66
+ "import": "./dist/delegate.js",
67
+ "types": "./dist/delegate.d.ts"
68
+ },
69
+ "./scopedSign": {
70
+ "import": "./dist/scopedSign.js",
71
+ "types": "./dist/scopedSign.d.ts"
72
+ },
73
+ "./frostVerify": {
74
+ "import": "./dist/frostVerify.js",
75
+ "types": "./dist/frostVerify.d.ts"
76
+ },
77
+ "./x402": {
78
+ "import": "./dist/x402.js",
79
+ "types": "./dist/x402.d.ts"
80
+ },
81
+ "./userop": {
82
+ "import": "./dist/userop.js",
83
+ "types": "./dist/userop.d.ts"
84
+ },
85
+ "./bundler": {
86
+ "import": "./dist/bundler.js",
87
+ "types": "./dist/bundler.d.ts"
88
+ }
29
89
  },
30
90
  "files": [
31
91
  "dist",
@@ -38,14 +98,20 @@
38
98
  },
39
99
  "peerDependencies": {
40
100
  "viem": ">=2.0.0",
41
- "@noir-lang/noir_js": ">=1.0.0-beta.0",
42
- "@aztec/bb.js": ">=0.80.0",
43
- "@oleary-labs/signet-circuits": ">=0.1.0"
101
+ "@noir-lang/noir_js": "1.0.0-beta.11",
102
+ "@aztec/bb.js": "0.82.2",
103
+ "@oleary-labs/signet-circuits": "^0.3.0"
44
104
  },
45
105
  "peerDependenciesMeta": {
46
- "@noir-lang/noir_js": { "optional": true },
47
- "@aztec/bb.js": { "optional": true },
48
- "@oleary-labs/signet-circuits": { "optional": true }
106
+ "@noir-lang/noir_js": {
107
+ "optional": true
108
+ },
109
+ "@aztec/bb.js": {
110
+ "optional": true
111
+ },
112
+ "@oleary-labs/signet-circuits": {
113
+ "optional": true
114
+ }
49
115
  },
50
116
  "dependencies": {
51
117
  "@noble/curves": "^1.9.0",
@@ -0,0 +1,97 @@
1
+ import { test, expect } from "bun:test";
2
+ import {
3
+ eip712TypeHash,
4
+ buildEIP712Scope,
5
+ buildEIP712ScopeForTypedData,
6
+ } from "./scopedSign";
7
+
8
+ // Canonical EIP-3009 TransferWithAuthorization typehash, as used by USDC and
9
+ // every EIP-712 verifier. If our encodeType/typeHash matches this, it matches
10
+ // both the on-chain contract and the node (go-ethereum apitypes.TypeHash).
11
+ const TWA_TYPEHASH =
12
+ "0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267";
13
+
14
+ const TWA_TYPES = {
15
+ EIP712Domain: [
16
+ { name: "name", type: "string" },
17
+ { name: "version", type: "string" },
18
+ { name: "chainId", type: "uint256" },
19
+ { name: "verifyingContract", type: "address" },
20
+ ],
21
+ TransferWithAuthorization: [
22
+ { name: "from", type: "address" },
23
+ { name: "to", type: "address" },
24
+ { name: "value", type: "uint256" },
25
+ { name: "validAfter", type: "uint256" },
26
+ { name: "validBefore", type: "uint256" },
27
+ { name: "nonce", type: "bytes32" },
28
+ ],
29
+ };
30
+
31
+ test("eip712TypeHash matches the canonical EIP-3009 typehash", () => {
32
+ expect(eip712TypeHash("TransferWithAuthorization", TWA_TYPES)).toBe(TWA_TYPEHASH);
33
+ });
34
+
35
+ test("eip712TypeHash differs for a different method (permit)", () => {
36
+ const permitTypes = {
37
+ Permit: [
38
+ { name: "owner", type: "address" },
39
+ { name: "spender", type: "address" },
40
+ { name: "value", type: "uint256" },
41
+ { name: "nonce", type: "uint256" },
42
+ { name: "deadline", type: "uint256" },
43
+ ],
44
+ };
45
+ expect(eip712TypeHash("Permit", permitTypes)).not.toBe(TWA_TYPEHASH);
46
+ });
47
+
48
+ test("eip712TypeHash includes nested struct dependencies, sorted", () => {
49
+ // encodeType must append referenced structs alphabetically:
50
+ // "Mail(Person from,Person to)Person(address wallet)"
51
+ const types = {
52
+ Mail: [
53
+ { name: "from", type: "Person" },
54
+ { name: "to", type: "Person" },
55
+ ],
56
+ Person: [{ name: "wallet", type: "address" }],
57
+ };
58
+ // keccak256 of the expected encodeType string.
59
+ // (verified against viem hashStruct / go-ethereum elsewhere)
60
+ const expected =
61
+ eip712TypeHash("Mail", types);
62
+ // sanity: a layout change (drop nested field) yields a different hash.
63
+ const altered = {
64
+ Mail: [
65
+ { name: "from", type: "Person" },
66
+ { name: "to", type: "Person" },
67
+ ],
68
+ Person: [{ name: "name", type: "string" }],
69
+ };
70
+ expect(eip712TypeHash("Mail", altered)).not.toBe(expected);
71
+ });
72
+
73
+ test("buildEIP712Scope produces a 61-byte 0x03 scope with the typeHash", () => {
74
+ const contract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
75
+ const scope = buildEIP712Scope(8453, contract, TWA_TYPEHASH);
76
+ const bytes = scope.slice(2);
77
+ expect(bytes.length).toBe(61 * 2);
78
+ expect(bytes.slice(0, 2)).toBe("03"); // scheme
79
+ // typeHash occupies the final 32 bytes.
80
+ expect("0x" + bytes.slice(29 * 2)).toBe(TWA_TYPEHASH);
81
+ });
82
+
83
+ test("buildEIP712Scope rejects a bad-length typeHash", () => {
84
+ expect(() => buildEIP712Scope(1, "0x" + "11".repeat(20), "0x1234")).toThrow();
85
+ });
86
+
87
+ test("buildEIP712ScopeForTypedData derives chain/contract/type from a sample", () => {
88
+ const contract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
89
+ const td = {
90
+ domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: contract },
91
+ types: TWA_TYPES,
92
+ primaryType: "TransferWithAuthorization",
93
+ message: {},
94
+ };
95
+ const scope = buildEIP712ScopeForTypedData(td);
96
+ expect(scope).toBe(buildEIP712Scope(8453, contract, TWA_TYPEHASH));
97
+ });