Skip to content

Commit

Permalink
feat: add new commitment signature to use complete representation pro…
Browse files Browse the repository at this point in the history
…of (#131)

Commitment signatures are currently used (via the `CommitmentSignature` type) in Tari protocols as part of transaction authorization, as described in [RFC-0201](https://rfc.tari.com/RFC-0201_TariScript.html). The cryptographic design of commitment signatures is such that they are a representation proof of a Pedersen commitment that is bound to arbitrary message data (via a Fiat-Shamir challenge) to produce a signature.

In both cases where commitment signatures are used, two elements of input data are summed prior to generating or verifying the signature. The cases are:
- an output commitment and a sender offset public key
- an input commitment and a script public key
In both cases, the individual elements are included separately in the challenge.

This approach is not a proof of knowledge of the openings of the individual elements. Instead, it is a proof of knowledge of the commitment value, and of the sum of the commitment mask and discrete logarithm of the public key. It is unclear if this can lead to practical transaction malleability or other insecure outcomes.

This PR adds a new `CommitmentAndPublicKeySignature` signature type that mitigates the issue by proving knowledge of the commitment value, commitment mask, and public key discrete logarithm. As before, it is the responsibility of the caller to ensure that the Fiat-Shamir challenge is generated correctly.

The existing `CommitmentSignature` is left unchanged, but should not be used for the aforementioned use cases.
  • Loading branch information
AaronFeickert committed Oct 27, 2022
1 parent b8fffca commit e02fa0f
Show file tree
Hide file tree
Showing 8 changed files with 1,281 additions and 10 deletions.
141 changes: 141 additions & 0 deletions src/ffi/keys.rs
Expand Up @@ -18,6 +18,7 @@ use crate::{
ristretto::{
pedersen::commitment_factory::PedersenCommitmentFactory,
RistrettoComSig,
RistrettoComAndPubSig,
RistrettoPublicKey,
RistrettoSchnorr,
RistrettoSecretKey,
Expand Down Expand Up @@ -262,6 +263,146 @@ pub unsafe extern "C" fn verify_comsig(
sig.verify(&commitment, &challenge, &factory)
}

/// Generate a commitment and public key signature (ephemeral_commitment, ephermeral_pubkey, u_a, u_x, u_y) using the
/// provided value, spending key, secret key, and challenge (a, x, y, e).
///
/// # Safety
/// If any args are null the function returns -1.
/// The caller MUST ensure that the string is null terminated e.g. "msg\0".
/// The *caller* must manage memory for the results, this function assumes that at least `KEY_LENGTH` bytes have been
/// allocated in `ephemeral_commitment`, `ephemeral_pubkey`, `u_a`, `u_x`, and `u_y`.
#[no_mangle]
pub unsafe extern "C" fn sign_comandpubsig(
a: *const KeyArray,
x: *const KeyArray,
y: *const KeyArray,
msg: *const c_char,
ephemeral_commitment: *mut KeyArray,
ephemeral_pubkey: *mut KeyArray,
u_a: *mut KeyArray,
u_x: *mut KeyArray,
u_y: *mut KeyArray,
) -> c_int {
if a.is_null() ||
x.is_null() ||
y.is_null() ||
msg.is_null() ||
ephemeral_commitment.is_null() ||
ephemeral_pubkey.is_null() ||
u_a.is_null() ||
u_x.is_null() ||
u_y.is_null()
{
return NULL_POINTER;
}
let a = match RistrettoSecretKey::from_bytes(&(*a)) {
Ok(k) => k,
_ => return INVALID_SECRET_KEY_SER,
};
let x = match RistrettoSecretKey::from_bytes(&(*x)) {
Ok(k) => k,
_ => return INVALID_SECRET_KEY_SER,
};
let y = match RistrettoSecretKey::from_bytes(&(*y)) {
Ok(k) => k,
_ => return INVALID_SECRET_KEY_SER,
};
let r_a = RistrettoSecretKey::random(&mut OsRng);
let r_x = RistrettoSecretKey::random(&mut OsRng);
let r_y = RistrettoSecretKey::random(&mut OsRng);
let msg = match CStr::from_ptr(msg).to_str() {
Ok(s) => s,
_ => return STR_CONV_ERR,
};
let challenge = Blake256::digest(msg.as_bytes()).to_vec();
let factory = PedersenCommitmentFactory::default();
let sig = match RistrettoComAndPubSig::sign(&a, &x, &y, &r_a, &r_x, &r_y, &challenge, &factory) {
Ok(sig) => sig,
_ => return SIGNING_ERROR,
};
(*ephemeral_commitment).copy_from_slice(sig.ephemeral_commitment().as_bytes());
(*ephemeral_pubkey).copy_from_slice(sig.ephemeral_pubkey().as_bytes());
(*u_a).copy_from_slice(sig.u_a().as_bytes());
(*u_x).copy_from_slice(sig.u_x().as_bytes());
(*u_y).copy_from_slice(sig.u_y().as_bytes());
OK
}

/// Verify that a commitment and public key signature (ephemeral_commitment, ephemeral_pubkey, u_a, u_a, u_x) is valid
/// for the provided commitment, public key, and challenge (C, D, e).
///
/// # Safety
/// If any args are null the function returns false and sets `err_code` to -1
#[no_mangle]
pub unsafe extern "C" fn verify_comandpubsig(
commitment: *const KeyArray,
pubkey: *const KeyArray,
msg: *const c_char,
ephemeral_commitment: *const KeyArray,
ephemeral_pubkey: *const KeyArray,
u_a: *const KeyArray,
u_x: *const KeyArray,
u_y: *const KeyArray,
err_code: *mut c_int,
) -> bool {
if commitment.is_null() ||
pubkey.is_null() ||
msg.is_null() ||
ephemeral_commitment.is_null() ||
ephemeral_pubkey.is_null() ||
u_a.is_null() ||
u_x.is_null() ||
u_y.is_null()
{
*err_code = NULL_POINTER;
return false;
}
let commitment = if let Ok(k) = HomomorphicCommitment::from_bytes(&(*commitment)) {
k
} else {
*err_code = INVALID_SECRET_KEY_SER;
return false;
};
let pubkey = if let Ok(k) = RistrettoPublicKey::from_bytes(&(*pubkey)) {
k
} else {
*err_code = INVALID_SECRET_KEY_SER;
return false;
};
let ephemeral_commitment = match HomomorphicCommitment::from_bytes(&(*ephemeral_commitment)) {
Ok(r) => r,
_ => return false,
};
let ephemeral_pubkey = match RistrettoPublicKey::from_bytes(&(*ephemeral_pubkey)) {
Ok(r) => r,
_ => return false,
};
let u_a = match RistrettoSecretKey::from_bytes(&(*u_a)) {
Ok(s) => s,
_ => return false,
};
let u_x = match RistrettoSecretKey::from_bytes(&(*u_x)) {
Ok(s) => s,
_ => return false,
};
let u_y = match RistrettoSecretKey::from_bytes(&(*u_y)) {
Ok(s) => s,
_ => return false,
};
let msg = match CStr::from_ptr(msg).to_str() {
Ok(s) => s,
_ => return false,
};
let sig = RistrettoComAndPubSig::new(ephemeral_commitment, ephemeral_pubkey, u_a, u_x, u_y);
let challenge = Blake256::digest(msg.as_bytes());
let challenge = match RistrettoSecretKey::from_bytes(challenge.as_slice()) {
Ok(e) => e,
_ => return false,
};
let factory = PedersenCommitmentFactory::default();
sig.verify(&commitment, &pubkey, &challenge, &factory, &mut OsRng)
}

#[cfg(test)]
mod test {
use std::ptr::null_mut;
Expand Down
2 changes: 1 addition & 1 deletion src/ffi/mod.rs
Expand Up @@ -9,7 +9,7 @@ mod error;
mod keys;

pub use error::lookup_error_message;
pub use keys::{commitment, random_keypair, sign, sign_comsig, verify, verify_comsig};
pub use keys::{commitment, random_keypair, sign, sign_comsig, sign_comandpubsig, verify, verify_comsig, verify_comandpubsig};

const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\u{00}");

Expand Down
2 changes: 2 additions & 0 deletions src/ristretto/mod.rs
Expand Up @@ -7,6 +7,7 @@ pub mod bulletproofs_plus;
pub mod constants;
mod dalek_range_proof;
pub mod pedersen;
mod ristretto_com_and_pub_sig;
mod ristretto_com_sig;
pub mod ristretto_keys;
mod ristretto_sig;
Expand All @@ -17,6 +18,7 @@ pub mod utils;
pub use dalek_range_proof::DalekRangeProofService;

pub use self::{
ristretto_com_and_pub_sig::RistrettoComAndPubSig,
ristretto_com_sig::RistrettoComSig,
ristretto_keys::{RistrettoPublicKey, RistrettoSecretKey},
ristretto_sig::RistrettoSchnorr,
Expand Down

0 comments on commit e02fa0f

Please sign in to comment.