【區(qū)塊鏈實(shí)踐案例】基于以太坊區(qū)塊鏈的電子存證應(yīng)用

導(dǎo)讀:由于 區(qū)塊鏈具有去中心雷客、不可逆等特點(diǎn),天然適合作為證據(jù)儲存的載體。360存證云是360區(qū)塊鏈實(shí)驗(yàn)室基于以太坊開發(fā)的電子證據(jù)存證系統(tǒng),本文簡單介紹了其中鏈存儲部分的設(shè)計(jì)思路和一些實(shí)現(xiàn)細(xì)節(jié)吐葱。

一、電子存證技術(shù)概述

傳統(tǒng)的電子存證簡單來說就是將源信息經(jīng)過加密存儲在一個(gè)具有公信力的獨(dú)立第三方處校翔,并綁定時(shí)間戳弟跑、創(chuàng)建人等信息用來證明在某個(gè)時(shí)間點(diǎn)存在這樣的信息。舉例來說防症,對于原創(chuàng)作品的保護(hù)孟辑,作者可以在創(chuàng)作完成后第一時(shí)間進(jìn)行電子存證哎甲,以保證在以后出現(xiàn)侵權(quán)后證明自己最早創(chuàng)作了作品從而保護(hù)自己的權(quán)益。

電子存證的源信息可以是一段文本饲嗽,文檔炭玫,圖片,視頻等形式貌虾。對于這種各式各樣的形式吞加,電子存證一般存儲的是源信息的哈希摘要;哈希是一段定長的比特串酝惧,類似于源信息的指紋榴鼎,源信息只要改變,哈希就會和原來的完全不一樣晚唇,由于哈衔撞疲基本上是不可遍歷的,所以在現(xiàn)實(shí)中可以認(rèn)為哈希和源信息一一對應(yīng)哩陕。哈希的這種特性普遍應(yīng)用在文件指紋等場景平项,例如下載文件中的哈希校驗(yàn)。電子存證存儲的哈希值是可以證明源信息真實(shí)未經(jīng)篡改悍及。哈希的另一個(gè)特性是無法從哈希摘要反推出原始信息闽瓢,所以這樣也保證了一些敏感信息的隱私性。

二心赶、區(qū)塊鏈電子存證的優(yōu)勢

對于傳統(tǒng)的電子存證扣讼,具有公信力的獨(dú)立第三方是一個(gè)很重要的角色;需要存證缨叫、取證椭符、驗(yàn)證的各方都無條件信任。這樣的第三方權(quán)限過于集中耻姥,如果第三方惡意修改數(shù)據(jù)销钝,則基本無從查證;所以這樣的第三方只能通過非技術(shù)的其他手段保證不去作惡琐簇。而區(qū)塊鏈本身通過一環(huán)套一環(huán)的鏈?zhǔn)浇Y(jié)構(gòu)蒸健、分布式的存儲、分布式的共識機(jī)制將這樣過大的權(quán)利分散到所有參與者身上婉商,保證了不產(chǎn)生這樣一個(gè)權(quán)限過大的中心化第三方來具有作惡的可能似忧。

通過區(qū)塊鏈解決的存證中的信任問題,基于這樣的一個(gè)前提据某,我們設(shè)計(jì)了基于以太坊的電子存證應(yīng)用橡娄。

三、區(qū)塊鏈存證合約設(shè)計(jì)

區(qū)塊鏈上的數(shù)據(jù)經(jīng)過礦工打包進(jìn)區(qū)塊中后基本上不可能更改癣籽,所以存證合約設(shè)計(jì)時(shí)候只需要做簡單的讀寫操作挽唉。我們設(shè)計(jì)的存證合約讀寫的數(shù)據(jù)結(jié)構(gòu)為:

struct Abstract {
    uint timestamp;
    address sender;
    uint version;
    bytes32 hash;
    byte[512] extend;
}

數(shù)據(jù)結(jié)構(gòu)中包含時(shí)間戳滤祖,調(diào)用存證合約的地址、存證的哈希值瓶籽、擴(kuò)展字段和標(biāo)識版本的 version 字段匠童。擴(kuò)展字段和版本由使用方自定義編碼和解碼方式。通過這樣的數(shù)據(jù)結(jié)構(gòu)塑顺,構(gòu)造一個(gè) mapping(bytes32 => Abstract) Map 來用于分別保存存證信息汤求,Map 結(jié)構(gòu)的 Key 可以簡單設(shè)置為存證的哈希值或者其他可追溯的值。

在這樣結(jié)構(gòu)上再封裝對 Map 的讀寫操作就是一個(gè)簡單的存證合約严拒⊙镄鳎可是由于區(qū)塊鏈的特性,合約一旦上鏈后就不能更改了裤唠,所以如果合約邏輯出現(xiàn)漏洞就影響比較大挤牛,并且不能修復(fù),重新部署合約又會丟失原有的數(shù)據(jù)种蘸,這樣設(shè)計(jì)的合約是不可維護(hù)的墓赴。所以設(shè)計(jì)對這樣的合約進(jìn)行更改,將使用方直接調(diào)用 Map 操作的讀寫進(jìn)行切斷航瞭,在中間加入一個(gè)訪問控制的合約層诫硕,這樣經(jīng)過修改的合約結(jié)構(gòu)如下:

底層數(shù)據(jù)層合約:僅封裝對 Map 結(jié)構(gòu)的讀寫操作,不設(shè)計(jì)具體的業(yè)務(wù)邏輯刊侯;在合約層加入權(quán)限控制章办,維護(hù)訪問地址的白名單,僅白名單內(nèi)部的地址具有操作合約數(shù)據(jù)的權(quán)限滨彻;僅合約部署者具有控制白名單的權(quán)限纲菌。

上層邏輯合約:封裝了簡單的存證業(yè)務(wù)邏輯,上層邏輯沒有數(shù)據(jù)存儲操作疮绷,在合約部署時(shí)候傳入底層合約的地址作為參數(shù),數(shù)據(jù)存儲通過合約調(diào)用底層合約來實(shí)現(xiàn)嚣潜。

這樣分層后冬骚,一旦上層邏輯出現(xiàn)問題,可以通過管理員吊銷上層合約的讀寫訪問權(quán)限來阻止進(jìn)一步的損失懂算;合約升級是通過部署新的上層合約只冻,賦予新的上層合約權(quán)限,吊銷舊上層合約權(quán)限來實(shí)現(xiàn)计技;底層合約出現(xiàn)問題喜德,也可以通過升級上層合約,在邏輯上繞過垮媒。

具體底層合約的代碼如下:

pragma solidity ^0.4.17;

contract DataModel {
    struct Abstract {
        uint timestamp;
        address sender;
        uint version;
        bytes32 hash;
        byte[512] extend;
    }

    mapping(bytes32 => Abstract) abstractData;
    mapping(address => bool) public allowedMap;
    address[] public allowedArray;

    event AddressAllowed(address _handler, address _address);
    event AddressDenied(address _handler, address _address);
    event DataSaved(address indexed _handler, uint timestamp, address indexed sender, uint version, bytes32 hash);
    event ExtendSaved(address indexed _handler, byte[512] extend);
    event ExtendNotSave(address indexed _handler, uint version, byte[512] extend);

    function DataModel() public {
        allowedMap[msg.sender] = true;
        allowedArray.push(msg.sender);
    }

    modifier allow() {
        require(allowedMap[msg.sender] == true);
        _;
    }

    function allowAccess(address _address) allow public {
        allowedMap[_address] = true;
        allowedArray.push(_address);
        AddressAllowed(msg.sender, _address);
    }

    function denyAccess(address _address) allow public {
        allowedMap[_address] = false;
        AddressDenied(msg.sender, _address);
    }

    function getData(bytes32 _key) public view returns(uint, address, uint, bytes32, byte[512]) {
        return (
            abstractData[_key].timestamp,
            abstractData[_key].sender,
            abstractData[_key].version,
            abstractData[_key].hash,
            abstractData[_key].extend
        );
    }

    function setData(bytes32 _key, uint timestamp, address sender, uint version, bytes32 hash) allow public {
        abstractData[_key].timestamp = timestamp;
        abstractData[_key].sender = sender;
        abstractData[_key].version = version;
        abstractData[_key].hash = hash;
        DataSaved(msg.sender, timestamp, sender, version, hash);
    }

    function setExtend(bytes32 _key, byte[512] extend) allow public {
        if (abstractData[_key].version > 0) {
            for (uint256 i; i < 512; i++) {
                abstractData[_key].extend[i] = extend[i];
            }
            ExtendSaved(msg.sender, extend);
        } else {
            ExtendNotSave(msg.sender, abstractData[_key].version, extend);
        }
    }
}

上層合約的代碼如下:

pragma solidity ^0.4.20;

import "./data-model.sol";

contract Storage {
    DataModel dataModel;
    uint currentVersion = 1;

    event StorageSaved(address handler, bytes32 indexed hashKey, uint timestamp, uint version, byte[512] extend);

    function Storage(address dataModelAddress) public {
        dataModel = DataModel(dataModelAddress);
        // require(dataModelAddress.delegatecall(bytes4(keccak256("allowAccess(address)")), this));
    }

    function getData(bytes32 key) public view returns(uint timestamp, address sender, uint version, bytes32 hashKey, string extend) {
        byte[512] memory extendByte;

        (timestamp, sender, version, hashKey, extendByte) = dataModel.getData(key);

        bytes memory bytesArray = new bytes(512);
        for (uint256 i; i < 512; i++) {
            bytesArray[i] = extendByte[i];
        }

        extend = string(bytesArray);
        return(timestamp, sender, version, hashKey, extend);
    }

    function saveData(bytes32 hashKey, byte[512] extend) public {
        dataModel.setData(hashKey, block.timestamp, msg.sender, currentVersion, hashKey);
        dataModel.setExtend(hashKey, extend);

        StorageSaved(msg.sender, hashKey, block.timestamp, currentVersion, extend);
    }
}

四舍悯、存證應(yīng)用和以太坊區(qū)塊鏈的交互

我們存證應(yīng)用采用的是 Go 語言開發(fā)航棱,通過 RPC 調(diào)用和鏈進(jìn)行交互;由于采用 Go 語言開發(fā)萌衬,而正好以太坊官方提供 go-ethereum 的開源代碼饮醇,所以以太坊 SDK 這塊就直接選用這份開源代碼;代碼中不僅有主動(dòng)調(diào)用 RPC 接口秕豫,而且需要接收節(jié)點(diǎn)推送的合約事件朴艰,所以 RPC 調(diào)用基于的是 WebSocket 協(xié)議,需要節(jié)點(diǎn)開啟 WebSocket RPC 調(diào)用支持混移,可以通過啟動(dòng)參數(shù) --ws --wsaddr value --wsport value --wsapi value 來實(shí)現(xiàn)WIKI祠墅,或者通過 JavaScript Console 的 Admin API來開啟。

調(diào)用 Go SDK 的基本流程如下(代碼省略錯(cuò)誤處理等邏輯歌径,僅保留核心流程):

import (
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/rpc"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/accounts/keystore"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
)

// 初始化 RPC 連接
RPCClient, _ := rpc.Dial(conf.BlockChainConf.RPCUrl)

// 初始化 ethclient
cli := ethclient.NewClient(RPCClient)

// 導(dǎo)入 ABI 接口字符串
parsedABI, _ := abi.JSON(strings.NewReader(evidenceABI))

// 初始化合約實(shí)例
evidence := bind.NewBoundContract(conf.BlockChainConf.ContractAddress, parsedABI, cli, cli, nil)

// 初始化上下文
ctx, cancel := context.WithTimeout(context.Background(), conf.BlockChainConf.ConnTimeout)
defer cancel()

// 交易簽名私鑰
auth := bind.NewKeyedTransactor(account.PrivateKey)
auth.Context = ctx

// 調(diào)用 RPC 發(fā)送存證合約交易
tx, _ := evidence.Transact(auth, "saveData", hash, stringToBytes512(extend))
最終返回的 tx 則為交易信息毁嗦,這時(shí)候交易并沒有即時(shí)出塊,需要等待出塊節(jié)點(diǎn)出塊沮脖;這里通過監(jiān)聽合約的日志事件來實(shí)現(xiàn):

// 訂閱事件的過濾條件金矛,這里傳入合約的地址
query := ethereum.FilterQuery{
    Addresses: []common.Address{conf.BlockChainConf.ContractAddress},
}

// Log 通道接收
var logChan = make(chan types.Log)
ctx := context.Background()

// 初始化客戶端
client, _ := blockchain.InitClient()

// 初始化事件監(jiān)聽
subscribe, _ := client.SubscribeFilterLogs(ctx, query, logChan)

// 同樣解析出 ABI 合約接口
parsedABI, _ := abi.JSON(strings.NewReader(evidenceABI))

// 收到的事件結(jié)構(gòu),和合約代碼中數(shù)據(jù)結(jié)構(gòu)對應(yīng)
var receivedData struct {
    Handler   common.Address
    HashKey   common.Hash
    Timestamp *big.Int
    Version   *big.Int
    Extend    Bytes512}for {
    select {
    case err := <-evt.Subscribe.Err():
        fmt.Printf("receive Error: %s\n", err.Error())
    case log := <-LogChan:
        // 解包收到的 Log勺届,receivedData 則為接收事件的數(shù)據(jù)
        err := parsedABI.Unpack(&receivedData, "StorageSaved", log.Data)
    }
}

通過這樣子驶俊,就可以在區(qū)塊鏈出塊后接受到事件,保證合約方法的成功調(diào)用

取證一種方式是通過調(diào)用合約的 getData 方法來做免姿,和寫入存證數(shù)據(jù)代碼大同小異饼酿,如下:

// 對應(yīng)存證的 evidence.Transact 方法
err = evidence.Call(callOpts, &output, "getData", key)
另一種方式是通過合約的 Log 過濾來實(shí)現(xiàn),如下:

// 這里過濾條件選用合約中 Map 數(shù)據(jù)的 Key
query := ethereum.FilterQuery{
    Topics: [][]common.Hash{[]common.Hash{}, []common.Hash{hashKey}},
}

// 調(diào)用 Client 的 FilterLogs 方法
logs, err := client.FilterLogs(ctx, query)

// 接著類似于監(jiān)聽事件那里胚膊,解包收到的 Log 得到數(shù)據(jù)

五故俐、結(jié)語

存證和區(qū)塊鏈結(jié)合是一個(gè)和合適透明的場景,利用區(qū)塊鏈解決的存證中存在的第三方信任問題紊婉;可是司法并沒有跟上技術(shù)進(jìn)步的節(jié)奏药版;存證現(xiàn)在還處于技術(shù)實(shí)現(xiàn)階段,距離真正落地使用應(yīng)該還有一段距離喻犁,這些都需要我們時(shí)刻關(guān)注相關(guān)信息槽片。

本文摘自 360區(qū)塊鏈實(shí)驗(yàn)室 公眾號文章基于以太坊區(qū)塊鏈的電子存證應(yīng)用,版權(quán)歸原作者所有

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肢础,隨后出現(xiàn)的幾起案子还栓,更是在濱河造成了極大的恐慌,老刑警劉巖传轰,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剩盒,死亡現(xiàn)場離奇詭異,居然都是意外死亡慨蛙,警方通過查閱死者的電腦和手機(jī)辽聊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門纪挎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人身隐,你說我怎么就攤上這事廷区。” “怎么了贾铝?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵隙轻,是天一觀的道長。 經(jīng)常有香客問我垢揩,道長玖绿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任叁巨,我火速辦了婚禮斑匪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锋勺。我一直安慰自己蚀瘸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布庶橱。 她就那樣靜靜地躺著贮勃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苏章。 梳的紋絲不亂的頭發(fā)上寂嘉,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音枫绅,去河邊找鬼泉孩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛并淋,可吹牛的內(nèi)容都是我干的寓搬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼县耽,長吁一口氣:“原來是場噩夢啊……” “哼订咸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酬诀,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骆撇,沒想到半個(gè)月后瞒御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡神郊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年肴裙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趾唱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜻懦,死狀恐怖甜癞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宛乃,我是刑警寧澤悠咱,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站征炼,受9級特大地震影響析既,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谆奥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一眼坏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酸些,春花似錦宰译、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逢渔,卻和暖如春肋坚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肃廓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工徒欣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人市俊。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓惊窖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哀蘑。 傳聞我的和親對象是個(gè)殘疾皇子诚卸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 以太坊(Ethereum ):下一代智能合約和去中心化應(yīng)用平臺 翻譯:巨蟹 、少平 譯者注:中文讀者可以到以太坊愛...
    車圣閱讀 3,722評論 1 7
  • 以太坊白皮書地址:https://github.com/ethereum/wiki/wiki/White-Pape...
    rectinajh閱讀 17,789評論 0 46
  • 2018年4月28日 星期六 多云 后來的我們什么都有了棠赛,卻沒有了我們。 今天“后來的我們”席卷了我的朋友圈,我也...
    叨小妹閱讀 632評論 0 0
  • 鬧鐘響了又睡了一個(gè)小時(shí) 12:51 下雪了 感覺從福建回北方一整年來睛约,第一次見到這么大的雪花?一小會兒世界就白了...
    傅五歲閱讀 179評論 2 0
  • 普通鼎俘,平淡的日常就是生活,甚至心累辩涝,困倦贸伐。 所以要留住那些令人心動(dòng)的溫度。
    f20b44e30ea3閱讀 140評論 0 2