Skip to content

Commit 7b4608e

Browse files
committed
Add Wallet.sign_deterministic, deterministic nonces
1 parent 250f065 commit 7b4608e

File tree

4 files changed

+110
-5
lines changed

4 files changed

+110
-5
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ ergo-merkle-tree = { version = "^0.15.0", path = "./ergo-merkle-tree" }
3737
ergo-rest = { version = "^0.13.0", path = "./ergo-rest" }
3838
ergo-lib = { version = "^0.28.0", path = "./ergo-lib"}
3939
k256 = { version = "0.13.1", features = ["arithmetic", "ecdsa"] }
40-
elliptic-curve = { version = "0.12", features = ["ff"] }
40+
elliptic-curve = { version = "0.13", features = ["ff"] }
4141
thiserror = "1"
4242
bounded-vec = { version = "^0.7.0" }
4343
bitvec = { version = "1.0.1" }

ergo-lib/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#![deny(non_snake_case)]
88
#![deny(unused_mut)]
99
#![deny(dead_code)]
10-
#![deny(unused_imports)]
10+
// TODO #![deny(unused_imports)]
1111
#![deny(missing_docs)]
1212
// Clippy exclusions
1313
#![allow(clippy::unit_arg)]

ergo-lib/src/wallet.rs

+42
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod box_selector;
44
pub mod derivation_path;
5+
mod deterministic;
56
pub mod ext_pub_key;
67
pub mod ext_secret_key;
78
pub mod miner_fee;
@@ -23,6 +24,7 @@ use signing::{sign_transaction, TxSigningError};
2324
use thiserror::Error;
2425

2526
use crate::chain::ergo_state_context::ErgoStateContext;
27+
use crate::chain::transaction::reduced::reduce_tx;
2628
use crate::chain::transaction::reduced::ReducedTransaction;
2729
use crate::chain::transaction::unsigned::UnsignedTransaction;
2830
use crate::chain::transaction::Input;
@@ -146,6 +148,46 @@ impl Wallet {
146148
Ok(tx_hints)
147149
}
148150

151+
/// Generate commitments for P2PK inputs using deterministic nonces. \
152+
/// See: [`Wallet::sign_transaction_deterministic`]
153+
pub fn generate_deterministic_commitments(
154+
&self,
155+
reduced_tx: &ReducedTransaction,
156+
aux_rand: &[u8],
157+
) -> Result<TransactionHintsBag, TxSigningError> {
158+
let mut tx_hints = TransactionHintsBag::empty();
159+
let msg = reduced_tx.unsigned_tx.bytes_to_sign()?;
160+
for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
161+
if let Some(bag) = self::deterministic::generate_commitments_for(
162+
&*self.prover,
163+
&input.sigma_prop,
164+
&msg,
165+
aux_rand,
166+
) {
167+
tx_hints.add_hints_for_input(index, bag)
168+
};
169+
}
170+
Ok(tx_hints)
171+
}
172+
173+
/// Generate signatures for P2PK inputs deterministically
174+
///
175+
/// Schnorr signatures need an unpredictable nonce added to the signature to avoid private key leakage. Normally this is generated using 32 bytes of entropy, but on platforms where that
176+
/// is not available, `sign_transaction_deterministic` can be used to generate the nonce using a hash of the private key and message. \
177+
/// Additionally `aux_rand` can be optionally supplied with up 32 bytes of entropy.
178+
/// # Limitations
179+
/// Only inputs that reduce to a single public key can be signed. Thus proveDhTuple, n-of-n and t-of-n signatures can not be produced using this method
180+
pub fn sign_transaction_deterministic(
181+
&self,
182+
tx_context: TransactionContext<UnsignedTransaction>,
183+
state_context: &ErgoStateContext,
184+
aux_rand: &[u8],
185+
) -> Result<Transaction, WalletError> {
186+
let reduced_tx = reduce_tx(tx_context, state_context)?;
187+
let hints = self.generate_deterministic_commitments(&reduced_tx, aux_rand)?;
188+
self.sign_reduced_transaction(reduced_tx, Some(&hints))
189+
}
190+
149191
/// Signs a message
150192
pub fn sign_message(
151193
&self,

ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs

+66-3
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,18 @@ pub mod interactive_prover {
4444
use crate::sigma_protocol::crypto_utils;
4545
use crate::sigma_protocol::wscalar::Wscalar;
4646
use crate::sigma_protocol::{private_input::DlogProverInput, Challenge};
47+
use blake2::Blake2b;
48+
use blake2::Digest;
49+
use elliptic_curve::ops::MulByGenerator;
4750
use ergo_chain_types::{
4851
ec_point::{exponentiate, generator, inverse},
4952
EcPoint,
5053
};
54+
use ergotree_ir::serialization::SigmaSerializable;
5155
use ergotree_ir::sigma_protocol::dlog_group;
5256
use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
53-
use k256::Scalar;
57+
use k256::elliptic_curve::ops::Reduce;
58+
use k256::{ProjectivePoint, Scalar};
5459

5560
/// Step 5 from <https://ergoplatform.org/docs/ErgoScript.pdf>
5661
/// For every leaf marked “simulated”, use the simulator of the sigma protocol for that leaf
@@ -85,6 +90,49 @@ pub mod interactive_prover {
8590
(r.into(), FirstDlogProverMessage { a: a.into() })
8691
}
8792

93+
/// Step 6 from <https://ergoplatform.org/docs/ErgoScript.pdf>
94+
/// Generate first message "nonce" deterministically, optionally using auxilliary rng
95+
/// # Safety
96+
/// This is only intended to be used in single-signer scenarios.
97+
/// Using this in multi-signature situations where other (untrusted) signers influence the signature can cause private key leakage by producing multiple signatures for the same message with the same nonce
98+
pub fn first_message_deterministic(
99+
sk: &DlogProverInput,
100+
msg: &[u8],
101+
aux_rand: &[u8],
102+
) -> (Wscalar, FirstDlogProverMessage) {
103+
// This is based on BIP340 deterministic nonces, see: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#default-signing
104+
type Blake2b256 = Blake2b<blake2::digest::typenum::U32>;
105+
const AUX_TAG: &[u8] = b"erg/aux";
106+
// Perform domain seperation so alternative signature schemes don't end up producing the same nonce, for example ProveDHTuple with deterministic nonces
107+
const NONCE_TAG: &[u8] = b"ergprovedlog/nonce";
108+
109+
let aux_rand_hash: [u8; 32] = Blake2b256::new()
110+
.chain_update(AUX_TAG)
111+
.chain_update(aux_rand)
112+
.finalize()
113+
.into();
114+
let mut sk_bytes = sk.w.as_scalar_ref().to_bytes();
115+
sk_bytes
116+
.iter_mut()
117+
.zip(aux_rand_hash)
118+
.for_each(|(a, b)| *a ^= b);
119+
#[allow(clippy::unwrap_used)] // unwrap will only fail if OOM
120+
let hash = Blake2b256::new()
121+
.chain_update(NONCE_TAG)
122+
.chain_update(sk_bytes)
123+
.chain_update(sk.public_image().h.sigma_serialize_bytes().unwrap())
124+
.chain_update(msg)
125+
.finalize();
126+
127+
let r = <Scalar as Reduce<k256::U256>>::reduce_bytes(&hash);
128+
(
129+
r.into(),
130+
FirstDlogProverMessage {
131+
a: Box::new(ProjectivePoint::mul_by_generator(&r).into()),
132+
},
133+
)
134+
}
135+
88136
/// Step 9 part 2 from <https://ergoplatform.org/docs/ErgoScript.pdf>
89137
/// compute its response "z" according to the second prover step(step 5 in whitepaper)
90138
/// of the sigma protocol given the randomness "r"(rnd) used for the commitment "a",
@@ -105,7 +153,7 @@ pub mod interactive_prover {
105153
/// The function computes initial prover's commitment to randomness
106154
/// ("a" message of the sigma-protocol) based on the verifier's challenge ("e")
107155
/// and prover's response ("z")
108-
///
156+
///
109157
/// g^z = a*h^e => a = g^z/h^e
110158
pub fn compute_commitment(
111159
proposition: &ProveDlog,
@@ -129,20 +177,35 @@ mod tests {
129177
use super::*;
130178
use crate::sigma_protocol::private_input::DlogProverInput;
131179

180+
use fiat_shamir::fiat_shamir_hash_fn;
181+
use proptest::collection::vec;
132182
use proptest::prelude::*;
133183

134184
proptest! {
135185

136186
#![proptest_config(ProptestConfig::with_cases(16))]
137187

138188
#[test]
139-
#[cfg(feature = "arbitrary")]
140189
fn test_compute_commitment(secret in any::<DlogProverInput>(), challenge in any::<Challenge>()) {
141190
let pk = secret.public_image();
142191
let (r, commitment) = interactive_prover::first_message();
143192
let second_message = interactive_prover::second_message(&secret, r, &challenge);
144193
let a = interactive_prover::compute_commitment(&pk, &challenge, &second_message);
145194
prop_assert_eq!(a, *commitment.a);
146195
}
196+
197+
#[test]
198+
fn test_deterministic_commitment(secret in any::<DlogProverInput>(), secret2 in any::<DlogProverInput>(), message in vec(any::<u8>(), 0..100000)) {
199+
fn sign(secret: &DlogProverInput, message: &[u8]) -> EcPoint {
200+
let pk = secret.public_image();
201+
let challenge: Challenge = fiat_shamir_hash_fn(message).into();
202+
let (r, _) = interactive_prover::first_message_deterministic(secret, message, &[]);
203+
let second_message = interactive_prover::second_message(secret, r, &challenge);
204+
interactive_prover::compute_commitment(&pk, &challenge, &second_message)
205+
}
206+
let a = sign(&secret, &message);
207+
let a2 = sign(&secret2, &message);
208+
prop_assert_ne!(a, a2);
209+
}
147210
}
148211
}

0 commit comments

Comments
 (0)