Skip to content

Commit ea3caff

Browse files
Merge pull request #92 from BitGo/BTC-2908.update-dims
feat(wasm-utxo): add dimension calculation methods
2 parents 8d4f41d + b2ee2ec commit ea3caff

3 files changed

Lines changed: 188 additions & 0 deletions

File tree

packages/wasm-utxo/js/fixedScriptWallet/Dimensions.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ export class Dimensions {
7474
return new Dimensions(this._wasm.plus(other._wasm));
7575
}
7676

77+
/**
78+
* Multiply dimensions by a scalar
79+
*/
80+
times(n: number): Dimensions {
81+
return new Dimensions(this._wasm.times(n));
82+
}
83+
7784
/**
7885
* Whether any inputs are segwit (affects overhead calculation)
7986
*/
@@ -96,4 +103,34 @@ export class Dimensions {
96103
getVSize(size: "min" | "max" = "max"): number {
97104
return this._wasm.get_vsize(size);
98105
}
106+
107+
/**
108+
* Get input weight only (min or max)
109+
* @param size - "min" or "max", defaults to "max"
110+
*/
111+
getInputWeight(size: "min" | "max" = "max"): number {
112+
return this._wasm.get_input_weight(size);
113+
}
114+
115+
/**
116+
* Get input virtual size (min or max)
117+
* @param size - "min" or "max", defaults to "max"
118+
*/
119+
getInputVSize(size: "min" | "max" = "max"): number {
120+
return this._wasm.get_input_vsize(size);
121+
}
122+
123+
/**
124+
* Get output weight
125+
*/
126+
getOutputWeight(): number {
127+
return this._wasm.get_output_weight();
128+
}
129+
130+
/**
131+
* Get output virtual size
132+
*/
133+
getOutputVSize(): number {
134+
return this._wasm.get_output_vsize();
135+
}
99136
}

packages/wasm-utxo/src/wasm/fixed_script_wallet/dimensions.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,16 @@ impl WasmDimensions {
456456
}
457457
}
458458

459+
/// Multiply dimensions by a scalar
460+
pub fn times(&self, n: u32) -> WasmDimensions {
461+
WasmDimensions {
462+
input_weight_min: self.input_weight_min * n as usize,
463+
input_weight_max: self.input_weight_max * n as usize,
464+
output_weight: self.output_weight * n as usize,
465+
has_segwit: self.has_segwit,
466+
}
467+
}
468+
459469
/// Whether any inputs are segwit (affects overhead calculation)
460470
pub fn has_segwit(&self) -> bool {
461471
self.has_segwit
@@ -501,4 +511,35 @@ impl WasmDimensions {
501511
let weight = self.get_weight(size);
502512
weight.div_ceil(4)
503513
}
514+
515+
/// Get input weight only (min or max)
516+
///
517+
/// # Arguments
518+
/// * `size` - "min" or "max", defaults to "max"
519+
pub fn get_input_weight(&self, size: Option<String>) -> u32 {
520+
let use_min = size.as_deref() == Some("min");
521+
if use_min {
522+
self.input_weight_min as u32
523+
} else {
524+
self.input_weight_max as u32
525+
}
526+
}
527+
528+
/// Get input virtual size (min or max)
529+
///
530+
/// # Arguments
531+
/// * `size` - "min" or "max", defaults to "max"
532+
pub fn get_input_vsize(&self, size: Option<String>) -> u32 {
533+
self.get_input_weight(size).div_ceil(4)
534+
}
535+
536+
/// Get output weight
537+
pub fn get_output_weight(&self) -> u32 {
538+
self.output_weight as u32
539+
}
540+
541+
/// Get output virtual size
542+
pub fn get_output_vsize(&self) -> u32 {
543+
(self.output_weight as u32).div_ceil(4)
544+
}
504545
}

packages/wasm-utxo/test/dimensions.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,116 @@ describe("Dimensions", function () {
207207
});
208208
});
209209

210+
describe("times", function () {
211+
it("should multiply dimensions by a scalar", function () {
212+
const input = Dimensions.fromInput({ chain: 10 });
213+
const doubled = input.times(2);
214+
215+
assert.strictEqual(doubled.getInputWeight("min"), input.getInputWeight("min") * 2);
216+
assert.strictEqual(doubled.getInputWeight("max"), input.getInputWeight("max") * 2);
217+
assert.strictEqual(doubled.hasSegwit, input.hasSegwit);
218+
});
219+
220+
it("should return zero for times(0)", function () {
221+
const input = Dimensions.fromInput({ chain: 10 });
222+
const zeroed = input.times(0);
223+
224+
assert.strictEqual(zeroed.getInputWeight(), 0);
225+
assert.strictEqual(zeroed.getOutputWeight(), 0);
226+
});
227+
228+
it("should multiply outputs", function () {
229+
const output = Dimensions.fromOutput(Buffer.alloc(34));
230+
const tripled = output.times(3);
231+
232+
assert.strictEqual(tripled.getOutputWeight(), output.getOutputWeight() * 3);
233+
});
234+
235+
it("times(1) should return equivalent dimensions", function () {
236+
const dim = Dimensions.fromInput({ chain: 20 }).plus(Dimensions.fromOutput(Buffer.alloc(23)));
237+
const same = dim.times(1);
238+
239+
assert.strictEqual(same.getInputWeight("min"), dim.getInputWeight("min"));
240+
assert.strictEqual(same.getInputWeight("max"), dim.getInputWeight("max"));
241+
assert.strictEqual(same.getOutputWeight(), dim.getOutputWeight());
242+
});
243+
});
244+
245+
describe("getInputWeight and getInputVSize", function () {
246+
it("should return input weight only (no overhead)", function () {
247+
const input = Dimensions.fromInput({ chain: 10 });
248+
const inputWeight = input.getInputWeight();
249+
250+
// Input weight should be less than total weight (which includes overhead)
251+
assert.ok(inputWeight < input.getWeight());
252+
assert.ok(inputWeight > 0);
253+
});
254+
255+
it("should return min/max input weights for ECDSA inputs", function () {
256+
const input = Dimensions.fromInput({ chain: 10 });
257+
258+
// p2shP2wsh has ECDSA variance
259+
assert.ok(input.getInputWeight("min") < input.getInputWeight("max"));
260+
assert.ok(input.getInputVSize("min") < input.getInputVSize("max"));
261+
});
262+
263+
it("should return equal min/max for Schnorr inputs", function () {
264+
const input = Dimensions.fromInput({ chain: 40 });
265+
266+
// p2trMusig2 keypath has no variance
267+
assert.strictEqual(input.getInputWeight("min"), input.getInputWeight("max"));
268+
assert.strictEqual(input.getInputVSize("min"), input.getInputVSize("max"));
269+
});
270+
271+
it("getInputVSize should be ceiling of weight/4", function () {
272+
const input = Dimensions.fromInput({ chain: 20 });
273+
274+
assert.strictEqual(input.getInputVSize("min"), Math.ceil(input.getInputWeight("min") / 4));
275+
assert.strictEqual(input.getInputVSize("max"), Math.ceil(input.getInputWeight("max") / 4));
276+
});
277+
278+
it("should return zero for output-only dimensions", function () {
279+
const output = Dimensions.fromOutput(Buffer.alloc(34));
280+
281+
assert.strictEqual(output.getInputWeight(), 0);
282+
assert.strictEqual(output.getInputVSize(), 0);
283+
});
284+
});
285+
286+
describe("getOutputWeight and getOutputVSize", function () {
287+
it("should return output weight only (no overhead)", function () {
288+
const output = Dimensions.fromOutput(Buffer.alloc(23));
289+
const outputWeight = output.getOutputWeight();
290+
291+
// Output weight = 4 * (8 + 1 + 23) = 128
292+
assert.strictEqual(outputWeight, 128);
293+
});
294+
295+
it("getOutputVSize should be ceiling of weight/4", function () {
296+
const output = Dimensions.fromOutput(Buffer.alloc(23));
297+
298+
assert.strictEqual(output.getOutputVSize(), Math.ceil(output.getOutputWeight() / 4));
299+
// 128 / 4 = 32
300+
assert.strictEqual(output.getOutputVSize(), 32);
301+
});
302+
303+
it("should return zero for input-only dimensions", function () {
304+
const input = Dimensions.fromInput({ chain: 10 });
305+
306+
assert.strictEqual(input.getOutputWeight(), 0);
307+
assert.strictEqual(input.getOutputVSize(), 0);
308+
});
309+
310+
it("should combine correctly with plus", function () {
311+
const input = Dimensions.fromInput({ chain: 10 });
312+
const output = Dimensions.fromOutput(Buffer.alloc(34));
313+
const combined = input.plus(output);
314+
315+
assert.strictEqual(combined.getInputWeight(), input.getInputWeight());
316+
assert.strictEqual(combined.getOutputWeight(), output.getOutputWeight());
317+
});
318+
});
319+
210320
describe("integration tests with fixtures", function () {
211321
// Zcash has additional transaction overhead (version group, expiry height, etc.)
212322
// that we don't account for in Dimensions - skip it for now

0 commit comments

Comments
 (0)