@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.
- package/README.md +210 -0
- package/dist/generate-inputs.d.ts +26 -0
- package/dist/generate-inputs.d.ts.map +1 -0
- package/dist/generate-inputs.js +106 -0
- package/dist/generate-inputs.js.map +1 -0
- package/dist/partial-sha.d.ts +5 -0
- package/dist/partial-sha.d.ts.map +1 -0
- package/dist/partial-sha.js +89 -0
- package/dist/partial-sha.js.map +1 -0
- package/dist/proof.d.ts +30 -0
- package/dist/proof.d.ts.map +1 -0
- package/dist/proof.js +72 -0
- package/dist/proof.js.map +1 -0
- package/dist/scopedSign.d.ts +27 -3
- package/dist/scopedSign.d.ts.map +1 -1
- package/dist/scopedSign.js +71 -11
- package/dist/scopedSign.js.map +1 -1
- package/dist/witness.d.ts +51 -0
- package/dist/witness.d.ts.map +1 -0
- package/dist/witness.js +81 -0
- package/dist/witness.js.map +1 -0
- package/package.json +93 -27
- package/src/scopedSign.test.ts +97 -0
- package/src/scopedSign.ts +92 -8
package/src/scopedSign.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* before computing the hash and signing.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { hashTypedData, keccak256, stringToBytes } from "viem";
|
|
10
|
+
import type { Hex } from "viem";
|
|
9
11
|
import type { SessionKeypair, IdTokenClaims } from "./types";
|
|
10
|
-
import {
|
|
12
|
+
import { signSignRequest } from "./request";
|
|
13
|
+
import { hexToBytes } from "./session";
|
|
11
14
|
|
|
12
15
|
// ---------------------------------------------------------------------------
|
|
13
16
|
// Types
|
|
@@ -37,14 +40,65 @@ export interface ScopedSignResult {
|
|
|
37
40
|
// Scope construction
|
|
38
41
|
// ---------------------------------------------------------------------------
|
|
39
42
|
|
|
43
|
+
type EIP712Types = Record<string, ReadonlyArray<{ name: string; type: string }>>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Encode an EIP-712 type per the spec: the primary type's definition
|
|
47
|
+
* followed by its referenced struct types in alphabetical order, e.g.
|
|
48
|
+
* `TransferWithAuthorization(address from,address to,uint256 value,...)`.
|
|
49
|
+
* Matches go-ethereum's `apitypes.TypeHash` encoding used by the node, so
|
|
50
|
+
* the resulting typeHash byte-matches what the node verifies.
|
|
51
|
+
*/
|
|
52
|
+
function encodeEIP712Type(primaryType: string, types: EIP712Types): string {
|
|
53
|
+
const deps = new Set<string>();
|
|
54
|
+
const visit = (t: string) => {
|
|
55
|
+
const base = t.replace(/(\[\d*\])+$/, ""); // strip array suffixes
|
|
56
|
+
if (!types[base] || deps.has(base)) return;
|
|
57
|
+
deps.add(base);
|
|
58
|
+
for (const f of types[base]) visit(f.type);
|
|
59
|
+
};
|
|
60
|
+
for (const f of types[primaryType] ?? []) visit(f.type);
|
|
61
|
+
|
|
62
|
+
const sorted = [...deps].filter((d) => d !== primaryType).sort();
|
|
63
|
+
const encodeOne = (t: string) =>
|
|
64
|
+
`${t}(${types[t].map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
65
|
+
return [primaryType, ...sorted].map(encodeOne).join("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* EIP-712 typeHash: keccak256(encodeType(primaryType)). This is the value a
|
|
70
|
+
* 0x03 scope binds, and the same value the verifying contract uses — so a
|
|
71
|
+
* key scoped to one method (e.g. TransferWithAuthorization) cannot sign a
|
|
72
|
+
* different method on the same contract (e.g. permit).
|
|
73
|
+
*/
|
|
74
|
+
export function eip712TypeHash(primaryType: string, types: EIP712Types): Hex {
|
|
75
|
+
if (!types[primaryType]) {
|
|
76
|
+
throw new Error(`primaryType "${primaryType}" not declared in types`);
|
|
77
|
+
}
|
|
78
|
+
return keccak256(stringToBytes(encodeEIP712Type(primaryType, types)));
|
|
79
|
+
}
|
|
80
|
+
|
|
40
81
|
/**
|
|
41
|
-
* Build an EIP-712 domain scope (scheme 0x03).
|
|
82
|
+
* Build an EIP-712 domain+type scope (scheme 0x03).
|
|
42
83
|
*
|
|
43
84
|
* Format: 0x03 | chainId (8 bytes, uint64 BE) | verifyingContract (20 bytes)
|
|
44
|
-
* Total:
|
|
85
|
+
* | typeHash (32 bytes). Total: 61 bytes.
|
|
86
|
+
*
|
|
87
|
+
* `typeHash` is keccak256(encodeType(primaryType)) — see {@link eip712TypeHash}.
|
|
88
|
+
* Binding the type (not just the domain) prevents a key authorized for one
|
|
89
|
+
* typed-data method from signing a different method on the same contract.
|
|
45
90
|
*/
|
|
46
|
-
export function buildEIP712Scope(
|
|
47
|
-
|
|
91
|
+
export function buildEIP712Scope(
|
|
92
|
+
chainId: number,
|
|
93
|
+
verifyingContract: string,
|
|
94
|
+
typeHash: Hex,
|
|
95
|
+
): string {
|
|
96
|
+
const th = typeHash.startsWith("0x") ? typeHash.slice(2) : typeHash;
|
|
97
|
+
if (th.length !== 64) {
|
|
98
|
+
throw new Error(`typeHash must be 32 bytes (64 hex chars), got ${th.length}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const buf = new Uint8Array(61);
|
|
48
102
|
buf[0] = 0x03;
|
|
49
103
|
|
|
50
104
|
// chainId as 8-byte big-endian
|
|
@@ -59,9 +113,31 @@ export function buildEIP712Scope(chainId: number, verifyingContract: string): st
|
|
|
59
113
|
buf[9 + i] = parseInt(addr.slice(i * 2, i * 2 + 2), 16);
|
|
60
114
|
}
|
|
61
115
|
|
|
116
|
+
// typeHash as 32 bytes
|
|
117
|
+
for (let i = 0; i < 32; i++) {
|
|
118
|
+
buf[29 + i] = parseInt(th.slice(i * 2, i * 2 + 2), 16);
|
|
119
|
+
}
|
|
120
|
+
|
|
62
121
|
return "0x" + Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
63
122
|
}
|
|
64
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Convenience: build a 0x03 scope directly from an EIP-712 typed-data sample,
|
|
126
|
+
* deriving chainId, verifyingContract, and typeHash from it. Any sample with
|
|
127
|
+
* the intended domain + primary type works (message values are irrelevant to
|
|
128
|
+
* the scope). This is the recommended way to scope a key for a given method.
|
|
129
|
+
*/
|
|
130
|
+
export function buildEIP712ScopeForTypedData(
|
|
131
|
+
typedData: Pick<EIP712TypedData, "domain" | "types" | "primaryType">,
|
|
132
|
+
): string {
|
|
133
|
+
const typeHash = eip712TypeHash(typedData.primaryType, typedData.types);
|
|
134
|
+
return buildEIP712Scope(
|
|
135
|
+
typedData.domain.chainId,
|
|
136
|
+
typedData.domain.verifyingContract,
|
|
137
|
+
typeHash,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
65
141
|
// ---------------------------------------------------------------------------
|
|
66
142
|
// Structured signing
|
|
67
143
|
// ---------------------------------------------------------------------------
|
|
@@ -93,17 +169,25 @@ export async function signTypedData(
|
|
|
93
169
|
claims: IdTokenClaims,
|
|
94
170
|
identity?: string,
|
|
95
171
|
): Promise<ScopedSignResult> {
|
|
96
|
-
//
|
|
97
|
-
// The canonical hash must use the full sub-key ID (identity + suffix).
|
|
172
|
+
// The canonical request hash must use the full sub-key ID (identity + suffix).
|
|
98
173
|
// Extract suffix from keyId: "oauth:iss:sub:suffix" → suffix is last segment
|
|
99
174
|
// The identity param is "iss:sub", so we need to add the suffix.
|
|
100
175
|
const keyParts = keyId.split(":");
|
|
101
176
|
const keySuffix = keyParts.length > 1 ? keyParts[keyParts.length - 1] : undefined;
|
|
102
177
|
|
|
103
|
-
|
|
178
|
+
// Compute the EIP-712 hash locally and bind it into the session request
|
|
179
|
+
// signature. The nodes recompute hashTypedData from the payload and verify
|
|
180
|
+
// the request signature against it — without this binding, a malicious
|
|
181
|
+
// initiator could substitute any payload matching the key's scope.
|
|
182
|
+
const payloadHash = hexToBytes(
|
|
183
|
+
hashTypedData(typedData as Parameters<typeof hashTypedData>[0]).slice(2),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const signReq = await signSignRequest(
|
|
104
187
|
sessionKeypair,
|
|
105
188
|
claims,
|
|
106
189
|
groupId,
|
|
190
|
+
payloadHash,
|
|
107
191
|
keySuffix,
|
|
108
192
|
identity,
|
|
109
193
|
);
|