amp-rust 0.0.9

A Rust client for the Blockstream AMP API, providing interfaces for asset management, user operations, and token handling on the Liquid Network.
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
# Signer Module Documentation

## ⚠️ CRITICAL SECURITY WARNING ⚠️

**THIS MODULE IS FOR TESTNET/REGTEST ONLY**

The signer implementations in this module store mnemonic phrases in **PLAIN TEXT** and are designed exclusively for development and testing environments. **NEVER** use these signers in production or with real funds.

## Overview

The signer module provides transaction signing capabilities for Elements/Liquid transactions using Blockstream's Liquid Wallet Kit (LWK). It supports:

- Software-based signing with mnemonic phrases
- Persistent mnemonic storage in JSON format
- Multiple signers with indexed access for test isolation
- Full BIP39 mnemonic validation and generation
- Async transaction signing interface

## JSON File Format

### File Location
- **Filename**: `mnemonic.local.json`
- **Location**: Current working directory
- **Encoding**: UTF-8 JSON

### File Structure

```json
{
  "mnemonic": [
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
    "legal winner thank year wave sausage worth useful legal winner thank yellow",
    "additional mnemonics as needed for test scenarios..."
  ]
}
```

### Field Specifications

#### `mnemonic` (Array of Strings)
- **Type**: Array of BIP39 mnemonic phrases
- **Format**: Space-separated lowercase English words
- **Word Count**: 12, 15, 18, 21, or 24 words (BIP39 standard)
- **Validation**: Full BIP39 checksum validation on load
- **Indexing**: Zero-based array indexing for consistent access

### File Operations

#### Automatic Creation
- File is created automatically when first mnemonic is generated
- Missing file is handled gracefully (returns empty storage)
- Empty file is treated as empty storage

#### Atomic Updates
- Updates use temporary file + rename for atomic writes
- Prevents corruption during concurrent access
- Maintains data integrity during system failures

#### Error Handling
- Invalid JSON structure returns `SignerError::Serialization`
- File I/O errors return `SignerError::FileIo`
- Mnemonic validation errors return `SignerError::InvalidMnemonic`

## Usage Examples

### Basic Signer Creation

```rust
use amp_rs::signer::{Signer, LwkSoftwareSigner};

// From existing mnemonic
let signer = LwkSoftwareSigner::new(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)?;

// Generate new or load from file
let (mnemonic, signer) = LwkSoftwareSigner::generate_new()?;
```

### Integration with Asset Operations

The signer integrates seamlessly with asset operation functions through the `Signer` trait:

```rust
use amp_rs::signer::{Signer, LwkSoftwareSigner, SignerError};
use amp_rs::client::ApiClient;

/// Example asset reissuance with integrated signing
async fn reissue_asset_with_signer(
    client: &ApiClient,
    signer: &dyn Signer,
    asset_id: &str,
    amount: u64,
) -> Result<String, Box<dyn std::error::Error>> {
    // 1. Create reissuance request through AMP API
    let reissuance_request = client.create_reissuance_request(asset_id, amount).await?;
    
    // 2. Get unsigned transaction from the response
    let unsigned_tx = reissuance_request.unsigned_transaction;
    
    // 3. Sign the transaction using the signer
    let signed_tx = signer.sign_transaction(&unsigned_tx).await?;
    
    // 4. Submit signed transaction back to AMP
    let result = client.submit_signed_transaction(&signed_tx).await?;
    
    Ok(result.transaction_id)
}

/// Example asset distribution with multiple signers
async fn distribute_asset_multi_signer(
    client: &ApiClient,
    issuer_signer: &dyn Signer,
    distributor_signer: &dyn Signer,
    asset_id: &str,
    recipients: Vec<(String, u64)>, // (address, amount) pairs
) -> Result<String, Box<dyn std::error::Error>> {
    // 1. Create distribution request (requires issuer signature)
    let distribution_request = client.create_distribution_request(asset_id, recipients).await?;
    
    // 2. Sign with issuer signer first
    let issuer_signed_tx = issuer_signer.sign_transaction(&distribution_request.unsigned_transaction).await?;
    
    // 3. If multi-sig required, sign with distributor
    let final_signed_tx = distributor_signer.sign_transaction(&issuer_signed_tx).await?;
    
    // 4. Submit final signed transaction
    let result = client.submit_signed_transaction(&final_signed_tx).await?;
    
    Ok(result.transaction_id)
}

/// Example asset burning with validation
async fn burn_asset_with_validation(
    client: &ApiClient,
    signer: &dyn Signer,
    asset_id: &str,
    amount: u64,
) -> Result<String, SignerError> {
    // 1. Validate signer is configured for testnet
    if !signer.is_testnet() {
        return Err(SignerError::Network("Signer not configured for testnet".to_string()));
    }
    
    // 2. Create burn request
    let burn_request = client.create_burn_request(asset_id, amount).await
        .map_err(|e| SignerError::Lwk(format!("API request failed: {}", e)))?;
    
    // 3. Sign and submit
    let signed_tx = signer.sign_transaction(&burn_request.unsigned_transaction).await?;
    let result = client.submit_signed_transaction(&signed_tx).await
        .map_err(|e| SignerError::Lwk(format!("Transaction submission failed: {}", e)))?;
    
    Ok(result.transaction_id)
}
```

### Indexed Mnemonic Access

```rust
use amp_rs::signer::LwkSoftwareSigner;

// Get specific mnemonic by index (generates if needed)
let (mnemonic_0, signer_0) = LwkSoftwareSigner::generate_new_indexed(0)?;
let (mnemonic_1, signer_1) = LwkSoftwareSigner::generate_new_indexed(1)?;
let (mnemonic_5, signer_5) = LwkSoftwareSigner::generate_new_indexed(5)?;

// This creates mnemonics at indices 0, 1, 2, 3, 4, 5
// Indices 2, 3, 4 are automatically generated
```

### Test Isolation Patterns

The indexed mnemonic system enables consistent test isolation and role-based testing:

```rust
use amp_rs::signer::LwkSoftwareSigner;

/// Test pattern: Role-based signers with consistent indices
#[tokio::test]
async fn test_asset_lifecycle_with_roles() -> Result<(), Box<dyn std::error::Error>> {
    // Use consistent indices for different roles across test runs
    let (_, issuer_signer) = LwkSoftwareSigner::generate_new_indexed(100)?;      // Asset issuer
    let (_, distributor_signer) = LwkSoftwareSigner::generate_new_indexed(101)?; // Asset distributor  
    let (_, user_a_signer) = LwkSoftwareSigner::generate_new_indexed(102)?;      // End user A
    let (_, user_b_signer) = LwkSoftwareSigner::generate_new_indexed(103)?;      // End user B
    
    // Each test run uses the same mnemonics for consistent addresses
    // This enables predictable testing of multi-party asset operations
    
    // Test asset issuance
    let asset_id = issue_asset(&issuer_signer).await?;
    
    // Test asset distribution
    distribute_to_users(&distributor_signer, &asset_id, vec![
        (get_address(&user_a_signer), 1000),
        (get_address(&user_b_signer), 2000),
    ]).await?;
    
    // Test asset transfers between users
    transfer_asset(&user_a_signer, &user_b_signer, &asset_id, 500).await?;
    
    Ok(())
}

/// Test pattern: Isolated test scenarios with unique index ranges
#[tokio::test]
async fn test_concurrent_operations() -> Result<(), Box<dyn std::error::Error>> {
    // Use index range 200-299 for this test to avoid conflicts
    let test_signers: Vec<_> = (200..210)
        .map(|i| LwkSoftwareSigner::generate_new_indexed(i))
        .collect::<Result<Vec<_>, _>>()?;
    
    // Run concurrent operations with isolated signers
    let handles: Vec<_> = test_signers.into_iter().enumerate().map(|(i, (_, signer))| {
        tokio::spawn(async move {
            // Each task has its own signer with unique mnemonic
            perform_asset_operation(signer, format!("test_asset_{}", i)).await
        })
    }).collect();
    
    // Wait for all operations to complete
    for handle in handles {
        handle.await??;
    }
    
    Ok(())
}

/// Test pattern: Multi-environment testing
#[cfg(test)]
mod test_environments {
    use super::*;
    
    /// Test signers for regtest environment (indices 1000-1999)
    pub async fn get_regtest_signer(role: &str) -> Result<LwkSoftwareSigner, SignerError> {
        let index = match role {
            "issuer" => 1000,
            "distributor" => 1001,
            "user_a" => 1002,
            "user_b" => 1003,
            "treasury" => 1004,
            _ => return Err(SignerError::InvalidMnemonic(format!("Unknown role: {}", role))),
        };
        
        let (_, signer) = LwkSoftwareSigner::generate_new_indexed(index)?;
        Ok(signer)
    }
    
    /// Test signers for liquid testnet environment (indices 2000-2999)
    pub async fn get_testnet_signer(role: &str) -> Result<LwkSoftwareSigner, SignerError> {
        let index = match role {
            "issuer" => 2000,
            "distributor" => 2001,
            "user_a" => 2002,
            "user_b" => 2003,
            "treasury" => 2004,
            _ => return Err(SignerError::InvalidMnemonic(format!("Unknown role: {}", role))),
        };
        
        let (_, signer) = LwkSoftwareSigner::generate_new_indexed(index)?;
        Ok(signer)
    }
}
```

### Transaction Signing

```rust
use amp_rs::signer::{Signer, LwkSoftwareSigner};

let (_, signer) = LwkSoftwareSigner::generate_new()?;
let unsigned_tx = "020000000001..."; // Your unsigned transaction hex
let signed_tx = signer.sign_transaction(unsigned_tx).await?;
```

### Error Handling

```rust
use amp_rs::signer::{SignerError, LwkSoftwareSigner};

match LwkSoftwareSigner::new("invalid mnemonic") {
    Ok(signer) => { /* Use signer */ },
    Err(SignerError::InvalidMnemonic(msg)) => {
        eprintln!("Invalid mnemonic: {}", msg);
    },
    Err(e) => {
        eprintln!("Other error: {}", e);
    }
}
```

## Mnemonic Management

### Generation
- Uses cryptographically secure randomness (`OsRng`)
- Generates 12-word mnemonics by default
- Full BIP39 compliance with checksum validation
- English wordlist only

### Validation
- Word count validation (12, 15, 18, 21, or 24 words)
- Character validation (lowercase letters only)
- BIP39 checksum validation
- Format validation (no multiple spaces, empty words)

### Storage
- Automatic persistence to `mnemonic.local.json`
- Array-based storage for multiple mnemonics
- Indexed access for consistent test identification
- Atomic file updates to prevent corruption

## Network Configuration

All signers are configured for **testnet/regtest only**:
- `is_testnet()` always returns `true`
- Compatible with Elements regtest and Liquid testnet
- Supports confidential transactions and Liquid features
- **Never** configured for mainnet (security restriction)

## Thread Safety

The signer is fully thread-safe:
- Implements `Send + Sync` traits
- Safe for concurrent signing operations
- No internal mutable state after creation
- Can be shared across async tasks

## Security Considerations

### Development Only
- Plain text mnemonic storage
- Unencrypted private keys in memory
- No password protection
- No hardware security features

### Production Alternatives
For production use, consider:
- **Hardware Wallets**: Ledger, Trezor
- **Encrypted Storage**: Key derivation with passwords
- **Remote Signing**: HSM-backed signing services
- **Multi-signature**: Distributed key management

### Best Practices
- Use only in isolated test environments
- Never commit `mnemonic.local.json` to version control
- Regularly rotate test mnemonics
- Use different mnemonics for different test scenarios
- Monitor file permissions on mnemonic storage

## Error Reference

### `SignerError::InvalidMnemonic`
- Invalid word count
- Invalid characters or formatting
- BIP39 checksum validation failure
- Empty or malformed mnemonic

### `SignerError::Lwk`
- SwSigner creation failure
- Transaction signing failure
- PSET operation errors

### `SignerError::HexParse`
- Invalid hex characters
- Odd-length hex strings
- Empty hex input

### `SignerError::InvalidTransaction`
- Malformed transaction structure
- Missing inputs or outputs
- Transaction deserialization failure

### `SignerError::FileIo`
- File read/write errors
- Permission denied
- Disk space issues

### `SignerError::Serialization`
- JSON parsing errors
- Invalid file structure
- Serialization failures

## Testing

Run the signer usage example:

```bash
cargo run --example signer_usage
```

This example demonstrates:
- Signer creation patterns
- Mnemonic management
- Error handling
- Multi-signer scenarios
- JSON file operations

## File Management

### Backup
```bash
# Backup your test mnemonics
cp mnemonic.local.json mnemonic.backup.json
```

### Reset
```bash
# Start fresh (removes all test mnemonics)
rm mnemonic.local.json
```

### Inspect
```bash
# View current mnemonics
cat mnemonic.local.json | jq .
```

Remember: These are test mnemonics only. Never use with real funds!