mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 01:17:36 +00:00
Clean up code, correct a few bugs, add leader based one-time-key/BP gen
This commit is contained in:
parent
c4b7cb71d7
commit
1d0a0c7c16
6 changed files with 253 additions and 114 deletions
|
@ -12,7 +12,7 @@ extern "C" {
|
||||||
ge_p3_tobytes(point, &e_p3);
|
ge_p3_tobytes(point, &e_p3);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* c_gen_bp(uint8_t len, uint64_t* a, uint8_t* m) {
|
uint8_t* c_generate_bp(uint8_t len, uint64_t* a, uint8_t* m) {
|
||||||
rct::keyV masks;
|
rct::keyV masks;
|
||||||
std::vector<uint64_t> amounts;
|
std::vector<uint64_t> amounts;
|
||||||
masks.resize(len);
|
masks.resize(len);
|
||||||
|
@ -33,6 +33,26 @@ extern "C" {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool c_verify_bp(uint s_len, uint8_t* s, uint8_t c_len, uint8_t* c) {
|
||||||
|
rct::Bulletproof bp;
|
||||||
|
std::stringstream ss;
|
||||||
|
std::string str;
|
||||||
|
str.assign((char*) s, (size_t) s_len);
|
||||||
|
ss << str;
|
||||||
|
binary_archive<false> ba(ss);
|
||||||
|
::serialization::serialize(ba, bp);
|
||||||
|
if (!ss.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bp.V.resize(c_len);
|
||||||
|
for (uint8_t i = 0; i < c_len; i++) {
|
||||||
|
memcpy(bp.V[i].bytes, &c[i * 32], 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
try { return rct::bulletproof_VERIFY(bp); } catch(...) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
bool c_verify_clsag(uint s_len, uint8_t* s, uint8_t* I, uint8_t k_len, uint8_t* k, uint8_t* m, uint8_t* p) {
|
bool c_verify_clsag(uint s_len, uint8_t* s, uint8_t* I, uint8_t k_len, uint8_t* k, uint8_t* m, uint8_t* p) {
|
||||||
rct::clsag clsag;
|
rct::clsag clsag;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
@ -59,6 +79,6 @@ extern "C" {
|
||||||
rct::key pseudo_out;
|
rct::key pseudo_out;
|
||||||
memcpy(pseudo_out.bytes, p, 32);
|
memcpy(pseudo_out.bytes, p, 32);
|
||||||
|
|
||||||
return verRctCLSAGSimple(msg, clsag, keys, pseudo_out);
|
try { return verRctCLSAGSimple(msg, clsag, keys, pseudo_out); } catch(...) { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use monero::{consensus::deserialize, util::ringct::Bulletproof};
|
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
||||||
|
|
||||||
use crate::{Commitment, transaction::TransactionError, free, c_gen_bp};
|
use monero::{consensus::{Encodable, deserialize}, util::ringct::Bulletproof};
|
||||||
|
|
||||||
pub fn generate(outputs: Vec<Commitment>) -> Result<Bulletproof, TransactionError> {
|
use crate::{Commitment, transaction::TransactionError, free, c_generate_bp, c_verify_bp};
|
||||||
|
|
||||||
|
pub fn generate(outputs: &[Commitment]) -> Result<Bulletproof, TransactionError> {
|
||||||
if outputs.len() > 16 {
|
if outputs.len() > 16 {
|
||||||
return Err(TransactionError::TooManyOutputs)?;
|
return Err(TransactionError::TooManyOutputs)?;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +13,7 @@ pub fn generate(outputs: Vec<Commitment>) -> Result<Bulletproof, TransactionErro
|
||||||
let amounts: Vec<u64> = outputs.iter().map(|commitment| commitment.amount).collect();
|
let amounts: Vec<u64> = outputs.iter().map(|commitment| commitment.amount).collect();
|
||||||
let res;
|
let res;
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = c_gen_bp(outputs.len() as u8, amounts.as_ptr(), masks.as_ptr());
|
let ptr = c_generate_bp(outputs.len() as u8, amounts.as_ptr(), masks.as_ptr());
|
||||||
let len = ((ptr.read() as usize) << 8) + (ptr.add(1).read() as usize);
|
let len = ((ptr.read() as usize) << 8) + (ptr.add(1).read() as usize);
|
||||||
res = deserialize(
|
res = deserialize(
|
||||||
std::slice::from_raw_parts(ptr.add(2), len)
|
std::slice::from_raw_parts(ptr.add(2), len)
|
||||||
|
@ -21,3 +23,18 @@ pub fn generate(outputs: Vec<Commitment>) -> Result<Bulletproof, TransactionErro
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn verify(bp: &Bulletproof, commitments: &[EdwardsPoint]) -> bool {
|
||||||
|
if commitments.len() > 16 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut serialized = vec![];
|
||||||
|
bp.consensus_encode(&mut serialized).unwrap();
|
||||||
|
let commitments: Vec<[u8; 32]> = commitments.iter().map(
|
||||||
|
|commitment| (commitment * Scalar::from(8 as u8).invert()).compress().to_bytes()
|
||||||
|
).collect();
|
||||||
|
unsafe {
|
||||||
|
c_verify_bp(serialized.len(), serialized.as_ptr(), commitments.len() as u8, commitments.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use ff::Field;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
|
@ -171,7 +169,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
||||||
let mut s = vec![];
|
let mut s = vec![];
|
||||||
s.resize(n, Scalar::zero());
|
s.resize(n, Scalar::zero());
|
||||||
while i != r {
|
while i != r {
|
||||||
s[i] = dalek_ff_group::Scalar::random(&mut *rng).0;
|
s[i] = random_scalar(&mut *rng);
|
||||||
let c_p = mu_P * c;
|
let c_p = mu_P * c;
|
||||||
let c_c = mu_C * c;
|
let c_c = mu_C * c;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ use curve25519_dalek::{
|
||||||
edwards::EdwardsPoint
|
edwards::EdwardsPoint
|
||||||
};
|
};
|
||||||
|
|
||||||
use ff::Field;
|
|
||||||
use group::Group;
|
use group::Group;
|
||||||
use dalek_ff_group as dfg;
|
use dalek_ff_group as dfg;
|
||||||
use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
|
use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
|
||||||
|
@ -17,6 +16,7 @@ use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
|
||||||
use monero::util::ringct::{Key, Clsag};
|
use monero::util::ringct::{Key, Clsag};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
random_scalar,
|
||||||
hash_to_point,
|
hash_to_point,
|
||||||
frost::{MultisigError, Ed25519, DLEqProof},
|
frost::{MultisigError, Ed25519, DLEqProof},
|
||||||
clsag::{Input, sign_core, verify}
|
clsag::{Input, sign_core, verify}
|
||||||
|
@ -154,7 +154,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||||
seed.extend(&self.context());
|
seed.extend(&self.context());
|
||||||
seed.extend(&self.b);
|
seed.extend(&self.b);
|
||||||
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
||||||
let mask = dfg::Scalar::random(&mut rng).0;
|
let mask = random_scalar(&mut rng);
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||||
|
|
|
@ -22,11 +22,18 @@ pub mod clsag;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
#[link(name = "wrapper")]
|
#[link(name = "wrapper")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub(crate) fn free(ptr: *const u8);
|
pub(crate) fn free(ptr: *const u8);
|
||||||
fn c_hash_to_point(point: *const u8);
|
fn c_hash_to_point(point: *const u8);
|
||||||
pub(crate) fn c_gen_bp(len: u8, a: *const u64, m: *const [u8; 32]) -> *const u8;
|
pub(crate) fn c_generate_bp(len: u8, amounts: *const u64, masks: *const [u8; 32]) -> *const u8;
|
||||||
|
pub(crate) fn c_verify_bp(
|
||||||
|
serialized_len: usize, serialized: *const u8,
|
||||||
|
commitments_len: u8, commitments: *const [u8; 32]
|
||||||
|
) -> bool;
|
||||||
pub(crate) fn c_verify_clsag(
|
pub(crate) fn c_verify_clsag(
|
||||||
serialized_len: usize, serialized: *const u8, I: *const u8,
|
serialized_len: usize, serialized: *const u8, I: *const u8,
|
||||||
ring_size: u8, ring: *const u8, msg: *const u8, pseudo_out: *const u8
|
ring_size: u8, ring: *const u8, msg: *const u8, pseudo_out: *const u8
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha12Rng;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use blake2::{Digest, Blake2b512};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
constants::ED25519_BASEPOINT_TABLE,
|
||||||
scalar::Scalar,
|
scalar::Scalar,
|
||||||
|
@ -18,7 +21,7 @@ use monero::{
|
||||||
},
|
},
|
||||||
util::{
|
util::{
|
||||||
key::PublicKey,
|
key::PublicKey,
|
||||||
ringct::{Key, CtKey, EcdhInfo, RctType, RctSigBase, RctSigPrunable, RctSig},
|
ringct::{Key, CtKey, EcdhInfo, Bulletproof, RctType, RctSigBase, RctSigPrunable, RctSig},
|
||||||
address::Address
|
address::Address
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -33,8 +36,15 @@ use crate::{
|
||||||
|
|
||||||
mod mixins;
|
mod mixins;
|
||||||
|
|
||||||
|
#[cfg(feature = "multisig")]
|
||||||
|
mod multisig;
|
||||||
|
#[cfg(feature = "multisig")]
|
||||||
|
pub use multisig::Multisig;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TransactionError {
|
pub enum TransactionError {
|
||||||
|
#[error("invalid preparation ({0})")]
|
||||||
|
InvalidPreparation(String),
|
||||||
#[error("no inputs")]
|
#[error("no inputs")]
|
||||||
NoInputs,
|
NoInputs,
|
||||||
#[error("too many outputs")]
|
#[error("too many outputs")]
|
||||||
|
@ -59,7 +69,7 @@ pub struct SpendableOutput {
|
||||||
pub commitment: Commitment
|
pub commitment: Commitment
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_tx(tx: &Transaction, view: Scalar, spend: EdwardsPoint) -> Vec<SpendableOutput> {
|
pub fn scan(tx: &Transaction, view: Scalar, spend: EdwardsPoint) -> Vec<SpendableOutput> {
|
||||||
let mut pubkeys = vec![];
|
let mut pubkeys = vec![];
|
||||||
if tx.tx_pubkey().is_some() {
|
if tx.tx_pubkey().is_some() {
|
||||||
pubkeys.push(tx.tx_pubkey().unwrap());
|
pubkeys.push(tx.tx_pubkey().unwrap());
|
||||||
|
@ -150,7 +160,11 @@ struct Output {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, output: (Address, u64), o: usize) -> Result<Output, TransactionError> {
|
pub fn new<R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
output: (Address, u64),
|
||||||
|
o: usize
|
||||||
|
) -> Result<Output, TransactionError> {
|
||||||
let r = random_scalar(rng);
|
let r = random_scalar(rng);
|
||||||
let shared_key = shared_key(
|
let shared_key = shared_key(
|
||||||
r,
|
r,
|
||||||
|
@ -171,6 +185,178 @@ impl Output {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Preparation<'a, R: RngCore + CryptoRng> {
|
||||||
|
Leader(&'a mut R),
|
||||||
|
Follower([u8; 32], Bulletproof)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_outputs<'a, R: RngCore + CryptoRng>(
|
||||||
|
prep: &mut Preparation<'a, R>,
|
||||||
|
inputs: &[SpendableOutput],
|
||||||
|
payments: &[(Address, u64)],
|
||||||
|
change: Address,
|
||||||
|
fee_per_byte: u64
|
||||||
|
) -> Result<(Vec<u8>, Scalar, Transaction), TransactionError> {
|
||||||
|
let fee = fee_per_byte * 2000; // TODO
|
||||||
|
|
||||||
|
// TODO TX MAX SIZE
|
||||||
|
|
||||||
|
// Make sure we have enough funds
|
||||||
|
let in_amount = inputs.iter().map(|input| input.commitment.amount).sum();
|
||||||
|
let out_amount = fee + payments.iter().map(|payment| payment.1).sum::<u64>();
|
||||||
|
if in_amount < out_amount {
|
||||||
|
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the change output
|
||||||
|
let mut payments = payments.to_vec();
|
||||||
|
payments.push((change, in_amount - out_amount));
|
||||||
|
|
||||||
|
// Grab the prep
|
||||||
|
let mut entropy = [0; 32];
|
||||||
|
let mut bp = None;
|
||||||
|
match prep {
|
||||||
|
Preparation::Leader(ref mut rng) => {
|
||||||
|
// The Leader generates the entropy for the one time keys and the bulletproof
|
||||||
|
rng.fill_bytes(&mut entropy);
|
||||||
|
},
|
||||||
|
Preparation::Follower(e, b) => {
|
||||||
|
entropy = e.clone();
|
||||||
|
bp = Some(b.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seed = b"StealthAddress_randomness".to_vec();
|
||||||
|
// Leader selected entropy to prevent de-anonymization via recalculation of randomness
|
||||||
|
seed.extend(&entropy);
|
||||||
|
// This output can only be spent once. Therefore, it forces all one time keys used here to be
|
||||||
|
// unique, even if the leader reuses entropy. While another transaction could use a different
|
||||||
|
// input ordering to swap which 0 is, that input set can't contain this input without being a
|
||||||
|
// double spend
|
||||||
|
seed.extend(&inputs[0].tx.0);
|
||||||
|
seed.extend(&inputs[0].o.to_le_bytes());
|
||||||
|
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
||||||
|
|
||||||
|
let mut outputs = Vec::with_capacity(payments.len());
|
||||||
|
let mut commitments = Vec::with_capacity(payments.len());
|
||||||
|
for o in 0 .. payments.len() {
|
||||||
|
outputs.push(Output::new(&mut rng, payments[o], o)?);
|
||||||
|
commitments.push(Commitment::new(outputs[o].mask, payments[o].1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if bp.is_none() {
|
||||||
|
// Generate the bulletproof if leader
|
||||||
|
bp = Some(bulletproofs::generate(&commitments)?);
|
||||||
|
} else {
|
||||||
|
// Verify the bulletproof if follower
|
||||||
|
if !bulletproofs::verify(
|
||||||
|
bp.as_ref().unwrap(),
|
||||||
|
&commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()
|
||||||
|
) {
|
||||||
|
Err(TransactionError::InvalidPreparation("invalid bulletproof".to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the TX extra
|
||||||
|
let mut extra = ExtraField(vec![
|
||||||
|
SubField::TxPublicKey(PublicKey { point: outputs[0].R.compress() })
|
||||||
|
]);
|
||||||
|
extra.0.push(SubField::AdditionalPublickKey(
|
||||||
|
outputs[1 .. outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
|
||||||
|
));
|
||||||
|
|
||||||
|
// Format it for monero-rs
|
||||||
|
let mut mrs_outputs = Vec::with_capacity(outputs.len());
|
||||||
|
let mut out_pk = Vec::with_capacity(outputs.len());
|
||||||
|
let mut ecdh_info = Vec::with_capacity(outputs.len());
|
||||||
|
for o in 0 .. outputs.len() {
|
||||||
|
mrs_outputs.push(TxOut {
|
||||||
|
amount: VarInt(0),
|
||||||
|
target: TxOutTarget::ToKey { key: PublicKey { point: outputs[o].dest.compress() } }
|
||||||
|
});
|
||||||
|
out_pk.push(CtKey {
|
||||||
|
mask: Key { key: commitments[o].calculate().compress().to_bytes() }
|
||||||
|
});
|
||||||
|
ecdh_info.push(EcdhInfo::Bulletproof { amount: outputs[o].amount });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
match prep {
|
||||||
|
// Encode the prep
|
||||||
|
Preparation::Leader(..) => {
|
||||||
|
let mut prep = entropy.to_vec();
|
||||||
|
bp.as_ref().unwrap().consensus_encode(&mut prep).expect("Couldn't encode bulletproof");
|
||||||
|
prep
|
||||||
|
},
|
||||||
|
Preparation::Follower(..) => {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outputs.iter().map(|output| output.mask).sum(),
|
||||||
|
Transaction {
|
||||||
|
prefix: TransactionPrefix {
|
||||||
|
version: VarInt(2),
|
||||||
|
unlock_time: VarInt(0),
|
||||||
|
inputs: vec![],
|
||||||
|
outputs: mrs_outputs,
|
||||||
|
extra
|
||||||
|
},
|
||||||
|
signatures: vec![],
|
||||||
|
rct_signatures: RctSig {
|
||||||
|
sig: Some(RctSigBase {
|
||||||
|
rct_type: RctType::Clsag,
|
||||||
|
txn_fee: VarInt(fee),
|
||||||
|
pseudo_outs: vec![],
|
||||||
|
ecdh_info,
|
||||||
|
out_pk
|
||||||
|
}),
|
||||||
|
p: Some(RctSigPrunable {
|
||||||
|
range_sigs: vec![],
|
||||||
|
bulletproofs: vec![bp.unwrap()],
|
||||||
|
MGs: vec![],
|
||||||
|
Clsags: vec![],
|
||||||
|
pseudo_outs: vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prepare_inputs(
|
||||||
|
rpc: &Rpc,
|
||||||
|
spend: &Scalar,
|
||||||
|
inputs: &[SpendableOutput],
|
||||||
|
tx: &mut Transaction
|
||||||
|
) -> Result<Vec<(Scalar, clsag::Input)>, TransactionError> {
|
||||||
|
let mut mixins = Vec::with_capacity(inputs.len());
|
||||||
|
let mut signable = Vec::with_capacity(inputs.len());
|
||||||
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
|
// Select mixins
|
||||||
|
let (m, mix) = mixins::select(
|
||||||
|
rpc.get_o_indexes(input.tx).await.map_err(|e| TransactionError::RpcError(e))?[input.o]
|
||||||
|
);
|
||||||
|
mixins.push(mix);
|
||||||
|
|
||||||
|
signable.push((
|
||||||
|
spend + input.key_offset,
|
||||||
|
clsag::Input::new(
|
||||||
|
key_image::generate(&(spend + input.key_offset)),
|
||||||
|
rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?,
|
||||||
|
m,
|
||||||
|
input.commitment
|
||||||
|
).map_err(|e| TransactionError::ClsagError(e))?
|
||||||
|
));
|
||||||
|
|
||||||
|
tx.prefix.inputs.push(TxIn::ToKey {
|
||||||
|
amount: VarInt(0),
|
||||||
|
key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(),
|
||||||
|
k_image: KeyImage { image: Hash(signable[i].1.image.compress().to_bytes()) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(signable)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send<R: RngCore + CryptoRng>(
|
pub async fn send<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
|
@ -180,112 +366,23 @@ pub async fn send<R: RngCore + CryptoRng>(
|
||||||
change: Address,
|
change: Address,
|
||||||
fee_per_byte: u64
|
fee_per_byte: u64
|
||||||
) -> Result<Hash, TransactionError> {
|
) -> Result<Hash, TransactionError> {
|
||||||
let fee = fee_per_byte * 2000; // TODO
|
let (_, mask_sum, mut tx) = prepare_outputs(
|
||||||
|
&mut Preparation::Leader(rng),
|
||||||
// TODO TX MAX SIZE
|
inputs,
|
||||||
|
payments,
|
||||||
let mut in_amount = 0;
|
change,
|
||||||
for input in inputs {
|
fee_per_byte
|
||||||
in_amount += input.commitment.amount;
|
|
||||||
}
|
|
||||||
let mut out_amount = fee;
|
|
||||||
for payment in payments {
|
|
||||||
out_amount += payment.1
|
|
||||||
}
|
|
||||||
if in_amount < out_amount {
|
|
||||||
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle outputs
|
|
||||||
let mut payments = payments.to_vec();
|
|
||||||
payments.push((change, in_amount - out_amount));
|
|
||||||
let mut outputs = Vec::with_capacity(payments.len());
|
|
||||||
for o in 0 .. payments.len() {
|
|
||||||
outputs.push(Output::new(&mut *rng, payments[o], o)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let bp = bulletproofs::generate(
|
|
||||||
outputs.iter().enumerate().map(|(o, output)| Commitment::new(output.mask, payments[o].1)).collect()
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut extra = ExtraField(vec![
|
let signable = prepare_inputs(rpc, spend, inputs, &mut tx).await?;
|
||||||
SubField::TxPublicKey(PublicKey { point: outputs[0].R.compress() })
|
|
||||||
]);
|
|
||||||
extra.0.push(SubField::AdditionalPublickKey(
|
|
||||||
outputs[1 .. outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
|
|
||||||
));
|
|
||||||
|
|
||||||
// Handle inputs
|
|
||||||
let mut mixins = Vec::with_capacity(inputs.len());
|
|
||||||
let mut signable = Vec::with_capacity(inputs.len());
|
|
||||||
for (i, input) in inputs.iter().enumerate() {
|
|
||||||
let (m, mix) = mixins::select(
|
|
||||||
rpc.get_o_indexes(input.tx).await.map_err(|e| TransactionError::RpcError(e))?[input.o]
|
|
||||||
);
|
|
||||||
mixins.push(mix);
|
|
||||||
signable.push((
|
|
||||||
spend + input.key_offset,
|
|
||||||
clsag::Input::new(
|
|
||||||
key_image::generate(&(spend + input.key_offset)),
|
|
||||||
rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?,
|
|
||||||
m,
|
|
||||||
input.commitment
|
|
||||||
).map_err(|e| TransactionError::ClsagError(e))?
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = TransactionPrefix {
|
|
||||||
version: VarInt(2),
|
|
||||||
unlock_time: VarInt(0),
|
|
||||||
inputs: signable.iter().enumerate().map(|(i, input)| TxIn::ToKey {
|
|
||||||
amount: VarInt(0),
|
|
||||||
key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(),
|
|
||||||
k_image: KeyImage {
|
|
||||||
image: Hash(input.1.image.compress().to_bytes())
|
|
||||||
}
|
|
||||||
}).collect(),
|
|
||||||
outputs: outputs.iter().map(|output| TxOut {
|
|
||||||
amount: VarInt(0),
|
|
||||||
target: TxOutTarget::ToKey { key: PublicKey { point: output.dest.compress() } }
|
|
||||||
}).collect(),
|
|
||||||
extra
|
|
||||||
};
|
|
||||||
|
|
||||||
let base = RctSigBase {
|
|
||||||
rct_type: RctType::Clsag,
|
|
||||||
txn_fee: VarInt(fee),
|
|
||||||
pseudo_outs: vec![],
|
|
||||||
ecdh_info: outputs.iter().map(|output| EcdhInfo::Bulletproof { amount: output.amount }).collect(),
|
|
||||||
out_pk: outputs.iter().enumerate().map(|(o, output)| CtKey {
|
|
||||||
mask: Key {
|
|
||||||
key: Commitment::new(output.mask, payments[o].1).calculate().compress().to_bytes()
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut prunable = RctSigPrunable {
|
|
||||||
range_sigs: vec![],
|
|
||||||
bulletproofs: vec![bp],
|
|
||||||
MGs: vec![],
|
|
||||||
Clsags: vec![],
|
|
||||||
pseudo_outs: vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx = Transaction {
|
|
||||||
prefix,
|
|
||||||
signatures: vec![],
|
|
||||||
rct_signatures: RctSig {
|
|
||||||
sig: Some(base),
|
|
||||||
p: Some(prunable.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let clsags = clsag::sign(
|
let clsags = clsag::sign(
|
||||||
rng,
|
rng,
|
||||||
tx.signature_hash().expect("Couldn't get the signature hash").0,
|
tx.signature_hash().expect("Couldn't get the signature hash").0,
|
||||||
&signable,
|
&signable,
|
||||||
outputs.iter().map(|output| output.mask).sum()
|
mask_sum
|
||||||
).ok_or(TransactionError::NoInputs)?;
|
).ok_or(TransactionError::NoInputs)?;
|
||||||
|
let mut prunable = tx.rct_signatures.p.unwrap();
|
||||||
prunable.Clsags = clsags.iter().map(|clsag| clsag.0.clone()).collect();
|
prunable.Clsags = clsags.iter().map(|clsag| clsag.0.clone()).collect();
|
||||||
prunable.pseudo_outs = clsags.iter().map(|clsag| Key { key: clsag.1.compress().to_bytes() }).collect();
|
prunable.pseudo_outs = clsags.iter().map(|clsag| Key { key: clsag.1.compress().to_bytes() }).collect();
|
||||||
tx.rct_signatures.p = Some(prunable);
|
tx.rct_signatures.p = Some(prunable);
|
||||||
|
|
Loading…
Reference in a new issue