Skip to content

Commit eecd9ab

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 256c7ac commit eecd9ab

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed

src/musig.rs

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

0 commit comments

Comments
 (0)