dcSpark / cardano-multiplatform-lib

Rust implementation of Cardano
MIT License
98 stars 35 forks source link

Cannot verify signature #310

Closed CantTouchDis closed 7 months ago

CantTouchDis commented 7 months ago

Hi,

I think I have encountered an issue when verifying a signature, or I might've just misunderstood the process of verifying a signature created by a CIP30 wallets signData. I don't quite understand why the verification fails.

cose_key and cose_sig were both returned by using signData with Nami,

Hashing the pub key also returns the same Ed25519KeyHash as contained in the attached address.

Could you please clarify if I used the verify function correctly or if I encountered a bug?

For this example I was using version 4.2 but it also happened on 5.1

Minimal code example:

fn main() {
    fn string_to_bytes(string: &str) -> Vec<u8> {
        (0..string.len())
            .step_by(2)
            .map(|i| u8::from_str_radix(&string[i..i + 2], 16).unwrap())
            .collect::<Vec<u8>>()
    }

    let cose_key =
        "a4010103272006215820a80a48d59a22316aac8e1b86af480387369f227ae567118367af552e3cc79518";
    // SIGN1
    let cose_sig = "845846a201276761646472657373583900fbb9d8bfc1acef684e39e5b4f7db587a0a8078f1a773321a8a0c4439a7e68f2739efdbe66b3a61250b95048415c6d78bdb0ec2fad231e594a166686173686564f458407b2274797065223a224c6f67696e222c2274696d657374616d70223a22323032342d30322d31302031363a31303a31392e32303435333130373920555443227d5840daf45006c55fc53456304f6a5df0e37ec78ee8c168115a8e6a0a285ab5d69cc3e05ea0cc248b0ccaa39013f3bce79f5c62be3a7521c615c3f710eca989f9680f";

    //println!("Key: {cose_key}\nSignature: {cose_sig}");

    let cose_key = serde_cbor::from_slice::<std::collections::HashMap<i32, serde_cbor::Value>>(
        &string_to_bytes(&cose_key),
    )
    .unwrap();
    let cose_sig =
        serde_cbor::from_slice::<Vec<serde_cbor::Value>>(&string_to_bytes(&cose_sig)).unwrap();

    println!("Key: {cose_key:?}\nSignature: {cose_sig:?}");

    // kty = OKP
    assert_eq!(cose_key.get(&1), Some(&serde_cbor::Value::Integer(1)));
    // alg = EdDSA
    assert_eq!(cose_key.get(&3), Some(&serde_cbor::Value::Integer(-8)));
    // crv = Ed25519
    assert_eq!(cose_key.get(&-1), Some(&serde_cbor::Value::Integer(6)));

    let pub_key = if let Some(serde_cbor::Value::Bytes(b)) = cose_key.get(&-2) {
        cml_crypto::PublicKey::from_raw_bytes(b).expect("Unable to read key")
    } else {
        panic!("did not encounter PubKey bytes")
    };

    // protected header
    let protected_header = if let Some(serde_cbor::Value::Bytes(b)) = cose_sig.get(0) {
        serde_cbor::from_slice::<std::collections::BTreeMap<serde_cbor::Value, serde_cbor::Value>>(
            b,
        )
        .expect("There should be a protected header!")
    } else {
        panic!("no protected_header")
    };
    println!("{protected_header:?}");
    let address = if let Some(serde_cbor::Value::Bytes(b)) =
        protected_header.get(&serde_cbor::Value::Text("address".to_owned()))
    {
        cml_chain::address::Address::from_raw_bytes(b).expect("Address parsing")
    } else {
        panic!("No field address in protected header!!!")
    };

    println!("Address: {address:?}");

    let unprotected_header = if let Some(serde_cbor::Value::Map(m)) = cose_sig.get(1) {
        m
    } else {
        panic!("no unprotected header")
    };

    //assert_eq!(address.unwrap().to_bech32(None).unwrap(), "addr_test1qramnk9lcxkw76zw88jmfa7mtpaq4qrc7xnhxvs63gxygwd8u68jww00m0nxkwnpy59e2pyyzhrd0z7mpmp04533uk2qm934k7");
    assert_eq!(
        unprotected_header.get(&serde_cbor::Value::Text("hashed".to_owned())),
        Some(&serde_cbor::Value::Bool(false))
    );

    let payload = if let Some(serde_cbor::Value::Bytes(b)) = cose_sig.get(2) {
        b
    } else {
        panic!("I know there is a payload!")
    };

    println!("Payload: {}", std::str::from_utf8(&payload).unwrap());

    let signature = if let Some(serde_cbor::Value::Bytes(b)) = cose_sig.get(3) {
        cml_crypto::Ed25519Signature::from_raw_bytes(b).expect("signature parsing")
    } else {
        panic!("where is the signature?")
    };

    println!("PubkeyHash {:?}", pub_key.hash());
    println!("Signature {:?}", signature);

    assert!(pub_key.verify(&payload, &signature));

    println!("we made it!");
}

Output:

Key: {1: Integer(1), -1: Integer(6), -2: Bytes([168, 10, 72, 213, 154, 34, 49, 106, 172, 142, 27, 134, 175, 72, 3, 135, 54, 159, 34, 122, 229, 103, 17, 131, 103, 175, 85, 46, 60, 199, 149, 24]), 3: Integer(-8)}
Signature: [Bytes([162, 1, 39, 103, 97, 100, 100, 114, 101, 115, 115, 88, 57, 0, 251, 185, 216, 191, 193, 172, 239, 104, 78, 57, 229, 180, 247, 219, 88, 122, 10, 128, 120, 241, 167, 115, 50, 26, 138, 12, 68, 57, 167, 230, 143, 39, 57, 239, 219, 230, 107, 58, 97, 37, 11, 149, 4, 132, 21, 198, 215, 139, 219, 14, 194, 250, 210, 49, 229, 148]), Map({Text("hashed"): Bool(false)}), Bytes([123, 34, 116, 121, 112, 101, 34, 58, 34, 76, 111, 103, 105, 110, 34, 44, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 34, 50, 48, 50, 52, 45, 48, 50, 45, 49, 48, 32, 49, 54, 58, 49, 48, 58, 49, 57, 46, 50, 48, 52, 53, 51, 49, 48, 55, 57, 32, 85, 84, 67, 34, 125]), Bytes([218, 244, 80, 6, 197, 95, 197, 52, 86, 48, 79, 106, 93, 240, 227, 126, 199, 142, 232, 193, 104, 17, 90, 142, 106, 10, 40, 90, 181, 214, 156, 195, 224, 94, 160, 204, 36, 139, 12, 202, 163, 144, 19, 243, 188, 231, 159, 92, 98, 190, 58, 117, 33, 198, 21, 195, 247, 16, 236, 169, 137, 249, 104, 15])]
{Integer(1): Integer(-8), Text("address"): Bytes([0, 251, 185, 216, 191, 193, 172, 239, 104, 78, 57, 229, 180, 247, 219, 88, 122, 10, 128, 120, 241, 167, 115, 50, 26, 138, 12, 68, 57, 167, 230, 143, 39, 57, 239, 219, 230, 107, 58, 97, 37, 11, 149, 4, 132, 21, 198, 215, 139, 219, 14, 194, 250, 210, 49, 229, 148])}
Address: Base(BaseAddress { network: 0, payment: PubKey { hash: Ed25519KeyHash([251, 185, 216, 191, 193, 172, 239, 104, 78, 57, 229, 180, 247, 219, 88, 122, 10, 128, 120, 241, 167, 115, 50, 26, 138, 12, 68, 57]), len_encoding: Canonical, tag_encoding: None, hash_encoding: Canonical }, stake: PubKey { hash: Ed25519KeyHash([167, 230, 143, 39, 57, 239, 219, 230, 107, 58, 97, 37, 11, 149, 4, 132, 21, 198, 215, 139, 219, 14, 194, 250, 210, 49, 229, 148]), len_encoding: Canonical, tag_encoding: None, hash_encoding: Canonical }, encoding: None })
Payload: {"type":"Login","timestamp":"2024-02-10 16:10:19.204531079 UTC"}
PubkeyHash Ed25519KeyHash([251, 185, 216, 191, 193, 172, 239, 104, 78, 57, 229, 180, 247, 219, 88, 122, 10, 128, 120, 241, 167, 115, 50, 26, 138, 12, 68, 57])
Signature Ed25519Signature(daf45006c55fc53456304f6a5df0e37ec78ee8c168115a8e6a0a285ab5d69cc3e05ea0cc248b0ccaa39013f3bce79f5c62be3a7521c615c3f710eca989f9680f)
thread 'main' panicked at src/main.rs:63:5:
assertion failed: pub_key.verify(&payload, &signature)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
CantTouchDis commented 7 months ago

I am sorry for opening the Issue. I figured it out. For other ppl having the same issue:

I was missing the part in the CIP where they mentioned they sign a structure in a different format... Therefore instead of passing payload to verify we need to pass data to verify which is constructed as follows:

    let data = serde_cbor::to_vec(&serde_cbor::Value::Array(vec![
        serde_cbor::Value::Text("Signature1".to_string()),
        cose_sig.get(0).unwrap().clone(),
        serde_cbor::Value::Bytes(vec![]),
        cose_sig.get(2).unwrap().clone(),
    ])).unwrap();

    assert!(pub_key.verify(&data, &signature));

    println!("we made it!");