diff --git a/coins/monero/src/clsag/mod.rs b/coins/monero/src/clsag/mod.rs
index d2c0dcb0..9f2e97dd 100644
--- a/coins/monero/src/clsag/mod.rs
+++ b/coins/monero/src/clsag/mod.rs
@@ -1,6 +1,8 @@
use rand_core::{RngCore, CryptoRng};
use ff::Field;
+use thiserror::Error;
+
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
@@ -15,7 +17,6 @@ use monero::{
use crate::{
Commitment,
- transaction::SignableInput,
c_verify_clsag,
random_scalar,
hash_to_scalar,
@@ -27,11 +28,68 @@ mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::Multisig;
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("internal error ({0})")]
+ InternalError(String),
+ #[error("invalid ring member (member {0}, ring size {1})")]
+ InvalidRingMember(u8, u8),
+ #[error("invalid commitment")]
+ InvalidCommitment
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Input {
+ pub image: EdwardsPoint,
+ // Ring, the index we're signing for, and the actual commitment behind it
+ pub ring: Vec<[EdwardsPoint; 2]>,
+ pub i: usize,
+ pub commitment: Commitment
+}
+
+impl Input {
+ pub fn new(
+ image: EdwardsPoint,
+ ring: Vec<[EdwardsPoint; 2]>,
+ i: u8,
+ commitment: Commitment
+) -> Result {
+ let n = ring.len();
+ if n > u8::MAX.into() {
+ Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
+ }
+ if i >= (n as u8) {
+ Err(Error::InvalidRingMember(i, n as u8))?;
+ }
+ let i: usize = i.into();
+
+ // Validate the commitment matches
+ if ring[i][1] != commitment.calculate() {
+ Err(Error::InvalidCommitment)?;
+ }
+
+ Ok(Input { image, ring, i, commitment })
+ }
+
+ #[cfg(feature = "multisig")]
+ pub fn context(&self) -> Vec {
+ let mut context = self.image.compress().to_bytes().to_vec();
+ for pair in &self.ring {
+ // Doesn't include mixins[i] as CLSAG doesn't care and won't be affected by it
+ context.extend(&pair[0].compress().to_bytes());
+ context.extend(&pair[1].compress().to_bytes());
+ }
+ context.extend(&u8::try_from(self.i).unwrap().to_le_bytes());
+ // Doesn't include commitment as the above ring + index includes the commitment
+ context
+ }
+}
+
#[allow(non_snake_case)]
pub(crate) fn sign_core(
rng: &mut R,
msg: &[u8; 32],
- input: &SignableInput,
+ input: &Input,
mask: Scalar,
A: EdwardsPoint,
AH: EdwardsPoint
@@ -148,7 +206,7 @@ pub(crate) fn sign_core(
pub fn sign(
rng: &mut R,
msg: [u8; 32],
- inputs: &[(Scalar, SignableInput)],
+ inputs: &[(Scalar, Input)],
sum_outputs: Scalar
) -> Option> {
if inputs.len() == 0 {
diff --git a/coins/monero/src/clsag/multisig.rs b/coins/monero/src/clsag/multisig.rs
index 423e94cf..fd1221ff 100644
--- a/coins/monero/src/clsag/multisig.rs
+++ b/coins/monero/src/clsag/multisig.rs
@@ -19,7 +19,7 @@ use monero::util::ringct::{Key, Clsag};
use crate::{
hash_to_point,
frost::{MultisigError, Ed25519, DLEqProof},
- clsag::{SignableInput, sign_core, verify}
+ clsag::{Input, sign_core, verify}
};
#[allow(non_snake_case)]
@@ -40,7 +40,7 @@ pub struct Multisig {
AH: dfg::EdwardsPoint,
msg: [u8; 32],
- input: SignableInput,
+ input: Input,
interim: Option
}
@@ -49,7 +49,7 @@ impl Multisig {
pub fn new(
rng: &mut R,
msg: [u8; 32],
- input: SignableInput
+ input: Input
) -> Result {
let mut seed = [0; 32];
rng.fill_bytes(&mut seed);
diff --git a/coins/monero/src/transaction/mod.rs b/coins/monero/src/transaction/mod.rs
index afa9f793..54618055 100644
--- a/coins/monero/src/transaction/mod.rs
+++ b/coins/monero/src/transaction/mod.rs
@@ -35,12 +35,6 @@ mod mixins;
#[derive(Error, Debug)]
pub enum TransactionError {
- #[error("internal error ({0})")]
- InternalError(String),
- #[error("invalid ring member (member {0}, ring size {1})")]
- InvalidRingMember(u8, u8),
- #[error("invalid commitment")]
- InvalidCommitment,
#[error("no inputs")]
NoInputs,
#[error("too many outputs")]
@@ -51,6 +45,8 @@ pub enum TransactionError {
InvalidAddress,
#[error("rpc error ({0})")]
RpcError(RpcError),
+ #[error("clsag error ({0})")]
+ ClsagError(clsag::Error),
#[error("invalid transaction ({0})")]
InvalidTransaction(RpcError)
}
@@ -122,55 +118,6 @@ pub fn scan_tx(tx: &Transaction, view: Scalar, spend: EdwardsPoint) -> Vec,
- // Ring, the index we're signing for, and the actual commitment behind it
- pub(crate) ring: Vec<[EdwardsPoint; 2]>,
- pub(crate) i: usize,
- pub(crate) commitment: Commitment
-}
-
-impl SignableInput {
- pub fn new(
- image: EdwardsPoint,
- mixins: Vec,
- ring: Vec<[EdwardsPoint; 2]>,
- i: u8,
- commitment: Commitment
- ) -> Result {
- let n = ring.len();
- if n > u8::MAX.into() {
- Err(TransactionError::InternalError("max ring size in this library is u8 max".to_string()))?;
- }
- if i >= (n as u8) {
- Err(TransactionError::InvalidRingMember(i, n as u8))?;
- }
- let i: usize = i.into();
-
- // Validate the commitment matches
- if ring[i][1] != commitment.calculate() {
- Err(TransactionError::InvalidCommitment)?;
- }
-
- Ok(SignableInput { image, mixins, ring, i, commitment })
- }
-
- #[cfg(feature = "multisig")]
- pub fn context(&self) -> Vec {
- let mut context = self.image.compress().to_bytes().to_vec();
- for pair in &self.ring {
- // Doesn't include mixins[i] as CLSAG doesn't care and won't be affected by it
- context.extend(&pair[0].compress().to_bytes());
- context.extend(&pair[1].compress().to_bytes());
- }
- context.extend(&u8::try_from(self.i).unwrap().to_le_bytes());
- // Doesn't include commitment as the above ring + index includes the commitment
- context
- }
-}
-
#[allow(non_snake_case)]
fn shared_key(s: Scalar, P: &EdwardsPoint, o: usize) -> Scalar {
let mut shared = (s * P).mul_by_cofactor().compress().to_bytes().to_vec();
@@ -269,29 +216,30 @@ pub async fn send(
));
// Handle inputs
+ let mut mixins = Vec::with_capacity(inputs.len());
let mut signable = Vec::with_capacity(inputs.len());
- for input in inputs {
- let (m, mixins) = mixins::select(
+ for (i, input) in inputs.iter().enumerate() {
+ let (m, mix) = mixins::select(
rpc.get_o_indexes(input.tx).await.map_err(|e| TransactionError::RpcError(e))?[input.o]
);
+ mixins.push(mix);
signable.push((
spend + input.key_offset,
- SignableInput::new(
+ clsag::Input::new(
key_image::generate(&(spend + input.key_offset)),
- mixins.clone(),
- rpc.get_ring(&mixins).await.map_err(|e| TransactionError::RpcError(e))?,
+ rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?,
m,
input.commitment
- )?
+ ).map_err(|e| TransactionError::ClsagError(e))?
));
}
let prefix = TransactionPrefix {
version: VarInt(2),
unlock_time: VarInt(0),
- inputs: signable.iter().map(|input| TxIn::ToKey {
+ inputs: signable.iter().enumerate().map(|(i, input)| TxIn::ToKey {
amount: VarInt(0),
- key_offsets: mixins::offset(&input.1.mixins).iter().map(|x| VarInt(*x)).collect(),
+ key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(),
k_image: KeyImage {
image: Hash(input.1.image.compress().to_bytes())
}
diff --git a/coins/monero/tests/clsag.rs b/coins/monero/tests/clsag.rs
index 361bb320..ba1dd4f1 100644
--- a/coins/monero/tests/clsag.rs
+++ b/coins/monero/tests/clsag.rs
@@ -3,7 +3,7 @@ use rand_chacha::ChaCha12Rng;
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
-use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag, transaction::SignableInput};
+use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag};
#[cfg(feature = "multisig")]
use ::frost::sign;
@@ -47,9 +47,8 @@ fn test_single() {
msg,
&vec![(
secrets[0],
- SignableInput::new(
+ clsag::Input::new(
image,
- [0; RING_LEN as usize].to_vec(),
ring.clone(),
RING_INDEX,
Commitment::new(secrets[1], AMOUNT)
@@ -113,7 +112,7 @@ fn test_multisig() -> Result<(), MultisigError> {
clsag::Multisig::new(
&mut ChaCha12Rng::seed_from_u64(1),
msg,
- SignableInput::new(image, vec![], ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap()
+ clsag::Input::new(image, ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap()
).unwrap(),
keys[i - 1].clone(),
&(1 ..= t).collect::>()