Skip to content

Commit

Permalink
Add Wallet.sign_deterministic, deterministic nonces
Browse files Browse the repository at this point in the history
  • Loading branch information
SethDusek committed Nov 21, 2024
1 parent 250f065 commit 4a000ac
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ ergo-merkle-tree = { version = "^0.15.0", path = "./ergo-merkle-tree" }
ergo-rest = { version = "^0.13.0", path = "./ergo-rest" }
ergo-lib = { version = "^0.28.0", path = "./ergo-lib"}
k256 = { version = "0.13.1", features = ["arithmetic", "ecdsa"] }
elliptic-curve = { version = "0.12", features = ["ff"] }
elliptic-curve = { version = "0.13", features = ["ff"] }
thiserror = "1"
bounded-vec = { version = "^0.7.0" }
bitvec = { version = "1.0.1" }
Expand Down
42 changes: 42 additions & 0 deletions ergo-lib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod box_selector;
pub mod derivation_path;
mod deterministic;

Check failure on line 5 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / clippy

file not found for module `deterministic`

error[E0583]: file not found for module `deterministic` --> ergo-lib/src/wallet.rs:5:1 | 5 | mod deterministic; | ^^^^^^^^^^^^^^^^^^ | = help: to create the module `deterministic`, create file "ergo-lib/src/wallet/deterministic.rs" or "ergo-lib/src/wallet/deterministic/mod.rs" = note: if there is a `mod deterministic` elsewhere in the crate already, import it with `use crate::...` instead

Check failure on line 5 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Build without default features

file not found for module `deterministic`

Check failure on line 5 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Tests on macOS-latest

file not found for module `deterministic`

Check failure on line 5 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Check intra-documentation links

file not found for module `deterministic`
pub mod ext_pub_key;
pub mod ext_secret_key;
pub mod miner_fee;
Expand All @@ -23,6 +24,7 @@ use signing::{sign_transaction, TxSigningError};
use thiserror::Error;

use crate::chain::ergo_state_context::ErgoStateContext;
use crate::chain::transaction::reduced::reduce_tx;
use crate::chain::transaction::reduced::ReducedTransaction;
use crate::chain::transaction::unsigned::UnsignedTransaction;
use crate::chain::transaction::Input;
Expand Down Expand Up @@ -146,6 +148,46 @@ impl Wallet {
Ok(tx_hints)
}

/// Generate commitments for P2PK inputs using deterministic nonces. \
/// See: [`Wallet::sign_transaction_deterministic`]
pub fn generate_deterministic_commitments(
&self,
reduced_tx: &ReducedTransaction,
aux_rand: &[u8],
) -> Result<TransactionHintsBag, TxSigningError> {
let mut tx_hints = TransactionHintsBag::empty();
let msg = reduced_tx.unsigned_tx.bytes_to_sign()?;
for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
if let Some(bag) = self::deterministic::generate_commitments_for(

Check failure on line 161 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / clippy

cannot find function `generate_commitments_for` in module `self::deterministic`

error[E0425]: cannot find function `generate_commitments_for` in module `self::deterministic` --> ergo-lib/src/wallet.rs:161:53 | 161 | if let Some(bag) = self::deterministic::generate_commitments_for( | ^^^^^^^^^^^^^^^^^^^^^^^^ not found in `self::deterministic` | help: consider importing this function | 18 + use crate::wallet::multi_sig::generate_commitments_for; | help: if you import `generate_commitments_for`, refer to it directly | 161 - if let Some(bag) = self::deterministic::generate_commitments_for( 161 + if let Some(bag) = generate_commitments_for( |

Check failure on line 161 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Build without default features

cannot find function `generate_commitments_for` in module `self::deterministic`

Check failure on line 161 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Tests on macOS-latest

cannot find function `generate_commitments_for` in module `self::deterministic`

Check failure on line 161 in ergo-lib/src/wallet.rs

View workflow job for this annotation

GitHub Actions / Check intra-documentation links

cannot find function `generate_commitments_for` in module `self::deterministic`
&*self.prover,
&input.sigma_prop,
&msg,
aux_rand,
) {
tx_hints.add_hints_for_input(index, bag)
};
}
Ok(tx_hints)
}

/// Generate signatures for P2PK inputs deterministically
///
/// 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
/// is not available, `sign_transaction_deterministic` can be used to generate the nonce using a hash of the private key and message. \
/// Additionally `aux_rand` can be optionally supplied with up 32 bytes of entropy.
/// # Limitations
/// 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
pub fn sign_transaction_deterministic(
&self,
tx_context: TransactionContext<UnsignedTransaction>,
state_context: &ErgoStateContext,
aux_rand: &[u8],
) -> Result<Transaction, WalletError> {
let reduced_tx = reduce_tx(tx_context, state_context)?;
let hints = self.generate_deterministic_commitments(&reduced_tx, aux_rand)?;
self.sign_reduced_transaction(reduced_tx, Some(&hints))
}

/// Signs a message
pub fn sign_message(
&self,
Expand Down
69 changes: 66 additions & 3 deletions ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ pub mod interactive_prover {
use crate::sigma_protocol::crypto_utils;
use crate::sigma_protocol::wscalar::Wscalar;
use crate::sigma_protocol::{private_input::DlogProverInput, Challenge};
use blake2::Blake2b;
use blake2::Digest;
use elliptic_curve::ops::MulByGenerator;
use ergo_chain_types::{
ec_point::{exponentiate, generator, inverse},
EcPoint,
};
use ergotree_ir::serialization::SigmaSerializable;
use ergotree_ir::sigma_protocol::dlog_group;
use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
use k256::Scalar;
use k256::elliptic_curve::ops::Reduce;
use k256::{ProjectivePoint, Scalar};

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

/// Step 6 from <https://ergoplatform.org/docs/ErgoScript.pdf>
/// Generate first message "nonce" deterministically, optionally using auxilliary rng
/// # Safety
/// This is only intended to be used in single-signer scenarios.
/// 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
pub fn first_message_deterministic(
sk: &DlogProverInput,
msg: &[u8],
aux_rand: &[u8],
) -> (Wscalar, FirstDlogProverMessage) {
// This is based on BIP340 deterministic nonces, see: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#default-signing
type Blake2b256 = Blake2b<blake2::digest::typenum::U32>;
const AUX_TAG: &[u8] = b"erg/aux";
// Perform domain seperation so alternative signature schemes don't end up producing the same nonce, for example ProveDHTuple with deterministic nonces
const NONCE_TAG: &[u8] = b"ergprovedlog/nonce";

let aux_rand_hash: [u8; 32] = Blake2b256::new()
.chain_update(AUX_TAG)
.chain_update(aux_rand)
.finalize()
.into();
let mut sk_bytes = sk.w.as_scalar_ref().to_bytes();
sk_bytes
.iter_mut()
.zip(aux_rand_hash)
.for_each(|(a, b)| *a ^= b);
#[allow(clippy::unwrap_used)] // unwrap will only fail if OOM
let hash = Blake2b256::new()
.chain_update(NONCE_TAG)
.chain_update(sk_bytes)
.chain_update(sk.public_image().h.sigma_serialize_bytes().unwrap())
.chain_update(msg)
.finalize();

let r = <Scalar as Reduce<k256::U256>>::reduce_bytes(&hash);
(
r.into(),
FirstDlogProverMessage {
a: Box::new(ProjectivePoint::mul_by_generator(&r).into()),
},
)
}

/// Step 9 part 2 from <https://ergoplatform.org/docs/ErgoScript.pdf>
/// compute its response "z" according to the second prover step(step 5 in whitepaper)
/// of the sigma protocol given the randomness "r"(rnd) used for the commitment "a",
Expand All @@ -105,7 +153,7 @@ pub mod interactive_prover {
/// The function computes initial prover's commitment to randomness
/// ("a" message of the sigma-protocol) based on the verifier's challenge ("e")
/// and prover's response ("z")
///
///
/// g^z = a*h^e => a = g^z/h^e
pub fn compute_commitment(
proposition: &ProveDlog,
Expand All @@ -129,20 +177,35 @@ mod tests {
use super::*;
use crate::sigma_protocol::private_input::DlogProverInput;

use fiat_shamir::fiat_shamir_hash_fn;
use proptest::collection::vec;
use proptest::prelude::*;

proptest! {

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

#[test]
#[cfg(feature = "arbitrary")]
fn test_compute_commitment(secret in any::<DlogProverInput>(), challenge in any::<Challenge>()) {
let pk = secret.public_image();
let (r, commitment) = interactive_prover::first_message();
let second_message = interactive_prover::second_message(&secret, r, &challenge);
let a = interactive_prover::compute_commitment(&pk, &challenge, &second_message);
prop_assert_eq!(a, *commitment.a);
}

#[test]
fn test_deterministic_commitment(secret in any::<DlogProverInput>(), secret2 in any::<DlogProverInput>(), message in vec(any::<u8>(), 0..100000)) {
fn sign(secret: &DlogProverInput, message: &[u8]) -> EcPoint {
let pk = secret.public_image();
let challenge: Challenge = fiat_shamir_hash_fn(message).into();
let (r, _) = interactive_prover::first_message_deterministic(secret, message, &[]);
let second_message = interactive_prover::second_message(secret, r, &challenge);
interactive_prover::compute_commitment(&pk, &challenge, &second_message)
}
let a = sign(&secret, &message);
let a2 = sign(&secret2, &message);
prop_assert_ne!(a, a2);
}
}
}

0 comments on commit 4a000ac

Please sign in to comment.