@massalabs/gossip-sdk 0.0.2-dev.20260128094509 → 0.0.2-dev.20260128111120
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/dist/api/messageProtocol/index.d.ts +19 -0
- package/dist/api/messageProtocol/index.js +26 -0
- package/dist/api/messageProtocol/mock.d.ts +12 -0
- package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
- package/dist/api/messageProtocol/rest.d.ts +22 -0
- package/dist/api/messageProtocol/rest.js +161 -0
- package/dist/api/messageProtocol/types.d.ts +61 -0
- package/dist/api/messageProtocol/types.js +6 -0
- package/dist/assets/generated/wasm/README.md +281 -0
- package/dist/assets/generated/wasm/gossip_wasm.d.ts +498 -0
- package/dist/assets/generated/wasm/gossip_wasm.js +1399 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +68 -0
- package/dist/assets/generated/wasm/package.json +15 -0
- package/dist/config/protocol.d.ts +36 -0
- package/dist/config/protocol.js +77 -0
- package/dist/config/sdk.d.ts +82 -0
- package/dist/config/sdk.js +55 -0
- package/{src/contacts.ts → dist/contacts.d.ts} +10 -94
- package/dist/contacts.js +166 -0
- package/dist/core/SdkEventEmitter.d.ts +36 -0
- package/dist/core/SdkEventEmitter.js +59 -0
- package/dist/core/SdkPolling.d.ts +35 -0
- package/dist/core/SdkPolling.js +100 -0
- package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
- package/dist/core/index.js +5 -0
- package/dist/crypto/bip39.d.ts +34 -0
- package/dist/crypto/bip39.js +62 -0
- package/dist/crypto/encryption.d.ts +37 -0
- package/dist/crypto/encryption.js +46 -0
- package/dist/db.d.ts +190 -0
- package/dist/db.js +311 -0
- package/dist/gossipSdk.d.ts +274 -0
- package/dist/gossipSdk.js +690 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +77 -0
- package/dist/services/announcement.d.ts +43 -0
- package/dist/services/announcement.js +491 -0
- package/dist/services/auth.d.ts +37 -0
- package/dist/services/auth.js +76 -0
- package/dist/services/discussion.d.ts +63 -0
- package/dist/services/discussion.js +297 -0
- package/dist/services/message.d.ts +74 -0
- package/dist/services/message.js +826 -0
- package/dist/services/refresh.d.ts +41 -0
- package/dist/services/refresh.js +205 -0
- package/{src/sw.ts → dist/sw.d.ts} +1 -8
- package/dist/sw.js +10 -0
- package/dist/types/events.d.ts +80 -0
- package/dist/types/events.js +7 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +7 -0
- package/dist/utils/base64.d.ts +10 -0
- package/dist/utils/base64.js +30 -0
- package/dist/utils/contacts.d.ts +42 -0
- package/dist/utils/contacts.js +113 -0
- package/dist/utils/discussions.d.ts +24 -0
- package/dist/utils/discussions.js +38 -0
- package/dist/utils/logs.d.ts +19 -0
- package/dist/utils/logs.js +89 -0
- package/dist/utils/messageSerialization.d.ts +64 -0
- package/dist/utils/messageSerialization.js +184 -0
- package/dist/utils/queue.d.ts +50 -0
- package/dist/utils/queue.js +110 -0
- package/dist/utils/type.d.ts +10 -0
- package/dist/utils/type.js +4 -0
- package/dist/utils/userId.d.ts +40 -0
- package/dist/utils/userId.js +90 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +112 -0
- package/dist/utils.d.ts +30 -0
- package/{src/utils.ts → dist/utils.js} +9 -19
- package/dist/wasm/encryption.d.ts +56 -0
- package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
- package/dist/wasm/index.d.ts +10 -0
- package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
- package/dist/wasm/loader.d.ts +21 -0
- package/dist/wasm/loader.js +103 -0
- package/dist/wasm/session.d.ts +85 -0
- package/dist/wasm/session.js +226 -0
- package/dist/wasm/userKeys.d.ts +17 -0
- package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
- package/package.json +5 -1
- package/src/api/messageProtocol/index.ts +0 -53
- package/src/api/messageProtocol/rest.ts +0 -209
- package/src/api/messageProtocol/types.ts +0 -70
- package/src/config/protocol.ts +0 -97
- package/src/config/sdk.ts +0 -131
- package/src/core/SdkEventEmitter.ts +0 -91
- package/src/core/SdkPolling.ts +0 -134
- package/src/crypto/bip39.ts +0 -84
- package/src/crypto/encryption.ts +0 -77
- package/src/db.ts +0 -465
- package/src/gossipSdk.ts +0 -994
- package/src/index.ts +0 -211
- package/src/services/announcement.ts +0 -653
- package/src/services/auth.ts +0 -95
- package/src/services/discussion.ts +0 -380
- package/src/services/message.ts +0 -1055
- package/src/services/refresh.ts +0 -234
- package/src/types/events.ts +0 -108
- package/src/types.ts +0 -70
- package/src/utils/base64.ts +0 -39
- package/src/utils/contacts.ts +0 -161
- package/src/utils/discussions.ts +0 -55
- package/src/utils/logs.ts +0 -86
- package/src/utils/messageSerialization.ts +0 -257
- package/src/utils/queue.ts +0 -106
- package/src/utils/type.ts +0 -7
- package/src/utils/userId.ts +0 -114
- package/src/utils/validation.ts +0 -144
- package/src/wasm/loader.ts +0 -123
- package/src/wasm/session.ts +0 -276
- package/test/config/protocol.spec.ts +0 -31
- package/test/config/sdk.spec.ts +0 -163
- package/test/db/helpers.spec.ts +0 -142
- package/test/db/operations.spec.ts +0 -128
- package/test/db/states.spec.ts +0 -535
- package/test/integration/discussion-flow.spec.ts +0 -422
- package/test/integration/messaging-flow.spec.ts +0 -708
- package/test/integration/sdk-lifecycle.spec.ts +0 -325
- package/test/mocks/index.ts +0 -9
- package/test/mocks/mockMessageProtocol.ts +0 -100
- package/test/services/auth.spec.ts +0 -311
- package/test/services/discussion.spec.ts +0 -279
- package/test/services/message-deduplication.spec.ts +0 -299
- package/test/services/message-startup.spec.ts +0 -331
- package/test/services/message.spec.ts +0 -817
- package/test/services/refresh.spec.ts +0 -199
- package/test/services/session-status.spec.ts +0 -349
- package/test/session/wasm.spec.ts +0 -227
- package/test/setup.ts +0 -52
- package/test/utils/contacts.spec.ts +0 -156
- package/test/utils/discussions.spec.ts +0 -66
- package/test/utils/queue.spec.ts +0 -52
- package/test/utils/serialization.spec.ts +0 -120
- package/test/utils/userId.spec.ts +0 -120
- package/test/utils/validation.spec.ts +0 -223
- package/test/utils.ts +0 -212
- package/tsconfig.json +0 -26
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -28
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Protocol Module
|
|
3
|
+
*
|
|
4
|
+
* Factory functions and exports for message protocol implementations.
|
|
5
|
+
*/
|
|
6
|
+
export type { EncryptedMessage, IMessageProtocol, MessageProtocolResponse, BulletinItem, } from './types';
|
|
7
|
+
export { RestMessageProtocol } from './rest';
|
|
8
|
+
export { MessageProtocol } from './mock';
|
|
9
|
+
import type { IMessageProtocol } from './types';
|
|
10
|
+
import { type MessageProtocolType } from '../../config/protocol';
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create message protocol instances
|
|
13
|
+
*/
|
|
14
|
+
export declare function createMessageProtocol(type?: MessageProtocolType, config?: Partial<{
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
timeout: number;
|
|
17
|
+
retryAttempts: number;
|
|
18
|
+
}>): IMessageProtocol;
|
|
19
|
+
export declare const restMessageProtocol: IMessageProtocol;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Protocol Module
|
|
3
|
+
*
|
|
4
|
+
* Factory functions and exports for message protocol implementations.
|
|
5
|
+
*/
|
|
6
|
+
export { RestMessageProtocol } from './rest';
|
|
7
|
+
export { MessageProtocol } from './mock';
|
|
8
|
+
import { defaultMessageProtocol, protocolConfig, } from '../../config/protocol';
|
|
9
|
+
import { RestMessageProtocol } from './rest';
|
|
10
|
+
import { MessageProtocol } from './mock';
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create message protocol instances
|
|
13
|
+
*/
|
|
14
|
+
export function createMessageProtocol(type = defaultMessageProtocol, config) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'rest': {
|
|
17
|
+
return new RestMessageProtocol(config?.baseUrl || protocolConfig.baseUrl, config?.timeout || 10000, config?.retryAttempts || 3);
|
|
18
|
+
}
|
|
19
|
+
case 'mock': {
|
|
20
|
+
return new MessageProtocol(config?.baseUrl || protocolConfig.baseUrl, config?.timeout || 10000, config?.retryAttempts || 3);
|
|
21
|
+
}
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unsupported message protocol type: ${type}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const restMessageProtocol = createMessageProtocol();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Protocol Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides a concrete protocol class backed by the REST implementation.
|
|
5
|
+
* This uses the real Gossip API rather than a mock transport.
|
|
6
|
+
*/
|
|
7
|
+
import { RestMessageProtocol } from './rest';
|
|
8
|
+
/**
|
|
9
|
+
* Create a MessageProtocol instance backed by REST.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MessageProtocol extends RestMessageProtocol {
|
|
12
|
+
}
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* Provides a concrete protocol class backed by the REST implementation.
|
|
5
5
|
* This uses the real Gossip API rather than a mock transport.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
7
|
import { RestMessageProtocol } from './rest';
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* Create a MessageProtocol instance backed by REST.
|
|
12
10
|
*/
|
|
13
|
-
export class MessageProtocol extends RestMessageProtocol {
|
|
11
|
+
export class MessageProtocol extends RestMessageProtocol {
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST API implementation of the message protocol
|
|
3
|
+
*/
|
|
4
|
+
import { BulletinItem, EncryptedMessage, IMessageProtocol, MessageProtocolResponse } from './types';
|
|
5
|
+
export type BulletinsPage = {
|
|
6
|
+
counter: string;
|
|
7
|
+
data: string;
|
|
8
|
+
}[];
|
|
9
|
+
export declare class RestMessageProtocol implements IMessageProtocol {
|
|
10
|
+
private baseUrl;
|
|
11
|
+
private timeout;
|
|
12
|
+
private retryAttempts;
|
|
13
|
+
constructor(baseUrl: string, timeout?: number, retryAttempts?: number);
|
|
14
|
+
fetchMessages(seekers: Uint8Array[]): Promise<EncryptedMessage[]>;
|
|
15
|
+
sendMessage(message: EncryptedMessage): Promise<void>;
|
|
16
|
+
sendAnnouncement(announcement: Uint8Array): Promise<string>;
|
|
17
|
+
fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
|
|
18
|
+
fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
|
|
19
|
+
postPublicKey(base64PublicKeys: string): Promise<string>;
|
|
20
|
+
private makeRequest;
|
|
21
|
+
changeNode(nodeUrl?: string): Promise<MessageProtocolResponse>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST API implementation of the message protocol
|
|
3
|
+
*/
|
|
4
|
+
import { encodeToBase64, decodeFromBase64 } from '../../utils/base64';
|
|
5
|
+
const BULLETIN_ENDPOINT = '/bulletin';
|
|
6
|
+
const MESSAGES_ENDPOINT = '/messages';
|
|
7
|
+
export class RestMessageProtocol {
|
|
8
|
+
constructor(baseUrl, timeout = 10000, retryAttempts = 3) {
|
|
9
|
+
Object.defineProperty(this, "baseUrl", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: baseUrl
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "timeout", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: timeout
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "retryAttempts", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: retryAttempts
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// TODO: Implement a fetch with pagination to avoid fetching all messages at once
|
|
29
|
+
async fetchMessages(seekers) {
|
|
30
|
+
const url = `${this.baseUrl}${MESSAGES_ENDPOINT}/fetch`;
|
|
31
|
+
const response = await this.makeRequest(url, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({ seekers: seekers.map(encodeToBase64) }),
|
|
35
|
+
});
|
|
36
|
+
if (!response.success || !response.data) {
|
|
37
|
+
throw new Error(response.error || 'Failed to fetch messages');
|
|
38
|
+
}
|
|
39
|
+
return response.data.map((item) => {
|
|
40
|
+
const seeker = decodeFromBase64(item.key);
|
|
41
|
+
const ciphertext = decodeFromBase64(item.value);
|
|
42
|
+
return {
|
|
43
|
+
seeker,
|
|
44
|
+
ciphertext,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async sendMessage(message) {
|
|
49
|
+
const url = `${this.baseUrl}${MESSAGES_ENDPOINT}/`;
|
|
50
|
+
const response = await this.makeRequest(url, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
key: encodeToBase64(message.seeker),
|
|
55
|
+
value: encodeToBase64(message.ciphertext),
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
if (!response.success) {
|
|
59
|
+
throw new Error(response.error || 'Failed to send message');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async sendAnnouncement(announcement) {
|
|
63
|
+
const url = `${this.baseUrl}${BULLETIN_ENDPOINT}`;
|
|
64
|
+
const response = await this.makeRequest(url, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
data: encodeToBase64(announcement),
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
if (!response.success || !response.data) {
|
|
72
|
+
throw new Error(response.error || 'Failed to broadcast outgoing session');
|
|
73
|
+
}
|
|
74
|
+
return response.data.counter;
|
|
75
|
+
}
|
|
76
|
+
async fetchAnnouncements(limit = 50, cursor) {
|
|
77
|
+
const params = new URLSearchParams();
|
|
78
|
+
params.set('limit', limit.toString());
|
|
79
|
+
// Always pass 'after' parameter. If cursor is undefined, use '0' to fetch from the beginning.
|
|
80
|
+
// This ensures pagination works correctly: after=0 gets counters 1-20, after=20 gets 21-40, etc.
|
|
81
|
+
params.set('after', cursor ?? '0');
|
|
82
|
+
const url = `${this.baseUrl}${BULLETIN_ENDPOINT}?${params.toString()}`;
|
|
83
|
+
const response = await this.makeRequest(url, {
|
|
84
|
+
method: 'GET',
|
|
85
|
+
});
|
|
86
|
+
if (!response.success || response.data == null) {
|
|
87
|
+
throw new Error(response.error || 'Failed to fetch announcements');
|
|
88
|
+
}
|
|
89
|
+
return response.data.map(item => ({
|
|
90
|
+
counter: item.counter,
|
|
91
|
+
data: decodeFromBase64(item.data),
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
async fetchPublicKeyByUserId(userId) {
|
|
95
|
+
const response = await this.makeRequest(`${this.baseUrl}/auth/retrieve`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
body: JSON.stringify({ key: encodeToBase64(userId) }),
|
|
99
|
+
});
|
|
100
|
+
if (!response.success || !response.data) {
|
|
101
|
+
throw new Error(response.error || 'Failed to fetch public key');
|
|
102
|
+
}
|
|
103
|
+
if (!response.data.value) {
|
|
104
|
+
throw new Error('Public key not found');
|
|
105
|
+
}
|
|
106
|
+
return response.data.value;
|
|
107
|
+
}
|
|
108
|
+
async postPublicKey(base64PublicKeys) {
|
|
109
|
+
const url = `${this.baseUrl}/auth`;
|
|
110
|
+
const response = await this.makeRequest(url, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({ value: base64PublicKeys }),
|
|
114
|
+
});
|
|
115
|
+
if (!response.success || !response.data) {
|
|
116
|
+
const errorMessage = response.error || 'Failed to store public key';
|
|
117
|
+
throw new Error(errorMessage);
|
|
118
|
+
}
|
|
119
|
+
return response.data.value;
|
|
120
|
+
}
|
|
121
|
+
async makeRequest(url, options) {
|
|
122
|
+
let lastError = null;
|
|
123
|
+
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
124
|
+
try {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
127
|
+
const response = await fetch(url, {
|
|
128
|
+
...options,
|
|
129
|
+
signal: controller.signal,
|
|
130
|
+
});
|
|
131
|
+
clearTimeout(timeoutId);
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
134
|
+
}
|
|
135
|
+
const data = await response.json();
|
|
136
|
+
return { success: true, data };
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
lastError = error;
|
|
140
|
+
console.warn(`Request attempt ${attempt} failed:`, error);
|
|
141
|
+
if (attempt < this.retryAttempts) {
|
|
142
|
+
// Exponential backoff
|
|
143
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: lastError?.message || 'Request failed after all retry attempts',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async changeNode(nodeUrl) {
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
data: 'This message protocol provider use a single node, so changing the node to ' +
|
|
157
|
+
nodeUrl +
|
|
158
|
+
' is not supported',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Protocol Types and Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Defines the core types and interfaces for message protocol operations.
|
|
5
|
+
*/
|
|
6
|
+
export type BulletinItem = {
|
|
7
|
+
counter: string;
|
|
8
|
+
data: Uint8Array;
|
|
9
|
+
};
|
|
10
|
+
export interface EncryptedMessage {
|
|
11
|
+
seeker: Uint8Array;
|
|
12
|
+
ciphertext: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
export interface MessageProtocolResponse<T = unknown> {
|
|
15
|
+
success: boolean;
|
|
16
|
+
data?: T;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Abstract interface for message protocol operations
|
|
21
|
+
*/
|
|
22
|
+
export interface IMessageProtocol {
|
|
23
|
+
/**
|
|
24
|
+
* Fetch encrypted messages for the provided set of seeker read keys
|
|
25
|
+
*/
|
|
26
|
+
fetchMessages(seekers: Uint8Array[]): Promise<EncryptedMessage[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Send an encrypted message to the key-value store
|
|
29
|
+
*/
|
|
30
|
+
sendMessage(message: EncryptedMessage): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Broadcast an outgoing session announcement produced by WASM.
|
|
33
|
+
* Returns the bulletin counter provided by the API.
|
|
34
|
+
*/
|
|
35
|
+
sendAnnouncement(announcement: Uint8Array): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch incoming discussion announcements from the bulletin storage.
|
|
38
|
+
* Returns raw announcement bytes as provided by the API.
|
|
39
|
+
* @param limit - Maximum number of announcements to fetch (default: 20)
|
|
40
|
+
* @param cursor - Optional cursor (counter) to fetch announcements after this value
|
|
41
|
+
*/
|
|
42
|
+
fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Fetch public key by userId hash (base64 string)
|
|
45
|
+
* @param userId - Decoded userId bytes
|
|
46
|
+
* @returns Base64-encoded public keys
|
|
47
|
+
*/
|
|
48
|
+
fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Store public key in the auth API
|
|
51
|
+
* @param base64PublicKeys - Base64-encoded public keys
|
|
52
|
+
* @returns The hash key (hex string) returned by the API
|
|
53
|
+
*/
|
|
54
|
+
postPublicKey(base64PublicKeys: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Change the current node provider
|
|
57
|
+
* @param nodeUrl - The URL of the new node
|
|
58
|
+
* @returns MessageProtocolResponse with the new node information
|
|
59
|
+
*/
|
|
60
|
+
changeNode(nodeUrl?: string): Promise<MessageProtocolResponse>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Gossip WASM - WebAssembly Bindings
|
|
2
|
+
|
|
3
|
+
This crate provides WebAssembly bindings for the Gossip secure messaging system, exposing the SessionManager and Auth facilities to JavaScript/TypeScript applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Session Management**: Create, persist, and manage encrypted messaging sessions
|
|
8
|
+
- **Authentication**: Generate and manage cryptographic keys from passphrases
|
|
9
|
+
- **Post-Quantum Security**: Uses ML-KEM and ML-DSA for quantum-resistant cryptography
|
|
10
|
+
- **Encrypted State**: Secure serialization and deserialization of session state
|
|
11
|
+
- **Seeker-based Addressing**: Messages use hashed Massa public keys for efficient lookups
|
|
12
|
+
|
|
13
|
+
## Building
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
|
|
17
|
+
- Rust toolchain with `wasm32-unknown-unknown` target
|
|
18
|
+
- wasm-pack (optional, for generating npm package)
|
|
19
|
+
|
|
20
|
+
### Build with Cargo
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cargo build --target wasm32-unknown-unknown --release
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The compiled WASM module will be at:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
../target/wasm32-unknown-unknown/release/gossip_wasm.wasm
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Build with wasm-pack
|
|
33
|
+
|
|
34
|
+
For a complete npm-ready package with TypeScript definitions:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
wasm-pack build --target web
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### JavaScript/TypeScript
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import init, {
|
|
46
|
+
SessionManagerWrapper,
|
|
47
|
+
SessionConfig,
|
|
48
|
+
generate_user_keys,
|
|
49
|
+
EncryptionKey,
|
|
50
|
+
} from './gossip_wasm';
|
|
51
|
+
|
|
52
|
+
// Initialize WASM
|
|
53
|
+
await init();
|
|
54
|
+
|
|
55
|
+
// Generate user keys from passphrase
|
|
56
|
+
const keys = generate_user_keys('my secure passphrase');
|
|
57
|
+
const publicKeys = keys.public_keys();
|
|
58
|
+
const secretKeys = keys.secret_keys();
|
|
59
|
+
const userId = publicKeys.derive_id();
|
|
60
|
+
|
|
61
|
+
// Create session manager with default configuration
|
|
62
|
+
const config = SessionConfig.new_default();
|
|
63
|
+
const manager = new SessionManagerWrapper(config);
|
|
64
|
+
|
|
65
|
+
// Establish session with peer
|
|
66
|
+
const peerKeys = generate_user_keys('peer passphrase');
|
|
67
|
+
const userData = new TextEncoder().encode('contact_request'); // Optional user data
|
|
68
|
+
const announcement = manager.establish_outgoing_session(
|
|
69
|
+
peerKeys.public_keys(),
|
|
70
|
+
publicKeys,
|
|
71
|
+
secretKeys,
|
|
72
|
+
userData
|
|
73
|
+
);
|
|
74
|
+
// Publish announcement to blockchain...
|
|
75
|
+
|
|
76
|
+
// Feed incoming announcement from peer
|
|
77
|
+
const result = manager.feed_incoming_announcement(
|
|
78
|
+
announcementBytes,
|
|
79
|
+
publicKeys,
|
|
80
|
+
secretKeys
|
|
81
|
+
);
|
|
82
|
+
if (result) {
|
|
83
|
+
console.log('Announcer public keys:', result.announcer_public_keys);
|
|
84
|
+
console.log('Timestamp:', result.timestamp);
|
|
85
|
+
console.log('User data:', new TextDecoder().decode(result.user_data));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Send a message (raw bytes)
|
|
89
|
+
const messageBytes = new TextEncoder().encode('Hello!');
|
|
90
|
+
const peerId = peerKeys.public_keys().derive_id();
|
|
91
|
+
const sendOutput = manager.send_message(peerId, messageBytes);
|
|
92
|
+
if (sendOutput) {
|
|
93
|
+
// Publish sendOutput.seeker and sendOutput.data to blockchain
|
|
94
|
+
console.log('Seeker:', sendOutput.seeker);
|
|
95
|
+
console.log('Data length:', sendOutput.data.length);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check for incoming messages
|
|
99
|
+
const seekers = manager.get_message_board_read_keys();
|
|
100
|
+
for (let i = 0; i < seekers.length; i++) {
|
|
101
|
+
const seeker = seekers.get(i);
|
|
102
|
+
// Read from blockchain using seeker...
|
|
103
|
+
const received = manager.feed_incoming_message_board_read(
|
|
104
|
+
seeker,
|
|
105
|
+
data, // encrypted message data
|
|
106
|
+
secretKeys
|
|
107
|
+
);
|
|
108
|
+
if (received) {
|
|
109
|
+
console.log('Received:', new TextDecoder().decode(received.message));
|
|
110
|
+
console.log('Timestamp:', received.timestamp);
|
|
111
|
+
// Check acknowledged seekers
|
|
112
|
+
const acks = received.acknowledged_seekers;
|
|
113
|
+
for (let j = 0; j < acks.length; j++) {
|
|
114
|
+
console.log('Acknowledged:', acks.get(j));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Persist session state
|
|
120
|
+
const encryptionKey = EncryptionKey.generate();
|
|
121
|
+
const encrypted = manager.to_encrypted_blob(encryptionKey);
|
|
122
|
+
// Save encrypted blob to storage...
|
|
123
|
+
|
|
124
|
+
// Restore session state
|
|
125
|
+
const restored = SessionManagerWrapper.from_encrypted_blob(
|
|
126
|
+
encrypted,
|
|
127
|
+
encryptionKey
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Configuration
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const config = new SessionConfig(
|
|
135
|
+
604800000, // max_incoming_announcement_age_millis (1 week)
|
|
136
|
+
60000, // max_incoming_announcement_future_millis (1 minute)
|
|
137
|
+
604800000, // max_incoming_message_age_millis (1 week)
|
|
138
|
+
60000, // max_incoming_message_future_millis (1 minute)
|
|
139
|
+
604800000, // max_session_inactivity_millis (1 week)
|
|
140
|
+
86400000, // keep_alive_interval_millis (1 day)
|
|
141
|
+
10000 // max_session_lag_length
|
|
142
|
+
);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## API Reference
|
|
146
|
+
|
|
147
|
+
### AEAD Encryption Functions
|
|
148
|
+
|
|
149
|
+
Direct access to AES-256-SIV authenticated encryption:
|
|
150
|
+
|
|
151
|
+
- `aead_encrypt(key: EncryptionKey, nonce: Nonce, plaintext: Uint8Array, aad: Uint8Array)`: Encrypt data
|
|
152
|
+
- `aead_decrypt(key: EncryptionKey, nonce: Nonce, ciphertext: Uint8Array, aad: Uint8Array)`: Decrypt data
|
|
153
|
+
|
|
154
|
+
#### AEAD Example
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import {
|
|
158
|
+
EncryptionKey,
|
|
159
|
+
Nonce,
|
|
160
|
+
aead_encrypt,
|
|
161
|
+
aead_decrypt,
|
|
162
|
+
} from './gossip_wasm';
|
|
163
|
+
|
|
164
|
+
// Generate key and nonce
|
|
165
|
+
const key = EncryptionKey.generate();
|
|
166
|
+
const nonce = Nonce.generate();
|
|
167
|
+
|
|
168
|
+
// Encrypt some data
|
|
169
|
+
const plaintext = new TextEncoder().encode('Secret message');
|
|
170
|
+
const aad = new TextEncoder().encode('context info'); // Additional authenticated data
|
|
171
|
+
const ciphertext = aead_encrypt(key, nonce, plaintext, aad);
|
|
172
|
+
|
|
173
|
+
// Decrypt
|
|
174
|
+
const decrypted = aead_decrypt(key, nonce, ciphertext, aad);
|
|
175
|
+
if (decrypted) {
|
|
176
|
+
console.log('Success:', new TextDecoder().decode(decrypted));
|
|
177
|
+
} else {
|
|
178
|
+
console.error('Decryption failed - tampering detected!');
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Security Notes:**
|
|
183
|
+
|
|
184
|
+
- Nonces should be unique per encryption (16 bytes)
|
|
185
|
+
- AAD (Additional Authenticated Data) is authenticated but NOT encrypted
|
|
186
|
+
- AES-SIV is nonce-misuse resistant - reusing nonces only leaks if plaintexts are identical
|
|
187
|
+
- Keys are 64 bytes (512 bits) for AES-256-SIV
|
|
188
|
+
|
|
189
|
+
### SessionManagerWrapper
|
|
190
|
+
|
|
191
|
+
Main class for managing messaging sessions.
|
|
192
|
+
|
|
193
|
+
- `new(config: SessionConfig)`: Create new session manager
|
|
194
|
+
- `from_encrypted_blob(blob: Uint8Array, key: EncryptionKey)`: Restore from encrypted state
|
|
195
|
+
- `to_encrypted_blob(key: EncryptionKey)`: Serialize to encrypted blob
|
|
196
|
+
- `establish_outgoing_session(peer_pk, our_pk, our_sk, user_data: Uint8Array)`: Initiate session with peer, including optional user data (returns announcement bytes)
|
|
197
|
+
- `feed_incoming_announcement(bytes, our_pk, our_sk)`: Process incoming announcement (returns AnnouncementResult with announcer's public keys and user data, or undefined)
|
|
198
|
+
- `send_message(peer_id: Uint8Array, message_contents: Uint8Array)`: Send raw message bytes to peer
|
|
199
|
+
- `feed_incoming_message_board_read(seeker, data, our_sk)`: Process incoming messages
|
|
200
|
+
- `get_message_board_read_keys()`: Get seekers to monitor for incoming messages
|
|
201
|
+
- `peer_list()`: Get all peer IDs
|
|
202
|
+
- `peer_session_status(peer_id: Uint8Array)`: Get session status
|
|
203
|
+
- `peer_discard(peer_id: Uint8Array)`: Remove peer
|
|
204
|
+
- `refresh()`: Refresh sessions and get keep-alive announcement list
|
|
205
|
+
|
|
206
|
+
### AnnouncementResult
|
|
207
|
+
|
|
208
|
+
Result from processing an incoming announcement:
|
|
209
|
+
|
|
210
|
+
- `announcer_public_keys(): UserPublicKeys`: The public keys of the peer who sent the announcement
|
|
211
|
+
- `timestamp(): number`: Unix timestamp in milliseconds when the announcement was created
|
|
212
|
+
- `user_data(): Uint8Array`: Arbitrary user data embedded in the announcement (can be empty)
|
|
213
|
+
|
|
214
|
+
**Use Cases for User Data:**
|
|
215
|
+
|
|
216
|
+
- Contact requests with metadata
|
|
217
|
+
- Version information
|
|
218
|
+
- Application-specific handshake data
|
|
219
|
+
- Display names or profile information
|
|
220
|
+
- Protocol negotiation parameters
|
|
221
|
+
|
|
222
|
+
**⚠️ Security Warning:**
|
|
223
|
+
|
|
224
|
+
The user_data in announcements has **reduced security compared to regular messages**:
|
|
225
|
+
|
|
226
|
+
- ✅ **Plausible deniability preserved**: The user_data is not cryptographically signed, so the sender can deny specific content
|
|
227
|
+
- ❌ **No post-compromise secrecy**: If long-term keys are compromised, past announcements can be decrypted
|
|
228
|
+
|
|
229
|
+
**Recommendation**: Use user_data for non-highly-sensitive metadata. Send truly sensitive information through regular messages after the session is established.
|
|
230
|
+
|
|
231
|
+
### SendMessageOutput
|
|
232
|
+
|
|
233
|
+
Output from sending a message:
|
|
234
|
+
|
|
235
|
+
- `seeker(): Uint8Array`: Database key for message lookup on message board
|
|
236
|
+
- `data(): Uint8Array`: Encrypted message data to publish
|
|
237
|
+
- `timestamp(): number`: Message timestamp (milliseconds since Unix epoch)
|
|
238
|
+
|
|
239
|
+
### ReceiveMessageOutput
|
|
240
|
+
|
|
241
|
+
Output from receiving a message:
|
|
242
|
+
|
|
243
|
+
- `message(): Uint8Array`: Decrypted message contents
|
|
244
|
+
- `timestamp(): number`: Message timestamp (milliseconds since Unix epoch)
|
|
245
|
+
- `acknowledged_seekers()`: Array of seekers that were acknowledged
|
|
246
|
+
|
|
247
|
+
### Auth Functions
|
|
248
|
+
|
|
249
|
+
- `generate_user_keys(passphrase: string)`: Generate keys from passphrase using password KDF
|
|
250
|
+
|
|
251
|
+
### Other Classes
|
|
252
|
+
|
|
253
|
+
- `SessionConfig`: Session manager configuration
|
|
254
|
+
- `EncryptionKey`: AES-256-SIV key (64 bytes)
|
|
255
|
+
- `generate()`: Generate random key
|
|
256
|
+
- `from_bytes(bytes: Uint8Array)`: Create from bytes
|
|
257
|
+
- `to_bytes()`: Get raw bytes
|
|
258
|
+
- `Nonce`: AES-256-SIV nonce (16 bytes)
|
|
259
|
+
- `generate()`: Generate random nonce
|
|
260
|
+
- `from_bytes(bytes: Uint8Array)`: Create from bytes
|
|
261
|
+
- `to_bytes()`: Get raw bytes
|
|
262
|
+
- `UserPublicKeys`: User's public keys
|
|
263
|
+
- `derive_id()`: Get user ID (32 bytes)
|
|
264
|
+
- `to_bytes()`: Serialize to bytes
|
|
265
|
+
- `UserSecretKeys`: User's secret keys
|
|
266
|
+
- `SessionStatus`: Enum for session states (Active, Inactive, etc.)
|
|
267
|
+
|
|
268
|
+
## Architecture
|
|
269
|
+
|
|
270
|
+
The Gossip system uses a multi-layer architecture:
|
|
271
|
+
|
|
272
|
+
1. **Crypto Primitives**: ML-KEM (post-quantum KEM), ML-DSA (post-quantum signatures), AES-SIV (AEAD)
|
|
273
|
+
2. **Agraphon Protocol**: Double-ratchet encryption with forward secrecy and post-compromise security
|
|
274
|
+
3. **Session Layer**: Manages Agraphon sessions with seeker-based addressing using hashed Massa public keys
|
|
275
|
+
4. **Session Manager**: High-level API for multi-peer messaging with session lifecycle management
|
|
276
|
+
|
|
277
|
+
Messages are identified by "seekers" - database keys derived from hashing ephemeral Massa public keys. This allows:
|
|
278
|
+
|
|
279
|
+
- Efficient message lookup on public message boards
|
|
280
|
+
- Privacy (seekers don't reveal sender/recipient identity)
|
|
281
|
+
- Unlinkability (each message uses a fresh keypair)
|