@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.
- package/DdkRn.podspec +1 -1
- package/README.md +14 -0
- package/android/CMakeLists.txt +6 -3
- package/android/build.gradle +0 -1
- package/android/cpp-adapter.cpp +6 -26
- package/android/src/main/jniLibs/arm64-v8a/libddk_ffi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libddk_ffi.so +0 -0
- package/android/src/main/jniLibs/x86/libddk_ffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libddk_ffi.so +0 -0
- package/cpp/bennyblader-ddk-rn.cpp +1 -1
- package/cpp/ddk_ffi.cpp +2361 -2671
- package/cpp/ddk_ffi.hpp +86 -255
- package/ios/DdkRn.xcframework/Info.plist +43 -0
- package/ios/DdkRn.xcframework/ios-arm64/libddk_ffi.a +0 -0
- package/ios/DdkRn.xcframework/ios-arm64-simulator/libddk_ffi.a +0 -0
- package/lib/commonjs/ddk_ffi-ffi.js +6 -29
- package/lib/commonjs/ddk_ffi-ffi.js.map +1 -1
- package/lib/commonjs/ddk_ffi.js +961 -944
- package/lib/commonjs/ddk_ffi.js.map +1 -1
- package/lib/module/ddk_ffi-ffi.js +6 -29
- package/lib/module/ddk_ffi-ffi.js.map +1 -1
- package/lib/module/ddk_ffi.js +924 -896
- package/lib/module/ddk_ffi.js.map +1 -1
- package/lib/typescript/commonjs/src/ddk_ffi-ffi.d.ts +71 -96
- package/lib/typescript/commonjs/src/ddk_ffi-ffi.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ddk_ffi.d.ts +500 -471
- package/lib/typescript/commonjs/src/ddk_ffi.d.ts.map +1 -1
- package/lib/typescript/module/src/ddk_ffi-ffi.d.ts +71 -96
- package/lib/typescript/module/src/ddk_ffi-ffi.d.ts.map +1 -1
- package/lib/typescript/module/src/ddk_ffi.d.ts +500 -471
- package/lib/typescript/module/src/ddk_ffi.d.ts.map +1 -1
- package/package.json +8 -38
- package/src/ddk_ffi-ffi.ts +419 -170
- package/src/ddk_ffi.ts +2648 -1939
- package/ubrn.config.yaml +18 -1
- package/ddk-ffi/Cargo.lock +0 -1013
- package/ddk-ffi/Cargo.toml +0 -36
- package/ddk-ffi/build.rs +0 -3
- package/ddk-ffi/rust-toolchain.toml +0 -10
- package/ddk-ffi/src/ddk_ffi.udl +0 -376
- package/ddk-ffi/src/lib.rs +0 -2509
- package/ddk-ffi/src/uniffi-bindgen.rs +0 -3
- package/ddk-ffi/uniffi.toml +0 -18
- package/scripts/apply-hotfix.js +0 -96
- package/scripts/postinstall.js +0 -298
- package/scripts/prepare-rust-src.js +0 -51
package/ddk-ffi/src/lib.rs
DELETED
|
@@ -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(¶ms.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(¶ms)?;
|
|
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(¶ms).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(¶ms).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
|
-
}
|