Skip to content

Commit aa0f303

Browse files
committed
musig: add a bunch of unit tests
I asked Claude to create an initial set of unit tests, which it did. I then manually cleaned up a lot of its repeated logic (though not all of it, as you can tell) and added a whole bunch more failure cases. For example it did not bother trying to repeat or swap keys/nonces. When it generated the tests, the empty-pubkey-list bug (fixed in the previous commit) was still present. It failed to find it, I think because it was reading the code looking for panics to trigger, and there wasn't one. In future I will try giving it only the API, and try telling it to be more adversarial. We'll see.
1 parent 5ba4d97 commit aa0f303

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed

src/musig.rs

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,3 +1192,334 @@ impl Session {
11921192
/// Get a mut pointer to the inner Session
11931193
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigSession { &mut self.0 }
11941194
}
1195+
1196+
#[cfg(test)]
1197+
mod tests {
1198+
use super::*;
1199+
#[cfg(feature = "std")]
1200+
#[cfg(feature = "rand")]
1201+
use crate::{Message, PublicKey, Secp256k1, SecretKey};
1202+
1203+
#[test]
1204+
#[cfg(feature = "std")]
1205+
#[cfg(feature = "rand")]
1206+
fn test_session_secret_rand() {
1207+
let mut rng = rand::rng();
1208+
let session_secrand = SessionSecretRand::from_rng(&mut rng);
1209+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1210+
assert_ne!(session_secrand.to_byte_array(), [0; 32]); // with overwhelming probability
1211+
assert_ne!(session_secrand, session_secrand1); // with overwhelming probability
1212+
}
1213+
1214+
#[test]
1215+
fn test_session_secret_no_rand() {
1216+
let custom_bytes = [42u8; 32];
1217+
let session_secrand = SessionSecretRand::assume_unique_per_nonce_gen(custom_bytes);
1218+
assert_eq!(session_secrand.to_byte_array(), custom_bytes);
1219+
assert_eq!(session_secrand.as_byte_array(), &custom_bytes);
1220+
}
1221+
1222+
#[test]
1223+
#[should_panic(expected = "session secrets may not be all zero")]
1224+
fn test_session_secret_rand_zero_panic() {
1225+
let zero_bytes = [0u8; 32];
1226+
let _session_secrand = SessionSecretRand::assume_unique_per_nonce_gen(zero_bytes);
1227+
}
1228+
1229+
#[test]
1230+
#[cfg(feature = "std")]
1231+
#[cfg(feature = "rand")]
1232+
fn test_key_agg_cache() {
1233+
let secp = Secp256k1::new();
1234+
let mut rng = rand::rng();
1235+
1236+
let (_seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1237+
let seckey2 = SecretKey::new(&mut rng);
1238+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1239+
1240+
let pubkeys = [&pubkey1, &pubkey2];
1241+
let key_agg_cache = KeyAggCache::new(&secp, &pubkeys);
1242+
let agg_pk = key_agg_cache.agg_pk();
1243+
1244+
// Test agg_pk_full
1245+
let agg_pk_full = key_agg_cache.agg_pk_full();
1246+
assert_eq!(agg_pk_full.x_only_public_key().0, agg_pk);
1247+
}
1248+
1249+
#[test]
1250+
#[cfg(feature = "std")]
1251+
#[cfg(feature = "rand")]
1252+
fn test_key_agg_cache_tweaking() {
1253+
let secp = Secp256k1::new();
1254+
let mut rng = rand::rng();
1255+
1256+
let (_seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1257+
let seckey2 = SecretKey::new(&mut rng);
1258+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1259+
1260+
let mut key_agg_cache = KeyAggCache::new(&secp, &[&pubkey1, &pubkey2]);
1261+
let key_agg_cache1 = KeyAggCache::new(&secp, &[&pubkey2, &pubkey1]);
1262+
let key_agg_cache2 = KeyAggCache::new(&secp, &[&pubkey1, &pubkey1]);
1263+
let key_agg_cache3 = KeyAggCache::new(&secp, &[&pubkey1, &pubkey1, &pubkey2]);
1264+
assert_ne!(key_agg_cache, key_agg_cache1); // swapped keys DOES mean not equal
1265+
assert_ne!(key_agg_cache, key_agg_cache2); // missing keys
1266+
assert_ne!(key_agg_cache, key_agg_cache3); // repeated key
1267+
let original_agg_pk = key_agg_cache.agg_pk();
1268+
assert_ne!(key_agg_cache.agg_pk(), key_agg_cache1.agg_pk()); // swapped keys DOES mean not equal
1269+
assert_ne!(key_agg_cache.agg_pk(), key_agg_cache2.agg_pk()); // missing keys
1270+
assert_ne!(key_agg_cache.agg_pk(), key_agg_cache3.agg_pk()); // repeated key
1271+
1272+
// Test EC tweaking
1273+
let plain_tweak: [u8; 32] = *b"this could be a BIP32 tweak....\0";
1274+
let plain_tweak = Scalar::from_be_bytes(plain_tweak).unwrap();
1275+
let tweaked_key = key_agg_cache.pubkey_ec_tweak_add(&secp, &plain_tweak).unwrap();
1276+
assert_ne!(key_agg_cache.agg_pk(), original_agg_pk);
1277+
assert_eq!(key_agg_cache.agg_pk(), tweaked_key.x_only_public_key().0);
1278+
1279+
// Test xonly tweaking
1280+
let xonly_tweak: [u8; 32] = *b"this could be a Taproot tweak..\0";
1281+
let xonly_tweak = Scalar::from_be_bytes(xonly_tweak).unwrap();
1282+
let tweaked_agg_pk = key_agg_cache.pubkey_xonly_tweak_add(&secp, &xonly_tweak).unwrap();
1283+
assert_eq!(key_agg_cache.agg_pk(), tweaked_agg_pk.x_only_public_key().0);
1284+
}
1285+
1286+
#[test]
1287+
#[cfg(feature = "std")]
1288+
#[cfg(feature = "rand")]
1289+
#[should_panic(expected = "Cannot aggregate an empty slice of pubkeys")]
1290+
fn test_key_agg_cache_empty_panic() {
1291+
let secp = Secp256k1::new();
1292+
let _ = KeyAggCache::new(&secp, &[]);
1293+
}
1294+
1295+
#[test]
1296+
#[cfg(feature = "std")]
1297+
#[cfg(feature = "rand")]
1298+
fn test_nonce_generation() {
1299+
let secp = Secp256k1::new();
1300+
let mut rng = rand::rng();
1301+
1302+
let (_seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1303+
let seckey2 = SecretKey::new(&mut rng);
1304+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1305+
1306+
let key_agg_cache = KeyAggCache::new(&secp, &[&pubkey1, &pubkey2]);
1307+
1308+
let msg_bytes: [u8; 32] = *b"this_could_be_the_hash_of_a_msg!";
1309+
let msg = Message::from_digest_slice(&msg_bytes).unwrap();
1310+
1311+
// Test nonce generation with KeyAggCache
1312+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1313+
let (_sec_nonce1, pub_nonce1) =
1314+
key_agg_cache.nonce_gen(&secp, session_secrand1, pubkey1, msg, None);
1315+
1316+
// Test direct nonce generation
1317+
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
1318+
let extra_rand = Some([42u8; 32]);
1319+
let (_sec_nonce2, _pub_nonce2) = new_nonce_pair(
1320+
&secp,
1321+
session_secrand2,
1322+
Some(&key_agg_cache),
1323+
Some(seckey2),
1324+
pubkey2,
1325+
Some(msg),
1326+
extra_rand,
1327+
);
1328+
1329+
// Test PublicNonce serialization/deserialization
1330+
let serialized_nonce = pub_nonce1.serialize();
1331+
let deserialized_nonce = PublicNonce::from_byte_array(&serialized_nonce).unwrap();
1332+
assert_eq!(pub_nonce1.serialize(), deserialized_nonce.serialize());
1333+
}
1334+
1335+
#[test]
1336+
#[cfg(feature = "std")]
1337+
#[cfg(feature = "rand")]
1338+
fn test_aggregated_nonce() {
1339+
let secp = Secp256k1::new();
1340+
let mut rng = rand::rng();
1341+
1342+
let (_seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1343+
let seckey2 = SecretKey::new(&mut rng);
1344+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1345+
1346+
let key_agg_cache = KeyAggCache::new(&secp, &[&pubkey1, &pubkey2]);
1347+
1348+
let msg_bytes: [u8; 32] = *b"this_could_be_the_hash_of_a_msg!";
1349+
let msg = Message::from_digest_slice(&msg_bytes).unwrap();
1350+
1351+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1352+
let (_, pub_nonce1) = key_agg_cache.nonce_gen(&secp, session_secrand1, pubkey1, msg, None);
1353+
1354+
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
1355+
let (_, pub_nonce2) = key_agg_cache.nonce_gen(&secp, session_secrand2, pubkey2, msg, None);
1356+
1357+
// Test AggregatedNonce creation
1358+
let agg_nonce = AggregatedNonce::new(&secp, &[&pub_nonce1, &pub_nonce2]);
1359+
let agg_nonce1 = AggregatedNonce::new(&secp, &[&pub_nonce2, &pub_nonce1]);
1360+
let agg_nonce2 = AggregatedNonce::new(&secp, &[&pub_nonce2, &pub_nonce2]);
1361+
let agg_nonce3 = AggregatedNonce::new(&secp, &[&pub_nonce2, &pub_nonce2]);
1362+
assert_eq!(agg_nonce, agg_nonce1); // swapped nonces
1363+
assert_ne!(agg_nonce, agg_nonce2); // repeated/different nonces
1364+
assert_ne!(agg_nonce, agg_nonce3); // repeated nonce but still both nonces present
1365+
1366+
// Test AggregatedNonce serialization/deserialization
1367+
let serialized_agg_nonce = agg_nonce.serialize();
1368+
let deserialized_agg_nonce =
1369+
AggregatedNonce::from_byte_array(&serialized_agg_nonce).unwrap();
1370+
assert_eq!(agg_nonce.serialize(), deserialized_agg_nonce.serialize());
1371+
}
1372+
1373+
#[test]
1374+
#[cfg(feature = "std")]
1375+
#[cfg(feature = "rand")]
1376+
#[should_panic(expected = "Cannot aggregate an empty slice of nonces")]
1377+
fn test_aggregated_nonce_empty_panic() {
1378+
let secp = Secp256k1::new();
1379+
let empty_nonces: Vec<&PublicNonce> = vec![];
1380+
let _agg_nonce = AggregatedNonce::new(&secp, &empty_nonces);
1381+
}
1382+
1383+
#[test]
1384+
#[cfg(feature = "std")]
1385+
#[cfg(feature = "rand")]
1386+
fn test_session_and_partial_signing() {
1387+
let secp = Secp256k1::new();
1388+
let mut rng = rand::rng();
1389+
1390+
let (seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1391+
let seckey2 = SecretKey::new(&mut rng);
1392+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1393+
1394+
let pubkeys = [&pubkey1, &pubkey2];
1395+
let key_agg_cache = KeyAggCache::new(&secp, &pubkeys);
1396+
1397+
let msg_bytes: [u8; 32] = *b"this_could_be_the_hash_of_a_msg!";
1398+
let msg = Message::from_digest_slice(&msg_bytes).unwrap();
1399+
1400+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1401+
let (sec_nonce1, pub_nonce1) =
1402+
key_agg_cache.nonce_gen(&secp, session_secrand1, pubkey1, msg, None);
1403+
1404+
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
1405+
let (sec_nonce2, pub_nonce2) =
1406+
key_agg_cache.nonce_gen(&secp, session_secrand2, pubkey2, msg, None);
1407+
1408+
let nonces = [&pub_nonce1, &pub_nonce2];
1409+
let agg_nonce = AggregatedNonce::new(&secp, &nonces);
1410+
1411+
// Test Session creation
1412+
let session = Session::new(&secp, &key_agg_cache, agg_nonce, msg);
1413+
1414+
// Test partial signing
1415+
let keypair1 = Keypair::from_secret_key(&secp, &seckey1);
1416+
let partial_sign1 = session.partial_sign(&secp, sec_nonce1, &keypair1, &key_agg_cache);
1417+
1418+
let keypair2 = Keypair::from_secret_key(&secp, &seckey2);
1419+
let partial_sign2 = session.partial_sign(&secp, sec_nonce2, &keypair2, &key_agg_cache);
1420+
1421+
// Test partial signature verification
1422+
assert!(session.partial_verify(&secp, &key_agg_cache, partial_sign1, pub_nonce1, pubkey1));
1423+
assert!(session.partial_verify(&secp, &key_agg_cache, partial_sign2, pub_nonce2, pubkey2));
1424+
// Test that they are invalid if you switch keys
1425+
assert!(!session.partial_verify(&secp, &key_agg_cache, partial_sign2, pub_nonce2, pubkey1));
1426+
assert!(!session.partial_verify(&secp, &key_agg_cache, partial_sign2, pub_nonce1, pubkey2));
1427+
assert!(!session.partial_verify(&secp, &key_agg_cache, partial_sign2, pub_nonce1, pubkey1));
1428+
1429+
// Test PartialSignature serialization/deserialization
1430+
let serialized_partial_sig = partial_sign1.serialize();
1431+
let deserialized_partial_sig =
1432+
PartialSignature::from_byte_array(&serialized_partial_sig).unwrap();
1433+
assert_eq!(partial_sign1.serialize(), deserialized_partial_sig.serialize());
1434+
}
1435+
1436+
#[test]
1437+
#[cfg(feature = "std")]
1438+
#[cfg(feature = "rand")]
1439+
fn test_signature_aggregation_and_verification() {
1440+
let secp = Secp256k1::new();
1441+
let mut rng = rand::rng();
1442+
1443+
let (seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1444+
let seckey2 = SecretKey::new(&mut rng);
1445+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1446+
1447+
let pubkeys = [&pubkey1, &pubkey2];
1448+
let key_agg_cache = KeyAggCache::new(&secp, &pubkeys);
1449+
1450+
let msg_bytes: [u8; 32] = *b"this_could_be_the_hash_of_a_msg!";
1451+
let msg = Message::from_digest_slice(&msg_bytes).unwrap();
1452+
1453+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1454+
let (sec_nonce1, pub_nonce1) =
1455+
key_agg_cache.nonce_gen(&secp, session_secrand1, pubkey1, msg, None);
1456+
1457+
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
1458+
let (sec_nonce2, pub_nonce2) =
1459+
key_agg_cache.nonce_gen(&secp, session_secrand2, pubkey2, msg, None);
1460+
1461+
let nonces = [&pub_nonce1, &pub_nonce2];
1462+
let agg_nonce = AggregatedNonce::new(&secp, &nonces);
1463+
let session = Session::new(&secp, &key_agg_cache, agg_nonce, msg);
1464+
1465+
let keypair1 = Keypair::from_secret_key(&secp, &seckey1);
1466+
let partial_sign1 = session.partial_sign(&secp, sec_nonce1, &keypair1, &key_agg_cache);
1467+
1468+
let keypair2 = Keypair::from_secret_key(&secp, &seckey2);
1469+
let partial_sign2 = session.partial_sign(&secp, sec_nonce2, &keypair2, &key_agg_cache);
1470+
1471+
// Test signature verification
1472+
let aggregated_signature = session.partial_sig_agg(&[&partial_sign1, &partial_sign2]);
1473+
let agg_pk = key_agg_cache.agg_pk();
1474+
aggregated_signature.verify(&secp, &agg_pk, &msg_bytes).unwrap();
1475+
1476+
// Test assume_valid
1477+
let schnorr_sig = aggregated_signature.assume_valid();
1478+
secp.verify_schnorr(&schnorr_sig, &msg_bytes, &agg_pk).unwrap();
1479+
1480+
// Test with wrong aggregate (repeated sigs)
1481+
let aggregated_signature = session.partial_sig_agg(&[&partial_sign1, &partial_sign1]);
1482+
aggregated_signature.verify(&secp, &agg_pk, &msg_bytes).unwrap_err();
1483+
let schnorr_sig = aggregated_signature.assume_valid();
1484+
secp.verify_schnorr(&schnorr_sig, &msg_bytes, &agg_pk).unwrap_err();
1485+
1486+
// Test with swapped sigs -- this will work. Unlike keys, sigs are not ordered.
1487+
let aggregated_signature = session.partial_sig_agg(&[&partial_sign2, &partial_sign1]);
1488+
aggregated_signature.verify(&secp, &agg_pk, &msg_bytes).unwrap();
1489+
let schnorr_sig = aggregated_signature.assume_valid();
1490+
secp.verify_schnorr(&schnorr_sig, &msg_bytes, &agg_pk).unwrap();
1491+
}
1492+
1493+
#[test]
1494+
#[cfg(feature = "std")]
1495+
#[cfg(feature = "rand")]
1496+
#[should_panic(expected = "Cannot aggregate an empty slice of partial signatures")]
1497+
fn test_partial_sig_agg_empty_panic() {
1498+
let secp = Secp256k1::new();
1499+
let mut rng = rand::rng();
1500+
1501+
let (_seckey1, pubkey1) = secp.generate_keypair(&mut rng);
1502+
let seckey2 = SecretKey::new(&mut rng);
1503+
let pubkey2 = PublicKey::from_secret_key(&secp, &seckey2);
1504+
1505+
let pubkeys = [pubkey1, pubkey2];
1506+
let mut pubkeys_ref: Vec<&PublicKey> = pubkeys.iter().collect();
1507+
let pubkeys_ref = pubkeys_ref.as_mut_slice();
1508+
1509+
let key_agg_cache = KeyAggCache::new(&secp, pubkeys_ref);
1510+
let msg_bytes: [u8; 32] = *b"this_could_be_the_hash_of_a_msg!";
1511+
let msg = Message::from_digest_slice(&msg_bytes).unwrap();
1512+
1513+
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
1514+
let (_, pub_nonce1) = key_agg_cache.nonce_gen(&secp, session_secrand1, pubkey1, msg, None);
1515+
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
1516+
let (_, pub_nonce2) = key_agg_cache.nonce_gen(&secp, session_secrand2, pubkey2, msg, None);
1517+
1518+
let nonces = [pub_nonce1, pub_nonce2];
1519+
let nonces_ref: Vec<&PublicNonce> = nonces.iter().collect();
1520+
let agg_nonce = AggregatedNonce::new(&secp, &nonces_ref);
1521+
let session = Session::new(&secp, &key_agg_cache, agg_nonce, msg);
1522+
1523+
let _agg_sig = session.partial_sig_agg(&[]);
1524+
}
1525+
}

0 commit comments

Comments
 (0)