mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Add OutputType::Forwarded to ensure a user's transfer in isn't misclassified
If a user transferred in without an InInstruction, and the amount exactly matched a forwarded output, the user's output would fulfill the forwarding. Then the forwarded output would come along, have no InInstruction, and be refunded (to the prior multisig) when the user should've been refunded. Adding this new address type resolves such concerns.
This commit is contained in:
@@ -302,6 +302,7 @@ impl BlockTrait<Bitcoin> for Block {
|
||||
const KEY_DST: &[u8] = b"Serai Bitcoin Output Offset";
|
||||
static BRANCH_OFFSET: Lazy<Scalar> = Lazy::new(|| Secp256k1::hash_to_F(KEY_DST, b"branch"));
|
||||
static CHANGE_OFFSET: Lazy<Scalar> = Lazy::new(|| Secp256k1::hash_to_F(KEY_DST, b"change"));
|
||||
static FORWARD_OFFSET: Lazy<Scalar> = Lazy::new(|| Secp256k1::hash_to_F(KEY_DST, b"forward"));
|
||||
|
||||
// Always construct the full scanner in order to ensure there's no collisions
|
||||
fn scanner(
|
||||
@@ -325,6 +326,7 @@ fn scanner(
|
||||
|
||||
register(OutputType::Branch, *BRANCH_OFFSET);
|
||||
register(OutputType::Change, *CHANGE_OFFSET);
|
||||
register(OutputType::Forwarded, *FORWARD_OFFSET);
|
||||
|
||||
(scanner, offsets, kinds)
|
||||
}
|
||||
@@ -550,6 +552,11 @@ impl Network for Bitcoin {
|
||||
Self::address(key + (ProjectivePoint::GENERATOR * offsets[&OutputType::Change]))
|
||||
}
|
||||
|
||||
fn forward_address(key: ProjectivePoint) -> Address {
|
||||
let (_, offsets, _) = scanner(key);
|
||||
Self::address(key + (ProjectivePoint::GENERATOR * offsets[&OutputType::Forwarded]))
|
||||
}
|
||||
|
||||
async fn get_latest_block_number(&self) -> Result<usize, NetworkError> {
|
||||
self.rpc.get_latest_block_number().await.map_err(|_| NetworkError::ConnectionError)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,9 @@ pub enum OutputType {
|
||||
|
||||
// Should be added to the available UTXO pool with no further action
|
||||
Change,
|
||||
|
||||
// Forwarded output from the prior multisig
|
||||
Forwarded,
|
||||
}
|
||||
|
||||
impl OutputType {
|
||||
@@ -79,6 +82,7 @@ impl OutputType {
|
||||
OutputType::External => 0,
|
||||
OutputType::Branch => 1,
|
||||
OutputType::Change => 2,
|
||||
OutputType::Forwarded => 3,
|
||||
}])
|
||||
}
|
||||
|
||||
@@ -89,6 +93,7 @@ impl OutputType {
|
||||
0 => OutputType::External,
|
||||
1 => OutputType::Branch,
|
||||
2 => OutputType::Change,
|
||||
3 => OutputType::Forwarded,
|
||||
_ => Err(io::Error::new(io::ErrorKind::Other, "invalid OutputType"))?,
|
||||
})
|
||||
}
|
||||
@@ -293,6 +298,8 @@ pub trait Network: 'static + Send + Sync + Clone + PartialEq + Eq + Debug {
|
||||
fn branch_address(key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
||||
/// Address for the given group key to use for change.
|
||||
fn change_address(key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
||||
/// Address for forwarded outputs from prior multisigs.
|
||||
fn forward_address(key: <Self::Curve as Ciphersuite>::G) -> Self::Address;
|
||||
|
||||
/// Get the latest block's number.
|
||||
async fn get_latest_block_number(&self) -> Result<usize, NetworkError>;
|
||||
|
||||
@@ -49,6 +49,7 @@ pub struct Output(SpendableOutput, Vec<u8>);
|
||||
const EXTERNAL_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(0, 0);
|
||||
const BRANCH_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(1, 0);
|
||||
const CHANGE_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(2, 0);
|
||||
const FORWARD_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(3, 0);
|
||||
|
||||
impl OutputTrait<Monero> for Output {
|
||||
// While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug.
|
||||
@@ -61,6 +62,7 @@ impl OutputTrait<Monero> for Output {
|
||||
EXTERNAL_SUBADDRESS => OutputType::External,
|
||||
BRANCH_SUBADDRESS => OutputType::Branch,
|
||||
CHANGE_SUBADDRESS => OutputType::Change,
|
||||
FORWARD_SUBADDRESS => OutputType::Forwarded,
|
||||
_ => panic!("unrecognized address was scanned for"),
|
||||
}
|
||||
}
|
||||
@@ -262,6 +264,7 @@ impl Monero {
|
||||
debug_assert!(EXTERNAL_SUBADDRESS.is_none());
|
||||
scanner.register_subaddress(BRANCH_SUBADDRESS.unwrap());
|
||||
scanner.register_subaddress(CHANGE_SUBADDRESS.unwrap());
|
||||
scanner.register_subaddress(FORWARD_SUBADDRESS.unwrap());
|
||||
scanner
|
||||
}
|
||||
|
||||
@@ -484,6 +487,10 @@ impl Network for Monero {
|
||||
Self::address_internal(key, CHANGE_SUBADDRESS)
|
||||
}
|
||||
|
||||
fn forward_address(key: EdwardsPoint) -> Address {
|
||||
Self::address_internal(key, FORWARD_SUBADDRESS)
|
||||
}
|
||||
|
||||
async fn get_latest_block_number(&self) -> Result<usize, NetworkError> {
|
||||
// Monero defines height as chain length, so subtract 1 for block number
|
||||
Ok(self.rpc.get_height().await.map_err(map_rpc_err)? - 1)
|
||||
@@ -520,7 +527,7 @@ impl Network for Monero {
|
||||
// This just ensures nothing invalid makes it through
|
||||
for tx_outputs in &txs {
|
||||
for output in tx_outputs {
|
||||
assert!([EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS]
|
||||
assert!([EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARD_SUBADDRESS]
|
||||
.contains(&output.output.metadata.subaddress));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user