@highway1/core 0.1.39 → 0.1.41
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/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +18 -4
- package/src/discovery/agent-card-encoder.ts +119 -0
- package/src/discovery/agent-card-schema.ts +87 -0
- package/src/discovery/agent-card-types.ts +99 -0
- package/src/discovery/agent-card.ts +190 -0
- package/src/discovery/bootstrap.ts +63 -0
- package/src/discovery/capability-matcher.ts +167 -0
- package/src/discovery/dht.ts +285 -0
- package/src/discovery/index.ts +3 -0
- package/src/discovery/search-index.ts +247 -0
- package/src/discovery/semantic-search.ts +218 -0
- package/src/identity/did.ts +48 -0
- package/src/identity/document.ts +77 -0
- package/src/identity/index.ts +4 -0
- package/src/identity/keys.ts +79 -0
- package/src/identity/signer.ts +55 -0
- package/src/index.ts +33 -0
- package/src/messaging/codec.ts +47 -0
- package/src/messaging/envelope.ts +107 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/router.ts +368 -0
- package/src/transport/connection.ts +77 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/node.ts +152 -0
- package/src/trust/endorsement.ts +167 -0
- package/src/trust/index.ts +194 -0
- package/src/trust/interaction-history.ts +155 -0
- package/src/trust/sybil-defense.ts +232 -0
- package/src/trust/trust-score.ts +136 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/logger.ts +48 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Search Engine
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent agent discovery through semantic search
|
|
5
|
+
* with local-first strategy and network fallback
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentCard } from './agent-card-types.js';
|
|
9
|
+
import { SearchIndex } from './search-index.js';
|
|
10
|
+
import type { SemanticQuery, SearchResult } from './search-index.js';
|
|
11
|
+
import { CapabilityMatcher } from './capability-matcher.js';
|
|
12
|
+
import type { DHTOperations } from './dht.js';
|
|
13
|
+
import { createLogger } from '../utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const logger = createLogger('semantic-search');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Semantic Search Engine
|
|
19
|
+
*/
|
|
20
|
+
export class SemanticSearchEngine {
|
|
21
|
+
private index: SearchIndex;
|
|
22
|
+
private matcher: CapabilityMatcher;
|
|
23
|
+
|
|
24
|
+
constructor(private dht?: DHTOperations) {
|
|
25
|
+
this.index = new SearchIndex();
|
|
26
|
+
this.matcher = new CapabilityMatcher();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main search interface
|
|
31
|
+
* Local-first with network fallback
|
|
32
|
+
*/
|
|
33
|
+
async search(query: SemanticQuery): Promise<AgentCard[]> {
|
|
34
|
+
logger.info('Searching for agents', { query });
|
|
35
|
+
|
|
36
|
+
// 1. Search local index
|
|
37
|
+
const localResults = this.index.search(query);
|
|
38
|
+
logger.debug('Local search results', { count: localResults.length });
|
|
39
|
+
|
|
40
|
+
// 2. If insufficient results, query network
|
|
41
|
+
const limit = query.limit || 10;
|
|
42
|
+
if (localResults.length < limit && this.dht) {
|
|
43
|
+
logger.debug('Insufficient local results, querying network');
|
|
44
|
+
const networkResults = await this.searchNetwork(query);
|
|
45
|
+
return this.mergeResults(localResults, networkResults, limit);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return localResults.map(r => r.card);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Index an Agent Card for local search
|
|
53
|
+
*/
|
|
54
|
+
indexAgentCard(card: AgentCard): void {
|
|
55
|
+
this.index.indexAgentCard(card);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove an Agent Card from local index
|
|
60
|
+
*/
|
|
61
|
+
removeAgentCard(did: string): void {
|
|
62
|
+
this.index.removeAgentCard(did);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get all indexed cards
|
|
67
|
+
*/
|
|
68
|
+
getAllIndexedCards(): AgentCard[] {
|
|
69
|
+
return this.index.getAllCards();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clear local index
|
|
74
|
+
*/
|
|
75
|
+
clearIndex(): void {
|
|
76
|
+
this.index.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get index size
|
|
81
|
+
*/
|
|
82
|
+
getIndexSize(): number {
|
|
83
|
+
return this.index.size();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Search network via DHT
|
|
88
|
+
*/
|
|
89
|
+
private async searchNetwork(query: SemanticQuery): Promise<SearchResult[]> {
|
|
90
|
+
if (!this.dht) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// For now, use capability-based DHT query
|
|
96
|
+
// In future, implement distributed semantic search overlay
|
|
97
|
+
const capability = query.capability || this.extractPrimaryCapability(query.text);
|
|
98
|
+
|
|
99
|
+
if (!capability) {
|
|
100
|
+
logger.debug('No capability extracted from query, skipping network search');
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const cards = await this.dht.queryByCapability(capability);
|
|
105
|
+
logger.debug('Network search results', { count: cards.length });
|
|
106
|
+
|
|
107
|
+
// Score network results
|
|
108
|
+
return cards.map(card => {
|
|
109
|
+
const score = this.scoreCard(card, query);
|
|
110
|
+
return { card, score };
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error('Network search failed', { error });
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Merge local and network results, removing duplicates
|
|
120
|
+
*/
|
|
121
|
+
private mergeResults(
|
|
122
|
+
local: SearchResult[],
|
|
123
|
+
network: SearchResult[],
|
|
124
|
+
limit: number
|
|
125
|
+
): AgentCard[] {
|
|
126
|
+
const seen = new Set<string>();
|
|
127
|
+
const merged: SearchResult[] = [];
|
|
128
|
+
|
|
129
|
+
// Add local results first (they're already scored and sorted)
|
|
130
|
+
for (const result of local) {
|
|
131
|
+
if (!seen.has(result.card.did)) {
|
|
132
|
+
seen.add(result.card.did);
|
|
133
|
+
merged.push(result);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Add network results
|
|
138
|
+
for (const result of network) {
|
|
139
|
+
if (!seen.has(result.card.did)) {
|
|
140
|
+
seen.add(result.card.did);
|
|
141
|
+
merged.push(result);
|
|
142
|
+
|
|
143
|
+
// Also index for future searches
|
|
144
|
+
this.index.indexAgentCard(result.card);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Sort by score and apply limit
|
|
149
|
+
merged.sort((a, b) => b.score - a.score);
|
|
150
|
+
return merged.slice(0, limit).map(r => r.card);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Score a card against a query
|
|
155
|
+
*/
|
|
156
|
+
private scoreCard(card: AgentCard, query: SemanticQuery): number {
|
|
157
|
+
let totalScore = 0;
|
|
158
|
+
let count = 0;
|
|
159
|
+
|
|
160
|
+
for (const capability of card.capabilities) {
|
|
161
|
+
const score = this.matcher.match(query, capability);
|
|
162
|
+
if (score > 0) {
|
|
163
|
+
totalScore += score;
|
|
164
|
+
count++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Average score across matching capabilities
|
|
169
|
+
const avgScore = count > 0 ? totalScore / count : 0;
|
|
170
|
+
|
|
171
|
+
// Boost by trust score if available
|
|
172
|
+
if (card.trust) {
|
|
173
|
+
const trustBoost = card.trust.interactionScore * 0.2;
|
|
174
|
+
return Math.min(avgScore + trustBoost, 1.0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return avgScore;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Extract primary capability from natural language text
|
|
182
|
+
*/
|
|
183
|
+
private extractPrimaryCapability(text?: string): string | undefined {
|
|
184
|
+
if (!text) return undefined;
|
|
185
|
+
|
|
186
|
+
const keywords = this.matcher.extractKeywords(text);
|
|
187
|
+
|
|
188
|
+
// Common capability keywords
|
|
189
|
+
const capabilityKeywords = [
|
|
190
|
+
'translate', 'translation',
|
|
191
|
+
'review', 'code',
|
|
192
|
+
'analyze', 'analysis',
|
|
193
|
+
'generate', 'generation',
|
|
194
|
+
'search', 'query',
|
|
195
|
+
'compute', 'calculation',
|
|
196
|
+
'store', 'storage',
|
|
197
|
+
'message', 'messaging',
|
|
198
|
+
'auth', 'authentication',
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// Find first matching keyword
|
|
202
|
+
for (const keyword of keywords) {
|
|
203
|
+
if (capabilityKeywords.includes(keyword)) {
|
|
204
|
+
return keyword;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Return first keyword as fallback
|
|
209
|
+
return keywords[0];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create a semantic search engine
|
|
215
|
+
*/
|
|
216
|
+
export function createSemanticSearch(dht?: DHTOperations): SemanticSearchEngine {
|
|
217
|
+
return new SemanticSearchEngine(dht);
|
|
218
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { base58btc } from 'multiformats/bases/base58';
|
|
2
|
+
import { IdentityError } from '../utils/errors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Derive a did:clawiverse DID from a public key
|
|
6
|
+
* Format: did:clawiverse:<base58btc-encoded-pubkey>
|
|
7
|
+
*/
|
|
8
|
+
export function deriveDID(publicKey: Uint8Array): string {
|
|
9
|
+
try {
|
|
10
|
+
const encoded = base58btc.encode(publicKey);
|
|
11
|
+
return `did:clawiverse:${encoded}`;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new IdentityError('Failed to derive DID', error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract public key from a did:clawiverse DID
|
|
19
|
+
*/
|
|
20
|
+
export function extractPublicKey(did: string): Uint8Array {
|
|
21
|
+
if (!did.startsWith('did:clawiverse:')) {
|
|
22
|
+
throw new IdentityError('Invalid DID format: must start with did:clawiverse:');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const encoded = did.replace('did:clawiverse:', '');
|
|
27
|
+
return base58btc.decode(encoded);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new IdentityError('Failed to extract public key from DID', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate a did:clawiverse DID format
|
|
35
|
+
*/
|
|
36
|
+
export function validateDID(did: string): boolean {
|
|
37
|
+
if (!did.startsWith('did:clawiverse:')) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const encoded = did.replace('did:clawiverse:', '');
|
|
43
|
+
base58btc.decode(encoded);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { deriveDID } from './did.js';
|
|
2
|
+
|
|
3
|
+
export interface DIDDocument {
|
|
4
|
+
'@context': string[];
|
|
5
|
+
id: string;
|
|
6
|
+
verificationMethod: VerificationMethod[];
|
|
7
|
+
authentication: string[];
|
|
8
|
+
assertionMethod: string[];
|
|
9
|
+
keyAgreement?: string[];
|
|
10
|
+
service?: ServiceEndpoint[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface VerificationMethod {
|
|
14
|
+
id: string;
|
|
15
|
+
type: string;
|
|
16
|
+
controller: string;
|
|
17
|
+
publicKeyMultibase: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ServiceEndpoint {
|
|
21
|
+
id: string;
|
|
22
|
+
type: string;
|
|
23
|
+
serviceEndpoint: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a DID Document for a did:clawiverse identity
|
|
28
|
+
*/
|
|
29
|
+
export function createDIDDocument(
|
|
30
|
+
publicKey: Uint8Array,
|
|
31
|
+
services?: ServiceEndpoint[]
|
|
32
|
+
): DIDDocument {
|
|
33
|
+
const did = deriveDID(publicKey);
|
|
34
|
+
const keyId = `${did}#key-1`;
|
|
35
|
+
// Use base58btc encoding for multibase format
|
|
36
|
+
const publicKeyMultibase = `z${Buffer.from(publicKey).toString('hex')}`;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
'@context': [
|
|
40
|
+
'https://www.w3.org/ns/did/v1',
|
|
41
|
+
'https://w3id.org/security/suites/ed25519-2020/v1',
|
|
42
|
+
],
|
|
43
|
+
id: did,
|
|
44
|
+
verificationMethod: [
|
|
45
|
+
{
|
|
46
|
+
id: keyId,
|
|
47
|
+
type: 'Ed25519VerificationKey2020',
|
|
48
|
+
controller: did,
|
|
49
|
+
publicKeyMultibase,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
authentication: [keyId],
|
|
53
|
+
assertionMethod: [keyId],
|
|
54
|
+
service: services,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate a DID Document structure
|
|
60
|
+
*/
|
|
61
|
+
export function validateDIDDocument(doc: unknown): doc is DIDDocument {
|
|
62
|
+
if (typeof doc !== 'object' || doc === null) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const d = doc as Partial<DIDDocument>;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
Array.isArray(d['@context']) &&
|
|
70
|
+
typeof d.id === 'string' &&
|
|
71
|
+
d.id.startsWith('did:clawiverse:') &&
|
|
72
|
+
Array.isArray(d.verificationMethod) &&
|
|
73
|
+
d.verificationMethod.length > 0 &&
|
|
74
|
+
Array.isArray(d.authentication) &&
|
|
75
|
+
Array.isArray(d.assertionMethod)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as ed25519 from '@noble/ed25519';
|
|
2
|
+
import { IdentityError } from '../utils/errors.js';
|
|
3
|
+
|
|
4
|
+
export interface KeyPair {
|
|
5
|
+
publicKey: Uint8Array;
|
|
6
|
+
privateKey: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a new Ed25519 key pair
|
|
11
|
+
*/
|
|
12
|
+
export async function generateKeyPair(): Promise<KeyPair> {
|
|
13
|
+
try {
|
|
14
|
+
const privateKey = ed25519.utils.randomPrivateKey();
|
|
15
|
+
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
publicKey,
|
|
19
|
+
privateKey,
|
|
20
|
+
};
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new IdentityError('Failed to generate key pair', error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sign a message with a private key
|
|
28
|
+
*/
|
|
29
|
+
export async function sign(
|
|
30
|
+
message: Uint8Array,
|
|
31
|
+
privateKey: Uint8Array
|
|
32
|
+
): Promise<Uint8Array> {
|
|
33
|
+
try {
|
|
34
|
+
return await ed25519.signAsync(message, privateKey);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new IdentityError('Failed to sign message', error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Verify a signature
|
|
42
|
+
*/
|
|
43
|
+
export async function verify(
|
|
44
|
+
signature: Uint8Array,
|
|
45
|
+
message: Uint8Array,
|
|
46
|
+
publicKey: Uint8Array
|
|
47
|
+
): Promise<boolean> {
|
|
48
|
+
try {
|
|
49
|
+
return await ed25519.verifyAsync(signature, message, publicKey);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new IdentityError('Failed to verify signature', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Export key pair to JSON format
|
|
57
|
+
*/
|
|
58
|
+
export function exportKeyPair(keyPair: KeyPair): {
|
|
59
|
+
publicKey: string;
|
|
60
|
+
privateKey: string;
|
|
61
|
+
} {
|
|
62
|
+
return {
|
|
63
|
+
publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
|
|
64
|
+
privateKey: Buffer.from(keyPair.privateKey).toString('hex'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Import key pair from JSON format
|
|
70
|
+
*/
|
|
71
|
+
export function importKeyPair(exported: {
|
|
72
|
+
publicKey: string;
|
|
73
|
+
privateKey: string;
|
|
74
|
+
}): KeyPair {
|
|
75
|
+
return {
|
|
76
|
+
publicKey: new Uint8Array(Buffer.from(exported.publicKey, 'hex')),
|
|
77
|
+
privateKey: new Uint8Array(Buffer.from(exported.privateKey, 'hex')),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { sign, verify } from './keys.js';
|
|
2
|
+
import { deriveDID } from './did.js';
|
|
3
|
+
import { IdentityError } from '../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
export interface SignedMessage {
|
|
6
|
+
payload: Uint8Array;
|
|
7
|
+
signature: Uint8Array;
|
|
8
|
+
signer: string; // DID
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sign a message and return a signed message object
|
|
13
|
+
*/
|
|
14
|
+
export async function signMessage(
|
|
15
|
+
payload: Uint8Array,
|
|
16
|
+
privateKey: Uint8Array,
|
|
17
|
+
publicKey: Uint8Array
|
|
18
|
+
): Promise<SignedMessage> {
|
|
19
|
+
try {
|
|
20
|
+
const signature = await sign(payload, privateKey);
|
|
21
|
+
const signer = deriveDID(publicKey);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
payload,
|
|
25
|
+
signature,
|
|
26
|
+
signer,
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new IdentityError('Failed to sign message', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Verify a signed message
|
|
35
|
+
*/
|
|
36
|
+
export async function verifyMessage(
|
|
37
|
+
signedMessage: SignedMessage,
|
|
38
|
+
expectedPublicKey: Uint8Array
|
|
39
|
+
): Promise<boolean> {
|
|
40
|
+
try {
|
|
41
|
+
const expectedDID = deriveDID(expectedPublicKey);
|
|
42
|
+
|
|
43
|
+
if (signedMessage.signer !== expectedDID) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return await verify(
|
|
48
|
+
signedMessage.signature,
|
|
49
|
+
signedMessage.payload,
|
|
50
|
+
expectedPublicKey
|
|
51
|
+
);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new IdentityError('Failed to verify message', error);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clawiverse Core - Main Export
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Identity
|
|
6
|
+
export * from './identity/keys.js';
|
|
7
|
+
export * from './identity/did.js';
|
|
8
|
+
export * from './identity/signer.js';
|
|
9
|
+
|
|
10
|
+
// Transport
|
|
11
|
+
export * from './transport/node.js';
|
|
12
|
+
|
|
13
|
+
// Discovery
|
|
14
|
+
export * from './discovery/agent-card.js';
|
|
15
|
+
export * from './discovery/agent-card-types.js';
|
|
16
|
+
export * from './discovery/agent-card-schema.js';
|
|
17
|
+
export * from './discovery/agent-card-encoder.js';
|
|
18
|
+
export * from './discovery/dht.js';
|
|
19
|
+
export * from './discovery/search-index.js';
|
|
20
|
+
export * from './discovery/capability-matcher.js';
|
|
21
|
+
export * from './discovery/semantic-search.js';
|
|
22
|
+
|
|
23
|
+
// Messaging
|
|
24
|
+
export * from './messaging/envelope.js';
|
|
25
|
+
export * from './messaging/codec.js';
|
|
26
|
+
export * from './messaging/router.js';
|
|
27
|
+
|
|
28
|
+
// Trust (Phase 2)
|
|
29
|
+
export * from './trust/index.js';
|
|
30
|
+
|
|
31
|
+
// Utils
|
|
32
|
+
export * from './utils/logger.js';
|
|
33
|
+
export * from './utils/errors.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { encode, decode } from 'cbor-x';
|
|
2
|
+
import type { MessageEnvelope } from './envelope.js';
|
|
3
|
+
import { MessagingError } from '../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Encode a message envelope to CBOR
|
|
7
|
+
*/
|
|
8
|
+
export function encodeMessage(envelope: MessageEnvelope): Uint8Array {
|
|
9
|
+
try {
|
|
10
|
+
return encode(envelope);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new MessagingError('Failed to encode message', error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Decode a CBOR message to envelope
|
|
18
|
+
*/
|
|
19
|
+
export function decodeMessage(data: Uint8Array): MessageEnvelope {
|
|
20
|
+
try {
|
|
21
|
+
return decode(data) as MessageEnvelope;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new MessagingError('Failed to decode message', error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Encode message to JSON (for debugging/logging)
|
|
29
|
+
*/
|
|
30
|
+
export function encodeMessageJSON(envelope: MessageEnvelope): string {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.stringify(envelope, null, 2);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new MessagingError('Failed to encode message to JSON', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Decode JSON message to envelope
|
|
40
|
+
*/
|
|
41
|
+
export function decodeMessageJSON(json: string): MessageEnvelope {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(json) as MessageEnvelope;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new MessagingError('Failed to decode message from JSON', error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { MessagingError } from '../utils/errors.js';
|
|
2
|
+
|
|
3
|
+
export interface MessageEnvelope {
|
|
4
|
+
id: string;
|
|
5
|
+
from: string; // DID
|
|
6
|
+
to: string; // DID
|
|
7
|
+
type: 'request' | 'response' | 'notification';
|
|
8
|
+
protocol: string;
|
|
9
|
+
payload: unknown;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
signature: string;
|
|
12
|
+
replyTo?: string; // For responses, the ID of the request
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a message envelope
|
|
17
|
+
*/
|
|
18
|
+
export function createEnvelope(
|
|
19
|
+
from: string,
|
|
20
|
+
to: string,
|
|
21
|
+
type: 'request' | 'response' | 'notification',
|
|
22
|
+
protocol: string,
|
|
23
|
+
payload: unknown,
|
|
24
|
+
replyTo?: string
|
|
25
|
+
): Omit<MessageEnvelope, 'signature'> {
|
|
26
|
+
return {
|
|
27
|
+
id: generateMessageId(),
|
|
28
|
+
from,
|
|
29
|
+
to,
|
|
30
|
+
type,
|
|
31
|
+
protocol,
|
|
32
|
+
payload,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
replyTo,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Sign a message envelope
|
|
40
|
+
*/
|
|
41
|
+
export async function signEnvelope(
|
|
42
|
+
envelope: Omit<MessageEnvelope, 'signature'>,
|
|
43
|
+
signFn: (data: Uint8Array) => Promise<Uint8Array>
|
|
44
|
+
): Promise<MessageEnvelope> {
|
|
45
|
+
try {
|
|
46
|
+
const envelopeJson = JSON.stringify(envelope);
|
|
47
|
+
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
48
|
+
const signature = await signFn(envelopeBytes);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...envelope,
|
|
52
|
+
signature: Buffer.from(signature).toString('hex'),
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new MessagingError('Failed to sign envelope', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Verify a message envelope signature
|
|
61
|
+
*/
|
|
62
|
+
export async function verifyEnvelope(
|
|
63
|
+
envelope: MessageEnvelope,
|
|
64
|
+
verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
|
|
65
|
+
): Promise<boolean> {
|
|
66
|
+
try {
|
|
67
|
+
const { signature, ...envelopeWithoutSig } = envelope;
|
|
68
|
+
const envelopeJson = JSON.stringify(envelopeWithoutSig);
|
|
69
|
+
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
70
|
+
const signatureBytes = Buffer.from(signature, 'hex');
|
|
71
|
+
|
|
72
|
+
return await verifyFn(signatureBytes, envelopeBytes);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new MessagingError('Failed to verify envelope', error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validate message envelope structure
|
|
80
|
+
*/
|
|
81
|
+
export function validateEnvelope(msg: unknown): msg is MessageEnvelope {
|
|
82
|
+
if (typeof msg !== 'object' || msg === null) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const m = msg as Partial<MessageEnvelope>;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
typeof m.id === 'string' &&
|
|
90
|
+
typeof m.from === 'string' &&
|
|
91
|
+
m.from.startsWith('did:clawiverse:') &&
|
|
92
|
+
typeof m.to === 'string' &&
|
|
93
|
+
m.to.startsWith('did:clawiverse:') &&
|
|
94
|
+
(m.type === 'request' || m.type === 'response' || m.type === 'notification') &&
|
|
95
|
+
typeof m.protocol === 'string' &&
|
|
96
|
+
m.payload !== undefined &&
|
|
97
|
+
typeof m.timestamp === 'number' &&
|
|
98
|
+
typeof m.signature === 'string'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate a unique message ID
|
|
104
|
+
*/
|
|
105
|
+
function generateMessageId(): string {
|
|
106
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
107
|
+
}
|