ergo_lib/wallet/
signing.rs

1//! Transaction signing
2
3use 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/// Errors on transaction signing
28#[derive(Error, Debug)]
29pub enum TxSigningError {
30    /// Transaction context error
31    #[error("TransactionContextError: {0}")]
32    TransactionContextError(#[from] TransactionContextError),
33    /// Error on proving an input
34    #[error("Prover error (tx input index {1}): {0}")]
35    ProverError(ProverError, usize),
36    /// Tx serialization failed (id calculation)
37    #[error("Transaction serialization failed: {0}")]
38    SerializationError(#[from] SigmaSerializationError),
39    /// SigParsingError
40    #[error("SigParsingError: {0}")]
41    SigParsingError(#[from] SigParsingError),
42}
43
44/// `self_index` - index of the SELF box in the tx_ctx.spending_tx.inputs
45pub 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    // Find self_box by matching BoxIDs
53    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}
114// Updates a Context, changing its self box and context extension to transaction.inputs[i]
115pub(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    // Find self_box by matching BoxIDs
121    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
139/// Signs a transaction (generating proofs for inputs)
140pub 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
167/// Signs a reduced transaction (generating proofs for inputs)
168pub 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        // `idx` is valid since it's indexing over `tx.inputs`
179        #[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
205/// Sign arbitrary message under a key representing a statement provable via a sigma-protocol.
206/// A statement can be a simple ProveDlog (PK) or a complex sigma conjectives tree
207pub 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
217/// Sign a transaction input
218pub 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        // if input is storage rent set ProofBytes to empty because no proof is needed
243        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        // if input is not storage rent use prover
252        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 are in the different order to test inputs <-> boxes_to_spend association in the
342            // prover (it should not depend on both of them to be in the same order)
343            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          // Reverse boxes for `UnsignedTransaction`
394          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            // possible to append bytes
449            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            // wrong message
458            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 spending_proof_input1 = Base16DecodedBytes::try_from("6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48".to_string()).unwrap();
547        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}