[WIP] Conform to Monero CLSAG

This commit is contained in:
Lucas Soriano del Pino 2021-05-10 17:25:58 +10:00
parent cbdda9b9c4
commit 05c1b63aa2
No known key found for this signature in database
GPG Key ID: EE611E973A1530E7
3 changed files with 160 additions and 82 deletions

View File

@ -4,23 +4,17 @@
use anyhow::{bail, Result};
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use curve25519_dalek::scalar::Scalar;
use hash_edwards_to_edwards::hash_point_to_point;
use rand::{CryptoRng, Rng};
use std::convert::TryInto;
use tiny_keccak::{Hasher, Keccak};
use tiny_keccak::Hasher;
pub const RING_SIZE: usize = 11;
const DOMAIN_TAG: &str = "CSLAG_c";
#[rustfmt::skip]
// aggregation hashes:
// mu_{P, C} =
// keccak256("CLSAG_agg_{0, 1}" ||
// ring || ring of commitments || I || z * hash_to_point(signing pk) || pseudooutput commitment)
//
// where z = blinding of real commitment - blinding of pseudooutput commitment.
const HASH_KEY_CLSAG_AGG_0: &str = "CLSAG_agg_0";
const HASH_KEY_CLSAG_AGG_1: &str = "CLSAG_agg_1";
const HASH_KEY_CLSAG_ROUND: &str = "CLSAG_round";
// for every iteration we compute:
// c_p = h_prev * mu_P; and
@ -31,34 +25,133 @@ const DOMAIN_TAG: &str = "CSLAG_c";
//
// h = keccak256("CLSAG_round" || ring
// ring of commitments || pseudooutput commitment || msg || L_i || R_i)
struct AggregationHashes {
mu_P: Scalar,
mu_C: Scalar,
}
impl AggregationHashes {
pub fn new(
ring: [EdwardsPoint; RING_SIZE],
commitment_ring: [EdwardsPoint; RING_SIZE],
I: EdwardsPoint,
z: Scalar,
H_p_pk: EdwardsPoint,
pseudo_output_commitment: EdwardsPoint,
) -> Self {
let z_key_image = z * H_p_pk;
let ring = ring
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let commitment_ring = commitment_ring
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let I = I.compress();
let z_key_image = z_key_image.compress();
let pseudo_output_commitment = pseudo_output_commitment.compress();
let mu_P = Self::hash(
HASH_KEY_CLSAG_AGG_0,
&ring,
&commitment_ring,
&I,
&z_key_image,
&pseudo_output_commitment,
);
let mu_C = Self::hash(
HASH_KEY_CLSAG_AGG_1,
&ring,
&commitment_ring,
&I,
&z_key_image,
&pseudo_output_commitment,
);
Self { mu_P, mu_C }
}
// aggregation hashes:
// mu_{P, C} =
// keccak256("CLSAG_agg_{0, 1}" ||
// ring || ring of commitments || I || z * hash_to_point(signing pk) ||
// pseudooutput commitment)
//
// where z = blinding of real commitment - blinding of pseudooutput commitment.
fn hash(
domain_prefix: &str,
ring: &[u8],
commitment_ring: &[u8],
I: &CompressedEdwardsY,
z_key_image: &CompressedEdwardsY,
pseudo_output_commitment: &CompressedEdwardsY,
) -> Scalar {
let mut hasher = tiny_keccak::Keccak::v256();
hasher.update(domain_prefix.as_bytes());
hasher.update(ring);
hasher.update(commitment_ring);
hasher.update(I.as_bytes());
hasher.update(z_key_image.as_bytes());
hasher.update(pseudo_output_commitment.as_bytes());
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
Scalar::from_bytes_mod_order(hash)
}
}
fn challenge(
prefix: &[u8],
s_i: Scalar,
pk_i: EdwardsPoint,
h_prev: Scalar,
I: EdwardsPoint,
mut prefix: Keccak,
) -> Result<Scalar> {
let L_i = s_i * ED25519_BASEPOINT_POINT + h_prev * pk_i;
let H_p_pk_i = hash_point_to_point(pk_i);
let R_i = s_i * H_p_pk_i + h_prev * I;
prefix.update(&L_i.compress().as_bytes().to_vec());
prefix.update(&R_i.compress().as_bytes().to_vec());
let mut hasher = tiny_keccak::Keccak::v256();
hasher.update(prefix);
hasher.update(&L_i.compress().as_bytes().to_vec());
hasher.update(&R_i.compress().as_bytes().to_vec());
let mut output = [0u8; 64];
prefix.finalize(&mut output);
let mut output = [0u8; 32];
hasher.finalize(&mut output);
Ok(Scalar::from_bytes_mod_order_wide(&output))
Ok(Scalar::from_bytes_mod_order(output))
}
#[rustfmt::skip]
// h_0 = keccak256("CLSAG_round" || ring
// ring of commitments || pseudooutput commitment || msg || alpha * G || alpha * hash_to_point(signing pk))
// h_0 = keccak256("CLSAG_round" || ring ||
// ring of commitments || pseudooutput commitment || msg || alpha * G ||
// alpha * hash_to_point(signing pk))
//
// where alpha is random
// TODO: Create ring newtype
fn clsag_round_hash_prefix(
ring: &[u8],
commitment_ring: &[u8],
pseudo_output_commitment: &EdwardsPoint,
msg: &[u8],
) -> Vec<u8> {
// TODO: Set capacity
let mut prefix = Vec::new();
prefix.extend(HASH_KEY_CLSAG_ROUND.as_bytes());
prefix.extend(ring);
prefix.extend(commitment_ring);
prefix.extend(pseudo_output_commitment.compress().as_bytes());
prefix.extend(msg);
prefix
}
#[allow(clippy::too_many_arguments)]
fn final_challenge(
fake_responses: [Scalar; RING_SIZE - 1],
@ -70,18 +163,16 @@ fn final_challenge(
I_hat_b: EdwardsPoint,
R_prime_a: EdwardsPoint,
I: EdwardsPoint,
msg: [u8; 32],
msg: &[u8],
) -> Result<(Scalar, Scalar)> {
let ring_concat = ring
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let prefix = clsag_round_hash_prefix(&ring_concat, todo!(), todo!(), msg);
let h_0 = {
let ring = ring
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let mut keccak = tiny_keccak::Keccak::v512();
keccak.update(DOMAIN_TAG.as_bytes());
keccak.update(ring.as_slice());
keccak.update(&msg);
let mut keccak = tiny_keccak::Keccak::v256();
keccak.update(&prefix);
keccak.update((T_a + T_b + R_a).compress().as_bytes());
keccak.update((I_hat_a + I_hat_b + R_prime_a).compress().as_bytes());
let mut output = [0u8; 64];
@ -89,25 +180,19 @@ fn final_challenge(
Scalar::from_bytes_mod_order_wide(&output)
};
// ring size is 11
let ring_concat = ring
.iter()
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let mut keccak = tiny_keccak::Keccak::v512();
keccak.update(DOMAIN_TAG.as_bytes());
keccak.update(ring_concat.as_slice());
keccak.update(&msg);
let h_last = fake_responses
.iter()
.enumerate()
.fold(h_0, |h_prev, (i, s_i)| {
let pk_i = ring[i + 1];
// TODO: Do not unwrap here
challenge(*s_i, pk_i, h_prev, I, keccak.clone()).unwrap()
challenge(&prefix, *s_i, pk_i, h_prev, I).unwrap()
});
Ok((h_last, h_0))
@ -158,16 +243,17 @@ impl Signature {
.flat_map(|pk| pk.compress().as_bytes().to_vec())
.collect::<Vec<u8>>();
let mut prefix = tiny_keccak::Keccak::v512();
prefix.update(DOMAIN_TAG.as_bytes());
prefix.update(ring_concat.as_slice());
prefix.update(msg);
let mut h = self.h_0;
for (i, s_i) in self.responses.iter().enumerate() {
let pk_i = ring[(i + 1) % RING_SIZE];
h = challenge(*s_i, pk_i, h, self.I, prefix.clone())?;
h = challenge(
&clsag_round_hash_prefix(&ring_concat, todo!(), todo!(), msg),
*s_i,
pk_i,
h,
self.I,
)?;
}
Ok(h == self.h_0)
@ -276,11 +362,10 @@ impl Alice0 {
msg.I_hat_b,
self.R_prime_a,
self.I_a + msg.I_b,
self.msg,
&self.msg,
)?;
// TODO: Final scalar is computed slightly differentley for Monero (involves
// mu_P and mu_C constants)
// TODO: alpha_a - h_last * (mu_P * s_prime_a + mu_C * z)
let s_0_a = self.alpha_a - h_last * self.s_prime_a;
Ok(Alice1 {
@ -449,9 +534,10 @@ impl Bob1 {
self.I_hat_b,
self.R_prime_a,
I_a + self.I_b,
self.msg,
&self.msg,
)?;
// TODO: alpha_b - h_last * (mu_P * s_b + mu_C * z);
let s_0_b = self.alpha_b - h_last * self.s_b;
let adaptor_sig = AdaptorSignature {

View File

@ -13,7 +13,6 @@ use monero::{
PrivateKey, PublicKey, Transaction, TransactionPrefix, TxIn, TxOut, VarInt, ViewPair,
};
use monero_harness::Monero;
use monero_rpc::monerod;
use monero_rpc::monerod::{GetOutputsOut, MonerodRpc};
use monero_wallet::MonerodClientExt;
use rand::rngs::OsRng;
@ -22,41 +21,14 @@ use std::convert::TryInto;
use std::iter;
use testcontainers::clients::Cli;
async fn prepare_nodes(address: monero::Address, amount: u64) -> (monerod::Client, monero::Hash) {
let cli = Cli::default();
let (monero, _monerod_container, _monero_wallet_rpc_containers) =
Monero::new(&cli, vec![]).await.unwrap();
monero.init_miner().await.unwrap();
let wallet = monero.wallet("miner").expect("wallet to exist");
let transfer = wallet
.transfer(&address.to_string(), amount)
.await
.expect("lock to succeed");
let monerod = monero.monerod().client();
let miner_address = wallet
.address()
.await
.expect("miner address to exist")
.address;
monerod
.generateblocks(10, miner_address)
.await
.expect("can generate blocks");
let lock_tx_hash = transfer.tx_hash.parse().unwrap();
(monerod.clone(), lock_tx_hash)
}
#[tokio::test]
async fn monerod_integration_test() {
let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]);
let cli = Cli::default();
let (monero, _monerod_container, _monero_wallet_rpc_containers) =
Monero::new(&cli, vec![]).await.unwrap();
let s_a = curve25519_dalek::scalar::Scalar::random(&mut rng);
let s_b = curve25519_dalek::scalar::Scalar::random(&mut rng);
let lock_kp = monero::KeyPair {
@ -72,7 +44,27 @@ async fn monerod_integration_test() {
dbg!(lock_address.to_string()); // 45BcRKAHaA4b5A9SdamF2f1w7zk1mKkBPhaqVoDWzuAtMoSAytzm5A6b2fE6ruupkAFmStrQzdojUExt96mR3oiiSKp8Exf
let (client, lock_tx) = prepare_nodes(lock_address, lock_amount).await;
monero.init_miner().await.unwrap();
let wallet = monero.wallet("miner").expect("wallet to exist");
let transfer = wallet
.transfer(&lock_address.to_string(), lock_amount)
.await
.expect("lock to succeed");
let client = monero.monerod().client();
let miner_address = wallet
.address()
.await
.expect("miner address to exist")
.address;
client
.generateblocks(10, miner_address)
.await
.expect("can generate blocks");
let lock_tx = transfer.tx_hash.parse().unwrap();
let o_indexes_response = client.get_o_indexes(lock_tx).await.unwrap();

View File

@ -109,7 +109,7 @@ impl<'c> Monero {
let monerod = &self.monerod;
let res = monerod
.client()
.generateblocks(70, miner_address.clone())
.generateblocks(150, miner_address.clone())
.await?;
tracing::info!("Generated {:?} blocks", res.blocks.len());
miner_wallet.refresh().await?;