@bennyblader/ddk-rn 0.3.42 → 0.4.0

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.
Files changed (46) hide show
  1. package/DdkRn.podspec +1 -1
  2. package/README.md +14 -0
  3. package/android/CMakeLists.txt +6 -3
  4. package/android/build.gradle +0 -1
  5. package/android/cpp-adapter.cpp +6 -26
  6. package/android/src/main/jniLibs/arm64-v8a/libddk_ffi.so +0 -0
  7. package/android/src/main/jniLibs/armeabi-v7a/libddk_ffi.so +0 -0
  8. package/android/src/main/jniLibs/x86/libddk_ffi.so +0 -0
  9. package/android/src/main/jniLibs/x86_64/libddk_ffi.so +0 -0
  10. package/cpp/bennyblader-ddk-rn.cpp +1 -1
  11. package/cpp/ddk_ffi.cpp +2361 -2671
  12. package/cpp/ddk_ffi.hpp +86 -255
  13. package/ios/DdkRn.xcframework/Info.plist +43 -0
  14. package/ios/DdkRn.xcframework/ios-arm64/libddk_ffi.a +0 -0
  15. package/ios/DdkRn.xcframework/ios-arm64-simulator/libddk_ffi.a +0 -0
  16. package/lib/commonjs/ddk_ffi-ffi.js +6 -29
  17. package/lib/commonjs/ddk_ffi-ffi.js.map +1 -1
  18. package/lib/commonjs/ddk_ffi.js +961 -944
  19. package/lib/commonjs/ddk_ffi.js.map +1 -1
  20. package/lib/module/ddk_ffi-ffi.js +6 -29
  21. package/lib/module/ddk_ffi-ffi.js.map +1 -1
  22. package/lib/module/ddk_ffi.js +924 -896
  23. package/lib/module/ddk_ffi.js.map +1 -1
  24. package/lib/typescript/commonjs/src/ddk_ffi-ffi.d.ts +71 -96
  25. package/lib/typescript/commonjs/src/ddk_ffi-ffi.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/src/ddk_ffi.d.ts +500 -471
  27. package/lib/typescript/commonjs/src/ddk_ffi.d.ts.map +1 -1
  28. package/lib/typescript/module/src/ddk_ffi-ffi.d.ts +71 -96
  29. package/lib/typescript/module/src/ddk_ffi-ffi.d.ts.map +1 -1
  30. package/lib/typescript/module/src/ddk_ffi.d.ts +500 -471
  31. package/lib/typescript/module/src/ddk_ffi.d.ts.map +1 -1
  32. package/package.json +8 -38
  33. package/src/ddk_ffi-ffi.ts +419 -170
  34. package/src/ddk_ffi.ts +2648 -1939
  35. package/ubrn.config.yaml +18 -1
  36. package/ddk-ffi/Cargo.lock +0 -1013
  37. package/ddk-ffi/Cargo.toml +0 -36
  38. package/ddk-ffi/build.rs +0 -3
  39. package/ddk-ffi/rust-toolchain.toml +0 -10
  40. package/ddk-ffi/src/ddk_ffi.udl +0 -376
  41. package/ddk-ffi/src/lib.rs +0 -2509
  42. package/ddk-ffi/src/uniffi-bindgen.rs +0 -3
  43. package/ddk-ffi/uniffi.toml +0 -18
  44. package/scripts/apply-hotfix.js +0 -96
  45. package/scripts/postinstall.js +0 -298
  46. package/scripts/prepare-rust-src.js +0 -51
@@ -1,2509 +0,0 @@
1
- #![allow(clippy::too_many_arguments)]
2
- #![allow(deprecated)]
3
- use bip39::{Language, Mnemonic};
4
- use bitcoin::bip32::{IntoDerivationPath, Xpriv, Xpub};
5
- use bitcoin::hashes::Hash;
6
- use bitcoin::sighash::EcdsaSighashType;
7
- use bitcoin::{
8
- Amount, Network, OutPoint, Psbt, ScriptBuf, Sequence, Transaction as BtcTransaction, TxIn,
9
- TxOut as BtcTxOut, Txid, Witness,
10
- };
11
- use bitcoin::{Script, WPubkeyHash};
12
- use ddk_dlc::secp_utils;
13
- use ddk_dlc::{
14
- self, dlc_input::DlcInputInfo as RustDlcInputInfo, DlcTransactions as RustDlcTransactions,
15
- OracleInfo as DlcOracleInfo, PartyParams as DlcPartyParams, Payout as DlcPayout,
16
- TxInputInfo as DlcTxInputInfo,
17
- };
18
- use secp256k1_zkp::{
19
- ecdsa::Signature as EcdsaSignature, Message, PublicKey, Scalar, Secp256k1, SecretKey,
20
- XOnlyPublicKey,
21
- };
22
- use secp256k1_zkp::{schnorr::Signature as SchnorrSignature, All, EcdsaAdaptorSignature};
23
- use std::str::FromStr;
24
- use std::sync::OnceLock;
25
-
26
- uniffi::include_scaffolding!("ddk_ffi");
27
-
28
- static SECP_CONTEXT: OnceLock<Secp256k1<All>> = OnceLock::new();
29
-
30
- pub fn get_secp_context() -> &'static Secp256k1<All> {
31
- SECP_CONTEXT.get_or_init(Secp256k1::new)
32
- }
33
-
34
- pub fn version() -> String {
35
- env!("CARGO_PKG_VERSION").to_string()
36
- }
37
-
38
- /// Minimum value that can be included in a transaction output. Under this value,
39
- /// outputs are discarded
40
- /// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#change-outputs
41
- const DUST_LIMIT: u64 = 1000;
42
-
43
- /// The witness size of a P2WPKH input
44
- /// See: <https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#fees>
45
- pub const P2WPKH_WITNESS_SIZE: usize = 107;
46
-
47
- // Error type implementation
48
- #[derive(Debug, thiserror::Error)]
49
- pub enum DLCError {
50
- #[error("Invalid signature")]
51
- InvalidSignature,
52
- #[error("Invalid public key")]
53
- InvalidPublicKey,
54
- #[error("Invalid transaction")]
55
- InvalidTransaction,
56
- #[error("Insufficient funds")]
57
- InsufficientFunds,
58
- #[error("Invalid argument: {0}")]
59
- InvalidArgument(String),
60
- #[error("Serialization error")]
61
- SerializationError,
62
- #[error("Secp256k1 error: {0}")]
63
- Secp256k1Error(String),
64
- #[error("Miniscript error")]
65
- MiniscriptError,
66
- #[error("Invalid network")]
67
- InvalidNetwork,
68
- #[error("Extended key error: {0}")]
69
- KeyError(ExtendedKey),
70
- }
71
-
72
- #[derive(Debug, thiserror::Error)]
73
- pub enum ExtendedKey {
74
- #[error("Invalid mnemonic")]
75
- InvalidMnemonic,
76
- #[error("Invalid xpriv")]
77
- InvalidXpriv,
78
- #[error("Invalid xpub")]
79
- InvalidXpub,
80
- #[error("Invalid derivation path")]
81
- InvalidDerivationPath,
82
- }
83
-
84
- impl From<ddk_dlc::Error> for DLCError {
85
- fn from(err: ddk_dlc::Error) -> Self {
86
- match err {
87
- ddk_dlc::Error::Secp256k1(_) => DLCError::Secp256k1Error(err.to_string()),
88
- ddk_dlc::Error::InvalidArgument(msg) => DLCError::InvalidArgument(msg),
89
- ddk_dlc::Error::Miniscript(_) => DLCError::MiniscriptError,
90
- ddk_dlc::Error::P2wpkh(_) => DLCError::InvalidTransaction,
91
- ddk_dlc::Error::InputsIndex(_) => {
92
- DLCError::InvalidArgument("Error from rust-dlc: InputsIndex".to_string())
93
- }
94
- }
95
- }
96
- }
97
-
98
- impl From<secp256k1_zkp::Error> for DLCError {
99
- fn from(err: secp256k1_zkp::Error) -> Self {
100
- DLCError::Secp256k1Error(err.to_string())
101
- }
102
- }
103
-
104
- // UniFFI struct definitions (as defined in UDL)
105
- #[derive(Clone)]
106
- pub struct Transaction {
107
- pub version: i32,
108
- pub lock_time: u32,
109
- pub inputs: Vec<TxInput>,
110
- pub outputs: Vec<TxOutput>,
111
- pub raw_bytes: Vec<u8>,
112
- }
113
-
114
- #[derive(Clone)]
115
- pub struct TxInput {
116
- pub txid: String,
117
- pub vout: u32,
118
- pub script_sig: Vec<u8>,
119
- pub sequence: u32,
120
- pub witness: Vec<Vec<u8>>,
121
- }
122
-
123
- #[derive(Clone)]
124
- pub struct TxOutput {
125
- pub value: u64,
126
- pub script_pubkey: Vec<u8>,
127
- }
128
-
129
- #[derive(Clone)]
130
- pub struct TxInputInfo {
131
- pub txid: String,
132
- pub vout: u32,
133
- pub script_sig: Vec<u8>,
134
- pub max_witness_length: u32,
135
- pub serial_id: u64,
136
- }
137
-
138
- #[derive(Clone)]
139
- pub struct Payout {
140
- pub offer: u64,
141
- pub accept: u64,
142
- }
143
-
144
- #[derive(Clone)]
145
- pub struct DlcInputInfo {
146
- pub fund_tx: Transaction,
147
- pub fund_vout: u32,
148
- pub local_fund_pubkey: Vec<u8>,
149
- pub remote_fund_pubkey: Vec<u8>,
150
- pub fund_amount: u64,
151
- pub max_witness_len: u32,
152
- pub input_serial_id: u64,
153
- pub contract_id: Vec<u8>,
154
- }
155
-
156
- #[derive(Clone)]
157
- pub struct PartyParams {
158
- pub fund_pubkey: Vec<u8>,
159
- pub change_script_pubkey: Vec<u8>,
160
- pub change_serial_id: u64,
161
- pub payout_script_pubkey: Vec<u8>,
162
- pub payout_serial_id: u64,
163
- pub inputs: Vec<TxInputInfo>,
164
- pub input_amount: u64,
165
- pub collateral: u64,
166
- pub dlc_inputs: Vec<DlcInputInfo>,
167
- }
168
-
169
- #[derive(Clone)]
170
- pub struct DlcTransactions {
171
- pub fund: Transaction,
172
- pub cets: Vec<Transaction>,
173
- pub refund: Transaction,
174
- pub funding_script_pubkey: Vec<u8>,
175
- }
176
-
177
- #[derive(Clone)]
178
- pub struct AdaptorSignature {
179
- pub signature: Vec<u8>,
180
- pub proof: Vec<u8>,
181
- }
182
-
183
- #[derive(Clone)]
184
- pub struct ChangeOutputAndFees {
185
- pub change_output: TxOutput,
186
- pub fund_fee: u64,
187
- pub cet_fee: u64,
188
- }
189
-
190
- #[derive(Clone)]
191
- pub struct OracleInfo {
192
- pub public_key: Vec<u8>,
193
- pub nonces: Vec<Vec<u8>>,
194
- }
195
-
196
- /// Debug info for CET adaptor signature inputs.
197
- ///
198
- /// Contains all the values that go into creating an adaptor signature,
199
- /// useful for comparing with external signers during debugging.
200
- ///
201
- /// This struct is intentionally always available (not feature-gated)
202
- /// to support production debugging scenarios.
203
- #[derive(Clone)]
204
- pub struct CetAdaptorSignatureDebugInfo {
205
- /// The sighash (32 bytes) - this is the message that gets signed
206
- pub sighash: Vec<u8>,
207
- /// The adaptor point (33 bytes compressed public key)
208
- pub adaptor_point: Vec<u8>,
209
- /// Input index (always 0 for CETs)
210
- pub input_index: u32,
211
- /// The funding script pubkey used for sighash
212
- pub script_pubkey: Vec<u8>,
213
- /// The fund output value used for sighash
214
- pub value: u64,
215
- /// The CET txid
216
- pub cet_txid: String,
217
- /// Raw CET bytes for verification
218
- pub cet_raw: Vec<u8>,
219
- }
220
-
221
- // Conversion helpers
222
- pub fn btc_tx_to_transaction(tx: &BtcTransaction) -> Transaction {
223
- use bitcoin::consensus::Encodable;
224
- let mut raw_bytes = Vec::new();
225
- tx.consensus_encode(&mut raw_bytes).unwrap();
226
-
227
- Transaction {
228
- version: tx.version.0,
229
- lock_time: tx.lock_time.to_consensus_u32(),
230
- inputs: tx
231
- .input
232
- .iter()
233
- .map(|input| TxInput {
234
- txid: input.previous_output.txid.to_string(),
235
- vout: input.previous_output.vout,
236
- script_sig: input.script_sig.to_bytes(),
237
- sequence: input.sequence.0,
238
- witness: input.witness.iter().map(|w| w.to_vec()).collect(),
239
- })
240
- .collect(),
241
- outputs: tx
242
- .output
243
- .iter()
244
- .map(|output| TxOutput {
245
- value: output.value.to_sat(),
246
- script_pubkey: output.script_pubkey.to_bytes(),
247
- })
248
- .collect(),
249
- raw_bytes,
250
- }
251
- }
252
-
253
- pub fn add_signature_to_transaction(
254
- tx: Transaction,
255
- signature: Vec<u8>,
256
- pubkey: Vec<u8>,
257
- input_index: u32,
258
- ) -> Result<Transaction, DLCError> {
259
- let mut tx = transaction_to_btc_tx(&tx).map_err(|_| DLCError::InvalidTransaction)?;
260
- let mut witness = Witness::new();
261
- witness.push(signature);
262
- witness.push(pubkey);
263
-
264
- tx.input[input_index as usize].witness = witness;
265
-
266
- Ok(btc_tx_to_transaction(&tx))
267
- }
268
-
269
- pub fn plz_work() -> String {
270
- "heyhowareya".to_string()
271
- }
272
-
273
- pub fn transaction_to_btc_tx(tx: &Transaction) -> Result<BtcTransaction, DLCError> {
274
- use bitcoin::consensus::Decodable;
275
- BtcTransaction::consensus_decode(&mut &tx.raw_bytes[..])
276
- .map_err(|_| DLCError::SerializationError)
277
- }
278
-
279
- pub fn dlc_input_info_to_rust(input: &DlcInputInfo) -> Result<RustDlcInputInfo, DLCError> {
280
- let btc_tx = transaction_to_btc_tx(&input.fund_tx)?;
281
- let local_fund_pubkey =
282
- PublicKey::from_slice(&input.local_fund_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
283
- let remote_fund_pubkey =
284
- PublicKey::from_slice(&input.remote_fund_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
285
- let contract_id: [u8; 32] = input.contract_id.as_slice().try_into().map_err(|_| {
286
- DLCError::InvalidArgument("Contract id length must be 32 bytes.".to_string())
287
- })?;
288
- Ok(RustDlcInputInfo {
289
- fund_tx: btc_tx,
290
- fund_vout: input.fund_vout,
291
- local_fund_pubkey,
292
- remote_fund_pubkey,
293
- fund_amount: Amount::from_sat(input.fund_amount),
294
- max_witness_len: input.max_witness_len as usize,
295
- input_serial_id: input.input_serial_id,
296
- contract_id,
297
- })
298
- }
299
-
300
- pub fn rust_to_dlc_input(input: &RustDlcInputInfo) -> Result<DlcInputInfo, DLCError> {
301
- Ok(DlcInputInfo {
302
- fund_tx: btc_tx_to_transaction(&input.fund_tx),
303
- fund_vout: input.fund_vout,
304
- local_fund_pubkey: input.local_fund_pubkey.serialize().to_vec(),
305
- remote_fund_pubkey: input.remote_fund_pubkey.serialize().to_vec(),
306
- fund_amount: input.fund_amount.to_sat(),
307
- max_witness_len: input.max_witness_len as u32,
308
- input_serial_id: input.input_serial_id,
309
- contract_id: input.contract_id.to_vec(),
310
- })
311
- }
312
-
313
- /// Convert UniFFI TxInputInfo to rust-dlc TxInputInfo
314
- pub fn tx_input_info_to_rust(input: &TxInputInfo) -> Result<DlcTxInputInfo, DLCError> {
315
- let txid = Txid::from_str(&input.txid)
316
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
317
- Ok(DlcTxInputInfo {
318
- outpoint: OutPoint {
319
- txid,
320
- vout: input.vout,
321
- },
322
- max_witness_len: input.max_witness_length as usize,
323
- redeem_script: ScriptBuf::from(input.script_sig.clone()),
324
- serial_id: input.serial_id,
325
- })
326
- }
327
-
328
- /// Convert UniFFI PartyParams to rust-dlc PartyParams
329
- pub fn party_params_to_rust(params: &PartyParams) -> Result<DlcPartyParams, DLCError> {
330
- let fund_pubkey =
331
- PublicKey::from_slice(&params.fund_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
332
-
333
- let inputs: Result<Vec<_>, _> = params.inputs.iter().map(tx_input_info_to_rust).collect();
334
-
335
- let dlc_inputs: Result<Vec<_>, _> = params
336
- .dlc_inputs
337
- .iter()
338
- .map(dlc_input_info_to_rust)
339
- .collect();
340
-
341
- Ok(DlcPartyParams {
342
- fund_pubkey,
343
- change_script_pubkey: ScriptBuf::from(params.change_script_pubkey.clone()),
344
- change_serial_id: params.change_serial_id,
345
- payout_script_pubkey: ScriptBuf::from(params.payout_script_pubkey.clone()),
346
- payout_serial_id: params.payout_serial_id,
347
- inputs: inputs?,
348
- dlc_inputs: dlc_inputs?,
349
- input_amount: Amount::from_sat(params.input_amount),
350
- collateral: Amount::from_sat(params.collateral),
351
- })
352
- }
353
-
354
- /// Convert rust-dlc DlcTransactions to UniFFI DlcTransactions
355
- pub fn rust_dlc_transactions_to_uniffi(dlc_txs: RustDlcTransactions) -> DlcTransactions {
356
- DlcTransactions {
357
- fund: btc_tx_to_transaction(&dlc_txs.fund),
358
- cets: dlc_txs.cets.iter().map(btc_tx_to_transaction).collect(),
359
- refund: btc_tx_to_transaction(&dlc_txs.refund),
360
- funding_script_pubkey: dlc_txs.funding_script_pubkey.to_bytes(),
361
- }
362
- }
363
-
364
- /// Create a funding script pubkey for DLC transactions
365
- pub fn create_fund_tx_locking_script(
366
- local_fund_pubkey: Vec<u8>,
367
- remote_fund_pubkey: Vec<u8>,
368
- ) -> Result<Vec<u8>, DLCError> {
369
- let local_pk =
370
- PublicKey::from_slice(&local_fund_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
371
- let remote_pk =
372
- PublicKey::from_slice(&remote_fund_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
373
-
374
- let script = ddk_dlc::make_funding_redeemscript(&local_pk, &remote_pk);
375
- Ok(script.to_bytes())
376
- }
377
-
378
- /// Create complete DLC transactions
379
- pub fn create_dlc_transactions(
380
- outcomes: Vec<Payout>,
381
- local_params: PartyParams,
382
- remote_params: PartyParams,
383
- refund_locktime: u32,
384
- fee_rate: u64,
385
- fund_lock_time: u32,
386
- cet_lock_time: u32,
387
- fund_output_serial_id: u64,
388
- contract_flags: u8,
389
- ) -> Result<DlcTransactions, DLCError> {
390
- // Convert UniFFI types to rust-dlc types
391
- let rust_local_params = party_params_to_rust(&local_params)?;
392
- let rust_remote_params = party_params_to_rust(&remote_params)?;
393
-
394
- // Convert outcomes to payouts
395
- let payouts: Vec<DlcPayout> = outcomes
396
- .iter()
397
- .map(|outcome| DlcPayout {
398
- offer: Amount::from_sat(outcome.offer),
399
- accept: Amount::from_sat(outcome.accept),
400
- })
401
- .collect();
402
-
403
- // Use rust-dlc library to create transactions
404
- let dlc_txs = ddk_dlc::create_dlc_transactions(
405
- &rust_local_params,
406
- &rust_remote_params,
407
- &payouts,
408
- refund_locktime,
409
- fee_rate,
410
- fund_lock_time,
411
- cet_lock_time,
412
- fund_output_serial_id,
413
- contract_flags,
414
- )
415
- .map_err(DLCError::from)?;
416
-
417
- // Convert back to UniFFI types
418
- Ok(rust_dlc_transactions_to_uniffi(dlc_txs))
419
- }
420
-
421
- /// Create spliced DLC transactions
422
- pub fn create_spliced_dlc_transactions(
423
- outcomes: Vec<Payout>,
424
- local_params: PartyParams,
425
- remote_params: PartyParams,
426
- refund_locktime: u32,
427
- fee_rate: u64,
428
- fund_lock_time: u32,
429
- cet_lock_time: u32,
430
- fund_output_serial_id: u64,
431
- contract_flags: u8,
432
- ) -> Result<DlcTransactions, DLCError> {
433
- // Convert UniFFI types to rust-dlc types
434
- let rust_local_params = party_params_to_rust(&local_params)?;
435
- let rust_remote_params = party_params_to_rust(&remote_params)?;
436
-
437
- // Convert outcomes to payouts
438
- let payouts: Vec<DlcPayout> = outcomes
439
- .iter()
440
- .map(|outcome| DlcPayout {
441
- offer: Amount::from_sat(outcome.offer),
442
- accept: Amount::from_sat(outcome.accept),
443
- })
444
- .collect();
445
-
446
- // Use rust-dlc library to create spliced transactions
447
- let dlc_txs = ddk_dlc::create_spliced_dlc_transactions(
448
- &rust_local_params,
449
- &rust_remote_params,
450
- &payouts,
451
- refund_locktime,
452
- fee_rate,
453
- fund_lock_time,
454
- cet_lock_time,
455
- fund_output_serial_id,
456
- contract_flags,
457
- )
458
- .map_err(DLCError::from)?;
459
-
460
- // Convert back to UniFFI types
461
- Ok(rust_dlc_transactions_to_uniffi(dlc_txs))
462
- }
463
-
464
- /// Create a single CET
465
- pub fn create_cet(
466
- local_output: TxOutput,
467
- local_payout_serial_id: u64,
468
- remote_output: TxOutput,
469
- remote_payout_serial_id: u64,
470
- fund_tx_id: String,
471
- fund_vout: u32,
472
- lock_time: u32,
473
- ) -> Result<Transaction, DLCError> {
474
- let txid = Txid::from_str(&fund_tx_id)
475
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
476
-
477
- let local_btc_output = BtcTxOut {
478
- value: Amount::from_sat(local_output.value),
479
- script_pubkey: ScriptBuf::from(local_output.script_pubkey),
480
- };
481
-
482
- let remote_btc_output = BtcTxOut {
483
- value: Amount::from_sat(remote_output.value),
484
- script_pubkey: ScriptBuf::from(remote_output.script_pubkey),
485
- };
486
-
487
- let fund_tx_input = TxIn {
488
- previous_output: OutPoint {
489
- txid,
490
- vout: fund_vout,
491
- },
492
- script_sig: ScriptBuf::new(),
493
- sequence: Sequence::ZERO,
494
- witness: Witness::new(),
495
- };
496
-
497
- let btc_tx = ddk_dlc::create_cet(
498
- local_btc_output,
499
- local_payout_serial_id,
500
- remote_btc_output,
501
- remote_payout_serial_id,
502
- &fund_tx_input,
503
- lock_time,
504
- );
505
-
506
- Ok(btc_tx_to_transaction(&btc_tx))
507
- }
508
-
509
- /// Create multiple CETs
510
- pub fn create_cets(
511
- fund_tx_id: String,
512
- fund_vout: u32,
513
- local_final_script_pubkey: Vec<u8>,
514
- remote_final_script_pubkey: Vec<u8>,
515
- outcomes: Vec<Payout>,
516
- lock_time: u32,
517
- local_serial_id: u64,
518
- remote_serial_id: u64,
519
- ) -> Result<Vec<Transaction>, DLCError> {
520
- let txid = Txid::from_str(&fund_tx_id)
521
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
522
-
523
- let fund_tx_input = TxIn {
524
- previous_output: OutPoint {
525
- txid,
526
- vout: fund_vout,
527
- },
528
- script_sig: ScriptBuf::new(),
529
- sequence: Sequence::ZERO,
530
- witness: Witness::new(),
531
- };
532
-
533
- let local_script = Script::from_bytes(&local_final_script_pubkey);
534
- let remote_script = Script::from_bytes(&remote_final_script_pubkey);
535
-
536
- let payouts: Vec<DlcPayout> = outcomes
537
- .iter()
538
- .map(|outcome| DlcPayout {
539
- offer: Amount::from_sat(outcome.offer),
540
- accept: Amount::from_sat(outcome.accept),
541
- })
542
- .collect();
543
-
544
- let btc_txs = ddk_dlc::create_cets(
545
- &fund_tx_input,
546
- local_script,
547
- local_serial_id,
548
- remote_script,
549
- remote_serial_id,
550
- &payouts,
551
- lock_time,
552
- );
553
-
554
- Ok(btc_txs.iter().map(btc_tx_to_transaction).collect())
555
- }
556
-
557
- /// Create a refund transaction
558
- pub fn create_refund_transaction(
559
- local_final_script_pubkey: Vec<u8>,
560
- remote_final_script_pubkey: Vec<u8>,
561
- local_amount: u64,
562
- remote_amount: u64,
563
- lock_time: u32,
564
- fund_tx_id: String,
565
- fund_vout: u32,
566
- ) -> Result<Transaction, DLCError> {
567
- let txid = Txid::from_str(&fund_tx_id)
568
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
569
-
570
- let local_output = BtcTxOut {
571
- value: Amount::from_sat(local_amount),
572
- script_pubkey: ScriptBuf::from(local_final_script_pubkey),
573
- };
574
-
575
- let remote_output = BtcTxOut {
576
- value: Amount::from_sat(remote_amount),
577
- script_pubkey: ScriptBuf::from(remote_final_script_pubkey),
578
- };
579
-
580
- let funding_input = TxIn {
581
- previous_output: OutPoint {
582
- txid,
583
- vout: fund_vout,
584
- },
585
- script_sig: ScriptBuf::new(),
586
- sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
587
- witness: Witness::new(),
588
- };
589
-
590
- let btc_tx =
591
- ddk_dlc::create_refund_transaction(local_output, remote_output, funding_input, lock_time);
592
-
593
- Ok(btc_tx_to_transaction(&btc_tx))
594
- }
595
-
596
- /// Check if a transaction output is dust
597
- pub fn is_dust_output(output: TxOutput) -> bool {
598
- output.value < DUST_LIMIT
599
- }
600
-
601
- /// Get change output and fees for a party
602
- pub fn get_change_output_and_fees(
603
- params: PartyParams,
604
- fee_rate: u64,
605
- ) -> Result<ChangeOutputAndFees, DLCError> {
606
- let rust_params = party_params_to_rust(&params)?;
607
- let total_collateral = Amount::from_sat(params.collateral * 2); // Assume bilateral
608
-
609
- let (change_output, fund_fee, cet_fee) = rust_params
610
- .get_change_output_and_fees(total_collateral, fee_rate, Amount::ZERO)
611
- .map_err(DLCError::from)?;
612
-
613
- let uniffi_output = TxOutput {
614
- value: change_output.value.to_sat(),
615
- script_pubkey: change_output.script_pubkey.to_bytes(),
616
- };
617
-
618
- Ok(ChangeOutputAndFees {
619
- change_output: uniffi_output,
620
- fund_fee: fund_fee.to_sat(),
621
- cet_fee: cet_fee.to_sat(),
622
- })
623
- }
624
-
625
- /// Get total input virtual size for fee calculation
626
- pub fn get_total_input_vsize(inputs: Vec<TxInputInfo>) -> u32 {
627
- // Simplified calculation: P2WPKH inputs are ~148 vbytes each
628
- inputs.len() as u32 * 148
629
- }
630
-
631
- /// Verify a fund transaction signature
632
- pub fn verify_fund_tx_signature(
633
- fund_tx: Transaction,
634
- signature: Vec<u8>,
635
- pubkey: Vec<u8>,
636
- txid: String,
637
- vout: u32,
638
- input_amount: u64,
639
- ) -> Result<bool, DLCError> {
640
- let btc_tx = transaction_to_btc_tx(&fund_tx)?;
641
- let pk = PublicKey::from_slice(&pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
642
- let input_txid = Txid::from_str(&txid)
643
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
644
-
645
- // Find the input index
646
- let input_index = btc_tx
647
- .input
648
- .iter()
649
- .position(|input| {
650
- input.previous_output.txid == input_txid && input.previous_output.vout == vout
651
- })
652
- .ok_or(DLCError::InvalidArgument(format!(
653
- "Input index not found in {input_txid}"
654
- )))?;
655
-
656
- // Create a simple P2WPKH script for verification
657
- let wpkh = WPubkeyHash::hash(&pk.serialize());
658
- let script = bitcoin::ScriptBuf::new_p2wpkh(&wpkh);
659
-
660
- // Parse signature
661
- let sig = EcdsaSignature::from_der(&signature).map_err(|_| DLCError::InvalidSignature)?;
662
-
663
- let secp = Secp256k1::verification_only();
664
- match ddk_dlc::verify_tx_input_sig(
665
- &secp,
666
- &sig,
667
- &btc_tx,
668
- input_index,
669
- &script,
670
- Amount::from_sat(input_amount),
671
- &pk,
672
- ) {
673
- Ok(()) => Ok(true),
674
- Err(_) => Ok(false),
675
- }
676
- }
677
-
678
- // ============================================================================
679
- // SIGNING AND SIGNATURE FUNCTIONS (using rust-dlc library)
680
- // ============================================================================
681
-
682
- /// Get raw signature for a fund transaction input
683
- pub fn get_raw_funding_transaction_input_signature(
684
- funding_transaction: Transaction,
685
- privkey: Vec<u8>,
686
- prev_tx_id: String,
687
- prev_tx_vout: u32,
688
- value: u64,
689
- ) -> Result<Vec<u8>, DLCError> {
690
- let btc_tx = transaction_to_btc_tx(&funding_transaction)?;
691
- let sk = SecretKey::from_slice(&privkey)
692
- .map_err(|_| DLCError::InvalidArgument("Invalid private key".to_string()))?;
693
- let prev_txid = Txid::from_str(&prev_tx_id)
694
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
695
-
696
- // Find the input index
697
- let input_index = btc_tx
698
- .input
699
- .iter()
700
- .position(|input| {
701
- input.previous_output.txid == prev_txid && input.previous_output.vout == prev_tx_vout
702
- })
703
- .ok_or(DLCError::InvalidArgument(format!(
704
- "Input index not found in {prev_txid}"
705
- )))?;
706
-
707
- let secp = get_secp_context();
708
- // Create P2WPKH script for signing
709
- let pk = PublicKey::from_secret_key(secp, &sk);
710
- let wpkh = WPubkeyHash::hash(&pk.serialize());
711
- let script = bitcoin::ScriptBuf::new_p2wpkh(&wpkh);
712
-
713
- let sig = ddk_dlc::util::get_sig_for_tx_input(
714
- secp,
715
- &btc_tx,
716
- input_index,
717
- &script,
718
- Amount::from_sat(value),
719
- EcdsaSighashType::All,
720
- &sk,
721
- )
722
- .map_err(DLCError::from)?;
723
-
724
- Ok(sig)
725
- }
726
-
727
- /// Sign a funding transaction input
728
- pub fn sign_fund_transaction_input(
729
- fund_transaction: Transaction,
730
- privkey: Vec<u8>,
731
- prev_tx_id: String,
732
- prev_tx_vout: u32,
733
- value: u64,
734
- ) -> Result<Transaction, DLCError> {
735
- let mut btc_tx = transaction_to_btc_tx(&fund_transaction)?;
736
- let sk = SecretKey::from_slice(&privkey)
737
- .map_err(|_| DLCError::InvalidArgument("Invalid private key".to_string()))?;
738
- let prev_txid = Txid::from_str(&prev_tx_id)
739
- .map_err(|_| DLCError::InvalidArgument("Invalid transaction id".to_string()))?;
740
-
741
- // Find the input index
742
- let input_index = btc_tx
743
- .input
744
- .iter()
745
- .position(|input| {
746
- input.previous_output.txid == prev_txid && input.previous_output.vout == prev_tx_vout
747
- })
748
- .ok_or(DLCError::InvalidArgument(format!(
749
- "Input index not found in {prev_txid}"
750
- )))?;
751
-
752
- let secp = Secp256k1::signing_only();
753
- ddk_dlc::util::sign_p2wpkh_input(
754
- &secp,
755
- &sk,
756
- &mut btc_tx,
757
- input_index,
758
- EcdsaSighashType::All,
759
- Amount::from_sat(value),
760
- )
761
- .map_err(DLCError::from)?;
762
-
763
- Ok(btc_tx_to_transaction(&btc_tx))
764
- }
765
-
766
- pub fn sign_multi_sig_input(
767
- txn: Transaction,
768
- dlc_input: DlcInputInfo,
769
- local_privkey: Vec<u8>,
770
- remote_signature: Vec<u8>,
771
- ) -> Result<Transaction, DLCError> {
772
- let secp = get_secp_context();
773
- let btc_tx = transaction_to_btc_tx(&txn)?;
774
- let sk = SecretKey::from_slice(&local_privkey)
775
- .map_err(|_| DLCError::InvalidArgument("Invalid private key".to_string()))?;
776
-
777
- let local_pk = PublicKey::from_slice(&dlc_input.local_fund_pubkey)
778
- .map_err(|_| DLCError::InvalidPublicKey)?;
779
- let remote_pk = PublicKey::from_slice(&dlc_input.remote_fund_pubkey)
780
- .map_err(|_| DLCError::InvalidPublicKey)?;
781
-
782
- let dlc_input = dlc_input_info_to_rust(&dlc_input)?;
783
-
784
- let signature = ddk_dlc::dlc_input::create_dlc_funding_input_signature(
785
- secp,
786
- &btc_tx,
787
- dlc_input.fund_vout as usize,
788
- &dlc_input,
789
- &sk,
790
- )
791
- .map_err(|_| DLCError::InvalidSignature)?;
792
-
793
- let (first, second) = if local_pk < remote_pk {
794
- (local_pk, remote_pk)
795
- } else {
796
- (remote_pk, local_pk)
797
- };
798
-
799
- let witness = ddk_dlc::dlc_input::combine_dlc_input_signatures(
800
- &dlc_input,
801
- &signature,
802
- &remote_signature,
803
- &first,
804
- &second,
805
- );
806
-
807
- let mut fund_psbt = Psbt::from_unsigned_tx(btc_tx).map_err(|_| DLCError::InvalidTransaction)?;
808
- fund_psbt.inputs[dlc_input.fund_vout as usize].final_script_witness = Some(witness);
809
-
810
- Ok(btc_tx_to_transaction(
811
- &fund_psbt.extract_tx_unchecked_fee_rate(),
812
- ))
813
- }
814
-
815
- pub fn sign_cet(
816
- cet: Transaction,
817
- adaptor_signature: Vec<u8>,
818
- oracle_signatures: Vec<Vec<u8>>,
819
- funding_secret_key: Vec<u8>,
820
- other_pubkey: Vec<u8>,
821
- funding_script_pubkey: Vec<u8>,
822
- fund_output_value: u64,
823
- ) -> Result<Transaction, DLCError> {
824
- let mut btc_tx = transaction_to_btc_tx(&cet)?;
825
- let adaptor_sig = vec_to_ecdsa_adaptor_signature(adaptor_signature)?;
826
- let oracle_sigs = oracle_signatures
827
- .iter()
828
- .map(|sig| vec_to_schnorr_signature(sig.as_slice()))
829
- .collect::<Result<Vec<_>, _>>()?;
830
- let funding_sk = SecretKey::from_slice(&funding_secret_key)
831
- .map_err(|_| DLCError::InvalidArgument("Invalid funding secret key".to_string()))?;
832
- let other_pk = PublicKey::from_slice(&other_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
833
- let funding_pubkey =
834
- PublicKey::from_slice(&funding_script_pubkey).map_err(|_| DLCError::InvalidPublicKey)?;
835
- let dlc_redeem_script = ddk_dlc::make_funding_redeemscript(&funding_pubkey, &other_pk);
836
- let secp = get_secp_context();
837
-
838
- ddk_dlc::sign_cet(
839
- secp,
840
- &mut btc_tx,
841
- &adaptor_sig,
842
- &[oracle_sigs],
843
- &funding_sk,
844
- &other_pk,
845
- dlc_redeem_script.as_script(),
846
- Amount::from_sat(fund_output_value),
847
- )
848
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
849
-
850
- Ok(btc_tx_to_transaction(&btc_tx))
851
- }
852
-
853
- fn vec_to_schnorr_signature(signature: &[u8]) -> Result<SchnorrSignature, DLCError> {
854
- let sig = SchnorrSignature::from_slice(signature).map_err(|_| DLCError::InvalidSignature)?;
855
- Ok(sig)
856
- }
857
-
858
- fn vec_to_ecdsa_adaptor_signature(signature: Vec<u8>) -> Result<EcdsaAdaptorSignature, DLCError> {
859
- EcdsaAdaptorSignature::from_slice(&signature).map_err(|_| DLCError::InvalidSignature)
860
- }
861
-
862
- fn signatures_to_secret(signatures: &[Vec<SchnorrSignature>]) -> Result<SecretKey, DLCError> {
863
- let s_values = signatures
864
- .iter()
865
- .flatten()
866
- .map(|x| match secp_utils::schnorrsig_decompose(x) {
867
- Ok(v) => Ok(v.1),
868
- Err(err) => Err(DLCError::Secp256k1Error(err.to_string())),
869
- })
870
- .collect::<Result<Vec<&[u8]>, DLCError>>()?;
871
-
872
- if s_values.is_empty() {
873
- return Err(DLCError::InvalidArgument(
874
- "No signatures provided".to_string(),
875
- ));
876
- }
877
-
878
- let secret = SecretKey::from_slice(s_values[0])
879
- .map_err(|_| DLCError::InvalidArgument("Invalid signature".to_string()))?;
880
-
881
- let result = s_values.iter().skip(1).fold(secret, |accum, s| {
882
- let sec = SecretKey::from_slice(s).unwrap();
883
- accum.add_tweak(&Scalar::from(sec)).unwrap()
884
- });
885
-
886
- Ok(result)
887
- }
888
-
889
- pub fn create_cet_adaptor_sigs_from_oracle_info(
890
- cets: Vec<Transaction>,
891
- oracle_info: Vec<OracleInfo>,
892
- funding_secret_key: Vec<u8>,
893
- funding_script_pubkey: Vec<u8>,
894
- fund_output_value: u64,
895
- msgs: Vec<Vec<Vec<Vec<u8>>>>,
896
- ) -> Result<Vec<AdaptorSignature>, DLCError> {
897
- let cets = cets
898
- .iter()
899
- .map(transaction_to_btc_tx)
900
- .collect::<Result<Vec<_>, _>>()?;
901
- let oracle_infos = oracle_info
902
- .iter()
903
- .map(|info| {
904
- let public_key = XOnlyPublicKey::from_slice(&info.public_key)
905
- .map_err(|_| DLCError::InvalidPublicKey)?;
906
- let nonces = info
907
- .nonces
908
- .iter()
909
- .map(|nonce| XOnlyPublicKey::from_slice(nonce))
910
- .collect::<Result<Vec<_>, _>>()
911
- .map_err(|_| DLCError::InvalidArgument("Invalid nonce pubkey".to_string()))?;
912
- Ok(DlcOracleInfo { public_key, nonces })
913
- })
914
- .collect::<Result<Vec<_>, DLCError>>()
915
- .map_err(|_| DLCError::InvalidArgument("Invalid oracle info".to_string()))?;
916
-
917
- let funding_sk = SecretKey::from_slice(&funding_secret_key)
918
- .map_err(|_| DLCError::InvalidArgument("Invalid funding secret key".to_string()))?;
919
- let funding_script = Script::from_bytes(&funding_script_pubkey);
920
- let msgs: Vec<Vec<Vec<Message>>> = msgs
921
- .iter()
922
- .map(|cet_msgs| {
923
- // For each CET
924
- cet_msgs
925
- .iter()
926
- .map(|outcome_msgs| {
927
- // For each outcome
928
- outcome_msgs
929
- .iter()
930
- .map(|msg_bytes| {
931
- // For each message (Vec<u8>)
932
- Message::from_digest_slice(msg_bytes).map_err(|_| {
933
- DLCError::InvalidArgument("Invalid message".to_string())
934
- })
935
- })
936
- .collect::<Result<Vec<_>, _>>()
937
- })
938
- .collect::<Result<Vec<_>, _>>()
939
- })
940
- .collect::<Result<Vec<_>, _>>()?;
941
- let secp = get_secp_context();
942
- let adaptor_sigs = ddk_dlc::create_cet_adaptor_sigs_from_oracle_info(
943
- secp,
944
- &cets,
945
- &oracle_infos,
946
- &funding_sk,
947
- funding_script,
948
- Amount::from_sat(fund_output_value),
949
- &msgs,
950
- )
951
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
952
-
953
- let adaptor_sigs = adaptor_sigs
954
- .iter()
955
- .map(|sig| AdaptorSignature {
956
- signature: sig.as_ref().to_vec(),
957
- proof: Vec::new(),
958
- })
959
- .collect::<Vec<_>>();
960
-
961
- Ok(adaptor_sigs)
962
- }
963
-
964
- /// Create adaptor signatures from pre-computed adaptor points.
965
- pub fn create_cet_adaptor_sigs_from_points(
966
- cets: Vec<Transaction>,
967
- adaptor_points: Vec<Vec<u8>>,
968
- funding_secret_key: Vec<u8>,
969
- funding_script_pubkey: Vec<u8>,
970
- fund_output_value: u64,
971
- ) -> Result<Vec<AdaptorSignature>, DLCError> {
972
- if cets.len() != adaptor_points.len() {
973
- return Err(DLCError::InvalidArgument(format!(
974
- "CETs length ({}) does not match adaptor points length ({})",
975
- cets.len(),
976
- adaptor_points.len()
977
- )));
978
- }
979
-
980
- let cets = cets
981
- .iter()
982
- .map(transaction_to_btc_tx)
983
- .collect::<Result<Vec<_>, _>>()?;
984
-
985
- let adaptor_points = adaptor_points
986
- .iter()
987
- .map(|p| {
988
- PublicKey::from_slice(p)
989
- .map_err(|_| DLCError::InvalidArgument("Invalid adaptor point".to_string()))
990
- })
991
- .collect::<Result<Vec<_>, _>>()?;
992
-
993
- let funding_sk = SecretKey::from_slice(&funding_secret_key)
994
- .map_err(|_| DLCError::InvalidArgument("Invalid funding secret key".to_string()))?;
995
- let funding_script = Script::from_bytes(&funding_script_pubkey);
996
-
997
- let inputs: Vec<(&bitcoin::Transaction, &PublicKey)> =
998
- cets.iter().zip(adaptor_points.iter()).collect();
999
-
1000
- let secp = get_secp_context();
1001
- let adaptor_sigs = ddk_dlc::create_cet_adaptor_sigs_from_points(
1002
- secp,
1003
- &inputs,
1004
- &funding_sk,
1005
- funding_script,
1006
- Amount::from_sat(fund_output_value),
1007
- )
1008
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
1009
-
1010
- let adaptor_sigs = adaptor_sigs
1011
- .iter()
1012
- .map(|sig| AdaptorSignature {
1013
- signature: sig.as_ref().to_vec(),
1014
- proof: Vec::new(),
1015
- })
1016
- .collect::<Vec<_>>();
1017
-
1018
- Ok(adaptor_sigs)
1019
- }
1020
-
1021
- pub fn verify_cet_adaptor_sig_from_oracle_info(
1022
- adaptor_sig: AdaptorSignature,
1023
- cet: Transaction,
1024
- oracle_infos: Vec<OracleInfo>,
1025
- pubkey: Vec<u8>,
1026
- funding_script_pubkey: Vec<u8>,
1027
- total_collateral: u64,
1028
- msgs: Vec<Vec<Vec<u8>>>,
1029
- ) -> bool {
1030
- let secp = get_secp_context();
1031
- let Ok(btc_tx) = transaction_to_btc_tx(&cet) else {
1032
- return false;
1033
- };
1034
- let Ok(adaptor_sig) = vec_to_ecdsa_adaptor_signature(adaptor_sig.signature) else {
1035
- return false;
1036
- };
1037
- let Ok(oracle_infos) = oracle_infos
1038
- .iter()
1039
- .map(|info| {
1040
- let public_key = XOnlyPublicKey::from_slice(&info.public_key)?;
1041
- let nonces = info
1042
- .nonces
1043
- .iter()
1044
- .map(|nonce| XOnlyPublicKey::from_slice(nonce))
1045
- .collect::<Result<Vec<_>, _>>()?;
1046
- Ok(DlcOracleInfo { public_key, nonces })
1047
- })
1048
- .collect::<Result<Vec<_>, ddk_dlc::Error>>()
1049
- else {
1050
- return false;
1051
- };
1052
- let Ok(pubkey) = PublicKey::from_slice(&pubkey) else {
1053
- return false;
1054
- };
1055
- let funding_script = Script::from_bytes(&funding_script_pubkey);
1056
- let Ok(msgs) = msgs
1057
- .into_iter()
1058
- .map(|msg| {
1059
- msg.iter()
1060
- .map(|m| Message::from_digest_slice(m).map_err(|_| DLCError::InvalidArgument))
1061
- .collect::<Result<Vec<_>, _>>()
1062
- })
1063
- .collect::<Result<Vec<_>, _>>()
1064
- else {
1065
- return false;
1066
- };
1067
- let Ok(adaptor_point) = ddk_dlc::get_adaptor_point_from_oracle_info(secp, &oracle_infos, &msgs)
1068
- else {
1069
- return false;
1070
- };
1071
- let Ok(_) = ddk_dlc::verify_cet_adaptor_sig_from_point(
1072
- secp,
1073
- &adaptor_sig,
1074
- &btc_tx,
1075
- &adaptor_point,
1076
- &pubkey,
1077
- funding_script,
1078
- Amount::from_sat(total_collateral),
1079
- ) else {
1080
- return false;
1081
- };
1082
-
1083
- true
1084
- }
1085
-
1086
- pub fn verify_cet_adaptor_sigs_from_oracle_info(
1087
- adaptor_sigs: Vec<AdaptorSignature>,
1088
- cets: Vec<Transaction>,
1089
- oracle_infos: Vec<OracleInfo>,
1090
- pubkey: Vec<u8>,
1091
- funding_script_pubkey: Vec<u8>,
1092
- total_collateral: u64,
1093
- msgs: Vec<Vec<Vec<Vec<u8>>>>,
1094
- ) -> bool {
1095
- cets.into_iter()
1096
- .zip(adaptor_sigs)
1097
- .enumerate()
1098
- .all(|(i, (cet, adaptor_sig))| {
1099
- verify_cet_adaptor_sig_from_oracle_info(
1100
- adaptor_sig,
1101
- cet,
1102
- oracle_infos.clone(),
1103
- pubkey.clone(),
1104
- funding_script_pubkey.clone(),
1105
- total_collateral,
1106
- msgs[i].clone(),
1107
- )
1108
- })
1109
- }
1110
-
1111
- /// Create CET adaptor signature from oracle info
1112
- pub fn create_cet_adaptor_signature_from_oracle_info(
1113
- cet: Transaction,
1114
- oracle_info: OracleInfo,
1115
- funding_sk: Vec<u8>,
1116
- funding_script_pubkey: Vec<u8>,
1117
- total_collateral: u64,
1118
- msgs: Vec<Vec<u8>>,
1119
- ) -> Result<AdaptorSignature, DLCError> {
1120
- let btc_tx = transaction_to_btc_tx(&cet)?;
1121
- let sk = SecretKey::from_slice(&funding_sk)
1122
- .map_err(|_| DLCError::InvalidArgument("Invalid funding secret key".to_string()))?;
1123
- let funding_script = Script::from_bytes(&funding_script_pubkey);
1124
-
1125
- // Convert oracle info
1126
- let oracle_pk = XOnlyPublicKey::from_slice(&oracle_info.public_key)
1127
- .map_err(|_| DLCError::InvalidPublicKey)?;
1128
- let nonces: Result<Vec<_>, _> = oracle_info
1129
- .nonces
1130
- .iter()
1131
- .map(|n| XOnlyPublicKey::from_slice(n))
1132
- .collect();
1133
- let oracle_nonces = nonces.map_err(|_| DLCError::InvalidPublicKey)?;
1134
-
1135
- let dlc_oracle_info = DlcOracleInfo {
1136
- public_key: oracle_pk,
1137
- nonces: oracle_nonces,
1138
- };
1139
-
1140
- // Convert messages
1141
- let messages: Result<Vec<_>, _> = msgs
1142
- .iter()
1143
- .map(|msg| Message::from_digest_slice(msg))
1144
- .collect();
1145
- let msg_vec = messages.map_err(|_| DLCError::InvalidArgument("Invalid message".to_string()))?;
1146
- let nested_msgs = vec![msg_vec]; // Wrap in vector for single oracle
1147
-
1148
- let secp = get_secp_context();
1149
- let adaptor_sig = ddk_dlc::create_cet_adaptor_sig_from_oracle_info(
1150
- secp,
1151
- &btc_tx,
1152
- &[dlc_oracle_info],
1153
- &sk,
1154
- funding_script,
1155
- Amount::from_sat(total_collateral),
1156
- &nested_msgs,
1157
- )
1158
- .map_err(DLCError::from)?;
1159
-
1160
- Ok(AdaptorSignature {
1161
- signature: adaptor_sig.as_ref().to_vec(),
1162
- proof: Vec::new(), // EcdsaAdaptorSignature doesn't expose proof directly
1163
- })
1164
- }
1165
-
1166
- pub fn create_cet_adaptor_points_from_oracle_info(
1167
- oracle_info: Vec<OracleInfo>,
1168
- msgs: Vec<Vec<Vec<Vec<u8>>>>,
1169
- ) -> Result<Vec<Vec<u8>>, DLCError> {
1170
- let oracle_infos = oracle_info
1171
- .iter()
1172
- .map(|info| {
1173
- let public_key = XOnlyPublicKey::from_slice(&info.public_key)
1174
- .map_err(|_| DLCError::InvalidPublicKey)?;
1175
- let nonces = info
1176
- .nonces
1177
- .iter()
1178
- .map(|nonce| XOnlyPublicKey::from_slice(nonce))
1179
- .collect::<Result<Vec<_>, _>>()
1180
- .map_err(|_| DLCError::InvalidArgument("Invalid nonce pubkey".to_string()))?;
1181
- Ok(DlcOracleInfo { public_key, nonces })
1182
- })
1183
- .collect::<Result<Vec<_>, DLCError>>()
1184
- .map_err(|_| DLCError::InvalidArgument("Invalid oracle info".to_string()))?;
1185
-
1186
- let secp = get_secp_context();
1187
- let mut adaptor_points = Vec::new();
1188
-
1189
- // Process each CET's messages separately
1190
- for cet_msgs in msgs {
1191
- // Flatten from Vec<Vec<Vec<u8>>> to Vec<Vec<u8>>
1192
- let cet_msgs: Vec<Vec<Message>> = cet_msgs
1193
- .into_iter()
1194
- .map(|outcome_msgs| {
1195
- outcome_msgs
1196
- .iter()
1197
- .map(|m| {
1198
- Message::from_digest_slice(m)
1199
- .map_err(|_| DLCError::InvalidArgument("Invalid message".to_string()))
1200
- })
1201
- .collect::<Result<Vec<_>, _>>()
1202
- })
1203
- .collect::<Result<Vec<_>, _>>()?;
1204
-
1205
- // Get adaptor point for this CET
1206
- let adaptor_point =
1207
- ddk_dlc::get_adaptor_point_from_oracle_info(secp, &oracle_infos, &cet_msgs)
1208
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
1209
-
1210
- // Convert the adaptor point to bytes
1211
- let adaptor_point_bytes = adaptor_point.serialize().to_vec();
1212
- adaptor_points.push(adaptor_point_bytes);
1213
- }
1214
-
1215
- Ok(adaptor_points)
1216
- }
1217
-
1218
- pub fn extract_ecdsa_signature_from_oracle_signatures(
1219
- oracle_signatures: Vec<Vec<u8>>,
1220
- adaptor_signature: Vec<u8>,
1221
- ) -> Result<Vec<u8>, DLCError> {
1222
- // Convert oracle signatures to Schnorr signatures
1223
- let oracle_sigs = oracle_signatures
1224
- .iter()
1225
- .map(|sig| vec_to_schnorr_signature(sig.as_slice()))
1226
- .collect::<Result<Vec<_>, _>>()?;
1227
-
1228
- // Extract the secret key from oracle signatures
1229
- let adaptor_secret = signatures_to_secret(&[oracle_sigs])?;
1230
-
1231
- // Convert adaptor signature to EcdsaAdaptorSignature
1232
- let adaptor_sig = vec_to_ecdsa_adaptor_signature(adaptor_signature)?;
1233
-
1234
- // Decrypt the adaptor signature to get the final ECDSA signature
1235
- let ecdsa_sig = adaptor_sig
1236
- .decrypt(&adaptor_secret)
1237
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
1238
-
1239
- // Return the DER-encoded signature
1240
- Ok(ecdsa_sig.serialize_der().to_vec())
1241
- }
1242
-
1243
- /// Get all the inputs that go into creating a CET adaptor signature.
1244
- ///
1245
- /// This debug function is intentionally always available (not feature-gated)
1246
- /// to enable debugging signature mismatches in production environments where
1247
- /// rebuilding with debug features may not be feasible.
1248
- ///
1249
- /// Use this to compare values with external signers (e.g., Fordefi) when
1250
- /// debugging adaptor signature verification failures.
1251
- ///
1252
- /// Returns:
1253
- /// - `sighash`: The 32-byte BIP143 sighash message that gets signed
1254
- /// - `adaptor_point`: The 33-byte compressed adaptor public key
1255
- /// - `input_index`: Always 0 for CETs
1256
- /// - `script_pubkey`: The funding script used for sighash calculation
1257
- /// - `value`: The fund output value used for sighash calculation
1258
- /// - `cet_txid`: The CET transaction ID
1259
- /// - `cet_raw`: Raw serialized CET bytes
1260
- pub fn get_cet_adaptor_signature_inputs(
1261
- cet: Transaction,
1262
- oracle_info: Vec<OracleInfo>,
1263
- funding_script_pubkey: Vec<u8>,
1264
- fund_output_value: u64,
1265
- msgs: Vec<Vec<Vec<u8>>>,
1266
- ) -> Result<CetAdaptorSignatureDebugInfo, DLCError> {
1267
- let btc_tx = transaction_to_btc_tx(&cet)?;
1268
- let funding_script = Script::from_bytes(&funding_script_pubkey);
1269
-
1270
- // Convert oracle info
1271
- let oracle_infos: Vec<DlcOracleInfo> = oracle_info
1272
- .iter()
1273
- .map(|info| {
1274
- let public_key = XOnlyPublicKey::from_slice(&info.public_key)
1275
- .map_err(|_| DLCError::InvalidPublicKey)?;
1276
- let nonces = info
1277
- .nonces
1278
- .iter()
1279
- .map(|nonce| XOnlyPublicKey::from_slice(nonce))
1280
- .collect::<Result<Vec<_>, _>>()
1281
- .map_err(|_| DLCError::InvalidArgument("Invalid nonce pubkey".to_string()))?;
1282
- Ok(DlcOracleInfo { public_key, nonces })
1283
- })
1284
- .collect::<Result<Vec<_>, DLCError>>()?;
1285
-
1286
- // Convert messages
1287
- let cet_msgs: Vec<Vec<Message>> = msgs
1288
- .into_iter()
1289
- .map(|outcome_msgs| {
1290
- outcome_msgs
1291
- .iter()
1292
- .map(|m| {
1293
- Message::from_digest_slice(m)
1294
- .map_err(|_| DLCError::InvalidArgument("Invalid message".to_string()))
1295
- })
1296
- .collect::<Result<Vec<_>, _>>()
1297
- })
1298
- .collect::<Result<Vec<_>, _>>()?;
1299
-
1300
- let secp = get_secp_context();
1301
-
1302
- // Get the adaptor point
1303
- let adaptor_point = ddk_dlc::get_adaptor_point_from_oracle_info(secp, &oracle_infos, &cet_msgs)
1304
- .map_err(|e| DLCError::Secp256k1Error(e.to_string()))?;
1305
-
1306
- // Get the sighash - this is the actual message being signed
1307
- let sig_hash = ddk_dlc::util::get_sig_hash_msg(
1308
- &btc_tx,
1309
- 0, // input_index is always 0 for CETs
1310
- funding_script,
1311
- Amount::from_sat(fund_output_value),
1312
- )
1313
- .map_err(DLCError::from)?;
1314
-
1315
- Ok(CetAdaptorSignatureDebugInfo {
1316
- sighash: sig_hash.as_ref().to_vec(),
1317
- adaptor_point: adaptor_point.serialize().to_vec(),
1318
- input_index: 0,
1319
- script_pubkey: funding_script_pubkey,
1320
- value: fund_output_value,
1321
- cet_txid: btc_tx.compute_txid().to_string(),
1322
- cet_raw: cet.raw_bytes,
1323
- })
1324
- }
1325
-
1326
- /// Get the sighash for a CET - the actual 32-byte message that gets signed.
1327
- ///
1328
- /// This debug function is intentionally always available (not feature-gated)
1329
- /// to enable debugging sighash mismatches in production environments where
1330
- /// rebuilding with debug features may not be feasible.
1331
- ///
1332
- /// Use this to compare sighash values with external signers (e.g., Fordefi)
1333
- /// when debugging signature verification failures.
1334
- pub fn get_cet_sighash(
1335
- cet: Transaction,
1336
- funding_script_pubkey: Vec<u8>,
1337
- fund_output_value: u64,
1338
- ) -> Result<Vec<u8>, DLCError> {
1339
- let btc_tx = transaction_to_btc_tx(&cet)?;
1340
- let funding_script = Script::from_bytes(&funding_script_pubkey);
1341
-
1342
- let sig_hash = ddk_dlc::util::get_sig_hash_msg(
1343
- &btc_tx,
1344
- 0, // input_index is always 0 for CETs
1345
- funding_script,
1346
- Amount::from_sat(fund_output_value),
1347
- )
1348
- .map_err(DLCError::from)?;
1349
-
1350
- Ok(sig_hash.as_ref().to_vec())
1351
- }
1352
-
1353
- pub fn convert_mnemonic_to_seed(
1354
- mnemonic: String,
1355
- passphrase: Option<String>,
1356
- ) -> Result<Vec<u8>, DLCError> {
1357
- let seed_mnemonic = Mnemonic::parse_in_normalized(Language::English, &mnemonic)
1358
- .map_err(|_| DLCError::KeyError(ExtendedKey::InvalidMnemonic))?;
1359
- let passphrase = passphrase.unwrap_or("".to_string());
1360
- let seed = seed_mnemonic.to_seed(&passphrase);
1361
- Ok(seed.to_vec())
1362
- }
1363
-
1364
- /// Create master extended private key from 64-byte seed
1365
- /// Returns 78-byte encoded xpriv
1366
- pub fn create_extkey_from_seed(seed: Vec<u8>, network: String) -> Result<Vec<u8>, DLCError> {
1367
- if seed.len() != 64 {
1368
- return Err(DLCError::KeyError(ExtendedKey::InvalidXpriv));
1369
- }
1370
- let network = Network::from_str(&network).map_err(|_| DLCError::InvalidNetwork)?;
1371
- let xpriv = Xpriv::new_master(network, &seed)
1372
- .map_err(|_| DLCError::KeyError(ExtendedKey::InvalidXpriv))?;
1373
- Ok(xpriv.encode().to_vec())
1374
- }
1375
-
1376
- /// Derive child extended private key from parent extended key
1377
- /// Input: 78-byte encoded xpriv, Output: 78-byte encoded xpriv
1378
- pub fn create_extkey_from_parent_path(extkey: Vec<u8>, path: String) -> Result<Vec<u8>, DLCError> {
1379
- if extkey.len() != 78 {
1380
- return Err(DLCError::KeyError(ExtendedKey::InvalidXpriv));
1381
- }
1382
-
1383
- let secp = get_secp_context();
1384
- let xpriv =
1385
- Xpriv::decode(&extkey).map_err(|_| DLCError::KeyError(ExtendedKey::InvalidXpriv))?;
1386
-
1387
- let derivation_path = path
1388
- .into_derivation_path()
1389
- .map_err(|_| DLCError::KeyError(ExtendedKey::InvalidDerivationPath))?;
1390
-
1391
- let derived_xpriv = xpriv
1392
- .derive_priv(secp, &derivation_path)
1393
- .map_err(|_| DLCError::KeyError(ExtendedKey::InvalidXpriv))?;
1394
-
1395
- Ok(derived_xpriv.encode().to_vec())
1396
- }
1397
-
1398
- /// Extract public key from extended key (private or public)
1399
- /// Input: 78-byte encoded xpriv/xpub, Output: 33-byte compressed public key
1400
- pub fn get_pubkey_from_extkey(extkey: Vec<u8>, network: String) -> Result<Vec<u8>, DLCError> {
1401
- if extkey.len() != 78 {
1402
- return Err(DLCError::KeyError(ExtendedKey::InvalidXpriv));
1403
- }
1404
-
1405
- let secp = get_secp_context();
1406
- let _network = Network::from_str(&network).map_err(|_| DLCError::InvalidNetwork)?;
1407
-
1408
- // Try as xpriv first
1409
- if let Ok(xpriv) = Xpriv::decode(&extkey) {
1410
- let xpub = Xpub::from_priv(secp, &xpriv);
1411
- return Ok(xpub.public_key.serialize().to_vec());
1412
- }
1413
-
1414
- // Try as xpub
1415
- if let Ok(xpub) = Xpub::decode(&extkey) {
1416
- return Ok(xpub.public_key.serialize().to_vec());
1417
- }
1418
-
1419
- Err(DLCError::KeyError(ExtendedKey::InvalidXpriv))
1420
- }
1421
-
1422
- /// DEPRECATED: Use create_extkey_from_seed + create_extkey_from_parent_path instead
1423
- /// This function handles both seeds (64 bytes) and xprivs (78 bytes) which is confusing
1424
- #[deprecated(
1425
- since = "0.4.0",
1426
- note = "Use create_extkey_from_seed + create_extkey_from_parent_path"
1427
- )]
1428
- pub fn create_xpriv_from_parent_path(
1429
- seed_or_xpriv: Vec<u8>,
1430
- base_derivation_path: String,
1431
- network: String,
1432
- path: String,
1433
- ) -> Result<Vec<u8>, DLCError> {
1434
- let master_xpriv = if seed_or_xpriv.len() == 64 {
1435
- // This is a seed, create master xpriv
1436
- create_extkey_from_seed(seed_or_xpriv, network.clone())?
1437
- } else if seed_or_xpriv.len() == 78 {
1438
- // This is already an xpriv
1439
- seed_or_xpriv
1440
- } else {
1441
- return Err(DLCError::KeyError(ExtendedKey::InvalidXpriv));
1442
- };
1443
-
1444
- // Derive base path from master
1445
- let base_xpriv =
1446
- create_extkey_from_parent_path(master_xpriv, base_derivation_path.replace("m/", ""))?;
1447
-
1448
- // Derive final path from base
1449
- create_extkey_from_parent_path(base_xpriv, path)
1450
- }
1451
-
1452
- /// Convert extended private key to extended public key
1453
- /// Input: 78-byte encoded xpriv, Output: 78-byte encoded xpub
1454
- pub fn get_xpub_from_xpriv(xpriv: Vec<u8>, network: String) -> Result<Vec<u8>, DLCError> {
1455
- if xpriv.len() != 78 {
1456
- return Err(DLCError::KeyError(ExtendedKey::InvalidXpriv));
1457
- }
1458
-
1459
- let secp = get_secp_context();
1460
- let _network = Network::from_str(&network).map_err(|_| DLCError::InvalidNetwork)?;
1461
-
1462
- let xpriv = Xpriv::decode(&xpriv).map_err(|_| DLCError::KeyError(ExtendedKey::InvalidXpriv))?;
1463
-
1464
- let xpub = Xpub::from_priv(secp, &xpriv);
1465
- Ok(xpub.encode().to_vec())
1466
- }
1467
-
1468
- #[cfg(test)]
1469
- mod tests {
1470
- use super::*;
1471
- use bitcoin::bip32::DerivationPath;
1472
- use bitcoin::{hashes::sha256, locktime::absolute::LockTime, Address, CompressedPublicKey};
1473
- use ddk_dlc::secp_utils;
1474
- use secp256k1_zkp::{
1475
- rand::{thread_rng, RngCore},
1476
- Keypair, Scalar,
1477
- };
1478
- use std::str::FromStr;
1479
-
1480
- /// Create test keys similar to rust-dlc tests
1481
- fn create_test_keys() -> (SecretKey, PublicKey, SecretKey, PublicKey) {
1482
- let secp = Secp256k1::new();
1483
- let offer_sk =
1484
- SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1485
- .unwrap();
1486
- let offer_pk = PublicKey::from_secret_key(&secp, &offer_sk);
1487
- let accept_sk =
1488
- SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000002")
1489
- .unwrap();
1490
- let accept_pk = PublicKey::from_secret_key(&secp, &accept_sk);
1491
- (offer_sk, offer_pk, accept_sk, accept_pk)
1492
- }
1493
-
1494
- /// Create realistic party params for testing
1495
- fn create_test_party_params(
1496
- input_amount: u64,
1497
- collateral: u64,
1498
- fund_pubkey: Vec<u8>,
1499
- serial_id: u64,
1500
- ) -> PartyParams {
1501
- let mut rng = thread_rng();
1502
-
1503
- // Create a realistic P2WPKH script
1504
- let mut random_hash = [0u8; 20];
1505
- rng.fill_bytes(&mut random_hash);
1506
- let mut change_script = vec![0x00, 0x14]; // OP_0 + 20 bytes (P2WPKH)
1507
- change_script.extend_from_slice(&random_hash);
1508
-
1509
- rng.fill_bytes(&mut random_hash);
1510
- let mut payout_script = vec![0x00, 0x14]; // OP_0 + 20 bytes (P2WPKH)
1511
- payout_script.extend_from_slice(&random_hash);
1512
-
1513
- PartyParams {
1514
- fund_pubkey,
1515
- change_script_pubkey: change_script,
1516
- change_serial_id: serial_id + 1,
1517
- payout_script_pubkey: payout_script,
1518
- payout_serial_id: serial_id + 2,
1519
- inputs: vec![TxInputInfo {
1520
- txid: "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"
1521
- .to_string(),
1522
- vout: serial_id as u32,
1523
- script_sig: vec![],
1524
- max_witness_length: 108,
1525
- serial_id,
1526
- }],
1527
- input_amount,
1528
- collateral,
1529
- dlc_inputs: vec![],
1530
- }
1531
- }
1532
-
1533
- #[test]
1534
- fn mnemonic_to_seed_test() {
1535
- let mnemonic = Mnemonic::generate(24).unwrap();
1536
- let rust_seed = mnemonic.to_seed_normalized("").to_vec();
1537
- let ffi_seed = convert_mnemonic_to_seed(mnemonic.to_string(), None).unwrap();
1538
- assert_eq!(rust_seed, ffi_seed);
1539
- }
1540
-
1541
- #[test]
1542
- fn xpriv_to_xpub_test() {
1543
- let mnemonic = Mnemonic::generate(24).unwrap();
1544
- let rust_xpriv =
1545
- Xpriv::new_master(Network::Bitcoin, &mnemonic.to_seed_normalized("").to_vec()).unwrap();
1546
- let ffi_xpriv = create_extkey_from_seed(
1547
- mnemonic.to_seed_normalized("").to_vec(),
1548
- "bitcoin".to_string(),
1549
- )
1550
- .unwrap();
1551
- let rust_xpub = Xpub::from_priv(get_secp_context(), &rust_xpriv);
1552
- let ffi_xpub = get_xpub_from_xpriv(ffi_xpriv, "bitcoin".to_string()).unwrap();
1553
- assert_eq!(rust_xpub.encode().to_vec(), ffi_xpub);
1554
- }
1555
-
1556
- #[test]
1557
- fn xpriv_to_path() {
1558
- let base_derivation_path = "84'/0'/0'";
1559
- let app_path = "0/1";
1560
- let network = "bitcoin";
1561
- let secp = get_secp_context();
1562
-
1563
- let mnemonic = Mnemonic::generate(24).unwrap();
1564
- let rust_xpriv =
1565
- Xpriv::new_master(Network::Bitcoin, &mnemonic.to_seed_normalized("")).unwrap();
1566
- let rust_path =
1567
- DerivationPath::from_str(&format!("{}/{}", base_derivation_path, app_path)).unwrap();
1568
- let rust_xpriv = rust_xpriv.derive_priv(&secp, &rust_path).unwrap();
1569
-
1570
- let ffi_xpriv_bytes = convert_mnemonic_to_seed(mnemonic.to_string(), None).unwrap();
1571
- let ffi_xpub = create_xpriv_from_parent_path(
1572
- ffi_xpriv_bytes,
1573
- base_derivation_path.to_string(),
1574
- network.to_string(),
1575
- app_path.to_string(),
1576
- )
1577
- .unwrap();
1578
- assert_eq!(rust_xpriv.encode().to_vec(), ffi_xpub);
1579
- }
1580
-
1581
- #[test]
1582
- fn test_create_fund_tx_locking_script_matches_rust_dlc() {
1583
- let (_offer_sk, offer_pk, _accept_sk, accept_pk) = create_test_keys();
1584
-
1585
- // Test our wrapper
1586
- let wrapper_result = create_fund_tx_locking_script(
1587
- offer_pk.serialize().to_vec(),
1588
- accept_pk.serialize().to_vec(),
1589
- )
1590
- .unwrap();
1591
-
1592
- // Compare with direct rust-dlc call
1593
- let direct_result = ddk_dlc::make_funding_redeemscript(&offer_pk, &accept_pk);
1594
-
1595
- assert_eq!(wrapper_result, direct_result.to_bytes());
1596
- }
1597
-
1598
- #[test]
1599
- fn test_get_change_output_and_fees_wrapper() {
1600
- let (_offer_sk, offer_pk, _accept_sk, _accept_pk) = create_test_keys();
1601
-
1602
- let params = create_test_party_params(
1603
- 150_000_000, // 1.5 BTC input
1604
- 100_000_000, // 1 BTC collateral
1605
- offer_pk.serialize().to_vec(),
1606
- 1,
1607
- );
1608
-
1609
- let result = get_change_output_and_fees(params.clone(), 4);
1610
- assert!(result.is_ok());
1611
-
1612
- let change_and_fees = result.unwrap();
1613
-
1614
- // Verify we get reasonable values
1615
- assert!(change_and_fees.fund_fee > 0);
1616
- assert!(change_and_fees.cet_fee > 0);
1617
- assert!(change_and_fees.change_output.value > 0);
1618
-
1619
- // Compare with direct rust-dlc call
1620
- let rust_params = party_params_to_rust(&params).unwrap();
1621
- let total_collateral = Amount::from_sat(params.collateral * 2);
1622
- let direct_result = rust_params
1623
- .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1624
- .unwrap();
1625
-
1626
- assert_eq!(change_and_fees.fund_fee, direct_result.1.to_sat());
1627
- assert_eq!(change_and_fees.cet_fee, direct_result.2.to_sat());
1628
- assert_eq!(
1629
- change_and_fees.change_output.value,
1630
- direct_result.0.value.to_sat()
1631
- );
1632
- }
1633
-
1634
- #[test]
1635
- fn test_create_dlc_transactions_wrapper() {
1636
- let (_offer_sk, offer_pk, _accept_sk, accept_pk) = create_test_keys();
1637
-
1638
- let offer_params = create_test_party_params(
1639
- 1_000_000_000, // 10 BTC input
1640
- 100_000_000, // 1 BTC collateral
1641
- offer_pk.serialize().to_vec(),
1642
- 1,
1643
- );
1644
-
1645
- let accept_params = create_test_party_params(
1646
- 1_000_000_000, // 10 BTC input
1647
- 100_000_000, // 1 BTC collateral
1648
- accept_pk.serialize().to_vec(),
1649
- 2,
1650
- );
1651
-
1652
- let outcomes = vec![
1653
- Payout {
1654
- offer: 200_000_000, // 2 BTC to offer
1655
- accept: 0, // 0 BTC to accept
1656
- },
1657
- Payout {
1658
- offer: 0, // 0 BTC to offer
1659
- accept: 200_000_000, // 2 BTC to accept
1660
- },
1661
- ];
1662
-
1663
- let result = create_dlc_transactions(
1664
- outcomes,
1665
- offer_params,
1666
- accept_params,
1667
- 100, // refund locktime
1668
- 4, // fee rate
1669
- 10, // fund lock time
1670
- 10, // cet lock time
1671
- 0, // fund output serial id
1672
- 0, // contract flags
1673
- );
1674
-
1675
- assert!(result.is_ok());
1676
- let dlc_txs = result.unwrap();
1677
-
1678
- // Verify structure
1679
- assert_eq!(dlc_txs.fund.lock_time, 10);
1680
- assert_eq!(dlc_txs.refund.lock_time, 100);
1681
- assert_eq!(dlc_txs.cets.len(), 2);
1682
- assert!(dlc_txs.cets.iter().all(|cet| cet.lock_time == 10));
1683
-
1684
- // Verify funding transaction has correct structure
1685
- assert_eq!(dlc_txs.fund.inputs.len(), 2); // Two parties contributing
1686
- assert!(dlc_txs.fund.outputs.len() >= 1); // At least funding output
1687
-
1688
- // Verify CETs have correct structure
1689
- for cet in &dlc_txs.cets {
1690
- assert_eq!(cet.inputs.len(), 1); // Single funding input
1691
- assert!(cet.outputs.len() >= 1); // At least one output (dust may be filtered)
1692
- }
1693
-
1694
- // Verify refund transaction
1695
- assert_eq!(dlc_txs.refund.inputs.len(), 1); // Single funding input
1696
- assert!(dlc_txs.refund.outputs.len() >= 2); // At least two refund outputs
1697
- }
1698
-
1699
- #[test]
1700
- fn test_create_cet_wrapper() {
1701
- let local_output = TxOutput {
1702
- value: 100_000_000, // 1 BTC
1703
- script_pubkey: vec![
1704
- 0x00, 0x14, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
1705
- 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
1706
- ],
1707
- };
1708
-
1709
- let remote_output = TxOutput {
1710
- value: 100_000_000, // 1 BTC
1711
- script_pubkey: vec![
1712
- 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1713
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1714
- ],
1715
- };
1716
-
1717
- let result = create_cet(
1718
- local_output,
1719
- 1,
1720
- remote_output,
1721
- 2,
1722
- "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
1723
- 0,
1724
- 10,
1725
- );
1726
-
1727
- assert!(result.is_ok());
1728
- let cet = result.unwrap();
1729
-
1730
- assert_eq!(cet.lock_time, 10);
1731
- assert_eq!(cet.inputs.len(), 1);
1732
- assert_eq!(cet.outputs.len(), 2);
1733
- assert_eq!(cet.outputs[0].value, 100_000_000);
1734
- assert_eq!(cet.outputs[1].value, 100_000_000);
1735
- }
1736
-
1737
- #[test]
1738
- fn test_create_refund_transaction_wrapper() {
1739
- let local_script = vec![
1740
- 0x00, 0x14, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
1741
- 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
1742
- ];
1743
- let remote_script = vec![
1744
- 0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1745
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1746
- ];
1747
-
1748
- let result = create_refund_transaction(
1749
- local_script,
1750
- remote_script,
1751
- 100_000_000, // 1 BTC to local
1752
- 100_000_000, // 1 BTC to remote
1753
- 144, // locktime (1 day in blocks)
1754
- "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
1755
- 0,
1756
- );
1757
-
1758
- assert!(result.is_ok());
1759
- let refund_tx = result.unwrap();
1760
-
1761
- assert_eq!(refund_tx.lock_time, 144);
1762
- assert_eq!(refund_tx.inputs.len(), 1);
1763
- assert_eq!(refund_tx.outputs.len(), 2);
1764
- assert_eq!(refund_tx.outputs[0].value, 100_000_000);
1765
- assert_eq!(refund_tx.outputs[1].value, 100_000_000);
1766
- }
1767
-
1768
- #[test]
1769
- fn test_is_dust_output() {
1770
- let dust_output = TxOutput {
1771
- value: 500, // Below dust limit
1772
- script_pubkey: vec![],
1773
- };
1774
-
1775
- let non_dust_output = TxOutput {
1776
- value: 5000, // Above dust limit
1777
- script_pubkey: vec![],
1778
- };
1779
-
1780
- assert!(is_dust_output(dust_output));
1781
- assert!(!is_dust_output(non_dust_output));
1782
- }
1783
-
1784
- #[test]
1785
- fn test_conversion_functions() {
1786
- let (_offer_sk, offer_pk, _accept_sk, _accept_pk) = create_test_keys();
1787
-
1788
- // Test party params conversion
1789
- let params =
1790
- create_test_party_params(100_000_000, 50_000_000, offer_pk.serialize().to_vec(), 1);
1791
-
1792
- let rust_params = party_params_to_rust(&params).unwrap();
1793
- assert_eq!(rust_params.fund_pubkey, offer_pk);
1794
- assert_eq!(rust_params.input_amount, Amount::from_sat(100_000_000));
1795
- assert_eq!(rust_params.collateral, Amount::from_sat(50_000_000));
1796
-
1797
- // Test TX input conversion
1798
- let tx_input = TxInputInfo {
1799
- txid: "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456".to_string(),
1800
- vout: 0,
1801
- script_sig: vec![],
1802
- max_witness_length: 108,
1803
- serial_id: 1,
1804
- };
1805
-
1806
- let rust_input = tx_input_info_to_rust(&tx_input).unwrap();
1807
- assert_eq!(rust_input.serial_id, 1);
1808
- assert_eq!(rust_input.max_witness_len, 108);
1809
- assert_eq!(rust_input.outpoint.vout, 0);
1810
- }
1811
-
1812
- #[test]
1813
- fn test_transaction_bidirectional_conversion() {
1814
- // Create a test Bitcoin transaction
1815
- let btc_tx = BtcTransaction {
1816
- version: bitcoin::transaction::Version::TWO,
1817
- lock_time: LockTime::from_consensus(144),
1818
- input: vec![TxIn {
1819
- previous_output: OutPoint {
1820
- txid: Txid::from_str(
1821
- "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
1822
- )
1823
- .unwrap(),
1824
- vout: 0,
1825
- },
1826
- script_sig: ScriptBuf::new(),
1827
- sequence: Sequence::ZERO,
1828
- witness: Witness::new(),
1829
- }],
1830
- output: vec![BtcTxOut {
1831
- value: Amount::from_sat(100_000_000),
1832
- script_pubkey: ScriptBuf::from(vec![0x00, 0x14]),
1833
- }],
1834
- };
1835
-
1836
- // Convert to UniFFI format and back
1837
- let uniffi_tx = btc_tx_to_transaction(&btc_tx);
1838
- let converted_back = transaction_to_btc_tx(&uniffi_tx).unwrap();
1839
-
1840
- // Verify they're equivalent
1841
- assert_eq!(btc_tx.version, converted_back.version);
1842
- assert_eq!(btc_tx.lock_time, converted_back.lock_time);
1843
- assert_eq!(btc_tx.input.len(), converted_back.input.len());
1844
- assert_eq!(btc_tx.output.len(), converted_back.output.len());
1845
- assert_eq!(
1846
- btc_tx.input[0].previous_output,
1847
- converted_back.input[0].previous_output
1848
- );
1849
- assert_eq!(btc_tx.output[0].value, converted_back.output[0].value);
1850
- }
1851
-
1852
- #[test]
1853
- fn test_error_handling_invalid_keys() {
1854
- // Test invalid public key
1855
- let result = create_fund_tx_locking_script(
1856
- vec![0u8; 20], // Invalid key length
1857
- vec![1u8; 33],
1858
- );
1859
- assert!(matches!(result, Err(DLCError::InvalidPublicKey)));
1860
-
1861
- // Test invalid txid
1862
- let result = create_cet(
1863
- TxOutput {
1864
- value: 1000,
1865
- script_pubkey: vec![],
1866
- },
1867
- 1,
1868
- TxOutput {
1869
- value: 1000,
1870
- script_pubkey: vec![],
1871
- },
1872
- 2,
1873
- "invalid_txid".to_string(),
1874
- 0,
1875
- 0,
1876
- );
1877
- assert!(matches!(result, Err(DLCError::InvalidArgument(_))));
1878
- }
1879
-
1880
- fn get_p2wpkh_script_pubkey(secp: &Secp256k1<All>) -> ScriptBuf {
1881
- let mut rng = secp256k1_zkp::rand::thread_rng();
1882
- let sk = bitcoin::PrivateKey {
1883
- inner: SecretKey::new(&mut rng),
1884
- network: Network::Testnet.into(),
1885
- compressed: true,
1886
- };
1887
- let pk = CompressedPublicKey::from_private_key(secp, &sk).unwrap();
1888
- Address::p2wpkh(&pk, Network::Testnet).script_pubkey()
1889
- }
1890
-
1891
- fn get_party_params(
1892
- input_amount: u64,
1893
- collateral: u64,
1894
- serial_id: Option<u64>,
1895
- ) -> (PartyParams, SecretKey) {
1896
- let secp = Secp256k1::new();
1897
- let mut rng = secp256k1_zkp::rand::thread_rng();
1898
- let fund_privkey = SecretKey::new(&mut rng);
1899
- let serial_id = serial_id.unwrap_or(1);
1900
- (
1901
- PartyParams {
1902
- fund_pubkey: PublicKey::from_secret_key(&secp, &fund_privkey)
1903
- .serialize()
1904
- .to_vec(),
1905
- change_script_pubkey: get_p2wpkh_script_pubkey(&secp).into_bytes(),
1906
- change_serial_id: serial_id,
1907
- payout_script_pubkey: get_p2wpkh_script_pubkey(&secp).into_bytes(),
1908
- payout_serial_id: serial_id,
1909
- input_amount,
1910
- collateral,
1911
- inputs: vec![TxInputInfo {
1912
- txid: "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"
1913
- .to_string(),
1914
- vout: 0,
1915
- max_witness_length: 108,
1916
- script_sig: vec![],
1917
- serial_id,
1918
- }],
1919
- dlc_inputs: vec![],
1920
- },
1921
- fund_privkey,
1922
- )
1923
- }
1924
-
1925
- fn payouts_test() -> Vec<Payout> {
1926
- vec![
1927
- Payout {
1928
- offer: 100000000,
1929
- accept: 100000000,
1930
- },
1931
- Payout {
1932
- offer: 100000000,
1933
- accept: 100000000,
1934
- },
1935
- Payout {
1936
- offer: 100000000,
1937
- accept: 100000000,
1938
- },
1939
- ]
1940
- }
1941
-
1942
- fn signatures_to_secret(signatures: &[Vec<SchnorrSignature>]) -> SecretKey {
1943
- let s_values = signatures
1944
- .iter()
1945
- .flatten()
1946
- .map(|x| secp_utils::schnorrsig_decompose(x).unwrap().1)
1947
- .collect::<Vec<_>>();
1948
- let secret = SecretKey::from_slice(s_values[0]).unwrap();
1949
-
1950
- s_values.iter().skip(1).fold(secret, |accum, s| {
1951
- let sec = SecretKey::from_slice(s).unwrap();
1952
- accum.add_tweak(&Scalar::from(sec)).unwrap()
1953
- })
1954
- }
1955
-
1956
- /// Verify a signature for a given transaction input.
1957
- fn verify_tx_input_sig(
1958
- signature: Vec<u8>,
1959
- tx: Transaction,
1960
- input_index: usize,
1961
- script_pubkey: Vec<u8>,
1962
- value: u64,
1963
- pk: Vec<u8>,
1964
- ) -> Result<(), DLCError> {
1965
- let secp = get_secp_context();
1966
- let btc_txn = transaction_to_btc_tx(&tx)?;
1967
- let script = ScriptBuf::from_bytes(script_pubkey);
1968
- let sig = EcdsaSignature::from_der(&signature).map_err(|_| DLCError::InvalidSignature)?;
1969
- let pk = PublicKey::from_slice(&pk).map_err(|_| DLCError::InvalidPublicKey)?;
1970
- ddk_dlc::verify_tx_input_sig(
1971
- secp,
1972
- &sig,
1973
- &btc_txn,
1974
- input_index,
1975
- &script,
1976
- Amount::from_sat(value),
1977
- &pk,
1978
- )?;
1979
- Ok(())
1980
- }
1981
-
1982
- #[test]
1983
- fn create_cet_adaptor_sig_single_oracle_three_outcomes() {
1984
- // Arrange
1985
- let secp = Secp256k1::new();
1986
- let mut rng = secp256k1_zkp::rand::thread_rng();
1987
- let (offer_party_params, offer_fund_sk) =
1988
- get_party_params(1_000_000_000, 100_000_000, None);
1989
- let (accept_party_params, _accept_fund_sk) =
1990
- get_party_params(1_000_000_000, 100_000_000, None);
1991
-
1992
- let dlc_txs = create_dlc_transactions(
1993
- payouts_test(),
1994
- offer_party_params.clone(),
1995
- accept_party_params.clone(),
1996
- 100,
1997
- 4,
1998
- 10,
1999
- 10,
2000
- 0,
2001
- 0,
2002
- )
2003
- .unwrap();
2004
-
2005
- let cets = dlc_txs.cets;
2006
- const NB_ORACLES: usize = 1; // 1 oracle
2007
- const NB_OUTCOMES: usize = 3; // 3 outcomes (enumeration)
2008
- const NB_DIGITS: usize = 1; // 1 nonce for enumeration contract
2009
-
2010
- let mut oracle_infos: Vec<OracleInfo> = Vec::with_capacity(NB_ORACLES);
2011
- let mut oracle_sks: Vec<Keypair> = Vec::with_capacity(NB_ORACLES);
2012
- let mut oracle_sk_nonce: Vec<Vec<[u8; 32]>> = Vec::with_capacity(NB_ORACLES);
2013
- let mut oracle_sigs: Vec<Vec<SchnorrSignature>> = Vec::with_capacity(NB_ORACLES);
2014
-
2015
- // Messages: 3 outcomes × 1 oracle × 1 message per outcome
2016
- let messages: Vec<Vec<Vec<_>>> = (0..NB_OUTCOMES)
2017
- .map(|outcome_idx| {
2018
- vec![
2019
- // Single oracle
2020
- vec![
2021
- // Single message for this outcome
2022
- {
2023
- let message = &[outcome_idx as u8]; // Different message per outcome
2024
- let hash = sha256::Hash::hash(message).to_byte_array();
2025
- hash.to_vec()
2026
- },
2027
- ],
2028
- ]
2029
- })
2030
- .collect();
2031
-
2032
- // Setup single oracle with single nonce
2033
- for i in 0..NB_ORACLES {
2034
- // Runs once
2035
- let oracle_kp = Keypair::new(&secp, &mut rng);
2036
- let oracle_pubkey = oracle_kp.x_only_public_key().0;
2037
- let mut nonces: Vec<XOnlyPublicKey> = Vec::with_capacity(NB_DIGITS);
2038
- let mut sk_nonces: Vec<[u8; 32]> = Vec::with_capacity(NB_DIGITS);
2039
- oracle_sigs.push(Vec::with_capacity(NB_DIGITS));
2040
-
2041
- // Single nonce for enumeration
2042
- let mut sk_nonce = [0u8; 32];
2043
- rng.fill_bytes(&mut sk_nonce);
2044
- let oracle_r_kp = Keypair::from_seckey_slice(&secp, &sk_nonce).unwrap();
2045
- let nonce = XOnlyPublicKey::from_keypair(&oracle_r_kp).0;
2046
-
2047
- // Sign the first outcome's message with the single nonce
2048
- let sig = secp_utils::schnorrsig_sign_with_nonce(
2049
- &secp,
2050
- &Message::from_digest_slice(&messages[0][0][0]).unwrap(), // First outcome, first oracle, first message
2051
- &oracle_kp,
2052
- &sk_nonce,
2053
- );
2054
-
2055
- oracle_sigs[i].push(sig);
2056
- nonces.push(nonce);
2057
- sk_nonces.push(sk_nonce);
2058
-
2059
- oracle_infos.push(OracleInfo {
2060
- public_key: oracle_pubkey.serialize().to_vec(),
2061
- nonces: nonces.iter().map(|n| n.serialize().to_vec()).collect(), // Just 1 nonce
2062
- });
2063
- oracle_sk_nonce.push(sk_nonces);
2064
- oracle_sks.push(oracle_kp);
2065
- }
2066
- let funding_script_pubkey = ddk_dlc::make_funding_redeemscript(
2067
- &PublicKey::from_slice(&offer_party_params.fund_pubkey.clone()).unwrap(),
2068
- &PublicKey::from_slice(&accept_party_params.fund_pubkey.clone()).unwrap(),
2069
- );
2070
- let fund_output_value = dlc_txs.fund.outputs[0].value;
2071
-
2072
- // Act
2073
- let cet_sigs = create_cet_adaptor_sigs_from_oracle_info(
2074
- cets.clone(), // Use only first 3 CETs
2075
- oracle_infos.clone(),
2076
- offer_fund_sk.secret_bytes().to_vec(),
2077
- funding_script_pubkey.clone().into_bytes(),
2078
- fund_output_value,
2079
- messages.clone(),
2080
- )
2081
- .unwrap();
2082
-
2083
- let oracle_signatures = oracle_sigs
2084
- .iter()
2085
- .map(|s| s.iter().map(|s| s.serialize().to_vec()).collect::<Vec<_>>())
2086
- .collect::<Vec<_>>();
2087
-
2088
- let sign_res = sign_cet(
2089
- cets[0].clone(),
2090
- cet_sigs[0].signature.clone(),
2091
- oracle_signatures[0].clone(),
2092
- _accept_fund_sk.secret_bytes().to_vec(),
2093
- offer_party_params.fund_pubkey.clone(),
2094
- accept_party_params.fund_pubkey.clone(),
2095
- fund_output_value,
2096
- );
2097
-
2098
- assert!(sign_res.is_ok());
2099
-
2100
- let adaptor_secret = signatures_to_secret(&oracle_sigs);
2101
- let signature = vec_to_ecdsa_adaptor_signature(cet_sigs[0].signature.clone()).unwrap();
2102
- let adapted_sig = signature.decrypt(&adaptor_secret).unwrap();
2103
-
2104
- let batch_verify = verify_cet_adaptor_sigs_from_oracle_info(
2105
- cet_sigs.clone(),
2106
- cets.clone(),
2107
- oracle_infos.clone(),
2108
- offer_party_params.fund_pubkey.clone(),
2109
- funding_script_pubkey.clone().into_bytes(),
2110
- fund_output_value,
2111
- messages.clone(),
2112
- );
2113
-
2114
- assert!(batch_verify);
2115
-
2116
- // Assert
2117
- assert_eq!(cet_sigs.len(), 3, "Should have 3 CET signatures");
2118
- assert!(cet_sigs
2119
- .iter()
2120
- .enumerate()
2121
- .all(|(i, x)| verify_cet_adaptor_sig_from_oracle_info(
2122
- x.clone(),
2123
- cets[i].clone(),
2124
- oracle_infos.clone(),
2125
- offer_party_params.fund_pubkey.clone(),
2126
- funding_script_pubkey.clone().into_bytes(),
2127
- fund_output_value,
2128
- messages[i].clone(),
2129
- )));
2130
- sign_res.expect("Error signing CET");
2131
- verify_tx_input_sig(
2132
- adapted_sig.serialize_der().to_vec(),
2133
- cets[0].clone(),
2134
- 0,
2135
- funding_script_pubkey.clone().into_bytes(),
2136
- fund_output_value,
2137
- offer_party_params.fund_pubkey.clone(),
2138
- )
2139
- .expect("Invalid decrypted adaptor signature");
2140
- }
2141
-
2142
- #[test]
2143
- fn test_extract_ecdsa_signature_from_oracle_signatures() {
2144
- // Setup test data (similar to the main test)
2145
- let secp = Secp256k1::new();
2146
- let mut rng = secp256k1_zkp::rand::thread_rng();
2147
- let (offer_party_params, offer_fund_sk) =
2148
- get_party_params(1_000_000_000, 100_000_000, None);
2149
- let (accept_party_params, _accept_fund_sk) =
2150
- get_party_params(1_000_000_000, 100_000_000, None);
2151
-
2152
- let dlc_txs = create_dlc_transactions(
2153
- payouts_test(),
2154
- offer_party_params.clone(),
2155
- accept_party_params.clone(),
2156
- 100,
2157
- 4,
2158
- 10,
2159
- 10,
2160
- 0,
2161
- 0,
2162
- )
2163
- .unwrap();
2164
-
2165
- let cets = dlc_txs.cets;
2166
- const NB_ORACLES: usize = 1; // 1 oracle
2167
- const NB_OUTCOMES: usize = 3; // 3 outcomes (enumeration)
2168
- const NB_DIGITS: usize = 1; // 1 nonce for enumeration contract
2169
-
2170
- let mut oracle_infos: Vec<OracleInfo> = Vec::with_capacity(NB_ORACLES);
2171
- let mut oracle_sks: Vec<Keypair> = Vec::with_capacity(NB_ORACLES);
2172
- let mut oracle_sk_nonce: Vec<Vec<[u8; 32]>> = Vec::with_capacity(NB_ORACLES);
2173
- let mut oracle_sigs: Vec<Vec<SchnorrSignature>> = Vec::with_capacity(NB_ORACLES);
2174
-
2175
- // Messages: 3 outcomes × 1 oracle × 1 message per outcome
2176
- let messages: Vec<Vec<Vec<_>>> = (0..NB_OUTCOMES)
2177
- .map(|outcome_idx| {
2178
- vec![
2179
- // Single oracle
2180
- vec![
2181
- // Single message for this outcome
2182
- {
2183
- let message = &[outcome_idx as u8]; // Different message per outcome
2184
- let hash = sha256::Hash::hash(message).to_byte_array();
2185
- hash.to_vec()
2186
- },
2187
- ],
2188
- ]
2189
- })
2190
- .collect();
2191
-
2192
- // Setup single oracle with single nonce
2193
- for i in 0..NB_ORACLES {
2194
- // Runs once
2195
- let oracle_kp = Keypair::new(&secp, &mut rng);
2196
- let oracle_pubkey = oracle_kp.x_only_public_key().0;
2197
- let mut nonces: Vec<XOnlyPublicKey> = Vec::with_capacity(NB_DIGITS);
2198
- let mut sk_nonces: Vec<[u8; 32]> = Vec::with_capacity(NB_DIGITS);
2199
- oracle_sigs.push(Vec::with_capacity(NB_DIGITS));
2200
-
2201
- // Single nonce for enumeration
2202
- let mut sk_nonce = [0u8; 32];
2203
- rng.fill_bytes(&mut sk_nonce);
2204
- let oracle_r_kp = Keypair::from_seckey_slice(&secp, &sk_nonce).unwrap();
2205
- let nonce = XOnlyPublicKey::from_keypair(&oracle_r_kp).0;
2206
-
2207
- // Sign the first outcome's message with the single nonce
2208
- let sig = secp_utils::schnorrsig_sign_with_nonce(
2209
- &secp,
2210
- &Message::from_digest_slice(&messages[0][0][0]).unwrap(), // First outcome, first oracle, first message
2211
- &oracle_kp,
2212
- &sk_nonce,
2213
- );
2214
-
2215
- oracle_sigs[i].push(sig);
2216
- nonces.push(nonce);
2217
- sk_nonces.push(sk_nonce);
2218
-
2219
- oracle_infos.push(OracleInfo {
2220
- public_key: oracle_pubkey.serialize().to_vec(),
2221
- nonces: nonces.iter().map(|n| n.serialize().to_vec()).collect(), // Just 1 nonce
2222
- });
2223
- oracle_sk_nonce.push(sk_nonces);
2224
- oracle_sks.push(oracle_kp);
2225
- }
2226
-
2227
- let funding_script_pubkey = ddk_dlc::make_funding_redeemscript(
2228
- &PublicKey::from_slice(&offer_party_params.fund_pubkey.clone()).unwrap(),
2229
- &PublicKey::from_slice(&accept_party_params.fund_pubkey.clone()).unwrap(),
2230
- );
2231
- let fund_output_value = dlc_txs.fund.outputs[0].value;
2232
-
2233
- // Create adaptor signatures
2234
- let cet_sigs = create_cet_adaptor_sigs_from_oracle_info(
2235
- cets.clone(),
2236
- oracle_infos.clone(),
2237
- offer_fund_sk.secret_bytes().to_vec(),
2238
- funding_script_pubkey.clone().into_bytes(),
2239
- fund_output_value,
2240
- messages.clone(),
2241
- )
2242
- .unwrap();
2243
-
2244
- // Convert oracle signatures to the format expected by our function
2245
- let oracle_signatures = oracle_sigs
2246
- .iter()
2247
- .map(|s| s.iter().map(|s| s.serialize().to_vec()).collect::<Vec<_>>())
2248
- .collect::<Vec<_>>();
2249
-
2250
- // Test our new function
2251
- let result = extract_ecdsa_signature_from_oracle_signatures(
2252
- oracle_signatures[0].clone(),
2253
- cet_sigs[0].signature.clone(),
2254
- );
2255
-
2256
- assert!(result.is_ok(), "Function should succeed");
2257
-
2258
- let ecdsa_sig_bytes = result.unwrap();
2259
- assert!(
2260
- !ecdsa_sig_bytes.is_empty(),
2261
- "Should return non-empty signature"
2262
- );
2263
-
2264
- // Verify the signature is valid DER format
2265
- let ecdsa_sig = EcdsaSignature::from_der(&ecdsa_sig_bytes);
2266
- assert!(ecdsa_sig.is_ok(), "Should be valid DER signature");
2267
- }
2268
-
2269
- #[test]
2270
- fn test_get_cet_sighash() {
2271
- // Setup: Create DLC transactions to get a valid CET
2272
- let (offer_party_params, _offer_fund_sk) =
2273
- get_party_params(1_000_000_000, 100_000_000, None);
2274
- let (accept_party_params, _accept_fund_sk) =
2275
- get_party_params(1_000_000_000, 100_000_000, Some(2));
2276
-
2277
- let dlc_txs = create_dlc_transactions(
2278
- payouts_test(),
2279
- offer_party_params.clone(),
2280
- accept_party_params.clone(),
2281
- 100,
2282
- 4,
2283
- 10,
2284
- 10,
2285
- 0,
2286
- 0,
2287
- )
2288
- .unwrap();
2289
-
2290
- let cet = dlc_txs.cets[0].clone();
2291
- let funding_script_pubkey = ddk_dlc::make_funding_redeemscript(
2292
- &PublicKey::from_slice(&offer_party_params.fund_pubkey).unwrap(),
2293
- &PublicKey::from_slice(&accept_party_params.fund_pubkey).unwrap(),
2294
- );
2295
- let fund_output_value = dlc_txs.fund.outputs[0].value;
2296
-
2297
- // Act: Get the sighash
2298
- let result = get_cet_sighash(
2299
- cet.clone(),
2300
- funding_script_pubkey.clone().into_bytes(),
2301
- fund_output_value,
2302
- );
2303
-
2304
- // Assert
2305
- assert!(result.is_ok(), "get_cet_sighash should succeed");
2306
- let sighash = result.unwrap();
2307
- assert_eq!(sighash.len(), 32, "Sighash should be 32 bytes");
2308
-
2309
- // Verify against direct ddk-dlc call
2310
- let btc_tx = transaction_to_btc_tx(&cet).unwrap();
2311
- let direct_sighash = ddk_dlc::util::get_sig_hash_msg(
2312
- &btc_tx,
2313
- 0,
2314
- Script::from_bytes(&funding_script_pubkey.clone().into_bytes()),
2315
- Amount::from_sat(fund_output_value),
2316
- )
2317
- .unwrap();
2318
-
2319
- assert_eq!(
2320
- sighash,
2321
- direct_sighash.as_ref().to_vec(),
2322
- "Sighash should match direct ddk-dlc calculation"
2323
- );
2324
- }
2325
-
2326
- #[test]
2327
- fn test_get_cet_adaptor_signature_inputs() {
2328
- // Setup: Create DLC transactions and oracle info
2329
- let secp = Secp256k1::new();
2330
- let mut rng = secp256k1_zkp::rand::thread_rng();
2331
- let (offer_party_params, _offer_fund_sk) =
2332
- get_party_params(1_000_000_000, 100_000_000, None);
2333
- let (accept_party_params, _accept_fund_sk) =
2334
- get_party_params(1_000_000_000, 100_000_000, Some(2));
2335
-
2336
- let dlc_txs = create_dlc_transactions(
2337
- payouts_test(),
2338
- offer_party_params.clone(),
2339
- accept_party_params.clone(),
2340
- 100,
2341
- 4,
2342
- 10,
2343
- 10,
2344
- 0,
2345
- 0,
2346
- )
2347
- .unwrap();
2348
-
2349
- let cet = dlc_txs.cets[0].clone();
2350
- let funding_script_pubkey = ddk_dlc::make_funding_redeemscript(
2351
- &PublicKey::from_slice(&offer_party_params.fund_pubkey).unwrap(),
2352
- &PublicKey::from_slice(&accept_party_params.fund_pubkey).unwrap(),
2353
- );
2354
- let fund_output_value = dlc_txs.fund.outputs[0].value;
2355
-
2356
- // Create oracle info (single oracle, single nonce for enumeration)
2357
- let oracle_kp = Keypair::new(&secp, &mut rng);
2358
- let oracle_pubkey = oracle_kp.x_only_public_key().0;
2359
- let mut sk_nonce = [0u8; 32];
2360
- rng.fill_bytes(&mut sk_nonce);
2361
- let oracle_r_kp = Keypair::from_seckey_slice(&secp, &sk_nonce).unwrap();
2362
- let nonce = XOnlyPublicKey::from_keypair(&oracle_r_kp).0;
2363
-
2364
- let oracle_info = vec![OracleInfo {
2365
- public_key: oracle_pubkey.serialize().to_vec(),
2366
- nonces: vec![nonce.serialize().to_vec()],
2367
- }];
2368
-
2369
- // Create message (first outcome)
2370
- let message = &[0u8];
2371
- let hash = sha256::Hash::hash(message).to_byte_array();
2372
- let msgs = vec![vec![hash.to_vec()]]; // Single oracle, single message
2373
-
2374
- // Act: Get debug info
2375
- let result = get_cet_adaptor_signature_inputs(
2376
- cet.clone(),
2377
- oracle_info.clone(),
2378
- funding_script_pubkey.clone().into_bytes(),
2379
- fund_output_value,
2380
- msgs.clone(),
2381
- );
2382
-
2383
- // Assert
2384
- assert!(
2385
- result.is_ok(),
2386
- "get_cet_adaptor_signature_inputs should succeed"
2387
- );
2388
- let debug_info = result.unwrap();
2389
-
2390
- // Verify sighash
2391
- assert_eq!(debug_info.sighash.len(), 32, "Sighash should be 32 bytes");
2392
- let expected_sighash = get_cet_sighash(
2393
- cet.clone(),
2394
- funding_script_pubkey.clone().into_bytes(),
2395
- fund_output_value,
2396
- )
2397
- .unwrap();
2398
- assert_eq!(
2399
- debug_info.sighash, expected_sighash,
2400
- "Sighash should match get_cet_sighash result"
2401
- );
2402
-
2403
- // Verify adaptor point
2404
- assert_eq!(
2405
- debug_info.adaptor_point.len(),
2406
- 33,
2407
- "Adaptor point should be 33 bytes (compressed pubkey)"
2408
- );
2409
-
2410
- // Verify input index is always 0 for CETs
2411
- assert_eq!(
2412
- debug_info.input_index, 0,
2413
- "Input index should always be 0 for CETs"
2414
- );
2415
-
2416
- // Verify script_pubkey matches what we passed in
2417
- assert_eq!(
2418
- debug_info.script_pubkey,
2419
- funding_script_pubkey.clone().into_bytes(),
2420
- "Script pubkey should match input"
2421
- );
2422
-
2423
- // Verify value matches
2424
- assert_eq!(
2425
- debug_info.value, fund_output_value,
2426
- "Value should match input"
2427
- );
2428
-
2429
- // Verify cet_txid is valid
2430
- let btc_tx = transaction_to_btc_tx(&cet).unwrap();
2431
- assert_eq!(
2432
- debug_info.cet_txid,
2433
- btc_tx.compute_txid().to_string(),
2434
- "CET txid should match"
2435
- );
2436
-
2437
- // Verify cet_raw matches input
2438
- assert_eq!(
2439
- debug_info.cet_raw, cet.raw_bytes,
2440
- "CET raw bytes should match input"
2441
- );
2442
- }
2443
-
2444
- #[test]
2445
- fn test_get_cet_sighash_invalid_transaction() {
2446
- // Create an invalid transaction (empty raw_bytes)
2447
- let invalid_tx = Transaction {
2448
- version: 2,
2449
- lock_time: 0,
2450
- inputs: vec![],
2451
- outputs: vec![],
2452
- raw_bytes: vec![0x00], // Invalid serialization
2453
- };
2454
-
2455
- let result = get_cet_sighash(invalid_tx, vec![0x00, 0x14], 100_000);
2456
-
2457
- assert!(
2458
- result.is_err(),
2459
- "Should fail with invalid transaction bytes"
2460
- );
2461
- }
2462
-
2463
- #[test]
2464
- fn test_get_cet_adaptor_signature_inputs_invalid_oracle_pubkey() {
2465
- // Setup valid CET
2466
- let (offer_party_params, _) = get_party_params(1_000_000_000, 100_000_000, None);
2467
- let (accept_party_params, _) = get_party_params(1_000_000_000, 100_000_000, Some(2));
2468
-
2469
- let dlc_txs = create_dlc_transactions(
2470
- payouts_test(),
2471
- offer_party_params.clone(),
2472
- accept_party_params.clone(),
2473
- 100,
2474
- 4,
2475
- 10,
2476
- 10,
2477
- 0,
2478
- 0,
2479
- )
2480
- .unwrap();
2481
-
2482
- let cet = dlc_txs.cets[0].clone();
2483
- let funding_script_pubkey = ddk_dlc::make_funding_redeemscript(
2484
- &PublicKey::from_slice(&offer_party_params.fund_pubkey).unwrap(),
2485
- &PublicKey::from_slice(&accept_party_params.fund_pubkey).unwrap(),
2486
- );
2487
-
2488
- // Invalid oracle info (wrong pubkey length)
2489
- let invalid_oracle_info = vec![OracleInfo {
2490
- public_key: vec![0x00; 20], // Invalid: should be 32 bytes for x-only
2491
- nonces: vec![vec![0x00; 32]],
2492
- }];
2493
-
2494
- let msgs = vec![vec![vec![0u8; 32]]];
2495
-
2496
- let result = get_cet_adaptor_signature_inputs(
2497
- cet,
2498
- invalid_oracle_info,
2499
- funding_script_pubkey.into_bytes(),
2500
- 100_000,
2501
- msgs,
2502
- );
2503
-
2504
- assert!(
2505
- result.is_err(),
2506
- "Should fail with invalid oracle public key"
2507
- );
2508
- }
2509
- }