ISC-8017
討論連結: https://ethereum-magicians.org/t/add-erc-erc-7593/17986
前言
iSunCoin Standard Consensus 8017 是基於 ERC-721 的區塊鏈存證標準,這份標準展示了如何從智能合約中取得特定的區塊鏈證據文件,這些文件同時受到許可權保護。擁有這些許可權的持有者可以使用一個新功能,share(),來分享這個證明。使用share()之後,原始NFT擁有者不會失去他們的許可權。被分享方將接收到一個新的NFT代幣,其TokenID增加1,但所有其他參數保持不變。因此,我們提出一個新的以太坊提案來開發這樣一個功能。
總體來說,這提案解決了以下問題:
- 我們通過將資產證據以NFT形式鑄造在區塊鏈上,每份文件都被賦予了一個獨一無二且不可更改的身份標識,每份文件都具有區塊鏈上的不可爭議的身份,從而提高了文件的安全性和信任度也保證了其內容的真實性並防止篡改。
- 引入的share()函數允許報告的擁有者在不失去對原始NFT的控制的情況下,將報告以新的NFT形式分享給指定地址,從而實現了對敏感信息的靈活分發和準確追踪。
- 只有NFT的持有者才能查看完整報告的內容,這增強了對敏感數據的訪問控制,尤其適用於需要嚴格數據保密的領域。
- 遵循ERC-721標準確保了這些NFT能夠在不同的平台和應用之間交互和兼容,使得系統更加通用和易於訪問。
關於ISC-8017
該標準定義了一套用於創建、分享和管理代表生成的報告的NFT的操作。每個報告NFT必須包含元數據,詳細描述報告的名稱、報告開始時間和報告結束時間、是否為公開的,遵循ISC-8017接口中定義的結構。鑄造功能將創建一個新的NFT並分配給它一個唯一的令牌ID,確保每個報告在技術上是不同的。儘管每個報告的內容(元數據)可能相同,但當使用share()函數分享報告時,系統將報告的元數據複製到目標地址,新創造的NFT將有一個新的、增加的令牌ID,從而保持每個NFT的唯一性。報告NFT的分享應通過專用功能進行,允許將其轉移到指定地址,同時保持可追踪性。該標準的實現必須符合ERC-721協議,以確保在不同平台之間的互操作性。本規範建議實施額外的安全措施,以防止未經授權的訪問和修改報告NFT。
該接口旨在統一增強數據完整性和可驗證性的區塊證據標準。我們設計了一種基於ERC-721標準的區塊鏈證據格式,該格式結合了在區塊鏈上生成的多個證據,以創建一種綜合的證據標準,該格式防篡改、保護隱私、可選授權且可共享。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ISC8017 {
struct Report {
string name;
uint256 startTime;
uint256 endTime;
}
event ReportNFTMinted(address indexed recipient,uint256 indexed newItemId, int256 indexed _ispublic,string reportName, uint256 startTime, uint256 endTime , address creator);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event ReportNFTShared(address indexed from, address indexed to, uint256 indexed tokenId);
error ERC721InvalidOwner(address owner);
error ERC721InvalidReceiver(address receiver);
error ERC721InvalidSender(address sender);
error ERC721IncorrectOwner(address owner, uint256 tokenId, address previousOwner);
error ERC721InvalidApprover(address approver);
error ERC721NonexistentToken(uint256 tokenId);
error ERC721InvalidOperator(address operator);
error ERC721InsufficientApproval(address spender, uint256 tokenId);
//Info: (20231229 - Yang){ supportsInterface(bytes4 interfaceId) is a function that is based on ERC165.}
//Info: (20231229 - Yang){ Checks if the contract supports a specific interface.}
function supportsInterface(bytes4 interfaceId) external view returns (bool);
//Info: (20231229 - Yang){ the following functions are based on by ERC721.}
//Info: (20231229 - Yang){ Returns the name of the token.}
function name() external view returns (string memory);
//Info: (20231229 - Yang){ Returns the symbol of the token, usually a shorter version of the name.}
function symbol() external view returns (string memory);
//Info: (20231229 - Yang){ Returns the number of tokens owned by a given address.}
function balanceOf(address tokenOwner) external view returns (uint256);
//Info: (20231229 - Yang){ Returns the URI for a given token ID, usually pointing to a JSON file containing the token's metadata.}
function tokenURI(uint256 tokenId) external view returns (string memory);
//Info: (20231229 - Yang){ Safely transfers a token from one address to another.}
function safeTransferFrom(address from, address to, uint256 tokenId) external;
//Info: (20231229 - Yang){ Safely transfers a token from one address to another, and includes an additional data parameter}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external;
//Info: (20231229 - Yang){ Returns the owner address of a given token ID.}
function ownerOf(uint256 tokenId) external view returns (address);
//Info: (20231229 - Yang){ Returns the owner address of the contract.}
function owner() external view returns (address);
//Info: (20231229 - Yang){ Approves a given address to manage a specific token ID.}
function approve(address to, uint256 tokenId) external;
//Info: (20231229 - Yang){ The following functions are created originally by isuncloud.}
//Info: (20231229 - Yang){ Shares a report by minting a new token for a specified wallet address.}
function share(uint256 tokenId, address targetWallet) external returns (uint256);
//Info: (20231229 - Yang){ Returns the report data for a given token ID.}
function getReportData(uint256 tokenId) external view returns (Report memory);
//Info: (20231229 - Yang){ Returns the latest token ID.}
function getLatestTokenID() external view returns (uint256);
//Info: (20231229 - Yang){ Mints a new report NFT and assigns it to the specified recipient.}
function mintReportNFT(address recipient, uint256 startTime, uint256 endTime, string memory reportName,int256 _ispublic) external returns (uint256);
}
調用表準化的interface
以下是上述interface的實例化。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../interfaces/i_erc_isuncloud.sol";
contract ReportNFT is ISC8017 {
uint256 private _tokenIdCounter;
string private _name;
string private _symbol;
mapping(uint256 => Report) private _reports;
mapping(uint256 tokenId => address) private _owners;
mapping(address owner => uint256) private _balances;
mapping(uint256 tokenId => address) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
bytes4 private constant _ERC721_INTERFACE_ID = 0x80ac58cd;
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
mapping(bytes4 => bool) private _supportedInterfaces;
string private _collectionName;
string private _collectionSymbol;
address private _owner;
constructor(string memory collectionName, string memory collectionSymbol) {
_registerInterface(_ERC721_INTERFACE_ID);
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
_collectionName = collectionName;
_collectionSymbol = collectionSymbol;
_owner = msg.sender;
_tokenIdCounter = 0;
}
modifier onlyOwner() {
require(msg.sender == _owner, "Caller is not the owner");
_;
}
function uintToString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
uint256 index = digits - 1;
temp = value;
while (temp != 0) {
buffer[index--] = bytes1(uint8(48 + temp % 10));
temp /= 10;
}
return string(buffer);
}
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
function name() public view override returns (string memory) {
return _collectionName;
}
function symbol() public view override returns (string memory) {
return _collectionSymbol;
}
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return _supportedInterfaces[interfaceId];
}
function _registerInterface(bytes4 interfaceId) internal {
require(interfaceId != 0xffffffff, "Invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
function balanceOf(address tokenOwner) public view virtual override returns (uint256) {
if (tokenOwner == address(0)) {
revert ERC721InvalidOwner(address(0));
}
return _balances[tokenOwner];
}
function _mint(address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner != address(0)) {
revert ERC721InvalidSender(address(0));
}
}
function mintReportNFT(address recipient, uint256 startTime, uint256 endTime, string memory reportName, int256 _ispublic) external override onlyOwner returns (uint256) {
require(recipient != address(0), "Recipient address cannot be zero");
require(endTime > startTime, "EndTime must be greater than StartTime");
_tokenIdCounter += 1;
uint256 newItemId = _tokenIdCounter;
_reports[newItemId] = Report(reportName, startTime, endTime);
_mint(recipient, newItemId);
emit ReportNFTMinted(recipient, newItemId, _ispublic, reportName, startTime, endTime, msg.sender);
return newItemId;
}
function _safeMint(address to, uint256 tokenId) internal {
_safeMint(to, tokenId, "");
}
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
_checkOnERC721Received(address(0), to, tokenId, data);
}
function transferFrom(address from, address to, uint256 tokenId) public virtual {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, msg.sender);
if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
function _transfer(address from, address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
function tokenURI(uint256 tokenId) public view override virtual returns (string memory) {
_requireOwned(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, uintToString(tokenId))) : "";
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
function safeTransferFrom(address from, address to, uint256 tokenId) override public {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override virtual {
transferFrom(from, to, tokenId);
_checkOnERC721Received(from, to, tokenId, data);
}
function _safeTransfer(address from, address to, uint256 tokenId) internal {
_safeTransfer(from, to, tokenId, "");
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
_checkOnERC721Received(from, to, tokenId, data);
}
function _approve(address to, uint256 tokenId, address auth) internal {
_approve(to, tokenId, auth, true);
}
function isApprovedForAll(address tokenOwner, address operator) public view virtual returns (bool) {
return _operatorApprovals[tokenOwner][operator];
}
function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
// Avoid reading the owner unless necessary
if (emitEvent || auth != address(0)) {
address tokenOwner = _requireOwned(tokenId);
// We do not use _isAuthorized because single-token approvals should not be able to call approve
if (auth != address(0) && tokenOwner != auth && !isApprovedForAll(tokenOwner, auth)) {
revert ERC721InvalidApprover(auth);
}
if (emitEvent) {
emit Approval(tokenOwner, to, tokenId);
}
}
_tokenApprovals[tokenId] = to;
}
function _increaseBalance(address account, uint128 value) internal virtual {
unchecked {
_balances[account] += value;
}
}
function _burn(uint256 tokenId) internal {
address previousOwner = _update(address(0), tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
}
function _setApprovalForAll(address tokenOwner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC721InvalidOperator(operator);
}
_operatorApprovals[tokenOwner][operator] = approved;
emit ApprovalForAll(tokenOwner, operator, approved);
}
function _requireOwned(uint256 tokenId) internal view returns (address) {
address tokenOwner = _ownerOf(tokenId);
if (tokenOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
return tokenOwner;
}
function ownerOf(uint256 tokenId) public override view returns (address) {
address tokenOwner = _owners[tokenId];
require(tokenOwner != address(0), "ERC721: owner query for nonexistent token");
return tokenOwner;
}
function owner() public view override returns (address) {
return _owner;
}
function getReportData(uint256 tokenId) external override view returns (Report memory) {
require(
ownerOf(tokenId) == msg.sender || msg.sender == owner(),
"Caller doesn't have the authority to access the token. Caller must be the NFT contract deployer or the token owner."
);
require(ownerOf(tokenId) != address(0), "Report does not exist.");
return _reports[tokenId];
}
function getLatestTokenID() external override view returns (uint256) {
return _tokenIdCounter;
}
function approve(address to, uint256 tokenId) override public virtual {
_approve(to, tokenId, msg.sender);
}
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
address from = _ownerOf(tokenId);
if (auth != address(0)) {
_checkAuthorized(from, auth, tokenId);
}
if (from != address(0)) {
_approve(address(0), tokenId, address(0), false);
unchecked {
_balances[from] -= 1;
}
}
if (to != address(0)) {
unchecked {
_balances[to] += 1;
}
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
return from;
}
function _checkAuthorized(address tokenOwner, address spender, uint256 tokenId) internal view virtual {
if (!_isAuthorized(tokenOwner, spender, tokenId)) {
if (tokenOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else {
revert ERC721InsufficientApproval(spender, tokenId);
}
}
}
function _isAuthorized(address tokenOwner, address spender, uint256 tokenId) internal view virtual returns (bool) {
return
spender != address(0) &&
(tokenOwner == spender || isApprovedForAll(tokenOwner, spender) || _getApproved(tokenId) == spender);
}
function _getApproved(uint256 tokenId) internal view virtual returns (address) {
return _tokenApprovals[tokenId];
}
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
if (to.code.length > 0) {
try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
if (retval != IERC721Receiver.onERC721Received.selector) {
revert ERC721InvalidReceiver(to);
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC721InvalidReceiver(to);
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
function share(uint256 tokenId, address targetWallet) override external returns (uint256) {
require(ownerOf(tokenId) == msg.sender, "Only the owner can share this report");
require(targetWallet != address(0), "Target wallet cannot be zero");
_tokenIdCounter += 1;
uint256 newTokenId = _tokenIdCounter;
Report memory originalReport = _reports[tokenId];
_reports[newTokenId] = Report(originalReport.name, originalReport.startTime, originalReport.endTime);
_mint(targetWallet, newTokenId);
emit ReportNFTShared(msg.sender, targetWallet, newTokenId);
return newTokenId;
}
}
創建資產(報表)的同時生產一份唯一標示符證據
以下是值針對此提案的舉例,我們可以在創建數位資產時,同時鑄造一個NFT evidence,並且爬取這些NFT的元數據,最後將數位資產與證據結合在一起。我們就可以透過這些證據,去管理權限、分享、追蹤這些數位資產。 function hash: 0x6176ffc5
async function generateReport(startTime, endTime, report_Name) {
const [signer] = await ethers.getSigners();
const contractWithSigner = new ethers.Contract(
routerContractAddress,
contractABI,
signer,
);
const nftContractWithSigner = new ethers.Contract(
nftContractAddress,
nftcontractABI,
signer,
);
try {
const tx = await contractWithSigner.generateReport(
startTime,
endTime,
report_Name,
);
const transaction_hash = tx.hash;
console.log('Transaction hash:', transaction_hash);
const receipt = await tx.wait();
if (receipt.status === 0) {
console.error('Transaction failed');
throw new Error('Transaction failed');
}
} catch (error) {
console.error('Error:', error);
}
const recipientAddress = '{recipientAddress}';
const _ispublic = 0;
try {
console.log('start minting NFT');
const tx = await nftContractWithSigner.mintReportNFT(
recipientAddress,
startTime,
endTime,
report_Name,
_ispublic,
);
const transaction_hash = tx.hash;
const receipt = await tx.wait();
console.log('start getting latestTokenID');
const latestTokenId = await nftContractWithSigner.getLatestTokenID();
console.log('Latest Token ID:', latestTokenId.toString());
fs.appendFileSync('.env', `REPORT_ID=${latestTokenId.toString()}\n`);
console.log('.env file updated');
} catch (error) {
console.error('Error minting NFT:', error);
}
}
原理
報告NFT標準的原理是利用區塊鏈技術,特別是NFT,來增強審計報告的安全性、真實性和可追踪性。通過將這些報告編碼為NFT,每個文件在區塊鏈上都被賦予了一個獨特的、不可變的身份,確保其真實性並防止篡改。通過專用功能安全地共享這些NFT,滿足了對於對敏感審計信息進行受控但靈活分發的需求。遵循ERC-721標準確保了在各種平台之間的互操作性,使系統具有多功能性和可訪問性。這種方法在維護可再生能源行業中審計文件的完整性和可信度方面代表了一個重大進步。
以會計系統為例(儘管這也可以應用於法律、醫療數據等其他領域),在區塊鏈上記錄的每筆交易都可以被視為一個小證據。這些交易經過計算後生成的報告代表了更大的證據。當我們希望將這些小證據整合為更大的證據以控制訪問時,我們需要一個機制來實現這一點。這種更大的證據以NFT的形式鑄造,只有擁有該NFT的人才能查看整個報告的內容。這樣的機制可以應用於各個領域,使得這一標準能夠實現這種功能。
安全考慮
在考慮報告NFT標準的安全方面時,關注處理和存儲敏感審計報告數據在區塊鏈上可能存在的潛在風險至關重要。NFT的不可變性確保了一旦報告被鑄造,其內容就無法更改,這對於維護審計信息的完整性至關重要。然而,這也意味著在報告鑄造之前的任何錯誤都將變得永久。此外,必須嚴格測試和減輕智能合約的漏洞,以防止利用可能導致未經授權的訪問或操縱NFT。為了確保只有授權的方可以鑄造、分享或轉移報告NFT,必須采取足夠的訪問控制和身份驗證措施。在實施過程中還應考慮隱私問題,因為區塊鏈交易通常是公開的,這可能導致敏感信息的意外曝光。