1use std::convert::TryInto;
2
3use elements::bitcoin::{self, secp256k1};
4use elements::encode::{deserialize, serialize};
5use elements::hashes::Hash;
6use elements::secp256k1_zkp::{
7 Generator, PedersenCommitment, PublicKey, RangeProof, SurjectionProof, Tweak,
8};
9use elements::{
10 confidential, AssetIssuance, OutPoint, Script, Transaction, TxIn, TxInWitness, TxOut,
11 TxOutWitness,
12};
13
14use crate::confidential::{
15 ConfidentialAssetInfo, ConfidentialNonceInfo, ConfidentialType, ConfidentialValueInfo,
16};
17use crate::tx::{
18 AssetIssuanceInfo, InputInfo, InputScriptInfo, InputWitnessInfo, OutputInfo, OutputScriptInfo,
19 OutputWitnessInfo, PeginDataInfo, PegoutDataInfo, TransactionInfo,
20};
21use crate::Network;
22
23#[derive(Debug, thiserror::Error)]
24pub enum TxError {
25 #[error("invalid JSON provided: {0}")]
26 JsonParse(serde_json::Error),
27
28 #[error("failed to decode raw transaction hex: {0}")]
29 TxHex(hex::FromHexError),
30
31 #[error("invalid tx format: {0}")]
32 TxDeserialize(elements::encode::Error),
33
34 #[error("field \"{field}\" is required.")]
35 MissingField {
36 field: String,
37 },
38
39 #[error("invalid prevout format: {0}")]
40 PrevoutParse(bitcoin::blockdata::transaction::ParseOutPointError),
41
42 #[error("txid field given without vout field")]
43 MissingVout,
44
45 #[error("conflicting prevout information")]
46 ConflictingPrevout,
47
48 #[error("no previous output provided")]
49 NoPrevout,
50
51 #[error("invalid confidential commitment: {0}")]
52 ConfidentialCommitment(elements::secp256k1_zkp::Error),
53
54 #[error("invalid confidential publicKey: {0}")]
55 ConfidentialCommitmentPublicKey(secp256k1::Error),
56
57 #[error("wrong size of nonce field")]
58 NonceSize,
59
60 #[error("invalid size of asset_entropy")]
61 AssetEntropySize,
62
63 #[error("invalid asset_blinding_nonce: {0}")]
64 AssetBlindingNonce(elements::secp256k1_zkp::Error),
65
66 #[error("decoding script assembly is not yet supported")]
67 AsmNotSupported,
68
69 #[error("no scriptSig info provided")]
70 NoScriptSig,
71
72 #[error("no scriptPubKey info provided")]
73 NoScriptPubKey,
74
75 #[error("invalid outpoint in pegin_data: {0}")]
76 PeginOutpoint(bitcoin::blockdata::transaction::ParseOutPointError),
77
78 #[error("outpoint in pegin_data does not correspond to input value")]
79 PeginOutpointMismatch,
80
81 #[error("asset in pegin_data should be explicit")]
82 PeginAssetNotExplicit,
83
84 #[error("invalid rangeproof: {0}")]
85 RangeProof(elements::secp256k1_zkp::Error),
86
87 #[error("invalid sequence: {0}")]
88 Sequence(core::num::TryFromIntError),
89
90 #[error("addresses for different networks are used in the output scripts")]
91 MixedNetworks,
92
93 #[error("invalid surjection proof: {0}")]
94 SurjectionProof(elements::secp256k1_zkp::Error),
95
96 #[error("value in pegout_data does not correspond to output value")]
97 PegoutValueMismatch,
98
99 #[error("explicit value is required for pegout data")]
100 PegoutValueNotExplicit,
101
102 #[error("asset in pegout_data does not correspond to output value")]
103 PegoutAssetMismatch,
104}
105
106fn outpoint_from_input_info(input: &InputInfo) -> Result<OutPoint, TxError> {
108 let op1: Option<OutPoint> =
109 input.prevout.as_ref().map(|op| op.parse().map_err(TxError::PrevoutParse)).transpose()?;
110 let op2 = match input.txid {
111 Some(txid) => match input.vout {
112 Some(vout) => Some(OutPoint {
113 txid,
114 vout,
115 }),
116 None => return Err(TxError::MissingVout),
117 },
118 None => None,
119 };
120
121 match (op1, op2) {
122 (Some(op1), Some(op2)) => {
123 if op1 != op2 {
124 return Err(TxError::ConflictingPrevout);
125 }
126 Ok(op1)
127 }
128 (Some(op), None) => Ok(op),
129 (None, Some(op)) => Ok(op),
130 (None, None) => Err(TxError::NoPrevout),
131 }
132}
133
134fn bytes_32(bytes: &[u8]) -> Option<[u8; 32]> {
135 if bytes.len() != 32 {
136 None
137 } else {
138 let mut array = [0; 32];
139 for (x, y) in bytes.iter().zip(array.iter_mut()) {
140 *y = *x;
141 }
142 Some(array)
143 }
144}
145
146fn create_confidential_value(info: ConfidentialValueInfo) -> Result<confidential::Value, TxError> {
147 match info.type_ {
148 ConfidentialType::Null => Ok(confidential::Value::Null),
149 ConfidentialType::Explicit => {
150 Ok(confidential::Value::Explicit(info.value.ok_or_else(|| TxError::MissingField {
151 field: "value".to_string(),
152 })?))
153 }
154 ConfidentialType::Confidential => {
155 let commitment_data = info.commitment.ok_or_else(|| TxError::MissingField {
156 field: "commitment".to_string(),
157 })?;
158 let comm = PedersenCommitment::from_slice(&commitment_data.0[..])
159 .map_err(TxError::ConfidentialCommitment)?;
160 Ok(confidential::Value::Confidential(comm))
161 }
162 }
163}
164
165fn create_confidential_asset(info: ConfidentialAssetInfo) -> Result<confidential::Asset, TxError> {
166 match info.type_ {
167 ConfidentialType::Null => Ok(confidential::Asset::Null),
168 ConfidentialType::Explicit => {
169 Ok(confidential::Asset::Explicit(info.asset.ok_or_else(|| TxError::MissingField {
170 field: "asset".to_string(),
171 })?))
172 }
173 ConfidentialType::Confidential => {
174 let commitment_data = info.commitment.ok_or_else(|| TxError::MissingField {
175 field: "commitment".to_string(),
176 })?;
177 let gen = Generator::from_slice(&commitment_data.0[..])
178 .map_err(TxError::ConfidentialCommitment)?;
179 Ok(confidential::Asset::Confidential(gen))
180 }
181 }
182}
183
184fn create_confidential_nonce(info: ConfidentialNonceInfo) -> Result<confidential::Nonce, TxError> {
185 match info.type_ {
186 ConfidentialType::Null => Ok(confidential::Nonce::Null),
187 ConfidentialType::Explicit => {
188 let nonce = info.nonce.ok_or_else(|| TxError::MissingField {
189 field: "nonce".to_string(),
190 })?;
191 let bytes = bytes_32(&nonce.0[..]).ok_or(TxError::NonceSize)?;
192 Ok(confidential::Nonce::Explicit(bytes))
193 }
194 ConfidentialType::Confidential => {
195 let commitment_data = info.commitment.ok_or_else(|| TxError::MissingField {
196 field: "commitment".to_string(),
197 })?;
198 let pubkey = PublicKey::from_slice(&commitment_data.0[..])
199 .map_err(TxError::ConfidentialCommitmentPublicKey)?;
200 Ok(confidential::Nonce::Confidential(pubkey))
201 }
202 }
203}
204
205fn create_asset_issuance(info: AssetIssuanceInfo) -> Result<AssetIssuance, TxError> {
206 let asset_blinding_nonce_data =
207 info.asset_blinding_nonce.ok_or_else(|| TxError::MissingField {
208 field: "asset_blinding_nonce".to_string(),
209 })?;
210 let asset_blinding_nonce =
211 Tweak::from_slice(&asset_blinding_nonce_data.0[..]).map_err(TxError::AssetBlindingNonce)?;
212
213 let asset_entropy_data = info.asset_entropy.ok_or_else(|| TxError::MissingField {
214 field: "asset_entropy".to_string(),
215 })?;
216 let asset_entropy = bytes_32(&asset_entropy_data.0[..]).ok_or(TxError::AssetEntropySize)?;
217
218 let amount_info = info.amount.ok_or_else(|| TxError::MissingField {
219 field: "amount".to_string(),
220 })?;
221 let amount = create_confidential_value(amount_info)?;
222
223 let inflation_keys_info = info.inflation_keys.ok_or_else(|| TxError::MissingField {
224 field: "inflation_keys".to_string(),
225 })?;
226 let inflation_keys = create_confidential_value(inflation_keys_info)?;
227
228 Ok(AssetIssuance {
229 asset_blinding_nonce,
230 asset_entropy,
231 amount,
232 inflation_keys,
233 })
234}
235
236fn create_script_sig(ss: InputScriptInfo) -> Result<Script, TxError> {
237 if let Some(hex) = ss.hex {
238 Ok(hex.0.into())
239 } else if ss.asm.is_some() {
240 Err(TxError::AsmNotSupported)
241 } else {
242 Err(TxError::NoScriptSig)
243 }
244}
245
246fn create_pegin_witness(
247 pd: PeginDataInfo,
248 prevout: bitcoin::OutPoint,
249) -> Result<Vec<Vec<u8>>, TxError> {
250 let parsed_outpoint = pd.outpoint.parse().map_err(TxError::PeginOutpoint)?;
251 if prevout != parsed_outpoint {
252 return Err(TxError::PeginOutpointMismatch);
253 }
254
255 let asset = match create_confidential_asset(pd.asset)? {
256 confidential::Asset::Explicit(asset) => asset,
257 _ => return Err(TxError::PeginAssetNotExplicit),
258 };
259 Ok(vec![
260 serialize(&pd.value),
261 serialize(&asset),
262 pd.genesis_hash.to_byte_array().to_vec(),
263 serialize(&pd.claim_script.0),
264 serialize(&pd.mainchain_tx_hex.0),
265 serialize(&pd.merkle_proof.0),
266 ])
267}
268
269fn convert_outpoint_to_btc(p: elements::OutPoint) -> bitcoin::OutPoint {
270 bitcoin::OutPoint {
271 txid: bitcoin::Txid::from_byte_array(p.txid.to_byte_array()),
272 vout: p.vout,
273 }
274}
275
276fn create_input_witness(
277 info: Option<InputWitnessInfo>,
278 pd: Option<PeginDataInfo>,
279 prevout: OutPoint,
280) -> Result<TxInWitness, TxError> {
281 let pegin_witness =
282 if let Some(info_wit) = info.as_ref().and_then(|info| info.pegin_witness.as_ref()) {
283 info_wit.iter().map(|h| h.clone().0).collect()
284 } else if let Some(pd) = pd {
285 create_pegin_witness(pd, convert_outpoint_to_btc(prevout))?
286 } else {
287 Default::default()
288 };
289
290 if let Some(wi) = info {
291 let amount_rangeproof = wi
292 .amount_rangeproof
293 .map(|b| RangeProof::from_slice(&b.0).map_err(TxError::RangeProof).map(Box::new))
294 .transpose()?;
295 let inflation_keys_rangeproof = wi
296 .inflation_keys_rangeproof
297 .map(|b| RangeProof::from_slice(&b.0).map_err(TxError::RangeProof).map(Box::new))
298 .transpose()?;
299
300 Ok(TxInWitness {
301 amount_rangeproof,
302 inflation_keys_rangeproof,
303 script_witness: match wi.script_witness {
304 Some(ref w) => w.iter().map(|h| h.clone().0).collect(),
305 None => Vec::new(),
306 },
307 pegin_witness,
308 })
309 } else {
310 Ok(TxInWitness {
311 pegin_witness,
312 ..Default::default()
313 })
314 }
315}
316
317fn create_input(input: InputInfo) -> Result<TxIn, TxError> {
318 let has_issuance = input.has_issuance.unwrap_or(input.asset_issuance.is_some());
319 let is_pegin = input.is_pegin.unwrap_or(input.pegin_data.is_some());
320 let prevout = outpoint_from_input_info(&input)?;
321
322 let script_sig = input.script_sig.map(create_script_sig).transpose()?.unwrap_or_default();
323
324 let sequence = elements::Sequence::from_height(
325 input.sequence.unwrap_or_default().try_into().map_err(TxError::Sequence)?,
326 );
327
328 let asset_issuance = if has_issuance {
329 input.asset_issuance.map(create_asset_issuance).transpose()?.unwrap_or_default()
330 } else {
331 Default::default()
332 };
333
334 let witness = create_input_witness(input.witness, input.pegin_data, prevout)?;
335
336 Ok(TxIn {
337 previous_output: prevout,
338 script_sig,
339 sequence,
340 is_pegin,
341 asset_issuance,
342 witness,
343 })
344}
345
346fn create_script_pubkey(
347 spk: OutputScriptInfo,
348 used_network: &mut Option<Network>,
349) -> Result<Script, TxError> {
350 if let Some(hex) = spk.hex {
351 Ok(hex.0.into())
353 } else if spk.asm.is_some() {
354 Err(TxError::AsmNotSupported)
355 } else if let Some(address) = spk.address {
356 if let Some(network) = Network::from_params(address.params) {
358 if used_network.replace(network).unwrap_or(network) != network {
359 return Err(TxError::MixedNetworks);
360 }
361 }
362 Ok(address.script_pubkey())
363 } else {
364 Err(TxError::NoScriptPubKey)
365 }
366}
367
368fn create_bitcoin_script_pubkey(
369 spk: hal::tx::OutputScriptInfo,
370) -> Result<bitcoin::ScriptBuf, TxError> {
371 if let Some(hex) = spk.hex {
372 Ok(hex.0.into())
374 } else if spk.asm.is_some() {
375 Err(TxError::AsmNotSupported)
376 } else if let Some(address) = spk.address {
377 Ok(address.assume_checked().script_pubkey())
378 } else {
379 Err(TxError::NoScriptPubKey)
380 }
381}
382
383fn create_output_witness(w: OutputWitnessInfo) -> Result<TxOutWitness, TxError> {
384 let surjection_proof = w
385 .surjection_proof
386 .map(|b| {
387 SurjectionProof::from_slice(&b.0[..]).map_err(TxError::SurjectionProof).map(Box::new)
388 })
389 .transpose()?;
390 let rangeproof = w
391 .rangeproof
392 .map(|b| RangeProof::from_slice(&b.0[..]).map_err(TxError::RangeProof).map(Box::new))
393 .transpose()?;
394
395 Ok(TxOutWitness {
396 surjection_proof,
397 rangeproof,
398 })
399}
400
401fn create_script_pubkey_from_pegout_data(pd: PegoutDataInfo) -> Result<Script, TxError> {
402 let script_pubkey = create_bitcoin_script_pubkey(pd.script_pub_key)?;
403 let mut builder = elements::script::Builder::new()
404 .push_opcode(elements::opcodes::all::OP_RETURN)
405 .push_slice(&pd.genesis_hash.to_byte_array())
406 .push_slice(script_pubkey.as_bytes());
407 for d in pd.extra_data {
408 builder = builder.push_slice(&d.0);
409 }
410 Ok(builder.into_script())
411}
412
413fn create_output(output: OutputInfo) -> Result<TxOut, TxError> {
414 let mut used_network = None;
417 let value_info = output.value.ok_or_else(|| TxError::MissingField {
418 field: "value".to_string(),
419 })?;
420 let value = create_confidential_value(value_info)?;
421
422 let asset_info = output.asset.ok_or_else(|| TxError::MissingField {
423 field: "asset".to_string(),
424 })?;
425 let asset = create_confidential_asset(asset_info)?;
426
427 let nonce = output
428 .nonce
429 .map(create_confidential_nonce)
430 .transpose()?
431 .unwrap_or(confidential::Nonce::Null);
432
433 let script_pubkey = if let Some(spk) = output.script_pub_key {
434 create_script_pubkey(spk, &mut used_network)?
435 } else if let Some(pd) = output.pegout_data {
436 match value {
437 confidential::Value::Explicit(v) => {
438 if v != pd.value {
439 return Err(TxError::PegoutValueMismatch);
440 }
441 }
442 _ => return Err(TxError::PegoutValueNotExplicit),
443 }
444 let pd_asset = create_confidential_asset(pd.asset.clone())?;
445 if asset != pd_asset {
446 return Err(TxError::PegoutAssetMismatch);
447 }
448 create_script_pubkey_from_pegout_data(pd)?
449 } else {
450 Default::default()
451 };
452
453 let witness = output.witness.map(create_output_witness).transpose()?.unwrap_or_default();
454
455 Ok(TxOut {
456 asset,
457 value,
458 nonce,
459 script_pubkey,
460 witness,
461 })
462}
463
464pub fn tx_create(info: TransactionInfo) -> Result<Transaction, TxError> {
466 let version = info.version.ok_or_else(|| TxError::MissingField {
467 field: "version".to_string(),
468 })?;
469 let lock_time = info.locktime.ok_or_else(|| TxError::MissingField {
470 field: "locktime".to_string(),
471 })?;
472
473 let inputs = info
474 .inputs
475 .ok_or_else(|| TxError::MissingField {
476 field: "inputs".to_string(),
477 })?
478 .into_iter()
479 .map(create_input)
480 .collect::<Result<Vec<_>, _>>()?;
481
482 let outputs = info
483 .outputs
484 .ok_or_else(|| TxError::MissingField {
485 field: "outputs".to_string(),
486 })?
487 .into_iter()
488 .map(create_output)
489 .collect::<Result<Vec<_>, _>>()?;
490
491 Ok(Transaction {
492 version,
493 lock_time,
494 input: inputs,
495 output: outputs,
496 })
497}
498
499pub fn tx_decode(raw_tx_hex: &str, network: Network) -> Result<TransactionInfo, TxError> {
501 use crate::GetInfo;
502
503 let raw_tx = hex::decode(raw_tx_hex).map_err(TxError::TxHex)?;
504 let tx: Transaction = deserialize(&raw_tx).map_err(TxError::TxDeserialize)?;
505
506 Ok(tx.get_info(network))
507}