Skip to content

Commit

Permalink
feat: add multisig script that returns aggregate of signed public keys (
Browse files Browse the repository at this point in the history
#4742)

Description
---
Added an `m-of-n` multisig TariScript that returns the aggregate public key of the signatories if successful and fails otherwise. 

This is useful if the aggregate public key of the signatories is also the script public key, where signatories would work together to create an aggregate script signature using their individual script private keys.

Motivation and Context
---
To enhance the practicality of the  `m-of-n` multisig TariScript.

How Has This Been Tested?
---
Unit tests

Co-Authored-By: SW van Heerden [email protected]
  • Loading branch information
hansieodendaal authored Oct 19, 2022
1 parent 87dfab5 commit c004e30
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 17 deletions.
36 changes: 35 additions & 1 deletion infrastructure/tari_script/src/op_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub const OP_HASH_BLAKE256: u8 = 0xb0;
pub const OP_HASH_SHA256: u8 = 0xb1;
pub const OP_HASH_SHA3: u8 = 0xb2;
pub const OP_TO_RISTRETTO_POINT: u8 = 0xb3;
pub const OP_CHECK_MULTI_SIG_VERIFY_AGGREGATE_PUB_KEY: u8 = 0xb4;

// Opcode constants: Miscellaneous
pub const OP_RETURN: u8 = 0x60;
Expand Down Expand Up @@ -234,6 +235,9 @@ pub enum Opcode {
/// Identical to CheckMultiSig, except that nothing is pushed to the stack if the m signatures are valid, and the
/// operation fails with VERIFY_FAILED if any of the signatures are invalid.
CheckMultiSigVerify(u8, u8, Vec<RistrettoPublicKey>, Box<Message>),
/// Pop m signatures from the stack. If m signatures out of the provided n public keys sign the 32-byte message,
/// push the aggregate of the public keys to the stack, otherwise fails with VERIFY_FAILED.
CheckMultiSigVerifyAggregatePubKey(u8, u8, Vec<RistrettoPublicKey>, Box<Message>),
/// Pops the top element which must be a valid 32-byte scalar or hash and calculates the corresponding Ristretto
/// point, and pushes the result to the stack. Fails with EMPTY_STACK if the stack is empty.
ToRistrettoPoint,
Expand Down Expand Up @@ -355,6 +359,10 @@ impl Opcode {
let (m, n, keys, msg, end) = Opcode::read_multisig_args(bytes)?;
Ok((CheckMultiSigVerify(m, n, keys, msg), &bytes[end..]))
},
OP_CHECK_MULTI_SIG_VERIFY_AGGREGATE_PUB_KEY => {
let (m, n, keys, msg, end) = Opcode::read_multisig_args(bytes)?;
Ok((CheckMultiSigVerifyAggregatePubKey(m, n, keys, msg), &bytes[end..]))
},
OP_TO_RISTRETTO_POINT => Ok((ToRistrettoPoint, &bytes[1..])),
OP_RETURN => Ok((Return, &bytes[1..])),
OP_IF_THEN => Ok((IfThen, &bytes[1..])),
Expand Down Expand Up @@ -464,6 +472,13 @@ impl Opcode {
}
array.extend_from_slice(msg.deref());
},
CheckMultiSigVerifyAggregatePubKey(m, n, public_keys, msg) => {
array.extend_from_slice(&[OP_CHECK_MULTI_SIG_VERIFY_AGGREGATE_PUB_KEY, *m, *n]);
for public_key in public_keys {
array.extend(public_key.as_bytes());
}
array.extend_from_slice(msg.deref());
},
ToRistrettoPoint => array.push(OP_TO_RISTRETTO_POINT),
Return => array.push(OP_RETURN),
IfThen => array.push(OP_IF_THEN),
Expand Down Expand Up @@ -530,6 +545,17 @@ impl fmt::Display for Opcode {
(*msg).to_hex()
)
},
CheckMultiSigVerifyAggregatePubKey(m, n, public_keys, msg) => {
let keys: Vec<String> = public_keys.iter().map(|p| p.to_hex()).collect();
write!(
fmt,
"CheckMultiSigVerifyAggregatePubKey({}, {}, [{}], {})",
*m,
*n,
keys.join(", "),
(*msg).to_hex()
)
},
ToRistrettoPoint => write!(fmt, "ToRistrettoPoint"),
Return => write!(fmt, "Return"),
IfThen => write!(fmt, "IfThen"),
Expand Down Expand Up @@ -766,12 +792,20 @@ mod test {
6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)",
);
test_checkmultisig(
&Opcode::CheckMultiSigVerify(1, 2, keys, Box::new(*msg)),
&Opcode::CheckMultiSigVerify(1, 2, keys.clone(), Box::new(*msg)),
OP_CHECK_MULTI_SIG_VERIFY,
"CheckMultiSigVerify(1, 2, [9c8bc5f90d221191748e8dd7686f09e1114b4bada4c367ed58ae199c51eb100b, \
56e9f018b138ba843521b3243a29d81730c3a4c25108b108b1ca47c2132db569], \
6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)",
);
test_checkmultisig(
&Opcode::CheckMultiSigVerifyAggregatePubKey(1, 2, keys, Box::new(*msg)),
OP_CHECK_MULTI_SIG_VERIFY_AGGREGATE_PUB_KEY,
"CheckMultiSigVerifyAggregatePubKey(1, 2, \
[9c8bc5f90d221191748e8dd7686f09e1114b4bada4c367ed58ae199c51eb100b, \
56e9f018b138ba843521b3243a29d81730c3a4c25108b108b1ca47c2132db569], \
6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)",
);
}

#[test]
Expand Down
70 changes: 54 additions & 16 deletions infrastructure/tari_script/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,26 @@ impl TariScript {
}
},
CheckMultiSig(m, n, public_keys, msg) => {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())? {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
stack.push(Number(1))
} else {
stack.push(Number(0))
}
},
CheckMultiSigVerify(m, n, public_keys, msg) => {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())? {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
},
CheckMultiSigVerifyAggregatePubKey(m, n, public_keys, msg) => {
if let Some(agg_pub_key) = self.check_multisig(stack, *m, *n, public_keys, *msg.deref())? {
stack.push(PublicKey(agg_pub_key))
} else {
Err(ScriptError::VerifyFailed)
}
},
ToRistrettoPoint => self.handle_to_ristretto_point(stack),
Return => Err(ScriptError::Return),
IfThen => TariScript::handle_if_then(stack, state),
Expand Down Expand Up @@ -505,9 +512,9 @@ impl TariScript {
n: u8,
public_keys: &[RistrettoPublicKey],
message: Message,
) -> Result<bool, ScriptError> {
if m == 0 || n == 0 || m > n || n > MAX_MULTISIG_LIMIT {
return Err(ScriptError::InvalidData);
) -> Result<Option<RistrettoPublicKey>, ScriptError> {
if m == 0 || n == 0 || m > n || n > MAX_MULTISIG_LIMIT || public_keys.len() != n as usize {
return Err(ScriptError::ValueExceedsBounds);
}
// pop m sigs
let m = m as usize;
Expand All @@ -524,20 +531,25 @@ impl TariScript {
#[allow(clippy::mutable_key_type)]
let mut sig_set = HashSet::new();

let mut agg_pub_key = RistrettoPublicKey::default();
for s in &signatures {
for (i, pk) in public_keys.iter().enumerate() {
if !sig_set.contains(s) && !key_signed[i] && s.verify_challenge(pk, &message) {
key_signed[i] = true;
sig_set.insert(s);
agg_pub_key = agg_pub_key + pk;
break;
}
}
if !sig_set.contains(s) {
return Ok(false);
return Ok(None);
}
}

Ok(sig_set.len() == m)
if sig_set.len() == m {
Ok(Some(agg_pub_key))
} else {
Ok(None)
}
}

fn handle_to_ristretto_point(&self, stack: &mut ExecutionStack) -> Result<(), ScriptError> {
Expand Down Expand Up @@ -625,6 +637,7 @@ mod test {
inputs,
op_codes::{slice_to_boxed_hash, slice_to_boxed_message, HashValue, Message},
ExecutionStack,
Opcode::CheckMultiSigVerifyAggregatePubKey,
ScriptContext,
StackItem,
StackItem::{Commitment, Hash, Number},
Expand Down Expand Up @@ -1145,21 +1158,21 @@ mod test {
let script = TariScript::new(ops);
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSig(1, 0, keys, msg.clone())];
let script = TariScript::new(ops);
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

let keys = vec![p_alice, p_bob];
let ops = vec![CheckMultiSig(2, 1, keys, msg)];
let script = TariScript::new(ops);
let inputs = inputs!(s_alice);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

// max n is 32
let (msg, data) = multisig_data(33);
Expand All @@ -1169,7 +1182,7 @@ mod test {
let items = sigs.map(StackItem::Signature).collect();
let inputs = ExecutionStack::new(items);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

// 3 of 4
let (msg, data) = multisig_data(4);
Expand Down Expand Up @@ -1258,7 +1271,7 @@ mod test {

// 1 of 3
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSigVerify(1, 2, keys, msg.clone())];
let ops = vec![CheckMultiSigVerify(1, 3, keys, msg.clone())];
let script = TariScript::new(ops);

let inputs = inputs!(Number(1), s_alice.clone());
Expand Down Expand Up @@ -1292,6 +1305,31 @@ mod test {
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);

// 2 of 3 (returning the aggregate public key of the signatories)
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSigVerifyAggregatePubKey(2, 3, keys, msg.clone())];
let script = TariScript::new(ops);

let inputs = inputs!(s_alice.clone(), s_bob.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(agg_pub_key, StackItem::PublicKey(p_alice.clone() + p_bob.clone()));

let inputs = inputs!(s_alice.clone(), s_carol.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(agg_pub_key, StackItem::PublicKey(p_alice.clone() + p_carol.clone()));

let inputs = inputs!(s_bob.clone(), s_carol.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(agg_pub_key, StackItem::PublicKey(p_bob.clone() + p_carol.clone()));

let inputs = inputs!(s_alice.clone(), s_carol.clone(), s_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::NonUnitLengthStack);

let inputs = inputs!(p_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::StackUnderflow);

// 3 of 3
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
let ops = vec![CheckMultiSigVerify(3, 3, keys, msg.clone())];
Expand All @@ -1313,21 +1351,21 @@ mod test {
let script = TariScript::new(ops);
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSigVerify(1, 0, keys, msg.clone())];
let script = TariScript::new(ops);
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

let keys = vec![p_alice, p_bob];
let ops = vec![CheckMultiSigVerify(2, 1, keys, msg)];
let script = TariScript::new(ops);
let inputs = inputs!(s_alice);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::InvalidData);
assert_eq!(err, ScriptError::ValueExceedsBounds);

// 3 of 4
let (msg, data) = multisig_data(4);
Expand Down

0 comments on commit c004e30

Please sign in to comment.