mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +00:00
Add a public TransactionKeys
struct to monero-wallet
monero-wallet ships an Eventuality, yet it's across the entire transaction. It can't prove a single output's state with a traditional payment proof. By adding this new object, another library can obtain the ephemeral randomness used and do any/every proof they want regarding a transaction's outputs. Necessary for https://github.com/serai-dex/serai/issues/599.
This commit is contained in:
parent
23b433fe6c
commit
44d05518aa
2 changed files with 58 additions and 24 deletions
|
@ -29,6 +29,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod tx_keys;
|
mod tx_keys;
|
||||||
|
pub use tx_keys::TransactionKeys;
|
||||||
mod tx;
|
mod tx;
|
||||||
mod eventuality;
|
mod eventuality;
|
||||||
pub use eventuality::Eventuality;
|
pub use eventuality::Eventuality;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std_shims::{vec, vec::Vec};
|
use std_shims::{vec, vec::Vec};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use rand_core::SeedableRng;
|
use rand_core::SeedableRng;
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
@ -15,28 +15,61 @@ use crate::{
|
||||||
send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
|
send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn seeded_rng(
|
||||||
|
dst: &'static [u8],
|
||||||
|
outgoing_view_key: &[u8; 32],
|
||||||
|
mut input_keys: Vec<EdwardsPoint>,
|
||||||
|
) -> ChaCha20Rng {
|
||||||
|
// Apply the DST
|
||||||
|
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
||||||
|
transcript.extend(dst);
|
||||||
|
|
||||||
|
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
||||||
|
transcript.extend(outgoing_view_key);
|
||||||
|
|
||||||
|
// We sort the inputs here to ensure a consistent order
|
||||||
|
// We use the key image sort as it's applicable and well-defined, not because these are key
|
||||||
|
// images
|
||||||
|
input_keys.sort_by(key_image_sort);
|
||||||
|
|
||||||
|
// Ensure uniqueness across transactions by binding to a use-once object
|
||||||
|
// The keys for the inputs is binding to their key images, making them use-once
|
||||||
|
for key in input_keys {
|
||||||
|
transcript.extend(key.compress().to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = ChaCha20Rng::from_seed(keccak256(&transcript));
|
||||||
|
transcript.zeroize();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator yielding an endless amount of ephemeral keys to use within a transaction.
|
||||||
|
///
|
||||||
|
/// This is used when sending and can be used after sending to re-derive the keys used, as
|
||||||
|
/// necessary for payment proofs.
|
||||||
|
pub struct TransactionKeys(ChaCha20Rng);
|
||||||
|
impl TransactionKeys {
|
||||||
|
/// Construct a new `TransactionKeys`.
|
||||||
|
///
|
||||||
|
/// `input_keys` is the list of keys from the outputs spent within this transaction.
|
||||||
|
pub fn new(outgoing_view_key: &Zeroizing<[u8; 32]>, input_keys: Vec<EdwardsPoint>) -> Self {
|
||||||
|
Self(seeded_rng(b"transaction_keys", outgoing_view_key, input_keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Iterator for TransactionKeys {
|
||||||
|
type Item = Zeroizing<Scalar>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
Some(Zeroizing::new(Scalar::random(&mut self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SignableTransaction {
|
impl SignableTransaction {
|
||||||
|
fn input_keys(&self) -> Vec<EdwardsPoint> {
|
||||||
|
self.inputs.iter().map(OutputWithDecoys::key).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
|
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
|
||||||
// Apply the DST
|
seeded_rng(dst, &self.outgoing_view_key, self.input_keys())
|
||||||
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
|
||||||
transcript.extend(dst);
|
|
||||||
|
|
||||||
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
|
||||||
transcript.extend(self.outgoing_view_key.as_slice());
|
|
||||||
|
|
||||||
// Ensure uniqueness across transactions by binding to a use-once object
|
|
||||||
// The keys for the inputs is binding to their key images, making them use-once
|
|
||||||
let mut input_keys = self.inputs.iter().map(OutputWithDecoys::key).collect::<Vec<_>>();
|
|
||||||
// We sort the inputs mid-way through TX construction, so apply our own sort to ensure a
|
|
||||||
// consistent order
|
|
||||||
// We use the key image sort as it's applicable and well-defined, not because these are key
|
|
||||||
// images
|
|
||||||
input_keys.sort_by(key_image_sort);
|
|
||||||
for key in input_keys {
|
|
||||||
transcript.extend(key.compress().to_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
ChaCha20Rng::from_seed(keccak256(&transcript))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_payments_to_subaddresses(&self) -> bool {
|
fn has_payments_to_subaddresses(&self) -> bool {
|
||||||
|
@ -81,14 +114,14 @@ impl SignableTransaction {
|
||||||
|
|
||||||
// Calculate the transaction keys used as randomness.
|
// Calculate the transaction keys used as randomness.
|
||||||
fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
|
fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
|
||||||
let mut rng = self.seeded_rng(b"transaction_keys");
|
let mut tx_keys = TransactionKeys::new(&self.outgoing_view_key, self.input_keys());
|
||||||
|
|
||||||
let tx_key = Zeroizing::new(Scalar::random(&mut rng));
|
let tx_key = tx_keys.next().unwrap();
|
||||||
|
|
||||||
let mut additional_keys = vec![];
|
let mut additional_keys = vec![];
|
||||||
if self.should_use_additional_keys() {
|
if self.should_use_additional_keys() {
|
||||||
for _ in 0 .. self.payments.len() {
|
for _ in 0 .. self.payments.len() {
|
||||||
additional_keys.push(Zeroizing::new(Scalar::random(&mut rng)));
|
additional_keys.push(tx_keys.next().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(tx_key, additional_keys)
|
(tx_key, additional_keys)
|
||||||
|
|
Loading…
Reference in a new issue