Support sending to integrated addresses

This commit is contained in:
Luke Parker 2022-08-22 06:54:01 -04:00
parent 99b683b843
commit f0b914c721
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
3 changed files with 66 additions and 47 deletions

View file

@ -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 {

View file

@ -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,

View file

@ -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()));