Skip to content

Commit 50ffb8e

Browse files
committed
Setting Maximum threshold for obtaining plan
For the planning module we are considering that total possible ways to spend should always be less than 1000. This protects the network from any DDoS Attack if in case you receive a very large descriptor enough that it has 1000 possible spend paths. Computationally it would be a heavy thing to perform. Signed-off-by: Harshil Jani <[email protected]>
1 parent b55f478 commit 50ffb8e

File tree

6 files changed

+47
-28
lines changed

6 files changed

+47
-28
lines changed

src/descriptor/mod.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -549,33 +549,33 @@ impl Descriptor<DefiniteDescriptorKey> {
549549

550550
impl Descriptor<DescriptorPublicKey> {
551551
/// Count total possible assets for a given descriptor.
552-
pub fn count_assets(&self) -> u64 {
552+
pub fn count_assets(&self) -> u32 {
553553
match self {
554554
Descriptor::Bare(k) => k.as_inner().count_assets(),
555555
Descriptor::Pkh(_) => 1,
556556
Descriptor::Wpkh(_) => 1,
557557
Descriptor::Sh(k) => match k.as_inner() {
558558
ShInner::Wsh(k) => match k.as_inner() {
559559
WshInner::SortedMulti(k) => {
560-
let n = k.pks.len() as u64;
561-
let k = k.k as u64;
562-
k_of_n(k, n)
560+
let n = k.pks.len() as u32;
561+
let k = k.k as u32;
562+
k_of_n(k, n).unwrap()
563563
}
564564
WshInner::Ms(k) => k.count_assets(),
565565
},
566566
ShInner::Wpkh(_) => 1,
567567
ShInner::SortedMulti(k) => {
568-
let n = k.clone().pks.len() as u64;
569-
let k = k.clone().k as u64;
570-
k_of_n(k, n)
568+
let n = k.clone().pks.len() as u32;
569+
let k = k.clone().k as u32;
570+
k_of_n(k, n).unwrap()
571571
}
572572
ShInner::Ms(k) => k.count_assets(),
573573
},
574574
Descriptor::Wsh(k) => match k.as_inner() {
575575
WshInner::SortedMulti(k) => {
576-
let n = k.clone().pks.len() as u64;
577-
let k = k.clone().k as u64;
578-
k_of_n(k, n)
576+
let n = k.clone().pks.len() as u32;
577+
let k = k.clone().k as u32;
578+
k_of_n(k, n).unwrap()
579579
}
580580
WshInner::Ms(k) => k.count_assets(),
581581
},
@@ -595,6 +595,10 @@ impl Descriptor<DescriptorPublicKey> {
595595

596596
/// Get all possible assets for a given descriptor
597597
pub fn all_assets(&self) -> Result<Vec<Assets>, Error> {
598+
let threshold = self.count_assets();
599+
if threshold >= 1000 {
600+
return Err(Error::MaxAssetThresholdExceeded);
601+
}
598602
match self {
599603
Descriptor::Bare(k) => Ok(k.as_inner().all_assets()),
600604
Descriptor::Pkh(k) => {

src/descriptor/tr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl TapTree<DescriptorPublicKey> {
168168
}
169169

170170
/// Get total possible assets for TapTree
171-
pub fn count_assets(&self) -> u64 {
171+
pub fn count_assets(&self) -> u32 {
172172
match self {
173173
TapTree::Tree { left, right, height: _ } => {
174174
let a = left.count_assets();

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ pub enum Error {
509509
const MAX_RECURSION_DEPTH: u32 = 402;
510510
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
511511
const MAX_SCRIPT_SIZE: u32 = 10000;
512+
// For the planning module we are considering that total possible ways to spend
513+
// should be less than 1000
514+
const MAX_ASSET_THRESHOLD: u32 = 1000;
512515

513516
impl fmt::Display for Error {
514517
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

src/miniscript/astelem.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
598598

599599
impl<Ctx: ScriptContext> Terminal<DescriptorPublicKey, Ctx> {
600600
/// Count total possible assets
601-
pub fn count_assets(&self) -> u64 {
601+
pub fn count_assets(&self) -> u32 {
602602
match self {
603603
Terminal::True => 0,
604604
Terminal::False => 0,
@@ -642,17 +642,17 @@ impl<Ctx: ScriptContext> Terminal<DescriptorPublicKey, Ctx> {
642642
for ms in ms_v {
643643
count_array.push(ms.count_assets());
644644
}
645-
let products = get_combinations_product(&count_array, *k as u64);
646-
let mut total_count: u64 = 0;
645+
let products = get_combinations_product(&count_array, *k as u32);
646+
let mut total_count: u32 = 0;
647647
for product in products {
648648
total_count += product;
649649
}
650650
total_count
651651
}
652652
Terminal::Multi(k, dpk) | Terminal::MultiA(k, dpk) => {
653-
let k: u64 = *k as u64;
654-
let n: u64 = dpk.len() as u64;
655-
k_of_n(k, n)
653+
let k: u32 = *k as u32;
654+
let n: u32 = dpk.len() as u32;
655+
k_of_n(k, n).unwrap()
656656
}
657657
}
658658
}

src/miniscript/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ impl<Ctx: ScriptContext> Miniscript<DescriptorPublicKey, Ctx> {
391391
pub fn all_assets(&self) -> Vec<Assets> { self.node.all_assets() }
392392

393393
/// Get the total number of assets possible
394-
pub fn count_assets(&self) -> u64 { self.node.count_assets() }
394+
pub fn count_assets(&self) -> u32 { self.node.count_assets() }
395395
}
396396

397397
impl<Pk: MiniscriptKey, Ctx: ScriptContext> ForEachKey<Pk> for Miniscript<Pk, Ctx> {

src/util.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use crate::miniscript::context;
1010
use crate::miniscript::satisfy::Placeholder;
1111
use crate::plan::Assets;
1212
use crate::prelude::*;
13-
use crate::{DescriptorPublicKey, MiniscriptKey, ScriptContext, ToPublicKey};
13+
use crate::{
14+
DescriptorPublicKey, Error, MiniscriptKey, ScriptContext, ToPublicKey, MAX_ASSET_THRESHOLD,
15+
};
1416
pub(crate) fn varint_len(n: usize) -> usize { bitcoin::VarInt(n as u64).len() }
1517

1618
pub(crate) trait ItemSize {
@@ -56,7 +58,7 @@ pub(crate) fn witness_to_scriptsig(witness: &[Vec<u8>]) -> ScriptBuf {
5658
} else {
5759
let push = <&PushBytes>::try_from(wit.as_slice())
5860
.expect("All pushes in miniscript are <73 bytes");
59-
b = b.push_slice(push)
61+
b = b.push_slice(push);
6062
}
6163
}
6264
b.into_script()
@@ -129,12 +131,11 @@ pub fn combine_assets(
129131
combine_assets(k, dpk_v, index + 1, current_assets.clone(), all_assets);
130132
let mut new_asset = current_assets;
131133
new_asset = new_asset.add(dpk_v[index].clone());
132-
println!("{:#?}", new_asset);
133134
combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets)
134135
}
135136

136137
// Do product of K combinations
137-
pub fn get_combinations_product(values: &[u64], k: u64) -> Vec<u64> {
138+
pub fn get_combinations_product(values: &[u32], k: u32) -> Vec<u32> {
138139
let mut products = Vec::new();
139140
let n = values.len();
140141

@@ -145,10 +146,10 @@ pub fn get_combinations_product(values: &[u64], k: u64) -> Vec<u64> {
145146
// Using bitwise operations to generate combinations
146147
let max_combinations = 1u32 << n;
147148
for combination_bits in 1..max_combinations {
148-
if combination_bits.count_ones() as usize == k as usize {
149+
if (combination_bits.count_ones() as usize) == (k as usize) {
149150
let mut product = 1;
150151
for i in 0..n {
151-
if combination_bits & (1u32 << i) != 0 {
152+
if (combination_bits & (1u32 << i)) != 0 {
152153
product *= values[i];
153154
}
154155
}
@@ -160,9 +161,20 @@ pub fn get_combinations_product(values: &[u64], k: u64) -> Vec<u64> {
160161
}
161162

162163
// ways to select k things out of n
163-
pub fn k_of_n(k: u64, n: u64) -> u64 {
164-
if k == 0 || k == n {
165-
return 1;
164+
pub fn k_of_n(k: u32, n: u32) -> Result<u32, Error> {
165+
let mut k = k;
166+
if k > n - k {
167+
k = n - k;
166168
}
167-
k_of_n(k - 1, n - 1) + k_of_n(k, n - 1)
169+
170+
let mut result = 1;
171+
for i in 0..k {
172+
result *= n - i;
173+
result /= i + 1;
174+
if result > MAX_ASSET_THRESHOLD.into() {
175+
return Err(Error::MaxAssetThresholdExceeded);
176+
}
177+
}
178+
179+
Ok(result)
168180
}

0 commit comments

Comments
 (0)