@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.
Files changed (86) hide show
  1. package/README.md +202 -0
  2. package/dist/esm/src/client/VerbethClient.d.ts +134 -0
  3. package/dist/esm/src/client/VerbethClient.d.ts.map +1 -0
  4. package/dist/esm/src/client/VerbethClient.js +191 -0
  5. package/dist/esm/src/client/index.d.ts +3 -0
  6. package/dist/esm/src/client/index.d.ts.map +1 -0
  7. package/dist/esm/src/client/index.js +2 -0
  8. package/dist/esm/src/client/types.d.ts +30 -0
  9. package/dist/esm/src/client/types.d.ts.map +1 -0
  10. package/dist/esm/src/client/types.js +2 -0
  11. package/dist/esm/src/crypto.d.ts +46 -0
  12. package/dist/esm/src/crypto.d.ts.map +1 -0
  13. package/dist/esm/src/crypto.js +137 -0
  14. package/dist/esm/src/executor.d.ts +73 -0
  15. package/dist/esm/src/executor.d.ts.map +1 -0
  16. package/dist/esm/src/executor.js +353 -0
  17. package/dist/esm/src/identity.d.ts +28 -0
  18. package/dist/esm/src/identity.d.ts.map +1 -0
  19. package/dist/esm/src/identity.js +70 -0
  20. package/dist/esm/src/index.d.ts +18 -0
  21. package/dist/esm/src/index.d.ts.map +1 -0
  22. package/dist/esm/src/index.js +17 -0
  23. package/dist/esm/src/payload.d.ts +94 -0
  24. package/dist/esm/src/payload.d.ts.map +1 -0
  25. package/dist/esm/src/payload.js +216 -0
  26. package/dist/esm/src/send.d.ts +50 -0
  27. package/dist/esm/src/send.d.ts.map +1 -0
  28. package/dist/esm/src/send.js +75 -0
  29. package/dist/esm/src/types.d.ts +73 -0
  30. package/dist/esm/src/types.d.ts.map +1 -0
  31. package/dist/esm/src/types.js +2 -0
  32. package/dist/esm/src/utils/nonce.d.ts +2 -0
  33. package/dist/esm/src/utils/nonce.d.ts.map +1 -0
  34. package/dist/esm/src/utils/nonce.js +6 -0
  35. package/dist/esm/src/utils/x25519.d.ts +6 -0
  36. package/dist/esm/src/utils/x25519.d.ts.map +1 -0
  37. package/dist/esm/src/utils/x25519.js +12 -0
  38. package/dist/esm/src/utils.d.ts +29 -0
  39. package/dist/esm/src/utils.d.ts.map +1 -0
  40. package/dist/esm/src/utils.js +123 -0
  41. package/dist/esm/src/verify.d.ts +54 -0
  42. package/dist/esm/src/verify.d.ts.map +1 -0
  43. package/dist/esm/src/verify.js +186 -0
  44. package/dist/src/client/VerbethClient.d.ts +134 -0
  45. package/dist/src/client/VerbethClient.d.ts.map +1 -0
  46. package/dist/src/client/VerbethClient.js +191 -0
  47. package/dist/src/client/index.d.ts +3 -0
  48. package/dist/src/client/index.d.ts.map +1 -0
  49. package/dist/src/client/index.js +2 -0
  50. package/dist/src/client/types.d.ts +30 -0
  51. package/dist/src/client/types.d.ts.map +1 -0
  52. package/dist/src/client/types.js +2 -0
  53. package/dist/src/crypto.d.ts +46 -0
  54. package/dist/src/crypto.d.ts.map +1 -0
  55. package/dist/src/crypto.js +137 -0
  56. package/dist/src/executor.d.ts +73 -0
  57. package/dist/src/executor.d.ts.map +1 -0
  58. package/dist/src/executor.js +353 -0
  59. package/dist/src/identity.d.ts +28 -0
  60. package/dist/src/identity.d.ts.map +1 -0
  61. package/dist/src/identity.js +70 -0
  62. package/dist/src/index.d.ts +18 -0
  63. package/dist/src/index.d.ts.map +1 -0
  64. package/dist/src/index.js +17 -0
  65. package/dist/src/payload.d.ts +94 -0
  66. package/dist/src/payload.d.ts.map +1 -0
  67. package/dist/src/payload.js +216 -0
  68. package/dist/src/send.d.ts +50 -0
  69. package/dist/src/send.d.ts.map +1 -0
  70. package/dist/src/send.js +75 -0
  71. package/dist/src/types.d.ts +73 -0
  72. package/dist/src/types.d.ts.map +1 -0
  73. package/dist/src/types.js +2 -0
  74. package/dist/src/utils/nonce.d.ts +2 -0
  75. package/dist/src/utils/nonce.d.ts.map +1 -0
  76. package/dist/src/utils/nonce.js +6 -0
  77. package/dist/src/utils/x25519.d.ts +6 -0
  78. package/dist/src/utils/x25519.d.ts.map +1 -0
  79. package/dist/src/utils/x25519.js +12 -0
  80. package/dist/src/utils.d.ts +29 -0
  81. package/dist/src/utils.d.ts.map +1 -0
  82. package/dist/src/utils.js +123 -0
  83. package/dist/src/verify.d.ts +54 -0
  84. package/dist/src/verify.d.ts.map +1 -0
  85. package/dist/src/verify.js +186 -0
  86. package/package.json +38 -0
@@ -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
+ }
@@ -0,0 +1,54 @@
1
+ import { JsonRpcProvider } from "ethers";
2
+ import { HandshakeLog, HandshakeResponseLog, IdentityProof, TopicInfoWire, DuplexTopics } from "./types.js";
3
+ import { Rpcish } from "./utils.js";
4
+ /**
5
+ * handshake verification with mandatory identity proof
6
+ */
7
+ export declare function verifyHandshakeIdentity(handshakeEvent: HandshakeLog, provider: JsonRpcProvider): Promise<boolean>;
8
+ /**
9
+ * handshake response verification with mandatory identity proof
10
+ */
11
+ export declare function verifyHandshakeResponseIdentity(responseEvent: HandshakeResponseLog, responderIdentityPubKey: Uint8Array, initiatorEphemeralSecretKey: Uint8Array, provider: JsonRpcProvider): Promise<boolean>;
12
+ /**
13
+ * Verify "IdentityProof" for EOAs and smart accounts.
14
+ * - Verifies the signature with viem (EOA / ERC-1271 / ERC-6492).
15
+ * - Parses and checks the expected address and public key against the message content.
16
+ */
17
+ export declare function verifyIdentityProof(identityProof: IdentityProof, smartAccountAddress: string, expectedUnifiedKeys: {
18
+ identityPubKey: Uint8Array;
19
+ signingPubKey: Uint8Array;
20
+ }, provider: Rpcish): Promise<boolean>;
21
+ export declare function verifyAndExtractHandshakeKeys(handshakeEvent: HandshakeLog, provider: JsonRpcProvider): Promise<{
22
+ isValid: boolean;
23
+ keys?: {
24
+ identityPubKey: Uint8Array;
25
+ signingPubKey: Uint8Array;
26
+ };
27
+ }>;
28
+ export declare function verifyAndExtractHandshakeResponseKeys(responseEvent: HandshakeResponseLog, initiatorEphemeralSecretKey: Uint8Array, provider: JsonRpcProvider): Promise<{
29
+ isValid: boolean;
30
+ keys?: {
31
+ identityPubKey: Uint8Array;
32
+ signingPubKey: Uint8Array;
33
+ ephemeralPubKey: Uint8Array;
34
+ note?: string;
35
+ };
36
+ }>;
37
+ /**
38
+ * Verify and derive duplex topics from a long-term DH secret.
39
+ * - Accepts either `tag` (inResponseTo) or a raw salt as KDF input.
40
+ * - Recomputes topicOut/topicIn deterministically from the identity DH.
41
+ * - If topicInfo is provided (from HSR), also verify the checksum.
42
+ * - Used by the initiator after decrypting a HandshakeResponse to confirm responder’s topics.
43
+ */
44
+ export declare function verifyDerivedDuplexTopics({ myIdentitySecretKey, theirIdentityPubKey, tag, salt, topicInfo }: {
45
+ myIdentitySecretKey: Uint8Array;
46
+ theirIdentityPubKey: Uint8Array;
47
+ tag?: `0x${string}`;
48
+ salt?: Uint8Array;
49
+ topicInfo?: TopicInfoWire;
50
+ }): {
51
+ topics: DuplexTopics;
52
+ ok?: boolean;
53
+ };
54
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/verify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAiC,MAAM,QAAQ,CAAC;AAExE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5G,OAAO,EACL,MAAM,EAGP,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,YAAY,EAC5B,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,CAAC,CA6ClB;AAID;;GAEG;AACH,wBAAsB,+BAA+B,CACnD,aAAa,EAAE,oBAAoB,EACnC,uBAAuB,EAAE,UAAU,EACnC,2BAA2B,EAAE,UAAU,EACvC,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,CAAC,CAgDlB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,aAAa,EAC5B,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE;IACnB,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;CAC3B,EACD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CA+DlB;AAID,wBAAsB,6BAA6B,CACjD,cAAc,EAAE,YAAY,EAC5B,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE;QACL,cAAc,EAAE,UAAU,CAAC;QAC3B,aAAa,EAAE,UAAU,CAAC;KAC3B,CAAC;CACH,CAAC,CAgBD;AAED,wBAAsB,qCAAqC,CACzD,aAAa,EAAE,oBAAoB,EACnC,2BAA2B,EAAE,UAAU,EACvC,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE;QACL,cAAc,EAAE,UAAU,CAAC;QAC3B,aAAa,EAAE,UAAU,CAAC;QAC1B,eAAe,EAAE,UAAU,CAAC;QAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,CAAC,CAwCD;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,mBAAmB,EACnB,mBAAmB,EACnB,GAAG,EACH,IAAI,EACJ,SAAS,EACV,EAAE;IACD,mBAAmB,EAAE,UAAU,CAAC;IAChC,mBAAmB,EAAE,UAAU,CAAC;IAChC,GAAG,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,CAYzC"}
@@ -0,0 +1,186 @@
1
+ // packages/sdk/src/verify.ts
2
+ import { getBytes, hexlify, getAddress } from "ethers";
3
+ import { decryptAndExtractHandshakeKeys, computeTagFromInitiator, verifyDuplexTopicsChecksum, deriveDuplexTopics } from "./crypto.js";
4
+ import { parseHandshakePayload, parseHandshakeKeys } from "./payload.js";
5
+ import { makeViemPublicClient, parseBindingMessage, } from "./utils.js";
6
+ // ============= Handshake Verification =============
7
+ /**
8
+ * handshake verification with mandatory identity proof
9
+ */
10
+ export async function verifyHandshakeIdentity(handshakeEvent, provider) {
11
+ try {
12
+ let plaintextPayload = handshakeEvent.plaintextPayload;
13
+ if (typeof plaintextPayload === "string" &&
14
+ plaintextPayload.startsWith("0x")) {
15
+ try {
16
+ const bytes = new Uint8Array(Buffer.from(plaintextPayload.slice(2), "hex"));
17
+ plaintextPayload = new TextDecoder().decode(bytes);
18
+ }
19
+ catch (err) {
20
+ console.error("Failed to decode hex payload:", err);
21
+ return false;
22
+ }
23
+ }
24
+ const content = parseHandshakePayload(plaintextPayload);
25
+ const parsedKeys = parseHandshakeKeys(handshakeEvent);
26
+ if (!parsedKeys) {
27
+ console.error("Failed to parse unified pubKeys from handshake event");
28
+ return false;
29
+ }
30
+ // // 6492 awareness
31
+ // const dp: any = content.identityProof;
32
+ // const sigPrimary: string = dp.signature;
33
+ // const sig6492: string | undefined = dp.signature6492 ?? dp.erc6492;
34
+ // const uses6492 = hasERC6492Suffix(sigPrimary) || !!sig6492;
35
+ // const isContract1271 = await isSmartContract1271(handshakeEvent.sender, provider);
36
+ return await verifyIdentityProof(content.identityProof, handshakeEvent.sender, parsedKeys, provider);
37
+ }
38
+ catch (err) {
39
+ console.error("verifyHandshakeIdentity error:", err);
40
+ return false;
41
+ }
42
+ }
43
+ // ============= HandshakeResponse Verification =============
44
+ /**
45
+ * handshake response verification with mandatory identity proof
46
+ */
47
+ export async function verifyHandshakeResponseIdentity(responseEvent, responderIdentityPubKey, initiatorEphemeralSecretKey, provider) {
48
+ try {
49
+ const extractedResponse = decryptAndExtractHandshakeKeys(responseEvent.ciphertext, initiatorEphemeralSecretKey);
50
+ if (!extractedResponse) {
51
+ console.error("Failed to decrypt handshake response");
52
+ return false;
53
+ }
54
+ if (!Buffer.from(extractedResponse.identityPubKey).equals(Buffer.from(responderIdentityPubKey))) {
55
+ console.error("Identity public key mismatch in handshake response");
56
+ return false;
57
+ }
58
+ // 6492 awareness
59
+ const dpAny = extractedResponse.identityProof;
60
+ if (!dpAny) {
61
+ console.error("Missing identityProof in handshake response payload");
62
+ return false;
63
+ }
64
+ // const sigPrimary: string = dpAny.signature;
65
+ // const sig6492: string | undefined = dpAny.signature6492 ?? dpAny.erc6492;
66
+ // const uses6492 = hasERC6492Suffix(sigPrimary) || !!sig6492;
67
+ // const isContract1271 = await isSmartContract1271(responseEvent.responder,provider);
68
+ const expectedKeys = {
69
+ identityPubKey: extractedResponse.identityPubKey,
70
+ signingPubKey: extractedResponse.signingPubKey,
71
+ };
72
+ return await verifyIdentityProof(extractedResponse.identityProof, responseEvent.responder, expectedKeys, provider);
73
+ }
74
+ catch (err) {
75
+ console.error("verifyHandshakeResponseIdentity error:", err);
76
+ return false;
77
+ }
78
+ }
79
+ /**
80
+ * Verify "IdentityProof" for EOAs and smart accounts.
81
+ * - Verifies the signature with viem (EOA / ERC-1271 / ERC-6492).
82
+ * - Parses and checks the expected address and public key against the message content.
83
+ */
84
+ export async function verifyIdentityProof(identityProof, smartAccountAddress, expectedUnifiedKeys, provider) {
85
+ try {
86
+ const client = await makeViemPublicClient(provider);
87
+ const address = smartAccountAddress;
88
+ const okSig = await client.verifyMessage({
89
+ address,
90
+ message: identityProof.message,
91
+ signature: identityProof.signature,
92
+ });
93
+ if (!okSig) {
94
+ console.error("Binding signature invalid for address");
95
+ return false;
96
+ }
97
+ const parsed = parseBindingMessage(identityProof.message);
98
+ if (parsed.header && parsed.header !== "VerbEth Key Binding v1") {
99
+ console.error("Unexpected binding header:", parsed.header);
100
+ return false;
101
+ }
102
+ if (!parsed.address ||
103
+ getAddress(parsed.address) !== getAddress(smartAccountAddress)) {
104
+ console.error("Binding message address mismatch");
105
+ return false;
106
+ }
107
+ const expectedPkX = hexlify(expectedUnifiedKeys.identityPubKey);
108
+ const expectedPkEd = hexlify(expectedUnifiedKeys.signingPubKey);
109
+ if (!parsed.pkX25519 || hexlify(parsed.pkX25519) !== expectedPkX) {
110
+ console.error("PkX25519 mismatch");
111
+ return false;
112
+ }
113
+ if (!parsed.pkEd25519 || hexlify(parsed.pkEd25519) !== expectedPkEd) {
114
+ console.error("PkEd25519 mismatch");
115
+ return false;
116
+ }
117
+ if (parsed.context && parsed.context !== "verbeth") {
118
+ console.error("Unexpected context:", parsed.context);
119
+ return false;
120
+ }
121
+ if (parsed.version && parsed.version !== "1") {
122
+ console.error("Unexpected version:", parsed.version);
123
+ return false;
124
+ }
125
+ // if (typeof parsed.chainId === 'number' && parsed.chainId !== currentChainId) return false;
126
+ return true;
127
+ }
128
+ catch (err) {
129
+ console.error("verifyIdentityProof error:", err);
130
+ return false;
131
+ }
132
+ }
133
+ // ============= Utility Functions =============
134
+ export async function verifyAndExtractHandshakeKeys(handshakeEvent, provider) {
135
+ const isValid = await verifyHandshakeIdentity(handshakeEvent, provider);
136
+ if (!isValid) {
137
+ return { isValid: false };
138
+ }
139
+ const parsedKeys = parseHandshakeKeys(handshakeEvent);
140
+ if (!parsedKeys) {
141
+ return { isValid: false };
142
+ }
143
+ return {
144
+ isValid: true,
145
+ keys: parsedKeys,
146
+ };
147
+ }
148
+ export async function verifyAndExtractHandshakeResponseKeys(responseEvent, initiatorEphemeralSecretKey, provider) {
149
+ const Rbytes = getBytes(responseEvent.responderEphemeralR); // hex -> Uint8Array
150
+ const expectedTag = computeTagFromInitiator(initiatorEphemeralSecretKey, Rbytes);
151
+ if (expectedTag !== responseEvent.inResponseTo) {
152
+ return { isValid: false };
153
+ }
154
+ const extractedResponse = decryptAndExtractHandshakeKeys(responseEvent.ciphertext, initiatorEphemeralSecretKey);
155
+ if (!extractedResponse) {
156
+ return { isValid: false };
157
+ }
158
+ const isValid = await verifyHandshakeResponseIdentity(responseEvent, extractedResponse.identityPubKey, initiatorEphemeralSecretKey, provider);
159
+ if (!isValid) {
160
+ return { isValid: false };
161
+ }
162
+ return {
163
+ isValid: true,
164
+ keys: {
165
+ identityPubKey: extractedResponse.identityPubKey,
166
+ signingPubKey: extractedResponse.signingPubKey,
167
+ ephemeralPubKey: extractedResponse.ephemeralPubKey,
168
+ note: extractedResponse.note,
169
+ },
170
+ };
171
+ }
172
+ /**
173
+ * Verify and derive duplex topics from a long-term DH secret.
174
+ * - Accepts either `tag` (inResponseTo) or a raw salt as KDF input.
175
+ * - Recomputes topicOut/topicIn deterministically from the identity DH.
176
+ * - If topicInfo is provided (from HSR), also verify the checksum.
177
+ * - Used by the initiator after decrypting a HandshakeResponse to confirm responder’s topics.
178
+ */
179
+ export function verifyDerivedDuplexTopics({ myIdentitySecretKey, theirIdentityPubKey, tag, salt, topicInfo }) {
180
+ const s = salt ?? (tag ? getBytes(tag) : undefined);
181
+ if (!s)
182
+ throw new Error("Provide either salt or inResponseTo");
183
+ const { topicOut, topicIn, checksum } = deriveDuplexTopics(myIdentitySecretKey, theirIdentityPubKey, s);
184
+ const ok = topicInfo ? verifyDuplexTopicsChecksum(topicOut, topicIn, topicInfo.chk) : undefined;
185
+ return { topics: { topicOut, topicIn }, ok };
186
+ }
@@ -0,0 +1,134 @@
1
+ import type { VerbethClientConfig, HandshakeResult, HandshakeResponseResult } from './types.js';
2
+ import type { IExecutor } from '../executor.js';
3
+ import type { IdentityKeyPair } from '../types.js';
4
+ import * as crypto from '../crypto.js';
5
+ import * as payload from '../payload.js';
6
+ import * as verify from '../verify.js';
7
+ import * as utils from '../utils.js';
8
+ import * as identity from '../identity.js';
9
+ /**
10
+ * High-level client for Verbeth E2EE messaging
11
+ *
12
+ * VerbethClient provides a simplified API for common operations while
13
+ * maintaining access to all low-level functions.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const client = new VerbethClient({
18
+ * executor,
19
+ * identityKeyPair,
20
+ * identityProof,
21
+ * signer,
22
+ * address: '0x...'
23
+ * });
24
+ *
25
+ * // Send a handshake
26
+ * const { tx, ephemeralKeyPair } = await client.sendHandshake(
27
+ * '0xBob...',
28
+ * 'Hello Bob!'
29
+ * );
30
+ *
31
+ * // Send a message
32
+ * await client.sendMessage(
33
+ * contact.topicOutbound,
34
+ * contact.identityPubKey,
35
+ * 'Hello again!'
36
+ * );
37
+ * ```
38
+ */
39
+ export declare class VerbethClient {
40
+ private readonly executor;
41
+ private readonly identityKeyPair;
42
+ private readonly identityProof;
43
+ private readonly signer;
44
+ private readonly address;
45
+ /**
46
+ * creates a new VerbethClient instance
47
+ *
48
+ * @param config - Client configuration with session-level parameters
49
+ */
50
+ constructor(config: VerbethClientConfig);
51
+ /**
52
+ * Initiates a handshake with a recipient
53
+ *
54
+ * generates an ephemeral keypair for this handshake.
55
+ * the ephemeralKeyPair must be stored to decrypt the response later.
56
+ *
57
+ * @param recipientAddress - Blockchain address of the recipient
58
+ * @param message - Plaintext message to include in the handshake
59
+ * @returns Transaction response and the ephemeral keypair (must be stored!)
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const { tx, ephemeralKeyPair } = await client.sendHandshake(
64
+ * '0xBob...',
65
+ * 'Hi Bob!'
66
+ * );
67
+ *
68
+ * // Store ephemeralKeyPair.secretKey to decrypt Bob's response
69
+ * await storage.saveContact({
70
+ * address: '0xBob...',
71
+ * ephemeralKey: ephemeralKeyPair.secretKey,
72
+ * // ...
73
+ * });
74
+ * ```
75
+ */
76
+ sendHandshake(recipientAddress: string, message: string): Promise<HandshakeResult>;
77
+ /**
78
+ * Accepts a handshake from an initiator
79
+ *
80
+ * derives duplex topics for the conversation and returns them.
81
+ *
82
+ * @param initiatorEphemeralPubKey - initiator's ephemeral public key from handshake event
83
+ * @param initiatorIdentityPubKey - initiator's long-term X25519 identity key
84
+ * @param note - response message to send back
85
+ * @returns transaction, derived duplex topics, and response tag
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const { tx, duplexTopics } = await client.acceptHandshake(
90
+ * handshake.ephemeralPubKey,
91
+ * handshake.identityPubKey,
92
+ * 'Hello Alice!'
93
+ * );
94
+ *
95
+ * // Store the topics for future messaging
96
+ * await storage.saveContact({
97
+ * address: handshake.sender,
98
+ * topicOutbound: duplexTopics.topicIn, // Responder writes to topicIn
99
+ * topicInbound: duplexTopics.topicOut, // Responder reads from topicOut
100
+ * // ...
101
+ * });
102
+ * ```
103
+ */
104
+ acceptHandshake(initiatorEphemeralPubKey: Uint8Array, initiatorIdentityPubKey: Uint8Array, note: string): Promise<HandshakeResponseResult>;
105
+ /**
106
+ * Sends an encrypted message to a contact
107
+ *
108
+ * handles timestamp, signing keys, and sender address.
109
+ *
110
+ * @param topicOutbound - The outbound topic for this conversation
111
+ * @param recipientPubKey - Recipient's X25519 public key (from handshake)
112
+ * @param message - Plaintext message to encrypt and send
113
+ * @returns Transaction response
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * await client.sendMessage(
118
+ * contact.topicOutbound,
119
+ * contact.identityPubKey,
120
+ * 'Hello again!'
121
+ * );
122
+ * ```
123
+ */
124
+ sendMessage(topicOutbound: string, recipientPubKey: Uint8Array, message: string): Promise<any>;
125
+ get crypto(): typeof crypto;
126
+ get payload(): typeof payload;
127
+ get verify(): typeof verify;
128
+ get utils(): typeof utils;
129
+ get identity(): typeof identity;
130
+ get executorInstance(): IExecutor;
131
+ get identityKeyPairInstance(): IdentityKeyPair;
132
+ get userAddress(): string;
133
+ }
134
+ //# sourceMappingURL=VerbethClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VerbethClient.d.ts","sourceRoot":"","sources":["../../../src/client/VerbethClient.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,aAAa,CAAC;AAGlE,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,OAAO,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,KAAK,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC;;;;OAIG;gBACS,MAAM,EAAE,mBAAmB;IAQvC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,aAAa,CACjB,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC;IAgB3B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,eAAe,CACnB,wBAAwB,EAAE,UAAU,EACpC,uBAAuB,EAAE,UAAU,EACnC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,uBAAuB,CAAC;IAoBnC;;;;;;;;;;;;;;;;;;OAkBG;IACG,WAAW,CACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,UAAU,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,GAAG,CAAC;IAqBf,IAAI,MAAM,kBAET;IAED,IAAI,OAAO,mBAEV;IAED,IAAI,MAAM,kBAET;IAED,IAAI,KAAK,iBAER;IAED,IAAI,QAAQ,oBAEX;IAED,IAAI,gBAAgB,IAAI,SAAS,CAEhC;IAGD,IAAI,uBAAuB,IAAI,eAAe,CAE7C;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}