<p>For anyone looking for a more exact solution, below is an explanation for how I implemented this in JavaScript.</p>
<p>To understand the authentication flow, I started by reading <a href="https://www.toptal.com/ethereum/one-click-login-flows-a-metamask-tutorial#how-the-login-flow-works" rel="noreferrer">this article</a>. While the article is for Metamask and Ethereum, the same flow can be applied with Nami (or Eternl, etc) and Cardano. It goes:</p>
<ol>
<li>Connect to wallet -&gt;<code>window.cardano.enable()</code></li>
<li>Send public address to backend -&gt; <code>window.cardano.getRewardAddress()</code></li>
<li>Store public address and a signature (description + nonce) in database</li>
<li>Return hex string of signature to frontend, and have your wallet extension sign it. This requires the user to enter their staking password. If there is a way to get around this, I'd love to know.</li>
</ol>
<ul>
<li>For most wallets -&gt; call <code>signData(publicAddress, signature)</code> on the object returned in step 1.</li>
<li>For Nami (last time I checked the newer method didn't work right) -&gt; <code>window.cardano.signData(publicAddress, signature)</code></li>
</ul>
<ol start="5">
<li>Send the signed signature to backend</li>
<li>Verify that signature was signed by correct wallet (explained below)</li>
<li>Return http cookie with JWT, signed by backend secret key (won't detail how this is done, as there are many non-crypto tutorials of this online)</li>
</ol>
<p>The most difficult part is verifying the signed nonce. Using <a href="https://github.com/Berry-Pool/nami-wallet/blob/faf5a7a7993defbb7267a25a5f4cef4e06d39c7d/MessageSigning.md" rel="noreferrer">this article</a> from Thom's answer, we can see that verification relies on two packages: <a href="https://www.npmjs.com/package/@emurgo/cardano-serialization-lib-nodejs" rel="noreferrer">cardano-serialization-lib-nodejs</a> and <a href="https://github.com/Emurgo/message-signing" rel="noreferrer">emurgo_message_signing</a>.</p>
<p>The first can be added to the backend server through npm or yarn. The second is more difficult, as it is written in Rust. To create a javascript-friendly version, you must first install Rust and wasm-pack. Then, you can clone emurgo_message_signing, start a terminal in that project's &quot;rust&quot; directory, and enter <code>wasm-pack build --target nodejs</code>. The &quot;rust&quot; directory can then be moved into your project's backend directory and imported like a normal package: <code>const MS = require('./message-signing/rust/pkg/emurgo_message_signing')</code>.</p>
<p>If you are using TypeScript and ES6, you may run into additional problems because tsc doesn't copy the additional WebAssembly files used by emurgo_message_signing. I got around this by creating a post-install command in my backend's package.json file that copies them over.</p>
<p>Using Thom's example and the example file in emurgo_message_signing, you can get a sense for how to create and verify the signature. This is how I implemented it:</p>
<pre><code>import * as Serialization from '@emurgo/cardano-serialization-lib-nodejs';
import * as MessageSigning from './message_signing/rust/pkg/emurgo_message_signing';
import { randomBytes } from 'crypto';

// Generate a random string of the specified length, consisting of the specified characters
function randomString(
    length: number = 64,
    chars: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
): string {
    // Check for valid parameters
    if (length &lt;= 0 || length &gt; 2048) throw new Error('Length must be bewteen 1 and 2048.');
    const charsLength = chars.length;
    if (charsLength &lt; 10 || chars.length &gt; 256) throw new Error('Chars must be bewteen 10 and 256.');
    // Generate random bytes
    const bytes = randomBytes(length);
    // Create result array
    let result = new Array(length);
    // Fill result array with bytes, modified to consist of the specified characters
    let cursor = 0;
    for (let i = 0; i &lt; length; i++) {
        cursor += bytes[i];
        result[i] = chars[cursor % charsLength];
    }
    // Return result as string
    return result.join('');
}

// Generate signable nonce, which includes human-readable description
// Returns hex string
export const generateNonce = async (
    description: string = 'Please sign this message so we can verify your wallet:',
    length: number = 64,
) =&gt; {
    if (length &lt;= 0 || length &gt; 2048) throw new Error('Length must be bewteen 1 and 2048');
    // Generate nonce (payload)
    const payload = randomString(length);
    // Return description + nonce
    return Buffer.from(`${description} ${payload}`).toString('hex');
}

/**
 * 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) =&gt; {
    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) =&gt; {
    return Buffer.from(payloadCose).compare(Buffer.from(payload, 'hex')) === 0;
};

const verifyAddress = (address: string, addressCose: Serialization.Address, publicKeyCose: Serialization.PublicKey) =&gt; {
    const checkAddress = Serialization.Address.from_bytes(Buffer.from(address, 'hex'));
    if (addressCose.to_bech32() !== checkAddress.to_bech32()) return false;
    // 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;
};
</code></pre>

CC BY-SA 4.0
2022-04-17T04:34:29.190Z
Any idea why I am getting when calling verifySignedMessage: `ERROR [ExceptionsHandler] Deserialization failed in Address because: Encountered unknown address header 0b10101101`
Jingles
CC BY-SA 4.0
2022-04-17T06:12:51.730Z
Getting this error on: `const addressCose: Serialization.Address = Serialization.Address.from_bytes(headerCBORBytes);`
Jingles
CC BY-SA 4.0
2022-04-17T20:07:24.737Z
@Jingles I'm not sure what's causing that error, but I think I have an idea. After writing my original answer, the authentication processed changed a bit (unless you're using Nami, because their new code doesn't work correctly last time I checked). It's probably an error with the coseSign1Hex parameter being passed into verifySignedMessage. Instead of signing the message with window.cardano.signData, you have to use the signData function on the object returned by window.cardano.enable(). I will update my answer now
Matt Halloran
CC BY-SA 4.0
2022-04-17T20:37:14.550Z
@Jingles Here is the CIP that caused the change: https://github.com/cardano-foundation/CIPs/pull/148
Matt Halloran
CC BY-SA 4.0
2022-04-18T01:57:50.217Z
Does it still work for you? I tried both Nami and Eternl. Using Nami older method (`window.cardano.signData`) returned `Code: 4. If a wallet enforces data format requirements, this error signifies that the data did not conform to valid formats.`. Where both inputs `publicAddress` and `signature` are string, for example `addrblabla` and `abcabcabc`.
Jingles
CC BY-SA 4.0
2022-04-18T02:05:07.663Z
Using Eternl, I managed to `signData`. Then send to the backend, with the code you provided,`let isValid = await verifySignedMessage('addrblabla', 'abcabcabc', '<this is a long string returned after signData using Eternl>');` But this returns the `Deserialization failed in Address because: Encountered unknown address header 0b10101101` at `Serialization.Address.from_bytes(headerCBORBytes)`.
Jingles
CC BY-SA 4.0
2022-04-18T15:26:06.487Z
@Jingles Sorry, I'm not sure what the problem is then. The steps I provided are working for my site. If it helps, you can check out my code. This file is where most of the frontend magic happens: https://github.com/MattHalloran/Vrooli/blob/master/packages/ui/src/utils/walletIntegration.ts
Matt Halloran
CC BY-SA 4.0
2022-04-19T04:32:55.950Z
Thanks. The codes in your repo works. I replaced the backend codes. Had issues on `verifyAddress`.
Jingles
CC BY-SA 4.0
2022-04-19T04:45:40.723Z
@Jingles Glad I could help. Thanks for helping me improve my answer
Matt Halloran
CC BY-SA 4.0
2021-12-05T22:02:06.033Z
2022-04-19T04:44:36.240Z
2022-04-19T04:44:36.240Z
Matt Halloran
<p>There are a few options for something like this, Nami specifically supports <code>signData</code> which lets the user sign some arbitrary data with their private key. Your backend can then take this signature and public key to authenticate the user behind the wallet.</p>
<p>So if your data is the same, it will produce a unique signature for each wallet that signs it. You can store this signature in the database (which is totally safe since the signature isn't private information), and use it to associate a particular user with a <code>project</code> or whatever other information you want to store in your database.</p>
<p>An alternative (more centralized) approach is to still have some form of username:password authentication, but instead of letting the user fill in their own password, just sign some data with their wallet and use that signature as their password. When a user logs in, you just need to verify that the password (read: signature) is signed by their wallet. Then you can still use all the standard session handling and user association of a regular application, with some added decentralization and security.</p>
<p>There's some example code here that implements message signing with Nami and verification using Emurgo's message_signing library:
<a href="https://github.com/Berry-Pool/nami-wallet/blob/faf5a7a7993defbb7267a25a5f4cef4e06d39c7d/MessageSigning.md" rel="nofollow noreferrer">https://github.com/Berry-Pool/nami-wallet/blob/faf5a7a7993defbb7267a25a5f4cef4e06d39c7d/MessageSigning.md</a></p>

CC BY-SA 4.0
2021-12-03T17:03:48.077Z
Thank you! This is exactly the type of reply I was looking for.
Matt Halloran
CC BY-SA 4.0
2021-12-03T16:56:00.873Z
2021-12-03T16:56:00.873Z
Thom
<p>I am building an application that will hopefully offer a login option via Nami wallet, rather than the typical email/password. I understand how this approach can work for on-chain data, but is it possible (and if so, what are the limitations?) to use wallet authentication to associate users with other site data?</p>
<p>For example, my site will allow users to create &quot;projects&quot; to organize links and relevant data from the website. This data will be stored off-chain in a database. To ensure data privacy, the backend server must be able to verify that the user has access to the correct wallet. Is there a way to generate a site-specific password (i.e. private key) that can be securely stored on the wallet, then sent to the backend for authentication?</p>
<p>One alternative would be to remove centralized storage of private data altogether, and instead store data off-site where only the user can access (such as local file storage). Then this could be loaded using the File System Access API. This is more aligned with the spirit of decentralization, but is not yet widely supported and raises issues for multi-device support.</p>
<p>Of course I could skip the Nami login altogether and stick to the traditional email/password approach for authentication. Then, users would connect to Nami later on when they need to perform an action that involves the wallet. The downside here is the requirement of an email and a longer log-in/sign-up process. Thoughts?</p>

<wallet><nami-wallet><cardano-serialization-lib>
How to establish trust between a user's wallet, and the backend of a website?
# How to establish trust between a user's wallet, and the backend of a website?

(Asked by: username_0 on 2021-12-03)

I am building an application that will hopefully offer a login option via Nami wallet, rather than the typical email/password. I understand how this approach can work for on-chain data, but is it possible (and if so, what are the limitations?) to use wallet authentication to associate users with other site data?

For example, my site will allow users to create "projects" to organize links and relevant data from the website. This data will be stored off-chain in a database. To ensure data privacy, the backend server must be able to verify that the user has access to the correct wallet. Is there a way to generate a site-specific password (i.e. private key) that can be securely stored on the wallet, then sent to the backend for authentication?

One alternative would be to remove centralized storage of private data altogether, and instead store data off-site where only the user can access (such as local file storage). Then this could be loaded using the File System Access API. This is more aligned with the spirit of decentralization, but is not yet widely supported and raises issues for multi-device support.

Of course I could skip the Nami login altogether and stick to the traditional email/password approach for authentication. Then, users would connect to Nami later on when they need to perform an action that involves the wallet. The downside here is the requirement of an email and a longer log-in/sign-up process. Thoughts?

**Tags:** wallet, nami-wallet, cardano-serialization-lib

---

## 2 answers

### Answer by username_0 on 2021-12-05. Score: 14 | Accepted answer

For anyone looking for a more exact solution, below is an explanation for how I implemented this in JavaScript.
To understand the authentication flow, I started by reading this article. While the article is for Metamask and Ethereum, the same flow can be applied with Nami (or Eternl, etc) and Cardano. It goes:

Connect to wallet ->window.cardano.enable()
Send public address to backend -> window.cardano.getRewardAddress()
Store public address and a signature (description + nonce) in database
Return hex string of signature to frontend, and have your wallet extension sign it. This requires the user to enter their staking password. If there is a way to get around this, I'd love to know.


For most wallets -> call signData(publicAddress, signature) on the object returned in step 1.
For Nami (last time I checked the newer method didn't work right) -> window.cardano.signData(publicAddress, signature)


Send the signed signature to backend
Verify that signature was signed by correct wallet (explained below)
Return http cookie with JWT, signed by backend secret key (won't detail how this is done, as there are many non-crypto tutorials of this online)

The most difficult part is verifying the signed nonce. Using this article from Thom's answer, we can see that verification relies on two packages: cardano-serialization-lib-nodejs and emurgo_message_signing.
The first can be added to the backend server through npm or yarn. The second is more difficult, as it is written in Rust. To create a javascript-friendly version, you must first install Rust and wasm-pack. Then, you can clone emurgo_message_signing, start a terminal in that project's "rust" directory, and enter wasm-pack build --target nodejs. The "rust" directory can then be moved into your project's backend directory and imported like a normal package: const MS = require('./message-signing/rust/pkg/emurgo_message_signing').
If you are using TypeScript and ES6, you may run into additional problems because tsc doesn't copy the additional WebAssembly files used by emurgo_message_signing. I got around this by creating a post-install command in my backend's package.json file that copies them over.
Using Thom's example and the example file in emurgo_message_signing, you can get a sense for how to create and verify the signature. This is how I implemented it:
import * as Serialization from '@emurgo/cardano-serialization-lib-nodejs';
import * as MessageSigning from './message_signing/rust/pkg/emurgo_message_signing';
import { randomBytes } from 'crypto';

// Generate a random string of the specified length, consisting of the specified characters
function randomString(
    length: number = 64,
    chars: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
): string {
    // Check for valid parameters
    if (length <= 0 || length > 2048) throw new Error('Length must be bewteen 1 and 2048.');
    const charsLength = chars.length;
    if (charsLength < 10 || chars.length > 256) throw new Error('Chars must be bewteen 10 and 256.');
    // Generate random bytes
    const bytes = randomBytes(length);
    // Create result array
    let result = new Array(length);
    // Fill result array with bytes, modified to consist of the specified characters
    let cursor = 0;
    for (let i = 0; i < length; i++) {
        cursor += bytes[i];
        result[i] = chars[cursor % charsLength];
    }
    // Return result as string
    return result.join('');
}

// Generate signable nonce, which includes human-readable description
// Returns hex string
export const generateNonce = async (
    description: string = 'Please sign this message so we can verify your wallet:',
    length: number = 64,
) => {
    if (length <= 0 || length > 2048) throw new Error('Length must be bewteen 1 and 2048');
    // Generate nonce (payload)
    const payload = randomString(length);
    // Return description + nonce
    return Buffer.from(`${description} ${payload}`).toString('hex');
}

/**
 * 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 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;
};

> **Comments:**
> **username_1**: Any idea why I am getting when calling verifySignedMessage: `ERROR [ExceptionsHandler] Deserialization failed in Address because: Encountered unknown address header 0b10101101`  
> **username_1**: Getting this error on: `const addressCose: Serialization.Address = Serialization.Address.from_bytes(headerCBORBytes);`  
> **username_0**: @Jingles I'm not sure what's causing that error, but I think I have an idea. After writing my original answer, the authentication processed changed a bit (unless you're using Nami, because their new code doesn't work correctly last time I checked). It's probably an error with the coseSign1Hex parameter being passed into verifySignedMessage. Instead of signing the message with window.cardano.signData, you have to use the signData function on the object returned by window.cardano.enable(). I will update my answer now  
> **username_0**: @Jingles Here is the CIP that caused the change: https://github.com/cardano-foundation/CIPs/pull/148  
> **username_1**: Does it still work for you? I tried both Nami and Eternl. Using Nami older method (`window.cardano.signData`) returned `Code: 4. If a wallet enforces data format requirements, this error signifies that the data did not conform to valid formats.`. Where both inputs `publicAddress` and `signature` are string, for example `addrblabla` and `abcabcabc`.  
> **username_1**: Using Eternl, I managed to `signData`. Then send to the backend, with the code you provided,`let isValid = await verifySignedMessage('addrblabla', 'abcabcabc', '<this is a long string returned after signData using Eternl>');` But this returns the `Deserialization failed in Address because: Encountered unknown address header 0b10101101` at `Serialization.Address.from_bytes(headerCBORBytes)`.  
> **username_0**: @Jingles Sorry, I'm not sure what the problem is then. The steps I provided are working for my site. If it helps, you can check out my code. This file is where most of the frontend magic happens: https://github.com/MattHalloran/Vrooli/blob/master/packages/ui/src/utils/walletIntegration.ts  
> **username_1**: Thanks. The codes in your repo works. I replaced the backend codes. Had issues on `verifyAddress`.  
> **username_0**: @Jingles Glad I could help. Thanks for helping me improve my answer  

---

### Answer by username_2 on 2021-12-03. Score: 3

There are a few options for something like this, Nami specifically supports `signData` which lets the user sign some arbitrary data with their private key. Your backend can then take this signature and public key to authenticate the user behind the wallet.

So if your data is the same, it will produce a unique signature for each wallet that signs it. You can store this signature in the database (which is totally safe since the signature isn't private information), and use it to associate a particular user with a `project` or whatever other information you want to store in your database.

An alternative (more centralized) approach is to still have some form of username:password authentication, but instead of letting the user fill in their own password, just sign some data with their wallet and use that signature as their password. When a user logs in, you just need to verify that the password (read: signature) is signed by their wallet. Then you can still use all the standard session handling and user association of a regular application, with some added decentralization and security.

There's some example code here that implements message signing with Nami and verification using Emurgo's message\_signing library:
<https://github.com/Berry-Pool/nami-wallet/blob/faf5a7a7993defbb7267a25a5f4cef4e06d39c7d/MessageSigning.md>

> **Comments:**
> **username_0**: Thank you! This is exactly the type of reply I was looking for.  

---

