mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Reject torsioned spend keys to ensure we can spend the outputs we scan
This commit is contained in:
@@ -172,7 +172,6 @@ impl InternalScanner {
|
|||||||
// Our subtracting of a prime-order element means any torsion will be preserved
|
// Our subtracting of a prime-order element means any torsion will be preserved
|
||||||
// If someone wanted to malleate output keys with distinct torsions, only one will be
|
// If someone wanted to malleate output keys with distinct torsions, only one will be
|
||||||
// scanned accordingly (the one which has matching torsion of the spend key)
|
// scanned accordingly (the one which has matching torsion of the spend key)
|
||||||
// TODO: If there's a torsioned spend key, can we spend outputs to it?
|
|
||||||
let subaddress_spend_key =
|
let subaddress_spend_key =
|
||||||
output_key - (&output_derivations.shared_key * ED25519_BASEPOINT_TABLE);
|
output_key - (&output_derivations.shared_key * ED25519_BASEPOINT_TABLE);
|
||||||
self.subaddresses.get(&subaddress_spend_key.compress())
|
self.subaddresses.get(&subaddress_spend_key.compress())
|
||||||
|
|||||||
@@ -9,6 +9,21 @@ use crate::{
|
|||||||
address::{Network, AddressType, SubaddressIndex, MoneroAddress},
|
address::{Network, AddressType, SubaddressIndex, MoneroAddress},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An error while working with a ViewPair.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
|
pub enum ViewPairError {
|
||||||
|
/// The spend key was torsioned.
|
||||||
|
///
|
||||||
|
/// Torsioned spend keys are of questionable spendability. This library avoids that question by
|
||||||
|
/// rejecting such ViewPairs.
|
||||||
|
// CLSAG seems to support it if the challenge does a torsion clear, FCMP++ should ship with a
|
||||||
|
// torsion clear, yet it's not worth it to modify CLSAG sign to generate challenges until the
|
||||||
|
// torsion clears and ensure spendability (nor can we reasonably guarantee that in the future)
|
||||||
|
#[cfg_attr(feature = "std", error("torsioned spend key"))]
|
||||||
|
TorsionedSpendKey,
|
||||||
|
}
|
||||||
|
|
||||||
/// The pair of keys necessary to scan transactions.
|
/// The pair of keys necessary to scan transactions.
|
||||||
///
|
///
|
||||||
/// This is composed of the public spend key and the private view key.
|
/// This is composed of the public spend key and the private view key.
|
||||||
@@ -20,8 +35,11 @@ pub struct ViewPair {
|
|||||||
|
|
||||||
impl ViewPair {
|
impl ViewPair {
|
||||||
/// Create a new ViewPair.
|
/// Create a new ViewPair.
|
||||||
pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Self {
|
pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
|
||||||
ViewPair { spend, view }
|
if !spend.is_torsion_free() {
|
||||||
|
Err(ViewPairError::TorsionedSpendKey)?;
|
||||||
|
}
|
||||||
|
Ok(ViewPair { spend, view })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The public spend key for this ViewPair.
|
/// The public spend key for this ViewPair.
|
||||||
@@ -86,8 +104,8 @@ pub struct GuaranteedViewPair(pub(crate) ViewPair);
|
|||||||
|
|
||||||
impl GuaranteedViewPair {
|
impl GuaranteedViewPair {
|
||||||
/// Create a new GuaranteedViewPair.
|
/// Create a new GuaranteedViewPair.
|
||||||
pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Self {
|
pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
|
||||||
GuaranteedViewPair(ViewPair::new(spend, view))
|
ViewPair::new(spend, view).map(GuaranteedViewPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The public spend key for this GuaranteedViewPair.
|
/// The public spend key for this GuaranteedViewPair.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
|
|||||||
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||||
(
|
(
|
||||||
spend,
|
spend,
|
||||||
ViewPair::new(spend_pub, view.clone()),
|
ViewPair::new(spend_pub, view.clone()).unwrap(),
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
AddressType::Legacy,
|
AddressType::Legacy,
|
||||||
@@ -52,7 +52,7 @@ pub fn random_guaranteed_address() -> (Scalar, GuaranteedViewPair, MoneroAddress
|
|||||||
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||||
(
|
(
|
||||||
spend,
|
spend,
|
||||||
GuaranteedViewPair::new(spend_pub, view.clone()),
|
GuaranteedViewPair::new(spend_pub, view.clone()).unwrap(),
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
AddressType::Legacy,
|
AddressType::Legacy,
|
||||||
@@ -240,7 +240,7 @@ macro_rules! test {
|
|||||||
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||||
let view = ViewPair::new(spend_pub, view_priv.clone());
|
let view = ViewPair::new(spend_pub, view_priv.clone()).unwrap();
|
||||||
let addr = view.legacy_address(Network::Mainnet);
|
let addr = view.legacy_address(Network::Mainnet);
|
||||||
|
|
||||||
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
||||||
@@ -258,7 +258,7 @@ macro_rules! test {
|
|||||||
&ViewPair::new(
|
&ViewPair::new(
|
||||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||||
Zeroizing::new(Scalar::random(&mut OsRng))
|
Zeroizing::new(Scalar::random(&mut OsRng))
|
||||||
),
|
).unwrap(),
|
||||||
),
|
),
|
||||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ test!(
|
|||||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||||
let change_view =
|
let change_view =
|
||||||
ViewPair::new(&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, view_priv.clone());
|
ViewPair::new(&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, view_priv.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = SignableTransactionBuilder::new(
|
let mut builder = SignableTransactionBuilder::new(
|
||||||
rct_type,
|
rct_type,
|
||||||
@@ -123,7 +124,8 @@ test!(
|
|||||||
let sub_view = ViewPair::new(
|
let sub_view = ViewPair::new(
|
||||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||||
Zeroizing::new(Scalar::random(&mut OsRng)),
|
Zeroizing::new(Scalar::random(&mut OsRng)),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_payment(sub_view.subaddress(Network::Mainnet, SubaddressIndex::new(0, 1).unwrap()), 1);
|
.add_payment(sub_view.subaddress(Network::Mainnet, SubaddressIndex::new(0, 1).unwrap()), 1);
|
||||||
(builder.build().unwrap(), (change_view, sub_view))
|
(builder.build().unwrap(), (change_view, sub_view))
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ impl Monero {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view_pair(spend: EdwardsPoint) -> GuaranteedViewPair {
|
fn view_pair(spend: EdwardsPoint) -> GuaranteedViewPair {
|
||||||
GuaranteedViewPair::new(spend.0, Zeroizing::new(additional_key::<Monero>(0).0))
|
GuaranteedViewPair::new(spend.0, Zeroizing::new(additional_key::<Monero>(0).0)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_internal(spend: EdwardsPoint, subaddress: Option<SubaddressIndex>) -> Address {
|
fn address_internal(spend: EdwardsPoint, subaddress: Option<SubaddressIndex>) -> Address {
|
||||||
@@ -351,6 +351,7 @@ impl Monero {
|
|||||||
payments.push(Payment {
|
payments.push(Payment {
|
||||||
address: Address::new(
|
address: Address::new(
|
||||||
ViewPair::new(EdwardsPoint::generator().0, Zeroizing::new(Scalar::ONE.0))
|
ViewPair::new(EdwardsPoint::generator().0, Zeroizing::new(Scalar::ONE.0))
|
||||||
|
.unwrap()
|
||||||
.legacy_address(MoneroNetwork::Mainnet),
|
.legacy_address(MoneroNetwork::Mainnet),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -413,7 +414,7 @@ impl Monero {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn test_view_pair() -> ViewPair {
|
fn test_view_pair() -> ViewPair {
|
||||||
ViewPair::new(*EdwardsPoint::generator(), Zeroizing::new(Scalar::ONE.0))
|
ViewPair::new(*EdwardsPoint::generator(), Zeroizing::new(Scalar::ONE.0)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ async fn mint_and_burn_test() {
|
|||||||
use monero_wallet::{rpc::Rpc, ViewPair, address::Network};
|
use monero_wallet::{rpc::Rpc, ViewPair, address::Network};
|
||||||
|
|
||||||
let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
||||||
|
.unwrap()
|
||||||
.legacy_address(Network::Mainnet);
|
.legacy_address(Network::Mainnet);
|
||||||
|
|
||||||
let rpc = producer_handles.monero(ops).await;
|
let rpc = producer_handles.monero(ops).await;
|
||||||
@@ -353,7 +354,7 @@ async fn mint_and_burn_test() {
|
|||||||
|
|
||||||
// Grab the first output on the chain
|
// Grab the first output on the chain
|
||||||
let rpc = handles[0].monero(&ops).await;
|
let rpc = handles[0].monero(&ops).await;
|
||||||
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE));
|
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)).unwrap();
|
||||||
let mut scanner = Scanner::new(view_pair.clone());
|
let mut scanner = Scanner::new(view_pair.clone());
|
||||||
let output = scanner
|
let output = scanner
|
||||||
.scan(&rpc, &rpc.get_block_by_number(1).await.unwrap())
|
.scan(&rpc, &rpc.get_block_by_number(1).await.unwrap())
|
||||||
@@ -578,7 +579,8 @@ async fn mint_and_burn_test() {
|
|||||||
{
|
{
|
||||||
use monero_wallet::{transaction::Transaction, rpc::Rpc, ViewPair, Scanner};
|
use monero_wallet::{transaction::Transaction, rpc::Rpc, ViewPair, Scanner};
|
||||||
let rpc = handles[0].monero(&ops).await;
|
let rpc = handles[0].monero(&ops).await;
|
||||||
let mut scanner = Scanner::new(ViewPair::new(monero_spend, Zeroizing::new(monero_view)));
|
let mut scanner =
|
||||||
|
Scanner::new(ViewPair::new(monero_spend, Zeroizing::new(monero_view)).unwrap());
|
||||||
|
|
||||||
// Check for up to 5 minutes
|
// Check for up to 5 minutes
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ impl Coordinator {
|
|||||||
rpc
|
rpc
|
||||||
.generate_blocks(
|
.generate_blocks(
|
||||||
&ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
&ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
||||||
|
.unwrap()
|
||||||
.legacy_address(Network::Mainnet),
|
.legacy_address(Network::Mainnet),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ impl Wallet {
|
|||||||
let view_key = Scalar::random(&mut OsRng);
|
let view_key = Scalar::random(&mut OsRng);
|
||||||
|
|
||||||
let view_pair =
|
let view_pair =
|
||||||
ViewPair::new(ED25519_BASEPOINT_POINT * spend_key, Zeroizing::new(view_key));
|
ViewPair::new(ED25519_BASEPOINT_POINT * spend_key, Zeroizing::new(view_key)).unwrap();
|
||||||
|
|
||||||
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user