@dzhechkov/skills-presentation-storyteller 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/bin/cli.js +5 -0
  4. package/package.json +47 -0
  5. package/sources.json +19 -0
  6. package/src/cli.js +107 -0
  7. package/src/commands/doctor.js +340 -0
  8. package/src/commands/init.js +168 -0
  9. package/src/commands/list.js +146 -0
  10. package/src/commands/remove.js +182 -0
  11. package/src/commands/update.js +170 -0
  12. package/src/utils.js +149 -0
  13. package/templates/.claude/commands/presentation-storyteller.md +23 -0
  14. package/templates/.claude/skills/explore/SKILL.md +218 -0
  15. package/templates/.claude/skills/explore/references/questioning-techniques.md +151 -0
  16. package/templates/.claude/skills/explore/references/task-brief-templates.md +355 -0
  17. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +418 -0
  18. package/templates/.claude/skills/goap-research-ed25519/references/ed25519-verification.md +658 -0
  19. package/templates/.claude/skills/goap-research-ed25519/references/research-actions.md +544 -0
  20. package/templates/.claude/skills/goap-research-ed25519/references/source-evaluation.md +560 -0
  21. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +662 -0
  22. package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +720 -0
  23. package/templates/.claude/skills/presentation-storyteller/SKILL.md +374 -0
  24. package/templates/.claude/skills/presentation-storyteller/references/example-presentation.md +273 -0
  25. package/templates/.claude/skills/presentation-storyteller/references/slide-types.md +426 -0
  26. package/templates/.claude/skills/presentation-storyteller/references/sources-index-template.md +213 -0
  27. package/templates/.claude/skills/presentation-storyteller/references/speaker-script-patterns.md +324 -0
  28. package/templates/.claude/skills/presentation-storyteller/references/storytelling-frameworks.md +270 -0
@@ -0,0 +1,658 @@
1
+ # Ed25519 Verification Reference
2
+
3
+ Comprehensive documentation for Ed25519 cryptographic verification in GOAP research.
4
+
5
+ ## Overview
6
+
7
+ Ed25519 is an elliptic curve digital signature algorithm using Curve25519. It provides:
8
+ - **128-bit security level** - resistant to known attacks
9
+ - **Fast verification** - ~71,000 verifications/second on modern hardware
10
+ - **Small signatures** - 64 bytes
11
+ - **Deterministic** - same message + key always produces same signature
12
+
13
+ ## Why Ed25519 for Anti-Hallucination?
14
+
15
+ | Problem | Ed25519 Solution |
16
+ |---------|------------------|
17
+ | Source authenticity | Cryptographic proof of origin |
18
+ | Content tampering | Hash-based integrity verification |
19
+ | Citation chain breaks | Linked signature chains |
20
+ | Audit requirements | Signed verification ledger |
21
+ | Trust establishment | Trusted issuer whitelist |
22
+
23
+ ## Key Concepts
24
+
25
+ ### Keypair Structure
26
+
27
+ ```
28
+ Private Key (Seed): 32 bytes - KEEP SECRET
29
+ └── Used for signing
30
+
31
+ Public Key: 32 bytes - SHAREABLE
32
+ └── Used for verification
33
+ └── Can be published or fetched from keyserver
34
+
35
+ Signature: 64 bytes
36
+ └── Proves private key holder signed specific content
37
+ ```
38
+
39
+ ### Signature Workflow
40
+
41
+ ```
42
+ SIGNING (by source/researcher):
43
+ content → SHA-512(content) → Ed25519.sign(hash, private_key) → signature
44
+
45
+ VERIFICATION (by consumer):
46
+ (content, signature, public_key) → Ed25519.verify() → true/false
47
+ ```
48
+
49
+ ## Implementation
50
+
51
+ ### Python Implementation
52
+
53
+ ```python
54
+ """
55
+ Ed25519 Verification Module for GOAP Research
56
+
57
+ Requirements:
58
+ pip install cryptography pynacl --break-system-packages
59
+ """
60
+
61
+ import hashlib
62
+ import json
63
+ import base64
64
+ from datetime import datetime
65
+ from typing import Optional, Dict, List, Tuple
66
+ from dataclasses import dataclass, field, asdict
67
+
68
+ # Use cryptography library (preferred) or pynacl
69
+ try:
70
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
71
+ Ed25519PrivateKey, Ed25519PublicKey
72
+ )
73
+ from cryptography.hazmat.primitives import serialization
74
+ CRYPTO_BACKEND = "cryptography"
75
+ except ImportError:
76
+ import nacl.signing
77
+ import nacl.encoding
78
+ CRYPTO_BACKEND = "pynacl"
79
+
80
+
81
+ @dataclass
82
+ class VerificationResult:
83
+ """Result of a verification operation."""
84
+ verified: bool
85
+ content_hash: str
86
+ signature: str
87
+ issuer: str
88
+ issuer_pubkey: str
89
+ timestamp: str
90
+ confidence: float
91
+ error: Optional[str] = None
92
+
93
+
94
+ @dataclass
95
+ class SignedFact:
96
+ """A fact with cryptographic signature."""
97
+ claim: str
98
+ source_url: str
99
+ source_hash: str
100
+ issuer: str
101
+ issuer_pubkey: str
102
+ signature: str
103
+ timestamp: str
104
+ parent_citation: Optional[str] = None
105
+ confidence: float = 0.0
106
+
107
+ def to_dict(self) -> Dict:
108
+ return asdict(self)
109
+
110
+ def to_json(self) -> str:
111
+ return json.dumps(self.to_dict(), indent=2)
112
+
113
+
114
+ @dataclass
115
+ class CitationChain:
116
+ """Chain of signed citations."""
117
+ chain_id: str
118
+ facts: List[SignedFact] = field(default_factory=list)
119
+ chain_signature: Optional[str] = None
120
+ integrity_verified: bool = False
121
+
122
+ def add_fact(self, fact: SignedFact) -> None:
123
+ if self.facts:
124
+ fact.parent_citation = f"chain:{self.chain_id}:fact:{len(self.facts)-1}"
125
+ self.facts.append(fact)
126
+
127
+ def get_chain_hash(self) -> str:
128
+ """Calculate hash of entire chain for signing."""
129
+ chain_data = json.dumps([f.to_dict() for f in self.facts], sort_keys=True)
130
+ return hashlib.sha256(chain_data.encode()).hexdigest()
131
+
132
+
133
+ class Ed25519Verifier:
134
+ """
135
+ Ed25519 verification system for GOAP research.
136
+
137
+ Provides:
138
+ - Keypair generation
139
+ - Content signing
140
+ - Signature verification
141
+ - Citation chain management
142
+ - Trusted issuer whitelist
143
+ """
144
+
145
+ DEFAULT_TRUSTED_ISSUERS = {
146
+ # News agencies
147
+ "reuters.com": "ed25519:reuters_pubkey_placeholder",
148
+ "ap.org": "ed25519:ap_pubkey_placeholder",
149
+ "bbc.com": "ed25519:bbc_pubkey_placeholder",
150
+ # Academic
151
+ "arxiv.org": "ed25519:arxiv_pubkey_placeholder",
152
+ "nature.com": "ed25519:nature_pubkey_placeholder",
153
+ "science.org": "ed25519:science_pubkey_placeholder",
154
+ "pubmed.gov": "ed25519:pubmed_pubkey_placeholder",
155
+ # Government
156
+ "sec.gov": "ed25519:sec_pubkey_placeholder",
157
+ "federalreserve.gov": "ed25519:fed_pubkey_placeholder",
158
+ }
159
+
160
+ def __init__(
161
+ self,
162
+ trusted_issuers: Optional[Dict[str, str]] = None,
163
+ verification_threshold: float = 0.85
164
+ ):
165
+ self.trusted_issuers = trusted_issuers or self.DEFAULT_TRUSTED_ISSUERS
166
+ self.verification_threshold = verification_threshold
167
+ self.verification_ledger: List[VerificationResult] = []
168
+ self._private_key = None
169
+ self._public_key = None
170
+
171
+ def generate_keypair(self) -> Tuple[bytes, bytes]:
172
+ """Generate new Ed25519 keypair."""
173
+ if CRYPTO_BACKEND == "cryptography":
174
+ private_key = Ed25519PrivateKey.generate()
175
+ public_key = private_key.public_key()
176
+
177
+ private_bytes = private_key.private_bytes(
178
+ encoding=serialization.Encoding.Raw,
179
+ format=serialization.PrivateFormat.Raw,
180
+ encryption_algorithm=serialization.NoEncryption()
181
+ )
182
+ public_bytes = public_key.public_bytes(
183
+ encoding=serialization.Encoding.Raw,
184
+ format=serialization.PublicFormat.Raw
185
+ )
186
+ else:
187
+ signing_key = nacl.signing.SigningKey.generate()
188
+ private_bytes = bytes(signing_key)
189
+ public_bytes = bytes(signing_key.verify_key)
190
+
191
+ self._private_key = private_bytes
192
+ self._public_key = public_bytes
193
+
194
+ return private_bytes, public_bytes
195
+
196
+ def load_keypair(self, private_key: bytes, public_key: bytes) -> None:
197
+ """Load existing keypair."""
198
+ self._private_key = private_key
199
+ self._public_key = public_key
200
+
201
+ def sign_content(self, content: str) -> Tuple[str, str]:
202
+ """
203
+ Sign content with private key.
204
+
205
+ Returns:
206
+ Tuple of (signature_base64, content_hash)
207
+ """
208
+ if self._private_key is None:
209
+ raise ValueError("No private key loaded. Call generate_keypair() first.")
210
+
211
+ content_bytes = content.encode('utf-8')
212
+ content_hash = hashlib.sha256(content_bytes).hexdigest()
213
+
214
+ if CRYPTO_BACKEND == "cryptography":
215
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
216
+ private_key = Ed25519PrivateKey.from_private_bytes(self._private_key)
217
+ signature = private_key.sign(content_bytes)
218
+ else:
219
+ signing_key = nacl.signing.SigningKey(self._private_key)
220
+ signed = signing_key.sign(content_bytes)
221
+ signature = signed.signature
222
+
223
+ signature_b64 = base64.b64encode(signature).decode('ascii')
224
+ return signature_b64, content_hash
225
+
226
+ def verify_signature(
227
+ self,
228
+ content: str,
229
+ signature_b64: str,
230
+ public_key: bytes
231
+ ) -> bool:
232
+ """Verify Ed25519 signature."""
233
+ try:
234
+ content_bytes = content.encode('utf-8')
235
+ signature = base64.b64decode(signature_b64)
236
+
237
+ if CRYPTO_BACKEND == "cryptography":
238
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
239
+ pub_key = Ed25519PublicKey.from_public_bytes(public_key)
240
+ pub_key.verify(signature, content_bytes)
241
+ return True
242
+ else:
243
+ verify_key = nacl.signing.VerifyKey(public_key)
244
+ verify_key.verify(content_bytes, signature)
245
+ return True
246
+ except Exception:
247
+ return False
248
+
249
+ def is_trusted_issuer(self, domain: str) -> bool:
250
+ """Check if domain is in trusted issuers whitelist."""
251
+ # Direct match
252
+ if domain in self.trusted_issuers:
253
+ return True
254
+
255
+ # Suffix match for government domains
256
+ for trusted in self.trusted_issuers:
257
+ if trusted.startswith('.') and domain.endswith(trusted):
258
+ return True
259
+
260
+ return False
261
+
262
+ def create_signed_fact(
263
+ self,
264
+ claim: str,
265
+ source_url: str,
266
+ source_content: str,
267
+ issuer: str
268
+ ) -> SignedFact:
269
+ """Create a signed fact with full verification metadata."""
270
+ if self._private_key is None:
271
+ raise ValueError("No private key loaded.")
272
+
273
+ source_hash = hashlib.sha256(source_content.encode()).hexdigest()
274
+ timestamp = datetime.utcnow().isoformat() + "Z"
275
+
276
+ # Sign the claim + source_hash
277
+ signing_content = f"{claim}|{source_hash}|{timestamp}"
278
+ signature, _ = self.sign_content(signing_content)
279
+
280
+ public_key_b64 = base64.b64encode(self._public_key).decode('ascii')
281
+
282
+ # Calculate confidence
283
+ base_confidence = 0.8 # Base for signed content
284
+ if self.is_trusted_issuer(issuer):
285
+ base_confidence = 0.95
286
+
287
+ return SignedFact(
288
+ claim=claim,
289
+ source_url=source_url,
290
+ source_hash=source_hash,
291
+ issuer=issuer,
292
+ issuer_pubkey=f"ed25519:{public_key_b64}",
293
+ signature=signature,
294
+ timestamp=timestamp,
295
+ confidence=base_confidence
296
+ )
297
+
298
+ def verify_fact(self, fact: SignedFact) -> VerificationResult:
299
+ """Verify a signed fact."""
300
+ # Extract public key from fact
301
+ if not fact.issuer_pubkey.startswith("ed25519:"):
302
+ return VerificationResult(
303
+ verified=False,
304
+ content_hash=fact.source_hash,
305
+ signature=fact.signature,
306
+ issuer=fact.issuer,
307
+ issuer_pubkey=fact.issuer_pubkey,
308
+ timestamp=fact.timestamp,
309
+ confidence=0.0,
310
+ error="Invalid public key format"
311
+ )
312
+
313
+ try:
314
+ pubkey_b64 = fact.issuer_pubkey[8:] # Remove "ed25519:" prefix
315
+ public_key = base64.b64decode(pubkey_b64)
316
+
317
+ # Reconstruct signing content
318
+ signing_content = f"{fact.claim}|{fact.source_hash}|{fact.timestamp}"
319
+
320
+ verified = self.verify_signature(signing_content, fact.signature, public_key)
321
+
322
+ # Calculate confidence
323
+ confidence = 0.0
324
+ if verified:
325
+ confidence = 0.8
326
+ if self.is_trusted_issuer(fact.issuer):
327
+ confidence = 0.95
328
+
329
+ result = VerificationResult(
330
+ verified=verified,
331
+ content_hash=fact.source_hash,
332
+ signature=fact.signature,
333
+ issuer=fact.issuer,
334
+ issuer_pubkey=fact.issuer_pubkey,
335
+ timestamp=fact.timestamp,
336
+ confidence=confidence,
337
+ error=None if verified else "Signature verification failed"
338
+ )
339
+
340
+ self.verification_ledger.append(result)
341
+ return result
342
+
343
+ except Exception as e:
344
+ return VerificationResult(
345
+ verified=False,
346
+ content_hash=fact.source_hash,
347
+ signature=fact.signature,
348
+ issuer=fact.issuer,
349
+ issuer_pubkey=fact.issuer_pubkey,
350
+ timestamp=fact.timestamp,
351
+ confidence=0.0,
352
+ error=str(e)
353
+ )
354
+
355
+ def verify_citation_chain(self, chain: CitationChain) -> Tuple[bool, float]:
356
+ """
357
+ Verify entire citation chain integrity.
358
+
359
+ Returns:
360
+ Tuple of (all_verified, aggregate_confidence)
361
+ """
362
+ if not chain.facts:
363
+ return False, 0.0
364
+
365
+ all_verified = True
366
+ total_confidence = 0.0
367
+
368
+ for i, fact in enumerate(chain.facts):
369
+ result = self.verify_fact(fact)
370
+
371
+ if not result.verified:
372
+ all_verified = False
373
+
374
+ total_confidence += result.confidence
375
+
376
+ # Verify chain linkage
377
+ if i > 0:
378
+ expected_parent = f"chain:{chain.chain_id}:fact:{i-1}"
379
+ if fact.parent_citation != expected_parent:
380
+ all_verified = False
381
+
382
+ aggregate_confidence = total_confidence / len(chain.facts)
383
+ chain.integrity_verified = all_verified
384
+
385
+ return all_verified, aggregate_confidence
386
+
387
+ def get_verification_ledger(self) -> List[Dict]:
388
+ """Get verification ledger as list of dicts."""
389
+ return [asdict(r) for r in self.verification_ledger]
390
+
391
+ def sign_ledger(self) -> str:
392
+ """Sign the entire verification ledger."""
393
+ ledger_json = json.dumps(self.get_verification_ledger(), sort_keys=True)
394
+ signature, _ = self.sign_content(ledger_json)
395
+ return signature
396
+
397
+
398
+ # Convenience functions for standalone usage
399
+ def generate_keypair() -> Tuple[str, str]:
400
+ """Generate keypair and return as base64 strings."""
401
+ verifier = Ed25519Verifier()
402
+ private_bytes, public_bytes = verifier.generate_keypair()
403
+ return (
404
+ base64.b64encode(private_bytes).decode('ascii'),
405
+ base64.b64encode(public_bytes).decode('ascii')
406
+ )
407
+
408
+
409
+ def sign_content(content: str, private_key_b64: str) -> str:
410
+ """Sign content with base64-encoded private key."""
411
+ verifier = Ed25519Verifier()
412
+ private_bytes = base64.b64decode(private_key_b64)
413
+ # Derive public key from private (first 32 bytes are seed)
414
+ if CRYPTO_BACKEND == "cryptography":
415
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
416
+ pk = Ed25519PrivateKey.from_private_bytes(private_bytes)
417
+ pub_bytes = pk.public_key().public_bytes(
418
+ encoding=serialization.Encoding.Raw,
419
+ format=serialization.PublicFormat.Raw
420
+ )
421
+ else:
422
+ signing_key = nacl.signing.SigningKey(private_bytes)
423
+ pub_bytes = bytes(signing_key.verify_key)
424
+
425
+ verifier.load_keypair(private_bytes, pub_bytes)
426
+ signature, _ = verifier.sign_content(content)
427
+ return signature
428
+
429
+
430
+ def verify_content(content: str, signature_b64: str, public_key_b64: str) -> bool:
431
+ """Verify content signature with base64-encoded public key."""
432
+ verifier = Ed25519Verifier()
433
+ public_bytes = base64.b64decode(public_key_b64)
434
+ return verifier.verify_signature(content, signature_b64, public_bytes)
435
+ ```
436
+
437
+ ### Node.js Implementation
438
+
439
+ ```javascript
440
+ /**
441
+ * Ed25519 Verification Module for GOAP Research
442
+ *
443
+ * Requirements:
444
+ * npm install @noble/ed25519
445
+ */
446
+
447
+ import * as ed from '@noble/ed25519';
448
+ import { sha512 } from '@noble/hashes/sha512';
449
+ import { createHash } from 'crypto';
450
+
451
+ // Enable sync methods
452
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
453
+
454
+ /**
455
+ * Generate Ed25519 keypair
456
+ * @returns {Promise<{privateKey: Uint8Array, publicKey: Uint8Array}>}
457
+ */
458
+ async function generateKeypair() {
459
+ const privateKey = ed.utils.randomPrivateKey();
460
+ const publicKey = await ed.getPublicKeyAsync(privateKey);
461
+ return { privateKey, publicKey };
462
+ }
463
+
464
+ /**
465
+ * Sign content
466
+ * @param {string} content - Content to sign
467
+ * @param {Uint8Array} privateKey - Private key
468
+ * @returns {Promise<{signature: string, contentHash: string}>}
469
+ */
470
+ async function signContent(content, privateKey) {
471
+ const contentBytes = new TextEncoder().encode(content);
472
+ const contentHash = createHash('sha256').update(contentBytes).digest('hex');
473
+ const signature = await ed.signAsync(contentBytes, privateKey);
474
+ return {
475
+ signature: Buffer.from(signature).toString('base64'),
476
+ contentHash
477
+ };
478
+ }
479
+
480
+ /**
481
+ * Verify signature
482
+ * @param {string} content - Original content
483
+ * @param {string} signatureB64 - Base64 signature
484
+ * @param {Uint8Array} publicKey - Public key
485
+ * @returns {Promise<boolean>}
486
+ */
487
+ async function verifySignature(content, signatureB64, publicKey) {
488
+ try {
489
+ const contentBytes = new TextEncoder().encode(content);
490
+ const signature = Buffer.from(signatureB64, 'base64');
491
+ return await ed.verifyAsync(signature, contentBytes, publicKey);
492
+ } catch {
493
+ return false;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Create signed fact
499
+ */
500
+ async function createSignedFact(claim, sourceUrl, sourceContent, issuer, privateKey, publicKey) {
501
+ const sourceHash = createHash('sha256')
502
+ .update(sourceContent)
503
+ .digest('hex');
504
+
505
+ const timestamp = new Date().toISOString();
506
+ const signingContent = `${claim}|${sourceHash}|${timestamp}`;
507
+ const { signature } = await signContent(signingContent, privateKey);
508
+
509
+ return {
510
+ claim,
511
+ source_url: sourceUrl,
512
+ source_hash: sourceHash,
513
+ issuer,
514
+ issuer_pubkey: `ed25519:${Buffer.from(publicKey).toString('base64')}`,
515
+ signature,
516
+ timestamp,
517
+ parent_citation: null,
518
+ confidence: 0.8
519
+ };
520
+ }
521
+
522
+ /**
523
+ * Verify signed fact
524
+ */
525
+ async function verifyFact(fact) {
526
+ const pubkeyB64 = fact.issuer_pubkey.replace('ed25519:', '');
527
+ const publicKey = Buffer.from(pubkeyB64, 'base64');
528
+
529
+ const signingContent = `${fact.claim}|${fact.source_hash}|${fact.timestamp}`;
530
+ const verified = await verifySignature(signingContent, fact.signature, publicKey);
531
+
532
+ return {
533
+ verified,
534
+ content_hash: fact.source_hash,
535
+ signature: fact.signature,
536
+ issuer: fact.issuer,
537
+ timestamp: fact.timestamp,
538
+ confidence: verified ? 0.8 : 0.0,
539
+ error: verified ? null : 'Signature verification failed'
540
+ };
541
+ }
542
+
543
+ export {
544
+ generateKeypair,
545
+ signContent,
546
+ verifySignature,
547
+ createSignedFact,
548
+ verifyFact
549
+ };
550
+ ```
551
+
552
+ ## Trusted Issuer Management
553
+
554
+ ### Adding Custom Trusted Issuers
555
+
556
+ ```python
557
+ verifier = Ed25519Verifier()
558
+
559
+ # Add organization's public key
560
+ verifier.trusted_issuers["mycompany.com"] = "ed25519:base64_encoded_pubkey"
561
+
562
+ # Add academic institution
563
+ verifier.trusted_issuers["mit.edu"] = "ed25519:mit_pubkey"
564
+ ```
565
+
566
+ ### Fetching Public Keys
567
+
568
+ In production, public keys should be fetched from:
569
+ 1. **DNS TXT records** - `_ed25519.domain.com`
570
+ 2. **Well-known endpoints** - `/.well-known/ed25519-keys.json`
571
+ 3. **Keyservers** - Similar to PGP keyservers
572
+
573
+ Example DNS lookup:
574
+ ```bash
575
+ dig TXT _ed25519.reuters.com
576
+ ```
577
+
578
+ ## Security Considerations
579
+
580
+ ### Key Management
581
+ - **Never commit private keys** to repositories
582
+ - Use environment variables or secure vaults
583
+ - Rotate keys periodically
584
+ - Maintain key revocation lists
585
+
586
+ ### Verification Limitations
587
+ - Ed25519 proves who signed, not truthfulness
588
+ - Trust still depends on issuer integrity
589
+ - Signatures don't prevent plagiarism
590
+ - Timestamps can be backdated by signer
591
+
592
+ ### Attack Vectors
593
+ | Attack | Mitigation |
594
+ |--------|------------|
595
+ | Key compromise | Regular rotation, revocation checks |
596
+ | Replay attacks | Include timestamps, nonces |
597
+ | Man-in-the-middle | TLS + signature verification |
598
+ | Fake issuers | Strict whitelist management |
599
+
600
+ ## Integration with GOAP Actions
601
+
602
+ ### New Actions Added
603
+
604
+ | Action | Input | Output | Verification |
605
+ |--------|-------|--------|--------------|
606
+ | `configure_trusted_issuers` | issuer list | whitelist_active | None |
607
+ | `fetch_signed_source` | URL + pubkey | signed_content | Ed25519 |
608
+ | `sign_extracted_facts` | facts + privkey | signed_facts | Self-sig |
609
+ | `verify_claim_cryptographic` | claim + sig | verified | Ed25519 |
610
+ | `build_citation_chain` | facts | chain | Chain hash |
611
+ | `verify_citation_chain` | chain | verified_chain | Chain sigs |
612
+ | `generate_signed_report` | report + privkey | signed_report | Ed25519 |
613
+
614
+ ### Cost Adjustments
615
+
616
+ Verification adds overhead:
617
+ - `fetch_signed_source`: +1 cost (verification time)
618
+ - `verify_claim_cryptographic`: +1 cost
619
+ - `verify_citation_chain`: +2 cost (full chain traversal)
620
+
621
+ But provides verification bonus in confidence calculation.
622
+
623
+ ## CLI Reference
624
+
625
+ ### Using with goalie
626
+
627
+ ```bash
628
+ # Verified search
629
+ goalie search "query" --verify --strict-verify
630
+
631
+ # Check issuer trust
632
+ goalie trust check "reuters.com"
633
+
634
+ # Add trusted issuer
635
+ goalie trust add "mycompany.com" --pubkey "ed25519:..."
636
+
637
+ # Verify existing fact
638
+ goalie verify --fact '{"claim":"...", "signature":"..."}'
639
+
640
+ # Generate research keypair
641
+ goalie keys generate --output ./research-keys.json
642
+ ```
643
+
644
+ ## Appendix: Test Vectors
645
+
646
+ ### Test Keypair
647
+ ```
648
+ Private (seed): 9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60
649
+ Public: d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
650
+ ```
651
+
652
+ ### Test Signature
653
+ ```
654
+ Message: "Test message for GOAP research"
655
+ Signature: e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b
656
+ ```
657
+
658
+ Verify: `Ed25519.verify(signature, message, public_key) == true`