Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
bloomingpeach committed Oct 18, 2024
1 parent 5b9f8c9 commit ae1bec4
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 82 deletions.
2 changes: 2 additions & 0 deletions packages/engine/src/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub mod Error {
pub const SCRIPT_TOO_LARGE: felt252 = 'Script is too large';
pub const SCRIPT_UNFINISHED: felt252 = 'Script unfinished';
pub const SCRIPT_ERR_SIG_DER: felt252 = 'Signature DER error';
pub const PUBKEYTYPE: felt252 = 'Unsupported public key type';
pub const SIG_HIGH_S: felt252 = 'Sig not canonical high S value';
}

pub fn byte_array_err(err: felt252) -> ByteArray {
Expand Down
36 changes: 21 additions & 15 deletions packages/engine/src/opcodes/crypto.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pub fn opcode_checksig<
let res = BaseSigVerifierTrait::new(ref engine, @full_sig_bytes, @pk_bytes);
if res.is_err() {
let err = res.unwrap_err();
if err == Error::SCRIPT_ERR_SIG_DER || err == Error::WITNESS_PUBKEYTYPE {
if err == Error::SCRIPT_ERR_SIG_DER || err == Error::PUBKEYTYPE {
return Result::Err(err);
};
engine.dstack.push_bool(false);
Expand Down Expand Up @@ -161,6 +161,7 @@ pub fn opcode_checkmultisig<
// TODO Error on taproot exec

let verify_der = engine.has_flag(ScriptFlags::ScriptVerifyDERSignatures);
let strict_encoding = engine.has_flag(ScriptFlags::ScriptVerifyStrictEncoding);
// Get number of public keys and construct array
let num_keys = engine.dstack.pop_int()?;
let mut num_pub_keys: i64 = ScriptNum::to_int32(num_keys).into();
Expand Down Expand Up @@ -210,7 +211,7 @@ pub fn opcode_checkmultisig<
if err != 0 {
return Result::Err(err);
}

println!("num_sigs: {}", sigs[0].len());
// Historical bug
let dummy = engine.dstack.pop_byte_array()?;

Expand All @@ -220,18 +221,19 @@ pub fn opcode_checkmultisig<

let mut script = engine.sub_script();

let mut s: u32 = 0;
let end = sigs.len();
while s != end {
script = signature::remove_signature(@script, sigs.at(s)).clone();
s += 1;
};
if (engine.is_witness_active(0)) {
let mut s: u32 = 0;
let end = sigs.len();
while s != end {
script = signature::remove_signature(@script, sigs.at(s)).clone();
s += 1;
};
}

let mut success = true;
num_pub_keys += 1; // Offset due to decrementing it in the loop
let mut pub_key_idx: i64 = -1;
let mut sig_idx: i64 = 0;

while num_sigs != 0 {
pub_key_idx += 1;
num_pub_keys -= 1;
Expand All @@ -245,6 +247,7 @@ pub fn opcode_checkmultisig<
if sig.len() == 0 {
continue;
}

let res = signature::parse_base_sig_and_pk(ref engine, pub_key, sig);
if res.is_err() {
success = false;
Expand All @@ -263,10 +266,6 @@ pub fn opcode_checkmultisig<
}
};

if err != 0 {
return Result::Err(err);
}

if !success {
if engine.has_flag(ScriptFlags::ScriptVerifyNullFail) {
let mut err = '';
Expand All @@ -279,8 +278,15 @@ pub fn opcode_checkmultisig<
if err != '' {
return Result::Err(err);
}
} else if verify_der {
return Result::Err(Error::SCRIPT_ERR_SIG_DER);
} else if verify_der || strict_encoding {
if err == Error::SCRIPT_ERR_SIG_DER || err == Error::PUBKEYTYPE {
return Result::Err(err);
}
if err == 'invalid sig fmt: S padding' {
return Result::Err(Error::SCRIPT_ERR_SIG_DER);
}
} else if err == Error::SIG_HIGH_S {
return Result::Err(err);
}
}

Expand Down
97 changes: 61 additions & 36 deletions packages/engine/src/signature/signature.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::hash_cache::SigHashMidstateTrait;
use shinigami_utils::byte_array::u256_from_byte_array_with_offset;
use crate::signature::{sighash, constants};
use crate::errors::Error;
use shinigami_utils::byte_array::{sub_byte_array};


//`BaseSigVerifier` is used to verify ECDSA signatures encoded in DER or BER format (pre-SegWit sig)
#[derive(Drop)]
Expand Down Expand Up @@ -153,8 +155,8 @@ pub fn check_hash_type_encoding<
if hash_type > constants::SIG_HASH_ANYONECANPAY {
hash_type -= constants::SIG_HASH_ANYONECANPAY;
}

if hash_type < constants::SIG_HASH_ALL || hash_type > constants::SIG_HASH_SINGLE {
println!("Invalid hash type: {}", hash_type);
return Result::Err('invalid hash type');
}

Expand Down Expand Up @@ -185,9 +187,15 @@ pub fn check_signature_encoding<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
>
>(
ref vm: Engine<T>, sig_bytes: @ByteArray, strict_encoding: bool
ref vm: Engine<T>, sig_bytes: @ByteArray
) -> Result<(), felt252> {
// https://github.com/btcsuite/btcd/blob/master/txscript/engine.go#L1221
let low_s = vm.has_flag(ScriptFlags::ScriptVerifyLowS);
let strict_encoding = vm.has_flag(ScriptFlags::ScriptVerifyStrictEncoding);
let verify_der = vm.has_flag(ScriptFlags::ScriptVerifyDERSignatures);
if !low_s && !strict_encoding && !verify_der {
return Result::Ok(());
}

// ASN.1 identifiers for sequence and integer types.*
let asn1_sequence_id: u8 = 0x30;
Expand All @@ -205,24 +213,29 @@ pub fn check_signature_encoding<
if sig_bytes_len == 0 {
return Result::Err('invalid sig fmt: empty sig');
}

// Calculate the actual length of the signature, excluding the hash type.
let sig_len = sig_bytes_len - constants::HASH_TYPE_LEN;
// Check if the signature is too short.
if sig_len < constants::MIN_SIG_LEN {
return Result::Err('invalid sig fmt: too short');
}

// Check if the signature is too long.
if sig_len > constants::MAX_SIG_LEN {
return Result::Err('invalid sig fmt: too long');
}

// Ensure the signature starts with the correct ASN.1 sequence identifier.
if sig_bytes[sequence_offset] != asn1_sequence_id {
return Result::Err('invalid sig fmt: wrong type');
}

// Verify that the length field matches the expected length.
if sig_bytes[data_len_offset] != (sig_len - data_offset).try_into().unwrap() {
return Result::Err('invalid sig fmt: bad length');
}

// Determine the length of the `R` value in the signature.
let r_len: usize = sig_bytes[r_len_offset].into();
let s_type_offset = r_offset + r_len;
Expand All @@ -231,6 +244,7 @@ pub fn check_signature_encoding<
if s_type_offset > sig_len {
return Result::Err('invalid sig fmt: S type missing');
}

// Check if the `S` length offset exceeds the length of the signature.
if s_len_offset > sig_len {
return Result::Err('invalid sig fmt: miss S length');
Expand All @@ -246,16 +260,14 @@ pub fn check_signature_encoding<
if r_len <= 0 || r_len > sig_len - r_offset - 3 {
return Result::Err('invalid sig fmt:R length');
}
// If strict encoding is enforced, check for negative or excessively padded `R` values.
if strict_encoding {
if sig_bytes[r_offset] & 0x80 != 0 {
return Result::Err('invalid sig fmt: negative R');
}
if sig_bytes[r_offset] & 0x80 != 0 {
return Result::Err('invalid sig fmt: negative R');
}

if r_len > 1 && sig_bytes[r_offset] == 0 && sig_bytes[r_offset + 1] & 0x80 == 0 {
return Result::Err('invalid sig fmt: R padding');
}
if r_len > 1 && sig_bytes[r_offset] == 0 && sig_bytes[r_offset + 1] & 0x80 == 0 {
return Result::Err('invalid sig fmt: R padding');
}

// Ensure the `S` value is correctly identified as an ASN.1 integer.
if sig_bytes[s_type_offset] != asn1_integer_id {
return Result::Err('invalid sig fmt:S ASN.1');
Expand All @@ -264,16 +276,17 @@ pub fn check_signature_encoding<
if s_len <= 0 || s_len > sig_len - s_offset {
return Result::Err('invalid sig fmt:S length');
}

// If strict encoding is enforced, check for negative or excessively padded `S` values.
if strict_encoding {
if sig_bytes[s_offset] & 0x80 != 0 {
return Result::Err('invalid sig fmt: negative S');
}

if s_len > 1 && sig_bytes[s_offset] == 0 && sig_bytes[s_offset + 1] & 0x80 == 0 {
return Result::Err('invalid sig fmt: S padding');
}
}

// If the "low S" rule is enforced, check that the `S` value is below the threshold.
if low_s {
let s_value = u256_from_byte_array_with_offset(sig_bytes, s_offset, 32);
Expand All @@ -283,9 +296,8 @@ pub fn check_signature_encoding<
let carry = half_order_high_lower;
half_order.low = (half_order.low / 2) + (carry * (constants::MAX_U128 / 2 + 1));
half_order.high = half_order_high_upper;

if s_value > half_order {
return Result::Err('sig not canonical high S value');
return Result::Err(Error::SIG_HIGH_S);
}
}

Expand Down Expand Up @@ -341,7 +353,7 @@ pub fn check_pub_key_encoding<
}

if !is_supported_pub_key_type(pk_bytes) {
return Result::Err('unsupported public key type');
return Result::Err(Error::PUBKEYTYPE);
}

return Result::Ok(());
Expand Down Expand Up @@ -392,13 +404,17 @@ pub fn parse_pub_key(pk_bytes: @ByteArray) -> Result<Secp256k1Point, felt252> {
// This function extracts the `r` and `s` values from a DER-encoded ECDSA signature (`sig_bytes`).
// The function performs various checks to ensure the integrity and validity of the signature.
pub fn parse_signature(sig_bytes: @ByteArray) -> Result<Signature, felt252> {
let mut sig_len: usize = sig_bytes.len() - constants::HASH_TYPE_LEN;
let mut sig_len: usize = sig_bytes[1].into();
if sig_len + 2 > sig_bytes.len() || sig_len + 2 < constants::MIN_SIG_LEN {
return Result::Err('invalid sig fmt: bad length');
}
let mut start = 0;
let sig_bytes = @sub_byte_array(sig_bytes, ref start, sig_len + 2);
let mut r_len: usize = sig_bytes[3].into();
let mut s_len: usize = sig_bytes[r_len + 5].into();
let mut r_offset = 4;
let mut s_offset = 6 + r_len;
let order: u256 = Secp256Trait::<Secp256k1Point>::get_curve_size();

let mut i = 0;

//Strip leading zero
Expand Down Expand Up @@ -440,9 +456,11 @@ pub fn parse_signature(sig_bytes: @ByteArray) -> Result<Signature, felt252> {
if s_sig == 0 {
return Result::Err('invalid sig: S is zero');
}
if sig_len != r_len + s_len + 6 {

if sig_bytes.len() != sig_bytes[3].into() + sig_bytes[sig_bytes[3].into() + 5].into() + 6 {
return Result::Err('invalid sig: bad final length');
}

return Result::Ok(Signature { r: r_sig, s: s_sig, y_parity: false, });
}

Expand All @@ -464,26 +482,31 @@ pub fn parse_base_sig_and_pk<
ref vm: Engine<T>, pk_bytes: @ByteArray, sig_bytes: @ByteArray
) -> Result<(Secp256k1Point, Signature, u32), felt252> {
let verify_der = vm.has_flag(ScriptFlags::ScriptVerifyDERSignatures);
let strict_encoding = vm.has_flag(ScriptFlags::ScriptVerifyStrictEncoding) || verify_der;
let verify_strict_encoding = vm.has_flag(ScriptFlags::ScriptVerifyStrictEncoding);
let strict_encoding = verify_strict_encoding || verify_der;

if sig_bytes.len() == 0 {
return if strict_encoding {
Result::Err(Error::SCRIPT_ERR_SIG_DER)
} else {
Result::Err('empty signature')
};
}

// TODO: strct encoding
let hash_type_offset: usize = sig_bytes.len() - 1;
let hash_type: u32 = sig_bytes[hash_type_offset].into();
if let Result::Err(e) = check_hash_type_encoding(ref vm, hash_type) {
return if verify_der {
Result::Err(Error::SCRIPT_ERR_SIG_DER)
} else {
Result::Err(e)
};

if strict_encoding {
if let Result::Err(e) = check_hash_type_encoding(ref vm, hash_type) {
return if verify_der {
Result::Err(Error::SCRIPT_ERR_SIG_DER)
} else {
Result::Err(e)
};
}
}
if let Result::Err(e) = check_signature_encoding(ref vm, sig_bytes, strict_encoding) {

if let Result::Err(e) = check_signature_encoding(ref vm, sig_bytes) {
return if verify_der {
Result::Err(Error::SCRIPT_ERR_SIG_DER)
} else {
Expand All @@ -499,15 +522,11 @@ pub fn parse_base_sig_and_pk<
};
}

let pub_key = match parse_pub_key(pk_bytes) {
Result::Ok(key) => key,
Result::Err(e) => if verify_der {
return Result::Err(Error::SCRIPT_ERR_SIG_DER);
} else {
return Result::Err(e);
},
};

let mut start = 0;
if hash_type_offset < 1 {
return Result::Err('invalid hash offset');
}
let sig_bytes = @sub_byte_array(sig_bytes, ref start, hash_type_offset);
let sig = match parse_signature(sig_bytes) {
Result::Ok(signature) => signature,
Result::Err(e) => if verify_der {
Expand All @@ -516,6 +535,12 @@ pub fn parse_base_sig_and_pk<
return Result::Err(e);
},
};
println!("Parsed sig and pk");

let pub_key = match parse_pub_key(pk_bytes) {
Result::Ok(key) => key,
Result::Err(e) => { return Result::Err(e); }
};

Result::Ok((pub_key, sig, hash_type))
}
Expand All @@ -535,7 +560,6 @@ pub fn remove_signature(script: @ByteArray, sig_bytes: @ByteArray) -> @ByteArray
if push_data >= 8 && push_data <= 72 {
let mut len: usize = push_data.into();
let mut found: bool = false;

if len == sig_bytes.len() {
found = compare_data(script, sig_bytes, i, push_data);
}
Expand All @@ -549,6 +573,7 @@ pub fn remove_signature(script: @ByteArray, sig_bytes: @ByteArray) -> @ByteArray
i += 1;
continue;
}

processed_script.append_byte(push_data);
while len != 0 && i - len < script_len {
processed_script.append_byte(script[i - len + 1]);
Expand Down
Loading

0 comments on commit ae1bec4

Please sign in to comment.