Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/
- Node RPC: new methods added - `chainstate_tokens_info`, `chainstate_orders_info_by_currencies`.

- Wallet RPC:
- new methods added: `node_get_tokens_info`, `order_list_own`, `order_list_all_active`.
- new methods added: `node_get_tokens_info`, `order_list_own`, `order_list_all_active`, `utxo_spend`.
- new value `ledger` in the `hardware_wallet` option for `wallet_create`, `wallet_recover` and `wallet_open` methods.

- Wallet CLI:
- the commands `order-create`, `order-fill`, `order-freeze`, `order-conclude` were added,
mirroring their existing RPC counterparts;
- other new commands added: `order-list-own`, `order-list-all-active`;
- the commands `order-create`, `order-fill`, `order-freeze`, `order-conclude`, `htlc-create-transaction` were added,
mirroring their existing RPC counterparts.
- other new commands added: `order-list-own`, `order-list-all-active`, `utxo-spend`, `htlc-generate-secret`,
`htlc-calc-secret-hash`.
- `wallet-create`/`wallet-recover`/`wallet-open` support the `ledger` subcommand, in addition to the existing
`software` and `trezor`, which specifies the type of the wallet to operate on.

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions common/src/chain/transaction/output/htlc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use hex::FromHex as _;

use crypto::hash::{self, hash};
use randomness::Rng;
use randomness::{CryptoRng, Rng};
use serialization::{Decode, Encode};

use super::{timelock::OutputTimeLock, Destination};
Expand Down Expand Up @@ -47,7 +47,7 @@ impl HtlcSecret {
Self { secret }
}

pub fn new_from_rng(rng: &mut impl Rng) -> Self {
pub fn new_from_rng(rng: &mut (impl Rng + CryptoRng)) -> Self {
let secret: [u8; HTLC_SECRET_SIZE] = std::array::from_fn(|_| rng.gen::<u8>());
Self { secret }
}
Expand Down
56 changes: 56 additions & 0 deletions common/src/primitives/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ impl Time {
self.time.saturating_sub(t.time)
}

/// Convert this `Time` instance to `chrono::DateTime`.
///
/// This may fail if the time is too far into the future.
pub fn as_absolute_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
TryInto::<i64>::try_into(self.time.as_secs()).ok().and_then(|secs| {
// Note: chrono::DateTime supports time values up to about 262,000 years away
Expand All @@ -118,6 +121,19 @@ impl Time {
chrono::Utc.timestamp_opt(secs, self.time.subsec_nanos()).single()
})
}

/// Create a new `Time` instance from `chrono::DateTime`.
///
/// This will fail if the date-time is before the Unix epoch or if it doesn't represent
/// a whole number of seconds.
pub fn from_absolute_time(time: &chrono::DateTime<chrono::Utc>) -> Option<Self> {
if time.timestamp_subsec_nanos() != 0 {
return None;
}

let seconds: u64 = time.timestamp().try_into().ok()?;
Some(Self::from_secs_since_epoch(seconds))
}
}

impl std::ops::Add<Duration> for Time {
Expand Down Expand Up @@ -173,7 +189,11 @@ impl Display for Time {

#[cfg(test)]
mod tests {
use rstest::rstest;

use logging::log;
use randomness::Rng as _;
use test_utils::random::{make_seedable_rng, Seed};

use super::*;

Expand Down Expand Up @@ -243,4 +263,40 @@ mod tests {
let s = format!("{t}");
assert_eq!(s, "18446744073709551615.999999999s since Unix epoch");
}

#[rstest]
#[trace]
#[case(Seed::from_entropy())]
fn from_to_absolute_time(#[case] seed: Seed) {
use chrono::prelude::*;

let mut rng = make_seedable_rng(seed);

for _ in 0..10 {
let year = rng.gen_range(1970..=3000);
let month = rng.gen_range(1..=12);
let days_in_month =
NaiveDate::from_ymd_opt(year, month, 1).unwrap().num_days_in_month();
let day = rng.gen_range(1..=days_in_month);
let hour = rng.gen_range(0..24);
let min = rng.gen_range(0..60);
let sec = rng.gen_range(0..60);
let abs_time = DateTime::from_naive_utc_and_offset(
NaiveDateTime::new(
NaiveDate::from_ymd_opt(year, month, day.into()).unwrap(),
NaiveTime::from_hms_opt(hour, min, sec).unwrap(),
),
Utc,
);

let time = Time::from_absolute_time(&abs_time).unwrap();
let abs_time_from_conversion = time.as_absolute_time().unwrap();
assert_eq!(abs_time_from_conversion, abs_time);

// Only a whole number of seconds should be allowed.
let millis = rng.gen_range(1..1000);
let abs_time_with_millis = abs_time + Duration::from_millis(millis);
assert!(Time::from_absolute_time(&abs_time_with_millis).is_none());
}
}
}
104 changes: 86 additions & 18 deletions common/src/size_estimation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,21 @@ use serialization::{CompactLen, Encode};

use crate::chain::{
classic_multisig::ClassicMultisigChallenge,
htlc::{HtlcSecret, HTLC_SECRET_SIZE},
signature::{
inputsig::{
authorize_hashed_timelock_contract_spend::{
AuthorizedHashedTimelockContractSpend, AuthorizedHashedTimelockContractSpendTag,
},
authorize_pubkey_spend::AuthorizedPublicKeySpend,
authorize_pubkeyhash_spend::AuthorizedPublicKeyHashSpend,
classical_multisig::authorize_classical_multisig::AuthorizedClassicalMultisigSpend,
standard_signature::StandardInputSignature, InputWitness,
standard_signature::StandardInputSignature,
InputWitness,
},
sighash::sighashtype::SigHashType,
},
Destination, SignedTransaction, Transaction, TxOutput,
Destination, SignedTransaction, Transaction, TxInput, TxOutput,
};

/// Wallet errors
Expand All @@ -51,10 +56,11 @@ pub enum SizeEstimationError {
/// provided destination.
pub fn input_signature_size(
txo: &TxOutput,
htlc_spend_tag: Option<AuthorizedHashedTimelockContractSpendTag>,
dest_info_provider: Option<&dyn DestinationInfoProvider>,
) -> Result<usize, SizeEstimationError> {
get_tx_output_destination(txo).map_or(Ok(0), |dest| {
input_signature_size_from_destination(dest, dest_info_provider)
input_signature_size_from_destination(dest, htlc_spend_tag, dest_info_provider)
})
}

Expand Down Expand Up @@ -107,9 +113,7 @@ lazy_static::lazy_static! {
static ref NO_SIGNATURE_SIZE: usize = {
InputWitness::NoSignature(None).encoded_size()
};
}

lazy_static::lazy_static! {
static ref PUB_KEY_SIGNATURE_SIZE: usize = {
let raw_signature =
AuthorizedPublicKeySpend::new(BOGUS_KEY_PAIR_AND_SIGNATURE.2.clone()).encode();
Expand All @@ -119,9 +123,7 @@ lazy_static::lazy_static! {
);
InputWitness::Standard(standard).encoded_size()
};
}

lazy_static::lazy_static! {
static ref ADDRESS_SIGNATURE_SIZE: usize = {
let raw_signature = AuthorizedPublicKeyHashSpend::new(
BOGUS_KEY_PAIR_AND_SIGNATURE.1.clone(),
Expand All @@ -134,6 +136,36 @@ lazy_static::lazy_static! {
);
InputWitness::Standard(standard).encoded_size()
};

static ref HTLC_SPEND_SIGNATURE_OVERHEAD: usize = {
let pkh_spend_encoded = AuthorizedPublicKeyHashSpend::new(
BOGUS_KEY_PAIR_AND_SIGNATURE.1.clone(),
BOGUS_KEY_PAIR_AND_SIGNATURE.2.clone(),
)
.encode();
let pkh_spend_encoded_size = pkh_spend_encoded.len();

let htlc_spend = AuthorizedHashedTimelockContractSpend::Spend(
HtlcSecret::new([0; HTLC_SECRET_SIZE]), pkh_spend_encoded
);
let htlc_spend_size = htlc_spend.encoded_size();

htlc_spend_size.checked_sub(pkh_spend_encoded_size).expect("HTLC spend size is known to be bigger")
};

static ref HTLC_REFUND_SIGNATURE_OVERHEAD: usize = {
let pkh_spend_encoded = AuthorizedPublicKeyHashSpend::new(
BOGUS_KEY_PAIR_AND_SIGNATURE.1.clone(),
BOGUS_KEY_PAIR_AND_SIGNATURE.2.clone(),
)
.encode();
let pkh_spend_encoded_size = pkh_spend_encoded.len();

let htlc_spend = AuthorizedHashedTimelockContractSpend::Refund(pkh_spend_encoded);
let htlc_spend_size = htlc_spend.encoded_size();

htlc_spend_size.checked_sub(pkh_spend_encoded_size).expect("HTLC spend size is known to be bigger")
};
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
Expand Down Expand Up @@ -177,21 +209,36 @@ pub trait DestinationInfoProvider {
/// provided destination.
pub fn input_signature_size_from_destination(
destination: &Destination,
htlc_spend_tag: Option<AuthorizedHashedTimelockContractSpendTag>,
dest_info_provider: Option<&dyn DestinationInfoProvider>,
) -> Result<usize, SizeEstimationError> {
// Sizes calculated upfront
match destination {
Destination::PublicKeyHash(_) => Ok(*ADDRESS_SIGNATURE_SIZE),
Destination::PublicKey(_) => Ok(*PUB_KEY_SIGNATURE_SIZE),
Destination::AnyoneCanSpend => Ok(*NO_SIGNATURE_SIZE),
Destination::ScriptHash(_) => Err(SizeEstimationError::UnsupportedInputDestination(
destination.clone(),
)),
let size = match destination {
Destination::PublicKeyHash(_) => *ADDRESS_SIGNATURE_SIZE,
Destination::PublicKey(_) => *PUB_KEY_SIGNATURE_SIZE,
Destination::AnyoneCanSpend => *NO_SIGNATURE_SIZE,
Destination::ScriptHash(_) => {
return Err(SizeEstimationError::UnsupportedInputDestination(
destination.clone(),
));
}
Destination::ClassicMultisig(_) => dest_info_provider
.and_then(|dest_info_provider| dest_info_provider.get_multisig_info(destination))
.map(multisig_signature_size)
.ok_or_else(|| SizeEstimationError::UnsupportedInputDestination(destination.clone())),
}
.ok_or_else(|| SizeEstimationError::UnsupportedInputDestination(destination.clone()))?,
};

let adjusted_size = match htlc_spend_tag {
None => size,
Some(AuthorizedHashedTimelockContractSpendTag::Spend) => {
size + *HTLC_SPEND_SIGNATURE_OVERHEAD
}
Some(AuthorizedHashedTimelockContractSpendTag::Refund) => {
size + *HTLC_REFUND_SIGNATURE_OVERHEAD
}
};

Ok(adjusted_size)
}

/// Return the encoded size for a SignedTransaction also accounting for the compact encoding of the
Expand Down Expand Up @@ -232,8 +279,29 @@ pub fn tx_size_with_num_inputs_and_outputs(
Ok(*EMPTY_SIGNED_TX_SIZE + output_compact_size_diff + (input_compact_size_diff * 2))
}

pub fn outputs_encoded_size(outputs: &[TxOutput]) -> usize {
outputs.iter().map(serialization::Encode::encoded_size).sum()
pub fn outputs_encoded_size<'a>(outputs: impl IntoIterator<Item = &'a TxOutput>) -> usize {
outputs.into_iter().map(serialization::Encode::encoded_size).sum()
}

pub fn inputs_encoded_size<'a>(inputs: impl IntoIterator<Item = &'a TxInput>) -> usize {
inputs.into_iter().map(serialization::Encode::encoded_size).sum()
}

pub fn input_signatures_size_from_destinations<'a>(
destinations: impl IntoIterator<
Item = (
&'a Destination,
Option<AuthorizedHashedTimelockContractSpendTag>,
),
>,
dest_info_provider: Option<&dyn DestinationInfoProvider>,
) -> Result<usize, SizeEstimationError> {
destinations
.into_iter()
.map(|(dest, htlc_spend_tag)| {
input_signature_size_from_destination(dest, htlc_spend_tag, dest_info_provider)
})
.sum()
}

fn get_tx_output_destination(txo: &TxOutput) -> Option<&Destination> {
Expand Down
Loading