1use crate::chain::transaction::ergo_transaction::ErgoTransaction;
4use crate::chain::transaction::reduced::ReducedTransaction;
5use crate::chain::transaction::{Input, TransactionError};
6use crate::chain::{
7 ergo_state_context::ErgoStateContext,
8 transaction::{unsigned::UnsignedTransaction, Transaction},
9};
10use ergotree_interpreter::sigma_protocol::prover::hint::HintsBag;
11use ergotree_interpreter::sigma_protocol::sig_serializer::SigParsingError;
12use ergotree_ir::serialization::SigmaSerializationError;
13use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
14
15use crate::chain::transaction::storage_rent::check_storage_rent_conditions;
16use crate::wallet::multi_sig::TransactionHintsBag;
17use ergotree_interpreter::eval::context::Context;
18use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
19use ergotree_interpreter::sigma_protocol::prover::Prover;
20use ergotree_interpreter::sigma_protocol::prover::ProverError;
21use ergotree_interpreter::sigma_protocol::prover::ProverResult;
22use thiserror::Error;
23
24pub use super::tx_context::TransactionContext;
25use super::tx_context::TransactionContextError;
26
27#[derive(Error, Debug)]
29pub enum TxSigningError {
30 #[error("TransactionContextError: {0}")]
32 TransactionContextError(#[from] TransactionContextError),
33 #[error("Prover error (tx input index {1}): {0}")]
35 ProverError(ProverError, usize),
36 #[error("Transaction serialization failed: {0}")]
38 SerializationError(#[from] SigmaSerializationError),
39 #[error("SigParsingError: {0}")]
41 SigParsingError(#[from] SigParsingError),
42}
43
44pub fn make_context<'ctx, T: ErgoTransaction>(
46 state_ctx: &'ctx ErgoStateContext,
47 tx_ctx: &'ctx TransactionContext<T>,
48 self_index: usize,
49) -> Result<Context<'ctx>, TransactionContextError> {
50 let height = state_ctx.pre_header.height;
51
52 let self_box = tx_ctx
54 .get_input_box(
55 &tx_ctx
56 .spending_tx
57 .inputs_ids()
58 .nth(self_index)
59 .ok_or(TransactionError::InputNofFound(self_index))?,
60 )
61 .ok_or(TransactionContextError::InputBoxNotFound(self_index))?;
62
63 let outputs = tx_ctx.spending_tx.outputs();
64 let data_inputs_ir = if let Some(data_inputs) = tx_ctx.spending_tx.data_inputs() {
65 Some(
66 data_inputs
67 .iter()
68 .enumerate()
69 .map(|(idx, di)| {
70 tx_ctx
71 .data_boxes
72 .as_ref()
73 .ok_or(TransactionContextError::DataInputBoxNotFound(idx))?
74 .iter()
75 .find(|b| di.box_id == b.box_id())
76 .ok_or(TransactionContextError::DataInputBoxNotFound(idx))
77 })
78 .collect::<Result<Vec<_>, _>>()?
79 .try_into()
80 .map_err(|_| TransactionContextError::TooManyDataInputBoxes(data_inputs.len()))?,
81 )
82 } else {
83 None
84 };
85 let inputs_ir = tx_ctx
86 .spending_tx
87 .inputs_ids()
88 .enumerate()
89 .map(|(idx, u)| {
90 tx_ctx
91 .get_input_box(&u)
92 .ok_or(TransactionContextError::InputBoxNotFound(idx))
93 })
94 .collect::<Result<Vec<_>, _>>()?
95 .try_into()
96 .map_err(|_| {
97 TransactionContextError::TooManyInputBoxes(tx_ctx.spending_tx.inputs_ids().len())
98 })?;
99 let extension = tx_ctx
100 .spending_tx
101 .context_extension(self_index)
102 .ok_or(TransactionError::InputNofFound(self_index))?;
103 Ok(Context {
104 height,
105 self_box,
106 outputs,
107 data_inputs: data_inputs_ir,
108 inputs: inputs_ir,
109 pre_header: state_ctx.pre_header.clone(),
110 extension,
111 headers: state_ctx.headers.clone(),
112 })
113}
114pub(crate) fn update_context<'ctx, T: ErgoTransaction>(
116 ctx: &mut Context<'ctx>,
117 tx_ctx: &'ctx TransactionContext<T>,
118 self_index: usize,
119) -> Result<(), TransactionContextError> {
120 let self_box = tx_ctx
122 .get_input_box(
123 &tx_ctx
124 .spending_tx
125 .inputs_ids()
126 .nth(self_index)
127 .ok_or(TransactionError::InputNofFound(self_index))?,
128 )
129 .ok_or(TransactionContextError::InputBoxNotFound(self_index))?;
130 let extension = tx_ctx
131 .spending_tx
132 .context_extension(self_index)
133 .ok_or(TransactionError::InputNofFound(self_index))?;
134 ctx.self_box = self_box;
135 ctx.extension = extension;
136 Ok(())
137}
138
139pub fn sign_transaction(
141 prover: &dyn Prover,
142 tx_context: TransactionContext<UnsignedTransaction>,
143 state_context: &ErgoStateContext,
144 tx_hints: Option<&TransactionHintsBag>,
145) -> Result<Transaction, TxSigningError> {
146 let tx = tx_context.spending_tx.clone();
147 let message_to_sign = tx.bytes_to_sign()?;
148 let mut ctx = make_context(state_context, &tx_context, 0)?;
149 let signed_inputs = tx.inputs.enumerated().try_mapped(|(idx, _)| {
150 sign_tx_input(
151 prover,
152 &tx_context,
153 state_context,
154 &mut ctx,
155 tx_hints,
156 idx,
157 message_to_sign.as_slice(),
158 )
159 })?;
160 Ok(Transaction::new(
161 signed_inputs,
162 tx.data_inputs,
163 tx.output_candidates,
164 )?)
165}
166
167pub fn sign_reduced_transaction(
169 prover: &dyn Prover,
170 reduced_tx: ReducedTransaction,
171 tx_hints: Option<&TransactionHintsBag>,
172) -> Result<Transaction, TxSigningError> {
173 let tx = reduced_tx.unsigned_tx.clone();
174 let message_to_sign = tx.bytes_to_sign()?;
175 let signed_inputs = tx.inputs.enumerated().try_mapped(|(idx, input)| {
176 let inputs = reduced_tx.reduced_inputs();
177
178 #[allow(clippy::unwrap_used)]
180 let reduced_input = inputs.get(idx).unwrap();
181 let mut hints_bag = HintsBag::empty();
182 if let Some(bag) = tx_hints {
183 hints_bag = bag.all_hints_for_input(idx);
184 }
185 prover
186 .generate_proof(
187 reduced_input.sigma_prop.clone(),
188 message_to_sign.as_slice(),
189 &hints_bag,
190 )
191 .map(|proof| ProverResult {
192 proof,
193 extension: reduced_input.extension.clone(),
194 })
195 .map(|proof| Input::new(input.box_id, proof.into()))
196 .map_err(|e| TxSigningError::ProverError(e, idx))
197 })?;
198 Ok(Transaction::new(
199 signed_inputs,
200 tx.data_inputs,
201 tx.output_candidates,
202 )?)
203}
204
205pub fn sign_message(
208 prover: &dyn Prover,
209 sigma_tree: SigmaBoolean,
210 msg: &[u8],
211) -> Result<Vec<u8>, ProverError> {
212 prover
213 .generate_proof(sigma_tree, msg, &HintsBag::empty())
214 .map(Vec::from)
215}
216
217pub fn sign_tx_input<'ctx>(
219 prover: &dyn Prover,
220 tx_context: &'ctx TransactionContext<UnsignedTransaction>,
221 state_context: &ErgoStateContext,
222 context: &mut Context<'ctx>,
223 tx_hints: Option<&TransactionHintsBag>,
224 input_idx: usize,
225 message_to_sign: &[u8],
226) -> Result<Input, TxSigningError> {
227 update_context(context, tx_context, input_idx)?;
228 let unsigned_input = tx_context
229 .spending_tx
230 .inputs
231 .get(input_idx)
232 .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
233 let input_box = tx_context
234 .get_input_box(&unsigned_input.box_id)
235 .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
236 let mut hints_bag = HintsBag::empty();
237 if let Some(bag) = tx_hints {
238 hints_bag = bag.all_hints_for_input(input_idx);
239 }
240
241 match check_storage_rent_conditions(input_box, state_context, context) {
242 Some(()) => Ok(Input::new(
244 unsigned_input.box_id,
245 ProverResult {
246 proof: ProofBytes::Empty,
247 extension: context.extension.clone(),
248 }
249 .into(),
250 )),
251 None => prover
253 .prove(&input_box.ergo_tree, context, message_to_sign, &hints_bag)
254 .map(|proof| Input::new(unsigned_input.box_id, proof.into()))
255 .map_err(|e| TxSigningError::ProverError(e, input_idx)),
256 }
257}
258
259#[cfg(test)]
260#[allow(clippy::unwrap_used, clippy::panic)]
261mod tests {
262 use super::*;
263 use ergotree_interpreter::eval::context::TxIoVec;
264 use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
265 use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
266 use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
267 use ergotree_interpreter::sigma_protocol::prover::TestProver;
268 use ergotree_interpreter::sigma_protocol::verifier::verify_signature;
269 use ergotree_interpreter::sigma_protocol::verifier::TestVerifier;
270 use ergotree_interpreter::sigma_protocol::verifier::Verifier;
271 use ergotree_interpreter::sigma_protocol::verifier::VerifierError;
272 use ergotree_ir::chain::address::AddressEncoder;
273 use ergotree_ir::chain::address::NetworkPrefix;
274 use ergotree_ir::chain::ergo_box::box_value::BoxValue;
275 use ergotree_ir::chain::ergo_box::ErgoBox;
276 use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
277 use ergotree_ir::chain::tx_id::TxId;
278 use ergotree_ir::serialization::SigmaSerializable;
279 use proptest::collection::vec;
280 use proptest::prelude::*;
281 use rand::prelude::SliceRandom;
282 use rand::thread_rng;
283 use sigma_test_util::force_any_val;
284
285 use crate::chain::transaction::reduced::reduce_tx;
286 use crate::chain::transaction::DataInput;
287 use crate::chain::{
288 ergo_box::box_builder::ErgoBoxCandidateBuilder, transaction::UnsignedInput,
289 };
290 use crate::wallet::secret_key::SecretKey;
291 use crate::wallet::Wallet;
292 use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
293 use ergotree_ir::ergo_tree::ErgoTree;
294 use ergotree_ir::mir::expr::Expr;
295 use std::convert::TryFrom;
296 use std::convert::TryInto;
297 use std::rc::Rc;
298
299 fn verify_tx_proofs(
300 tx: &Transaction,
301 boxes_to_spend: &[ErgoBox],
302 ) -> Result<bool, VerifierError> {
303 let verifier = TestVerifier;
304 let message = tx.bytes_to_sign().unwrap();
305 tx.inputs.iter().try_fold(true, |acc, input| {
306 let b = boxes_to_spend
307 .iter()
308 .find(|b| b.box_id() == input.box_id)
309 .unwrap();
310 let res = verifier.verify(
311 &b.ergo_tree,
312 &force_any_val::<Context>(),
313 input.spending_proof.proof.clone(),
314 &message,
315 )?;
316 Ok(res.result && acc)
317 })
318 }
319
320 proptest! {
321
322 #![proptest_config(ProptestConfig::with_cases(16))]
323
324 #[test]
325 fn test_tx_signing(secrets in vec(any::<DlogProverInput>(), 3..10)) {
326 let mut boxes_to_spend: Vec<ErgoBox> = secrets.iter().map(|secret|{
327 let pk = secret.public_image();
328 let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
329 ErgoBox::new(BoxValue::SAFE_USER_MIN,
330 tree,
331 None,
332 NonMandatoryRegisters::empty(),
333 0,
334 TxId::zero(),
335 0).unwrap()
336 }).collect();
337 let prover = Rc::new(TestProver {
338 secrets: secrets.clone().into_iter().map(PrivateInput::DlogProverInput).collect(),
339 });
340 let inputs: Vec<UnsignedInput> = boxes_to_spend.clone().into_iter().map(UnsignedInput::from).collect();
341 boxes_to_spend.shuffle(&mut thread_rng());
344 let ergo_tree = ErgoTree::try_from(Expr::Const(secrets.first().unwrap().public_image().into())).unwrap();
345 let candidate = ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, ergo_tree, 0)
346 .build().unwrap();
347 let output_candidates = vec![candidate];
348 let tx = UnsignedTransaction::new_from_vec(inputs, vec![], output_candidates).unwrap();
349 let tx_context = TransactionContext::new(tx, boxes_to_spend.clone(), vec![]).unwrap();
350 let tx_hint_bag=TransactionHintsBag::empty();
351 let res = sign_transaction(prover.as_ref(), tx_context.clone(), &force_any_val::<ErgoStateContext>(), Some(&tx_hint_bag));
352 let signed_tx = res.unwrap();
353 prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
354 let reduced_tx = reduce_tx(tx_context, &force_any_val::<ErgoStateContext>()).unwrap();
355 let signed_reduced_tx = sign_reduced_transaction(prover.as_ref(), reduced_tx,None).unwrap();
356 prop_assert!(verify_tx_proofs(&signed_reduced_tx, &boxes_to_spend).unwrap());
357 }
358 }
359
360 proptest! {
361 #![proptest_config(ProptestConfig::with_cases(16))]
362
363 #[test]
364 fn test_tx_context_input_reorderings(
365 inputs in vec((any::<ErgoBox>(), any::<ContextExtension>()), 1..10),
366 mut data_input_boxes in vec(any::<ErgoBox>(), 1..10),
367 candidate in any::<ErgoBoxCandidate>(),
368 ) {
369 let num_inputs = inputs.len();
370 let ut_inputs: Vec<_> = inputs
371 .iter()
372 .map(|(b, extension)|
373 UnsignedInput {
374 box_id: b.box_id(),
375 extension: extension.clone(),
376 }
377 )
378 .collect();
379 let ut_inputs = TxIoVec::from_vec(ut_inputs).unwrap();
380
381 let mut boxes_to_spend: Vec<_> = inputs.into_iter().map(|(b,_)| b).collect();
382
383 let data_inputs = Some(
384 TxIoVec::from_vec(data_input_boxes
385 .clone())
386 .unwrap()
387 .mapped(|b| DataInput{box_id: b.box_id()})
388 );
389
390 let expected_data_input_boxes = data_input_boxes.clone();
391 let expected_input_boxes = boxes_to_spend.clone();
392
393 boxes_to_spend.reverse();
395 data_input_boxes.reverse();
396 let boxes_to_spend = boxes_to_spend;
397 let spending_tx = UnsignedTransaction::new(
398 ut_inputs,
399 data_inputs,
400 TxIoVec::from_vec(vec![candidate]).unwrap(),
401 ).unwrap();
402 let tx_context = TransactionContext::new(
403 spending_tx,
404 boxes_to_spend,
405 data_input_boxes,
406 )
407 .unwrap();
408
409 let expected_data_input_boxes = Some(TxIoVec::from_vec(expected_data_input_boxes).unwrap());
410 let expected_input_boxes = TxIoVec::from_vec(expected_input_boxes).unwrap();
411 for i in 0..num_inputs {
412 let state_ctx = force_any_val::<ErgoStateContext>();
413 let context = make_context(&state_ctx, &tx_context, i).unwrap();
414 expected_data_input_boxes
415 .iter()
416 .flatten()
417 .zip(context.data_inputs.iter().flatten())
418 .for_each(|(left, right)| assert_eq!(&left, right));
419
420 expected_input_boxes
421 .iter()
422 .zip(context.inputs.iter())
423 .for_each(|(left, right)| assert_eq!(&left, right));
424 assert_eq!(tx_context.spending_tx.inputs.as_vec()[i].box_id, context.self_box.box_id());
425 }
426 }
427 }
428
429 proptest! {
430
431 #![proptest_config(ProptestConfig::with_cases(16))]
432
433 #[test]
434 fn test_prover_verify_signature(secret in any::<DlogProverInput>(), message in vec(any::<u8>(), 100..200)) {
435 let sb: SigmaBoolean = secret.public_image().into();
436 let prover = TestProver {
437 secrets: vec![PrivateInput::DlogProverInput(secret)],
438 };
439
440 let signature = sign_message(&prover, sb.clone(), message.as_slice()).unwrap();
441
442 prop_assert_eq!(verify_signature(
443 sb.clone(),
444 message.as_slice(),
445 signature.as_slice()).unwrap(),
446 true);
447
448 let mut ext_signature = signature;
450 ext_signature.push(1u8);
451 prop_assert_eq!(verify_signature(
452 sb.clone(),
453 message.as_slice(),
454 ext_signature.as_slice()).unwrap(),
455 true);
456
457 prop_assert_eq!(verify_signature(
459 sb,
460 message.as_slice(),
461 vec![1u8; 100].as_slice()).unwrap(),
462 false);
463 }
464 }
465
466 #[test]
467 fn test_proof_from_mainnet() {
468 use crate::chain::transaction::Transaction;
469
470 let tx_json = r#"
471 {
472 "id": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
473 "inputs": [
474 {
475 "boxId": "f353ae1b2027e40ea318e7a2673ea4bbaa281b7acee518a0994c5cbdefb05f55",
476 "spendingProof": {
477 "proofBytes":"",
478 "extension": {}
479 }
480 },
481 {
482 "boxId": "56111b039b86f71004b768d2e8b4579f1d79e28e7a617fd5add57a5239498c26",
483 "spendingProof": {
484 "proofBytes": "6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48",
485 "extension": {}
486 }
487 }
488 ],
489 "dataInputs": [
490 {
491 "boxId": "e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece"
492 }
493 ],
494 "outputs": [
495 {
496 "boxId": "55be517150fcb7f0f1661ad3ab30f1ac62084b83ad6aa772579bc06cbb52832e",
497 "value": 1000000,
498 "ergoTree": "100604000400050004000e20b662db51cf2dc39f110a021c2a31c74f0a1a18ffffbf73e8a051a7b8c0f09ebc0e2079974b2314c531e62776e6bc4babff35b37b178cebf0976fc0f416ff34ddbc4fd803d601b2a5730000d602e4c6a70407d603b2db6501fe730100ea02d1ededededed93e4c672010407720293e4c67201050ec5720391e4c672010605730293c27201c2a793db63087201db6308a7ed938cb2db6308720373030001730493cbc272037305cd7202",
499 "assets": [
500 {
501 "tokenId": "12caaacb51c89646fac9a3786eb98d0113bd57d68223ccc11754a4f67281daed",
502 "amount": 1
503 }
504 ],
505 "creationHeight": 299218,
506 "additionalRegisters": {
507 "R4": "070327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
508 "R5": "0e20e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece",
509 "R6": "05feaff5de0f"
510 },
511 "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
512 "index": 0
513 },
514 {
515 "boxId": "fa4a484c855d32a60987a4ddcf1c506aa6bab1c4cb0293c2d5ff35fcd11f2c7b",
516 "value": 1000000,
517 "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
518 "assets": [],
519 "creationHeight": 299218,
520 "additionalRegisters": {},
521 "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
522 "index": 1
523 },
524 {
525 "boxId": "3dee27d0dfb193fd6a263cf2b5b58cab99cb640d1443cd1ce63d909ad3a54197",
526 "value": 44516500000,
527 "ergoTree": "0008cd0327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
528 "assets": [],
529 "creationHeight": 299218,
530 "additionalRegisters": {},
531 "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
532 "index": 2
533 }
534 ],
535 "size": 673
536 }
537 "#;
538
539 let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
540 let decoded_addr = encoder
541 .parse_address_from_str("9gmNsqrqdSppLUBqg2UzREmmivgqh1r3jmNcLAc53hk3YCvAGWE")
542 .unwrap();
543
544 let ergo_tree = decoded_addr.script().unwrap();
545
546 let tx: Transaction = serde_json::from_str(tx_json).unwrap();
548 let tx_id_str: String = tx.id().into();
549 assert_eq!(
550 "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
551 tx_id_str
552 );
553 let message = tx.bytes_to_sign().unwrap();
554 let verifier = TestVerifier;
555 let ver_res = verifier.verify(
556 &ergo_tree,
557 &force_any_val::<Context>(),
558 tx.inputs.get(1).unwrap().spending_proof.proof.clone(),
559 message.as_slice(),
560 );
561 assert!(ver_res.unwrap().result);
562 }
563
564 #[test]
565 fn test_multi_sig_issue_597() {
566 let secrets: Vec<SecretKey> = [
567 "00eda6c0e9fc808d4cf050fc4e98705372b9f0786a6b63aa4013d1a20539b104",
568 "cc2e48e5e53059e0d68866eff97a6037cb39945ea9f09f40fcec82d12cd8cb8b",
569 "c97250f41cfa8d545c2f8d75b2ee24002b5feec32340c2bb81fa4e2d4c7527d3",
570 "53ceef0ece83401cf5cd853fd0c1a9bbfab750d76f278b3187f1a14768d6e9c4",
571 ]
572 .iter()
573 .map(|s| {
574 let sized_bytes: &[u8; DlogProverInput::SIZE_BYTES] =
575 &base16::decode(s).unwrap().try_into().unwrap();
576 SecretKey::dlog_from_bytes(sized_bytes).unwrap()
577 })
578 .collect();
579 let reduced = ReducedTransaction::sigma_parse_bytes(&base16::decode("ce04022f4cd0df4db787875b3a071e098b72ba4923bd2460e08184b34359563febe04700005e8269c8e2b975a43dc6e74a9c5b10b273313c6d32c1dd40c171fc0a8852ca0100000001a6ac381e6fa99929fd1477b3ba9499790a775e91d4c14c5aa86e9a118dfac8530480ade204100504000400040004000402d804d601b2a5730000d602e4c6a7041ad603e4c6a70510d604ad7202d901040ecdee7204ea02d19683020193c27201c2a7938cb2db63087201730100018cb2db6308a773020001eb02ea02d19683020193e4c67201041a720293e4c672010510720398b27203730300720498b272037304007204d18b0f010001021a04210302e57ca7ebf8cfa1802d4bc79a455008307a936b4f50f0629d9bef484fdd5189210399f5724bbc4d08c6e146d61449c05a3e0546868b1d4f83411f325187d5ca4f8521024e06e6c6073e13a03fa4629882a69108cd60e0a9fbb2e0fcc898ce68a7051b6621027a069cc972fc7816539a316ba1cfc0164656d63dd1873ee407670b0e8195f3bd100206088094ebdc030008cd0314368e16c9c99c5a6e20dda917aeb826b3a908becff543b3a36b38e6b3355ff5d18b0f0000c0843d1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304d18b0f0000c0af87c3210008cd0314368e16c9c99c5a6e20dda917aeb826b3a908becff543b3a36b38e6b3355ff5d18b0f00009702980304cd0302e57ca7ebf8cfa1802d4bc79a455008307a936b4f50f0629d9bef484fdd5189cd0399f5724bbc4d08c6e146d61449c05a3e0546868b1d4f83411f325187d5ca4f85cd024e06e6c6073e13a03fa4629882a69108cd60e0a9fbb2e0fcc898ce68a7051b66cd027a069cc972fc7816539a316ba1cfc0164656d63dd1873ee407670b0e8195f3bd9604cd0302e57ca7ebf8cfa1802d4bc79a455008307a936b4f50f0629d9bef484fdd5189cd0399f5724bbc4d08c6e146d61449c05a3e0546868b1d4f83411f325187d5ca4f85cd024e06e6c6073e13a03fa4629882a69108cd60e0a9fbb2e0fcc898ce68a7051b66cd027a069cc972fc7816539a316ba1cfc0164656d63dd1873ee407670b0e8195f3bdf39b03d3cb9e02d073").unwrap()).unwrap();
580 let prover = Wallet::from_secrets(secrets);
581 assert!(prover.sign_reduced_transaction(reduced, None).is_ok());
582 }
583}