如何使用Nami创建Cardano一键登录

Cardano是世界上最受欢迎的加密货币之一,目前在写这篇文章时,它的市值为180亿美元,同时还有大量的社交媒体粉丝。

然而,在试图创建我的Cardano NFT铸币平台Saturn时,我需要阅读关于以太坊的Metamask的技术文章,比如这篇文章,以学习如何创建一个一键式加密货币登录系统,然后将这些知识应用于Cardano。这些知识并不总是转移得很干净,所以我写这篇文章是为了帮助那些建立Cardano应用程序和Dapps的人使用Nami钱包创建一个Cardano的登录系统

Nami Cardano钱包
注意:这个方法也适用于所有其他Cardano钱包,如Eternl、Flint、Gero、Typhoon、Nufi等。

首先,我们需要快速了解一下传统登录系统的工作原理。传统的登录系统会要求提供用户名和密码来注册和登录。用户名和密码将通过安全的https连接发送,并到达后端。一旦到达后端,系统将在数据库中查找用户名,并将发送的密码与数据库条目中的适当的盐进行散列,然后将计算出的密码散列与存储在数据库中的散列进行比较。

传统的登录步骤。

要求提供用户名和密码
通过https向服务器发送用户名和密码
从数据库中获取用户名数据(密码哈希值和盐)。
用盐对给定的密码进行散列,并与存储在数据库中的密码散列进行比较
如果两者匹配,则向用户发送JWT/刷新令牌,让其登录。

使用bcrypt进行密码哈希的传统登录方式
传统的登录系统已经为我们服务了很长时间,但也有一些主要的缺点。

传统登录系统的弊端。

我们经常需要为每个网站注册一个新的电子邮件/用户名/密码
我们经常忘记哪个网站的密码
一些网站可能不会正确地哈希密码(或根本没有!),这可能会被黑客攻击。
如果我们在许多不同的网站上使用相同的密码,我们的密码就可能被盗。
如果我们的密码太容易被猜到,我们可能会受到彩虹表的攻击。
谷歌和单点登录系统有帮助,但它们涉及将你的所有数据交给这些公司。考虑到这些缺点,我们想使用一个新的登录系统。

Cardano标志
加密货币登录系统。

Crypto登录系统的工作方式与传统的登录系统类似,但使用钱包的公共地址作为用户名,用地址的私钥签名的信息作为密码。

我们不希望直接使用钱包中的公钥和私钥,因为私钥不应该离开钱包。

为了做到这一点,当用户试图使用加密登录并发送他们的公共地址时,我们需要在后台生成一个nonce。nonce是一个一次性的密码,我们将要求用户签名,以便我们知道他们拥有与公共地址相关的私钥。(注意:用户每次尝试登录时都会得到一个新的nonce。)

我们用nonce回应客户的请求,客户的加密钱包用私钥签署nonce,客户将这个签署的信息连同他们的公共地址和nonce一起发回服务器。最后,我们的后端检查该用户的nonce是否正确,并验证该签名信息实际上是由该用户的公钥签署的。

密码登录步骤。

要求用户进入应用程序(简单的按钮点击)
Crypto钱包向服务器发送公共地址
服务器存储并发回一个nonce
客户端加密钱包用他们的私钥签署nonce,并将其发送给服务器
服务器检查nonce和签署的信息,以确保它是由钱包的私钥签署的。
服务器发回JWT/刷新令牌
image

Nami登录
正如你所看到的,加密登录系统没有传统登录系统的任何缺点。唯一的缺点是用户需要拥有一个钱包。

如何使用Nami建立一个加密货币登录系统。

现在我们已经解决了技术细节问题,是时候用Nami建立一个加密货币登录系统了。

我们将需要什么?

一个前端
一个后端
一个数据库
一个Nami钱包(浏览器扩展)。
你可以使用NextJS(它支持后端api路由)和PostgresSQL。在这篇文章中,我将只展示Nami钱包/Cardano钱包的代码,因为关于Nextjs/Postgres的文章很多。

NextJS Javascript框架
连接到Nami。

首先,我们需要在用户点击按钮时连接我们的Nami钱包。下面是一个简单的函数,用于连接我们的Cardano。

export const connect = async () => {
    const cardano = window.cardano;
    const api = await cardano.nami.enable();
    返回api。
};

这是一个非常、非常简单的函数。我们用于NFT铸币平台Saturn的函数在错误处理和网络检测方面更为复杂,但上面的代码暂时可以完成工作。如果你使用其他钱包,如Flint,请将cardano.nami.enable()替换为cardano.flint.enable(),并对其他Cardano钱包使用类似的模式。

注意:关于如何与Nami钱包对接的实时例子。我已经创建了ADA Blobs的开源 repo,这是在2021年发布的第一个Cardano智能合约拍卖NFT项目。

现在我们已经连接了我们的Nami钱包,我们需要创建一个API,让我们的后端创建一个Nonce,将其存储在数据库中,并将其发回给客户端。

用Nami对Nonce进行签名。

当客户端收到Nonce时,我们需要像这样用我们的Cardano钱包进行签名。

export const signData = async (api, nonce) => {
    const hexAddresses = await api.getUsedAddresses();
    const messageToSign = toHex(nonce);
    const signedMessage = await api.signData(hexAddresses[0], messageToSign);
    return signedMessage;
};
export const toHex = (bytes) => Buffer.from(bytes).toString('hex');

注意:"api "是我们在之前的connect函数中返回的api。这里我是直接传入nonce,但你也可以像我对Saturn所做的那样,将nonce作为一个字符串与一些附加数据一起传入。现在我们需要向服务器发送数据以进行验证!
image

验证服务器上的签名。

我们现在需要把我们的地址、nonce和签名的nonce传回给服务器。我们需要把这三样东西都送出去,因为服务器必须检查你签署的nonce是否真的是他们发给你的nonce。

现在,有趣的事情开始了。为了验证这个签名,我们需要这个代码。

export const verifySignature = async (address: string, payload: string, signature: any) => {
const coseSign1Hex = signature.signature;
const coseKey = signature.key;
const coseSign1 = Message.COSESign1.from_bytes(Buffer.from(coseSign1Hex, 'hex'));
const payloadCose = coseSign1.payload();
// Verify Payload here
const protectedHeaders = coseSign1.headers().protected().deserialized_headers();const addressCose = Cardano.Address.from_bytes(protectedHeaders.header(Message.Label.new_text('address')).as_bytes());
const addressCose = Cardano.Address.from_bytes(protectedHeaders.header(APILoader.Message.Label.new_text('address')).as_bytes());
const key = Message.COSEKey.from_bytes(Buffer.from(coseKey, 'hex'));
const publicKeyBytes = key.header(Message.Label.new_int(APILoader.Message.Int.new_negative(Message.BigNum.from_str('2')))).as_bytes();
const publicKeyCose = Cardano.PublicKey.from_bytes(publicKeyBytes);
// Verify Address here
const ed25519Signature = Cardano.Ed25519Signature.from_bytes(coseSign1.signature());
const data = coseSign1.signed_data().to_bytes();
const verified = publicKeyCose.verify(data, ed25519Signature);
return verified;

哦,该从哪里开始呢?上面是验证消息签名所需的代码。截至2022年8月18日,我对Cardano的主要批评之一是缺乏简单的消息签名库,你可以看到。

在上面的代码中,Message指的是消息签名库(下面是Github),Cardano指的是序列化库(下面也是Github)

该代码使用签名密钥,获得签名的有效载荷,并与给定的有效载荷(我们的nonce)进行比较,然后代码检查以确保我们发送给他们的地址与签名上的地址相同。你看到的Ed25519签名是椭圆曲线加密。

我们在这里使用的库是。

emurgo消息签名库GitHub - Emurgo/message-signing
Cardano序列化库 GitHub - Emurgo/cardano-serialization-lib: This is a library, written in Rust, for serialization & deserialization of data structures used in Cardano's Haskell implementation of Alonzo along with useful utility functions.
Nami GitHub - Berry-Pool/nami-wallet: Nami Wallet is a browser based wallet extension to interact with the Cardano blockchain.
我强烈建议你看一下这些库的文档。我在上面的代码中评论的验证地址的代码特别长,但存在于Nami的开放源码 repo中。

公钥私钥信息签名
完成了。

在我们验证了用户用他们的私钥签署了一条消息,并且该消息是我们发送给他们的nonce之后,我们可以通过向他们发送JWT令牌和刷新令牌来登录用户。这与传统的登录系统的工作方式完全相同,所以你可以使用这部分的许多资源。