|
| 1 | +use ergotree_interpreter::sigma_protocol::{ |
| 2 | + dlog_protocol::interactive_prover::first_message_deterministic, |
| 3 | + private_input::PrivateInput, |
| 4 | + prover::{ |
| 5 | + hint::{CommitmentHint, Hint, HintsBag, OwnCommitment, RealCommitment}, |
| 6 | + Prover, |
| 7 | + }, |
| 8 | + unproven_tree::NodePosition, |
| 9 | + FirstProverMessage, |
| 10 | +}; |
| 11 | +use ergotree_ir::sigma_protocol::sigma_boolean::{SigmaBoolean, SigmaProofOfKnowledgeTree}; |
| 12 | + |
| 13 | +pub(super) fn generate_commitments_for<P: Prover + ?Sized>( |
| 14 | + prover: &P, |
| 15 | + sigma_tree: &SigmaBoolean, |
| 16 | + msg: &[u8], |
| 17 | + aux_rand: &[u8], |
| 18 | +) -> Option<HintsBag> { |
| 19 | + let position = NodePosition::crypto_tree_prefix(); |
| 20 | + match sigma_tree { |
| 21 | + SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk)) => { |
| 22 | + let PrivateInput::DlogProverInput(sk) = prover |
| 23 | + .secrets() |
| 24 | + .iter() |
| 25 | + .find(|secret| secret.public_image() == *sigma_tree)? |
| 26 | + .clone() |
| 27 | + else { |
| 28 | + return None; |
| 29 | + }; |
| 30 | + let (r, a) = first_message_deterministic(&sk, msg, aux_rand); |
| 31 | + let mut bag = HintsBag::empty(); |
| 32 | + let own_commitment: Hint = |
| 33 | + Hint::CommitmentHint(CommitmentHint::OwnCommitment(OwnCommitment { |
| 34 | + image: SigmaBoolean::ProofOfKnowledge(pk.clone().into()), |
| 35 | + secret_randomness: r, |
| 36 | + commitment: FirstProverMessage::FirstDlogProverMessage(a.clone()), |
| 37 | + position: position.clone(), |
| 38 | + })); |
| 39 | + let real_commitment: Hint = |
| 40 | + Hint::CommitmentHint(CommitmentHint::RealCommitment(RealCommitment { |
| 41 | + image: SigmaBoolean::ProofOfKnowledge(pk.clone().into()), |
| 42 | + commitment: FirstProverMessage::FirstDlogProverMessage(a), |
| 43 | + position, |
| 44 | + })); |
| 45 | + bag.add_hint(real_commitment); |
| 46 | + bag.add_hint(own_commitment); |
| 47 | + Some(bag) |
| 48 | + } |
| 49 | + SigmaBoolean::TrivialProp(_) |
| 50 | + | SigmaBoolean::ProofOfKnowledge(_) |
| 51 | + | SigmaBoolean::SigmaConjecture(_) => None, |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +#[cfg(test)] |
| 56 | +#[allow(clippy::unwrap_used, clippy::unreachable, clippy::panic)] |
| 57 | +mod test { |
| 58 | + use ergo_chain_types::EcPoint; |
| 59 | + use ergotree_interpreter::sigma_protocol::dlog_protocol::interactive_prover::compute_commitment; |
| 60 | + use ergotree_interpreter::sigma_protocol::sig_serializer::parse_sig_compute_challenges; |
| 61 | + use ergotree_interpreter::sigma_protocol::unchecked_tree::{UncheckedLeaf, UncheckedTree}; |
| 62 | + use ergotree_interpreter::sigma_protocol::{private_input::DlogProverInput, wscalar::Wscalar}; |
| 63 | + use ergotree_ir::chain::context_extension::ContextExtension; |
| 64 | + use ergotree_ir::chain::ergo_box::box_value::BoxValue; |
| 65 | + use ergotree_ir::chain::ergo_box::NonMandatoryRegisters; |
| 66 | + use ergotree_ir::chain::ergo_box::{arbitrary::ArbBoxParameters, ErgoBox}; |
| 67 | + use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree; |
| 68 | + use proptest::collection::vec; |
| 69 | + use proptest::prelude::*; |
| 70 | + use sigma_test_util::force_any_val; |
| 71 | + |
| 72 | + use crate::chain::ergo_box::box_builder::ErgoBoxCandidateBuilder; |
| 73 | + use crate::chain::transaction::unsigned::UnsignedTransaction; |
| 74 | + use crate::chain::transaction::{Input, Transaction, UnsignedInput}; |
| 75 | + use crate::wallet::secret_key::SecretKey; |
| 76 | + use crate::wallet::signing::TransactionContext; |
| 77 | + use crate::wallet::Wallet; |
| 78 | + fn gen_boxes() -> impl Strategy<Value = (SecretKey, Vec<ErgoBox>)> { |
| 79 | + any::<Wscalar>() |
| 80 | + .prop_map(|s| SecretKey::DlogSecretKey(DlogProverInput { w: s })) |
| 81 | + .prop_flat_map(|sk: SecretKey| { |
| 82 | + ( |
| 83 | + Just(sk.clone()), |
| 84 | + vec( |
| 85 | + any_with::<ErgoBox>(ArbBoxParameters { |
| 86 | + ergo_tree: Just(sk.get_address_from_public_image().script().unwrap()) |
| 87 | + .boxed(), |
| 88 | + registers: Just(NonMandatoryRegisters::empty()).boxed(), |
| 89 | + tokens: Just(None).boxed(), |
| 90 | + ..Default::default() |
| 91 | + }), |
| 92 | + 1..10, |
| 93 | + ), |
| 94 | + ) |
| 95 | + }) |
| 96 | + } |
| 97 | + |
| 98 | + fn parse_sig(sk: &SecretKey, input: &Input) -> (EcPoint, Vec<u8>) { |
| 99 | + let ergotree_ir::chain::address::Address::P2Pk(pk) = sk.get_address_from_public_image() |
| 100 | + else { |
| 101 | + unreachable!() |
| 102 | + }; |
| 103 | + let UncheckedTree::UncheckedLeaf(UncheckedLeaf::UncheckedSchnorr(schnorr)) = |
| 104 | + parse_sig_compute_challenges( |
| 105 | + &SigmaProofOfKnowledgeTree::from(pk.clone()).into(), |
| 106 | + input.spending_proof.proof.clone().to_bytes(), |
| 107 | + ) |
| 108 | + .unwrap() |
| 109 | + else { |
| 110 | + unreachable!(); |
| 111 | + }; |
| 112 | + let commitment = compute_commitment(&pk, &schnorr.challenge, &schnorr.second_message); |
| 113 | + (commitment, schnorr.challenge.into()) |
| 114 | + } |
| 115 | + |
| 116 | + proptest! { |
| 117 | + // Produce signatures for different messages and test for nonce re-use |
| 118 | + #[test] |
| 119 | + fn test_sign_deterministic((sk, boxes) in gen_boxes()) { |
| 120 | + let wallet = Wallet::from_secrets(vec![sk.clone()]); |
| 121 | + let output = ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, sk.get_address_from_public_image().script().unwrap(), 0).build().unwrap(); |
| 122 | + let inputs: Vec<_> = boxes.iter().map(|b| UnsignedInput::new(b.box_id(), ContextExtension::empty())).collect(); |
| 123 | + let txes: Vec<Transaction> = (1..10).map(|i| { |
| 124 | + let mut output = output.clone(); |
| 125 | + output.value = output.value.checked_mul_u32(i).unwrap(); |
| 126 | + let tx = UnsignedTransaction::new_from_vec(inputs.clone(), vec![], vec![output]).unwrap(); |
| 127 | + wallet.sign_transaction_deterministic(TransactionContext::new(tx, boxes.clone(), vec![]).unwrap(), &force_any_val(), &[]).unwrap() |
| 128 | + }).collect(); |
| 129 | + let signatures: Vec<_> = txes.iter().flat_map(|tx| tx.inputs.iter()).map(|input| parse_sig(&sk, input)).collect(); |
| 130 | + |
| 131 | + for (i, (r, c)) in signatures.iter().enumerate() { |
| 132 | + if let Some((_, _)) = signatures.iter().enumerate().find(|(j, (r1, c1))| i != *j && r1 == r && c != c1) { |
| 133 | + panic!(); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + } |
| 138 | + } |
| 139 | +} |
0 commit comments