0
# Digital Signatures
1
2
Digital signature functions provide message authentication, integrity, and non-repudiation using Ed25519 elliptic curve cryptography. Signatures prove that a message was signed by the holder of a private key without revealing the private key.
3
4
## Key Pair Generation
5
6
### Generate Random Key Pair
7
8
```javascript { .api }
9
/**
10
* Generate a random Ed25519 key pair for signing
11
* @returns Object with publicKey and privateKey properties
12
*/
13
function crypto_sign_keypair(): {
14
publicKey: Uint8Array; // 32 bytes - can be shared publicly
15
privateKey: Uint8Array; // 64 bytes - keep secret
16
};
17
```
18
19
### Generate Key Pair from Seed
20
21
```javascript { .api }
22
/**
23
* Generate deterministic Ed25519 key pair from seed
24
* @param seed - 32-byte seed for reproducible key generation
25
* @returns Object with publicKey and privateKey properties
26
*/
27
function crypto_sign_seed_keypair(seed: Uint8Array): {
28
publicKey: Uint8Array;
29
privateKey: Uint8Array;
30
};
31
```
32
33
## Digital Signatures
34
35
### Attached Signatures
36
37
Attached signatures include the message within the signed data.
38
39
```javascript { .api }
40
/**
41
* Sign a message (signature includes the message)
42
* @param message - Data to sign
43
* @param privateKey - 64-byte private signing key
44
* @returns Uint8Array - Signed message (message + signature)
45
*/
46
function crypto_sign(
47
message: Uint8Array,
48
privateKey: Uint8Array
49
): Uint8Array;
50
51
/**
52
* Verify and extract message from signed data
53
* @param signedMessage - Signed message from crypto_sign
54
* @param publicKey - 32-byte public verification key
55
* @returns Uint8Array - Original message if signature is valid
56
* @throws Error if signature verification fails
57
*/
58
function crypto_sign_open(
59
signedMessage: Uint8Array,
60
publicKey: Uint8Array
61
): Uint8Array;
62
```
63
64
### Detached Signatures (Recommended)
65
66
Detached signatures are separate from the message, allowing independent storage.
67
68
```javascript { .api }
69
/**
70
* Create detached signature for a message
71
* @param message - Data to sign
72
* @param privateKey - 64-byte private signing key
73
* @returns Uint8Array - 64-byte signature
74
*/
75
function crypto_sign_detached(
76
message: Uint8Array,
77
privateKey: Uint8Array
78
): Uint8Array;
79
80
/**
81
* Verify detached signature for a message
82
* @param signature - 64-byte signature to verify
83
* @param message - Original message data
84
* @param publicKey - 32-byte public verification key
85
* @returns boolean - True if signature is valid
86
*/
87
function crypto_sign_verify_detached(
88
signature: Uint8Array,
89
message: Uint8Array,
90
publicKey: Uint8Array
91
): boolean;
92
```
93
94
## Streaming Signatures
95
96
For large messages that don't fit in memory, use streaming signature operations.
97
98
### Initialize Streaming
99
100
```javascript { .api }
101
/**
102
* Initialize streaming signature state
103
* @returns Uint8Array - State object for streaming operations
104
*/
105
function crypto_sign_init(): Uint8Array;
106
```
107
108
### Update with Data
109
110
```javascript { .api }
111
/**
112
* Add data chunk to streaming signature
113
* @param state_address - State from crypto_sign_init
114
* @param message_chunk - Data chunk to add to signature
115
*/
116
function crypto_sign_update(
117
state_address: any,
118
message_chunk: Uint8Array
119
): void;
120
```
121
122
### Finalize Signature
123
124
```javascript { .api }
125
/**
126
* Finalize streaming signature and create signature
127
* @param state_address - State from init/update operations
128
* @param privateKey - 64-byte private signing key
129
* @returns Uint8Array - 64-byte signature
130
*/
131
function crypto_sign_final_create(
132
state_address: any,
133
privateKey: Uint8Array
134
): Uint8Array;
135
136
/**
137
* Finalize streaming signature and verify existing signature
138
* @param state_address - State from init/update operations
139
* @param signature - 64-byte signature to verify
140
* @param publicKey - 32-byte public verification key
141
* @returns boolean - True if signature is valid
142
*/
143
function crypto_sign_final_verify(
144
state_address: any,
145
signature: Uint8Array,
146
publicKey: Uint8Array
147
): boolean;
148
```
149
150
## Key Management
151
152
### Extract Public Key from Private Key
153
154
```javascript { .api }
155
/**
156
* Extract public key from Ed25519 private key
157
* @param privateKey - 64-byte Ed25519 private key
158
* @returns Uint8Array - 32-byte public key
159
*/
160
function crypto_sign_ed25519_sk_to_pk(privateKey: Uint8Array): Uint8Array;
161
162
/**
163
* Extract seed from Ed25519 private key
164
* @param privateKey - 64-byte Ed25519 private key
165
* @returns Uint8Array - 32-byte seed
166
*/
167
function crypto_sign_ed25519_sk_to_seed(privateKey: Uint8Array): Uint8Array;
168
```
169
170
### Key Conversion
171
172
Convert between Ed25519 signing keys and Curve25519 encryption keys.
173
174
```javascript { .api }
175
/**
176
* Convert Ed25519 public key to Curve25519 public key
177
* @param edPk - 32-byte Ed25519 public key
178
* @returns Uint8Array - 32-byte Curve25519 public key
179
*/
180
function crypto_sign_ed25519_pk_to_curve25519(edPk: Uint8Array): Uint8Array;
181
182
/**
183
* Convert Ed25519 private key to Curve25519 private key
184
* @param edSk - 64-byte Ed25519 private key
185
* @returns Uint8Array - 32-byte Curve25519 private key
186
*/
187
function crypto_sign_ed25519_sk_to_curve25519(edSk: Uint8Array): Uint8Array;
188
```
189
190
## Constants
191
192
```javascript { .api }
193
const crypto_sign_BYTES: number; // 64 (signature length)
194
const crypto_sign_PUBLICKEYBYTES: number; // 32 (public key length)
195
const crypto_sign_SECRETKEYBYTES: number; // 64 (private key length)
196
const crypto_sign_SEEDBYTES: number; // 32 (seed length)
197
const crypto_sign_MESSAGEBYTES_MAX: number; // Maximum message size
198
199
// Ed25519-specific constants (same values)
200
const crypto_sign_ed25519_BYTES: number; // 64
201
const crypto_sign_ed25519_PUBLICKEYBYTES: number; // 32
202
const crypto_sign_ed25519_SECRETKEYBYTES: number; // 64
203
const crypto_sign_ed25519_SEEDBYTES: number; // 32
204
const crypto_sign_ed25519_MESSAGEBYTES_MAX: number;
205
```
206
207
## Usage Examples
208
209
### Basic Digital Signatures
210
211
```javascript
212
import _sodium from 'libsodium-wrappers-sumo';
213
await _sodium.ready;
214
const sodium = _sodium;
215
216
// Generate signing key pair
217
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
218
219
// Message to sign
220
const message = sodium.from_string('This message is authentic');
221
222
// Create detached signature
223
const signature = sodium.crypto_sign_detached(message, privateKey);
224
225
// Verify signature
226
const isValid = sodium.crypto_sign_verify_detached(signature, message, publicKey);
227
228
console.log('Signature valid:', isValid); // true
229
console.log('Signature length:', signature.length); // 64 bytes
230
console.log('Public key length:', publicKey.length); // 32 bytes
231
```
232
233
### Attached Signatures
234
235
```javascript
236
// Sign message with attached signature
237
const signedMessage = sodium.crypto_sign(message, privateKey);
238
239
console.log('Original message length:', message.length);
240
console.log('Signed message length:', signedMessage.length); // +64 bytes
241
242
// Verify and extract original message
243
const verifiedMessage = sodium.crypto_sign_open(signedMessage, publicKey);
244
245
console.log('Messages match:',
246
sodium.memcmp(message, verifiedMessage)); // true
247
```
248
249
### Deterministic Key Generation
250
251
```javascript
252
// Generate reproducible keys from seed
253
const seed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES);
254
const keyPair1 = sodium.crypto_sign_seed_keypair(seed);
255
const keyPair2 = sodium.crypto_sign_seed_keypair(seed);
256
257
// Keys are identical
258
console.log('Public keys match:',
259
sodium.memcmp(keyPair1.publicKey, keyPair2.publicKey)); // true
260
261
console.log('Private keys match:',
262
sodium.memcmp(keyPair1.privateKey, keyPair2.privateKey)); // true
263
```
264
265
### Streaming Signatures for Large Data
266
267
```javascript
268
// Sign large data in chunks
269
function signLargeData(chunks, privateKey) {
270
const state = sodium.crypto_sign_init();
271
272
for (const chunk of chunks) {
273
sodium.crypto_sign_update(state, chunk);
274
}
275
276
return sodium.crypto_sign_final_create(state, privateKey);
277
}
278
279
// Verify large data in chunks
280
function verifyLargeData(chunks, signature, publicKey) {
281
const state = sodium.crypto_sign_init();
282
283
for (const chunk of chunks) {
284
sodium.crypto_sign_update(state, chunk);
285
}
286
287
return sodium.crypto_sign_final_verify(state, signature, publicKey);
288
}
289
290
// Usage with large file
291
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
292
const largeData = new Uint8Array(10 * 1024 * 1024); // 10MB
293
sodium.randombytes_buf_into(largeData);
294
295
// Split into chunks
296
const chunkSize = 64 * 1024; // 64KB chunks
297
const chunks = [];
298
for (let i = 0; i < largeData.length; i += chunkSize) {
299
chunks.push(largeData.subarray(i, i + chunkSize));
300
}
301
302
console.time('Sign large data');
303
const signature = signLargeData(chunks, privateKey);
304
console.timeEnd('Sign large data');
305
306
console.time('Verify large data');
307
const isValid = verifyLargeData(chunks, signature, publicKey);
308
console.timeEnd('Verify large data');
309
310
console.log('Large data signature valid:', isValid); // true
311
```
312
313
### Document Signing System
314
315
```javascript
316
class DocumentSigner {
317
constructor() {
318
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
319
this.publicKey = publicKey;
320
this.privateKey = privateKey;
321
}
322
323
signDocument(document) {
324
const documentBytes = sodium.from_string(JSON.stringify(document));
325
const signature = sodium.crypto_sign_detached(documentBytes, this.privateKey);
326
327
return {
328
document,
329
signature: sodium.to_base64(signature),
330
publicKey: sodium.to_hex(this.publicKey),
331
timestamp: new Date().toISOString()
332
};
333
}
334
335
verifyDocument(signedDoc) {
336
try {
337
const documentBytes = sodium.from_string(JSON.stringify(signedDoc.document));
338
const signature = sodium.from_base64(signedDoc.signature);
339
const publicKey = sodium.from_hex(signedDoc.publicKey);
340
341
return sodium.crypto_sign_verify_detached(signature, documentBytes, publicKey);
342
} catch (e) {
343
return false;
344
}
345
}
346
347
getPublicKeyHex() {
348
return sodium.to_hex(this.publicKey);
349
}
350
}
351
352
// Usage
353
const signer = new DocumentSigner();
354
const document = {
355
title: 'Important Contract',
356
content: 'Contract terms and conditions...',
357
amount: 10000
358
};
359
360
const signedDoc = signer.signDocument(document);
361
console.log('Signed document:', signedDoc);
362
363
const isValid = signer.verifyDocument(signedDoc);
364
console.log('Document signature valid:', isValid); // true
365
366
// Try to tamper with document
367
const tamperedDoc = { ...signedDoc };
368
tamperedDoc.document.amount = 50000;
369
370
const isTamperedValid = signer.verifyDocument(tamperedDoc);
371
console.log('Tampered document valid:', isTamperedValid); // false
372
```
373
374
### Multi-Signature Validation
375
376
```javascript
377
class MultiSigner {
378
constructor(requiredSignatures = 2) {
379
this.requiredSignatures = requiredSignatures;
380
this.signers = [];
381
}
382
383
addSigner(name) {
384
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
385
this.signers.push({
386
name,
387
publicKey,
388
privateKey: privateKey // In practice, each signer keeps their own private key
389
});
390
return sodium.to_hex(publicKey);
391
}
392
393
signMessage(message, signerIndex) {
394
if (signerIndex >= this.signers.length) {
395
throw new Error('Invalid signer index');
396
}
397
398
const messageBytes = sodium.from_string(message);
399
const signature = sodium.crypto_sign_detached(
400
messageBytes,
401
this.signers[signerIndex].privateKey
402
);
403
404
return {
405
signerName: this.signers[signerIndex].name,
406
publicKey: sodium.to_hex(this.signers[signerIndex].publicKey),
407
signature: sodium.to_base64(signature)
408
};
409
}
410
411
verifyMultiSignature(message, signatures) {
412
if (signatures.length < this.requiredSignatures) {
413
return false;
414
}
415
416
const messageBytes = sodium.from_string(message);
417
let validSignatures = 0;
418
419
for (const sig of signatures) {
420
try {
421
const publicKey = sodium.from_hex(sig.publicKey);
422
const signature = sodium.from_base64(sig.signature);
423
424
if (sodium.crypto_sign_verify_detached(signature, messageBytes, publicKey)) {
425
validSignatures++;
426
}
427
} catch (e) {
428
// Invalid signature format
429
continue;
430
}
431
}
432
433
return validSignatures >= this.requiredSignatures;
434
}
435
}
436
437
// Usage
438
const multiSigner = new MultiSigner(2); // Require 2 signatures
439
440
const alice = multiSigner.addSigner('Alice');
441
const bob = multiSigner.addSigner('Bob');
442
const charlie = multiSigner.addSigner('Charlie');
443
444
const message = 'Execute transaction: transfer $1000';
445
446
// Multiple parties sign
447
const signatures = [
448
multiSigner.signMessage(message, 0), // Alice
449
multiSigner.signMessage(message, 1), // Bob
450
];
451
452
const isValid = multiSigner.verifyMultiSignature(message, signatures);
453
console.log('Multi-signature valid:', isValid); // true
454
```
455
456
### Key Conversion for Hybrid Cryptography
457
458
```javascript
459
// Convert signing keys for use with encryption
460
const { publicKey: signPublicKey, privateKey: signPrivateKey } = sodium.crypto_sign_keypair();
461
462
// Convert to encryption keys
463
const encryptPublicKey = sodium.crypto_sign_ed25519_pk_to_curve25519(signPublicKey);
464
const encryptPrivateKey = sodium.crypto_sign_ed25519_sk_to_curve25519(signPrivateKey);
465
466
console.log('Sign public key:', sodium.to_hex(signPublicKey));
467
console.log('Encrypt public key:', sodium.to_hex(encryptPublicKey));
468
469
// Now you can use the same identity for both signing and encryption
470
const message = sodium.from_string('Signed and encrypted message');
471
472
// Sign with Ed25519
473
const signature = sodium.crypto_sign_detached(message, signPrivateKey);
474
475
// Encrypt with Curve25519 (need recipient's key)
476
const recipientKeys = sodium.crypto_box_keypair();
477
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
478
const ciphertext = sodium.crypto_box_easy(message, nonce, recipientKeys.publicKey, encryptPrivateKey);
479
480
console.log('Message signed and encrypted with same identity');
481
```
482
483
## Security Considerations
484
485
- **Private Key Security**: Keep private keys absolutely secret - signatures prove key possession
486
- **Message Integrity**: Signatures protect against message tampering
487
- **Non-Repudiation**: Valid signatures prove the signer cannot deny signing
488
- **Public Key Authentication**: Verify public key authenticity through trusted channels
489
- **Replay Protection**: Include timestamps or sequence numbers in signed messages
490
- **Key Rotation**: Consider periodic key rotation for long-term security
491
492
## Algorithm Details
493
494
Ed25519 provides:
495
- **128-bit security level**: Equivalent to 3072-bit RSA
496
- **Fast verification**: Optimized for signature verification
497
- **Small signatures**: Only 64 bytes per signature
498
- **Deterministic**: Same message and key always produce same signature
499
- **Side-channel resistant**: Designed to prevent timing and power analysis attacks
500
501
Digital signatures are essential for authentication, integrity, and non-repudiation in modern cryptographic systems.