Skip to content

Commit

Permalink
Merge pull request #5 from jbencin/multisig-order-independence
Browse files Browse the repository at this point in the history
Add/fix tests for when num_signers > required_signers
  • Loading branch information
fess-v authored Apr 11, 2024
2 parents 9dfa3c4 + d981cec commit fa95032
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 32 deletions.
67 changes: 37 additions & 30 deletions stackslib/src/chainstate/stacks/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
})
];

Expand Down Expand Up @@ -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
Expand Down
123 changes: 121 additions & 2 deletions stackslib/src/chainstate/stacks/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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),
},
Expand Down Expand Up @@ -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),
},
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit fa95032

Please sign in to comment.