@verbeth/sdk 0.1.4
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 +202 -0
- package/dist/esm/src/client/VerbethClient.d.ts +134 -0
- package/dist/esm/src/client/VerbethClient.d.ts.map +1 -0
- package/dist/esm/src/client/VerbethClient.js +191 -0
- package/dist/esm/src/client/index.d.ts +3 -0
- package/dist/esm/src/client/index.d.ts.map +1 -0
- package/dist/esm/src/client/index.js +2 -0
- package/dist/esm/src/client/types.d.ts +30 -0
- package/dist/esm/src/client/types.d.ts.map +1 -0
- package/dist/esm/src/client/types.js +2 -0
- package/dist/esm/src/crypto.d.ts +46 -0
- package/dist/esm/src/crypto.d.ts.map +1 -0
- package/dist/esm/src/crypto.js +137 -0
- package/dist/esm/src/executor.d.ts +73 -0
- package/dist/esm/src/executor.d.ts.map +1 -0
- package/dist/esm/src/executor.js +353 -0
- package/dist/esm/src/identity.d.ts +28 -0
- package/dist/esm/src/identity.d.ts.map +1 -0
- package/dist/esm/src/identity.js +70 -0
- package/dist/esm/src/index.d.ts +18 -0
- package/dist/esm/src/index.d.ts.map +1 -0
- package/dist/esm/src/index.js +17 -0
- package/dist/esm/src/payload.d.ts +94 -0
- package/dist/esm/src/payload.d.ts.map +1 -0
- package/dist/esm/src/payload.js +216 -0
- package/dist/esm/src/send.d.ts +50 -0
- package/dist/esm/src/send.d.ts.map +1 -0
- package/dist/esm/src/send.js +75 -0
- package/dist/esm/src/types.d.ts +73 -0
- package/dist/esm/src/types.d.ts.map +1 -0
- package/dist/esm/src/types.js +2 -0
- package/dist/esm/src/utils/nonce.d.ts +2 -0
- package/dist/esm/src/utils/nonce.d.ts.map +1 -0
- package/dist/esm/src/utils/nonce.js +6 -0
- package/dist/esm/src/utils/x25519.d.ts +6 -0
- package/dist/esm/src/utils/x25519.d.ts.map +1 -0
- package/dist/esm/src/utils/x25519.js +12 -0
- package/dist/esm/src/utils.d.ts +29 -0
- package/dist/esm/src/utils.d.ts.map +1 -0
- package/dist/esm/src/utils.js +123 -0
- package/dist/esm/src/verify.d.ts +54 -0
- package/dist/esm/src/verify.d.ts.map +1 -0
- package/dist/esm/src/verify.js +186 -0
- package/dist/src/client/VerbethClient.d.ts +134 -0
- package/dist/src/client/VerbethClient.d.ts.map +1 -0
- package/dist/src/client/VerbethClient.js +191 -0
- package/dist/src/client/index.d.ts +3 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +2 -0
- package/dist/src/client/types.d.ts +30 -0
- package/dist/src/client/types.d.ts.map +1 -0
- package/dist/src/client/types.js +2 -0
- package/dist/src/crypto.d.ts +46 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +137 -0
- package/dist/src/executor.d.ts +73 -0
- package/dist/src/executor.d.ts.map +1 -0
- package/dist/src/executor.js +353 -0
- package/dist/src/identity.d.ts +28 -0
- package/dist/src/identity.d.ts.map +1 -0
- package/dist/src/identity.js +70 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +17 -0
- package/dist/src/payload.d.ts +94 -0
- package/dist/src/payload.d.ts.map +1 -0
- package/dist/src/payload.js +216 -0
- package/dist/src/send.d.ts +50 -0
- package/dist/src/send.d.ts.map +1 -0
- package/dist/src/send.js +75 -0
- package/dist/src/types.d.ts +73 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/nonce.d.ts +2 -0
- package/dist/src/utils/nonce.d.ts.map +1 -0
- package/dist/src/utils/nonce.js +6 -0
- package/dist/src/utils/x25519.d.ts +6 -0
- package/dist/src/utils/x25519.d.ts.map +1 -0
- package/dist/src/utils/x25519.js +12 -0
- package/dist/src/utils.d.ts +29 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +123 -0
- package/dist/src/verify.d.ts +54 -0
- package/dist/src/verify.d.ts.map +1 -0
- package/dist/src/verify.js +186 -0
- package/package.json +38 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
export function parseHandshakePayload(plaintextPayload) {
|
|
2
|
+
try {
|
|
3
|
+
const parsed = JSON.parse(plaintextPayload);
|
|
4
|
+
if (typeof parsed === 'object' && parsed.plaintextPayload && parsed.identityProof) {
|
|
5
|
+
return parsed;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
catch (e) {
|
|
9
|
+
}
|
|
10
|
+
throw new Error("Invalid handshake payload: missing identityProof");
|
|
11
|
+
}
|
|
12
|
+
export function serializeHandshakeContent(content) {
|
|
13
|
+
return JSON.stringify(content);
|
|
14
|
+
}
|
|
15
|
+
export function encodePayload(ephemeralPubKey, nonce, ciphertext, sig) {
|
|
16
|
+
const payload = {
|
|
17
|
+
v: 1,
|
|
18
|
+
epk: Buffer.from(ephemeralPubKey).toString('base64'),
|
|
19
|
+
n: Buffer.from(nonce).toString('base64'),
|
|
20
|
+
ct: Buffer.from(ciphertext).toString('base64'),
|
|
21
|
+
...(sig && { sig: Buffer.from(sig).toString('base64') })
|
|
22
|
+
};
|
|
23
|
+
return JSON.stringify(payload);
|
|
24
|
+
}
|
|
25
|
+
export function decodePayload(json) {
|
|
26
|
+
let actualJson = json;
|
|
27
|
+
if (typeof json === 'string' && json.startsWith('0x')) {
|
|
28
|
+
try {
|
|
29
|
+
const bytes = new Uint8Array(Buffer.from(json.slice(2), 'hex'));
|
|
30
|
+
actualJson = new TextDecoder().decode(bytes);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new Error(`Hex decode error: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const { epk, n, ct, sig } = JSON.parse(actualJson);
|
|
38
|
+
return {
|
|
39
|
+
epk: Buffer.from(epk, 'base64'),
|
|
40
|
+
nonce: Buffer.from(n, 'base64'),
|
|
41
|
+
ciphertext: Buffer.from(ct, 'base64'),
|
|
42
|
+
...(sig && { sig: Buffer.from(sig, 'base64') })
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (parseError) {
|
|
46
|
+
throw new Error(`JSON parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Unified function for encoding any structured content as Uint8Array
|
|
50
|
+
export function encodeStructuredContent(content) {
|
|
51
|
+
const serialized = JSON.stringify(content, (key, value) => {
|
|
52
|
+
if (value instanceof Uint8Array) {
|
|
53
|
+
return Buffer.from(value).toString('base64');
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
});
|
|
57
|
+
return new TextEncoder().encode(serialized);
|
|
58
|
+
}
|
|
59
|
+
// Unified function for decoding structured content
|
|
60
|
+
export function decodeStructuredContent(encoded, converter) {
|
|
61
|
+
const decoded = JSON.parse(new TextDecoder().decode(encoded));
|
|
62
|
+
return converter(decoded);
|
|
63
|
+
}
|
|
64
|
+
// ========== UNIFIED KEYS MANAGEMENT ==========
|
|
65
|
+
/**
|
|
66
|
+
* Encodes X25519 + Ed25519 keys into a single 65-byte array with versioning
|
|
67
|
+
*/
|
|
68
|
+
export function encodeUnifiedPubKeys(identityPubKey, // X25519 - 32 bytes
|
|
69
|
+
signingPubKey // Ed25519 - 32 bytes
|
|
70
|
+
) {
|
|
71
|
+
const version = new Uint8Array([0x01]); // v1
|
|
72
|
+
return new Uint8Array([
|
|
73
|
+
...version,
|
|
74
|
+
...identityPubKey,
|
|
75
|
+
...signingPubKey
|
|
76
|
+
]); // 65 bytes total
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decodes unified pubKeys back to individual X25519 and Ed25519 keys
|
|
80
|
+
*/
|
|
81
|
+
export function decodeUnifiedPubKeys(pubKeys) {
|
|
82
|
+
if (pubKeys.length === 64) {
|
|
83
|
+
// Legacy
|
|
84
|
+
return {
|
|
85
|
+
version: 0,
|
|
86
|
+
identityPubKey: pubKeys.slice(0, 32),
|
|
87
|
+
signingPubKey: pubKeys.slice(32, 64)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (pubKeys.length === 65 && pubKeys[0] === 0x01) {
|
|
91
|
+
// V1: with versioning
|
|
92
|
+
return {
|
|
93
|
+
version: 1,
|
|
94
|
+
identityPubKey: pubKeys.slice(1, 33),
|
|
95
|
+
signingPubKey: pubKeys.slice(33, 65)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
export function encodeHandshakePayload(payload) {
|
|
101
|
+
return new TextEncoder().encode(JSON.stringify({
|
|
102
|
+
unifiedPubKeys: Buffer.from(payload.unifiedPubKeys).toString('base64'),
|
|
103
|
+
ephemeralPubKey: Buffer.from(payload.ephemeralPubKey).toString('base64'),
|
|
104
|
+
plaintextPayload: payload.plaintextPayload
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
export function decodeHandshakePayload(encoded) {
|
|
108
|
+
const json = new TextDecoder().decode(encoded);
|
|
109
|
+
const parsed = JSON.parse(json);
|
|
110
|
+
return {
|
|
111
|
+
unifiedPubKeys: Uint8Array.from(Buffer.from(parsed.unifiedPubKeys, 'base64')),
|
|
112
|
+
ephemeralPubKey: Uint8Array.from(Buffer.from(parsed.ephemeralPubKey, 'base64')),
|
|
113
|
+
plaintextPayload: parsed.plaintextPayload
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function encodeHandshakeResponseContent(content) {
|
|
117
|
+
return new TextEncoder().encode(JSON.stringify({
|
|
118
|
+
unifiedPubKeys: Buffer.from(content.unifiedPubKeys).toString('base64'),
|
|
119
|
+
ephemeralPubKey: Buffer.from(content.ephemeralPubKey).toString('base64'),
|
|
120
|
+
note: content.note,
|
|
121
|
+
identityProof: content.identityProof,
|
|
122
|
+
topicInfo: content.topicInfo ? {
|
|
123
|
+
out: content.topicInfo.out,
|
|
124
|
+
in: content.topicInfo.in,
|
|
125
|
+
chk: content.topicInfo.chk
|
|
126
|
+
} : undefined
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
export function decodeHandshakeResponseContent(encoded) {
|
|
130
|
+
const json = new TextDecoder().decode(encoded);
|
|
131
|
+
const obj = JSON.parse(json);
|
|
132
|
+
if (!obj.identityProof) {
|
|
133
|
+
throw new Error("Invalid handshake response: missing identityProof");
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
unifiedPubKeys: Uint8Array.from(Buffer.from(obj.unifiedPubKeys, 'base64')),
|
|
137
|
+
ephemeralPubKey: Uint8Array.from(Buffer.from(obj.ephemeralPubKey, 'base64')),
|
|
138
|
+
note: obj.note,
|
|
139
|
+
identityProof: obj.identityProof,
|
|
140
|
+
topicInfo: obj.topicInfo ? {
|
|
141
|
+
out: obj.topicInfo.out,
|
|
142
|
+
in: obj.topicInfo.in,
|
|
143
|
+
chk: obj.topicInfo.chk
|
|
144
|
+
} : undefined
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Creates HandshakePayload from separate identity keys
|
|
149
|
+
*/
|
|
150
|
+
export function createHandshakePayload(identityPubKey, signingPubKey, ephemeralPubKey, plaintextPayload) {
|
|
151
|
+
return {
|
|
152
|
+
unifiedPubKeys: encodeUnifiedPubKeys(identityPubKey, signingPubKey),
|
|
153
|
+
ephemeralPubKey,
|
|
154
|
+
plaintextPayload
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Creates HandshakeResponseContent from separate identity keys
|
|
159
|
+
*/
|
|
160
|
+
export function createHandshakeResponseContent(identityPubKey, signingPubKey, ephemeralPubKey, note, identityProof, topicInfo) {
|
|
161
|
+
if (!identityProof) {
|
|
162
|
+
throw new Error("Identity proof is now mandatory for handshake responses");
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
unifiedPubKeys: encodeUnifiedPubKeys(identityPubKey, signingPubKey),
|
|
166
|
+
ephemeralPubKey,
|
|
167
|
+
note,
|
|
168
|
+
identityProof,
|
|
169
|
+
topicInfo
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Extracts individual keys from HandshakePayload
|
|
174
|
+
*/
|
|
175
|
+
export function extractKeysFromHandshakePayload(payload) {
|
|
176
|
+
const decoded = decodeUnifiedPubKeys(payload.unifiedPubKeys);
|
|
177
|
+
if (!decoded)
|
|
178
|
+
return null;
|
|
179
|
+
return {
|
|
180
|
+
identityPubKey: decoded.identityPubKey,
|
|
181
|
+
signingPubKey: decoded.signingPubKey,
|
|
182
|
+
ephemeralPubKey: payload.ephemeralPubKey
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extracts individual keys from HandshakeResponseContent
|
|
187
|
+
*/
|
|
188
|
+
export function extractKeysFromHandshakeResponse(content) {
|
|
189
|
+
const decoded = decodeUnifiedPubKeys(content.unifiedPubKeys);
|
|
190
|
+
if (!decoded)
|
|
191
|
+
return null;
|
|
192
|
+
return {
|
|
193
|
+
identityPubKey: decoded.identityPubKey,
|
|
194
|
+
signingPubKey: decoded.signingPubKey,
|
|
195
|
+
ephemeralPubKey: content.ephemeralPubKey
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parses unified pubKeys from HandshakeLog event
|
|
200
|
+
*/
|
|
201
|
+
export function parseHandshakeKeys(event) {
|
|
202
|
+
try {
|
|
203
|
+
const pubKeysBytes = new Uint8Array(Buffer.from(event.pubKeys.slice(2), 'hex'));
|
|
204
|
+
const decoded = decodeUnifiedPubKeys(pubKeysBytes);
|
|
205
|
+
if (!decoded)
|
|
206
|
+
return null;
|
|
207
|
+
return {
|
|
208
|
+
identityPubKey: decoded.identityPubKey,
|
|
209
|
+
signingPubKey: decoded.signingPubKey
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error('Failed to parse handshake keys:', error);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Signer } from "ethers";
|
|
2
|
+
import nacl from 'tweetnacl';
|
|
3
|
+
import { IdentityKeyPair, IdentityProof } from './types.js';
|
|
4
|
+
import { IExecutor } from './executor.js';
|
|
5
|
+
/**
|
|
6
|
+
* Sends an encrypted message assuming recipient's keys were already obtained via handshake.
|
|
7
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
8
|
+
*/
|
|
9
|
+
export declare function sendEncryptedMessage({ executor, topic, message, recipientPubKey, senderAddress, senderSignKeyPair, timestamp }: {
|
|
10
|
+
executor: IExecutor;
|
|
11
|
+
topic: string;
|
|
12
|
+
message: string;
|
|
13
|
+
recipientPubKey: Uint8Array;
|
|
14
|
+
senderAddress: string;
|
|
15
|
+
senderSignKeyPair: nacl.SignKeyPair;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}): Promise<any>;
|
|
18
|
+
/**
|
|
19
|
+
* Initiates an on-chain handshake with unified keys and mandatory identity proof.
|
|
20
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
21
|
+
*/
|
|
22
|
+
export declare function initiateHandshake({ executor, recipientAddress, identityKeyPair, ephemeralPubKey, plaintextPayload, identityProof, signer }: {
|
|
23
|
+
executor: IExecutor;
|
|
24
|
+
recipientAddress: string;
|
|
25
|
+
identityKeyPair: IdentityKeyPair;
|
|
26
|
+
ephemeralPubKey: Uint8Array;
|
|
27
|
+
plaintextPayload: string;
|
|
28
|
+
identityProof: IdentityProof;
|
|
29
|
+
signer: Signer;
|
|
30
|
+
}): Promise<any>;
|
|
31
|
+
/**
|
|
32
|
+
* Responds to a handshake with unified keys and mandatory identity proof.
|
|
33
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
34
|
+
*/
|
|
35
|
+
export declare function respondToHandshake({ executor, initiatorPubKey, // X25519 key from initiator (ephemeral)
|
|
36
|
+
responderIdentityKeyPair, responderEphemeralKeyPair, note, identityProof, signer, initiatorIdentityPubKey, }: {
|
|
37
|
+
executor: IExecutor;
|
|
38
|
+
initiatorPubKey: Uint8Array;
|
|
39
|
+
responderIdentityKeyPair: IdentityKeyPair;
|
|
40
|
+
responderEphemeralKeyPair?: nacl.BoxKeyPair;
|
|
41
|
+
note?: string;
|
|
42
|
+
identityProof: IdentityProof;
|
|
43
|
+
signer: Signer;
|
|
44
|
+
initiatorIdentityPubKey?: Uint8Array;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
tx: any;
|
|
47
|
+
salt: Uint8Array<ArrayBufferLike>;
|
|
48
|
+
tag: `0x${string}`;
|
|
49
|
+
}>;
|
|
50
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/send.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,MAAM,EAEP,MAAM,QAAQ,CAAC;AAChB,OAAO,IAAI,MAAM,WAAW,CAAC;AAU7B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAK1C;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,QAAQ,EACR,KAAK,EACL,OAAO,EACP,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,SAAS,EACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,UAAU,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,IAAI,CAAC,WAAW,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB,gBAmBA;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,MAAM,EACP,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,eAAe,EAAE,UAAU,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB,gBA4BA;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,QAAQ,EACR,eAAe,EAAE,wCAAwC;AACzD,wBAAwB,EACxB,yBAAyB,EACzB,IAAI,EACJ,aAAa,EACb,MAAM,EACN,uBAAuB,GACxB,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,eAAe,EAAE,UAAU,CAAC;IAC5B,wBAAwB,EAAE,eAAe,CAAC;IAC1C,yBAAyB,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB,CAAC,EAAE,UAAU,CAAC;CACtC;;;;GAuDA"}
|
package/dist/src/send.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// packages/sdk/src/send.ts
|
|
2
|
+
import { keccak256, toUtf8Bytes, hexlify, getBytes } from "ethers";
|
|
3
|
+
import nacl from 'tweetnacl';
|
|
4
|
+
import { getNextNonce } from './utils/nonce.js';
|
|
5
|
+
import { encryptMessage, encryptStructuredPayload, deriveDuplexTopics } from './crypto.js';
|
|
6
|
+
import { serializeHandshakeContent, encodeUnifiedPubKeys, createHandshakeResponseContent, } from './payload.js';
|
|
7
|
+
import { computeTagFromResponder } from './crypto.js';
|
|
8
|
+
/**
|
|
9
|
+
* Sends an encrypted message assuming recipient's keys were already obtained via handshake.
|
|
10
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
11
|
+
*/
|
|
12
|
+
export async function sendEncryptedMessage({ executor, topic, message, recipientPubKey, senderAddress, senderSignKeyPair, timestamp }) {
|
|
13
|
+
if (!executor) {
|
|
14
|
+
throw new Error("Executor must be provided");
|
|
15
|
+
}
|
|
16
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
17
|
+
const ciphertext = encryptMessage(message, recipientPubKey, // X25519 for encryption
|
|
18
|
+
ephemeralKeyPair.secretKey, ephemeralKeyPair.publicKey, senderSignKeyPair.secretKey, // Ed25519 for signing
|
|
19
|
+
senderSignKeyPair.publicKey);
|
|
20
|
+
const nonce = getNextNonce(senderAddress, topic);
|
|
21
|
+
return executor.sendMessage(toUtf8Bytes(ciphertext), topic, timestamp, nonce);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initiates an on-chain handshake with unified keys and mandatory identity proof.
|
|
25
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
26
|
+
*/
|
|
27
|
+
export async function initiateHandshake({ executor, recipientAddress, identityKeyPair, ephemeralPubKey, plaintextPayload, identityProof, signer }) {
|
|
28
|
+
if (!executor) {
|
|
29
|
+
throw new Error("Executor must be provided");
|
|
30
|
+
}
|
|
31
|
+
const recipientHash = keccak256(toUtf8Bytes('contact:' + recipientAddress.toLowerCase()));
|
|
32
|
+
const handshakeContent = {
|
|
33
|
+
plaintextPayload,
|
|
34
|
+
identityProof
|
|
35
|
+
};
|
|
36
|
+
const serializedPayload = serializeHandshakeContent(handshakeContent);
|
|
37
|
+
// Create unified pubKeys (65 bytes: version + X25519 + Ed25519)
|
|
38
|
+
const unifiedPubKeys = encodeUnifiedPubKeys(identityKeyPair.publicKey, // X25519 for encryption
|
|
39
|
+
identityKeyPair.signingPublicKey // Ed25519 for signing
|
|
40
|
+
);
|
|
41
|
+
return await executor.initiateHandshake(recipientHash, hexlify(unifiedPubKeys), hexlify(ephemeralPubKey), toUtf8Bytes(serializedPayload));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Responds to a handshake with unified keys and mandatory identity proof.
|
|
45
|
+
* Executor-agnostic: works with EOA, UserOp, and Direct EntryPoint (for tests)
|
|
46
|
+
*/
|
|
47
|
+
export async function respondToHandshake({ executor, initiatorPubKey, // X25519 key from initiator (ephemeral)
|
|
48
|
+
responderIdentityKeyPair, responderEphemeralKeyPair, note, identityProof, signer, initiatorIdentityPubKey, }) {
|
|
49
|
+
if (!executor) {
|
|
50
|
+
throw new Error("Executor must be provided");
|
|
51
|
+
}
|
|
52
|
+
const ephemeralKeyPair = responderEphemeralKeyPair || nacl.box.keyPair();
|
|
53
|
+
// Generate a separate ephemeral key (R,r) just for the tag
|
|
54
|
+
const tagKeyPair = nacl.box.keyPair();
|
|
55
|
+
const inResponseTo = computeTagFromResponder(tagKeyPair.secretKey, initiatorPubKey);
|
|
56
|
+
const salt = getBytes(inResponseTo); // for topics HKDF
|
|
57
|
+
let topicInfo = undefined;
|
|
58
|
+
if (initiatorIdentityPubKey) {
|
|
59
|
+
const { topicOut, topicIn, checksum } = deriveDuplexTopics(responderIdentityKeyPair.secretKey, initiatorIdentityPubKey, salt);
|
|
60
|
+
topicInfo = { out: topicOut, in: topicIn, chk: checksum };
|
|
61
|
+
}
|
|
62
|
+
const responseContent = createHandshakeResponseContent(responderIdentityKeyPair.publicKey, // X25519
|
|
63
|
+
responderIdentityKeyPair.signingPublicKey, // Ed25519
|
|
64
|
+
ephemeralKeyPair.publicKey, note, identityProof, topicInfo);
|
|
65
|
+
// Encrypt the response for the initiator
|
|
66
|
+
const payload = encryptStructuredPayload(responseContent, initiatorPubKey, // Encrypt to initiator's X25519 (ephemeral) key
|
|
67
|
+
ephemeralKeyPair.secretKey, ephemeralKeyPair.publicKey);
|
|
68
|
+
// Execute the transaction
|
|
69
|
+
const tx = await executor.respondToHandshake(inResponseTo, hexlify(tagKeyPair.publicKey), toUtf8Bytes(payload));
|
|
70
|
+
return {
|
|
71
|
+
tx,
|
|
72
|
+
salt,
|
|
73
|
+
tag: inResponseTo
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface LogMessage {
|
|
2
|
+
sender: string;
|
|
3
|
+
ciphertext: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
topic: string;
|
|
6
|
+
nonce: bigint;
|
|
7
|
+
}
|
|
8
|
+
export interface HandshakeLog {
|
|
9
|
+
recipientHash: string;
|
|
10
|
+
sender: string;
|
|
11
|
+
pubKeys: string;
|
|
12
|
+
ephemeralPubKey: string;
|
|
13
|
+
plaintextPayload: string;
|
|
14
|
+
}
|
|
15
|
+
export interface HandshakeResponseLog {
|
|
16
|
+
inResponseTo: string;
|
|
17
|
+
responder: string;
|
|
18
|
+
responderEphemeralR: string;
|
|
19
|
+
ciphertext: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DuplexTopics {
|
|
22
|
+
/** Initiator → Responder */
|
|
23
|
+
topicOut: `0x${string}`;
|
|
24
|
+
/** Responder → Initiator */
|
|
25
|
+
topicIn: `0x${string}`;
|
|
26
|
+
}
|
|
27
|
+
/** Formato compatto per invio via HSR cifrata */
|
|
28
|
+
export interface TopicInfoWire {
|
|
29
|
+
out: `0x${string}`;
|
|
30
|
+
in: `0x${string}`;
|
|
31
|
+
/** checksum corto per conferma (8 byte, hex) */
|
|
32
|
+
chk: `0x${string}`;
|
|
33
|
+
}
|
|
34
|
+
export interface IdentityKeyPair {
|
|
35
|
+
publicKey: Uint8Array;
|
|
36
|
+
secretKey: Uint8Array;
|
|
37
|
+
signingPublicKey: Uint8Array;
|
|
38
|
+
signingSecretKey: Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
export interface IdentityProof {
|
|
41
|
+
message: string;
|
|
42
|
+
signature: string;
|
|
43
|
+
messageRawHex?: `0x${string}`;
|
|
44
|
+
}
|
|
45
|
+
export type PackedUserOperation = typeof DEFAULT_AA_VERSION extends "v0.6" ? UserOpV06 : UserOpV07;
|
|
46
|
+
export interface BaseUserOp {
|
|
47
|
+
sender: string;
|
|
48
|
+
nonce: bigint;
|
|
49
|
+
initCode: string;
|
|
50
|
+
callData: string;
|
|
51
|
+
preVerificationGas: bigint;
|
|
52
|
+
paymasterAndData: string;
|
|
53
|
+
signature: string;
|
|
54
|
+
}
|
|
55
|
+
export interface UserOpV06 extends BaseUserOp {
|
|
56
|
+
callGasLimit: bigint;
|
|
57
|
+
verificationGasLimit: bigint;
|
|
58
|
+
maxFeePerGas: bigint;
|
|
59
|
+
maxPriorityFeePerGas: bigint;
|
|
60
|
+
}
|
|
61
|
+
export interface UserOpV07 extends BaseUserOp {
|
|
62
|
+
/**
|
|
63
|
+
* = (verificationGasLimit << 128) \| callGasLimit
|
|
64
|
+
*/
|
|
65
|
+
accountGasLimits: bigint;
|
|
66
|
+
/**
|
|
67
|
+
* = (maxFeePerGas << 128) \| maxPriorityFeePerGas
|
|
68
|
+
*/
|
|
69
|
+
gasFees: bigint;
|
|
70
|
+
}
|
|
71
|
+
export type AASpecVersion = "v0.6" | "v0.7";
|
|
72
|
+
export declare const DEFAULT_AA_VERSION: AASpecVersion;
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,QAAQ,EAAE,KAAK,MAAM,EAAE,CAAC;IACxB,4BAA4B;IAC5B,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;CACxB;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,KAAK,MAAM,EAAE,CAAC;IACnB,EAAE,EAAE,KAAK,MAAM,EAAE,CAAC;IAClB,gDAAgD;IAChD,GAAG,EAAE,KAAK,MAAM,EAAE,CAAC;CACpB;AAGD,MAAM,WAAW,eAAe;IAE9B,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;IAEtB,gBAAgB,EAAE,UAAU,CAAC;IAC7B,gBAAgB,EAAE,UAAU,CAAC;CAC9B;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,kBAAkB,SAAS,MAAM,GACtE,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAC5C,eAAO,MAAM,kBAAkB,EAAE,aAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../../../src/utils/nonce.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlE"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a 64-byte raw secp256k1 public key into a 32-byte x25519-compatible public key.
|
|
3
|
+
* This is done by hashing it and using the first 32 bytes.
|
|
4
|
+
*/
|
|
5
|
+
export declare function convertPublicKeyToX25519(secpPubKey: Uint8Array): Uint8Array;
|
|
6
|
+
//# sourceMappingURL=x25519.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x25519.d.ts","sourceRoot":"","sources":["../../../src/utils/x25519.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAO3E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a 64-byte raw secp256k1 public key into a 32-byte x25519-compatible public key.
|
|
4
|
+
* This is done by hashing it and using the first 32 bytes.
|
|
5
|
+
*/
|
|
6
|
+
export function convertPublicKeyToX25519(secpPubKey) {
|
|
7
|
+
if (secpPubKey.length !== 64) {
|
|
8
|
+
throw new Error('Expected raw 64-byte secp256k1 public key (uncompressed, no prefix)');
|
|
9
|
+
}
|
|
10
|
+
const hash = sha256(secpPubKey);
|
|
11
|
+
return Uint8Array.from(hash.slice(0, 32));
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JsonRpcProvider } from "ethers";
|
|
2
|
+
import { type PublicClient } from "viem";
|
|
3
|
+
import { DuplexTopics } from "./types.js";
|
|
4
|
+
export declare function parseBindingMessage(message: string): {
|
|
5
|
+
header?: string;
|
|
6
|
+
address?: string;
|
|
7
|
+
pkEd25519?: `0x${string}`;
|
|
8
|
+
pkX25519?: `0x${string}`;
|
|
9
|
+
context?: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
chainId?: number;
|
|
12
|
+
rpId?: string;
|
|
13
|
+
};
|
|
14
|
+
export type Rpcish = import("ethers").JsonRpcProvider | import("ethers").BrowserProvider | {
|
|
15
|
+
request: (args: {
|
|
16
|
+
method: string;
|
|
17
|
+
params?: any[];
|
|
18
|
+
}) => Promise<any>;
|
|
19
|
+
};
|
|
20
|
+
export declare function makeViemPublicClient(provider: Rpcish): Promise<PublicClient>;
|
|
21
|
+
export declare const ERC6492_SUFFIX = "0x6492649264926492649264926492649264926492649264926492649264926492";
|
|
22
|
+
export declare function hasERC6492Suffix(sigHex: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Checks if an address is a smart contract that supports EIP-1271 signature verification
|
|
25
|
+
* Returns true if the address has deployed code AND implements isValidSignature function
|
|
26
|
+
*/
|
|
27
|
+
export declare function isSmartContract1271(address: string, provider: JsonRpcProvider): Promise<boolean>;
|
|
28
|
+
export declare function pickOutboundTopic(isInitiator: boolean, t: DuplexTopics): `0x${string}`;
|
|
29
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,eAAe,EAGhB,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAyBA;AAED,MAAM,MAAM,MAAM,GACd,OAAO,QAAQ,EAAE,eAAe,GAChC,OAAO,QAAQ,EAAE,eAAe,GAChC;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC;AAe5E,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAmBvB;AAED,eAAO,MAAM,cAAc,uEAC2C,CAAC;AAEvE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAIxD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,CAAC,CA+DlB;AAGD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,GAAG,KAAK,MAAM,EAAE,CAEtF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// packages/sdk/src/utils.ts
|
|
2
|
+
import { Contract, getAddress, hexlify, } from "ethers";
|
|
3
|
+
import { keccak256, toUtf8Bytes } from "ethers";
|
|
4
|
+
import { AbiCoder } from "ethers";
|
|
5
|
+
import { createPublicClient, custom, defineChain, } from "viem";
|
|
6
|
+
export function parseBindingMessage(message) {
|
|
7
|
+
const lines = message.split("\n").map((l) => l.trim());
|
|
8
|
+
const out = {};
|
|
9
|
+
if (lines[0])
|
|
10
|
+
out.header = lines[0];
|
|
11
|
+
for (let i = 1; i < lines.length; i++) {
|
|
12
|
+
const line = lines[i];
|
|
13
|
+
const idx = line.indexOf(":");
|
|
14
|
+
if (idx === -1)
|
|
15
|
+
continue;
|
|
16
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
17
|
+
const val = line.slice(idx + 1).trim();
|
|
18
|
+
if (key === "address")
|
|
19
|
+
out.address = getAddress(val);
|
|
20
|
+
if (key === "pked25519") {
|
|
21
|
+
out.pkEd25519 = hexlify(val);
|
|
22
|
+
}
|
|
23
|
+
if (key === "pkx25519") {
|
|
24
|
+
out.pkX25519 = hexlify(val);
|
|
25
|
+
}
|
|
26
|
+
if (key === "context")
|
|
27
|
+
out.context = val;
|
|
28
|
+
if (key === "version")
|
|
29
|
+
out.version = val;
|
|
30
|
+
if (key === "chainid")
|
|
31
|
+
out.chainId = Number(val);
|
|
32
|
+
if (key === "rpid")
|
|
33
|
+
out.rpId = val;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function toEip1193(provider) {
|
|
38
|
+
if (provider.request)
|
|
39
|
+
return provider;
|
|
40
|
+
if (provider.send) {
|
|
41
|
+
return {
|
|
42
|
+
request: ({ method, params }) => provider.send(method, params ?? []),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
throw new Error("Unsupported provider: cannot build EIP-1193 request");
|
|
46
|
+
}
|
|
47
|
+
export async function makeViemPublicClient(provider) {
|
|
48
|
+
const eip1193 = toEip1193(provider);
|
|
49
|
+
let chainId = 1;
|
|
50
|
+
try {
|
|
51
|
+
const hex = await eip1193.request({ method: "eth_chainId" });
|
|
52
|
+
chainId = Number(hex);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
}
|
|
56
|
+
const chain = defineChain({
|
|
57
|
+
id: chainId,
|
|
58
|
+
name: `chain-${chainId}`,
|
|
59
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
60
|
+
rpcUrls: { default: { http: [] } },
|
|
61
|
+
});
|
|
62
|
+
return createPublicClient({ chain, transport: custom(eip1193) });
|
|
63
|
+
}
|
|
64
|
+
export const ERC6492_SUFFIX = "0x6492649264926492649264926492649264926492649264926492649264926492";
|
|
65
|
+
export function hasERC6492Suffix(sigHex) {
|
|
66
|
+
if (!sigHex || typeof sigHex !== "string")
|
|
67
|
+
return false;
|
|
68
|
+
const s = sigHex.toLowerCase();
|
|
69
|
+
return s.endsWith(ERC6492_SUFFIX.slice(2).toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Checks if an address is a smart contract that supports EIP-1271 signature verification
|
|
73
|
+
* Returns true if the address has deployed code AND implements isValidSignature function
|
|
74
|
+
*/
|
|
75
|
+
export async function isSmartContract1271(address, provider) {
|
|
76
|
+
try {
|
|
77
|
+
const code = await provider.getCode(address);
|
|
78
|
+
if (code === "0x") {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const contract = new Contract(address, [
|
|
82
|
+
"function isValidSignature(bytes32, bytes) external view returns (bytes4)",
|
|
83
|
+
], provider);
|
|
84
|
+
// ECDSA smart contracts
|
|
85
|
+
try {
|
|
86
|
+
await contract.isValidSignature.staticCall("0x0000000000000000000000000000000000000000000000000000000000000000", "0x");
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch (simpleErr) {
|
|
90
|
+
// WebAuthn format
|
|
91
|
+
try {
|
|
92
|
+
const authenticatorData = "0xdeadbeef";
|
|
93
|
+
const clientDataJSON = "0xbeefdead";
|
|
94
|
+
const rawSignature = "0x" + "11".repeat(64);
|
|
95
|
+
const abi = AbiCoder.defaultAbiCoder();
|
|
96
|
+
const webAuthnAuth = abi.encode(["bytes", "bytes", "bytes"], [authenticatorData, clientDataJSON, rawSignature]);
|
|
97
|
+
const ownerIndex = 0;
|
|
98
|
+
const signatureWrapper = abi.encode(["uint256", "bytes"], [ownerIndex, webAuthnAuth]);
|
|
99
|
+
const hash = keccak256(toUtf8Bytes("test message"));
|
|
100
|
+
const result = await contract.isValidSignature.staticCall(hash, signatureWrapper);
|
|
101
|
+
return result === "0x1626ba7e";
|
|
102
|
+
}
|
|
103
|
+
catch (webAuthnErr) {
|
|
104
|
+
// if it's a CALL_EXCEPTION without data then function exists
|
|
105
|
+
if (webAuthnErr.code === "CALL_EXCEPTION" &&
|
|
106
|
+
(!webAuthnErr.data ||
|
|
107
|
+
webAuthnErr.data === "0x" ||
|
|
108
|
+
webAuthnErr.data === null)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error("Error checking if address is smart contract:", err);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// picks the correct outbound topic from a DuplexTopics structure
|
|
121
|
+
export function pickOutboundTopic(isInitiator, t) {
|
|
122
|
+
return isInitiator ? t.topicOut : t.topicIn;
|
|
123
|
+
}
|