nami icon indicating copy to clipboard operation
nami copied to clipboard

Clarification on CIP-0030 signData

Open MattHalloran opened this issue 3 years ago • 3 comments

Hello, I'm building a dapp that uses signData for its authentication process with the backend. I'm having difficulty figuring out how to upgrade from the deprecated signData function to the new one. I am able to sign the data the same as before, but the difficulty lies in interpreting the { key, signature } result. Previously, it returned a COSESign1 hex string. With the hex, I could verify the signData like so:

import * as Serialization from '@emurgo/cardano-serialization-lib-nodejs';
import * as MessageSigning from './message_signing/rust/pkg/emurgo_message_signing';

/**
 * Determines if a wallet address signed a message (payload)
 * @param address Serialized wallet address
 * @param payload Serialized payload (i.e. message with nonce)
 * @param coseSign1Hex Hex string of signed payload (signed by user's wallet)
 * @returns True if payload was signed by wallet address
 */
export const verifySignedMessage = (address: string, payload: string, coseSign1Hex: string) => {
    const coseSign1 = MessageSigning.COSESign1.from_bytes(Buffer.from(coseSign1Hex, 'hex'));
    const payloadCose: Uint8Array | undefined = coseSign1.payload();

    if (!payloadCose || !verifyPayload(payload, payloadCose)) {
        throw new Error('Payload does not match');
    }

    const protectedHeaders: MessageSigning.HeaderMap = coseSign1
        .headers()
        .protected()
        .deserialized_headers();
    const headerCBORBytes: Uint8Array | undefined = protectedHeaders.header(MessageSigning.Label.new_text('address'))?.as_bytes();
    if (!headerCBORBytes) {
        throw new Error('Failed to convert header to bytes');
    }
    const keyId: Uint8Array | undefined = protectedHeaders.key_id();
    if (!keyId) {
        throw new Error('Failed to get keyId from header');
    }
    const addressCose: Serialization.Address = Serialization.Address.from_bytes(headerCBORBytes);
    const publicKeyCose = Serialization.PublicKey.from_bytes(keyId);

    if (!verifyAddress(address, addressCose, publicKeyCose))
        throw new Error('Could not verify because of address mismatch');

    const signature = Serialization.Ed25519Signature.from_bytes(coseSign1.signature());
    const data = coseSign1.signed_data().to_bytes();
    return publicKeyCose.verify(data, signature);
};

const verifyPayload = (payload: string, payloadCose: Uint8Array) => {
    return Buffer.from(payloadCose).compare(Buffer.from(payload, 'hex')) === 0;
};

const verifyAddress = (address: string, addressCose: Serialization.Address, publicKeyCose: Serialization.PublicKey) => {
    const checkAddress = Serialization.Address.from_bytes(Buffer.from(address, 'hex'));
    if (addressCose.to_bech32() !== checkAddress.to_bech32()) return false;
    // check if BaseAddress
    try {
        const baseAddress: Serialization.BaseAddress | undefined = Serialization.BaseAddress.from_address(addressCose);
        if (!baseAddress) {
            throw new Error('Failed to get base address from addressCose');
        }
        //reconstruct address
        const paymentKeyHash = publicKeyCose.hash();
        const stakeKeyHash: Serialization.Ed25519KeyHash | undefined = baseAddress.stake_cred().to_keyhash();
        if (!stakeKeyHash) {
            throw new Error('Failed to find stake key hash');
        }
        const reconstructedAddress = Serialization.BaseAddress.new(
            checkAddress.network_id(),
            Serialization.StakeCredential.from_keyhash(paymentKeyHash),
            Serialization.StakeCredential.from_keyhash(stakeKeyHash)
        );
        if (
            checkAddress.to_bech32() !== reconstructedAddress.to_address().to_bech32()
        )
            return false;

        return true;
    } catch (error) {
        console.error('Caught error verifying address', error);
    }
    // check if RewardAddress
    try {
        //reconstruct address
        const stakeKeyHash = publicKeyCose.hash();
        const reconstructedAddress = Serialization.RewardAddress.new(
            checkAddress.network_id(),
            Serialization.StakeCredential.from_keyhash(stakeKeyHash)
        );
        if (
            checkAddress.to_bech32() !== reconstructedAddress.to_address().to_bech32()
        )
            return false;

        return true;
    } catch (error) {
        console.error('Caught error verifying address', error)
    }
    return false;
};

From what I understand, the signature field of the new return object is also a COSESign1 hex. If I try using that field (and ignoring the key), I get an error when retrieving the keyid (makes sense). I tried using the key to calculate the keyid like so:

    const newKey = MessageSigning.COSEKey.from_bytes(Buffer.from(keyHex, 'hex'));
    const keyId = newKey.key_id();

, but that didn't work. Is there something I'm missing?

MattHalloran avatar Jan 19 '22 00:01 MattHalloran

Client side workaround we're using for now:

if (walletName === 'nami') {
  signature = await window.cardano.signData(publicKey, toHex(message));
} else {
  signature = await window.cardano[walletName].signData(publicKey, toHex(message));
}

Fall back to the old implementation if a message has to besigned by nami. On other wallets, e.g. CCVault, use the CIP-0030 v0.1.0 standard.

eddex avatar Jan 20 '22 19:01 eddex

I've looked at the current implementation for signData. Something like this seems to work for obtaining the public key.

if (key) {
    const coseKey = MS.COSEKey.from_bytes(Buffer.from(key, 'hex'));
    const publicKeyBytes = coseKey.header(
        MS.Label.new_int(
            MS.Int.new_negative(MS.BigNum.from_str('2'))
        )
    ).as_bytes();
    publicKeyCose = Cardano.PublicKey.from_bytes(publicKeyBytes);
} else {
    publicKeyCose = Cardano.PublicKey.from_bytes(protectedHeaders.key_id());
}

To be honest I need to read up un this some more to get a more complete picture.

mad2sm0key avatar Jan 21 '22 12:01 mad2sm0key

Any updates when Nami will be up to date with CIP?

jinglescode avatar Jun 15 '22 06:06 jinglescode