@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
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# @verbeth/sdk
|
|
2
|
+
|
|
3
|
+
Verbeth enables secure, E2EE messaging using Ethereum event logs as the only transport layer. No servers, no relays—just the blockchain.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **End-to-end encryption** using NaCl Box (X25519 + XSalsa20-Poly1305)
|
|
8
|
+
- **Forward secrecy** with ephemeral keys per message
|
|
9
|
+
- **Handshake protocol** for secure key exchange
|
|
10
|
+
- **Privacy-focused** with minimal metadata via `recipientHash`
|
|
11
|
+
- **EOA & Smart Account support** (ERC-1271/6492 compatible)
|
|
12
|
+
- **Fully on-chain** - no centralized infrastructure
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @verbeth/sdk ethers tweetnacl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Initialize with VerbethClient (Recommended)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { VerbethClient, ExecutorFactory, deriveIdentityKeyPairWithProof } from '@verbeth/sdk';
|
|
26
|
+
import { Contract, BrowserProvider } from 'ethers';
|
|
27
|
+
import { LogChainV1__factory } from '@verbeth/contracts/typechain-types';
|
|
28
|
+
|
|
29
|
+
// Setup
|
|
30
|
+
const provider = new BrowserProvider(window.ethereum);
|
|
31
|
+
const signer = await provider.getSigner();
|
|
32
|
+
const address = await signer.getAddress();
|
|
33
|
+
|
|
34
|
+
// Create contract instance
|
|
35
|
+
const contract = LogChainV1__factory.connect(LOGCHAIN_ADDRESS, signer);
|
|
36
|
+
|
|
37
|
+
// Derive identity keys (done once, then stored)
|
|
38
|
+
const { identityKeyPair, identityProof } = await deriveIdentityKeyPairWithProof(signer);
|
|
39
|
+
|
|
40
|
+
// Create executor (handles transaction submission)
|
|
41
|
+
const executor = ExecutorFactory.createEOA(contract);
|
|
42
|
+
|
|
43
|
+
// Initialize client
|
|
44
|
+
const client = new VerbethClient({
|
|
45
|
+
executor,
|
|
46
|
+
identityKeyPair,
|
|
47
|
+
identityProof,
|
|
48
|
+
signer,
|
|
49
|
+
address
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Send a handshake to start chatting
|
|
53
|
+
const { tx, ephemeralKeyPair } = await client.sendHandshake(
|
|
54
|
+
'0xRecipientAddress...',
|
|
55
|
+
'Hello! Want to chat?'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Store ephemeralKeyPair. you'll just need it to decrypt the handshake response!
|
|
59
|
+
|
|
60
|
+
// Accept a handshake
|
|
61
|
+
const { tx, duplexTopics } = await client.acceptHandshake(
|
|
62
|
+
handshakeEvent.ephemeralPubKey,
|
|
63
|
+
handshakeEvent.identityPubKey,
|
|
64
|
+
'Sure, lets chat!'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Send encrypted messages
|
|
68
|
+
await client.sendMessage(
|
|
69
|
+
duplexTopics.topicOut,
|
|
70
|
+
recipientIdentityPubKey,
|
|
71
|
+
'This message is encrypted!'
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Decrypt received messages
|
|
75
|
+
const decrypted = await client.decryptMessage(
|
|
76
|
+
messageEvent.ciphertext,
|
|
77
|
+
senderIdentityPubKey
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Low-level API
|
|
82
|
+
|
|
83
|
+
For more control, use the low-level functions:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import {
|
|
87
|
+
initiateHandshake,
|
|
88
|
+
respondToHandshake,
|
|
89
|
+
sendEncryptedMessage,
|
|
90
|
+
decryptMessage,
|
|
91
|
+
deriveIdentityKeyPairWithProof
|
|
92
|
+
} from '@verbeth/sdk';
|
|
93
|
+
|
|
94
|
+
// Generate identity keys
|
|
95
|
+
const { identityKeyPair, identityProof } = await deriveIdentityKeyPairWithProof(signer);
|
|
96
|
+
|
|
97
|
+
// Initiate handshake
|
|
98
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
99
|
+
const tx = await initiateHandshake({
|
|
100
|
+
executor,
|
|
101
|
+
recipientAddress: '0xBob...',
|
|
102
|
+
ephemeralPubKey: ephemeralKeyPair.publicKey,
|
|
103
|
+
identityKeyPair,
|
|
104
|
+
identityProof,
|
|
105
|
+
plaintextPayload: 'Hi Bob!'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Send encrypted message
|
|
109
|
+
await sendEncryptedMessage({
|
|
110
|
+
executor,
|
|
111
|
+
topic: derivedTopic,
|
|
112
|
+
message: 'Secret message',
|
|
113
|
+
recipientPubKey: bobsIdentityKey,
|
|
114
|
+
senderAddress: myAddress,
|
|
115
|
+
senderSignKeyPair: identityKeyPair,
|
|
116
|
+
timestamp: Date.now()
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Decrypt message
|
|
120
|
+
const plaintext = decryptMessage(
|
|
121
|
+
ciphertext,
|
|
122
|
+
senderIdentityPubKey,
|
|
123
|
+
myIdentityKeyPair.secretKey
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Smart Account Support
|
|
128
|
+
|
|
129
|
+
Verbeth works with ERC-4337 smart accounts:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ExecutorFactory } from '@verbeth/sdk';
|
|
133
|
+
|
|
134
|
+
// For UserOp-based execution
|
|
135
|
+
const executor = ExecutorFactory.createUserOp(
|
|
136
|
+
contract,
|
|
137
|
+
bundler,
|
|
138
|
+
smartAccount,
|
|
139
|
+
signer
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// For direct EntryPoint execution
|
|
143
|
+
const executor = ExecutorFactory.createDirectEntryPoint(
|
|
144
|
+
contract,
|
|
145
|
+
entryPoint,
|
|
146
|
+
smartAccountAddress,
|
|
147
|
+
signer
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Contract Addresses
|
|
152
|
+
|
|
153
|
+
**LogChainV1 Singleton:** `0x41a3eaC0d858028E9228d1E2092e6178fc81c4f0`
|
|
154
|
+
|
|
155
|
+
**ERC1967Proxy:** `0x62720f39d5Ec6501508bDe4D152c1E13Fd2F6707`
|
|
156
|
+
|
|
157
|
+
## How It Works
|
|
158
|
+
|
|
159
|
+
1. **Identity Keys**: Each account derives long-term X25519 (encryption) + Ed25519 (signing) keys bound to their address via signature
|
|
160
|
+
2. **Handshake**: Alice sends her ephemeral key + identity proof to Bob via a `Handshake` event
|
|
161
|
+
3. **Response**: Bob verifies Alice's identity and responds with his keys + duplex topics
|
|
162
|
+
4. **Messaging**: Both parties derive shared topics and exchange encrypted messages via `MessageSent` events
|
|
163
|
+
5. **Decryption**: Recipients monitor their inbound topic and decrypt with their identity key
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Security Considerations
|
|
167
|
+
|
|
168
|
+
- **Forward Secrecy**: Fresh ephemeral keys per message provide sender-side forward secrecy
|
|
169
|
+
- **Identity Binding**: Addresses are cryptographically bound to long-term keys via signature
|
|
170
|
+
- **Non-Repudiation**: Optional Ed25519 signatures prove message origin
|
|
171
|
+
- **Privacy**: RecipientHash hides recipient identity; duplex topics separate communication channels
|
|
172
|
+
|
|
173
|
+
⚠️ **Note**: Current design provides sender-side forward secrecy. Recipient-side FS requires ephemeral↔ephemeral or session ratcheting (e.g., Double Ratchet).
|
|
174
|
+
|
|
175
|
+
## Built With
|
|
176
|
+
|
|
177
|
+
- [TweetNaCl](https://tweetnacl.js.org/) - Encryption primitives
|
|
178
|
+
- [Ethers v6](https://docs.ethers.org/v6/) - Ethereum interactions
|
|
179
|
+
- [Viem](https://viem.sh/) - EIP-1271/6492 verification
|
|
180
|
+
- [Noble Curves](https://github.com/paulmillr/noble-curves) - Elliptic curve operations
|
|
181
|
+
|
|
182
|
+
## Examples
|
|
183
|
+
|
|
184
|
+
Check out the [demo application](https://github.com/okrame/verbeth-sdk/tree/main/apps/demo) for a complete implementation.
|
|
185
|
+
|
|
186
|
+
## Documentation
|
|
187
|
+
|
|
188
|
+
For detailed protocol documentation, security analysis, and improvement proposals, see the [main repository](https://github.com/okrame/verbeth-sdk).
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MPL-2.0
|
|
193
|
+
|
|
194
|
+
## Links
|
|
195
|
+
|
|
196
|
+
- [GitHub Repository](https://github.com/okrame/verbeth-sdk)
|
|
197
|
+
- [Demo App](https://verbeth-demo.vercel.app/)
|
|
198
|
+
- [Contract Source](https://github.com/okrame/verbeth-sdk/tree/main/packages/contracts)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
**Questions or feedback?** Open an issue on [GitHub](https://github.com/okrame/verbeth-sdk/issues).
|
|
@@ -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"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// packages/sdk/src/client/VerbethClient.ts
|
|
2
|
+
import nacl from 'tweetnacl';
|
|
3
|
+
import { initiateHandshake, respondToHandshake, sendEncryptedMessage } from '../send.js';
|
|
4
|
+
import { deriveDuplexTopics } from '../crypto.js';
|
|
5
|
+
import * as crypto from '../crypto.js';
|
|
6
|
+
import * as payload from '../payload.js';
|
|
7
|
+
import * as verify from '../verify.js';
|
|
8
|
+
import * as utils from '../utils.js';
|
|
9
|
+
import * as identity from '../identity.js';
|
|
10
|
+
/**
|
|
11
|
+
* High-level client for Verbeth E2EE messaging
|
|
12
|
+
*
|
|
13
|
+
* VerbethClient provides a simplified API for common operations while
|
|
14
|
+
* maintaining access to all low-level functions.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const client = new VerbethClient({
|
|
19
|
+
* executor,
|
|
20
|
+
* identityKeyPair,
|
|
21
|
+
* identityProof,
|
|
22
|
+
* signer,
|
|
23
|
+
* address: '0x...'
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Send a handshake
|
|
27
|
+
* const { tx, ephemeralKeyPair } = await client.sendHandshake(
|
|
28
|
+
* '0xBob...',
|
|
29
|
+
* 'Hello Bob!'
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* // Send a message
|
|
33
|
+
* await client.sendMessage(
|
|
34
|
+
* contact.topicOutbound,
|
|
35
|
+
* contact.identityPubKey,
|
|
36
|
+
* 'Hello again!'
|
|
37
|
+
* );
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class VerbethClient {
|
|
41
|
+
/**
|
|
42
|
+
* creates a new VerbethClient instance
|
|
43
|
+
*
|
|
44
|
+
* @param config - Client configuration with session-level parameters
|
|
45
|
+
*/
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.executor = config.executor;
|
|
48
|
+
this.identityKeyPair = config.identityKeyPair;
|
|
49
|
+
this.identityProof = config.identityProof;
|
|
50
|
+
this.signer = config.signer;
|
|
51
|
+
this.address = config.address;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Initiates a handshake with a recipient
|
|
55
|
+
*
|
|
56
|
+
* generates an ephemeral keypair for this handshake.
|
|
57
|
+
* the ephemeralKeyPair must be stored to decrypt the response later.
|
|
58
|
+
*
|
|
59
|
+
* @param recipientAddress - Blockchain address of the recipient
|
|
60
|
+
* @param message - Plaintext message to include in the handshake
|
|
61
|
+
* @returns Transaction response and the ephemeral keypair (must be stored!)
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const { tx, ephemeralKeyPair } = await client.sendHandshake(
|
|
66
|
+
* '0xBob...',
|
|
67
|
+
* 'Hi Bob!'
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* // Store ephemeralKeyPair.secretKey to decrypt Bob's response
|
|
71
|
+
* await storage.saveContact({
|
|
72
|
+
* address: '0xBob...',
|
|
73
|
+
* ephemeralKey: ephemeralKeyPair.secretKey,
|
|
74
|
+
* // ...
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
async sendHandshake(recipientAddress, message) {
|
|
79
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
80
|
+
const tx = await initiateHandshake({
|
|
81
|
+
executor: this.executor,
|
|
82
|
+
recipientAddress,
|
|
83
|
+
identityKeyPair: this.identityKeyPair,
|
|
84
|
+
ephemeralPubKey: ephemeralKeyPair.publicKey,
|
|
85
|
+
plaintextPayload: message,
|
|
86
|
+
identityProof: this.identityProof,
|
|
87
|
+
signer: this.signer,
|
|
88
|
+
});
|
|
89
|
+
return { tx, ephemeralKeyPair };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Accepts a handshake from an initiator
|
|
93
|
+
*
|
|
94
|
+
* derives duplex topics for the conversation and returns them.
|
|
95
|
+
*
|
|
96
|
+
* @param initiatorEphemeralPubKey - initiator's ephemeral public key from handshake event
|
|
97
|
+
* @param initiatorIdentityPubKey - initiator's long-term X25519 identity key
|
|
98
|
+
* @param note - response message to send back
|
|
99
|
+
* @returns transaction, derived duplex topics, and response tag
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const { tx, duplexTopics } = await client.acceptHandshake(
|
|
104
|
+
* handshake.ephemeralPubKey,
|
|
105
|
+
* handshake.identityPubKey,
|
|
106
|
+
* 'Hello Alice!'
|
|
107
|
+
* );
|
|
108
|
+
*
|
|
109
|
+
* // Store the topics for future messaging
|
|
110
|
+
* await storage.saveContact({
|
|
111
|
+
* address: handshake.sender,
|
|
112
|
+
* topicOutbound: duplexTopics.topicIn, // Responder writes to topicIn
|
|
113
|
+
* topicInbound: duplexTopics.topicOut, // Responder reads from topicOut
|
|
114
|
+
* // ...
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async acceptHandshake(initiatorEphemeralPubKey, initiatorIdentityPubKey, note) {
|
|
119
|
+
const { tx, salt, tag } = await respondToHandshake({
|
|
120
|
+
executor: this.executor,
|
|
121
|
+
initiatorPubKey: initiatorEphemeralPubKey,
|
|
122
|
+
responderIdentityKeyPair: this.identityKeyPair,
|
|
123
|
+
note,
|
|
124
|
+
identityProof: this.identityProof,
|
|
125
|
+
signer: this.signer,
|
|
126
|
+
initiatorIdentityPubKey,
|
|
127
|
+
});
|
|
128
|
+
const duplexTopics = deriveDuplexTopics(this.identityKeyPair.secretKey, initiatorIdentityPubKey, salt);
|
|
129
|
+
return { tx, duplexTopics, tag };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sends an encrypted message to a contact
|
|
133
|
+
*
|
|
134
|
+
* handles timestamp, signing keys, and sender address.
|
|
135
|
+
*
|
|
136
|
+
* @param topicOutbound - The outbound topic for this conversation
|
|
137
|
+
* @param recipientPubKey - Recipient's X25519 public key (from handshake)
|
|
138
|
+
* @param message - Plaintext message to encrypt and send
|
|
139
|
+
* @returns Transaction response
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* await client.sendMessage(
|
|
144
|
+
* contact.topicOutbound,
|
|
145
|
+
* contact.identityPubKey,
|
|
146
|
+
* 'Hello again!'
|
|
147
|
+
* );
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
async sendMessage(topicOutbound, recipientPubKey, message) {
|
|
151
|
+
const signingKeyPair = {
|
|
152
|
+
publicKey: this.identityKeyPair.signingPublicKey,
|
|
153
|
+
secretKey: this.identityKeyPair.signingSecretKey,
|
|
154
|
+
};
|
|
155
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
156
|
+
return sendEncryptedMessage({
|
|
157
|
+
executor: this.executor,
|
|
158
|
+
topic: topicOutbound,
|
|
159
|
+
message,
|
|
160
|
+
recipientPubKey,
|
|
161
|
+
senderAddress: this.address,
|
|
162
|
+
senderSignKeyPair: signingKeyPair,
|
|
163
|
+
timestamp,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// ========== low-level API ==========
|
|
167
|
+
get crypto() {
|
|
168
|
+
return crypto;
|
|
169
|
+
}
|
|
170
|
+
get payload() {
|
|
171
|
+
return payload;
|
|
172
|
+
}
|
|
173
|
+
get verify() {
|
|
174
|
+
return verify;
|
|
175
|
+
}
|
|
176
|
+
get utils() {
|
|
177
|
+
return utils;
|
|
178
|
+
}
|
|
179
|
+
get identity() {
|
|
180
|
+
return identity;
|
|
181
|
+
}
|
|
182
|
+
get executorInstance() {
|
|
183
|
+
return this.executor;
|
|
184
|
+
}
|
|
185
|
+
get identityKeyPairInstance() {
|
|
186
|
+
return this.identityKeyPair;
|
|
187
|
+
}
|
|
188
|
+
get userAddress() {
|
|
189
|
+
return this.address;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACxB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Signer } from 'ethers';
|
|
2
|
+
import type { IExecutor } from '../executor.js';
|
|
3
|
+
import type { IdentityKeyPair, IdentityProof, DuplexTopics } from '../types.js';
|
|
4
|
+
import type nacl from 'tweetnacl';
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for creating a VerbethClient instance
|
|
7
|
+
*/
|
|
8
|
+
export interface VerbethClientConfig {
|
|
9
|
+
executor: IExecutor;
|
|
10
|
+
identityKeyPair: IdentityKeyPair;
|
|
11
|
+
identityProof: IdentityProof;
|
|
12
|
+
signer: Signer;
|
|
13
|
+
address: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Result from initiating a handshake
|
|
17
|
+
*/
|
|
18
|
+
export interface HandshakeResult {
|
|
19
|
+
tx: any;
|
|
20
|
+
ephemeralKeyPair: nacl.BoxKeyPair;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Result from accepting a handshake
|
|
24
|
+
*/
|
|
25
|
+
export interface HandshakeResponseResult {
|
|
26
|
+
tx: any;
|
|
27
|
+
duplexTopics: DuplexTopics;
|
|
28
|
+
tag: string;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,GAAG,CAAC;IACR,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,GAAG,CAAC;IACR,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { HandshakeResponseContent } from './payload.js';
|
|
2
|
+
import { IdentityProof } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Encrypts a structured payload (JSON-serializable objects)
|
|
5
|
+
*/
|
|
6
|
+
export declare function encryptStructuredPayload<T>(payload: T, recipientPublicKey: Uint8Array, ephemeralSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, staticSigningSecretKey?: Uint8Array, staticSigningPublicKey?: Uint8Array): string;
|
|
7
|
+
/**
|
|
8
|
+
* Decrypts a structured payload with converter function
|
|
9
|
+
*/
|
|
10
|
+
export declare function decryptStructuredPayload<T>(payloadJson: string, recipientSecretKey: Uint8Array, converter: (obj: any) => T, staticSigningPublicKey?: Uint8Array): T | null;
|
|
11
|
+
export declare function encryptMessage(message: string, recipientPublicKey: Uint8Array, ephemeralSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, staticSigningSecretKey?: Uint8Array, staticSigningPublicKey?: Uint8Array): string;
|
|
12
|
+
export declare function decryptMessage(payloadJson: string, recipientSecretKey: Uint8Array, staticSigningPublicKey?: Uint8Array): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Decrypts handshake response and extracts individual keys from unified format
|
|
15
|
+
*/
|
|
16
|
+
export declare function decryptHandshakeResponse(payloadJson: string, initiatorEphemeralSecretKey: Uint8Array): HandshakeResponseContent | null;
|
|
17
|
+
/**
|
|
18
|
+
* helper to decrypt handshake response and extract individual keys
|
|
19
|
+
*/
|
|
20
|
+
export declare function decryptAndExtractHandshakeKeys(payloadJson: string, initiatorEphemeralSecretKey: Uint8Array): {
|
|
21
|
+
identityPubKey: Uint8Array;
|
|
22
|
+
signingPubKey: Uint8Array;
|
|
23
|
+
ephemeralPubKey: Uint8Array;
|
|
24
|
+
note?: string;
|
|
25
|
+
identityProof: IdentityProof;
|
|
26
|
+
} | null;
|
|
27
|
+
/**
|
|
28
|
+
* Responder: tag = H( KDF( ECDH(r, viewPubA), "verbeth:hsr"))
|
|
29
|
+
*/
|
|
30
|
+
export declare function computeTagFromResponder(rSecretKey: Uint8Array, viewPubA: Uint8Array): `0x${string}`;
|
|
31
|
+
/**
|
|
32
|
+
* Initiator: tag = H( KDF( ECDH(viewPrivA, R), "verbeth:hsr"))
|
|
33
|
+
*/
|
|
34
|
+
export declare function computeTagFromInitiator(viewPrivA: Uint8Array, R: Uint8Array): `0x${string}`;
|
|
35
|
+
export declare function deriveLongTermShared(myIdentitySecretKey: Uint8Array, theirIdentityPublicKey: Uint8Array): Uint8Array;
|
|
36
|
+
/**
|
|
37
|
+
* Directional duplex topics (Initiator-Responder, Responder-Initiator).
|
|
38
|
+
* Recommended salt: tag (bytes)
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveDuplexTopics(myIdentitySecretKey: Uint8Array, theirIdentityPublicKey: Uint8Array, salt?: Uint8Array): {
|
|
41
|
+
topicOut: `0x${string}`;
|
|
42
|
+
topicIn: `0x${string}`;
|
|
43
|
+
checksum: `0x${string}`;
|
|
44
|
+
};
|
|
45
|
+
export declare function verifyDuplexTopicsChecksum(topicOut: `0x${string}`, topicIn: `0x${string}`, checksum: `0x${string}`): boolean;
|
|
46
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/crypto.ts"],"names":[],"mappings":"AAMA,OAAO,EAML,wBAAwB,EAEzB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,OAAO,EAAE,CAAC,EACV,kBAAkB,EAAE,UAAU,EAC9B,kBAAkB,EAAE,UAAU,EAC9B,kBAAkB,EAAE,UAAU,EAC9B,sBAAsB,CAAC,EAAE,UAAU,EACnC,sBAAsB,CAAC,EAAE,UAAU,GAClC,MAAM,CAcR;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,UAAU,EAC9B,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,EAC1B,sBAAsB,CAAC,EAAE,UAAU,GAClC,CAAC,GAAG,IAAI,CAaV;AAGD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,UAAU,EAC9B,kBAAkB,EAAE,UAAU,EAC9B,kBAAkB,EAAE,UAAU,EAC9B,sBAAsB,CAAC,EAAE,UAAU,EACnC,sBAAsB,CAAC,EAAE,UAAU,GAClC,MAAM,CAUR;AAED,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,UAAU,EAC9B,sBAAsB,CAAC,EAAE,UAAU,GAClC,MAAM,GAAG,IAAI,CAQf;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,2BAA2B,EAAE,UAAU,GACtC,wBAAwB,GAAG,IAAI,CAgBjC;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,EACnB,2BAA2B,EAAE,UAAU,GACtC;IACD,cAAc,EAAE,UAAU,CAAC;IAC3B,aAAa,EAAE,UAAU,CAAC;IAC1B,eAAe,EAAE,UAAU,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;CAC9B,GAAG,IAAI,CAcP;AAWD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,UAAU,GACnB,KAAK,MAAM,EAAE,CAGf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,UAAU,EACrB,CAAC,EAAE,UAAU,GACZ,KAAK,MAAM,EAAE,CAGf;AAkBD,wBAAgB,oBAAoB,CAClC,mBAAmB,EAAE,UAAU,EAC/B,sBAAsB,EAAE,UAAU,GACjC,UAAU,CAEZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,mBAAmB,EAAE,UAAU,EAC/B,sBAAsB,EAAE,UAAU,EAClC,IAAI,CAAC,EAAE,UAAU,GAChB;IAAE,QAAQ,EAAE,KAAK,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,CAW9E;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,KAAK,MAAM,EAAE,EACvB,OAAO,EAAE,KAAK,MAAM,EAAE,EACtB,QAAQ,EAAE,KAAK,MAAM,EAAE,GACtB,OAAO,CAOT"}
|