區塊鏈研究實驗室|首例智慧合約中的全動態訪問控制管理解決方案

買賣虛擬貨幣

訪問控制是軟體基礎設施安全的基本要素。企業應用程式需要嚴格的規則來決定誰可以做什麼,這取決於每個使用者的許可權。可以說,智慧合約中的訪問控制需要更嚴格的審查,因為漏洞可能導致惡意參與者控制系統。如今智慧合約中僅存有簡單形式的靜態訪問控制。最常見的是onlyOwner模式。另一個是Openzeppelin的“角色”智慧合約,該智慧合約可以在部署之前定義角色。儘管這為大多數智慧合約應用程式提供了良好的基礎,但基於現有的角色的訪問控制(RBAC)系統使管理員能夠在執行時動態地定義角色。從這個意義上說,Roles智慧合約是具有限制性的,因為在部署之後無法定義角色。本文介紹現有的智慧合約訪問控制模式,並提出了RBAC和基於屬性的訪問控制(ABAC)智慧合約的定義。Only Owner

onlyOwner模式是智慧合約中最常用、最容易實現的訪問控制方法。程式碼1顯示了Ownable智慧合約的Openzeppelin實現。

1pragmasolidity^0.5.0; 2 3import"../GSN/Context.sol"; 4/** 5*@devContractmodulewhichprovidesabasicaccesscontrolmechanism,where 6*thereisanaccount(anowner)thatcanbegrantedexclusiveaccessto 7*specificfunctions. 8* 9*Thismoduleisusedthroughinheritance.Itwillmakeavailablethemodifier10*`onlyOwner`,whichcanbeappliedtoyourfunctionstorestricttheiruseto11*theowner.12*/13contractOwnableisContext{14addressprivate_owner;1516eventOwnershipTransferred(addressindexedpreviousOwner,addressindexednewOwner);1718/**19*@devInitializesthecontractsettingthedeployerastheinitialowner.20*/21constructor()internal{22addressmsgSender=_msgSender();23_owner=msgSender;24emitOwnershipTransferred(address(0),msgSender);25}2627/**28*@devReturnstheaddressofthecurrentowner.29*/30functionowner()publicviewreturns(address){31return_owner;32}3334/**35*@devThrowsifcalledbyanyaccountotherthantheowner.36*/37modifieronlyOwner(){38require(isOwner(),"Ownable:callerisnottheowner");39_;40}4142/**43*@devReturnstrueifthecalleristhecurrentowner.44*/45functionisOwner()publicviewreturns(bool){46return_msgSender()==_owner;47}4849/**50*@devLeavesthecontractwithoutowner.Itwillnotbepossibletocall51*`onlyOwner`functionsanymore.Canonlybecalledbythecurrentowner.52*53*NOTE:Renouncingownershipwillleavethecontractwithoutanowner,54*therebyremovinganyfunctionalitythatisonlyavailabletotheowner.55*/56functionrenounceOwnership()publiconlyOwner{57emitOwnershipTransferred(_owner,address(0));58_owner=address(0);59}6061/**62*@devTransfersownershipofthecontracttoanewaccount(`newOwner`).63*Canonlybecalledbythecurrentowner.64*/65functiontransferOwnership(addressnewOwner)publiconlyOwner{66_transferOwnership(newOwner);67}6869/**70*@devTransfersownershipofthecontracttoanewaccount(`newOwner`).71*/72function_transferOwnership(addressnewOwner)internal{73require(newOwner!=address(0),"Ownable:newowneristhezeroaddress");74emitOwnershipTransferred(_owner,newOwner);75_owner=newOwner;76}77}該模式假定智慧合約只有一個管理員,並使管理員可以將所有權轉移到另一個地址。擴充套件Ownable智慧合約允許子智慧合約使用onlyOwner自定義修飾符定義函式。這些函式要求事務的傳送者是單一管理員。程式碼2展示瞭如何在子智慧合約中實現此示例。

1pragmasolidity^0.5.0; 2 3import"@openzeppelin/contracts/ownership/Ownable.sol"; 4 5contractMyContractisOwnable{ 6functionnormalThing()public{ 7//anyonecancallthisnormalThing() 8} 910functionspecialThing()publiconlyOwner{11//onlytheownercancallspecialThing()!12}13}Roles

如果Ownable僅限於一個管理員,則Openzeppelin的Roles庫可以定義多個角色。

1pragmasolidity^0.5.0; 2 3/** 4*@titleRoles 5*@devLibraryformanagingaddressesassignedtoaRole. 6*/ 7libraryRoles{ 8structRole{ 9mapping(address=>bool)bearer;10}1112/**13*@devGiveanaccountaccesstothisrole.14*/15functionadd(Rolestoragerole,addressaccount)internal{16require(!has(role,account),"Roles:accountalreadyhasrole");17role.bearer[account]=true;18}1920/**21*@devRemoveanaccount'saccesstothisrole.22*/23functionremove(Rolestoragerole,addressaccount)internal{24require(has(role,account),"Roles:accountdoesnothaverole");25role.bearer[account]=false;26}2728/**29*@devCheckifanaccounthasthisrole.30*@returnbool31*/32functionhas(Rolestoragerole,addressaccount)internalviewreturns(bool){33require(account!=address(0),"Roles:accountisthezeroaddress");34returnrole.bearer[account];35}36}程式碼3展示了Roles智慧合約的實施。與Ownable不同,Role不提供自定義訪問修飾符。相反使用此庫的智慧合約必須在函式內部實現角色要求。他們還必須為每個角色定義Roles.Role型別狀態變數。程式碼4展示了實現兩個角色的ERC20智慧合約的示例:_burners和_minters。

1pragmasolidity^0.5.0; 2 3import"@openzeppelin/contracts/access/Roles.sol"; 4import"@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5import"@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol"; 6 7contractMyTokenisERC20,ERC20Detailed{ 8usingRolesforRoles.Role; 910Roles.Roleprivate_minters;11Roles.Roleprivate_burners;1213constructor(address[]memoryminters,address[]memoryburners)14ERC20Detailed("MyToken","MTKN",18)15public16{17for(uint256i=0;i<minters.length;++i){18_minters.add(minters[i]);19}2021for(uint256i=0;i<burners.length;++i){22_burners.add(burners[i]);23}24}2526functionmint(addressto,uint256amount)public{27//Onlyminterscanmint28require(_minters.has(msg.sender),"DOES_NOT_HAVE_MINTER_ROLE");2930_mint(to,amount);31}3233functionburn(addressfrom,uint256amount)public{34//Onlyburnerscanburn35require(_burners.has(msg.sender),"DOES_NOT_HAVE_BURNER_ROLE");3637_burn(from,amount);38}39}它具有靈活性,允許智慧合約定義所需的儘可能多的角色,但這將require語句的實現留給了智慧合約。由於未提供自定義訪問修飾符,因此在某種程度上降低了可讀性,並且增加了引入編碼錯誤的可能性。但是沒有什麼能阻止智慧合約實現包含require語句的自定義訪問修飾符。Limitations(侷限性)

這些當前的解決方案在部署智慧合約之前提供了角色的靈活性。由於角色取決於硬編碼規則,因此不支援動態建立的角色。這非常適合在已知的內部智慧合約之間提供訪問控制,但它不能提供與現代面向使用者的訪問控制軟體相當的靈活性。具有活躍使用者基礎的軟體,特別是企業軟體,本質上需要不同級別的訪問。隨著組織的發展或萎縮,訪問控制應用程式的管理員需要能夠輕鬆新增和分配新角色的能力。這些結構如今以基於角色的訪問控制(RBAC)和基於屬性的訪問控制(ABAC)的形式存在於軟體中。Role-Based Access Control (RBAC)在RBAC中,每個使用者都被分配了一個角色,每個角色都具有一組許可權,並且只要他們的角色具有正確的許可權,使用者就可以訪問資源。只要系統不需要新的角色,RBAC主要透過Roles智慧合約來滿足。Attribute-Based Access Control (ABAC)在ABAC中,為每個使用者分配了一組主題屬性,為每個資源分配了一組物件屬性。中央訪問控制機構定義有關需要執行哪些主題和物件屬性的規則。與RBAC相比,這是一個更復雜且耗時的解決方案。但是對於大型應用程式和企業而言,它更加靈活,因為它允許每個使用者獨有的各種許可權。在runtime,Ownable或Roles都不滿足這些公共模式中的任何一種。如果需要建立新角色,則需要傳送新程式碼。這些體系結構不允許InfoSec管理員透過簡單的介面建立、更新或刪除新角色。幸運的是,我們正在努力解決這一問題。Access Control (Beta)Openzeppelin即將釋出的v3.0版本(當前處於Beta版)已終止Roles庫,轉而使用名為AccessControl的abstract 智慧合約。注意:不建議您在生產應用程式中使用此解決方案,因為它仍處於測試階段。程式碼5展示了AccessControl的實現。

1pragmasolidity^0.6.0; 2 3import"../utils/EnumerableSet.sol"; 4import"../utils/Address.sol"; 5import"../GSN/Context.sol"; 6 7/** 8*@devContractmodulethatallowschildrentoimplementrole-basedaccess 9*controlmechanisms. 10* 11*Rolesarereferredtobytheir`bytes32`identifier.Theseshouldbeexposed 12*intheexternalAPIandbeunique.Thebestwaytoachievethisisby 13*using`publicconstant`hashdigests: 14* 15*``` 16*bytes32publicconstantMY_ROLE=keccak256("MY_ROLE"); 17*``` 18* 19*Rolescanbeusedtorepresentasetofpermissions.Torestrictaccesstoa 20*functioncall,use{hasRole}: 21* 22*``` 23*functionfoo()public{ 24*require(hasRole(MY_ROLE,_msgSender())); 25*... 26*} 27*``` 28* 29*Rolescanbegrantedandrevokeddynamicallyviathe{grantRole}and 30*{revokeRole}functions.Eachrolehasanassociatedadminrole,andonly 31*accountsthathavearole'sadminrolecancall{grantRole}and{revokeRole}. 32* 33*Bydefault,theadminroleforallrolesis`DEFAULT_ADMIN_ROLE`,whichmeans 34*thatonlyaccountswiththisrolewillbeabletograntorrevokeother 35*roles.Morecomplexrolerelationshipscanbecreatedbyusing 36*{_setRoleAdmin}. 37*/ 38abstractcontractAccessControlisContext{ 39usingEnumerableSetforEnumerableSet.AddressSet; 40usingAddressforaddress; 41 42structRoleData{ 43EnumerableSet.AddressSetmembers; 44bytes32adminRole; 45} 46 47mapping(bytes32=>RoleData)private_roles; 48 49bytes32publicconstantDEFAULT_ADMIN_ROLE=0x00; 50 51/** 52*@devEmittedwhen`account`isgranted`role`. 53* 54*`sender`istheaccountthatoriginatedthecontractcall,anadminrole 55*bearerexceptwhenusing{_setupRole}. 56*/ 57eventRoleGranted(bytes32indexedrole,addressindexedaccount,addressindexedsender); 58 59/** 60*@devEmittedwhen`account`isrevoked`role`. 61* 62*`sender`istheaccountthatoriginatedthecontractcall: 63*-ifusing`revokeRole`,itistheadminrolebearer 64*-ifusing`renounceRole`,itistherolebearer(i.e.`account`) 65*/ 66eventRoleRevoked(bytes32indexedrole,addressindexedaccount,addressindexedsender); 67 68/** 69*@devReturns`true`if`account`hasbeengranted`role`. 70*/ 71functionhasRole(bytes32role,addressaccount)publicviewreturns(bool){ 72return_roles[role].members.contains(account); 73} 74 75/** 76*@devReturnsthenumberofaccountsthathave`role`.Canbeused 77*togetherwith{getRoleMember}toenumerateallbearersofarole. 78*/ 79functiongetRoleMemberCount(bytes32role)publicviewreturns(uint256){ 80return_roles[role].members.length(); 81} 82 83/** 84*@devReturnsoneoftheaccountsthathave`role`.`index`mustbea 85*valuebetween0and{getRoleMemberCount},non-inclusive. 86* 87*Rolebearersarenotsortedinanyparticularway,andtheirorderingmay 88*changeatanypoint. 89* 90*WARNING:Whenusing{getRoleMember}and{getRoleMemberCount},makesure 91*youperformallqueriesonthesameblock.Seethefollowing 92*https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forumpost] 93*formoreinformation. 94*/ 95functiongetRoleMember(bytes32role,uint256index)publicviewreturns(address){ 96return_roles[role].members.at(index); 97} 98 99/**100*@devReturnstheadminrolethatcontrols`role`.See{grantRole}and101*{revokeRole}.102*103*Tochangearole'sadmin,use{_setRoleAdmin}.104*/105functiongetRoleAdmin(bytes32role)publicviewreturns(bytes32){106return_roles[role].adminRole;107}108109/**110*@devGrants`role`to`account`.111*112*If`account`hadnotbeenalreadygranted`role`,emitsa{RoleGranted}113*event.114*115*Requirements:116*117*-thecallermusthave``role``'sadminrole.118*/119functiongrantRole(bytes32role,addressaccount)publicvirtual{120require(hasRole(_roles[role].adminRole,_msgSender()),"AccessControl:sendermustbeanadmintogrant");121122_grantRole(role,account);123}124125/**126*@devRevokes`role`from`account`.127*128*If`account`hadbeengranted`role`,emitsa{RoleRevoked}event.129*130*Requirements:131*132*-thecallermusthave``role``'sadminrole.133*/134functionrevokeRole(bytes32role,addressaccount)publicvirtual{135require(hasRole(_roles[role].adminRole,_msgSender()),"AccessControl:sendermustbeanadmintorevoke");136137_revokeRole(role,account);138}139140/**141*@devRevokes`role`fromthecallingaccount.142*143*Rolesareoftenmanagedvia{grantRole}and{revokeRole}:thisfunction's144*purposeistoprovideamechanismforaccountstolosetheirprivileges145*iftheyarecompromised(suchaswhenatrusteddeviceismisplaced).146*147*Ifthecallingaccounthadbeengranted`role`,emitsa{RoleRevoked}148*event.149*150*Requirements:151*152*-thecallermustbe`account`.153*/154functionrenounceRole(bytes32role,addressaccount)publicvirtual{155require(account==_msgSender(),"AccessControl:canonlyrenouncerolesforself");156157_revokeRole(role,account);158}159160/**161*@devGrants`role`to`account`.162*163*If`account`hadnotbeenalreadygranted`role`,emitsa{RoleGranted}164*event.Notethatunlike{grantRole},thisfunctiondoesn'tperformany165*checksonthecallingaccount.166*167*Requirements:168*169*-thisfunctioncanonlybecalledfromaconstructor.170*/171function_setupRole(bytes32role,addressaccount)internalvirtual{172require(!address(this).isContract(),"AccessControl:rolescannotbesetupafterconstruction");173_grantRole(role,account);174}175176/**177*@devSets`adminRole`as``role``'sadminrole.178*/179function_setRoleAdmin(bytes32role,bytes32adminRole)internalvirtual{180_roles[role].adminRole=adminRole;181}182183function_grantRole(bytes32role,addressaccount)private{184if(_roles[role].members.add(account)){185emitRoleGranted(role,account,_msgSender());186}187}188189function_revokeRole(bytes32role,addressaccount)private{190if(_roles[role].members.remove(account)){191emitRoleRevoked(role,account,_msgSender());192}193}194}它具有許多與Roles相同的功能,但是不需要子智慧合約來實現每個角色的aRoles.Role狀態變數。相反由子智慧合約實現的每個角色都由bytes32變數表示。AccessControl還為超級管理員定義了DEFAULT_ADMIN_ROLE,程式碼6的子智慧合約的實現所示。

1pragmasolidity^0.6.0; 2 3import"@openzeppelin/contracts/access/AccessControl.sol"; 4import"@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 6contractMyTokenisERC20,AccessControl{ 7bytes32publicconstantMINTER_ROLE=keccak256("MINTER_ROLE"); 8bytes32publicconstantBURNER_ROLE=keccak256("BURNER_ROLE"); 910constructor()publicERC20("MyToken","TKN"){11//Grantthecontractdeployerthedefaultadminrole:itwillbeable12//tograntandrevokeanyroles13_setupRole(DEFAULT_ADMIN_ROLE,msg.sender);14}1516functionmint(addressto,uint256amount)public{17require(hasRole(MINTER_ROLE,msg.sender),"Callerisnotaminter");18_mint(to,amount);19}2021functionburn(addressfrom,uint256amount)public{22require(hasRole(BURNER_ROLE,msg.sender),"Callerisnotaburner");23_burn(from,amount);24}25}程式碼6中提供的示例還是靜態的,但是AccessControl提供了實現動態版本所需的靈活性。Alberto CuestaCañada擁有一個名為Hierarchy的示例智慧合約,該智慧合約實現了AccessControl,從而可以在執行時動態建立角色。程式碼7顯示了層次結構智慧合約程式碼。

1pragmasolidity^0.6.0; 2import"@openzeppelin/contracts/access/AccessControl.sol"; 3 4 5/** 6*@titleHierarchy 7*@authorAlbertoCuestaCanada 8*@noticeImplementsadynamicrolestructureforRoles 9*/10contractHierarchyisAccessControl{11eventAdminRoleSet(bytes32roleId,bytes32adminRoleId);1213///@devAdd`root`asamemberoftherootrole.14constructor(addressroot)public{15_grantRole(DEFAULT_ADMIN_ROLE,root);16}1718///@devRestrictedtomembersoftherolepassedasaparameter.19modifieronlyMember(bytes32roleId){20require(hasRole(roleId,msg.sender),"Restrictedtomembers.");21_;22}2324///@devCreateanewrolewiththespecifiedadminrole.25functionaddRole(bytes32roleId,bytes32adminRoleId)26publiconlyMember(adminRoleId){27_setRoleAdmin(roleId,adminRoleId);28emitAdminRoleSet(roleId,adminRoleId);29}30}這遵循RBAC標準,以某種方式啟用了動態訪問控制。但是這只是解決方案的一半。它可以動態設定角色,但是必須對功能的訪問級別進行硬編碼。例如我們有一個名為“設定”的智慧合約,該合約只能由具有“ ADMIN”和“ EDITOR”角色的使用者呼叫。設定智慧合約中的require語句必須按照以下方式進行硬編碼:

1functionaSettingsFunction()publiconlyMember('ADMIN')onlyMember('EDITOR'){}因此仍然存在相同的問題。如果InfoSec管理員想要動態新增應該有權使用此功能的新角色,則必須為此功能編寫並附帶一個新定義。另一種方法是為每個使用者分配多個角色。因此,將為管理員分配角色“ ADMIN”,“ EDITOR”和“ WRITER”。假設所有管理員也是編輯者和作家,則可以將上述功能編寫為:
1functionaSettingsFunction()publiconlyMember('EDITOR'){}但這並沒有真正遵循RBAC,在RBAC中,每個使用者都有一個角色,並且在分層系統中,他們繼承了較低角色的許可權。我們仍然存在這樣一個問題:根據需要引入新的角色,並將它們動態地分配給函式。提議

首先我提出了一個稱為DynamicAccessControl的子智慧合約,該智慧合約類似於Hierarchy,但onlyMember修飾符實現了onlyMembersOf修飾符,該修飾符接受一組角色ID,並要求將傳送方至少分配給這些角色中的一個。程式碼8展示了這個的實現。

1pragmasolidity^0.6.0; 2 3import"openzeppelin-solidity/contracts/access/AccessControl.sol"; 4 5contractDynamicAccessControlisAccessControl{ 6eventAdminRoleSet(bytes32roleId,bytes32adminRoleId); 7 8constructor(addressroot)public{ 9_grantRole(DEFAULT_ADMIN_ROLE,root);10}1112modifieronlyMember(bytes32roleId){13require(hasRole(roleId,msg.sender),"Restrictedtomembers.");14_;15}1617modifieronlyMembersOf(bytes32[]memoryroleIds){18boolisMember=false;19for(uinti=0;i<roleIds.length;i++){20if(hasRole(roleIds[i],msg.sender)){21isMember=true;22break;23}24}25require(isMember==true,"Restrictedtomembers.");26_;27}2829functionaddRole(bytes32roleId,bytes32adminRoleId)publiconlyMember(adminRoleId){30_setRoleAdmin(roleId,adminRoleId);31emitAdminRoleSet(roleId,adminRoleId);32}33}作為實現此修飾符的智慧合約的一個例子,管理員使用者只需要“ADMIN”角色,函式定義將這樣定義。

1functionaSettingsFunction()publiconlyMembersOf(['ADMIN','EDITOR']){}在這個階段,仍然存在能夠輕鬆更新子函式的訪問修飾符的問題,因此InfoSec仍然無法在不傳送新程式碼的情況下更新某些角色對某些函式的訪問。但是我們現在可以為每個使用者分配一個角色,從而為每個函式分配多個訪問級別(角色)。為了啟用函式的更新許可權,子智慧合約需要維護角色集。其次,我提出一個名為RoleSets的庫,該庫維護訪問控制的智慧合約可以使用的角色集。程式碼9展示了該庫的外觀的部分示例。

1pragmasolidity^0.6.0; 2 3libraryRoleSets{ 4 5structRoleSet{ 6bytes32[]roleIds; 7} 8 9functionaddRoleId(RoleSetstorage_roleSet,bytes32_roleId)internal{10_roleSet.roleIds.push(_roleId);11}12}現在我們假設有一個名為ControlledContract的訪問控制合約,如程式碼10所示。

1pragmasolidity^0.6.0; 2 3import"./DynamicAccessControl.sol"; 4import"./RoleSets.sol"; 5 6contractControlledContractisDynamicAccessControl{ 7usingRoleSetsforRoleSets.RoleSet; 8 9RoleSets.RoleSetrootRoleSet;10RoleSets.RoleSetsecondaryRoleSet;11RoleSets.RoleSettertiaryRoleSet;1213bytes32publicconstantROOT_ROLE_SET=0x00;1415constructor()publicDynamicAccessControl(msg.sender){16rootRoleSet.addRoleId(ROOT_ROLE_SET);17secondaryRoleSet.addRoleId(ROOT_ROLE_SET);18tertiaryRoleSet.addRoleId(ROOT_ROLE_SET);19}2021functionaddRoleToSecondarySet(bytes32_roleId)publiconlyMembersOf(rootRoleSet.roleIds){22secondaryRoleSet.addRoleId(_roleId);23}2425functionaddRoleToTertiarySet(bytes32_roleId)publiconlyMembersOf(secondaryRoleSet.roleIds){26tertiaryRoleSet.addRoleId(_roleId);27}2829}定義的三個RoleSet在每個函式的定義中具有屬性,因此只有該集合中角色的成員才能呼叫這些函式(only成員必須是access修飾符)。例如,只有secondaryRoleSet角色成員可以呼叫addRoleToTertiarySet()。不僅如此,還可以更新集合以包括建立的新角色。因此在此智慧合約中,可以將動態建立的角色新增到集合中,從而提供對訪問控制功能的訪問。如果沒有釋出新的硬編碼RoleSet,這是以前無法完成的。但是,由於RoleSets.RoleSet狀態變數的數量取決於對其進行編碼,因此仍然需要進行硬編碼。結論

從本質上講,這導致了基於角色的訪問控制(RBAC)和基於屬性的訪問控制(ABAC)之間的混合。每個使用者僅分配有一個角色,每個功能都分配有一個包含多個角色的RoleSet(可以與ABAC中的屬性進行比較)。這消除了為每個使用者授予多個角色以手動實施較小角色的繼承的必要性(例如“ ADMIN”還授予了“ EDITOR”和“ WRITER”角色以繼承使用這些屬性呼叫函式的功能)。可以將定義的RoleSets.RoleSet狀態變數更新為包括新建立和動態建立的角色,從而提供InfoSec管理員所需的功能。然而,這仍然在一定程度上限於最初硬編碼的集合的數量。未來發展

我相信有一種方法可以從此解決方案中完全刪除硬編碼。代替控制單個智慧合約中對功能的訪問,整個智慧合約可以受RoleSet的訪問控制。我將嘗試將整個智慧合約(具有關聯的RoleSet)註冊到中央授權智慧合約。每個註冊的智慧合約都可以由root管理員動態更改關聯的RoleSet,從而完全不需要硬編碼。相關文章閱讀:

區塊鏈研究實驗室|關於資料代幣與資料訪問控制許可權的設想

區塊鏈研究實驗室|Solidity所有權和訪問控制教程指南

區塊鏈研究實驗室|智慧合約(in)安全性–資料越權訪問(&nbsp;Broken&nbsp;Access&nbsp;Control&nbsp;)

區塊鏈研究實驗室-Hyperledger&nbsp;Fabric指定訪問控制列表

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;