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.
- package/README.md +22 -10
- package/docs/bot-ownership-v1.md +43 -0
- package/docs/implementation-levels.md +235 -0
- package/docs/private-vault-v1.md +104 -0
- package/docs/signet-in-5-minutes.md +181 -0
- package/examples/full-flow.ts +260 -0
- package/examples/jurisdictions/brazil-lawyer.ts +186 -0
- package/examples/jurisdictions/eu-doctor.ts +160 -0
- package/examples/jurisdictions/india-doctor.ts +207 -0
- package/examples/jurisdictions/japan-notary.ts +157 -0
- package/examples/jurisdictions/multi-jurisdiction.ts +174 -0
- package/examples/jurisdictions/uae-professional.ts +190 -0
- package/examples/jurisdictions/uk-solicitor.ts +170 -0
- package/examples/jurisdictions/us-attorney.ts +161 -0
- package/examples/signet-me.ts +253 -0
- package/llms-full.txt +3775 -0
- package/llms.txt +152 -0
- package/package.json +14 -3
- package/spec/protocol.md +2628 -0
- package/spec/voting.md +563 -0
|
@@ -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('═══════════════════════════════════════════════════');
|