2023-03-11 10:31:58 -05:00
|
|
|
use core::{ops::Deref, fmt};
|
2024-06-28 05:25:02 -04:00
|
|
|
use std_shims::io;
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
use zeroize::{Zeroize, Zeroizing};
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
use rand_core::{RngCore, CryptoRng};
|
|
|
|
|
use rand::seq::SliceRandom;
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
|
2022-05-21 15:33:35 -04:00
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
|
use frost::FrostError;
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
use crate::{
|
2024-06-28 05:25:02 -04:00
|
|
|
io::*,
|
2024-06-27 07:36:45 -04:00
|
|
|
generators::{MAX_COMMITMENTS, hash_to_point},
|
|
|
|
|
primitives::Decoys,
|
2022-05-22 02:24:24 -04:00
|
|
|
ringct::{
|
2024-06-14 16:17:51 -04:00
|
|
|
clsag::{ClsagError, ClsagContext, Clsag},
|
2024-06-27 07:36:45 -04:00
|
|
|
RctType, RctPrunable, RctProofs,
|
2022-05-22 02:24:24 -04:00
|
|
|
},
|
2024-06-27 07:36:45 -04:00
|
|
|
transaction::Transaction,
|
|
|
|
|
extra::MAX_ARBITRARY_DATA_SIZE,
|
2024-06-16 18:40:15 -04:00
|
|
|
address::{Network, AddressSpec, MoneroAddress},
|
2024-06-27 07:36:45 -04:00
|
|
|
rpc::FeeRate,
|
|
|
|
|
ViewPair,
|
|
|
|
|
scan::SpendableOutput,
|
2022-05-21 15:33:35 -04:00
|
|
|
};
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
mod tx_keys;
|
|
|
|
|
mod tx;
|
|
|
|
|
mod eventuality;
|
|
|
|
|
pub use eventuality::Eventuality;
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2024-06-28 13:47:03 -04:00
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
|
mod multisig;
|
|
|
|
|
|
|
|
|
|
pub(crate) fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> core::cmp::Ordering {
|
|
|
|
|
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
#[derive(Clone, PartialEq, Eq, Zeroize)]
|
|
|
|
|
enum ChangeEnum {
|
|
|
|
|
None,
|
|
|
|
|
AddressOnly(MoneroAddress),
|
|
|
|
|
AddressWithView(MoneroAddress, Zeroizing<Scalar>),
|
2024-06-22 09:02:59 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
impl fmt::Debug for ChangeEnum {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
ChangeEnum::None => f.debug_struct("ChangeEnum::None").finish_non_exhaustive(),
|
|
|
|
|
ChangeEnum::AddressOnly(addr) => {
|
|
|
|
|
f.debug_struct("ChangeEnum::AddressOnly").field("addr", &addr).finish()
|
|
|
|
|
}
|
|
|
|
|
ChangeEnum::AddressWithView(addr, _) => {
|
|
|
|
|
f.debug_struct("ChangeEnum::AddressWithView").field("addr", &addr).finish_non_exhaustive()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
/// Specification for a change output.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
|
|
|
|
pub struct Change(ChangeEnum);
|
2022-08-22 04:27:58 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
impl Change {
|
|
|
|
|
/// Create a change output specification.
|
|
|
|
|
///
|
|
|
|
|
/// This take the view key as Monero assumes it has the view key for change outputs. It optimizes
|
|
|
|
|
/// its wallet protocol accordingly.
|
|
|
|
|
// TODO: Accept AddressSpec, not `guaranteed: bool`
|
|
|
|
|
pub fn new(view: &ViewPair, guaranteed: bool) -> Change {
|
|
|
|
|
Change(ChangeEnum::AddressWithView(
|
|
|
|
|
view.address(
|
|
|
|
|
// Which network doesn't matter as the derivations will all be the same
|
|
|
|
|
Network::Mainnet,
|
|
|
|
|
if !guaranteed {
|
|
|
|
|
AddressSpec::Standard
|
|
|
|
|
} else {
|
|
|
|
|
AddressSpec::Featured { subaddress: None, payment_id: None, guaranteed: true }
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
view.view.clone(),
|
|
|
|
|
))
|
|
|
|
|
}
|
2024-02-19 17:45:50 -08:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
/// Create a fingerprintable change output specification.
|
|
|
|
|
///
|
|
|
|
|
/// You MUST assume this will harm your privacy. Only use this if you know what you're doing.
|
|
|
|
|
///
|
|
|
|
|
/// If the change address is Some, this will be unable to optimize the transaction as the
|
|
|
|
|
/// Monero wallet protocol expects it can (due to presumably having the view key for the change
|
|
|
|
|
/// output). If a transaction should be optimized, and isn'tm it will be fingerprintable.
|
|
|
|
|
///
|
|
|
|
|
/// If the change address is None, there are two fingerprints:
|
|
|
|
|
///
|
|
|
|
|
/// 1) The change in the TX is shunted to the fee (making it fingerprintable).
|
|
|
|
|
///
|
|
|
|
|
/// 2) If there are two outputs in the TX, Monero would create a payment ID for the non-change
|
|
|
|
|
/// output so an observer can't tell apart TXs with a payment ID from TXs without a payment
|
|
|
|
|
/// ID. monero-wallet will simply not create a payment ID in this case, revealing it's a
|
|
|
|
|
/// monero-wallet TX without change.
|
|
|
|
|
pub fn fingerprintable(address: Option<MoneroAddress>) -> Change {
|
|
|
|
|
if let Some(address) = address {
|
|
|
|
|
Change(ChangeEnum::AddressOnly(address))
|
|
|
|
|
} else {
|
|
|
|
|
Change(ChangeEnum::None)
|
|
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
}
|
2023-03-11 10:31:58 -05:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
#[derive(Clone, PartialEq, Eq, Zeroize)]
|
|
|
|
|
enum InternalPayment {
|
|
|
|
|
Payment(MoneroAddress, u64),
|
|
|
|
|
Change(MoneroAddress, Option<Zeroizing<Scalar>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl InternalPayment {
|
|
|
|
|
fn address(&self) -> &MoneroAddress {
|
|
|
|
|
match self {
|
|
|
|
|
InternalPayment::Payment(addr, _) | InternalPayment::Change(addr, _) => addr,
|
|
|
|
|
}
|
2023-03-11 10:31:58 -05:00
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
}
|
2023-03-11 10:31:58 -05:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
impl fmt::Debug for InternalPayment {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
InternalPayment::Payment(addr, amount) => f
|
|
|
|
|
.debug_struct("InternalPayment::Payment")
|
|
|
|
|
.field("addr", &addr)
|
|
|
|
|
.field("amount", &amount)
|
|
|
|
|
.finish(),
|
|
|
|
|
InternalPayment::Change(addr, _) => {
|
|
|
|
|
f.debug_struct("InternalPayment::Change").field("addr", &addr).finish_non_exhaustive()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-11 10:31:58 -05:00
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-29 04:14:29 -04:00
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
|
|
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
2024-06-27 07:36:45 -04:00
|
|
|
pub enum SendError {
|
|
|
|
|
#[cfg_attr(feature = "std", error("this library doesn't yet support that RctType"))]
|
|
|
|
|
UnsupportedRctType,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("no inputs"))]
|
2022-05-21 15:33:35 -04:00
|
|
|
NoInputs,
|
2023-07-19 12:06:05 -07:00
|
|
|
#[cfg_attr(feature = "std", error("invalid number of decoys"))]
|
|
|
|
|
InvalidDecoyQuantity,
|
2024-06-27 07:36:45 -04:00
|
|
|
#[cfg_attr(feature = "std", error("no outputs"))]
|
|
|
|
|
NoOutputs,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("too many outputs"))]
|
2022-05-21 15:33:35 -04:00
|
|
|
TooManyOutputs,
|
2024-06-27 07:36:45 -04:00
|
|
|
#[cfg_attr(feature = "std", error("only one output and no change address"))]
|
|
|
|
|
NoChange,
|
|
|
|
|
#[cfg_attr(feature = "std", error("multiple addresses with payment IDs"))]
|
|
|
|
|
MultiplePaymentIds,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("too much data"))]
|
2022-08-30 15:42:23 -04:00
|
|
|
TooMuchData,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("too many inputs/too much arbitrary data"))]
|
2023-03-12 03:54:30 -04:00
|
|
|
TooLargeTransaction,
|
2023-07-29 06:55:55 -04:00
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "std",
|
2024-06-27 07:36:45 -04:00
|
|
|
error("not enough funds (inputs {inputs}, outputs {outputs}, fee {fee:?})")
|
2023-07-29 06:55:55 -04:00
|
|
|
)]
|
2024-06-27 07:36:45 -04:00
|
|
|
NotEnoughFunds { inputs: u64, outputs: u64, fee: Option<u64> },
|
|
|
|
|
#[cfg_attr(feature = "std", error("invalid amount of key images specified"))]
|
|
|
|
|
InvalidAmountOfKeyImages,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("wrong spend private key"))]
|
2024-06-28 05:25:02 -04:00
|
|
|
WrongPrivateKey,
|
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "std",
|
|
|
|
|
error("this SignableTransaction was created by deserializing a malicious serialization")
|
|
|
|
|
)]
|
|
|
|
|
MaliciousSerialization,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("clsag error ({0})"))]
|
2022-05-21 15:33:35 -04:00
|
|
|
ClsagError(ClsagError),
|
|
|
|
|
#[cfg(feature = "multisig")]
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("frost error {0}"))]
|
2022-05-21 15:33:35 -04:00
|
|
|
FrostError(FrostError),
|
|
|
|
|
}
|
|
|
|
|
|
Processor (#259)
* Initial work on a message box
* Finish message-box (untested)
* Expand documentation
* Embed the recipient in the signature challenge
Prevents a message from A -> B from being read as from A -> C.
* Update documentation by bifurcating sender/receiver
* Panic on receiving an invalid signature
If we've received an invalid signature in an authenticated system, a
service is malicious, critically faulty (equivalent to malicious), or
the message layer has been compromised (or is otherwise critically
faulty).
Please note a receiver who handles a message they shouldn't will trigger
this. That falls under being critically faulty.
* Documentation and helper methods
SecureMessage::new and SecureMessage::serialize.
Secure Debug for MessageBox.
* Have SecureMessage not be serialized by default
Allows passing around in-memory, if desired, and moves the error from
decrypt to new (which performs deserialization).
Decrypt no longer has an error since it panics if given an invalid
signature, due to this being intranet code.
* Explain and improve nonce handling
Includes a missing zeroize call.
* Rebase to latest develop
Updates to transcript 0.2.0.
* Add a test for the MessageBox
* Export PrivateKey and PublicKey
* Also test serialization
* Add a key_gen binary to message_box
* Have SecureMessage support Serde
* Add encrypt_to_bytes and decrypt_from_bytes
* Support String ser via base64
* Rename encrypt/decrypt to encrypt_bytes/decrypt_to_bytes
* Directly operate with values supporting Borsh
* Use bincode instead of Borsh
By staying inside of serde, we'll support many more structs. While
bincode isn't canonical, we don't need canonicity on an authenticated,
internal system.
* Turn PrivateKey, PublicKey into structs
Uses Zeroizing for the PrivateKey per #150.
* from_string functions intended for loading from an env
* Use &str for PublicKey from_string (now from_str)
The PrivateKey takes the String to take ownership of its memory and
zeroize it. That isn't needed with PublicKeys.
* Finish updating from develop
* Resolve warning
* Use ZeroizingAlloc on the key_gen binary
* Move message-box from crypto/ to common/
* Move key serialization functions to ser
* add/remove functions in MessageBox
* Implement Hash on dalek_ff_group Points
* Make MessageBox generic to its key
Exposes a &'static str variant for internal use and a RistrettoPoint
variant for external use.
* Add Private to_string as deprecated
Stub before more competent tooling is deployed.
* Private to_public
* Test both Internal and External MessageBox, only use PublicKey in the pub API
* Remove panics on invalid signatures
Leftover from when this was solely internal which is now unsafe.
* Chicken scratch a Scanner task
* Add a write function to the DKG library
Enables writing directly to a file.
Also modifies serialize to return Zeroizing<Vec<u8>> instead of just Vec<u8>.
* Make dkg::encryption pub
* Remove encryption from MessageBox
* Use a 64-bit block number in Substrate
We use a 64-bit block number in general since u32 only works for 120 years
(with a 1 second block time). As some chains even push the 1 second threshold,
especially ones based on DAG consensus, this becomes potentially as low as 60
years.
While that should still be plenty, it's not worth wondering/debating. Since
Serai uses 64-bit block numbers elsewhere, this ensures consistency.
* Misc crypto lints
* Get the scanner scratch to compile
* Initial scanner test
* First few lines of scheduler
* Further work on scheduler, solidify API
* Define Scheduler TX format
* Branch creation algorithm
* Document when the branch algorithm isn't perfect
* Only scanned confirmed blocks
* Document Coin
* Remove Canonical/ChainNumber from processor
The processor should be abstracted from canonical numbers thanks to the
coordinator, making this unnecessary.
* Add README documenting processor flow
* Use Zeroize on substrate primitives
* Define messages from/to the processor
* Correct over-specified versioning
* Correct build re: in_instructions::primitives
* Debug/some serde in crypto/
* Use a struct for ValidatorSetInstance
* Add a processor key_gen task
Redos DB handling code.
* Replace trait + impl with wrapper struct
* Add a key confirmation flow to the key gen task
* Document concerns on key_gen
* Start on a signer task
* Add Send to FROST traits
* Move processor lib.rs to main.rs
Adds a dummy main to reduce clippy dead_code warnings.
* Further flesh out main.rs
* Move the DB trait to AsRef<[u8]>
* Signer task
* Remove a panic in bitcoin when there's insufficient funds
Unchecked underflow.
* Have Monero's mine_block mine one block, not 10
It was initially a nicety to deal with the 10 block lock. C::CONFIRMATIONS
should be used for that instead.
* Test signer
* Replace channel expects with log statements
The expects weren't problematic and had nicer code. They just clutter test
output.
* Remove the old wallet file
It predates the coordinator design and shouldn't be used.
* Rename tests/scan.rs to tests/scanner.rs
* Add a wallet test
Complements the recently removed wallet file by adding a test for the scanner,
scheduler, and signer together.
* Work on a run function
Triggers a clippy ICE.
* Resolve clippy ICE
The issue was the non-fully specified lambda in signer.
* Add KeyGenEvent and KeyGenOrder
Needed so we get KeyConfirmed messages from the key gen task.
While we could've read the CoordinatorMessage to see that, routing through the
key gen tasks ensures we only handle it once it's been successfully saved to
disk.
* Expand scanner test
* Clarify processor documentation
* Have the Scanner load keys on boot/save outputs to disk
* Use Vec<u8> for Block ID
Much more flexible.
* Panic if we see the same output multiple times
* Have the Scanner DB mark itself as corrupt when doing a multi-put
This REALLY should be a TX. Since we don't have a TX API right now, this at
least offers detection.
* Have DST'd DB keys accept AsRef<[u8]>
* Restore polling all signers
Writes a custom future to do so.
Also loads signers on boot using what the scanner claims are active keys.
* Schedule OutInstructions
Adds a data field to Payment.
Also cleans some dead code.
* Panic if we create an invalid transaction
Saves the TX once it's successfully signed so if we do panic, we have a copy.
* Route coordinator messages to their respective signer
Requires adding key to the SignId.
* Send SignTransaction orders for all plans
* Add a timer to retry sign_plans when prepare_send fails
* Minor fmt'ing
* Basic Fee API
* Move the change key into Plan
* Properly route activation_number
* Remove ScannerEvent::Block
It's not used under current designs
* Nicen logs
* Add utilities to get a block's number
* Have main issue AckBlock
Also has a few misc lints.
* Parse instructions out of outputs
* Tweak TODOs and remove an unwrap
* Update Bitcoin max input/output quantity
* Only read one piece of data from Monero
Due to output randomization, it's infeasible.
* Embed plan IDs into the TXs they create
We need to stop attempting signing if we've already signed a protocol. Ideally,
any one of the participating signers should be able to provide a proof the TX
was successfully signed. We can't just run a second signing protocol though as
a single malicious signer could complete the TX signature, and publish it,
yet not complete the secondary signature.
The TX itself has to be sufficient to show that the TX matches the plan. This
is done by embedding the ID, so matching addresses/amounts plans are
distinguished, and by allowing verification a TX actually matches a set of
addresses/amounts.
For Monero, this will need augmenting with the ephemeral keys (or usage of a
static seed for them).
* Don't use OP_RETURN to encode the plan ID on Bitcoin
We can use the inputs to distinguih identical-output plans without issue.
* Update OP_RETURN data access
It's not required to be the last output.
* Add Eventualities to Monero
An Eventuality is an effective equivalent to a SignableTransaction. That is
declared not by the inputs it spends, yet the outputs it creates.
Eventualities are also bound to a 32-byte RNG seed, enabling usage of a
hash-based identifier in a SignableTransaction, allowing multiple
SignableTransactions with the same output set to have different Eventualities.
In order to prevent triggering the burning bug, the RNG seed is hashed with
the planned-to-be-used inputs' output keys. While this does bind to them, it's
only loosely bound. The TX actually created may use different inputs entirely
if a forgery is crafted (which requires no brute forcing).
Binding to the key images would provide a strong binding, yet would require
knowing the key images, which requires active communication with the spend
key.
The purpose of this is so a multisig can identify if a Transaction the entire
group planned has been executed by a subset of the group or not. Once a plan
is created, it can have an Eventuality made. The Eventuality's extra is able
to be inserted into a HashMap, so all new on-chain transactions can be
trivially checked as potential candidates. Once a potential candidate is found,
a check involving ECC ops can be performed.
While this is arguably a DoS vector, the underlying Monero blockchain would
need to be spammed with transactions to trigger it. Accordingly, it becomes
a Monero blockchain DoS vector, when this code is written on the premise
of the Monero blockchain functioning. Accordingly, it is considered handled.
If a forgery does match, it must have created the exact same outputs the
multisig would've. Accordingly, it's argued the multisig shouldn't mind.
This entire suite of code is only necessary due to the lack of outgoing
view keys, yet it's able to avoid an interactive protocol to communicate
key images on every single received output.
While this could be locked to the multisig feature, there's no practical
benefit to doing so.
* Add support for encoding Monero address to instructions
* Move Serai's Monero address encoding into serai-client
serai-client is meant to be a single library enabling using Serai. While it was
originally written as an RPC client for Serai, apps actually using Serai will
primarily be sending transactions on connected networks. Sending those
transactions require proper {In, Out}Instructions, including proper address
encoding.
Not only has address encoding been moved, yet the subxt client is now behind
a feature. coin integrations have their own features, which are on by default.
primitives are always exposed.
* Reorganize file layout a bit, add feature flags to processor
* Tidy up ETH Dockerfile
* Add Bitcoin address encoding
* Move Bitcoin::Address to serai-client's
* Comment where tweaking needs to happen
* Add an API to check if a plan was completed in a specific TX
This allows any participating signer to submit the TX ID to prevent further
signing attempts.
Also performs some API cleanup.
* Minimize FROST dependencies
* Use a seeded RNG for key gen
* Tweak keys from Key gen
* Test proper usage of Branch/Change addresses
Adds a more descriptive error to an error case in decoys, and pads Monero
payments as needed.
* Also test spending the change output
* Add queued_plans to the Scheduler
queued_plans is for payments to be issued when an amount appears, yet the
amount is currently pre-fee. One the output is actually created, the
Scheduler should be notified of the amount it was created with, moving from
queued_plans to plans under the actual amount.
Also tightens debug_asserts to asserts for invariants which may are at risk of
being exclusive to prod.
* Add missing tweak_keys call
* Correct decoy selection height handling
* Add a few log statements to the scheduler
* Simplify test's get_block_number
* Simplify, while making more robust, branch address handling in Scheduler
* Have fees deducted from payments
Corrects Monero's handling of fees when there's no change address.
Adds a DUST variable, as needed due to 1_00_000_000 not being enough to pay
its fee on Monero.
* Add comment to Monero
* Consolidate BTC/XMR prepare_send code
These aren't fully consolidated. We'd need a SignableTransaction trait for
that. This is a lot cleaner though.
* Ban integrated addresses
The reasoning why is accordingly documented.
* Tidy TODOs/dust handling
* Update README TODO
* Use a determinisitic protocol version in Monero
* Test rebuilt KeyGen machines function as expected
* Use a more robust KeyGen entropy system
* Add DB TXNs
Also load entropy from env
* Add a loop for processing messages from substrate
Allows detecting if we're behind, and if so, waiting to handle the message
* Set Monero MAX_INPUTS properly
The previous number was based on an old hard fork. With the ring size having
increased, transactions have since got larger.
* Distinguish TODOs into TODO and TODO2s
TODO2s are for after protonet
* Zeroize secret share repr in ThresholdCore write
* Work on Eventualities
Adds serialization and stops signing when an eventuality is proven.
* Use a more robust DB key schema
* Update to {k, p}256 0.12
* cargo +nightly clippy
* cargo update
* Slight message-box tweaks
* Update to recent Monero merge
* Add a Coordinator trait for communication with coordinator
* Remove KeyGenHandle for just KeyGen
While KeyGen previously accepted instructions over a channel, this breaks the
ack flow needed for coordinator communication. Now, KeyGen is the direct object
with a handle() function for messages.
Thankfully, this ended up being rather trivial for KeyGen as it has no
background tasks.
* Add a handle function to Signer
Enables determining when it's finished handling a CoordinatorMessage and
therefore creating an acknowledgement.
* Save transactions used to complete eventualities
* Use a more intelligent sleep in the signer
* Emit SignedTransaction with the first ID *we can still get from our node*
* Move Substrate message handling into the new coordinator recv loop
* Add handle function to Scanner
* Remove the plans timer
Enables ensuring the ordring on the handling of plans.
* Remove the outputs function which panicked if a precondition wasn't met
The new API only returns outputs upon satisfaction of the precondition.
* Convert SignerOrder::SignTransaction to a function
* Remove the key_gen object from sign_plans
* Refactor out get_fee/prepare_send into dedicated functions
* Save plans being signed to the DB
* Reload transactions being signed on boot
* Stop reloading TXs being signed (and report it to peers)
* Remove message-box from the processor branch
We don't use it here yet.
* cargo +nightly fmt
* Move back common/zalloc
* Update subxt to 0.27
* Zeroize ^1.5, not 1
* Update GitHub workflow
* Remove usage of SignId in completed
2023-03-16 22:59:40 -04:00
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
2022-05-21 15:33:35 -04:00
|
|
|
pub struct SignableTransaction {
|
2024-06-27 07:36:45 -04:00
|
|
|
rct_type: RctType,
|
|
|
|
|
sender_view_key: Zeroizing<Scalar>,
|
2023-07-19 12:06:05 -07:00
|
|
|
inputs: Vec<(SpendableOutput, Decoys)>,
|
2023-03-11 10:31:58 -05:00
|
|
|
payments: Vec<InternalPayment>,
|
2022-12-09 18:58:11 +03:00
|
|
|
data: Vec<Vec<u8>>,
|
2024-06-16 19:59:25 -04:00
|
|
|
fee_rate: FeeRate,
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
struct SignableTransactionWithKeyImages {
|
|
|
|
|
intent: SignableTransaction,
|
|
|
|
|
key_images: Vec<EdwardsPoint>,
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-21 15:33:35 -04:00
|
|
|
impl SignableTransaction {
|
2024-06-28 05:25:02 -04:00
|
|
|
fn validate(&self) -> Result<(), SendError> {
|
|
|
|
|
match self.rct_type {
|
2024-06-27 07:36:45 -04:00
|
|
|
RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {}
|
|
|
|
|
_ => Err(SendError::UnsupportedRctType)?,
|
2024-06-28 05:25:02 -04:00
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
if self.inputs.is_empty() {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::NoInputs)?;
|
|
|
|
|
}
|
2024-06-28 05:25:02 -04:00
|
|
|
for (_, decoys) in &self.inputs {
|
2024-06-27 07:36:45 -04:00
|
|
|
if decoys.len() !=
|
2024-06-28 05:25:02 -04:00
|
|
|
match self.rct_type {
|
2024-06-27 07:36:45 -04:00
|
|
|
RctType::ClsagBulletproof => 11,
|
|
|
|
|
RctType::ClsagBulletproofPlus => 16,
|
|
|
|
|
_ => panic!("unsupported RctType"),
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
Err(SendError::InvalidDecoyQuantity)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
// Check we have at least one non-change output
|
|
|
|
|
if !self.payments.iter().any(|payment| matches!(payment, InternalPayment::Payment(_, _))) {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::NoOutputs)?;
|
|
|
|
|
}
|
|
|
|
|
// If we don't have at least two outputs, as required by Monero, error
|
2024-06-28 05:25:02 -04:00
|
|
|
if self.payments.len() < 2 {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::NoChange)?;
|
|
|
|
|
}
|
2024-06-28 05:25:02 -04:00
|
|
|
// Check we don't have multiple Change outputs due to decoding a malicious serialization
|
|
|
|
|
{
|
|
|
|
|
let mut change_count = 0;
|
|
|
|
|
for payment in &self.payments {
|
|
|
|
|
change_count += usize::from(u8::from(matches!(payment, InternalPayment::Change(_, _))));
|
|
|
|
|
}
|
|
|
|
|
if change_count > 1 {
|
|
|
|
|
Err(SendError::MaliciousSerialization)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
|
|
|
|
|
// Make sure there's at most one payment ID
|
|
|
|
|
{
|
2022-08-22 06:54:01 -04:00
|
|
|
let mut payment_ids = 0;
|
2024-06-28 05:25:02 -04:00
|
|
|
for payment in &self.payments {
|
|
|
|
|
payment_ids += usize::from(u8::from(payment.address().payment_id().is_some()));
|
2022-08-22 06:54:01 -04:00
|
|
|
}
|
|
|
|
|
if payment_ids > 1 {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::MultiplePaymentIds)?;
|
2022-08-22 04:27:58 -04:00
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
if self.payments.len() > MAX_COMMITMENTS {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::TooManyOutputs)?;
|
|
|
|
|
}
|
2023-07-19 12:06:05 -07:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Check the length of each arbitrary data
|
2024-06-28 05:25:02 -04:00
|
|
|
for part in &self.data {
|
2023-03-11 05:47:25 -05:00
|
|
|
if part.len() > MAX_ARBITRARY_DATA_SIZE {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::TooMuchData)?;
|
2022-12-09 18:58:11 +03:00
|
|
|
}
|
2022-08-30 15:42:23 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Check the length of TX extra
|
2023-03-25 01:26:42 -04:00
|
|
|
// https://github.com/monero-project/monero/pull/8733
|
|
|
|
|
const MAX_EXTRA_SIZE: usize = 1060;
|
2024-06-28 05:25:02 -04:00
|
|
|
if self.extra().len() > MAX_EXTRA_SIZE {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::TooMuchData)?;
|
2023-03-25 01:26:42 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Make sure we have enough funds
|
2024-06-28 05:25:02 -04:00
|
|
|
let in_amount = self.inputs.iter().map(|(input, _)| input.commitment().amount).sum::<u64>();
|
|
|
|
|
let payments_amount = self
|
|
|
|
|
.payments
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|payment| match payment {
|
|
|
|
|
InternalPayment::Payment(_, amount) => Some(amount),
|
|
|
|
|
InternalPayment::Change(_, _) => None,
|
|
|
|
|
})
|
|
|
|
|
.sum::<u64>();
|
2024-06-27 07:36:45 -04:00
|
|
|
// Necessary so weight_and_fee doesn't underflow
|
|
|
|
|
if in_amount < payments_amount {
|
|
|
|
|
Err(SendError::NotEnoughFunds { inputs: in_amount, outputs: payments_amount, fee: None })?;
|
|
|
|
|
}
|
2024-06-28 05:25:02 -04:00
|
|
|
let (weight, fee) = self.weight_and_fee();
|
2024-06-27 07:36:45 -04:00
|
|
|
if in_amount < (payments_amount + fee) {
|
|
|
|
|
Err(SendError::NotEnoughFunds {
|
|
|
|
|
inputs: in_amount,
|
|
|
|
|
outputs: payments_amount,
|
|
|
|
|
fee: Some(fee),
|
|
|
|
|
})?;
|
|
|
|
|
}
|
2023-03-12 03:54:30 -04:00
|
|
|
|
|
|
|
|
// The actual limit is half the block size, and for the minimum block size of 300k, that'd be
|
|
|
|
|
// 150k
|
|
|
|
|
// wallet2 will only create transactions up to 100k bytes however
|
|
|
|
|
const MAX_TX_SIZE: usize = 100_000;
|
2023-07-19 12:06:05 -07:00
|
|
|
if weight >= MAX_TX_SIZE {
|
2024-06-27 07:36:45 -04:00
|
|
|
Err(SendError::TooLargeTransaction)?;
|
2022-08-22 06:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new(
|
|
|
|
|
rct_type: RctType,
|
|
|
|
|
sender_view_key: Zeroizing<Scalar>,
|
|
|
|
|
inputs: Vec<(SpendableOutput, Decoys)>,
|
|
|
|
|
payments: Vec<(MoneroAddress, u64)>,
|
|
|
|
|
change: Change,
|
|
|
|
|
data: Vec<Vec<u8>>,
|
|
|
|
|
fee_rate: FeeRate,
|
|
|
|
|
) -> Result<SignableTransaction, SendError> {
|
|
|
|
|
// Re-format the payments and change into a consolidated payments list
|
|
|
|
|
let mut payments = payments
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(addr, amount)| InternalPayment::Payment(addr, amount))
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
match change.0 {
|
|
|
|
|
ChangeEnum::None => {}
|
|
|
|
|
ChangeEnum::AddressOnly(addr) => payments.push(InternalPayment::Change(addr, None)),
|
|
|
|
|
ChangeEnum::AddressWithView(addr, view) => {
|
|
|
|
|
payments.push(InternalPayment::Change(addr, Some(view)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 16:04:08 -04:00
|
|
|
let mut res =
|
|
|
|
|
SignableTransaction { rct_type, sender_view_key, inputs, payments, data, fee_rate };
|
2024-06-28 05:25:02 -04:00
|
|
|
res.validate()?;
|
2024-06-28 16:04:08 -04:00
|
|
|
|
|
|
|
|
// Shuffle the payments
|
|
|
|
|
{
|
|
|
|
|
let mut rng = res.seeded_rng(b"shuffle_payments");
|
|
|
|
|
res.payments.shuffle(&mut rng);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
Ok(res)
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 16:04:08 -04:00
|
|
|
pub fn fee_rate(&self) -> FeeRate {
|
|
|
|
|
self.fee_rate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn fee(&self) -> u64 {
|
|
|
|
|
self.weight_and_fee().1
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 05:25:02 -04:00
|
|
|
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
|
|
|
|
fn write_input<W: io::Write>(input: &(SpendableOutput, Decoys), w: &mut W) -> io::Result<()> {
|
|
|
|
|
input.0.write(w)?;
|
|
|
|
|
input.1.write(w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
|
|
|
|
|
match payment {
|
|
|
|
|
InternalPayment::Payment(addr, amount) => {
|
|
|
|
|
w.write_all(&[0])?;
|
|
|
|
|
write_vec(write_byte, addr.to_string().as_bytes(), w)?;
|
|
|
|
|
w.write_all(&amount.to_le_bytes())
|
|
|
|
|
}
|
|
|
|
|
InternalPayment::Change(addr, change_view) => {
|
|
|
|
|
w.write_all(&[1])?;
|
|
|
|
|
write_vec(write_byte, addr.to_string().as_bytes(), w)?;
|
|
|
|
|
if let Some(view) = change_view.as_ref() {
|
|
|
|
|
w.write_all(&[1])?;
|
|
|
|
|
write_scalar(view, w)
|
|
|
|
|
} else {
|
|
|
|
|
w.write_all(&[0])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_byte(&u8::from(self.rct_type), w)?;
|
|
|
|
|
write_scalar(&self.sender_view_key, w)?;
|
|
|
|
|
write_vec(write_input, &self.inputs, w)?;
|
|
|
|
|
write_vec(write_payment, &self.payments, w)?;
|
|
|
|
|
write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
|
|
|
|
|
self.fee_rate.write(w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
|
|
|
let mut buf = Vec::with_capacity(256);
|
|
|
|
|
self.write(&mut buf).unwrap();
|
|
|
|
|
buf
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
|
|
|
|
|
fn read_input(r: &mut impl io::Read) -> io::Result<(SpendableOutput, Decoys)> {
|
|
|
|
|
Ok((SpendableOutput::read(r)?, Decoys::read(r)?))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
|
|
|
|
|
String::from_utf8(read_vec(read_byte, r)?)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|str| MoneroAddress::from_str_raw(&str).ok())
|
|
|
|
|
.ok_or_else(|| io::Error::other("invalid address"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_payment<R: io::Read>(r: &mut R) -> io::Result<InternalPayment> {
|
|
|
|
|
Ok(match read_byte(r)? {
|
|
|
|
|
0 => InternalPayment::Payment(read_address(r)?, read_u64(r)?),
|
|
|
|
|
1 => InternalPayment::Change(
|
|
|
|
|
read_address(r)?,
|
|
|
|
|
match read_byte(r)? {
|
|
|
|
|
0 => None,
|
|
|
|
|
1 => Some(Zeroizing::new(read_scalar(r)?)),
|
|
|
|
|
_ => Err(io::Error::other("invalid change view"))?,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
_ => Err(io::Error::other("invalid payment"))?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let res = SignableTransaction {
|
|
|
|
|
rct_type: RctType::try_from(read_byte(r)?)
|
|
|
|
|
.map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
|
|
|
|
|
sender_view_key: Zeroizing::new(read_scalar(r)?),
|
|
|
|
|
inputs: read_vec(read_input, r)?,
|
|
|
|
|
payments: read_vec(read_payment, r)?,
|
|
|
|
|
data: read_vec(|r| read_vec(read_byte, r), r)?,
|
|
|
|
|
fee_rate: FeeRate::read(r)?,
|
|
|
|
|
};
|
|
|
|
|
match res.validate() {
|
|
|
|
|
Ok(()) => {}
|
|
|
|
|
Err(e) => Err(io::Error::other(e))?,
|
|
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
Ok(res)
|
Processor (#259)
* Initial work on a message box
* Finish message-box (untested)
* Expand documentation
* Embed the recipient in the signature challenge
Prevents a message from A -> B from being read as from A -> C.
* Update documentation by bifurcating sender/receiver
* Panic on receiving an invalid signature
If we've received an invalid signature in an authenticated system, a
service is malicious, critically faulty (equivalent to malicious), or
the message layer has been compromised (or is otherwise critically
faulty).
Please note a receiver who handles a message they shouldn't will trigger
this. That falls under being critically faulty.
* Documentation and helper methods
SecureMessage::new and SecureMessage::serialize.
Secure Debug for MessageBox.
* Have SecureMessage not be serialized by default
Allows passing around in-memory, if desired, and moves the error from
decrypt to new (which performs deserialization).
Decrypt no longer has an error since it panics if given an invalid
signature, due to this being intranet code.
* Explain and improve nonce handling
Includes a missing zeroize call.
* Rebase to latest develop
Updates to transcript 0.2.0.
* Add a test for the MessageBox
* Export PrivateKey and PublicKey
* Also test serialization
* Add a key_gen binary to message_box
* Have SecureMessage support Serde
* Add encrypt_to_bytes and decrypt_from_bytes
* Support String ser via base64
* Rename encrypt/decrypt to encrypt_bytes/decrypt_to_bytes
* Directly operate with values supporting Borsh
* Use bincode instead of Borsh
By staying inside of serde, we'll support many more structs. While
bincode isn't canonical, we don't need canonicity on an authenticated,
internal system.
* Turn PrivateKey, PublicKey into structs
Uses Zeroizing for the PrivateKey per #150.
* from_string functions intended for loading from an env
* Use &str for PublicKey from_string (now from_str)
The PrivateKey takes the String to take ownership of its memory and
zeroize it. That isn't needed with PublicKeys.
* Finish updating from develop
* Resolve warning
* Use ZeroizingAlloc on the key_gen binary
* Move message-box from crypto/ to common/
* Move key serialization functions to ser
* add/remove functions in MessageBox
* Implement Hash on dalek_ff_group Points
* Make MessageBox generic to its key
Exposes a &'static str variant for internal use and a RistrettoPoint
variant for external use.
* Add Private to_string as deprecated
Stub before more competent tooling is deployed.
* Private to_public
* Test both Internal and External MessageBox, only use PublicKey in the pub API
* Remove panics on invalid signatures
Leftover from when this was solely internal which is now unsafe.
* Chicken scratch a Scanner task
* Add a write function to the DKG library
Enables writing directly to a file.
Also modifies serialize to return Zeroizing<Vec<u8>> instead of just Vec<u8>.
* Make dkg::encryption pub
* Remove encryption from MessageBox
* Use a 64-bit block number in Substrate
We use a 64-bit block number in general since u32 only works for 120 years
(with a 1 second block time). As some chains even push the 1 second threshold,
especially ones based on DAG consensus, this becomes potentially as low as 60
years.
While that should still be plenty, it's not worth wondering/debating. Since
Serai uses 64-bit block numbers elsewhere, this ensures consistency.
* Misc crypto lints
* Get the scanner scratch to compile
* Initial scanner test
* First few lines of scheduler
* Further work on scheduler, solidify API
* Define Scheduler TX format
* Branch creation algorithm
* Document when the branch algorithm isn't perfect
* Only scanned confirmed blocks
* Document Coin
* Remove Canonical/ChainNumber from processor
The processor should be abstracted from canonical numbers thanks to the
coordinator, making this unnecessary.
* Add README documenting processor flow
* Use Zeroize on substrate primitives
* Define messages from/to the processor
* Correct over-specified versioning
* Correct build re: in_instructions::primitives
* Debug/some serde in crypto/
* Use a struct for ValidatorSetInstance
* Add a processor key_gen task
Redos DB handling code.
* Replace trait + impl with wrapper struct
* Add a key confirmation flow to the key gen task
* Document concerns on key_gen
* Start on a signer task
* Add Send to FROST traits
* Move processor lib.rs to main.rs
Adds a dummy main to reduce clippy dead_code warnings.
* Further flesh out main.rs
* Move the DB trait to AsRef<[u8]>
* Signer task
* Remove a panic in bitcoin when there's insufficient funds
Unchecked underflow.
* Have Monero's mine_block mine one block, not 10
It was initially a nicety to deal with the 10 block lock. C::CONFIRMATIONS
should be used for that instead.
* Test signer
* Replace channel expects with log statements
The expects weren't problematic and had nicer code. They just clutter test
output.
* Remove the old wallet file
It predates the coordinator design and shouldn't be used.
* Rename tests/scan.rs to tests/scanner.rs
* Add a wallet test
Complements the recently removed wallet file by adding a test for the scanner,
scheduler, and signer together.
* Work on a run function
Triggers a clippy ICE.
* Resolve clippy ICE
The issue was the non-fully specified lambda in signer.
* Add KeyGenEvent and KeyGenOrder
Needed so we get KeyConfirmed messages from the key gen task.
While we could've read the CoordinatorMessage to see that, routing through the
key gen tasks ensures we only handle it once it's been successfully saved to
disk.
* Expand scanner test
* Clarify processor documentation
* Have the Scanner load keys on boot/save outputs to disk
* Use Vec<u8> for Block ID
Much more flexible.
* Panic if we see the same output multiple times
* Have the Scanner DB mark itself as corrupt when doing a multi-put
This REALLY should be a TX. Since we don't have a TX API right now, this at
least offers detection.
* Have DST'd DB keys accept AsRef<[u8]>
* Restore polling all signers
Writes a custom future to do so.
Also loads signers on boot using what the scanner claims are active keys.
* Schedule OutInstructions
Adds a data field to Payment.
Also cleans some dead code.
* Panic if we create an invalid transaction
Saves the TX once it's successfully signed so if we do panic, we have a copy.
* Route coordinator messages to their respective signer
Requires adding key to the SignId.
* Send SignTransaction orders for all plans
* Add a timer to retry sign_plans when prepare_send fails
* Minor fmt'ing
* Basic Fee API
* Move the change key into Plan
* Properly route activation_number
* Remove ScannerEvent::Block
It's not used under current designs
* Nicen logs
* Add utilities to get a block's number
* Have main issue AckBlock
Also has a few misc lints.
* Parse instructions out of outputs
* Tweak TODOs and remove an unwrap
* Update Bitcoin max input/output quantity
* Only read one piece of data from Monero
Due to output randomization, it's infeasible.
* Embed plan IDs into the TXs they create
We need to stop attempting signing if we've already signed a protocol. Ideally,
any one of the participating signers should be able to provide a proof the TX
was successfully signed. We can't just run a second signing protocol though as
a single malicious signer could complete the TX signature, and publish it,
yet not complete the secondary signature.
The TX itself has to be sufficient to show that the TX matches the plan. This
is done by embedding the ID, so matching addresses/amounts plans are
distinguished, and by allowing verification a TX actually matches a set of
addresses/amounts.
For Monero, this will need augmenting with the ephemeral keys (or usage of a
static seed for them).
* Don't use OP_RETURN to encode the plan ID on Bitcoin
We can use the inputs to distinguih identical-output plans without issue.
* Update OP_RETURN data access
It's not required to be the last output.
* Add Eventualities to Monero
An Eventuality is an effective equivalent to a SignableTransaction. That is
declared not by the inputs it spends, yet the outputs it creates.
Eventualities are also bound to a 32-byte RNG seed, enabling usage of a
hash-based identifier in a SignableTransaction, allowing multiple
SignableTransactions with the same output set to have different Eventualities.
In order to prevent triggering the burning bug, the RNG seed is hashed with
the planned-to-be-used inputs' output keys. While this does bind to them, it's
only loosely bound. The TX actually created may use different inputs entirely
if a forgery is crafted (which requires no brute forcing).
Binding to the key images would provide a strong binding, yet would require
knowing the key images, which requires active communication with the spend
key.
The purpose of this is so a multisig can identify if a Transaction the entire
group planned has been executed by a subset of the group or not. Once a plan
is created, it can have an Eventuality made. The Eventuality's extra is able
to be inserted into a HashMap, so all new on-chain transactions can be
trivially checked as potential candidates. Once a potential candidate is found,
a check involving ECC ops can be performed.
While this is arguably a DoS vector, the underlying Monero blockchain would
need to be spammed with transactions to trigger it. Accordingly, it becomes
a Monero blockchain DoS vector, when this code is written on the premise
of the Monero blockchain functioning. Accordingly, it is considered handled.
If a forgery does match, it must have created the exact same outputs the
multisig would've. Accordingly, it's argued the multisig shouldn't mind.
This entire suite of code is only necessary due to the lack of outgoing
view keys, yet it's able to avoid an interactive protocol to communicate
key images on every single received output.
While this could be locked to the multisig feature, there's no practical
benefit to doing so.
* Add support for encoding Monero address to instructions
* Move Serai's Monero address encoding into serai-client
serai-client is meant to be a single library enabling using Serai. While it was
originally written as an RPC client for Serai, apps actually using Serai will
primarily be sending transactions on connected networks. Sending those
transactions require proper {In, Out}Instructions, including proper address
encoding.
Not only has address encoding been moved, yet the subxt client is now behind
a feature. coin integrations have their own features, which are on by default.
primitives are always exposed.
* Reorganize file layout a bit, add feature flags to processor
* Tidy up ETH Dockerfile
* Add Bitcoin address encoding
* Move Bitcoin::Address to serai-client's
* Comment where tweaking needs to happen
* Add an API to check if a plan was completed in a specific TX
This allows any participating signer to submit the TX ID to prevent further
signing attempts.
Also performs some API cleanup.
* Minimize FROST dependencies
* Use a seeded RNG for key gen
* Tweak keys from Key gen
* Test proper usage of Branch/Change addresses
Adds a more descriptive error to an error case in decoys, and pads Monero
payments as needed.
* Also test spending the change output
* Add queued_plans to the Scheduler
queued_plans is for payments to be issued when an amount appears, yet the
amount is currently pre-fee. One the output is actually created, the
Scheduler should be notified of the amount it was created with, moving from
queued_plans to plans under the actual amount.
Also tightens debug_asserts to asserts for invariants which may are at risk of
being exclusive to prod.
* Add missing tweak_keys call
* Correct decoy selection height handling
* Add a few log statements to the scheduler
* Simplify test's get_block_number
* Simplify, while making more robust, branch address handling in Scheduler
* Have fees deducted from payments
Corrects Monero's handling of fees when there's no change address.
Adds a DUST variable, as needed due to 1_00_000_000 not being enough to pay
its fee on Monero.
* Add comment to Monero
* Consolidate BTC/XMR prepare_send code
These aren't fully consolidated. We'd need a SignableTransaction trait for
that. This is a lot cleaner though.
* Ban integrated addresses
The reasoning why is accordingly documented.
* Tidy TODOs/dust handling
* Update README TODO
* Use a determinisitic protocol version in Monero
* Test rebuilt KeyGen machines function as expected
* Use a more robust KeyGen entropy system
* Add DB TXNs
Also load entropy from env
* Add a loop for processing messages from substrate
Allows detecting if we're behind, and if so, waiting to handle the message
* Set Monero MAX_INPUTS properly
The previous number was based on an old hard fork. With the ring size having
increased, transactions have since got larger.
* Distinguish TODOs into TODO and TODO2s
TODO2s are for after protonet
* Zeroize secret share repr in ThresholdCore write
* Work on Eventualities
Adds serialization and stops signing when an eventuality is proven.
* Use a more robust DB key schema
* Update to {k, p}256 0.12
* cargo +nightly clippy
* cargo update
* Slight message-box tweaks
* Update to recent Monero merge
* Add a Coordinator trait for communication with coordinator
* Remove KeyGenHandle for just KeyGen
While KeyGen previously accepted instructions over a channel, this breaks the
ack flow needed for coordinator communication. Now, KeyGen is the direct object
with a handle() function for messages.
Thankfully, this ended up being rather trivial for KeyGen as it has no
background tasks.
* Add a handle function to Signer
Enables determining when it's finished handling a CoordinatorMessage and
therefore creating an acknowledgement.
* Save transactions used to complete eventualities
* Use a more intelligent sleep in the signer
* Emit SignedTransaction with the first ID *we can still get from our node*
* Move Substrate message handling into the new coordinator recv loop
* Add handle function to Scanner
* Remove the plans timer
Enables ensuring the ordring on the handling of plans.
* Remove the outputs function which panicked if a precondition wasn't met
The new API only returns outputs upon satisfaction of the precondition.
* Convert SignerOrder::SignTransaction to a function
* Remove the key_gen object from sign_plans
* Refactor out get_fee/prepare_send into dedicated functions
* Save plans being signed to the DB
* Reload transactions being signed on boot
* Stop reloading TXs being signed (and report it to peers)
* Remove message-box from the processor branch
We don't use it here yet.
* cargo +nightly fmt
* Move back common/zalloc
* Update subxt to 0.27
* Zeroize ^1.5, not 1
* Update GitHub workflow
* Remove usage of SignId in completed
2023-03-16 22:59:40 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
fn with_key_images(mut self, key_images: Vec<EdwardsPoint>) -> SignableTransactionWithKeyImages {
|
|
|
|
|
debug_assert_eq!(self.inputs.len(), key_images.len());
|
Processor (#259)
* Initial work on a message box
* Finish message-box (untested)
* Expand documentation
* Embed the recipient in the signature challenge
Prevents a message from A -> B from being read as from A -> C.
* Update documentation by bifurcating sender/receiver
* Panic on receiving an invalid signature
If we've received an invalid signature in an authenticated system, a
service is malicious, critically faulty (equivalent to malicious), or
the message layer has been compromised (or is otherwise critically
faulty).
Please note a receiver who handles a message they shouldn't will trigger
this. That falls under being critically faulty.
* Documentation and helper methods
SecureMessage::new and SecureMessage::serialize.
Secure Debug for MessageBox.
* Have SecureMessage not be serialized by default
Allows passing around in-memory, if desired, and moves the error from
decrypt to new (which performs deserialization).
Decrypt no longer has an error since it panics if given an invalid
signature, due to this being intranet code.
* Explain and improve nonce handling
Includes a missing zeroize call.
* Rebase to latest develop
Updates to transcript 0.2.0.
* Add a test for the MessageBox
* Export PrivateKey and PublicKey
* Also test serialization
* Add a key_gen binary to message_box
* Have SecureMessage support Serde
* Add encrypt_to_bytes and decrypt_from_bytes
* Support String ser via base64
* Rename encrypt/decrypt to encrypt_bytes/decrypt_to_bytes
* Directly operate with values supporting Borsh
* Use bincode instead of Borsh
By staying inside of serde, we'll support many more structs. While
bincode isn't canonical, we don't need canonicity on an authenticated,
internal system.
* Turn PrivateKey, PublicKey into structs
Uses Zeroizing for the PrivateKey per #150.
* from_string functions intended for loading from an env
* Use &str for PublicKey from_string (now from_str)
The PrivateKey takes the String to take ownership of its memory and
zeroize it. That isn't needed with PublicKeys.
* Finish updating from develop
* Resolve warning
* Use ZeroizingAlloc on the key_gen binary
* Move message-box from crypto/ to common/
* Move key serialization functions to ser
* add/remove functions in MessageBox
* Implement Hash on dalek_ff_group Points
* Make MessageBox generic to its key
Exposes a &'static str variant for internal use and a RistrettoPoint
variant for external use.
* Add Private to_string as deprecated
Stub before more competent tooling is deployed.
* Private to_public
* Test both Internal and External MessageBox, only use PublicKey in the pub API
* Remove panics on invalid signatures
Leftover from when this was solely internal which is now unsafe.
* Chicken scratch a Scanner task
* Add a write function to the DKG library
Enables writing directly to a file.
Also modifies serialize to return Zeroizing<Vec<u8>> instead of just Vec<u8>.
* Make dkg::encryption pub
* Remove encryption from MessageBox
* Use a 64-bit block number in Substrate
We use a 64-bit block number in general since u32 only works for 120 years
(with a 1 second block time). As some chains even push the 1 second threshold,
especially ones based on DAG consensus, this becomes potentially as low as 60
years.
While that should still be plenty, it's not worth wondering/debating. Since
Serai uses 64-bit block numbers elsewhere, this ensures consistency.
* Misc crypto lints
* Get the scanner scratch to compile
* Initial scanner test
* First few lines of scheduler
* Further work on scheduler, solidify API
* Define Scheduler TX format
* Branch creation algorithm
* Document when the branch algorithm isn't perfect
* Only scanned confirmed blocks
* Document Coin
* Remove Canonical/ChainNumber from processor
The processor should be abstracted from canonical numbers thanks to the
coordinator, making this unnecessary.
* Add README documenting processor flow
* Use Zeroize on substrate primitives
* Define messages from/to the processor
* Correct over-specified versioning
* Correct build re: in_instructions::primitives
* Debug/some serde in crypto/
* Use a struct for ValidatorSetInstance
* Add a processor key_gen task
Redos DB handling code.
* Replace trait + impl with wrapper struct
* Add a key confirmation flow to the key gen task
* Document concerns on key_gen
* Start on a signer task
* Add Send to FROST traits
* Move processor lib.rs to main.rs
Adds a dummy main to reduce clippy dead_code warnings.
* Further flesh out main.rs
* Move the DB trait to AsRef<[u8]>
* Signer task
* Remove a panic in bitcoin when there's insufficient funds
Unchecked underflow.
* Have Monero's mine_block mine one block, not 10
It was initially a nicety to deal with the 10 block lock. C::CONFIRMATIONS
should be used for that instead.
* Test signer
* Replace channel expects with log statements
The expects weren't problematic and had nicer code. They just clutter test
output.
* Remove the old wallet file
It predates the coordinator design and shouldn't be used.
* Rename tests/scan.rs to tests/scanner.rs
* Add a wallet test
Complements the recently removed wallet file by adding a test for the scanner,
scheduler, and signer together.
* Work on a run function
Triggers a clippy ICE.
* Resolve clippy ICE
The issue was the non-fully specified lambda in signer.
* Add KeyGenEvent and KeyGenOrder
Needed so we get KeyConfirmed messages from the key gen task.
While we could've read the CoordinatorMessage to see that, routing through the
key gen tasks ensures we only handle it once it's been successfully saved to
disk.
* Expand scanner test
* Clarify processor documentation
* Have the Scanner load keys on boot/save outputs to disk
* Use Vec<u8> for Block ID
Much more flexible.
* Panic if we see the same output multiple times
* Have the Scanner DB mark itself as corrupt when doing a multi-put
This REALLY should be a TX. Since we don't have a TX API right now, this at
least offers detection.
* Have DST'd DB keys accept AsRef<[u8]>
* Restore polling all signers
Writes a custom future to do so.
Also loads signers on boot using what the scanner claims are active keys.
* Schedule OutInstructions
Adds a data field to Payment.
Also cleans some dead code.
* Panic if we create an invalid transaction
Saves the TX once it's successfully signed so if we do panic, we have a copy.
* Route coordinator messages to their respective signer
Requires adding key to the SignId.
* Send SignTransaction orders for all plans
* Add a timer to retry sign_plans when prepare_send fails
* Minor fmt'ing
* Basic Fee API
* Move the change key into Plan
* Properly route activation_number
* Remove ScannerEvent::Block
It's not used under current designs
* Nicen logs
* Add utilities to get a block's number
* Have main issue AckBlock
Also has a few misc lints.
* Parse instructions out of outputs
* Tweak TODOs and remove an unwrap
* Update Bitcoin max input/output quantity
* Only read one piece of data from Monero
Due to output randomization, it's infeasible.
* Embed plan IDs into the TXs they create
We need to stop attempting signing if we've already signed a protocol. Ideally,
any one of the participating signers should be able to provide a proof the TX
was successfully signed. We can't just run a second signing protocol though as
a single malicious signer could complete the TX signature, and publish it,
yet not complete the secondary signature.
The TX itself has to be sufficient to show that the TX matches the plan. This
is done by embedding the ID, so matching addresses/amounts plans are
distinguished, and by allowing verification a TX actually matches a set of
addresses/amounts.
For Monero, this will need augmenting with the ephemeral keys (or usage of a
static seed for them).
* Don't use OP_RETURN to encode the plan ID on Bitcoin
We can use the inputs to distinguih identical-output plans without issue.
* Update OP_RETURN data access
It's not required to be the last output.
* Add Eventualities to Monero
An Eventuality is an effective equivalent to a SignableTransaction. That is
declared not by the inputs it spends, yet the outputs it creates.
Eventualities are also bound to a 32-byte RNG seed, enabling usage of a
hash-based identifier in a SignableTransaction, allowing multiple
SignableTransactions with the same output set to have different Eventualities.
In order to prevent triggering the burning bug, the RNG seed is hashed with
the planned-to-be-used inputs' output keys. While this does bind to them, it's
only loosely bound. The TX actually created may use different inputs entirely
if a forgery is crafted (which requires no brute forcing).
Binding to the key images would provide a strong binding, yet would require
knowing the key images, which requires active communication with the spend
key.
The purpose of this is so a multisig can identify if a Transaction the entire
group planned has been executed by a subset of the group or not. Once a plan
is created, it can have an Eventuality made. The Eventuality's extra is able
to be inserted into a HashMap, so all new on-chain transactions can be
trivially checked as potential candidates. Once a potential candidate is found,
a check involving ECC ops can be performed.
While this is arguably a DoS vector, the underlying Monero blockchain would
need to be spammed with transactions to trigger it. Accordingly, it becomes
a Monero blockchain DoS vector, when this code is written on the premise
of the Monero blockchain functioning. Accordingly, it is considered handled.
If a forgery does match, it must have created the exact same outputs the
multisig would've. Accordingly, it's argued the multisig shouldn't mind.
This entire suite of code is only necessary due to the lack of outgoing
view keys, yet it's able to avoid an interactive protocol to communicate
key images on every single received output.
While this could be locked to the multisig feature, there's no practical
benefit to doing so.
* Add support for encoding Monero address to instructions
* Move Serai's Monero address encoding into serai-client
serai-client is meant to be a single library enabling using Serai. While it was
originally written as an RPC client for Serai, apps actually using Serai will
primarily be sending transactions on connected networks. Sending those
transactions require proper {In, Out}Instructions, including proper address
encoding.
Not only has address encoding been moved, yet the subxt client is now behind
a feature. coin integrations have their own features, which are on by default.
primitives are always exposed.
* Reorganize file layout a bit, add feature flags to processor
* Tidy up ETH Dockerfile
* Add Bitcoin address encoding
* Move Bitcoin::Address to serai-client's
* Comment where tweaking needs to happen
* Add an API to check if a plan was completed in a specific TX
This allows any participating signer to submit the TX ID to prevent further
signing attempts.
Also performs some API cleanup.
* Minimize FROST dependencies
* Use a seeded RNG for key gen
* Tweak keys from Key gen
* Test proper usage of Branch/Change addresses
Adds a more descriptive error to an error case in decoys, and pads Monero
payments as needed.
* Also test spending the change output
* Add queued_plans to the Scheduler
queued_plans is for payments to be issued when an amount appears, yet the
amount is currently pre-fee. One the output is actually created, the
Scheduler should be notified of the amount it was created with, moving from
queued_plans to plans under the actual amount.
Also tightens debug_asserts to asserts for invariants which may are at risk of
being exclusive to prod.
* Add missing tweak_keys call
* Correct decoy selection height handling
* Add a few log statements to the scheduler
* Simplify test's get_block_number
* Simplify, while making more robust, branch address handling in Scheduler
* Have fees deducted from payments
Corrects Monero's handling of fees when there's no change address.
Adds a DUST variable, as needed due to 1_00_000_000 not being enough to pay
its fee on Monero.
* Add comment to Monero
* Consolidate BTC/XMR prepare_send code
These aren't fully consolidated. We'd need a SignableTransaction trait for
that. This is a lot cleaner though.
* Ban integrated addresses
The reasoning why is accordingly documented.
* Tidy TODOs/dust handling
* Update README TODO
* Use a determinisitic protocol version in Monero
* Test rebuilt KeyGen machines function as expected
* Use a more robust KeyGen entropy system
* Add DB TXNs
Also load entropy from env
* Add a loop for processing messages from substrate
Allows detecting if we're behind, and if so, waiting to handle the message
* Set Monero MAX_INPUTS properly
The previous number was based on an old hard fork. With the ring size having
increased, transactions have since got larger.
* Distinguish TODOs into TODO and TODO2s
TODO2s are for after protonet
* Zeroize secret share repr in ThresholdCore write
* Work on Eventualities
Adds serialization and stops signing when an eventuality is proven.
* Use a more robust DB key schema
* Update to {k, p}256 0.12
* cargo +nightly clippy
* cargo update
* Slight message-box tweaks
* Update to recent Monero merge
* Add a Coordinator trait for communication with coordinator
* Remove KeyGenHandle for just KeyGen
While KeyGen previously accepted instructions over a channel, this breaks the
ack flow needed for coordinator communication. Now, KeyGen is the direct object
with a handle() function for messages.
Thankfully, this ended up being rather trivial for KeyGen as it has no
background tasks.
* Add a handle function to Signer
Enables determining when it's finished handling a CoordinatorMessage and
therefore creating an acknowledgement.
* Save transactions used to complete eventualities
* Use a more intelligent sleep in the signer
* Emit SignedTransaction with the first ID *we can still get from our node*
* Move Substrate message handling into the new coordinator recv loop
* Add handle function to Scanner
* Remove the plans timer
Enables ensuring the ordring on the handling of plans.
* Remove the outputs function which panicked if a precondition wasn't met
The new API only returns outputs upon satisfaction of the precondition.
* Convert SignerOrder::SignTransaction to a function
* Remove the key_gen object from sign_plans
* Refactor out get_fee/prepare_send into dedicated functions
* Save plans being signed to the DB
* Reload transactions being signed on boot
* Stop reloading TXs being signed (and report it to peers)
* Remove message-box from the processor branch
We don't use it here yet.
* cargo +nightly fmt
* Move back common/zalloc
* Update subxt to 0.27
* Zeroize ^1.5, not 1
* Update GitHub workflow
* Remove usage of SignId in completed
2023-03-16 22:59:40 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Sort the inputs by their key images
|
|
|
|
|
let mut sorted_inputs = self.inputs.into_iter().zip(key_images).collect::<Vec<_>>();
|
|
|
|
|
sorted_inputs
|
|
|
|
|
.sort_by(|(_, key_image_a), (_, key_image_b)| key_image_sort(key_image_a, key_image_b));
|
|
|
|
|
|
|
|
|
|
self.inputs = Vec::with_capacity(sorted_inputs.len());
|
|
|
|
|
let mut key_images = Vec::with_capacity(sorted_inputs.len());
|
|
|
|
|
for (input, key_image) in sorted_inputs {
|
|
|
|
|
self.inputs.push(input);
|
|
|
|
|
key_images.push(key_image);
|
Processor (#259)
* Initial work on a message box
* Finish message-box (untested)
* Expand documentation
* Embed the recipient in the signature challenge
Prevents a message from A -> B from being read as from A -> C.
* Update documentation by bifurcating sender/receiver
* Panic on receiving an invalid signature
If we've received an invalid signature in an authenticated system, a
service is malicious, critically faulty (equivalent to malicious), or
the message layer has been compromised (or is otherwise critically
faulty).
Please note a receiver who handles a message they shouldn't will trigger
this. That falls under being critically faulty.
* Documentation and helper methods
SecureMessage::new and SecureMessage::serialize.
Secure Debug for MessageBox.
* Have SecureMessage not be serialized by default
Allows passing around in-memory, if desired, and moves the error from
decrypt to new (which performs deserialization).
Decrypt no longer has an error since it panics if given an invalid
signature, due to this being intranet code.
* Explain and improve nonce handling
Includes a missing zeroize call.
* Rebase to latest develop
Updates to transcript 0.2.0.
* Add a test for the MessageBox
* Export PrivateKey and PublicKey
* Also test serialization
* Add a key_gen binary to message_box
* Have SecureMessage support Serde
* Add encrypt_to_bytes and decrypt_from_bytes
* Support String ser via base64
* Rename encrypt/decrypt to encrypt_bytes/decrypt_to_bytes
* Directly operate with values supporting Borsh
* Use bincode instead of Borsh
By staying inside of serde, we'll support many more structs. While
bincode isn't canonical, we don't need canonicity on an authenticated,
internal system.
* Turn PrivateKey, PublicKey into structs
Uses Zeroizing for the PrivateKey per #150.
* from_string functions intended for loading from an env
* Use &str for PublicKey from_string (now from_str)
The PrivateKey takes the String to take ownership of its memory and
zeroize it. That isn't needed with PublicKeys.
* Finish updating from develop
* Resolve warning
* Use ZeroizingAlloc on the key_gen binary
* Move message-box from crypto/ to common/
* Move key serialization functions to ser
* add/remove functions in MessageBox
* Implement Hash on dalek_ff_group Points
* Make MessageBox generic to its key
Exposes a &'static str variant for internal use and a RistrettoPoint
variant for external use.
* Add Private to_string as deprecated
Stub before more competent tooling is deployed.
* Private to_public
* Test both Internal and External MessageBox, only use PublicKey in the pub API
* Remove panics on invalid signatures
Leftover from when this was solely internal which is now unsafe.
* Chicken scratch a Scanner task
* Add a write function to the DKG library
Enables writing directly to a file.
Also modifies serialize to return Zeroizing<Vec<u8>> instead of just Vec<u8>.
* Make dkg::encryption pub
* Remove encryption from MessageBox
* Use a 64-bit block number in Substrate
We use a 64-bit block number in general since u32 only works for 120 years
(with a 1 second block time). As some chains even push the 1 second threshold,
especially ones based on DAG consensus, this becomes potentially as low as 60
years.
While that should still be plenty, it's not worth wondering/debating. Since
Serai uses 64-bit block numbers elsewhere, this ensures consistency.
* Misc crypto lints
* Get the scanner scratch to compile
* Initial scanner test
* First few lines of scheduler
* Further work on scheduler, solidify API
* Define Scheduler TX format
* Branch creation algorithm
* Document when the branch algorithm isn't perfect
* Only scanned confirmed blocks
* Document Coin
* Remove Canonical/ChainNumber from processor
The processor should be abstracted from canonical numbers thanks to the
coordinator, making this unnecessary.
* Add README documenting processor flow
* Use Zeroize on substrate primitives
* Define messages from/to the processor
* Correct over-specified versioning
* Correct build re: in_instructions::primitives
* Debug/some serde in crypto/
* Use a struct for ValidatorSetInstance
* Add a processor key_gen task
Redos DB handling code.
* Replace trait + impl with wrapper struct
* Add a key confirmation flow to the key gen task
* Document concerns on key_gen
* Start on a signer task
* Add Send to FROST traits
* Move processor lib.rs to main.rs
Adds a dummy main to reduce clippy dead_code warnings.
* Further flesh out main.rs
* Move the DB trait to AsRef<[u8]>
* Signer task
* Remove a panic in bitcoin when there's insufficient funds
Unchecked underflow.
* Have Monero's mine_block mine one block, not 10
It was initially a nicety to deal with the 10 block lock. C::CONFIRMATIONS
should be used for that instead.
* Test signer
* Replace channel expects with log statements
The expects weren't problematic and had nicer code. They just clutter test
output.
* Remove the old wallet file
It predates the coordinator design and shouldn't be used.
* Rename tests/scan.rs to tests/scanner.rs
* Add a wallet test
Complements the recently removed wallet file by adding a test for the scanner,
scheduler, and signer together.
* Work on a run function
Triggers a clippy ICE.
* Resolve clippy ICE
The issue was the non-fully specified lambda in signer.
* Add KeyGenEvent and KeyGenOrder
Needed so we get KeyConfirmed messages from the key gen task.
While we could've read the CoordinatorMessage to see that, routing through the
key gen tasks ensures we only handle it once it's been successfully saved to
disk.
* Expand scanner test
* Clarify processor documentation
* Have the Scanner load keys on boot/save outputs to disk
* Use Vec<u8> for Block ID
Much more flexible.
* Panic if we see the same output multiple times
* Have the Scanner DB mark itself as corrupt when doing a multi-put
This REALLY should be a TX. Since we don't have a TX API right now, this at
least offers detection.
* Have DST'd DB keys accept AsRef<[u8]>
* Restore polling all signers
Writes a custom future to do so.
Also loads signers on boot using what the scanner claims are active keys.
* Schedule OutInstructions
Adds a data field to Payment.
Also cleans some dead code.
* Panic if we create an invalid transaction
Saves the TX once it's successfully signed so if we do panic, we have a copy.
* Route coordinator messages to their respective signer
Requires adding key to the SignId.
* Send SignTransaction orders for all plans
* Add a timer to retry sign_plans when prepare_send fails
* Minor fmt'ing
* Basic Fee API
* Move the change key into Plan
* Properly route activation_number
* Remove ScannerEvent::Block
It's not used under current designs
* Nicen logs
* Add utilities to get a block's number
* Have main issue AckBlock
Also has a few misc lints.
* Parse instructions out of outputs
* Tweak TODOs and remove an unwrap
* Update Bitcoin max input/output quantity
* Only read one piece of data from Monero
Due to output randomization, it's infeasible.
* Embed plan IDs into the TXs they create
We need to stop attempting signing if we've already signed a protocol. Ideally,
any one of the participating signers should be able to provide a proof the TX
was successfully signed. We can't just run a second signing protocol though as
a single malicious signer could complete the TX signature, and publish it,
yet not complete the secondary signature.
The TX itself has to be sufficient to show that the TX matches the plan. This
is done by embedding the ID, so matching addresses/amounts plans are
distinguished, and by allowing verification a TX actually matches a set of
addresses/amounts.
For Monero, this will need augmenting with the ephemeral keys (or usage of a
static seed for them).
* Don't use OP_RETURN to encode the plan ID on Bitcoin
We can use the inputs to distinguih identical-output plans without issue.
* Update OP_RETURN data access
It's not required to be the last output.
* Add Eventualities to Monero
An Eventuality is an effective equivalent to a SignableTransaction. That is
declared not by the inputs it spends, yet the outputs it creates.
Eventualities are also bound to a 32-byte RNG seed, enabling usage of a
hash-based identifier in a SignableTransaction, allowing multiple
SignableTransactions with the same output set to have different Eventualities.
In order to prevent triggering the burning bug, the RNG seed is hashed with
the planned-to-be-used inputs' output keys. While this does bind to them, it's
only loosely bound. The TX actually created may use different inputs entirely
if a forgery is crafted (which requires no brute forcing).
Binding to the key images would provide a strong binding, yet would require
knowing the key images, which requires active communication with the spend
key.
The purpose of this is so a multisig can identify if a Transaction the entire
group planned has been executed by a subset of the group or not. Once a plan
is created, it can have an Eventuality made. The Eventuality's extra is able
to be inserted into a HashMap, so all new on-chain transactions can be
trivially checked as potential candidates. Once a potential candidate is found,
a check involving ECC ops can be performed.
While this is arguably a DoS vector, the underlying Monero blockchain would
need to be spammed with transactions to trigger it. Accordingly, it becomes
a Monero blockchain DoS vector, when this code is written on the premise
of the Monero blockchain functioning. Accordingly, it is considered handled.
If a forgery does match, it must have created the exact same outputs the
multisig would've. Accordingly, it's argued the multisig shouldn't mind.
This entire suite of code is only necessary due to the lack of outgoing
view keys, yet it's able to avoid an interactive protocol to communicate
key images on every single received output.
While this could be locked to the multisig feature, there's no practical
benefit to doing so.
* Add support for encoding Monero address to instructions
* Move Serai's Monero address encoding into serai-client
serai-client is meant to be a single library enabling using Serai. While it was
originally written as an RPC client for Serai, apps actually using Serai will
primarily be sending transactions on connected networks. Sending those
transactions require proper {In, Out}Instructions, including proper address
encoding.
Not only has address encoding been moved, yet the subxt client is now behind
a feature. coin integrations have their own features, which are on by default.
primitives are always exposed.
* Reorganize file layout a bit, add feature flags to processor
* Tidy up ETH Dockerfile
* Add Bitcoin address encoding
* Move Bitcoin::Address to serai-client's
* Comment where tweaking needs to happen
* Add an API to check if a plan was completed in a specific TX
This allows any participating signer to submit the TX ID to prevent further
signing attempts.
Also performs some API cleanup.
* Minimize FROST dependencies
* Use a seeded RNG for key gen
* Tweak keys from Key gen
* Test proper usage of Branch/Change addresses
Adds a more descriptive error to an error case in decoys, and pads Monero
payments as needed.
* Also test spending the change output
* Add queued_plans to the Scheduler
queued_plans is for payments to be issued when an amount appears, yet the
amount is currently pre-fee. One the output is actually created, the
Scheduler should be notified of the amount it was created with, moving from
queued_plans to plans under the actual amount.
Also tightens debug_asserts to asserts for invariants which may are at risk of
being exclusive to prod.
* Add missing tweak_keys call
* Correct decoy selection height handling
* Add a few log statements to the scheduler
* Simplify test's get_block_number
* Simplify, while making more robust, branch address handling in Scheduler
* Have fees deducted from payments
Corrects Monero's handling of fees when there's no change address.
Adds a DUST variable, as needed due to 1_00_000_000 not being enough to pay
its fee on Monero.
* Add comment to Monero
* Consolidate BTC/XMR prepare_send code
These aren't fully consolidated. We'd need a SignableTransaction trait for
that. This is a lot cleaner though.
* Ban integrated addresses
The reasoning why is accordingly documented.
* Tidy TODOs/dust handling
* Update README TODO
* Use a determinisitic protocol version in Monero
* Test rebuilt KeyGen machines function as expected
* Use a more robust KeyGen entropy system
* Add DB TXNs
Also load entropy from env
* Add a loop for processing messages from substrate
Allows detecting if we're behind, and if so, waiting to handle the message
* Set Monero MAX_INPUTS properly
The previous number was based on an old hard fork. With the ring size having
increased, transactions have since got larger.
* Distinguish TODOs into TODO and TODO2s
TODO2s are for after protonet
* Zeroize secret share repr in ThresholdCore write
* Work on Eventualities
Adds serialization and stops signing when an eventuality is proven.
* Use a more robust DB key schema
* Update to {k, p}256 0.12
* cargo +nightly clippy
* cargo update
* Slight message-box tweaks
* Update to recent Monero merge
* Add a Coordinator trait for communication with coordinator
* Remove KeyGenHandle for just KeyGen
While KeyGen previously accepted instructions over a channel, this breaks the
ack flow needed for coordinator communication. Now, KeyGen is the direct object
with a handle() function for messages.
Thankfully, this ended up being rather trivial for KeyGen as it has no
background tasks.
* Add a handle function to Signer
Enables determining when it's finished handling a CoordinatorMessage and
therefore creating an acknowledgement.
* Save transactions used to complete eventualities
* Use a more intelligent sleep in the signer
* Emit SignedTransaction with the first ID *we can still get from our node*
* Move Substrate message handling into the new coordinator recv loop
* Add handle function to Scanner
* Remove the plans timer
Enables ensuring the ordring on the handling of plans.
* Remove the outputs function which panicked if a precondition wasn't met
The new API only returns outputs upon satisfaction of the precondition.
* Convert SignerOrder::SignTransaction to a function
* Remove the key_gen object from sign_plans
* Refactor out get_fee/prepare_send into dedicated functions
* Save plans being signed to the DB
* Reload transactions being signed on boot
* Stop reloading TXs being signed (and report it to peers)
* Remove message-box from the processor branch
We don't use it here yet.
* cargo +nightly fmt
* Move back common/zalloc
* Update subxt to 0.27
* Zeroize ^1.5, not 1
* Update GitHub workflow
* Remove usage of SignId in completed
2023-03-16 22:59:40 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
SignableTransactionWithKeyImages { intent: self, key_images }
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
pub fn sign(
|
|
|
|
|
self,
|
|
|
|
|
rng: &mut (impl RngCore + CryptoRng),
|
|
|
|
|
sender_spend_key: &Zeroizing<Scalar>,
|
|
|
|
|
) -> Result<Transaction, SendError> {
|
|
|
|
|
// Calculate the key images
|
|
|
|
|
let mut key_images = vec![];
|
2023-07-19 12:06:05 -07:00
|
|
|
for (input, _) in &self.inputs {
|
2024-06-27 07:36:45 -04:00
|
|
|
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
|
|
|
|
if (input_key.deref() * ED25519_BASEPOINT_TABLE) != input.key() {
|
|
|
|
|
Err(SendError::WrongPrivateKey)?;
|
2022-06-09 04:05:57 -04:00
|
|
|
}
|
2024-06-27 07:36:45 -04:00
|
|
|
let key_image = input_key.deref() * hash_to_point(input.key().compress().to_bytes());
|
|
|
|
|
key_images.push(key_image);
|
2022-05-21 21:44:57 -04:00
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Convert to a SignableTransactionWithKeyImages
|
|
|
|
|
let tx = self.with_key_images(key_images);
|
|
|
|
|
|
|
|
|
|
// Prepare the CLSAG signatures
|
|
|
|
|
let mut clsag_signs = Vec::with_capacity(tx.intent.inputs.len());
|
|
|
|
|
for (input, decoys) in &tx.intent.inputs {
|
|
|
|
|
// Re-derive the input key as this will be in a different order
|
|
|
|
|
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
|
|
|
|
clsag_signs.push((
|
|
|
|
|
input_key,
|
|
|
|
|
ClsagContext::new(decoys.clone(), input.commitment().clone())
|
|
|
|
|
.map_err(SendError::ClsagError)?,
|
|
|
|
|
));
|
|
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Get the output commitments' mask sum
|
2024-06-28 13:47:03 -04:00
|
|
|
let mask_sum = tx.intent.sum_output_masks(&tx.key_images);
|
2024-06-27 07:36:45 -04:00
|
|
|
|
|
|
|
|
// Get the actual TX, just needing the CLSAGs
|
2024-06-28 05:25:02 -04:00
|
|
|
let mut tx = tx.transaction_without_signatures();
|
2024-06-27 07:36:45 -04:00
|
|
|
|
|
|
|
|
// Sign the CLSAGs
|
|
|
|
|
let clsags_and_pseudo_outs =
|
|
|
|
|
Clsag::sign(rng, clsag_signs, mask_sum, tx.signature_hash().unwrap())
|
|
|
|
|
.map_err(SendError::ClsagError)?;
|
|
|
|
|
|
|
|
|
|
// Fill in the CLSAGs/pseudo-outs
|
|
|
|
|
let inputs_len = tx.prefix().inputs.len();
|
|
|
|
|
let Transaction::V2 {
|
|
|
|
|
proofs:
|
|
|
|
|
Some(RctProofs {
|
|
|
|
|
prunable: RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. },
|
|
|
|
|
..
|
|
|
|
|
}),
|
|
|
|
|
..
|
|
|
|
|
} = tx
|
|
|
|
|
else {
|
|
|
|
|
panic!("not signing clsag?")
|
2024-06-23 10:08:51 -04:00
|
|
|
};
|
2024-06-27 07:36:45 -04:00
|
|
|
*clsags = Vec::with_capacity(inputs_len);
|
|
|
|
|
*pseudo_outs = Vec::with_capacity(inputs_len);
|
|
|
|
|
for (clsag, pseudo_out) in clsags_and_pseudo_outs {
|
|
|
|
|
clsags.push(clsag);
|
|
|
|
|
pseudo_outs.push(pseudo_out);
|
2023-11-29 00:24:37 -05:00
|
|
|
}
|
2023-07-19 12:06:05 -07:00
|
|
|
|
2024-06-27 07:36:45 -04:00
|
|
|
// Return the signed TX
|
2022-05-21 15:33:35 -04:00
|
|
|
Ok(tx)
|
|
|
|
|
}
|
|
|
|
|
}
|