diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 33bf9e4bbb..c83eedf0ff 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -23,8 +23,7 @@ use stacks_common::codec::{ read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::StacksAddress; -use stacks_common::types::StacksEpochId; -use stacks_common::types::StacksPublicKeyBuffer; +use stacks_common::types::{StacksEpochId, StacksPublicKeyBuffer}; use stacks_common::util::hash::{to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::retry::{BoundReader, RetryReader}; use stacks_common::util::secp256k1::{MessageSignature, MESSAGE_SIGNATURE_ENCODED_SIZE}; @@ -1887,6 +1886,30 @@ mod test { ], signatures_required: 2 }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 123, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 + }), TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2WPKH, @@ -1918,6 +1941,18 @@ mod test { TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) ], signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 }) ]; @@ -2047,34 +2082,6 @@ mod test { 0x00, 0x01, ]; - // wrong number of public keys (too many signatures) - let bad_public_order_independent_key_count_bytes = vec![ - // hash mode - OrderIndependentMultisigHashMode::P2SH as u8, - // signer - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - // nonce - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, - // fee rate - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, - // fields length - 0x00, 0x00, 0x00, 0x03, - // field #1: signature - TransactionAuthFieldID::SignatureCompressed as u8, - // field #1: signature - 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - // field #2: signature - TransactionAuthFieldID::SignatureCompressed as u8, - // filed #2: signature - 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, - // field #3: public key - TransactionAuthFieldID::PublicKeyCompressed as u8, - // field #3: key (compressed) - 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, - // number of signatures - 0x00, 0x01, - ]; - // wrong number of public keys (not enough signatures) let bad_public_key_count_bytes_2 = vec![ // hash mode diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 210ccb85c3..1095e8e534 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -4060,6 +4060,17 @@ mod test { assert_eq!(txid_before, signed_tx.txid()); } + fn is_order_independent_multisig(tx: &StacksTransaction) -> bool { + let spending_condition = match &tx.auth { + TransactionAuth::Standard(origin) => origin, + TransactionAuth::Sponsored(_, sponsor) => sponsor, + }; + match spending_condition { + TransactionSpendingCondition::OrderIndependentMultisig(..) => true, + _ => false, + } + } + fn check_oversign_origin_multisig(signed_tx: &StacksTransaction) -> () { let tx = signed_tx.clone(); let privk = StacksPrivateKey::from_hex( @@ -4076,7 +4087,14 @@ mod test { Ok(_) => assert!(false), Err(e) => match e { net_error::VerifyingError(msg) => { - assert_eq!(&msg, "Incorrect number of signatures") + if is_order_independent_multisig(&oversigned_tx) { + assert!( + msg.contains("Signer hash does not equal hash of public key(s)"), + "{msg}" + ) + } else { + assert_eq!(&msg, "Incorrect number of signatures") + } } _ => assert!(false), }, @@ -4133,7 +4151,14 @@ mod test { Ok(_) => assert!(false), Err(e) => match e { net_error::VerifyingError(msg) => { - assert_eq!(&msg, "Incorrect number of signatures") + if is_order_independent_multisig(&oversigned_tx) { + assert!( + msg.contains("Signer hash does not equal hash of public key(s)"), + "{msg}" + ) + } else { + assert_eq!(&msg, "Incorrect number of signatures") + } } _ => assert!(false), }, @@ -5774,6 +5799,100 @@ mod test { } } + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh_extra_signers() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_2) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + //check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 3); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + #[test] fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh() { let origin_privk = StacksPrivateKey::from_hex(