@btc-vision/transaction 1.7.0 → 1.7.1
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/browser/index.js +1 -1
- package/browser/src/_version.d.ts +1 -1
- package/browser/src/mnemonic/BIPStandard.d.ts +8 -0
- package/browser/src/mnemonic/Mnemonic.d.ts +7 -2
- package/browser/src/opnet.d.ts +1 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/mnemonic/BIPStandard.d.ts +8 -0
- package/build/mnemonic/BIPStandard.js +24 -0
- package/build/mnemonic/Mnemonic.d.ts +7 -2
- package/build/mnemonic/Mnemonic.js +48 -6
- package/build/opnet.d.ts +1 -0
- package/build/opnet.js +1 -0
- package/documentation/README.md +32 -0
- package/documentation/quantum-support/01-introduction.md +88 -0
- package/documentation/quantum-support/02-mnemonic-and-wallet.md +445 -0
- package/documentation/quantum-support/03-address-generation.md +329 -0
- package/documentation/quantum-support/04-message-signing.md +623 -0
- package/documentation/quantum-support/05-address-verification.md +307 -0
- package/documentation/quantum-support/README.md +65 -0
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/mnemonic/BIPStandard.ts +92 -0
- package/src/mnemonic/Mnemonic.ts +133 -8
- package/src/opnet.ts +1 -0
- package/test/derivePath.test.ts +280 -1
- package/doc/README.md +0 -0
- /package/{doc → documentation}/addresses/P2OP.md +0 -0
- /package/{doc → documentation}/addresses/P2WDA.md +0 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
# Message Signing Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [ML-DSA Signing](#ml-dsa-signing)
|
|
5
|
+
- [Schnorr Signing](#schnorr-signing)
|
|
6
|
+
- [Input Formats](#input-formats)
|
|
7
|
+
- [Signature Verification](#signature-verification)
|
|
8
|
+
- [Tweaked Signatures](#tweaked-signatures)
|
|
9
|
+
- [Best Practices](#best-practices)
|
|
10
|
+
|
|
11
|
+
## ML-DSA Signing
|
|
12
|
+
|
|
13
|
+
### Basic ML-DSA Signing
|
|
14
|
+
|
|
15
|
+
Sign messages with quantum-resistant ML-DSA signatures:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Mnemonic, MessageSigner, MLDSASecurityLevel } from '@btc-vision/transaction';
|
|
19
|
+
import { networks } from '@btc-vision/bitcoin';
|
|
20
|
+
|
|
21
|
+
// Generate wallet
|
|
22
|
+
const mnemonic = Mnemonic.generate(undefined, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2);
|
|
23
|
+
const wallet = mnemonic.derive(0);
|
|
24
|
+
|
|
25
|
+
// Sign a message
|
|
26
|
+
const message = 'Hello, Quantum World!';
|
|
27
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message);
|
|
28
|
+
|
|
29
|
+
console.log('Message:', signed.message);
|
|
30
|
+
console.log('Signature:', Buffer.from(signed.signature).toString('hex'));
|
|
31
|
+
console.log('Public Key:', Buffer.from(signed.publicKey).toString('hex'));
|
|
32
|
+
console.log('Security Level:', signed.securityLevel);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### ML-DSA Signature Sizes
|
|
36
|
+
|
|
37
|
+
Different security levels produce different signature sizes:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// LEVEL2 (ML-DSA-44)
|
|
41
|
+
const level2Mnemonic = Mnemonic.generate(undefined, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2);
|
|
42
|
+
const level2Wallet = level2Mnemonic.derive(0);
|
|
43
|
+
const level2Sig = MessageSigner.signMLDSAMessage(level2Wallet.mldsaKeypair, 'test');
|
|
44
|
+
console.log('LEVEL2 Signature Size:', level2Sig.signature.length); // 2420 bytes
|
|
45
|
+
|
|
46
|
+
// LEVEL3 (ML-DSA-65)
|
|
47
|
+
const level3Mnemonic = Mnemonic.generate(undefined, '', networks.bitcoin, MLDSASecurityLevel.LEVEL3);
|
|
48
|
+
const level3Wallet = level3Mnemonic.derive(0);
|
|
49
|
+
const level3Sig = MessageSigner.signMLDSAMessage(level3Wallet.mldsaKeypair, 'test');
|
|
50
|
+
console.log('LEVEL3 Signature Size:', level3Sig.signature.length); // 3309 bytes
|
|
51
|
+
|
|
52
|
+
// LEVEL5 (ML-DSA-87)
|
|
53
|
+
const level5Mnemonic = Mnemonic.generate(undefined, '', networks.bitcoin, MLDSASecurityLevel.LEVEL5);
|
|
54
|
+
const level5Wallet = level5Mnemonic.derive(0);
|
|
55
|
+
const level5Sig = MessageSigner.signMLDSAMessage(level5Wallet.mldsaKeypair, 'test');
|
|
56
|
+
console.log('LEVEL5 Signature Size:', level5Sig.signature.length); // 4627 bytes
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Verifying ML-DSA Signatures
|
|
60
|
+
|
|
61
|
+
When verifying signatures, you need to create a public-key-only keypair using `QuantumBIP32Factory.fromPublicKey()`:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { MessageSigner, QuantumBIP32Factory } from '@btc-vision/transaction';
|
|
65
|
+
|
|
66
|
+
// Sign message
|
|
67
|
+
const message = 'Verify this quantum signature';
|
|
68
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message);
|
|
69
|
+
|
|
70
|
+
// Create public-key-only keypair for verification
|
|
71
|
+
const publicKeyPair = QuantumBIP32Factory.fromPublicKey(
|
|
72
|
+
signed.publicKey, // ML-DSA public key from signature
|
|
73
|
+
wallet.chainCode, // Chain code from wallet
|
|
74
|
+
network, // Network (mainnet/testnet/regtest)
|
|
75
|
+
securityLevel // ML-DSA security level (LEVEL2/LEVEL3/LEVEL5)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Verify signature
|
|
79
|
+
const isValid = MessageSigner.verifyMLDSASignature(
|
|
80
|
+
publicKeyPair, // Use the public-key-only keypair
|
|
81
|
+
signed.message,
|
|
82
|
+
signed.signature
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
console.log('Signature valid:', isValid); // true
|
|
86
|
+
|
|
87
|
+
// Verify with wrong message fails
|
|
88
|
+
const isInvalid = MessageSigner.verifyMLDSASignature(
|
|
89
|
+
publicKeyPair,
|
|
90
|
+
'Wrong message',
|
|
91
|
+
signed.signature
|
|
92
|
+
);
|
|
93
|
+
console.log('Invalid signature:', isInvalid); // false
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Important:** The `verifyMLDSASignature` method requires a keypair object, not just a raw public key.
|
|
97
|
+
|
|
98
|
+
- **If you have the original keypair:** Use it directly (e.g., `wallet.mldsaKeypair`)
|
|
99
|
+
- **If you only have the public key:** Use `QuantumBIP32Factory.fromPublicKey()` to reconstruct the keypair
|
|
100
|
+
|
|
101
|
+
### When to Use QuantumBIP32Factory.fromPublicKey()
|
|
102
|
+
|
|
103
|
+
**Use it when you DON'T have the original keypair:**
|
|
104
|
+
- Receiving a signature from someone else over the network
|
|
105
|
+
- Verifying signatures from stored public keys in a database
|
|
106
|
+
- Working with public keys in distributed systems
|
|
107
|
+
- Validating signatures from external sources
|
|
108
|
+
|
|
109
|
+
**Don't use it when you already have the keypair:**
|
|
110
|
+
- Verifying your own signatures in the same session
|
|
111
|
+
- Testing signatures you just created
|
|
112
|
+
- When you have access to `wallet.mldsaKeypair`
|
|
113
|
+
|
|
114
|
+
### Creating a Public-Key-Only Keypair
|
|
115
|
+
|
|
116
|
+
Parameters for `QuantumBIP32Factory.fromPublicKey()`:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const keypair = QuantumBIP32Factory.fromPublicKey(
|
|
120
|
+
publicKey, // Uint8Array - ML-DSA public key (1312-2592 bytes)
|
|
121
|
+
chainCode, // Buffer - Chain code (32 bytes)
|
|
122
|
+
network, // Network - networks.bitcoin, networks.testnet, or networks.regtest
|
|
123
|
+
securityLevel // MLDSASecurityLevel - LEVEL2, LEVEL3, or LEVEL5
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Parameter Details:**
|
|
128
|
+
- `publicKey`: The ML-DSA public key (1312 bytes for LEVEL2, 1952 for LEVEL3, 2592 for LEVEL5)
|
|
129
|
+
- `chainCode`: BIP32 chain code (32 bytes) - available from `wallet.chainCode`
|
|
130
|
+
- `network`: Bitcoin network configuration object
|
|
131
|
+
- `securityLevel`: **Must match** the security level used to generate the original key
|
|
132
|
+
|
|
133
|
+
**Why is this needed?**
|
|
134
|
+
|
|
135
|
+
The `verifyMLDSASignature` method requires a keypair object (not just a raw public key) because:
|
|
136
|
+
1. It needs the security level information embedded in the keypair
|
|
137
|
+
2. It needs the proper key structure for the ML-DSA verification algorithm
|
|
138
|
+
3. It maintains consistency with BIP32 hierarchical deterministic key derivation
|
|
139
|
+
|
|
140
|
+
### Common Verification Scenarios
|
|
141
|
+
|
|
142
|
+
**Scenario 1: Verifying your own signature (same session)**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const message = 'My message';
|
|
146
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message);
|
|
147
|
+
|
|
148
|
+
// You already have the keypair - use it directly
|
|
149
|
+
const valid = MessageSigner.verifyMLDSASignature(
|
|
150
|
+
wallet.mldsaKeypair, // Use existing keypair
|
|
151
|
+
signed.message,
|
|
152
|
+
signed.signature
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log('Valid:', valid); // true
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Scenario 2: Verifying a signature from someone else**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// You receive these from the network/API:
|
|
162
|
+
const receivedPublicKey = Buffer.from(/* hex string from network */);
|
|
163
|
+
const receivedMessage = 'Message from sender';
|
|
164
|
+
const receivedSignature = Buffer.from(/* hex string from network */);
|
|
165
|
+
const receivedChainCode = Buffer.from(/* hex string from network */);
|
|
166
|
+
const receivedSecurityLevel = MLDSASecurityLevel.LEVEL2;
|
|
167
|
+
|
|
168
|
+
// Reconstruct keypair from public key
|
|
169
|
+
const keypair = QuantumBIP32Factory.fromPublicKey(
|
|
170
|
+
receivedPublicKey,
|
|
171
|
+
receivedChainCode,
|
|
172
|
+
networks.bitcoin,
|
|
173
|
+
receivedSecurityLevel
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Verify the signature
|
|
177
|
+
const valid = MessageSigner.verifyMLDSASignature(
|
|
178
|
+
keypair,
|
|
179
|
+
receivedMessage,
|
|
180
|
+
receivedSignature
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
console.log('Signature from other party valid:', valid);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Scenario 3: Verifying stored signatures**
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// Load public key and signature from database
|
|
190
|
+
const storedPublicKey = await db.getPublicKey(userId);
|
|
191
|
+
const storedChainCode = await db.getChainCode(userId);
|
|
192
|
+
const storedSecurityLevel = await db.getSecurityLevel(userId);
|
|
193
|
+
const signature = await db.getSignature(messageId);
|
|
194
|
+
const message = await db.getMessage(messageId);
|
|
195
|
+
|
|
196
|
+
// Reconstruct keypair
|
|
197
|
+
const keypair = QuantumBIP32Factory.fromPublicKey(
|
|
198
|
+
Buffer.from(storedPublicKey, 'hex'),
|
|
199
|
+
Buffer.from(storedChainCode, 'hex'),
|
|
200
|
+
networks.bitcoin,
|
|
201
|
+
storedSecurityLevel
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Verify
|
|
205
|
+
const valid = MessageSigner.verifyMLDSASignature(
|
|
206
|
+
keypair,
|
|
207
|
+
message,
|
|
208
|
+
Buffer.from(signature, 'hex')
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
console.log('Stored signature valid:', valid);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Security Considerations
|
|
215
|
+
|
|
216
|
+
**Chain Code:**
|
|
217
|
+
- The chain code is public information in BIP32
|
|
218
|
+
- Store it alongside the public key for verification
|
|
219
|
+
- It's not sensitive but required for keypair reconstruction
|
|
220
|
+
|
|
221
|
+
**Security Level Matching:**
|
|
222
|
+
- Always use the same security level for verification as was used for signing
|
|
223
|
+
- Mismatched security levels will cause verification to fail
|
|
224
|
+
- Store the security level with the public key
|
|
225
|
+
|
|
226
|
+
**Network Matching:**
|
|
227
|
+
- Ensure the network parameter matches the original signing network
|
|
228
|
+
- Mainnet keys won't verify correctly if checked against testnet
|
|
229
|
+
|
|
230
|
+
**Message Integrity:**
|
|
231
|
+
- The message must match exactly between signing and verification
|
|
232
|
+
- Even a single byte difference will cause verification to fail
|
|
233
|
+
|
|
234
|
+
## Schnorr Signing
|
|
235
|
+
|
|
236
|
+
### Basic Schnorr Signing
|
|
237
|
+
|
|
238
|
+
Sign messages with classical Schnorr signatures:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { MessageSigner } from '@btc-vision/transaction';
|
|
242
|
+
|
|
243
|
+
const wallet = mnemonic.derive(0);
|
|
244
|
+
|
|
245
|
+
// Sign with Schnorr
|
|
246
|
+
const message = 'Hello, Bitcoin!';
|
|
247
|
+
const signed = MessageSigner.signMessage(wallet.keypair, message);
|
|
248
|
+
|
|
249
|
+
console.log('Message:', signed.message);
|
|
250
|
+
console.log('Signature:', Buffer.from(signed.signature).toString('hex'));
|
|
251
|
+
console.log('Public Key:', Buffer.from(signed.publicKey).toString('hex'));
|
|
252
|
+
console.log('Signature Size:', signed.signature.length); // 64 bytes (Schnorr)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Verifying Schnorr Signatures
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Sign message
|
|
259
|
+
const message = 'Verify this Schnorr signature';
|
|
260
|
+
const signed = MessageSigner.signMessage(wallet.keypair, message);
|
|
261
|
+
|
|
262
|
+
// Verify signature
|
|
263
|
+
const isValid = MessageSigner.verifySignature(
|
|
264
|
+
signed.publicKey,
|
|
265
|
+
signed.message,
|
|
266
|
+
signed.signature
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
console.log('Signature valid:', isValid); // true
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Input Formats
|
|
273
|
+
|
|
274
|
+
Both ML-DSA and Schnorr signing support multiple input formats:
|
|
275
|
+
|
|
276
|
+
### String Messages
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// UTF-8 string
|
|
280
|
+
const signed1 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, 'Hello, World!');
|
|
281
|
+
|
|
282
|
+
// Any string content
|
|
283
|
+
const signed2 = MessageSigner.signMLDSAMessage(
|
|
284
|
+
wallet.mldsaKeypair,
|
|
285
|
+
'Emoji test: 🚀 Quantum 🔐'
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Buffer Messages
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// From UTF-8 string
|
|
293
|
+
const message1 = Buffer.from('Hello, Buffer!', 'utf-8');
|
|
294
|
+
const signed1 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message1);
|
|
295
|
+
|
|
296
|
+
// Binary data
|
|
297
|
+
const message2 = Buffer.from([0x01, 0x02, 0x03, 0x04]);
|
|
298
|
+
const signed2 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message2);
|
|
299
|
+
|
|
300
|
+
// From hex
|
|
301
|
+
const message3 = Buffer.from('abcdef1234567890', 'hex');
|
|
302
|
+
const signed3 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message3);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Uint8Array Messages
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Uint8Array
|
|
309
|
+
const message = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"
|
|
310
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Hex String Messages
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Hex string (with 0x prefix)
|
|
317
|
+
const signed1 = MessageSigner.signMLDSAMessage(
|
|
318
|
+
wallet.mldsaKeypair,
|
|
319
|
+
'0xdeadbeef'
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Hex string (without 0x prefix)
|
|
323
|
+
const signed2 = MessageSigner.signMLDSAMessage(
|
|
324
|
+
wallet.mldsaKeypair,
|
|
325
|
+
'abcdef1234567890'
|
|
326
|
+
);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Cross-Format Verification
|
|
330
|
+
|
|
331
|
+
Verification works across all input formats:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const message = 'Test message';
|
|
335
|
+
|
|
336
|
+
// Sign with string
|
|
337
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, message);
|
|
338
|
+
|
|
339
|
+
// Create public-key-only keypair for verification
|
|
340
|
+
const publicKeyPair = QuantumBIP32Factory.fromPublicKey(
|
|
341
|
+
signed.publicKey,
|
|
342
|
+
wallet.chainCode,
|
|
343
|
+
network,
|
|
344
|
+
securityLevel
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Verify with Buffer
|
|
348
|
+
const messageBuffer = Buffer.from(message, 'utf-8');
|
|
349
|
+
const valid1 = MessageSigner.verifyMLDSASignature(
|
|
350
|
+
publicKeyPair,
|
|
351
|
+
messageBuffer,
|
|
352
|
+
signed.signature
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// Verify with Uint8Array
|
|
356
|
+
const messageArray = new Uint8Array(Buffer.from(message, 'utf-8'));
|
|
357
|
+
const valid2 = MessageSigner.verifyMLDSASignature(
|
|
358
|
+
publicKeyPair,
|
|
359
|
+
messageArray,
|
|
360
|
+
signed.signature
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
console.log(valid1 && valid2); // true - all formats work!
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Tweaked Signatures
|
|
367
|
+
|
|
368
|
+
### Tweaked Schnorr Signing
|
|
369
|
+
|
|
370
|
+
Sign with tweaked keys for Taproot compatibility:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { MessageSigner } from '@btc-vision/transaction';
|
|
374
|
+
|
|
375
|
+
const wallet = mnemonic.derive(0);
|
|
376
|
+
|
|
377
|
+
// Sign with tweaked key
|
|
378
|
+
const message = 'Taproot message';
|
|
379
|
+
const signed = MessageSigner.tweakAndSignMessage(wallet.keypair, message);
|
|
380
|
+
|
|
381
|
+
console.log('Tweaked Signature:', Buffer.from(signed.signature).toString('hex'));
|
|
382
|
+
console.log('Tweaked Public Key:', Buffer.from(signed.publicKey).toString('hex'));
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Verifying Tweaked Signatures
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// Sign with tweak
|
|
389
|
+
const message = 'Verify tweaked signature';
|
|
390
|
+
const signed = MessageSigner.tweakAndSignMessage(wallet.keypair, message);
|
|
391
|
+
|
|
392
|
+
// Verify with tweak
|
|
393
|
+
const isValid = MessageSigner.tweakAndVerifySignature(
|
|
394
|
+
signed.publicKey,
|
|
395
|
+
signed.message,
|
|
396
|
+
signed.signature
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
console.log('Tweaked signature valid:', isValid); // true
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Message Hashing
|
|
403
|
+
|
|
404
|
+
### SHA-256 Hashing
|
|
405
|
+
|
|
406
|
+
The MessageSigner automatically hashes messages before signing:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { MessageSigner } from '@btc-vision/transaction';
|
|
410
|
+
|
|
411
|
+
// Long message
|
|
412
|
+
const longMessage = 'This is a very long message that will be hashed before signing...';
|
|
413
|
+
|
|
414
|
+
// Automatically hashed to 32 bytes before signing
|
|
415
|
+
const hash = MessageSigner.sha256(longMessage);
|
|
416
|
+
console.log('Message hash:', Buffer.from(hash).toString('hex'));
|
|
417
|
+
console.log('Hash length:', hash.length); // 32 bytes
|
|
418
|
+
|
|
419
|
+
// Then signed
|
|
420
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, longMessage);
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Pre-hashed Messages
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
// You can also sign pre-hashed data
|
|
427
|
+
const message = 'Original message';
|
|
428
|
+
const hash = MessageSigner.sha256(message);
|
|
429
|
+
|
|
430
|
+
// Sign the hash directly
|
|
431
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, hash);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Best Practices
|
|
435
|
+
|
|
436
|
+
### ✅ DO:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Use appropriate security level for your use case
|
|
440
|
+
const standardWallet = Mnemonic.generate(
|
|
441
|
+
undefined, // Default strength (24 words)
|
|
442
|
+
'', // No passphrase
|
|
443
|
+
networks.bitcoin, // Mainnet
|
|
444
|
+
MLDSASecurityLevel.LEVEL2 // Good for most applications
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// Include context in your messages
|
|
448
|
+
const message = JSON.stringify({
|
|
449
|
+
action: 'transfer',
|
|
450
|
+
amount: 1000,
|
|
451
|
+
timestamp: Date.now(),
|
|
452
|
+
nonce: crypto.randomBytes(16).toString('hex')
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Verify signatures before trusting
|
|
456
|
+
const isValid = MessageSigner.verifyMLDSASignature(
|
|
457
|
+
publicKey,
|
|
458
|
+
message,
|
|
459
|
+
signature
|
|
460
|
+
);
|
|
461
|
+
if (!isValid) {
|
|
462
|
+
throw new Error('Invalid signature');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Store signatures with metadata
|
|
466
|
+
const signatureData = {
|
|
467
|
+
message: signed.message,
|
|
468
|
+
signature: Buffer.from(signed.signature).toString('hex'),
|
|
469
|
+
publicKey: Buffer.from(signed.publicKey).toString('hex'),
|
|
470
|
+
securityLevel: signed.securityLevel,
|
|
471
|
+
timestamp: Date.now()
|
|
472
|
+
};
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### ❌ DON'T:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// Don't sign without verification
|
|
479
|
+
MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, userInput); // Dangerous!
|
|
480
|
+
|
|
481
|
+
// Don't use signatures without checking validity
|
|
482
|
+
// Always verify!
|
|
483
|
+
|
|
484
|
+
// Don't expose private keys
|
|
485
|
+
console.log(wallet.privateKey); // Never do this!
|
|
486
|
+
|
|
487
|
+
// Don't sign arbitrary untrusted data
|
|
488
|
+
const untrustedData = externalAPI.getData();
|
|
489
|
+
// Validate and sanitize first!
|
|
490
|
+
|
|
491
|
+
// Don't reuse signatures for different messages
|
|
492
|
+
// Generate new signature for each unique message
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Message Structure
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// Good: Structured, verifiable message
|
|
499
|
+
interface SignedMessage {
|
|
500
|
+
version: number;
|
|
501
|
+
action: string;
|
|
502
|
+
payload: any;
|
|
503
|
+
timestamp: number;
|
|
504
|
+
nonce: string;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const message: SignedMessage = {
|
|
508
|
+
version: 1,
|
|
509
|
+
action: 'authenticate',
|
|
510
|
+
payload: { userId: '123' },
|
|
511
|
+
timestamp: Date.now(),
|
|
512
|
+
nonce: crypto.randomBytes(16).toString('hex')
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const messageString = JSON.stringify(message);
|
|
516
|
+
const signed = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, messageString);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Complete Example
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import {
|
|
523
|
+
MessageSigner,
|
|
524
|
+
MLDSASecurityLevel,
|
|
525
|
+
Mnemonic,
|
|
526
|
+
QuantumBIP32Factory,
|
|
527
|
+
} from '@btc-vision/transaction';
|
|
528
|
+
import { networks } from '@btc-vision/bitcoin';
|
|
529
|
+
|
|
530
|
+
const network = networks.regtest;
|
|
531
|
+
const securityLevel = MLDSASecurityLevel.LEVEL2;
|
|
532
|
+
|
|
533
|
+
// Setup
|
|
534
|
+
const mnemonic = Mnemonic.generate(undefined, undefined, network, securityLevel);
|
|
535
|
+
|
|
536
|
+
const wallet = mnemonic.derive(0);
|
|
537
|
+
|
|
538
|
+
// 1. Sign with ML-DSA (Quantum-resistant)
|
|
539
|
+
console.log('=== ML-DSA Signing ===');
|
|
540
|
+
const quantumMessage = 'Quantum-resistant message';
|
|
541
|
+
const quantumSigned = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, quantumMessage);
|
|
542
|
+
|
|
543
|
+
console.log('Message:', quantumSigned.message);
|
|
544
|
+
console.log('Signature Size:', quantumSigned.signature.length, 'bytes');
|
|
545
|
+
console.log('Public Key Size:', quantumSigned.publicKey.length, 'bytes');
|
|
546
|
+
console.log('Security Level:', quantumSigned.securityLevel);
|
|
547
|
+
|
|
548
|
+
const keypair = QuantumBIP32Factory.fromPublicKey(
|
|
549
|
+
quantumSigned.publicKey,
|
|
550
|
+
wallet.chainCode,
|
|
551
|
+
network,
|
|
552
|
+
securityLevel,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
// Verify ML-DSA
|
|
556
|
+
const quantumValid = MessageSigner.verifyMLDSASignature(
|
|
557
|
+
keypair,
|
|
558
|
+
quantumMessage,
|
|
559
|
+
quantumSigned.signature,
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
console.log('ML-DSA Valid:', quantumValid);
|
|
563
|
+
|
|
564
|
+
// 2. Sign with Schnorr (Classical)
|
|
565
|
+
console.log('\n=== Schnorr Signing ===');
|
|
566
|
+
const classicalMessage = 'Classical signature';
|
|
567
|
+
const classicalSigned = MessageSigner.signMessage(wallet.keypair, classicalMessage);
|
|
568
|
+
|
|
569
|
+
console.log('Message:', classicalSigned.message);
|
|
570
|
+
console.log('Signature Size:', classicalSigned.signature.length, 'bytes');
|
|
571
|
+
|
|
572
|
+
// Verify Schnorr
|
|
573
|
+
const classicalValid = MessageSigner.verifySignature(
|
|
574
|
+
wallet.keypair.publicKey,
|
|
575
|
+
classicalMessage,
|
|
576
|
+
classicalSigned.signature,
|
|
577
|
+
);
|
|
578
|
+
console.log('Schnorr Valid:', classicalValid);
|
|
579
|
+
|
|
580
|
+
// 3. Multiple Input Formats
|
|
581
|
+
console.log('\n=== Input Format Tests ===');
|
|
582
|
+
|
|
583
|
+
const testMessage = 'Format test';
|
|
584
|
+
|
|
585
|
+
// String
|
|
586
|
+
const sig1 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, testMessage);
|
|
587
|
+
|
|
588
|
+
// Buffer
|
|
589
|
+
const sig2 = MessageSigner.signMLDSAMessage(wallet.mldsaKeypair, Buffer.from(testMessage, 'utf-8'));
|
|
590
|
+
|
|
591
|
+
// Uint8Array
|
|
592
|
+
const sig3 = MessageSigner.signMLDSAMessage(
|
|
593
|
+
wallet.mldsaKeypair,
|
|
594
|
+
new Uint8Array(Buffer.from(testMessage, 'utf-8')),
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
// All verify successfully
|
|
598
|
+
console.log(
|
|
599
|
+
'String format valid:',
|
|
600
|
+
MessageSigner.verifyMLDSASignature(wallet.mldsaKeypair, testMessage, sig1.signature),
|
|
601
|
+
);
|
|
602
|
+
console.log(
|
|
603
|
+
'Buffer format valid:',
|
|
604
|
+
MessageSigner.verifyMLDSASignature(
|
|
605
|
+
wallet.mldsaKeypair,
|
|
606
|
+
Buffer.from(testMessage),
|
|
607
|
+
sig2.signature,
|
|
608
|
+
),
|
|
609
|
+
);
|
|
610
|
+
console.log(
|
|
611
|
+
'Uint8Array format valid:',
|
|
612
|
+
MessageSigner.verifyMLDSASignature(
|
|
613
|
+
wallet.mldsaKeypair,
|
|
614
|
+
new Uint8Array(Buffer.from(testMessage)),
|
|
615
|
+
sig3.signature,
|
|
616
|
+
),
|
|
617
|
+
);
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Next Steps
|
|
621
|
+
|
|
622
|
+
- [Address Verification](./05-address-verification.md) - Validate addresses and public keys
|
|
623
|
+
- [Introduction](./01-introduction.md) - Back to overview
|