区块链-NFT 的实现原理

掘金:https://juejin.im/user/1785262612681997

GitHub : https://github.com/af913337456/

出版的书籍:

《1.0-区块链以太坊DApp开发实战》

《2.0-区块链DApp开发:基于以太坊和比特币公链》

NFT (Non-Fungible Token),这2年又火了起来,早在18年已经火过一波。

本文只从写代码实现NFT的技术方案层面去介绍 NFT,不从其金融意义、案例,等层面去谈,因为这类内容可以随便在浏览器搜索到,而我接下来要谈的内容,浅层搜索下,数量不多。

第一步: 制作id

制作id,这是把物质制作成 NFT 的第一步。物质有哪些?一段文字、一张图片,一件衣服等,夸张的说,现实世界的物质,无论是虚拟的(游戏装备)或实质物质,都可以被通证化。

如何操作?

通过第三方技术手段获取物质的唯一标志性中间产物。

因此制作 NFT 第一步,广义于下面等式:

id = F(I)

I = 输入的物质

F = 处理函数,代表一种方法

id 唯一标志性的中间产物

最简单的例子就是哈希函数,不考虑哈希碰撞,它就可以根据不同的内容输出不同的哈希值。思维在这里不要局限于哈希函数。

获取图片的唯一id

这里用图片代表一系列的文件类数据。

我们可以将图片转换成 []byte 字节数组,然后计算其哈希值。这种操作虽然比较简单,但是图片别人却不能访问,看不到;

如果我们想向外部任何人提供图片的读权限,在计算完 id 后,有两种做法:

上传图片到文件服务器,任何人可以通过 url 链接访问。这里的服务器是中心化的;

增加区块链属性。上传文件到 IPFS (ipfs是什么,自行搜索),如此一来,文件别人能访问,同时还具备了区块链的去中心化等属性。其中 IPFS 会在上传完文件后,会使用它的算法,帮你计算好哈希值返回,可以直接用它的作为id。

获取衣服的唯一id

这里用衣服来代表一系列的实际物质。如果获取它们的唯一id呢?做法可以放飞思维去思考,比如可以:

衣服的出厂信息、扫描内容、照片,等系列关于它的信息,数据化,然后用这些数据制作成文件,最后参考图片的做法。

第二步:通证化

第一步中获取了物质的id,现在要把它们通证化。切记一点:目前公认的 NFT 都是基于区块链公链的,那么以后是不是会一直这样呢?不一定,说不准出来了新的共识。

基于不同公链的流程

通证化的流程如下:

选择一条区块链公链。这里的选择会决定后面智能合约等系统组件的技术栈,这一点很核心;

在所选的公链上开发智能合约;

所开发的智能合约需要遵循一些基础约定,比如至少能保证物质的id能达到验证去重,什么意思呢?意思是,如果 A 在今天上传了 id=1 到链上,明天 B 也上传同个 id=1 到链上,合约要能告诉 B,你不能上传了,id 已经存在;

部署智能合约到链上,此时它变成 DApp;

通过发交易的方式,调用该智能合约的方法,将id等相关数据存储到链上。

NFT 的智能合约

NFT 智能合约可以基于不同的公链开发,它不局限于任何一条公链。不同公链的智能合约方案实现也不同,下面以 以太坊 公链举例说明。

在以太坊上面,开发 NFT 智能合约,已经有很多标准,比如 ERC-721 \1155 \998,各有各的特点,但它们的特点是在基础属性上拓展而来的。(各标准文档: https://eips.ethereum.org/EIPS/eip-721)

如果选择 ERC-721 标准开发 NFT 智能合约,在元数据存储部分,就有 tokenUrl 这项,它相当于物质的唯一id,像下面的样子, _tokenURIs 存储的就是通证当前计数id与其对应的 tokenUrl,这里的tokenUrl 是字符串格式,一般是文件url,存储在 IPFS 或其他服务上面的文件的链接,但不局限于链接,也可以是其它的内容。

// 伪代码 contract MyERC721 is IERC721Metadata, ... { ... mapping(uint256 => address) private _tokenOwner; mapping(uint256 => string) private _tokenURIs; uint256 public tokenCounter; // 计数,当前总的 NFT 的数量,累增 constructor () public ERC721 ("name", "symbol"){ tokenCounter = 0; } // 外部调用方,调用这个函数,传参数:tokenURI 即物质的id,tokenURI 唯一 function createNFT(string memory tokenURI) public returns (uint256) { uint256 tokenId = tokenCounter; _mint(msg.sender, tokenId); // 将交易发送者和当前的 tokenId 绑定 _setTokenURI(tokenId, tokenURI); // tokenId 映射到 tokenUrl tokenCounter = tokenCounter + 1; // 累加 return tokenId; } // _exists 函数判断 tokenId 是否存在,_tokenOwner[tokenId] // 根据 id 读取对应的 url function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId)); return _tokenURIs[tokenId]; } // 根据 tokenId 和 url 建立 map 数据关系 function _setTokenURI(uint256 tokenId, string memory uri) internal { require(_exists(tokenId)); // _exists _tokenURIs[tokenId] = uri; } ... // 省略系列接口,包含读接口 }

上面的 tokenUrl 是标准要求的存储数据项。整个合约具备下面约束功能:

NFT 持有者,即 msg.sender(owner) 和 tokenId 一对多关系,代表一个人可以拥有多个 NFT;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzwfys.html