知乎專欄 | 多維度架構 | | | 微信號 netkiller-ebook | | | QQ群:128659835 請註明“讀者” |
參與藝術品上鏈,鑒定,交易包含了下面幾種角色。
平台至少有三種角色會
所以我們需要為不同的角色提供不同的App應用。
用戶端:功能包括防偽查詢,鏈上資產的瀏覽,權益轉讓,資產拍賣,資產抵押,社區互動,分享,數字資產行情,錢包等等
機構端:負責信息收集,信息整理,數據提交,數據審查,資產上鏈,資產劃撥等等
鑒定師:負責數字資產的鑒定,需要有相關資質。
防偽溯源涉及的技術棧
舉例,書畫怎樣做防偽
以國畫為例,我們首先找一個宣紙企業合作,在宣紙生產過程中,將NFC晶片夾在宣紙中間,然後我們使用螢光油墨在宣紙上印刷防偽圖案。也可以採用人民幣上的金屬絲方案,最後拍攝紙紋。視頻記錄下藝術家的整個作畫過程,然後再拍攝特徵數據。最後使用激光打孔技術,在宣紙上打出防偽圖案數據,針孔極小肉眼難以分辨,每個防偽數據都是唯一的。
NFC 標籤可以使用易碎紙粘貼在藝術品上,缺點是壽命較短。使用PVC材料又容易撕下。
油畫可以講NFC晶片放在紡織布中,使用堆彩技術的畫家可以講NFC晶片覆蓋(埋植)在顏料下面
由於 H5 技術無法滿足我們的需求,例如相機,麥克風,NFC,定位...等等。我們重點放在 App 開發,H5 緊緊用於官網,區塊鏈瀏覽器,資訊,等等。
由於使用了很多手機上技術,原生App更適合,而混合開發 React Native,Vue.js, Flutter 不在我們選擇之列。
微信小程序可以考慮,但是如果涉及 Token 可能隨時會被下架。
服務端設計為可以水平擴展,可以隨時根據用戶量,擴展伺服器規模。
Nginx 負載均衡,HTTP2 (安卓 Okhttp 已經很好的支持 http2)
框架採用 Spring cloud
資料庫開發使用 JPA
介面認證 Oauth2 + Jwt
ELK(ElasticSearch, Logstash, Kibana)
搜索是非常重要的功能,因為區塊鏈只能通過 hash 值取出鏈上的數據,雖然 Hyperledger Fabrc 在數據使用 CouchDB 時提供了 World State 的一些高級搜索功能,但是仍然不能滿足我們的需求。
所以鏈上數據需要存儲一份在搜索引擎中,搜索引擎的分詞功能,可以提供快速精準的搜索服務。
搜索引擎的工作流程是:
User --> Phone App --> Nginx --> Spring cloud --> Elastsearch --> Hyperledger Fabric
我們不做山寨鏈,我發現很多國內企業熱衷于做山寨鏈,什麼事山寨鏈呢,就是在現有的區塊鏈(Ethereum, Hyperledger Fabric 或 EOS) 的基礎上二次開發,首先開發山寨鏈需要大量的資金人力,私鏈是沒有任何意義的,沒有公信力。即使目前的現有區塊鏈無法滿足我們的需求,可以通過架構調節去適應他。
所以我們只用最成熟的產品:
Hyperledger Fabric 盟鏈:主要用於資產上鏈,鏈上資產查詢
Ethereum 公鏈:用於 Token ,由於 Hyperledger Fabric 無法實現 Token,所以我們仍然需要以太坊。(作者寫過一篇文章關於為什麼Hyperledger Fabric 不能實現 Token,請興趣自己在網上搜索)
EOS Token/資產上鏈:由於在我設計這個系統之時 EOS 還沒有 Release 所以當時沒有考慮 EOS。現在我們可以使用 EOS,甚至替換掉 Hyperledger Fabrc + Ethereum 方案。因為 EOS 即能實現資產上鏈,也能實現 Token。 這裡我們將資產也在 EOS 上鏈一份,同時也支持 EOS 發的 Token。
IPFS 星際檔案系統:用於存儲多媒體數據,例如圖片,視頻。(注意:IPFS 暫時不支持流媒體,我的解決方案是上鏈同事複製一份到 nginx 中,並開啟 mp4 流媒體功能)
註:雖然以太坊目前嘗嘗擁堵,但是很多應用場景仍是不可替代的。
RFID基本概念:
RFID(Radio Frequency Identification)的縮寫,即射頻識別,俗稱電子標籤。 RFID射頻識別是一種非接觸式的自動識別技術,它通過射頻信號自動識別目標對象並獲取相關數據,識別工作無須人工干預,可工作於各種惡劣環境。 RFID是一種簡單的無線系統,只有兩個基本器件,該系統用於控制、檢測和跟蹤物體。系統由一個詢問器(或閲讀器)和很多應答器(或標籤)組成。
RFID包括: 低頻125KHz 主要是動物管理 中頻 一般指433MHz(這個頻段一般也是有源的 也有做高速收費) 高頻13.56MHz 公交卡 身份證都是這個頻段。 超高頻860-960MHz 主要用在物流和停車場管理。 微波2.45GHz ETC用這個頻段的多
NFC基本概念:
NFC(Near Field Communication)縮寫,即近距離無線通訊技術。由飛利浦公司和索尼公司共同開發的一項無線技術。NFC由非接觸式射頻識別及互聯互通技術整合演變而來,可以在移動設備、消費類電子產品、PC和智能控件工具間進行近距離無線通信。NFC提供了一種簡單、觸控式的解決方案,可以讓消費者簡單直觀地交換信息、訪問內容與服務。 NFC技術特點: 1、 在13.56MHz頻率運行距離在20公分內; 2、 傳輸速度可分106Kbits/sec,212 Kbits/sec,424 Kbits/sec; 3、 運作可分主動與被動模式。主動模式需使用電池,也需要獨立發射模組;被動模式不需使用電池,但無法獨立發射訊號; 4、 已成為ISO/IEC IS 18092國家標準、ETSI TS 102 190標準、EMCA-340標準。
目前主流手機都帶有 NFC 是近場通信功能,安卓手機對 NFC 方案全開放,蘋果手機暫時開放部分功能.
傳統藝術品投資門檻非常高,一是用戶不知道從哪些渠道可以投資,二是藝術品價值過高,三是藝術品鑒定難。這導致了投資藝術品門檻過高。 Token 能實現份額化,實現人人參與,人人持有,P2P交易。
例如某機構上鏈一件藝術品,用戶可以投資該藝術品的一定份額,可以轉讓他持有的權益。且交易去中心化,不受任何機構,管理者的制約。
下面的合約可以展示如何分割藝術品份額,最終達到鏈上資產的份額分割和持有與交易。
pragma solidity ^0.4.25; /** * @title SafeMath * @dev Math operations with safety checks that revert on error */ library SafeMath { /** * @dev Multiplies two numbers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0); // Solidity only automatically asserts when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two numbers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor() public { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } } contract NetkillerAssetsToken is Ownable { using SafeMath for uint256; string public name; string public symbol; uint public decimals; uint256 public totalSupply; mapping(address => mapping(string => uint256)) internal balances; mapping(string => address) internal tokens; event Transfer(address indexed _from, address indexed _to, string indexed _tokenId); event Burn(address indexed from, string _tokenId); constructor( string tokenName, string tokenSymbol, uint decimalUnits ) public { owner = msg.sender; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; totalSupply = 0; } function add(address _owner, string _tokenId) onlyOwner returns(bool status){ balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); tokens[_tokenId] = _owner; totalSupply = totalSupply.add(1); return true; } function balanceOf(address _owner, string _tokenId) constant returns(uint balance){ return balances[_owner][_tokenId]; } function ownerOf(string _tokenId) constant returns (address owner) { return tokens[_tokenId]; } function transfer(address _to, string _tokenId){ address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; transfer(_to, amount, _tokenId); } function transfer(address _to, uint256 _value, string _tokenId){ require(msg.sender == ownerOf(_tokenId)); require(msg.sender != _to); require(_to != address(0)); address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; require(amount >= _value); balances[_from][_tokenId] = balances[_from][_tokenId].sub(_value); balances[_to][_tokenId] = balances[_to][_tokenId].add(_value); tokens[_tokenId] = _to; emit Transfer(_from, _to, _tokenId); } function burn(address _owner, string _tokenId) onlyOwner public returns (bool success) { require(balances[_owner][_tokenId] > 0 && balances[_owner][_tokenId] == 100 * 10 ** uint256(decimals)); balances[_owner][_tokenId] = 0; tokens[_tokenId] = address(0); totalSupply = totalSupply.sub(1); emit Burn(msg.sender, _tokenId); return true; } }
由於 ERC721 不太符合我的需求,所以我結合 ERC20 和 ERC721 寫出了我的合約。合約儘量保持了ERC20的使用習慣,函數定義儘量兼容 ERC20。
我們來看下面的構造方法,每個種類的物品一個合約,例如字畫,陶瓷,青銅器。
constructor( string tokenName, string tokenSymbol, uint decimalUnits ) public { owner = msg.sender; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; totalSupply = 0; }
通過下面函數,添加資產到 Token,使鏈上資產與Token綁定。
function add(address _owner, string _tokenId) onlyOwner returns(bool status){ balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); tokens[_tokenId] = _owner; totalSupply = totalSupply.add(1); return true; }
balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); 初始化份額是 100 表示 100%
totalSupply = totalSupply.add(1); 物品件數加一。可以用於統計鏈上資產的數量。
下面函數是查詢資產的持有人
function ownerOf(string _tokenId) constant returns (address owner) { return tokens[_tokenId]; }
下面函數是,權益轉讓和權益份額轉讓。
function transfer(address _to, string _tokenId){ address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; transfer(_to, amount, _tokenId); } function transfer(address _to, uint256 _value, string _tokenId){ require(msg.sender == ownerOf(_tokenId)); require(msg.sender != _to); require(_to != address(0)); address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; require(amount >= _value); balances[_from][_tokenId] = balances[_from][_tokenId].sub(_value); balances[_to][_tokenId] = balances[_to][_tokenId].add(_value); tokens[_tokenId] = _to; emit Transfer(_from, _to, _tokenId); }
接下來,我們就是可以開發 Dapp 錢包了,在錢包中實現資產的轉移交易。
這個合約可以移植到 EOS 上,Hyperledger Fabric 不可以,因為 Fabric 沒有鎖的機制,會導致計算出錯。
我們希望資產上鏈適用於任何領域,後面也方便將業務拓展。所以我實現了一個萬能合約。以不變應萬變,Hyperledger Fabic 鏈碼如下。
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type SmartContract struct {} func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (s *SmartContract) Query(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) pb.Response { // Retrieve the requested Smart Contract function and arguments function, args := stub.GetFunctionAndParameters() // Route to the appropriate handler function to interact with the ledger appropriately if function == "create" { return s.create(stub, args) } else if function == "find" { return s.find(stub, args) } else if function == "update" { return s.update(stub, args) } else if function == "delete" { return s.delete(stub, args) } return shim.Error("Invalid Smart Contract function name.") } func (s *SmartContract) create(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } _key := args[0] _data := args[1] if(_data == ""){ return shim.Error("Incorrect string of data") } existAsBytes,err := stub.GetState(_key) if string(existAsBytes) != "" { fmt.Println("Failed to create account, Duplicate key.") return shim.Error("Failed to create account, Duplicate key.") } err = stub.PutState(_key, []byte(_data)) if err != nil { return shim.Error(err.Error()) } fmt.Printf("create %s %s \n", _key, string(_data)) return shim.Success(nil) } func (s *SmartContract) find(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _key := args[0] _data, err := stub.GetState(_key) if err != nil { return shim.Error(err.Error()) } if string(_data) == "" { return shim.Error("The key isn't exist.") }else{ fmt.Printf("query %s %s \n", _key, string(_data)) } return shim.Success(_data) } func (s *SmartContract) update(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } _key := args[0] _data := args[1] if(_data == ""){ return shim.Error("Incorrect string of data") } err := stub.PutState(_key, []byte(_data)) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("update %s %s \n", _key, string(_data)) } return shim.Success(nil) } // Deletes an entity from state func (t *SmartContract) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _key := args[0] // Delete the key from the state in ledger err := stub.DelState(_key) if err != nil { return shim.Error("Failed to delete state") } return shim.Success(nil) } func main() { err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
鏈碼有四個函數,分別是創建,查找,更新,刪除。
if function == "create" { return s.create(stub, args) // 創建 } else if function == "find" { return s.find(stub, args) // 查找 } else if function == "update" { return s.update(stub, args) // 更新 } else if function == "delete" { return s.delete(stub, args) // 刪除 }
上鏈使用 create 方法,函數有兩個參數,一個是 key, 另一個是數據。
key 使用 UUID 存儲再資料庫和鏈上,同時 UUID 對應通證的
data 是序列化 byte 數據。例如可以使用 json, hession, msgpack 等序列化後的數據。
err = stub.PutState(_key, []byte(_data))
這個鏈碼考慮到前期產品上市,不確定性因素很多,需要更新和刪除等等。後期我們可以在數據中設置一個 status 變數,當 status = false 就不在允許數據的刪除和更新。
註冊
登錄,平台提供多重登錄方式
防偽查詢
查看鏈上資產
資產交易
評論
分享
用戶是權益持有人,當用戶(用戶端)委託機構負責自己的資產上鏈。權益持有人一次性獲得次產權益100%份額,機構可以查看的機構名下的權益持有人。
鑒定師是管理機構認證並頒發資質證書人員,藝術品溯源區塊鏈領域,他主要的職責事藝術品鑒定。鑒定師可以掛靠到機構。
如何註冊成為鑒定師?
首先進入APP -> 點擊『註冊 』->閲讀條款->點擊同意按鈕。 輸入手機號碼,發送驗證碼,輸入密碼,重複輸入密碼,選擇『鑒定師』,提交。
註冊成功會自動登錄,進入完善資料頁面,鑒定師需要實名認證,上傳身份證信息,鑒定師證書,等等資質檔案。鑒定師需要仔細填寫每一項,並保證資料的真實性。
提交資料後,等待管理機構審批,管理機構會仔細核對每項數據。如果被拒,需要鑒定師重新填寫,再提交。
審批通過前,只能看到鑒定師信息頁面。 審批通過後,可以看到,鑒定師信息,已簽名資產,未處理資產。
怎樣鑒定物品?
鑒定師登錄APP後,進入『我的』可以看淡未處理資產菜單,進入菜單可以看到任務列表。點開一件物品,將鑒定結果提交上去,完成鑒定。鑒定師可以在[已簽名資產]中查看自己鑒定過的物品。
機構主要負責資產上鏈,審查,資產託管
如何註冊成為機構
審批通過前:機構只能看到機構信息頁面
審批通過後:機構擁有機構權限,機構信息,錢包,地址管理,安全,區塊鏈屬性配置,資產管理,權益持有人,評論審核,申請溯源標籤
首先機構需要完善收貨地址,至少添加一個收貨地址。
然後進入『申請溯源標籤』,進入“我的” -> “機構” -> “申請溯源標籤” 。輸入數量,選擇類型,選擇收貨地址,系統會自動計算費用,提交後從錢包中扣取。
標籤有以下幾種類型
注意:暫時只能線上申請,綫下付款。待錢包功能上線後同意採用 Token 結算。提交信息後,管理員收到信息會主動聯繫你,完成付款後將標籤郵寄給機構的收貨地址。
數字資產上鏈是將企業數字化資產上傳到區塊鏈上,相比資料庫區塊鏈是分散式共識,可以防止信息篡改。
準備工作
資產錄入
經過上面幾個步驟完成數字資產上鏈。
注意:管理員沒有審批前可以修改資產信息,管理員審批後鏈上數據無法修改。
在整個區塊鏈溯源防偽的過程中,上鏈的工作量是最大的,所以非一人所為,必須團隊完成。 機構可以添加自己的員工,為員工分配令牌,通過令牌登錄後可以一同完成資產上鏈的數據錄入。
分支機構可以對員工添加的資產信息逐一核對,核對後點擊『提交』按鈕,信息將提交至管理員。待管理員在此審批通過,數據便會上鏈。
無論是機構拒絶還是管理員拒絶,信息都需要重新填寫。
信息的審核責任主要在機構,管理員審核通常是看物品是否符合國家法律,法規,政策等等方面。
當鑒定師為機構鑒定物品後即成為該機構的鑒定師,機構可以查看的機構名下的鑒定師,可以理解為鑒定師掛靠該機構,該機構負責管理鑒定師。
鑒定師負責物品鑒定,並給出鑒定結論。一個物品可以有多名鑒定師同時鑒定。
用戶也可以邀請鑒定師鑒定某肩藝術品。