mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Override Monero's random function with a Rust-seedable random
Closes https://github.com/serai-dex/serai/issues/2. Also finishes the implementation of https://github.com/monero-project/research-lab/issues/103.
This commit is contained in:
@@ -65,11 +65,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=c/wrapper.c");
|
println!("cargo:rerun-if-changed=c/wrapper.cpp");
|
||||||
if !Command::new("g++").args(&[
|
if !Command::new("g++").args(&[
|
||||||
"-O3", "-Wall", "-shared", "-std=c++14", "-fPIC",
|
"-O3", "-Wall", "-shared", "-std=c++14", "-fPIC",
|
||||||
"-Imonero/contrib/epee/include", "-Imonero/src",
|
"-Imonero/contrib/epee/include", "-Imonero/src",
|
||||||
"wrapper.c", "-o", &format!(
|
"wrapper.cpp", "-o", &format!(
|
||||||
"{}/{}wrapper.{}",
|
"{}/{}wrapper.{}",
|
||||||
out_dir,
|
out_dir,
|
||||||
&env::consts::DLL_PREFIX,
|
&env::consts::DLL_PREFIX,
|
||||||
|
|||||||
@@ -1,9 +1,37 @@
|
|||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "device/device_default.hpp"
|
#include "device/device_default.hpp"
|
||||||
|
|
||||||
#include "ringct/bulletproofs.h"
|
#include "ringct/bulletproofs.h"
|
||||||
#include "ringct/rctSigs.h"
|
#include "ringct/rctSigs.h"
|
||||||
|
|
||||||
|
std::mutex rng_mutex;
|
||||||
|
char rng_entropy[64];
|
||||||
|
void rng(uint8_t* seed) {
|
||||||
|
memcpy(rng_entropy, seed, 32);
|
||||||
|
memset(&rng_entropy[32], 0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
void generate_random_bytes_not_thread_safe(size_t n, uint8_t* value) {
|
||||||
|
size_t written = 0;
|
||||||
|
while (written != n) {
|
||||||
|
uint8_t hash[32];
|
||||||
|
crypto::cn_fast_hash(rng_entropy, 64, (char*) hash);
|
||||||
|
// Step the RNG by setting the latter half to the most recent result
|
||||||
|
// Does not leak the RNG, even if the values are leaked (which they are expected to be) due to
|
||||||
|
// the first half remaining constant and undisclosed
|
||||||
|
memcpy(&rng_entropy[32], hash, 32);
|
||||||
|
|
||||||
|
size_t next = n - written;
|
||||||
|
if (next > 32) {
|
||||||
|
next = 32;
|
||||||
|
}
|
||||||
|
memcpy(&value[written], hash, next);
|
||||||
|
written += next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void c_hash_to_point(uint8_t* point) {
|
void c_hash_to_point(uint8_t* point) {
|
||||||
rct::key key_point;
|
rct::key key_point;
|
||||||
ge_p3 e_p3;
|
ge_p3 e_p3;
|
||||||
@@ -12,7 +40,10 @@ extern "C" {
|
|||||||
ge_p3_tobytes(point, &e_p3);
|
ge_p3_tobytes(point, &e_p3);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* c_generate_bp(uint8_t len, uint64_t* a, uint8_t* m) {
|
uint8_t* c_generate_bp(uint8_t* seed, uint8_t len, uint64_t* a, uint8_t* m) {
|
||||||
|
std::lock_guard<std::mutex> guard(rng_mutex);
|
||||||
|
rng(seed);
|
||||||
|
|
||||||
rct::keyV masks;
|
rct::keyV masks;
|
||||||
std::vector<uint64_t> amounts;
|
std::vector<uint64_t> amounts;
|
||||||
masks.resize(len);
|
masks.resize(len);
|
||||||
@@ -21,6 +52,7 @@ extern "C" {
|
|||||||
memcpy(masks[i].bytes, m + (i * 32), 32);
|
memcpy(masks[i].bytes, m + (i * 32), 32);
|
||||||
amounts[i] = a[i];
|
amounts[i] = a[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
rct::Bulletproof bp = rct::bulletproof_PROVE(amounts, masks);
|
rct::Bulletproof bp = rct::bulletproof_PROVE(amounts, masks);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@@ -33,7 +65,14 @@ extern "C" {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool c_verify_bp(uint s_len, uint8_t* s, uint8_t c_len, uint8_t* c) {
|
bool c_verify_bp(uint8_t* seed, uint s_len, uint8_t* s, uint8_t c_len, uint8_t* c) {
|
||||||
|
// BPs are batch verified which use RNG based challenges to ensure individual integrity
|
||||||
|
// That's why this must also have control over RNG, to prevent interrupting multisig signing
|
||||||
|
// while not using known seeds. Considering this doesn't actually define a batch,
|
||||||
|
// and it's only verifying a single BP, it'd probably be fine, but...
|
||||||
|
std::lock_guard<std::mutex> guard(rng_mutex);
|
||||||
|
rng(seed);
|
||||||
|
|
||||||
rct::Bulletproof bp;
|
rct::Bulletproof bp;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
std::string str;
|
std::string str;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
||||||
|
|
||||||
use crate::{Commitment, wallet::TransactionError, serialize::*};
|
use crate::{Commitment, wallet::TransactionError, serialize::*};
|
||||||
@@ -20,47 +22,56 @@ pub struct Bulletproofs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bulletproofs {
|
impl Bulletproofs {
|
||||||
pub fn new(outputs: &[Commitment]) -> Result<Bulletproofs, TransactionError> {
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, outputs: &[Commitment]) -> Result<Bulletproofs, TransactionError> {
|
||||||
if outputs.len() > 16 {
|
if outputs.len() > 16 {
|
||||||
return Err(TransactionError::TooManyOutputs)?;
|
return Err(TransactionError::TooManyOutputs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let masks: Vec<[u8; 32]> = outputs.iter().map(|commitment| commitment.mask.to_bytes()).collect();
|
let mut seed = [0; 32];
|
||||||
let amounts: Vec<u64> = outputs.iter().map(|commitment| commitment.amount).collect();
|
rng.fill_bytes(&mut seed);
|
||||||
let res: Bulletproofs;
|
|
||||||
|
let masks = outputs.iter().map(|commitment| commitment.mask.to_bytes()).collect::<Vec<_>>();
|
||||||
|
let amounts = outputs.iter().map(|commitment| commitment.amount).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let res;
|
||||||
unsafe {
|
unsafe {
|
||||||
#[link(name = "wrapper")]
|
#[link(name = "wrapper")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn free(ptr: *const u8);
|
fn free(ptr: *const u8);
|
||||||
fn c_generate_bp(len: u8, amounts: *const u64, masks: *const [u8; 32]) -> *const u8;
|
fn c_generate_bp(seed: *const u8, len: u8, amounts: *const u64, masks: *const [u8; 32]) -> *const u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ptr = c_generate_bp(outputs.len() as u8, amounts.as_ptr(), masks.as_ptr());
|
let ptr = c_generate_bp(seed.as_ptr(), 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 = Bulletproofs::deserialize(
|
res = Bulletproofs::deserialize(
|
||||||
// Wrap in a cursor to provide a mutable Reader
|
// Wrap in a cursor to provide a mutable Reader
|
||||||
&mut std::io::Cursor::new(std::slice::from_raw_parts(ptr.add(2), len))
|
&mut std::io::Cursor::new(std::slice::from_raw_parts(ptr.add(2), len))
|
||||||
).expect("Couldn't deserialize Bulletproofs from Monero");
|
).expect("Couldn't deserialize Bulletproofs from Monero");
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(res.into())
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&self, commitments: &[EdwardsPoint]) -> bool {
|
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
|
||||||
if commitments.len() > 16 {
|
if commitments.len() > 16 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut seed = [0; 32];
|
||||||
|
rng.fill_bytes(&mut seed);
|
||||||
|
|
||||||
let mut serialized = Vec::with_capacity((9 + (2 * self.L.len())) * 32);
|
let mut serialized = Vec::with_capacity((9 + (2 * self.L.len())) * 32);
|
||||||
self.serialize(&mut serialized).unwrap();
|
self.serialize(&mut serialized).unwrap();
|
||||||
let commitments: Vec<[u8; 32]> = commitments.iter().map(
|
let commitments: Vec<[u8; 32]> = commitments.iter().map(
|
||||||
|commitment| (commitment * Scalar::from(8 as u8).invert()).compress().to_bytes()
|
|commitment| (commitment * Scalar::from(8 as u8).invert()).compress().to_bytes()
|
||||||
).collect();
|
).collect();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
#[link(name = "wrapper")]
|
#[link(name = "wrapper")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn c_verify_bp(
|
fn c_verify_bp(
|
||||||
|
seed: *const u8,
|
||||||
serialized_len: usize,
|
serialized_len: usize,
|
||||||
serialized: *const u8,
|
serialized: *const u8,
|
||||||
commitments_len: u8,
|
commitments_len: u8,
|
||||||
@@ -68,7 +79,7 @@ impl Bulletproofs {
|
|||||||
) -> bool;
|
) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
c_verify_bp(serialized.len(), serialized.as_ptr(), commitments.len() as u8, commitments.as_ptr())
|
c_verify_bp(seed.as_ptr(), serialized.len(), serialized.as_ptr(), commitments.len() as u8, commitments.as_ptr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ pub(crate) use decoys::Decoys;
|
|||||||
mod send;
|
mod send;
|
||||||
pub use send::{TransactionError, SignableTransaction};
|
pub use send::{TransactionError, SignableTransaction};
|
||||||
|
|
||||||
|
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering {
|
||||||
|
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/monero-project/research-lab/issues/103
|
// https://github.com/monero-project/research-lab/issues/103
|
||||||
pub(crate) fn uniqueness(inputs: &[Input]) -> [u8; 32] {
|
pub(crate) fn uniqueness(inputs: &[Input]) -> [u8; 32] {
|
||||||
let mut u = b"domain_separator".to_vec();
|
let mut u = b"domain_separator".to_vec();
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ pub struct SpendableOutput {
|
|||||||
pub commitment: Commitment
|
pub commitment: Commitment
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Enable disabling one of the shared key derivations and solely using one
|
|
||||||
// Change outputs currently always use unique derivations, so that must also be corrected
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn scan(
|
pub fn scan(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use crate::{
|
|||||||
generate_key_image, bulletproofs::Bulletproofs, clsag::{ClsagError, ClsagInput, Clsag},
|
generate_key_image, bulletproofs::Bulletproofs, clsag::{ClsagError, ClsagInput, Clsag},
|
||||||
rpc::{Rpc, RpcError},
|
rpc::{Rpc, RpcError},
|
||||||
transaction::*,
|
transaction::*,
|
||||||
wallet::{uniqueness, shared_key, commitment_mask, amount_encryption, SpendableOutput, Decoys}
|
wallet::{SpendableOutput, Decoys, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use crate::frost::MultisigError;
|
use crate::frost::MultisigError;
|
||||||
@@ -185,7 +185,7 @@ impl SignableTransaction {
|
|||||||
fn prepare_outputs<R: RngCore + CryptoRng>(
|
fn prepare_outputs<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
uniqueness: Option<[u8; 32]>
|
uniqueness: [u8; 32]
|
||||||
) -> Result<(Vec<Commitment>, Scalar), TransactionError> {
|
) -> Result<(Vec<Commitment>, Scalar), TransactionError> {
|
||||||
self.fee = self.fee_per_byte * 2000; // TODO
|
self.fee = self.fee_per_byte * 2000; // TODO
|
||||||
|
|
||||||
@@ -203,20 +203,7 @@ impl SignableTransaction {
|
|||||||
for payment in &self.payments {
|
for payment in &self.payments {
|
||||||
temp_outputs.push((None, (payment.0, payment.1)));
|
temp_outputs.push((None, (payment.0, payment.1)));
|
||||||
}
|
}
|
||||||
// Ideally, the change output would always have uniqueness, as we control this wallet software
|
temp_outputs.push((Some(uniqueness), (self.change, in_amount - out_amount)));
|
||||||
// Unfortunately, if this is used with multisig, doing so would add an extra round due to the
|
|
||||||
// fact Bulletproofs use a leader protocol reliant on this shared key before the first round of
|
|
||||||
// communication. Making the change output unique would require Bulletproofs not be a leader
|
|
||||||
// protocol, using a seeded random
|
|
||||||
// There is a vector where the multisig participants leak the output key they're about to send
|
|
||||||
// to, and someone could use that key, forcing some funds to be burnt accordingly if they win
|
|
||||||
// the race. Any multisig wallet, with this current setup, must only keep change keys in context
|
|
||||||
// accordingly, preferably as soon as they are proposed, even before they appear as confirmed
|
|
||||||
// Using another source of uniqueness would also be possible, yet it'd make scanning a tri-key
|
|
||||||
// system (currently dual for the simpler API, yet would be dual even with a more complex API
|
|
||||||
// under this decision)
|
|
||||||
// TODO after https://github.com/serai-dex/serai/issues/2
|
|
||||||
temp_outputs.push((uniqueness, (self.change, in_amount - out_amount)));
|
|
||||||
|
|
||||||
// Shuffle the outputs
|
// Shuffle the outputs
|
||||||
temp_outputs.shuffle(rng);
|
temp_outputs.shuffle(rng);
|
||||||
@@ -293,22 +280,20 @@ impl SignableTransaction {
|
|||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
images.push(generate_key_image(&(spend + input.key_offset)));
|
images.push(generate_key_image(&(spend + input.key_offset)));
|
||||||
}
|
}
|
||||||
images.sort_by(|x, y| x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse());
|
images.sort_by(key_image_sort);
|
||||||
|
|
||||||
let (commitments, mask_sum) = self.prepare_outputs(
|
let (commitments, mask_sum) = self.prepare_outputs(
|
||||||
rng,
|
rng,
|
||||||
Some(
|
uniqueness(
|
||||||
uniqueness(
|
&images.iter().map(|image| Input::ToKey {
|
||||||
&images.iter().map(|image| Input::ToKey {
|
amount: 0,
|
||||||
amount: 0,
|
key_offsets: vec![],
|
||||||
key_offsets: vec![],
|
key_image: *image
|
||||||
key_image: *image
|
}).collect::<Vec<_>>()
|
||||||
}).collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut tx = self.prepare_transaction(&commitments, Bulletproofs::new(&commitments)?);
|
let mut tx = self.prepare_transaction(&commitments, Bulletproofs::new(rng, &commitments)?);
|
||||||
|
|
||||||
let signable = prepare_inputs(rng, rpc, &self.inputs, spend, &mut tx).await?;
|
let signable = prepare_inputs(rng, rpc, &self.inputs, spend, &mut tx).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -13,16 +13,18 @@ use crate::{
|
|||||||
random_scalar, bulletproofs::Bulletproofs, clsag::{ClsagInput, ClsagDetails, ClsagMultisig},
|
random_scalar, bulletproofs::Bulletproofs, clsag::{ClsagInput, ClsagDetails, ClsagMultisig},
|
||||||
rpc::Rpc,
|
rpc::Rpc,
|
||||||
transaction::{Input, RctPrunable, Transaction},
|
transaction::{Input, RctPrunable, Transaction},
|
||||||
wallet::{TransactionError, SignableTransaction, Decoys}
|
wallet::{TransactionError, SignableTransaction, Decoys, key_image_sort, uniqueness}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TransactionMachine {
|
pub struct TransactionMachine {
|
||||||
leader: bool,
|
|
||||||
signable: SignableTransaction,
|
signable: SignableTransaction,
|
||||||
|
i: usize,
|
||||||
transcript: Transcript,
|
transcript: Transcript,
|
||||||
|
|
||||||
decoys: Vec<Decoys>,
|
decoys: Vec<Decoys>,
|
||||||
|
|
||||||
|
our_preprocess: Vec<u8>,
|
||||||
|
|
||||||
images: Vec<EdwardsPoint>,
|
images: Vec<EdwardsPoint>,
|
||||||
output_masks: Option<Scalar>,
|
output_masks: Option<Scalar>,
|
||||||
inputs: Vec<Rc<RefCell<Option<ClsagDetails>>>>,
|
inputs: Vec<Rc<RefCell<Option<ClsagDetails>>>>,
|
||||||
@@ -55,6 +57,10 @@ impl SignableTransaction {
|
|||||||
// depending on how these transactions are coordinated
|
// depending on how these transactions are coordinated
|
||||||
|
|
||||||
let mut transcript = Transcript::new(label);
|
let mut transcript = Transcript::new(label);
|
||||||
|
// Include the height we're using for our data
|
||||||
|
// The data itself will be included, making this unnecessary, yet a lot of this is technically
|
||||||
|
// unnecessary. Anything which further increases security at almost no cost should be followed
|
||||||
|
transcript.append_message(b"height", &u64::try_from(height).unwrap().to_le_bytes());
|
||||||
// Also include the spend_key as below only the key offset is included, so this confirms the sum product
|
// Also include the spend_key as below only the key offset is included, so this confirms the sum product
|
||||||
// Useful as confirming the sum product confirms the key image, further guaranteeing the one time
|
// Useful as confirming the sum product confirms the key image, further guaranteeing the one time
|
||||||
// properties noted below
|
// properties noted below
|
||||||
@@ -76,10 +82,12 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
// Select decoys
|
// Select decoys
|
||||||
// Ideally, this would be done post entropy, instead of now, yet doing so would require sign
|
// Ideally, this would be done post entropy, instead of now, yet doing so would require sign
|
||||||
// to be async which isn't feasible. This should be suitably competent though
|
// to be async which isn't preferable. This should be suitably competent though
|
||||||
// While this inability means we can immediately create the input, moving it out of the
|
// While this inability means we can immediately create the input, moving it out of the
|
||||||
// Rc RefCell, keeping it within an Rc RefCell keeps our options flexible
|
// Rc RefCell, keeping it within an Rc RefCell keeps our options flexible
|
||||||
let decoys = Decoys::select(
|
let decoys = Decoys::select(
|
||||||
|
// Using a seeded RNG with a specific height, committed to above, should make these decoys
|
||||||
|
// committed to. They'll also be committed to later via the TX message as a whole
|
||||||
&mut ChaCha12Rng::from_seed(transcript.rng_seed(b"decoys", None)),
|
&mut ChaCha12Rng::from_seed(transcript.rng_seed(b"decoys", None)),
|
||||||
rpc,
|
rpc,
|
||||||
height,
|
height,
|
||||||
@@ -100,15 +108,17 @@ impl SignableTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify these outputs by a dummy prep
|
// Verify these outputs by a dummy prep
|
||||||
self.prepare_outputs(rng, None)?;
|
self.prepare_outputs(rng, [0; 32])?;
|
||||||
|
|
||||||
Ok(TransactionMachine {
|
Ok(TransactionMachine {
|
||||||
leader: keys.params().i() == included[0],
|
|
||||||
signable: self,
|
signable: self,
|
||||||
|
i: keys.params().i(),
|
||||||
transcript,
|
transcript,
|
||||||
|
|
||||||
decoys,
|
decoys,
|
||||||
|
|
||||||
|
our_preprocess: vec![],
|
||||||
|
|
||||||
images,
|
images,
|
||||||
output_masks: None,
|
output_masks: None,
|
||||||
inputs,
|
inputs,
|
||||||
@@ -135,26 +145,19 @@ impl StateMachine for TransactionMachine {
|
|||||||
for (i, clsag) in self.clsags.iter_mut().enumerate() {
|
for (i, clsag) in self.clsags.iter_mut().enumerate() {
|
||||||
let preprocess = clsag.preprocess(rng)?;
|
let preprocess = clsag.preprocess(rng)?;
|
||||||
// First 64 bytes are FROST's commitments
|
// First 64 bytes are FROST's commitments
|
||||||
self.images[i] += CompressedEdwardsY(preprocess[64 .. 96].try_into().unwrap()).decompress().unwrap();
|
self.images[i] = CompressedEdwardsY(preprocess[64 .. 96].try_into().unwrap()).decompress().unwrap();
|
||||||
serialized.extend(&preprocess);
|
serialized.extend(&preprocess);
|
||||||
}
|
}
|
||||||
|
self.our_preprocess = serialized.clone();
|
||||||
|
|
||||||
if self.leader {
|
// We could add further entropy here, and previous versions of this library did so
|
||||||
let mut entropy = [0; 32];
|
// As of right now, the multisig's key, the inputs being spent, and the FROST data itself
|
||||||
rng.fill_bytes(&mut entropy);
|
// will be used for RNG seeds. In order to recreate these RNG seeds, breaking privacy,
|
||||||
serialized.extend(&entropy);
|
// counterparties must have knowledge of the multisig, either the view key or access to the
|
||||||
|
// coordination layer, and then access to the actual FROST signing process
|
||||||
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys", Some(entropy)));
|
// If the commitments are sent in plain text, then entropy here also would be, making it not
|
||||||
// Safe to unwrap thanks to the dummy prepare
|
// increase privacy. If they're not sent in plain text, or are otherwise inaccessible, they
|
||||||
let (commitments, output_masks) = self.signable.prepare_outputs(&mut rng, None).unwrap();
|
// already offer sufficient entropy. That's why further entropy is not included
|
||||||
self.output_masks = Some(output_masks);
|
|
||||||
|
|
||||||
let bp = Bulletproofs::new(&commitments).unwrap();
|
|
||||||
bp.serialize(&mut serialized).unwrap();
|
|
||||||
|
|
||||||
let tx = self.signable.prepare_transaction(&commitments, bp);
|
|
||||||
self.tx = Some(tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serialized)
|
Ok(serialized)
|
||||||
}
|
}
|
||||||
@@ -162,52 +165,35 @@ impl StateMachine for TransactionMachine {
|
|||||||
fn sign(
|
fn sign(
|
||||||
&mut self,
|
&mut self,
|
||||||
commitments: &[Option<Vec<u8>>],
|
commitments: &[Option<Vec<u8>>],
|
||||||
|
// Drop FROST's 'msg' since we calculate the actual message in this function
|
||||||
_: &[u8]
|
_: &[u8]
|
||||||
) -> Result<Vec<u8>, FrostError> {
|
) -> Result<Vec<u8>, FrostError> {
|
||||||
if self.state() != State::Preprocessed {
|
if self.state() != State::Preprocessed {
|
||||||
Err(FrostError::InvalidSignTransition(State::Preprocessed, self.state()))?;
|
Err(FrostError::InvalidSignTransition(State::Preprocessed, self.state()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FROST commitments, image, commitments, and their proofs
|
// Add all commitments to the transcript for their entropy
|
||||||
let clsag_len = 64 + ClsagMultisig::serialized_len();
|
// While each CLSAG will do this as they need to for security, they have their own transcripts
|
||||||
let clsag_lens = clsag_len * self.clsags.len();
|
// cloned from this TX's initial premise's transcript. For our TX transcript to have the CLSAG
|
||||||
|
// data for entropy, it'll have to be added ourselves
|
||||||
// Split out the prep and update the TX
|
for c in 0 .. commitments.len() {
|
||||||
let mut tx;
|
self.transcript.append_message(b"participant", &u16::try_from(c).unwrap().to_le_bytes());
|
||||||
if self.leader {
|
if c == self.i {
|
||||||
tx = self.tx.take().unwrap();
|
self.transcript.append_message(b"preprocess", &self.our_preprocess);
|
||||||
} else {
|
} else if let Some(commitments) = commitments[c].as_ref() {
|
||||||
let (l, prep) = commitments.iter().enumerate().filter(|(_, prep)| prep.is_some()).next()
|
self.transcript.append_message(b"preprocess", commitments);
|
||||||
.ok_or(FrostError::InternalError("no participants".to_string()))?;
|
|
||||||
let prep = prep.as_ref().unwrap();
|
|
||||||
|
|
||||||
// Not invalid outputs due to doing a dummy prep as leader
|
|
||||||
let (commitments, output_masks) = self.signable.prepare_outputs(
|
|
||||||
&mut ChaCha12Rng::from_seed(
|
|
||||||
self.transcript.rng_seed(
|
|
||||||
b"tx_keys",
|
|
||||||
Some(prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
None
|
|
||||||
).map_err(|_| FrostError::InvalidShare(l))?;
|
|
||||||
self.output_masks.replace(output_masks);
|
|
||||||
|
|
||||||
// Verify the provided bulletproofs if not leader
|
|
||||||
let bp = Bulletproofs::deserialize(
|
|
||||||
&mut std::io::Cursor::new(&prep[(clsag_lens + 32) .. prep.len()])
|
|
||||||
).map_err(|_| FrostError::InvalidShare(l))?;
|
|
||||||
if !bp.verify(&commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()) {
|
|
||||||
Err(FrostError::InvalidShare(l))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = self.signable.prepare_transaction(&commitments, bp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FROST commitments, image, H commitments, and their proofs
|
||||||
|
let clsag_len = 64 + ClsagMultisig::serialized_len();
|
||||||
|
|
||||||
for c in 0 .. self.clsags.len() {
|
for c in 0 .. self.clsags.len() {
|
||||||
// Calculate the key images in order to update the TX
|
// Calculate the key images
|
||||||
// Multisig will parse/calculate/validate this as needed, yet doing so here as well provides
|
// Multisig will parse/calculate/validate this as needed, yet doing so here as well provides
|
||||||
// the easiest API overall
|
// the easiest API overall, as this is where the TX is (which needs the key images in its
|
||||||
|
// message), along with where the outputs are determined (where our change output needs these
|
||||||
|
// to be unique)
|
||||||
for (l, serialized) in commitments.iter().enumerate().filter(|(_, s)| s.is_some()) {
|
for (l, serialized) in commitments.iter().enumerate().filter(|(_, s)| s.is_some()) {
|
||||||
self.images[c] += CompressedEdwardsY(
|
self.images[c] += CompressedEdwardsY(
|
||||||
serialized.as_ref().unwrap()[((c * clsag_len) + 64) .. ((c * clsag_len) + 96)]
|
serialized.as_ref().unwrap()[((c * clsag_len) + 64) .. ((c * clsag_len) + 96)]
|
||||||
@@ -216,6 +202,34 @@ impl StateMachine for TransactionMachine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the actual transaction
|
||||||
|
let mut tx = {
|
||||||
|
// Calculate uniqueness
|
||||||
|
let mut images = self.images.clone();
|
||||||
|
images.sort_by(key_image_sort);
|
||||||
|
|
||||||
|
// Not invalid outputs due to already doing a dummy prep
|
||||||
|
let (commitments, output_masks) = self.signable.prepare_outputs(
|
||||||
|
&mut ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys", None)),
|
||||||
|
uniqueness(
|
||||||
|
&images.iter().map(|image| Input::ToKey {
|
||||||
|
amount: 0,
|
||||||
|
key_offsets: vec![],
|
||||||
|
key_image: *image
|
||||||
|
}).collect::<Vec<_>>()
|
||||||
|
)
|
||||||
|
).expect("Couldn't prepare outputs despite already doing a dummy prep");
|
||||||
|
self.output_masks = Some(output_masks);
|
||||||
|
|
||||||
|
self.signable.prepare_transaction(
|
||||||
|
&commitments,
|
||||||
|
Bulletproofs::new(
|
||||||
|
&mut ChaCha12Rng::from_seed(self.transcript.rng_seed(b"bulletproofs", None)),
|
||||||
|
&commitments
|
||||||
|
).unwrap()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut commitments = (0 .. self.inputs.len()).map(|c| commitments.iter().map(
|
let mut commitments = (0 .. self.inputs.len()).map(|c| commitments.iter().map(
|
||||||
|commitments| commitments.clone().map(
|
|commitments| commitments.clone().map(
|
||||||
|commitments| commitments[(c * clsag_len) .. ((c * clsag_len) + clsag_len)].to_vec()
|
|commitments| commitments[(c * clsag_len) .. ((c * clsag_len) + clsag_len)].to_vec()
|
||||||
|
|||||||
Reference in New Issue
Block a user