Skip to content
This repository was archived by the owner on Oct 22, 2026. It is now read-only.

Commit ab32aa1

Browse files
committed
plan: test sig-bearing paths byte-exactly against a real satisfaction
A plan budgets every ECDSA signature at 73 bytes including its length prefix (the convention documented on types::extra_props::SatData), so a satisfier handing out 72-byte signatures (71-byte DER plus sighash flag, filling the budget exactly) lets wpkh, sh(wpkh), wsh and sh(wsh) plans be held to the exact wire size of a real satisfaction, not just the sigless paths. Assisted-by: Kimi kimi-latest
1 parent 46b64ef commit ab32aa1

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

‎src/plan.rs‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,4 +1348,67 @@ mod test {
13481348
assert_eq!(plan.scriptsig_size(), varint_len(script_sig.len()) + script_sig.len());
13491349
assert_eq!(plan.satisfaction_weight(), max_weight + 4 + 1);
13501350
}
1351+
1352+
/// A plan sizes every ECDSA signature at its 73-byte maximum (including the
1353+
/// length prefix, per the convention in `types::extra_props::SatData`), so a
1354+
/// satisfier that hands out signatures exactly filling that budget makes
1355+
/// even signature-bearing paths exact to the byte.
1356+
#[test]
1357+
fn sizes_match_max_sig_satisfaction() {
1358+
// A 72-byte signature: r with the high bit set and s without, so r is a
1359+
// 33-byte DER integer and s a 32-byte one (71 bytes of DER) plus the
1360+
// sighash flag. With its length prefix the witness element is exactly
1361+
// the 73 bytes a plan budgets.
1362+
let mut raw = [0x01u8; 64];
1363+
raw[0] = 0x80;
1364+
let sig = bitcoin::ecdsa::Signature::sighash_all(
1365+
secp256k1::ecdsa::Signature::from_compact(&raw).unwrap(),
1366+
);
1367+
assert_eq!(sig.to_vec().len(), 72);
1368+
1369+
let keys = [
1370+
DescriptorPublicKey::from_str(
1371+
"02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c",
1372+
)
1373+
.unwrap(),
1374+
DescriptorPublicKey::from_str(
1375+
"0257f4a2816338436cccabc43aa724cf6e69e43e84c3c8a305212761389dd73a8a",
1376+
)
1377+
.unwrap(),
1378+
];
1379+
let def_keys = keys
1380+
.iter()
1381+
.map(|k| DefiniteDescriptorKey::new(k.clone()).unwrap())
1382+
.collect::<Vec<_>>();
1383+
1384+
let sigs = def_keys
1385+
.iter()
1386+
.map(|k| (k.clone(), sig))
1387+
.collect::<BTreeMap<_, _>>();
1388+
let pkh_sigs = def_keys
1389+
.iter()
1390+
.map(|k| (k.to_pubkeyhash(SigType::Ecdsa), (k.clone(), sig)))
1391+
.collect::<BTreeMap<_, _>>();
1392+
let satisfier = (sigs, pkh_sigs);
1393+
let assets = Assets::new().add(keys[0].clone()).add(keys[1].clone());
1394+
1395+
for desc in [
1396+
format!("wpkh({})", keys[0]),
1397+
format!("sh(wpkh({}))", keys[0]),
1398+
format!("wsh(and_v(v:pk({}),pk({})))", keys[0], keys[1]),
1399+
format!("sh(wsh(and_v(v:pk({}),pk({}))))", keys[0], keys[1]),
1400+
] {
1401+
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(&desc).unwrap();
1402+
let max_weight = desc.max_weight_to_satisfy().unwrap().to_wu() as usize;
1403+
let plan = desc.clone().into_plan(&assets).unwrap();
1404+
let (witness, script_sig) = plan.satisfy(&satisfier).unwrap();
1405+
1406+
// Same wire-exact checks as `sizes_match_satisfaction`, now on paths
1407+
// that carry signatures.
1408+
let witness = bitcoin::Witness::from_slice(&witness);
1409+
assert_eq!(plan.witness_size(), bitcoin::consensus::serialize(&witness).len());
1410+
assert_eq!(plan.scriptsig_size(), varint_len(script_sig.len()) + script_sig.len());
1411+
assert_eq!(plan.satisfaction_weight(), max_weight + 4 + 1);
1412+
}
1413+
}
13511414
}

0 commit comments

Comments
 (0)