signet-protocol 1.11.0 → 1.11.2

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.
@@ -0,0 +1,253 @@
1
+ // Example: Full "Signet Me" Flow
2
+ // Profile creation → backup → connection → verification words
3
+ //
4
+ // Run: npx tsx examples/signet-me.ts
5
+
6
+ import {
7
+ generateMnemonic,
8
+ validateMnemonic,
9
+ mnemonicToEntropy,
10
+ entropyToMnemonic,
11
+ createSignetIdentity,
12
+ deriveAdditionalPersona,
13
+ splitSecret,
14
+ reconstructSecret,
15
+ shareToWords,
16
+ wordsToShare,
17
+ createQRPayload,
18
+ serializeQRPayload,
19
+ parseQRPayload,
20
+ computeSharedSecret,
21
+ createConnection,
22
+ getSignetWords,
23
+ verifySignetWords,
24
+ formatSignetWords,
25
+ getSignetDisplay,
26
+ } from '../src/index.js';
27
+ import { bytesToHex } from '@noble/hashes/utils.js';
28
+
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+ // Step 1: Alice creates her identity
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+
33
+ console.log('═══════════════════════════════════════════════════');
34
+ console.log(' STEP 1: Profile Creation');
35
+ console.log('═══════════════════════════════════════════════════\n');
36
+
37
+ const aliceMnemonic = generateMnemonic();
38
+ const aliceIdentity = createSignetIdentity(aliceMnemonic);
39
+ const alice = {
40
+ privateKey: bytesToHex(aliceIdentity.naturalPerson.identity.privateKey),
41
+ publicKey: bytesToHex(aliceIdentity.naturalPerson.identity.publicKey),
42
+ };
43
+
44
+ console.log('Alice creates a new Signet identity:\n');
45
+ console.log(` Mnemonic: ${aliceMnemonic}`);
46
+ console.log(` Public Key: ${alice.publicKey.slice(0, 16)}...`);
47
+ console.log(` Valid: ${validateMnemonic(aliceMnemonic)}`);
48
+
49
+ // Alice also derives a child persona
50
+ const aliceChildPersona = deriveAdditionalPersona(aliceIdentity.root, 'child', 0);
51
+ const aliceChild = {
52
+ publicKey: bytesToHex(aliceChildPersona.identity.publicKey),
53
+ };
54
+ console.log(`\n Child's Key: ${aliceChild.publicKey.slice(0, 16)}... (persona: child)`);
55
+ aliceIdentity.root.destroy();
56
+
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ // Step 2: Alice backs up her seed using Shamir's Secret Sharing
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+
61
+ console.log('\n═══════════════════════════════════════════════════');
62
+ console.log(' STEP 2: Shared Backup (Shamir 2-of-3)');
63
+ console.log('═══════════════════════════════════════════════════\n');
64
+
65
+ const entropy = mnemonicToEntropy(aliceMnemonic);
66
+ const shares = splitSecret(entropy, 2, 3);
67
+
68
+ console.log('Alice splits her seed into 3 shares (any 2 reconstruct):\n');
69
+ for (const share of shares) {
70
+ const words = shareToWords(share);
71
+ console.log(` Share ${share.id}: ${words.slice(0, 4).join(' ')}... (${words.length} words)`);
72
+ }
73
+
74
+ // Verify reconstruction with shares 1 and 3 (skipping share 2)
75
+ const reconstructed = reconstructSecret(
76
+ [wordsToShare(shareToWords(shares[0])), wordsToShare(shareToWords(shares[2]))],
77
+ 2
78
+ );
79
+ const reconstructedMnemonic = entropyToMnemonic(reconstructed);
80
+ console.log(`\n Reconstructed from shares 1 + 3: ${reconstructedMnemonic === aliceMnemonic ? 'SUCCESS ✓' : 'FAILED ✗'}`);
81
+
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+ // Step 3: Bob creates his identity
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+
86
+ console.log('\n═══════════════════════════════════════════════════');
87
+ console.log(' STEP 3: Bob Creates His Identity');
88
+ console.log('═══════════════════════════════════════════════════\n');
89
+
90
+ const bobMnemonic = generateMnemonic();
91
+ const bobIdentity = createSignetIdentity(bobMnemonic);
92
+ const bob = {
93
+ privateKey: bytesToHex(bobIdentity.naturalPerson.identity.privateKey),
94
+ publicKey: bytesToHex(bobIdentity.naturalPerson.identity.publicKey),
95
+ };
96
+ const bobChildPersona = deriveAdditionalPersona(bobIdentity.root, 'child', 0);
97
+ const bobChild = {
98
+ publicKey: bytesToHex(bobChildPersona.identity.publicKey),
99
+ };
100
+ bobIdentity.root.destroy();
101
+
102
+ console.log(`Bob's identity:`);
103
+ console.log(` Public Key: ${bob.publicKey.slice(0, 16)}...`);
104
+ console.log(` Child's Key: ${bobChild.publicKey.slice(0, 16)}...`);
105
+
106
+ // ─────────────────────────────────────────────────────────────────────────────
107
+ // Step 4: Alice and Bob meet in person — QR code exchange
108
+ // ─────────────────────────────────────────────────────────────────────────────
109
+
110
+ console.log('\n═══════════════════════════════════════════════════');
111
+ console.log(' STEP 4: In-Person Connection (QR Exchange)');
112
+ console.log('═══════════════════════════════════════════════════\n');
113
+
114
+ // Alice shows her QR code with selected contact info
115
+ const aliceQR = createQRPayload(alice.publicKey, {
116
+ name: 'Alice',
117
+ mobile: '+44 7700 900000',
118
+ childPubkeys: [aliceChild.publicKey],
119
+ });
120
+
121
+ // Bob shows his QR code
122
+ const bobQR = createQRPayload(bob.publicKey, {
123
+ name: 'Bob',
124
+ mobile: '+44 7700 900001',
125
+ address: '42 Oak Lane, London',
126
+ childPubkeys: [bobChild.publicKey],
127
+ });
128
+
129
+ console.log('Alice and Bob meet at a local meetup. They scan each other\'s QR codes.\n');
130
+
131
+ // Simulate the QR scan — each party scans the other's serialized payload
132
+ const aliceScannedBob = parseQRPayload(serializeQRPayload(bobQR));
133
+ const bobScannedAlice = parseQRPayload(serializeQRPayload(aliceQR));
134
+
135
+ // Each creates a connection
136
+ const aliceConnection = createConnection(alice.privateKey, aliceScannedBob, {
137
+ name: 'Alice',
138
+ mobile: '+44 7700 900000',
139
+ childPubkeys: [aliceChild.publicKey],
140
+ });
141
+
142
+ const bobConnection = createConnection(bob.privateKey, bobScannedAlice, {
143
+ name: 'Bob',
144
+ mobile: '+44 7700 900001',
145
+ address: '42 Oak Lane, London',
146
+ childPubkeys: [bobChild.publicKey],
147
+ });
148
+
149
+ console.log(`Alice → Bob shared secret: ${aliceConnection.sharedSecret.slice(0, 16)}...`);
150
+ console.log(`Bob → Alice shared secret: ${bobConnection.sharedSecret.slice(0, 16)}...`);
151
+ console.log(`Secrets match: ${aliceConnection.sharedSecret === bobConnection.sharedSecret ? 'YES ✓' : 'NO ✗'}`);
152
+ console.log(`\nAlice sees Bob's info:`);
153
+ console.log(` Name: ${aliceConnection.theirInfo.name}`);
154
+ console.log(` Mobile: ${aliceConnection.theirInfo.mobile}`);
155
+ console.log(` Kids: ${aliceConnection.theirInfo.childPubkeys?.length} child key(s)`);
156
+ console.log(`\nBob sees Alice's info:`);
157
+ console.log(` Name: ${bobConnection.theirInfo.name}`);
158
+ console.log(` Mobile: ${bobConnection.theirInfo.mobile}`);
159
+ console.log(` Kids: ${bobConnection.theirInfo.childPubkeys?.length} child key(s)`);
160
+
161
+ // ─────────────────────────────────────────────────────────────────────────────
162
+ // Step 5: Months later — Bob calls Alice asking for money
163
+ // ─────────────────────────────────────────────────────────────────────────────
164
+
165
+ console.log('\n═══════════════════════════════════════════════════');
166
+ console.log(' STEP 5: "Signet Me" — Phone Verification');
167
+ console.log('═══════════════════════════════════════════════════\n');
168
+
169
+ console.log('Six months later, Bob calls Alice:');
170
+ console.log('"Hey Alice, it\'s Bob. I\'m in a tight spot — can you send');
171
+ console.log(' some cash to my new bank account?"');
172
+ console.log('');
173
+ console.log('Alice: "Sure, no problem. Signet me first."\n');
174
+
175
+ // Both open each other's profiles at the same time
176
+ const now = Date.now();
177
+
178
+ const aliceSees = getSignetDisplay(aliceConnection.sharedSecret, now);
179
+ const bobSees = getSignetDisplay(bobConnection.sharedSecret, now);
180
+
181
+ console.log('Alice opens Bob\'s profile and sees:');
182
+ console.log(` My Signet: ${aliceSees.formatted}`);
183
+ console.log(` Expires in: ${aliceSees.expiresIn.toFixed(0)}s`);
184
+ console.log('');
185
+ console.log('Bob opens Alice\'s profile and sees:');
186
+ console.log(` My Signet: ${bobSees.formatted}`);
187
+ console.log(` Expires in: ${bobSees.expiresIn.toFixed(0)}s`);
188
+ console.log('');
189
+ console.log(`Words match: ${aliceSees.formatted === bobSees.formatted ? 'YES ✓' : 'NO ✗'}`);
190
+
191
+ // Bob reads the words to Alice
192
+ const bobReadsWords = getSignetWords(bobConnection.sharedSecret, now);
193
+ const aliceVerifies = verifySignetWords(aliceConnection.sharedSecret, bobReadsWords, now);
194
+
195
+ console.log(`\nBob reads: "${formatSignetWords(bobReadsWords)}"`);
196
+ console.log(`Alice verifies: ${aliceVerifies ? 'MATCH ✓ — It\'s really Bob!' : 'NO MATCH ✗ — Impostor!'}`);
197
+
198
+ // ─────────────────────────────────────────────────────────────────────────────
199
+ // Step 6: Demonstrating word rotation
200
+ // ─────────────────────────────────────────────────────────────────────────────
201
+
202
+ console.log('\n═══════════════════════════════════════════════════');
203
+ console.log(' STEP 6: Word Rotation');
204
+ console.log('═══════════════════════════════════════════════════\n');
205
+
206
+ console.log('Words rotate every 30 seconds:\n');
207
+ for (let i = 0; i < 4; i++) {
208
+ const futureTime = now + i * 30_000;
209
+ const words = getSignetWords(aliceConnection.sharedSecret, futureTime);
210
+ const label = i === 0 ? 'Now ' : `+${i * 30}s `;
211
+ console.log(` ${label} ${formatSignetWords(words)}`);
212
+ }
213
+
214
+ // ─────────────────────────────────────────────────────────────────────────────
215
+ // Step 7: Bank scenario
216
+ // ─────────────────────────────────────────────────────────────────────────────
217
+
218
+ console.log('\n═══════════════════════════════════════════════════');
219
+ console.log(' STEP 7: Bank Verification');
220
+ console.log('═══════════════════════════════════════════════════\n');
221
+
222
+ // The bank has a Nostr keypair too
223
+ const bankMnemonic = generateMnemonic();
224
+ const bankIdentity = createSignetIdentity(bankMnemonic);
225
+ const bank = {
226
+ privateKey: bytesToHex(bankIdentity.naturalPerson.identity.privateKey),
227
+ publicKey: bytesToHex(bankIdentity.naturalPerson.identity.publicKey),
228
+ };
229
+ bankIdentity.root.destroy();
230
+
231
+ // Alice visits the bank and scans their QR code
232
+ const bankQR = createQRPayload(bank.publicKey, { name: 'High Street Bank' });
233
+ const aliceBankConnection = createConnection(alice.privateKey, parseQRPayload(serializeQRPayload(bankQR)), {
234
+ name: 'Alice',
235
+ });
236
+
237
+ // Bank also stores the connection
238
+ const bankAliceSecret = computeSharedSecret(bank.privateKey, alice.publicKey);
239
+
240
+ console.log('"The bank" calls Alice about suspicious activity.\n');
241
+ console.log('Alice: "Read me my signet."\n');
242
+
243
+ const bankNow = Date.now();
244
+ const bankWords = getSignetWords(bankAliceSecret, bankNow);
245
+ const aliceBankWords = getSignetWords(aliceBankConnection.sharedSecret, bankNow);
246
+
247
+ console.log(`Bank agent reads: ${formatSignetWords(bankWords)}`);
248
+ console.log(`Alice sees: ${formatSignetWords(aliceBankWords)}`);
249
+ console.log(`Match: ${formatSignetWords(bankWords) === formatSignetWords(aliceBankWords) ? 'YES ✓ — It\'s really the bank.' : 'NO ✗ — Scam call!'}`);
250
+
251
+ console.log('\n═══════════════════════════════════════════════════');
252
+ console.log(' Done. Your signet is your proof.');
253
+ console.log('═══════════════════════════════════════════════════');