mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 12:09:37 +00:00
Support sending to integrated addresses
This commit is contained in:
parent
99b683b843
commit
f0b914c721
3 changed files with 66 additions and 47 deletions
|
@ -40,20 +40,24 @@ pub(crate) fn uniqueness(inputs: &[Input]) -> [u8; 32] {
|
||||||
hash(&u)
|
hash(&u)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hs("view_tag" || 8Ra || o) and Hs(8Ra || o) with uniqueness inclusion as an option
|
// Hs("view_tag" || 8Ra || o), Hs(8Ra || o), and H(8Ra || 0x8d) with uniqueness inclusion in the
|
||||||
|
// Scalar as an option
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub(crate) fn shared_key(
|
pub(crate) fn shared_key(
|
||||||
uniqueness: Option<[u8; 32]>,
|
uniqueness: Option<[u8; 32]>,
|
||||||
s: &Scalar,
|
s: &Scalar,
|
||||||
P: &EdwardsPoint,
|
P: &EdwardsPoint,
|
||||||
o: usize,
|
o: usize,
|
||||||
) -> (u8, Scalar) {
|
) -> (u8, Scalar, [u8; 8]) {
|
||||||
// 8Ra
|
// 8Ra
|
||||||
let mut output_derivation = (s * P).mul_by_cofactor().compress().to_bytes().to_vec();
|
let mut output_derivation = (s * P).mul_by_cofactor().compress().to_bytes().to_vec();
|
||||||
// || o
|
// || o
|
||||||
write_varint(&o.try_into().unwrap(), &mut output_derivation).unwrap();
|
write_varint(&o.try_into().unwrap(), &mut output_derivation).unwrap();
|
||||||
|
|
||||||
let view_tag = hash(&[b"view_tag".as_ref(), &output_derivation].concat())[0];
|
let view_tag = hash(&[b"view_tag".as_ref(), &output_derivation].concat())[0];
|
||||||
|
let mut payment_id_xor = [0; 8];
|
||||||
|
payment_id_xor
|
||||||
|
.copy_from_slice(&hash(&[output_derivation.as_ref(), [0x8d].as_ref()].concat())[.. 8]);
|
||||||
|
|
||||||
// uniqueness ||
|
// uniqueness ||
|
||||||
let shared_key = if let Some(uniqueness) = uniqueness {
|
let shared_key = if let Some(uniqueness) = uniqueness {
|
||||||
|
@ -62,13 +66,13 @@ pub(crate) fn shared_key(
|
||||||
output_derivation
|
output_derivation
|
||||||
};
|
};
|
||||||
|
|
||||||
(view_tag, hash_to_scalar(&shared_key))
|
(view_tag, hash_to_scalar(&shared_key), payment_id_xor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] {
|
pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] {
|
||||||
let mut amount_mask = b"amount".to_vec();
|
let mut amount_mask = b"amount".to_vec();
|
||||||
amount_mask.extend(key.to_bytes());
|
amount_mask.extend(key.to_bytes());
|
||||||
(amount ^ u64::from_le_bytes(hash(&amount_mask)[0 .. 8].try_into().unwrap())).to_le_bytes()
|
(amount ^ u64::from_le_bytes(hash(&amount_mask)[.. 8].try_into().unwrap())).to_le_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn amount_decryption(amount: [u8; 8], key: Scalar) -> u64 {
|
fn amount_decryption(amount: [u8; 8], key: Scalar) -> u64 {
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl Transaction {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
||||||
for key in &keys {
|
for key in &keys {
|
||||||
let (view_tag, key_offset) = shared_key(
|
let (view_tag, key_offset, _) = shared_key(
|
||||||
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
|
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
|
||||||
&view.view,
|
&view.view,
|
||||||
key,
|
key,
|
||||||
|
|
|
@ -47,35 +47,39 @@ impl SendOutput {
|
||||||
fn new<R: RngCore + CryptoRng>(
|
fn new<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
unique: [u8; 32],
|
unique: [u8; 32],
|
||||||
output: (Address, u64),
|
output: (usize, (Address, u64)),
|
||||||
o: usize,
|
) -> (SendOutput, Option<[u8; 8]>) {
|
||||||
) -> SendOutput {
|
let o = output.0;
|
||||||
|
let output = output.1;
|
||||||
|
|
||||||
let r = random_scalar(rng);
|
let r = random_scalar(rng);
|
||||||
let (view_tag, shared_key) =
|
let (view_tag, shared_key, payment_id_xor) =
|
||||||
shared_key(Some(unique).filter(|_| output.0.meta.kind.guaranteed()), &r, &output.0.view, o);
|
shared_key(Some(unique).filter(|_| output.0.meta.kind.guaranteed()), &r, &output.0.view, o);
|
||||||
|
|
||||||
if output.0.meta.kind.payment_id().is_some() {
|
(
|
||||||
unimplemented!("integrated addresses aren't currently supported");
|
SendOutput {
|
||||||
}
|
R: if !output.0.meta.kind.subaddress() {
|
||||||
|
&r * &ED25519_BASEPOINT_TABLE
|
||||||
SendOutput {
|
} else {
|
||||||
R: if !output.0.meta.kind.subaddress() {
|
r * output.0.spend
|
||||||
&r * &ED25519_BASEPOINT_TABLE
|
},
|
||||||
} else {
|
view_tag,
|
||||||
r * output.0.spend
|
dest: ((&shared_key * &ED25519_BASEPOINT_TABLE) + output.0.spend),
|
||||||
|
commitment: Commitment::new(commitment_mask(shared_key), output.1),
|
||||||
|
amount: amount_encryption(output.1, shared_key),
|
||||||
},
|
},
|
||||||
view_tag,
|
output
|
||||||
dest: ((&shared_key * &ED25519_BASEPOINT_TABLE) + output.0.spend),
|
.0
|
||||||
commitment: Commitment::new(commitment_mask(shared_key), output.1),
|
.payment_id()
|
||||||
amount: amount_encryption(output.1, shared_key),
|
.map(|id| (u64::from_le_bytes(id) ^ u64::from_le_bytes(payment_id_xor)).to_le_bytes()),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Error, Debug)]
|
#[derive(Clone, Error, Debug)]
|
||||||
pub enum TransactionError {
|
pub enum TransactionError {
|
||||||
#[error("invalid address")]
|
#[error("multiple addresses with payment IDs")]
|
||||||
InvalidAddress,
|
MultiplePaymentIds,
|
||||||
#[error("no inputs")]
|
#[error("no inputs")]
|
||||||
NoInputs,
|
NoInputs,
|
||||||
#[error("no outputs")]
|
#[error("no outputs")]
|
||||||
|
@ -178,21 +182,23 @@ impl SignableTransaction {
|
||||||
change_address: Option<Address>,
|
change_address: Option<Address>,
|
||||||
fee_rate: Fee,
|
fee_rate: Fee,
|
||||||
) -> Result<SignableTransaction, TransactionError> {
|
) -> Result<SignableTransaction, TransactionError> {
|
||||||
// Make sure all addresses are valid
|
// Make sure there's only one payment ID
|
||||||
let test = |addr: Address| {
|
{
|
||||||
if addr.meta.kind.payment_id().is_some() {
|
let mut payment_ids = 0;
|
||||||
// TODO
|
let mut count = |addr: Address| {
|
||||||
Err(TransactionError::InvalidAddress)
|
if addr.payment_id().is_some() {
|
||||||
} else {
|
payment_ids += 1
|
||||||
Ok(())
|
}
|
||||||
|
};
|
||||||
|
for payment in &payments {
|
||||||
|
count(payment.0);
|
||||||
|
}
|
||||||
|
if let Some(change) = change_address {
|
||||||
|
count(change);
|
||||||
|
}
|
||||||
|
if payment_ids > 1 {
|
||||||
|
Err(TransactionError::MultiplePaymentIds)?;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for payment in &payments {
|
|
||||||
test(payment.0)?;
|
|
||||||
}
|
|
||||||
if let Some(change) = change_address {
|
|
||||||
test(change)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
|
@ -258,12 +264,23 @@ impl SignableTransaction {
|
||||||
self.payments.shuffle(rng);
|
self.payments.shuffle(rng);
|
||||||
|
|
||||||
// Actually create the outputs
|
// Actually create the outputs
|
||||||
let outputs = self
|
let mut outputs = Vec::with_capacity(self.payments.len());
|
||||||
.payments
|
let mut id = None;
|
||||||
.drain(..)
|
for payment in self.payments.drain(..).enumerate() {
|
||||||
.enumerate()
|
let (output, payment_id) = SendOutput::new(rng, uniqueness, payment);
|
||||||
.map(|(o, output)| SendOutput::new(rng, uniqueness, output, o))
|
outputs.push(output);
|
||||||
.collect::<Vec<_>>();
|
id = id.or(payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include a random payment ID if we don't actually have one
|
||||||
|
// It prevents transactions from leaking if they're sending to integrated addresses or not
|
||||||
|
let id = if let Some(id) = id {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
let mut id = [0; 8];
|
||||||
|
rng.fill_bytes(&mut id);
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
let commitments = outputs.iter().map(|output| output.commitment.clone()).collect::<Vec<_>>();
|
let commitments = outputs.iter().map(|output| output.commitment.clone()).collect::<Vec<_>>();
|
||||||
let sum = commitments.iter().map(|commitment| commitment.mask).sum();
|
let sum = commitments.iter().map(|commitment| commitment.mask).sum();
|
||||||
|
@ -276,8 +293,6 @@ impl SignableTransaction {
|
||||||
let mut extra = Extra::new(outputs.iter().map(|output| output.R).collect());
|
let mut extra = Extra::new(outputs.iter().map(|output| output.R).collect());
|
||||||
|
|
||||||
// Additionally include a random payment ID
|
// Additionally include a random payment ID
|
||||||
let mut id = [0; 8];
|
|
||||||
rng.fill_bytes(&mut id);
|
|
||||||
extra.push(ExtraField::PaymentId(PaymentId::Encrypted(id)));
|
extra.push(ExtraField::PaymentId(PaymentId::Encrypted(id)));
|
||||||
|
|
||||||
let mut serialized = Vec::with_capacity(Extra::fee_weight(outputs.len()));
|
let mut serialized = Vec::with_capacity(Extra::fee_weight(outputs.len()));
|
||||||
|
|
Loading…
Reference in a new issue