Emurgo / message-signing

MIT License
26 stars 16 forks source link

Examples of library usage #12

Open olliejm opened 2 years ago

olliejm commented 2 years ago

Hi, sorry if this is not the best place for such questions but I'm struggling to find any examples on how to achieve something with this library.

I'm looking to verify the signature of a payload signed with the dApp injected API function signData(address, payload). When I run this method I get an output of an object like this:

{
  "signature": "<long_hex_string>",
  "key": "<shorter_hex_string>"
}

Now on a server I want to validate this signature as a means of proof of address ownership.

Anyway, I found this example linked from the CIP-0008 readme.

So firstly I tried to do something similar to construct an instance of PublicKey, on a presumption that key from the JSON above is indeed a public key, trying:

let key_bytes = signed_data.key.as_bytes();
let pub_key = cardano_serialization_lib::crypto::PublicKey::from_bytes(key_bytes);

Unfortunately this creates an error of Invalid Public Key size. For good measure I also tried from_bech32 on the string itself, but that results in Malformed public key.

There is one other example linked, which seems to outline how to then verify a signature after getting an instance of PublicKey, but that example constructs the key from a private key - which in my case I don't have.

I believe I'll also need to use PublicKey to check that the address belongs to the key, as well verifying the signature, but I don't think I saw any examples of that unless I missed something.

I may well just be making a really stupid mistake here - as I am new to Rust, Cose, CBOR and all Cardano development beyond the injected dApp APIs. I appreciate that it's very early days for a library like this to have fleshed out documentation with detailed examples, however I'd be very grateful if anyone was able to offer any pointers. Thanks.

Piefayth commented 10 months ago

This is one of the top Google results for this problem. I couldn't find a library that did this in Rust the way that I wanted; I'm sure there is one.

Say you have this flow in your client application:

    const address = await lucid.wallet.address();
    const foo = await lucid.wallet.signMessage(address, fromText("test"));
    const help = lucid.verifyMessage(address, fromText("test"), foo);

The equivalent of verifyMessage can be implemented on your Rust server as such:

pub fn verify_message(
    address: &str,
    key: &str,
    payload: &str,
    signature: &str,
) -> bool {
    let input_address = C::address::Address::from_bech32(address).expect("Input address should have been in bech32 format.");

    let signature_bytes = hex::decode(signature).expect("Signature was not valid hex.");
    let signature = COSESign1::from_bytes(signature_bytes).expect("Signature was not valid COSE.");

    let key_bytes = hex::decode(key).expect("Public key was not valid hex.");
    let cose_key = COSEKey::from_bytes(key_bytes).expect("Key was not valid COSE.");

    let protected_headers = signature.headers().protected().deserialized_headers();
    let signed_address = protected_headers.header(&M::Label::new_text(String::from("address")))
        .expect("No address found in the headers of the signature.");

    let signed_address_bytes = signed_address.as_bytes()
        .expect("Signature contained an address that could not be interpreted as bytes.");

    let cose_algorithm = protected_headers.algorithm_id()
        .expect("Signature did not specify a COSE algorithm_id.")
        .as_int()
        .expect("Signature's specified COSE algorithm_id was not an integer.")
        .as_i32()
        .expect("Could not parse signature  COSE algorithm as i32.");

    let key_algorithm = cose_key.algorithm_id()
        .expect("Key did not specify a COSE algorithm_id.")
        .as_int()
        .expect("Key's specified COSE algorithm_id was not an integer.")
        .as_i32()
        .expect("Could not parse key COSE algorithm as i32.");

    let key_curve = cose_key.header(&M::Label::new_int(
        &M::utils::Int::new_negative(
            M::utils::BigNum::from_str("1").unwrap())
        ))
        .expect("Could not derive key curve from key.")
        .as_int()
        .expect("Key's specified curve was not an integer.")
        .as_i32()
        .expect("Could not parse key COSE algorithm as i32.");

    let key_type = cose_key.key_type().as_int()
        .expect("Could not derive key type from key.")
        .as_i32()
        .expect("Could not parse key COSE algorithm as i32.");

    let public_key_bytes = cose_key.header(&M::Label::new_int(
        &M::utils::Int::new_negative(
            M::utils::BigNum::from_str("2").unwrap())
        )
    )
    .expect("Could not get public key curve from key.")
    .as_bytes()
    .expect("Could not interpret public key header as bytes.");

    let public_key = C::crypto::PublicKey::from_bytes(&public_key_bytes).expect("Could not interpret public key as PublicKey.");
    let cose_payload = signature.payload().expect("No payload included in signed message.");

    let ed25519 = C::crypto::Ed25519Signature::from_bytes(signature.signature())
        .expect("Could not parse Ed25519 signature from signature's signature. (Oof.)");

    let data = signature.signed_data(None, None)
        .expect("There should have been data in the signature's signed data.")
        .to_bytes();

    let input_address_hex = hex::encode(input_address.to_bytes());
    let signed_address_hex = hex::encode(signed_address_bytes);

    let input_address_keyhash = input_address.payment_cred().or_else(|| input_address.staking_cred())
        .expect("Could not derive credentials from address.")
        .to_keyhash()
        .expect("Could not derive keyhash from address.")
        .to_hex();
    let signed_address_keyhash = public_key.hash().to_hex();

    return 
        signed_address_hex == input_address_hex && 
        signed_address_keyhash == input_address_keyhash &&
        cose_algorithm == key_algorithm &&
        cose_algorithm == -8 &&
        key_curve == 6 &&
        key_type == 1 &&
        cose_payload == payload.as_bytes() &&
        public_key.verify(&data, &ed25519)
}

I am not sure on the accuracy of the specifics here, and I bet there are constants somewhere in the library to handle those magic numbers, but this works for me. I lifted the implementation directly from Lucid; just translated it to Rust. If you were to need the reverse, I would suggest cannibalizing the Lucid code as I did. Hope this helps someone...