From 21f31ccb8dfe54f7534155491da95aa0a6745372 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 4 May 2021 16:59:44 +1000 Subject: [PATCH] [WIP] More transaction stuff --- monero-adaptor/src/lib.rs | 20 ++- monero-adaptor/tests/integration_test.rs | 198 +++++++++++++++++------ monero-rpc/src/monerod.rs | 25 +-- monero-rpc/src/wallet.rs | 14 +- 4 files changed, 190 insertions(+), 67 deletions(-) diff --git a/monero-adaptor/src/lib.rs b/monero-adaptor/src/lib.rs index 4728be53..1d1a6219 100644 --- a/monero-adaptor/src/lib.rs +++ b/monero-adaptor/src/lib.rs @@ -11,7 +11,7 @@ use rand::rngs::OsRng; use std::convert::TryInto; use tiny_keccak::{Hasher, Keccak}; -const RING_SIZE: usize = 11; +pub const RING_SIZE: usize = 11; const DOMAIN_TAG: &str = "CSLAG_c"; fn challenge( @@ -154,6 +154,24 @@ impl Signature { } } +impl From for monero::util::ringct::Clsag { + fn from(from: Signature) -> Self { + Self { + s: from + .responses + .iter() + .map(|s| monero::util::ringct::Key { key: s.to_bytes() }) + .collect(), + c1: monero::util::ringct::Key { + key: from.h_0.to_bytes(), + }, + D: monero::util::ringct::Key { + key: from.I.compress().to_bytes(), + }, + } + } +} + pub struct Alice0 { // secret index is always 0 ring: [EdwardsPoint; RING_SIZE], diff --git a/monero-adaptor/tests/integration_test.rs b/monero-adaptor/tests/integration_test.rs index c5347b74..4628c7a4 100644 --- a/monero-adaptor/tests/integration_test.rs +++ b/monero-adaptor/tests/integration_test.rs @@ -1,9 +1,19 @@ -use monero_rpc::monerod; -use rand::{SeedableRng, Rng}; -use monero_rpc::monerod::{MonerodRpc, GetOutputsOut}; -use monero::{Transaction, TransactionPrefix, VarInt, TxIn}; -use std::iter; +#![allow(non_snake_case)] + +use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; +use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::scalar::Scalar; +use hash_edwards_to_edwards::hash_point_to_point; use itertools::Itertools; +use monero::cryptonote::hash::Hashable; +use monero::util::ringct::{RctSig, RctSigBase, RctSigPrunable, RctType}; +use monero::{Transaction, TransactionPrefix, TxIn, VarInt}; +use monero_rpc::monerod; +use monero_rpc::monerod::{GetOutputsOut, MonerodRpc}; +use rand::rngs::OsRng; +use rand::{Rng, SeedableRng}; +use std::convert::TryInto; +use std::iter; // [0u8; 32] = 466iKkx7MqVGD46dje3kwvSQRMfhNCvGaXTRATbQgz7kS8XTMmRmoTw9oJRRj523kTdQj8gXnF2xU9fmEPy9WXTr6pwetQj // [1u8; 32] = 47HCnKkBEeYfX5pScvBETAKdjBEPN7FcXEJPUqDPzWGCc6wC8VAdS8CjdtgKuSaY72K8fkoswjp176vbSPS8hzS17EZv8gj @@ -13,19 +23,27 @@ async fn monerod_integration_test() { let client = monerod::Client::localhost(18081).unwrap(); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let wallet = monero_wallet::Wallet::new_random(client.clone(), &mut rng); + let s_prime_a = curve25519_dalek::scalar::Scalar::random(&mut rng); + let s_b = curve25519_dalek::scalar::Scalar::random(&mut rng); + let lock_kp = monero::KeyPair { + view: monero::PrivateKey::from_scalar(curve25519_dalek::scalar::Scalar::random(&mut rng)), + spend: monero::PrivateKey::from_scalar(s_prime_a + s_b), + }; - let spend_tx = "d5d82405a655ebf721e32c8ef2f8b77f2476dd7cbdb06c05b9735abdf9a2a927".parse().unwrap(); + let lock_address = monero::Address::from_keypair(monero::Network::Mainnet, &lock_kp); + + dbg!(lock_address); + + let spend_tx = "d5d82405a655ebf721e32c8ef2f8b77f2476dd7cbdb06c05b9735abdf9a2a927" + .parse() + .unwrap(); let mut o_indexes_response = client.get_o_indexes(spend_tx).await.unwrap(); let real_key_offset = o_indexes_response.o_indexes.pop().unwrap(); // let (lower, upper) = dbg!(wallet.calculate_key_offset_boundaries().await.unwrap()); - let (lower, upper) = ( - VarInt(77), - VarInt(117), - ); + let (lower, upper) = (VarInt(77), VarInt(117)); let mut key_offsets = Vec::with_capacity(11); key_offsets.push(VarInt(real_key_offset)); @@ -42,15 +60,29 @@ async fn monerod_integration_test() { break; } } + + let response = client + .get_outs( + key_offsets + .iter() + .map(|offset| GetOutputsOut { + amount: 0, + index: offset.0, + }) + .collect(), + ) + .await + .unwrap(); + let ring = response + .outs + .iter() + .map(|out| out.key.point.decompress().unwrap()) + .collect::>() + .try_into() + .unwrap(); + key_offsets.sort(); - let outs = client.get_outs(key_offsets.iter().map(|offset| GetOutputsOut { - amount: 0, - index: offset.0 - }).collect()).await.unwrap(); - - dbg!(outs); - // [ // OutKey { // height: 80, @@ -133,22 +165,54 @@ async fn monerod_integration_test() { let relative_key_offsets = to_relative_offsets(&key_offsets); + let amount = 10_000_000; + let fee = 10_000; + // TODO: Pay lock amount (= amount + fee) to shared address (s_prime_a + s_b) + + let (bulletproof, out_pk, _) = monero::make_bulletproof(&mut rng, &[amount]).unwrap(); + let out_pk = out_pk + .iter() + .map(|c| monero::util::ringct::CtKey { + mask: monero::util::ringct::Key { key: c.to_bytes() }, + }) + .collect(); + + let prefix = TransactionPrefix { + version: VarInt(2), + unlock_time: Default::default(), + inputs: vec![TxIn::ToKey { + amount: VarInt(0), + key_offsets: relative_key_offsets, + k_image: todo!(), + }], + outputs: vec![], // 498AVruCDWgP9Az9LjMm89VWjrBrSZ2W2K3HFBiyzzrRjUJWUcCVxvY1iitfuKoek2FdX6MKGAD9Qb1G1P8QgR5jPmmt3Vj + extra: Default::default(), + }; + + let (adaptor_sig, adaptor) = + single_party_adaptor_sig(s_prime_a, s_b, ring, &prefix.hash().to_bytes()); + + let sig = adaptor_sig.adapt(adaptor); + let transaction = Transaction { - prefix: TransactionPrefix { - version: VarInt(2), - unlock_time: Default::default(), - inputs: vec![ - TxIn::ToKey { - amount: VarInt(0), - key_offsets: relative_key_offsets, - k_image: todo!() - } - ], - outputs: vec![], // 498AVruCDWgP9Az9LjMm89VWjrBrSZ2W2K3HFBiyzzrRjUJWUcCVxvY1iitfuKoek2FdX6MKGAD9Qb1G1P8QgR5jPmmt3Vj - extra: Default::default() + prefix, + signatures: Vec::new(), + rct_signatures: RctSig { + sig: Some(RctSigBase { + rct_type: RctType::Clsag, + txn_fee: VarInt(fee), + pseudo_outs: Vec::new(), + ecdh_info: todo!(), + out_pk, + }), + p: Some(RctSigPrunable { + range_sigs: Vec::new(), + bulletproofs: vec![bulletproof], + MGs: Vec::new(), + Clsags: vec![sig.into()], + pseudo_outs: todo!(), + }), }, - signatures: vec![], - rct_signatures: Default::default() }; } @@ -156,10 +220,48 @@ fn to_relative_offsets(offsets: &[VarInt]) -> Vec { let vals = offsets.iter(); let next_vals = offsets.iter().skip(1); - let diffs = vals.zip(next_vals).map(|(cur, next)| VarInt(next.0 - cur.0)); + let diffs = vals + .zip(next_vals) + .map(|(cur, next)| VarInt(next.0 - cur.0)); iter::once(offsets[0].clone()).chain(diffs).collect() } +/// First element of ring is the real pk. +fn single_party_adaptor_sig( + s_prime_a: Scalar, + s_b: Scalar, + ring: [EdwardsPoint; monero_adaptor::RING_SIZE], + msg: &[u8; 32], +) -> (monero_adaptor::AdaptorSignature, Scalar) { + let (r_a, R_a, R_prime_a) = { + let r_a = Scalar::random(&mut OsRng); + let R_a = r_a * ED25519_BASEPOINT_POINT; + + let pk_hashed_to_point = hash_point_to_point(ring[0]); + + let R_prime_a = r_a * pk_hashed_to_point; + + (r_a, R_a, R_prime_a) + }; + + let alice = monero_adaptor::Alice0::new(ring, *msg, R_a, R_prime_a, s_prime_a).unwrap(); + let bob = monero_adaptor::Bob0::new(ring, *msg, R_a, R_prime_a, s_b).unwrap(); + + let msg = alice.next_message(); + let bob = bob.receive(msg); + + let msg = bob.next_message(); + let alice = alice.receive(msg).unwrap(); + + let msg = alice.next_message(); + let bob = bob.receive(msg).unwrap(); + + let msg = bob.next_message(); + let alice = alice.receive(msg); + + (alice.adaptor_sig, r_a) +} + #[cfg(test)] mod tests { use super::*; @@ -182,19 +284,21 @@ mod tests { let relative_offsets = to_relative_offsets(&key_offsets); - assert_eq!(&relative_offsets, &[ - VarInt(78), - VarInt(3), - VarInt(10), - VarInt(0), - VarInt(5), - VarInt(2), - VarInt(3), - VarInt(11), - VarInt(1), - VarInt(1), - VarInt(3), - ]) + assert_eq!( + &relative_offsets, + &[ + VarInt(78), + VarInt(3), + VarInt(10), + VarInt(0), + VarInt(5), + VarInt(2), + VarInt(3), + VarInt(11), + VarInt(1), + VarInt(1), + VarInt(3), + ] + ) } - } diff --git a/monero-rpc/src/monerod.rs b/monero-rpc/src/monerod.rs index 5474b3d0..5561de58 100644 --- a/monero-rpc/src/monerod.rs +++ b/monero-rpc/src/monerod.rs @@ -52,7 +52,7 @@ impl Client { .inner .post(self.get_transactions.clone()) .json(&GetTransactionsPayload { - txs_hashes: txids.iter().map(|id| format!("{:x}", id)).collect() + txs_hashes: txids.iter().map(|id| format!("{:x}", id)).collect(), }) .send() .await?; @@ -67,9 +67,10 @@ impl Client { } pub async fn get_o_indexes(&self, txid: Hash) -> Result { - self.binary_request(self.get_o_indexes_bin_url.clone(), GetOIndexesPayload { - txid, - }) + self.binary_request( + self.get_o_indexes_bin_url.clone(), + GetOIndexesPayload { txid }, + ) .await } @@ -106,7 +107,7 @@ pub struct GenerateBlocks { pub height: u32, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Copy, Clone, Debug, Deserialize)] pub struct BlockCount { pub count: u32, } @@ -154,7 +155,7 @@ struct GetTransactionsResponse { #[derive(Clone, Debug, Deserialize)] struct GetTransactionsResponseEntry { #[serde(with = "monero_serde_hex_transaction")] - as_hex: Transaction + as_hex: Transaction, } #[derive(Clone, Debug, Serialize)] @@ -168,20 +169,20 @@ struct GetOutsPayload { outputs: Vec, } -#[derive(Clone, Debug, Serialize)] +#[derive(Copy, Clone, Debug, Serialize, PartialEq)] pub struct GetOutputsOut { pub amount: u64, pub index: u64, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq)] pub struct GetOutsResponse { #[serde(flatten)] pub base: BaseResponse, pub outs: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq)] pub struct OutKey { pub height: u64, #[serde(with = "byte_array")] @@ -193,7 +194,7 @@ pub struct OutKey { pub unlocked: bool, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq)] pub struct BaseResponse { pub credits: u64, pub status: Status, @@ -201,7 +202,7 @@ pub struct BaseResponse { pub untrusted: bool, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq)] pub struct GetOIndexesResponse { #[serde(flatten)] pub base: BaseResponse, @@ -209,7 +210,7 @@ pub struct GetOIndexesResponse { pub o_indexes: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Copy, Clone, Debug, Deserialize, PartialEq)] pub enum Status { #[serde(rename = "OK")] Ok, diff --git a/monero-rpc/src/wallet.rs b/monero-rpc/src/wallet.rs index 0c9a9f8e..ed2c78f0 100644 --- a/monero-rpc/src/wallet.rs +++ b/monero-rpc/src/wallet.rs @@ -82,7 +82,7 @@ pub struct GetAddress { pub address: String, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Copy)] pub struct GetBalance { pub balance: u64, pub blocks_to_unlock: u32, @@ -133,19 +133,19 @@ pub struct Transfer { pub unsigned_txset: String, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] pub struct BlockHeight { pub height: u32, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize)] #[serde(from = "CheckTxKeyResponse")] pub struct CheckTxKey { pub confirmations: u64, pub received: u64, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize)] struct CheckTxKeyResponse { pub confirmations: u64, pub received: u64, @@ -175,7 +175,7 @@ pub struct GenerateFromKeys { pub info: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Copy, Clone, Debug, Deserialize)] pub struct Refreshed { pub blocks_fetched: u32, pub received_money: bool, @@ -191,7 +191,7 @@ pub struct SweepAll { weight_list: Vec, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Copy, Debug, Clone, Deserialize)] pub struct Version { pub version: u32, } @@ -207,7 +207,7 @@ pub type WalletOpened = Empty; /// what the response of every RPC call is. Unfortunately, monerod likes to /// return empty objects instead of `null`s in certain cases. We use this struct /// to all the "deserialization" to happily continue. -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Copy, Clone, Deserialize)] pub struct Empty {} fn opt_key_from_blank<'de, D>(deserializer: D) -> Result, D::Error>