blvm-sdk 0.1.9

Bitcoin Commons software developer kit, governance infrastructure and composition framework for Bitcoin
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
//! PSBT (Partially Signed Bitcoin Transaction) Tests
//!
//! Tests for BIP174 PSBT format implementation.
//! Specification: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki

use blvm_sdk::governance::psbt::{
    Bip32Derivation, PartialSignature, PartiallySignedTransaction, PsbtGlobalKey, PsbtInputKey,
    PsbtOutputKey, SighashType, PSBT_MAGIC, PSBT_SEPARATOR,
};

/// Test helper: Create a minimal unsigned transaction (mock)
fn create_mock_unsigned_tx() -> Vec<u8> {
    // Minimal transaction structure (version + input count + output count + locktime)
    vec![
        0x01, 0x00, 0x00, 0x00, // version
        0x01, // input count (1)
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, // prevout hash
        0xff, 0xff, 0xff, 0xff, // prevout index
        0x00, // script length
        0xff, 0xff, 0xff, 0xff, // sequence
        0x01, // output count (1)
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value
        0x00, // script length
        0x00, 0x00, 0x00, 0x00, // locktime
    ]
}

// ============================================================================
// Phase 1: PSBT Creation Tests
// ============================================================================

#[test]
fn test_psbt_creation() {
    // Test creating a PSBT from unsigned transaction
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Should have version 0
    assert_eq!(psbt.version, 0);

    // Should have unsigned transaction in global map
    assert!(psbt
        .global
        .contains_key(&vec![PsbtGlobalKey::UnsignedTx as u8]));
}

#[test]
fn test_psbt_version() {
    // Test PSBT version is set correctly
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    assert_eq!(psbt.version, 0);
    assert!(psbt
        .global
        .contains_key(&vec![PsbtGlobalKey::Version as u8]));
}

#[test]
fn test_psbt_magic_bytes() {
    // Test PSBT magic bytes constant
    assert_eq!(PSBT_MAGIC, [0x70, 0x73, 0x62, 0x74]); // "psbt"
}

#[test]
fn test_psbt_separator() {
    // Test PSBT separator constant
    assert_eq!(PSBT_SEPARATOR, 0xff);
}

// ============================================================================
// Phase 2: PSBT Input/Output Map Tests
// ============================================================================

#[test]
fn test_psbt_add_input() {
    // Test adding input to PSBT
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Add input map
    psbt.inputs.push(std::collections::HashMap::new());

    assert_eq!(psbt.inputs.len(), 1);
}

#[test]
fn test_psbt_add_output() {
    // Test adding output to PSBT
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Add output map
    psbt.outputs.push(std::collections::HashMap::new());

    assert_eq!(psbt.outputs.len(), 1);
}

#[test]
fn test_psbt_multiple_inputs_outputs() {
    // Test PSBT with multiple inputs and outputs
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Add multiple inputs
    psbt.inputs.push(std::collections::HashMap::new());
    psbt.inputs.push(std::collections::HashMap::new());

    // Add multiple outputs
    psbt.outputs.push(std::collections::HashMap::new());
    psbt.outputs.push(std::collections::HashMap::new());

    assert_eq!(psbt.inputs.len(), 2);
    assert_eq!(psbt.outputs.len(), 2);
}

// ============================================================================
// Phase 3: PSBT Key Type Tests
// ============================================================================

#[test]
fn test_psbt_global_key_types() {
    // Test PSBT global key types
    assert_eq!(PsbtGlobalKey::UnsignedTx as u8, 0x00);
    assert_eq!(PsbtGlobalKey::Xpub as u8, 0x01);
    assert_eq!(PsbtGlobalKey::Version as u8, 0xfb);
    assert_eq!(PsbtGlobalKey::Proprietary as u8, 0xfc);
}

#[test]
fn test_psbt_input_key_types() {
    // Test PSBT input key types
    assert_eq!(PsbtInputKey::NonWitnessUtxo as u8, 0x00);
    assert_eq!(PsbtInputKey::WitnessUtxo as u8, 0x01);
    assert_eq!(PsbtInputKey::PartialSig as u8, 0x02);
    assert_eq!(PsbtInputKey::SighashType as u8, 0x03);
    assert_eq!(PsbtInputKey::RedeemScript as u8, 0x04);
    assert_eq!(PsbtInputKey::WitnessScript as u8, 0x05);
    assert_eq!(PsbtInputKey::Bip32Derivation as u8, 0x06);
    assert_eq!(PsbtInputKey::FinalScriptSig as u8, 0x07);
    assert_eq!(PsbtInputKey::FinalScriptWitness as u8, 0x08);
}

#[test]
fn test_psbt_output_key_types() {
    // Test PSBT output key types
    assert_eq!(PsbtOutputKey::RedeemScript as u8, 0x00);
    assert_eq!(PsbtOutputKey::WitnessScript as u8, 0x01);
    assert_eq!(PsbtOutputKey::Bip32Derivation as u8, 0x02);
}

// ============================================================================
// Phase 4: Sighash Type Tests
// ============================================================================

#[test]
fn test_sighash_type_all() {
    // Test SIGHASH_ALL
    let sighash = SighashType::All;
    assert_eq!(sighash.to_byte(), 0x01);

    let parsed = SighashType::from_byte(0x01);
    assert_eq!(parsed, Some(SighashType::All));
}

#[test]
fn test_sighash_type_none() {
    // Test SIGHASH_NONE
    let sighash = SighashType::None;
    assert_eq!(sighash.to_byte(), 0x02);

    let parsed = SighashType::from_byte(0x02);
    assert_eq!(parsed, Some(SighashType::None));
}

#[test]
fn test_sighash_type_single() {
    // Test SIGHASH_SINGLE
    let sighash = SighashType::Single;
    assert_eq!(sighash.to_byte(), 0x03);

    let parsed = SighashType::from_byte(0x03);
    assert_eq!(parsed, Some(SighashType::Single));
}

#[test]
fn test_sighash_type_anyonecanpay() {
    // Test SIGHASH_ANYONECANPAY variants
    assert_eq!(SighashType::AllAnyoneCanPay.to_byte(), 0x81);
    assert_eq!(SighashType::NoneAnyoneCanPay.to_byte(), 0x82);
    assert_eq!(SighashType::SingleAnyoneCanPay.to_byte(), 0x83);

    assert_eq!(
        SighashType::from_byte(0x81),
        Some(SighashType::AllAnyoneCanPay)
    );
    assert_eq!(
        SighashType::from_byte(0x82),
        Some(SighashType::NoneAnyoneCanPay)
    );
    assert_eq!(
        SighashType::from_byte(0x83),
        Some(SighashType::SingleAnyoneCanPay)
    );
}

#[test]
fn test_sighash_type_invalid() {
    // Test invalid sighash type
    let parsed = SighashType::from_byte(0xff);
    assert_eq!(parsed, None);
}

// ============================================================================
// Phase 5: BIP32 Derivation Tests
// ============================================================================

#[test]
fn test_bip32_derivation_creation() {
    // Test creating BIP32 derivation entry
    let derivation = Bip32Derivation {
        pubkey: vec![0x02; 33],                         // Compressed public key
        path: vec![0x80000000, 0x80000001, 0x80000002], // Hardened path
        master_fingerprint: [0x12, 0x34, 0x56, 0x78],
    };

    assert_eq!(derivation.pubkey.len(), 33);
    assert_eq!(derivation.path.len(), 3);
    assert_eq!(derivation.master_fingerprint, [0x12, 0x34, 0x56, 0x78]);
}

#[test]
fn test_bip32_derivation_path() {
    // Test BIP32 derivation path
    let derivation = Bip32Derivation {
        pubkey: vec![0x02; 33],
        path: vec![
            0x8000002c, // 44' (purpose)
            0x80000000, // 0' (coin type)
            0x80000000, // 0' (account)
            0x00000000, // 0 (change)
            0x00000000, // 0 (address index)
        ],
        master_fingerprint: [0x12, 0x34, 0x56, 0x78],
    };

    // Verify path structure
    assert_eq!(derivation.path[0], 0x8000002c); // 44' hardened
    assert_eq!(derivation.path[1], 0x80000000); // 0' hardened
}

// ============================================================================
// Phase 6: Partial Signature Tests
// ============================================================================

#[test]
fn test_partial_signature_creation() {
    // Test creating partial signature entry
    let partial_sig = PartialSignature {
        pubkey: vec![0x02; 33],                  // Public key
        signature: vec![0x30, 0x45, 0x02, 0x21], // Mock signature bytes
    };

    assert_eq!(partial_sig.pubkey.len(), 33);
    assert!(!partial_sig.signature.is_empty());
}

#[test]
fn test_partial_signature_structure() {
    // Test partial signature structure
    let pubkey = vec![0x03; 33];
    let signature = vec![0x30, 0x44, 0x02, 0x20, 0x01, 0x02, 0x03];

    let partial_sig = PartialSignature {
        pubkey: pubkey.clone(),
        signature: signature.clone(),
    };

    assert_eq!(partial_sig.pubkey, pubkey);
    assert_eq!(partial_sig.signature, signature);
}

// ============================================================================
// Phase 7: PSBT Global Map Tests
// ============================================================================

#[test]
fn test_psbt_global_map_unsigned_tx() {
    // Test unsigned transaction in global map
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let key = vec![PsbtGlobalKey::UnsignedTx as u8];
    assert!(psbt.global.contains_key(&key));

    let tx_data = psbt.global.get(&key).unwrap();
    assert_eq!(tx_data, &unsigned_tx);
}

#[test]
fn test_psbt_global_map_version() {
    // Test version in global map
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let key = vec![PsbtGlobalKey::Version as u8];
    assert!(psbt.global.contains_key(&key));

    let version_data = psbt.global.get(&key).unwrap();
    assert_eq!(version_data, &vec![0x00]); // Version 0
}

// ============================================================================
// Phase 8: PSBT Input Map Tests
// ============================================================================

#[test]
fn test_psbt_input_map_witness_utxo() {
    // Test adding witness UTXO to input map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut input_map = std::collections::HashMap::new();
    let witness_utxo = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // Mock UTXO
    input_map.insert(vec![PsbtInputKey::WitnessUtxo as u8], witness_utxo.clone());

    psbt.inputs.push(input_map);

    assert!(psbt.inputs[0].contains_key(&vec![PsbtInputKey::WitnessUtxo as u8]));
}

#[test]
fn test_psbt_input_map_partial_sig() {
    // Test adding partial signature to input map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut input_map = std::collections::HashMap::new();
    let partial_sig_data = vec![0x30, 0x45, 0x02, 0x21]; // Mock signature
    input_map.insert(
        vec![PsbtInputKey::PartialSig as u8],
        partial_sig_data.clone(),
    );

    psbt.inputs.push(input_map);

    assert!(psbt.inputs[0].contains_key(&vec![PsbtInputKey::PartialSig as u8]));
}

#[test]
fn test_psbt_input_map_sighash_type() {
    // Test adding sighash type to input map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut input_map = std::collections::HashMap::new();
    let sighash_byte = vec![SighashType::All.to_byte()];
    input_map.insert(vec![PsbtInputKey::SighashType as u8], sighash_byte.clone());

    psbt.inputs.push(input_map);

    assert!(psbt.inputs[0].contains_key(&vec![PsbtInputKey::SighashType as u8]));
}

#[test]
fn test_psbt_input_map_bip32_derivation() {
    // Test adding BIP32 derivation to input map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut input_map = std::collections::HashMap::new();
    let derivation_data = vec![0x02; 33]; // Mock derivation data
    input_map.insert(
        vec![PsbtInputKey::Bip32Derivation as u8],
        derivation_data.clone(),
    );

    psbt.inputs.push(input_map);

    assert!(psbt.inputs[0].contains_key(&vec![PsbtInputKey::Bip32Derivation as u8]));
}

// ============================================================================
// Phase 9: PSBT Output Map Tests
// ============================================================================

#[test]
fn test_psbt_output_map_redeem_script() {
    // Test adding redeem script to output map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut output_map = std::collections::HashMap::new();
    let redeem_script = vec![0x76, 0xa9, 0x14]; // Mock redeem script
    output_map.insert(
        vec![PsbtOutputKey::RedeemScript as u8],
        redeem_script.clone(),
    );

    psbt.outputs.push(output_map);

    assert!(psbt.outputs[0].contains_key(&vec![PsbtOutputKey::RedeemScript as u8]));
}

#[test]
fn test_psbt_output_map_witness_script() {
    // Test adding witness script to output map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut output_map = std::collections::HashMap::new();
    let witness_script = vec![0x00, 0x14]; // Mock witness script
    output_map.insert(
        vec![PsbtOutputKey::WitnessScript as u8],
        witness_script.clone(),
    );

    psbt.outputs.push(output_map);

    assert!(psbt.outputs[0].contains_key(&vec![PsbtOutputKey::WitnessScript as u8]));
}

#[test]
fn test_psbt_output_map_bip32_derivation() {
    // Test adding BIP32 derivation to output map
    let unsigned_tx = create_mock_unsigned_tx();
    let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    let mut output_map = std::collections::HashMap::new();
    let derivation_data = vec![0x02; 33]; // Mock derivation data
    output_map.insert(
        vec![PsbtOutputKey::Bip32Derivation as u8],
        derivation_data.clone(),
    );

    psbt.outputs.push(output_map);

    assert!(psbt.outputs[0].contains_key(&vec![PsbtOutputKey::Bip32Derivation as u8]));
}

// ============================================================================
// Phase 10: PSBT Validation Tests
// ============================================================================

#[test]
fn test_psbt_has_unsigned_tx() {
    // Test that PSBT has unsigned transaction
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Should have unsigned transaction
    let key = vec![PsbtGlobalKey::UnsignedTx as u8];
    assert!(psbt.global.contains_key(&key));
}

#[test]
fn test_psbt_empty_inputs_outputs() {
    // Test PSBT with empty inputs/outputs (valid for creation)
    let unsigned_tx = create_mock_unsigned_tx();
    let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();

    // Initially empty
    assert_eq!(psbt.inputs.len(), 0);
    assert_eq!(psbt.outputs.len(), 0);
}