Skip to content
Merged
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
37 changes: 37 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/Dimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ export class Dimensions {
return new Dimensions(this._wasm.plus(other._wasm));
}

/**
* Multiply dimensions by a scalar
*/
times(n: number): Dimensions {
return new Dimensions(this._wasm.times(n));
}

/**
* Whether any inputs are segwit (affects overhead calculation)
*/
Expand All @@ -96,4 +103,34 @@ export class Dimensions {
getVSize(size: "min" | "max" = "max"): number {
return this._wasm.get_vsize(size);
}

/**
* Get input weight only (min or max)
* @param size - "min" or "max", defaults to "max"
*/
getInputWeight(size: "min" | "max" = "max"): number {
return this._wasm.get_input_weight(size);
}

/**
* Get input virtual size (min or max)
* @param size - "min" or "max", defaults to "max"
*/
getInputVSize(size: "min" | "max" = "max"): number {
return this._wasm.get_input_vsize(size);
}

/**
* Get output weight
*/
getOutputWeight(): number {
return this._wasm.get_output_weight();
}

/**
* Get output virtual size
*/
getOutputVSize(): number {
return this._wasm.get_output_vsize();
}
}
41 changes: 41 additions & 0 deletions packages/wasm-utxo/src/wasm/fixed_script_wallet/dimensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,16 @@ impl WasmDimensions {
}
}

/// Multiply dimensions by a scalar
pub fn times(&self, n: u32) -> WasmDimensions {
WasmDimensions {
input_weight_min: self.input_weight_min * n as usize,
input_weight_max: self.input_weight_max * n as usize,
output_weight: self.output_weight * n as usize,
has_segwit: self.has_segwit,
}
}

/// Whether any inputs are segwit (affects overhead calculation)
pub fn has_segwit(&self) -> bool {
self.has_segwit
Expand Down Expand Up @@ -501,4 +511,35 @@ impl WasmDimensions {
let weight = self.get_weight(size);
weight.div_ceil(4)
}

/// Get input weight only (min or max)
///
/// # Arguments
/// * `size` - "min" or "max", defaults to "max"
pub fn get_input_weight(&self, size: Option<String>) -> u32 {
let use_min = size.as_deref() == Some("min");
if use_min {
self.input_weight_min as u32
} else {
self.input_weight_max as u32
}
}

/// Get input virtual size (min or max)
///
/// # Arguments
/// * `size` - "min" or "max", defaults to "max"
pub fn get_input_vsize(&self, size: Option<String>) -> u32 {
self.get_input_weight(size).div_ceil(4)
}

/// Get output weight
pub fn get_output_weight(&self) -> u32 {
self.output_weight as u32
}

/// Get output virtual size
pub fn get_output_vsize(&self) -> u32 {
(self.output_weight as u32).div_ceil(4)
}
}
110 changes: 110 additions & 0 deletions packages/wasm-utxo/test/dimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,116 @@ describe("Dimensions", function () {
});
});

describe("times", function () {
it("should multiply dimensions by a scalar", function () {
const input = Dimensions.fromInput({ chain: 10 });
const doubled = input.times(2);

assert.strictEqual(doubled.getInputWeight("min"), input.getInputWeight("min") * 2);
assert.strictEqual(doubled.getInputWeight("max"), input.getInputWeight("max") * 2);
assert.strictEqual(doubled.hasSegwit, input.hasSegwit);
});

it("should return zero for times(0)", function () {
const input = Dimensions.fromInput({ chain: 10 });
const zeroed = input.times(0);

assert.strictEqual(zeroed.getInputWeight(), 0);
assert.strictEqual(zeroed.getOutputWeight(), 0);
});

it("should multiply outputs", function () {
const output = Dimensions.fromOutput(Buffer.alloc(34));
const tripled = output.times(3);

assert.strictEqual(tripled.getOutputWeight(), output.getOutputWeight() * 3);
});

it("times(1) should return equivalent dimensions", function () {
const dim = Dimensions.fromInput({ chain: 20 }).plus(Dimensions.fromOutput(Buffer.alloc(23)));
const same = dim.times(1);

assert.strictEqual(same.getInputWeight("min"), dim.getInputWeight("min"));
assert.strictEqual(same.getInputWeight("max"), dim.getInputWeight("max"));
assert.strictEqual(same.getOutputWeight(), dim.getOutputWeight());
});
});

describe("getInputWeight and getInputVSize", function () {
it("should return input weight only (no overhead)", function () {
const input = Dimensions.fromInput({ chain: 10 });
const inputWeight = input.getInputWeight();

// Input weight should be less than total weight (which includes overhead)
assert.ok(inputWeight < input.getWeight());
assert.ok(inputWeight > 0);
});

it("should return min/max input weights for ECDSA inputs", function () {
const input = Dimensions.fromInput({ chain: 10 });

// p2shP2wsh has ECDSA variance
assert.ok(input.getInputWeight("min") < input.getInputWeight("max"));
assert.ok(input.getInputVSize("min") < input.getInputVSize("max"));
});

it("should return equal min/max for Schnorr inputs", function () {
const input = Dimensions.fromInput({ chain: 40 });

// p2trMusig2 keypath has no variance
assert.strictEqual(input.getInputWeight("min"), input.getInputWeight("max"));
assert.strictEqual(input.getInputVSize("min"), input.getInputVSize("max"));
});

it("getInputVSize should be ceiling of weight/4", function () {
const input = Dimensions.fromInput({ chain: 20 });

assert.strictEqual(input.getInputVSize("min"), Math.ceil(input.getInputWeight("min") / 4));
assert.strictEqual(input.getInputVSize("max"), Math.ceil(input.getInputWeight("max") / 4));
});

it("should return zero for output-only dimensions", function () {
const output = Dimensions.fromOutput(Buffer.alloc(34));

assert.strictEqual(output.getInputWeight(), 0);
assert.strictEqual(output.getInputVSize(), 0);
});
});

describe("getOutputWeight and getOutputVSize", function () {
it("should return output weight only (no overhead)", function () {
const output = Dimensions.fromOutput(Buffer.alloc(23));
const outputWeight = output.getOutputWeight();

// Output weight = 4 * (8 + 1 + 23) = 128
assert.strictEqual(outputWeight, 128);
});

it("getOutputVSize should be ceiling of weight/4", function () {
const output = Dimensions.fromOutput(Buffer.alloc(23));

assert.strictEqual(output.getOutputVSize(), Math.ceil(output.getOutputWeight() / 4));
// 128 / 4 = 32
assert.strictEqual(output.getOutputVSize(), 32);
});

it("should return zero for input-only dimensions", function () {
const input = Dimensions.fromInput({ chain: 10 });

assert.strictEqual(input.getOutputWeight(), 0);
assert.strictEqual(input.getOutputVSize(), 0);
});

it("should combine correctly with plus", function () {
const input = Dimensions.fromInput({ chain: 10 });
const output = Dimensions.fromOutput(Buffer.alloc(34));
const combined = input.plus(output);

assert.strictEqual(combined.getInputWeight(), input.getInputWeight());
assert.strictEqual(combined.getOutputWeight(), output.getOutputWeight());
});
});

describe("integration tests with fixtures", function () {
// Zcash has additional transaction overhead (version group, expiry height, etc.)
// that we don't account for in Dimensions - skip it for now
Expand Down