@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,70 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
2
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
3
|
+
import { concat, hexlify } from "ethers";
|
|
4
|
+
import nacl from "tweetnacl";
|
|
5
|
+
import { encodeUnifiedPubKeys } from "./payload.js";
|
|
6
|
+
/**
|
|
7
|
+
* HKDF (RFC 5869) identity key derivation.
|
|
8
|
+
* Returns a proof binding the derived keypair to the wallet address.
|
|
9
|
+
*/
|
|
10
|
+
export async function deriveIdentityKeyPairWithProof(signer, address) {
|
|
11
|
+
// 1) Local secret seed (32B CSPRNG), domain-separated by address
|
|
12
|
+
const r = nacl.randomBytes(32);
|
|
13
|
+
const enc = new TextEncoder();
|
|
14
|
+
const addrLower = address.toLowerCase();
|
|
15
|
+
// IKM = HKDF(r || "verbeth/addr:" || address_lower)
|
|
16
|
+
// salt/info are public domain labels
|
|
17
|
+
const seedSalt = enc.encode("verbeth/seed-v1");
|
|
18
|
+
const seedInfo = enc.encode("verbeth/ikm");
|
|
19
|
+
const ikmInput = concat([r, enc.encode("verbeth/addr:" + addrLower)]);
|
|
20
|
+
const ikm = hkdf(sha256, ikmInput, seedSalt, seedInfo, 32);
|
|
21
|
+
// Derive X25519 (encryption)
|
|
22
|
+
const info_x25519 = enc.encode("verbeth-x25519-v1");
|
|
23
|
+
const x25519_sk = hkdf(sha256, ikm, new Uint8Array(0), info_x25519, 32);
|
|
24
|
+
const boxKeyPair = nacl.box.keyPair.fromSecretKey(x25519_sk);
|
|
25
|
+
// Derive Ed25519 (signing)
|
|
26
|
+
const info_ed25519 = enc.encode("verbeth-ed25519-v1");
|
|
27
|
+
const ed25519_seed = hkdf(sha256, ikm, new Uint8Array(0), info_ed25519, 32);
|
|
28
|
+
const signKeyPair = nacl.sign.keyPair.fromSeed(ed25519_seed);
|
|
29
|
+
const pkX25519Hex = hexlify(boxKeyPair.publicKey);
|
|
30
|
+
const pkEd25519Hex = hexlify(signKeyPair.publicKey);
|
|
31
|
+
const keyPair = {
|
|
32
|
+
publicKey: boxKeyPair.publicKey,
|
|
33
|
+
secretKey: boxKeyPair.secretKey,
|
|
34
|
+
signingPublicKey: signKeyPair.publicKey,
|
|
35
|
+
signingSecretKey: signKeyPair.secretKey,
|
|
36
|
+
};
|
|
37
|
+
// 2) Single signature binding both public keys
|
|
38
|
+
const bindingMsgLines = [
|
|
39
|
+
"VerbEth Key Binding v1",
|
|
40
|
+
`Address: ${addrLower}`,
|
|
41
|
+
`PkEd25519: ${pkEd25519Hex}`,
|
|
42
|
+
`PkX25519: ${pkX25519Hex}`,
|
|
43
|
+
`Context: verbeth`,
|
|
44
|
+
`Version: 1`,
|
|
45
|
+
];
|
|
46
|
+
const message = bindingMsgLines.join("\n");
|
|
47
|
+
const signature = await signer.signMessage(message);
|
|
48
|
+
const messageRawHex = ("0x" +
|
|
49
|
+
Buffer.from(message, "utf-8").toString("hex"));
|
|
50
|
+
return {
|
|
51
|
+
keyPair,
|
|
52
|
+
identityProof: {
|
|
53
|
+
message,
|
|
54
|
+
signature,
|
|
55
|
+
messageRawHex,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function deriveIdentityWithUnifiedKeys(signer, address) {
|
|
60
|
+
const result = await deriveIdentityKeyPairWithProof(signer, address);
|
|
61
|
+
const unifiedPubKeys = encodeUnifiedPubKeys(result.keyPair.publicKey, // X25519
|
|
62
|
+
result.keyPair.signingPublicKey // Ed25519
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
identityProof: result.identityProof,
|
|
66
|
+
identityPubKey: result.keyPair.publicKey,
|
|
67
|
+
signingPubKey: result.keyPair.signingPublicKey,
|
|
68
|
+
unifiedPubKeys,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './crypto.js';
|
|
2
|
+
export * from './payload.js';
|
|
3
|
+
export * from './send.js';
|
|
4
|
+
export * from './verify.js';
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export * from './utils.js';
|
|
7
|
+
export * from './identity.js';
|
|
8
|
+
export * from './executor.js';
|
|
9
|
+
export { decryptMessage as decryptLog } from './crypto.js';
|
|
10
|
+
export { getNextNonce } from './utils/nonce.js';
|
|
11
|
+
export { encodeUnifiedPubKeys, decodeUnifiedPubKeys, createHandshakePayload, createHandshakeResponseContent, extractKeysFromHandshakePayload, extractKeysFromHandshakeResponse, parseHandshakeKeys } from './payload.js';
|
|
12
|
+
export { decryptAndExtractHandshakeKeys, decryptMessage, decryptHandshakeResponse } from './crypto.js';
|
|
13
|
+
export { verifyIdentityProof, verifyAndExtractHandshakeKeys, verifyAndExtractHandshakeResponseKeys } from './verify.js';
|
|
14
|
+
export { deriveIdentityKeyPairWithProof, deriveIdentityWithUnifiedKeys } from './identity.js';
|
|
15
|
+
export { IExecutor, EOAExecutor, UserOpExecutor, DirectEntryPointExecutor, ExecutorFactory } from './executor.js';
|
|
16
|
+
export { VerbethClient } from './client/index.js';
|
|
17
|
+
export type { VerbethClientConfig, HandshakeResult, HandshakeResponseResult } from './client/index.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,OAAO,EAAE,cAAc,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,8BAA8B,EAC9B,+BAA+B,EAC/B,gCAAgC,EAChC,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,8BAA8B,EAC9B,cAAc,EACd,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,qCAAqC,EACtC,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC9B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,SAAS,EACT,WAAW,EACX,cAAc,EACd,wBAAwB,EACxB,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACxB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './crypto.js';
|
|
2
|
+
export * from './payload.js';
|
|
3
|
+
export * from './send.js';
|
|
4
|
+
export * from './verify.js';
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export * from './utils.js';
|
|
7
|
+
export * from './identity.js';
|
|
8
|
+
export * from './executor.js';
|
|
9
|
+
export { decryptMessage as decryptLog } from './crypto.js';
|
|
10
|
+
export { getNextNonce } from './utils/nonce.js';
|
|
11
|
+
export { encodeUnifiedPubKeys, decodeUnifiedPubKeys, createHandshakePayload, createHandshakeResponseContent, extractKeysFromHandshakePayload, extractKeysFromHandshakeResponse, parseHandshakeKeys } from './payload.js';
|
|
12
|
+
export { decryptAndExtractHandshakeKeys, decryptMessage, decryptHandshakeResponse } from './crypto.js';
|
|
13
|
+
export { verifyIdentityProof, verifyAndExtractHandshakeKeys, verifyAndExtractHandshakeResponseKeys } from './verify.js';
|
|
14
|
+
export { deriveIdentityKeyPairWithProof, deriveIdentityWithUnifiedKeys } from './identity.js';
|
|
15
|
+
export { EOAExecutor, UserOpExecutor, DirectEntryPointExecutor, ExecutorFactory } from './executor.js';
|
|
16
|
+
// high-level client API
|
|
17
|
+
export { VerbethClient } from './client/index.js';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { IdentityProof, TopicInfoWire } from './types.js';
|
|
2
|
+
export interface EncryptedPayload {
|
|
3
|
+
v: number;
|
|
4
|
+
epk: string;
|
|
5
|
+
n: string;
|
|
6
|
+
ct: string;
|
|
7
|
+
sig?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MessagePayload {
|
|
10
|
+
content: string;
|
|
11
|
+
timestamp?: number;
|
|
12
|
+
messageType?: 'text' | 'file' | 'media';
|
|
13
|
+
metadata?: Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
export interface HandshakeResponsePayload extends EncryptedPayload {
|
|
16
|
+
}
|
|
17
|
+
export interface HandshakeContent {
|
|
18
|
+
plaintextPayload: string;
|
|
19
|
+
identityProof: IdentityProof;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseHandshakePayload(plaintextPayload: string): HandshakeContent;
|
|
22
|
+
export declare function serializeHandshakeContent(content: HandshakeContent): string;
|
|
23
|
+
export declare function encodePayload(ephemeralPubKey: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, sig?: Uint8Array): string;
|
|
24
|
+
export declare function decodePayload(json: string): {
|
|
25
|
+
epk: Uint8Array;
|
|
26
|
+
nonce: Uint8Array;
|
|
27
|
+
ciphertext: Uint8Array;
|
|
28
|
+
sig?: Uint8Array;
|
|
29
|
+
};
|
|
30
|
+
export declare function encodeStructuredContent<T>(content: T): Uint8Array;
|
|
31
|
+
export declare function decodeStructuredContent<T>(encoded: Uint8Array, converter: (obj: any) => T): T;
|
|
32
|
+
/**
|
|
33
|
+
* Encodes X25519 + Ed25519 keys into a single 65-byte array with versioning
|
|
34
|
+
*/
|
|
35
|
+
export declare function encodeUnifiedPubKeys(identityPubKey: Uint8Array, // X25519 - 32 bytes
|
|
36
|
+
signingPubKey: Uint8Array): Uint8Array;
|
|
37
|
+
/**
|
|
38
|
+
* Decodes unified pubKeys back to individual X25519 and Ed25519 keys
|
|
39
|
+
*/
|
|
40
|
+
export declare function decodeUnifiedPubKeys(pubKeys: Uint8Array): {
|
|
41
|
+
version: number;
|
|
42
|
+
identityPubKey: Uint8Array;
|
|
43
|
+
signingPubKey: Uint8Array;
|
|
44
|
+
} | null;
|
|
45
|
+
export interface HandshakePayload {
|
|
46
|
+
unifiedPubKeys: Uint8Array;
|
|
47
|
+
ephemeralPubKey: Uint8Array;
|
|
48
|
+
plaintextPayload: string;
|
|
49
|
+
}
|
|
50
|
+
export interface HandshakeResponseContent {
|
|
51
|
+
unifiedPubKeys: Uint8Array;
|
|
52
|
+
ephemeralPubKey: Uint8Array;
|
|
53
|
+
note?: string;
|
|
54
|
+
identityProof: IdentityProof;
|
|
55
|
+
topicInfo?: TopicInfoWire;
|
|
56
|
+
}
|
|
57
|
+
export declare function encodeHandshakePayload(payload: HandshakePayload): Uint8Array;
|
|
58
|
+
export declare function decodeHandshakePayload(encoded: Uint8Array): HandshakePayload;
|
|
59
|
+
export declare function encodeHandshakeResponseContent(content: HandshakeResponseContent): Uint8Array;
|
|
60
|
+
export declare function decodeHandshakeResponseContent(encoded: Uint8Array): HandshakeResponseContent;
|
|
61
|
+
/**
|
|
62
|
+
* Creates HandshakePayload from separate identity keys
|
|
63
|
+
*/
|
|
64
|
+
export declare function createHandshakePayload(identityPubKey: Uint8Array, signingPubKey: Uint8Array, ephemeralPubKey: Uint8Array, plaintextPayload: string): HandshakePayload;
|
|
65
|
+
/**
|
|
66
|
+
* Creates HandshakeResponseContent from separate identity keys
|
|
67
|
+
*/
|
|
68
|
+
export declare function createHandshakeResponseContent(identityPubKey: Uint8Array, signingPubKey: Uint8Array, ephemeralPubKey: Uint8Array, note?: string, identityProof?: IdentityProof, topicInfo?: TopicInfoWire): HandshakeResponseContent;
|
|
69
|
+
/**
|
|
70
|
+
* Extracts individual keys from HandshakePayload
|
|
71
|
+
*/
|
|
72
|
+
export declare function extractKeysFromHandshakePayload(payload: HandshakePayload): {
|
|
73
|
+
identityPubKey: Uint8Array;
|
|
74
|
+
signingPubKey: Uint8Array;
|
|
75
|
+
ephemeralPubKey: Uint8Array;
|
|
76
|
+
} | null;
|
|
77
|
+
/**
|
|
78
|
+
* Extracts individual keys from HandshakeResponseContent
|
|
79
|
+
*/
|
|
80
|
+
export declare function extractKeysFromHandshakeResponse(content: HandshakeResponseContent): {
|
|
81
|
+
identityPubKey: Uint8Array;
|
|
82
|
+
signingPubKey: Uint8Array;
|
|
83
|
+
ephemeralPubKey: Uint8Array;
|
|
84
|
+
} | null;
|
|
85
|
+
/**
|
|
86
|
+
* Parses unified pubKeys from HandshakeLog event
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseHandshakeKeys(event: {
|
|
89
|
+
pubKeys: string;
|
|
90
|
+
}): {
|
|
91
|
+
identityPubKey: Uint8Array;
|
|
92
|
+
signingPubKey: Uint8Array;
|
|
93
|
+
} | null;
|
|
94
|
+
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/payload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG1D,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;CACjE;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,CAUhF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAE3E;AAED,wBAAgB,aAAa,CAAC,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,MAAM,CAS9H;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAC3C,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB,CAyBA;AAID,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,UAAU,CAQjE;AAGD,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GACzB,CAAC,CAGH;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,UAAU,EAAG,oBAAoB;AACjD,aAAa,EAAE,UAAU,GACxB,UAAU,CAOZ;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;CAC3B,GAAG,IAAI,CAoBP;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,UAAU,CAAC;IAC3B,eAAe,EAAE,UAAU,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,UAAU,CAAC;IAC3B,eAAe,EAAE,UAAU,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU,CAM5E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,UAAU,GAAG,gBAAgB,CAQ5E;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,wBAAwB,GAAG,UAAU,CAY1F;AAEH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,UAAU,GAAG,wBAAwB,CAmB5F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,UAAU,EAC1B,aAAa,EAAE,UAAU,EACzB,eAAe,EAAE,UAAU,EAC3B,gBAAgB,EAAE,MAAM,GACvB,gBAAgB,CAMlB;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,UAAU,EAC1B,aAAa,EAAE,UAAU,EACzB,eAAe,EAAE,UAAU,EAC3B,IAAI,CAAC,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,aAAa,EAC7B,SAAS,CAAC,EAAE,aAAa,GACxB,wBAAwB,CAY1B;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,gBAAgB,GAAG;IAC1E,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;IAC1B,eAAe,EAAE,UAAU,CAAC;CAC7B,GAAG,IAAI,CASP;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,wBAAwB,GAAG;IACnF,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;IAC1B,eAAe,EAAE,UAAU,CAAC;CAC7B,GAAG,IAAI,CASP;AAGD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAC9D,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;CAC3B,GAAG,IAAI,CAkBP"}
|
|
@@ -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"}
|
|
@@ -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
|