Maximilian Pudelko

Voraussichtliche Lesedauer: 7 Minuten

Techblog

Zugriffskontrolle bei Smart Contracts

Smart Contract Access Control beyond Ownable Mit Smart Contracts programmieren Entwickler das Ethereum-Netzwerk aus weltweit verteilten Computern und Datenspeichern. Aufgrund der starken Verschiedenheit zu normalen Computern braucht man eine eigene Programmiersprache, Solidity, um Smart Contracts zu verfassen. Dazu muss man wissen: Es gibt nicht die Ethereum-Blockchain sondern mehrere, komplett voneinander unabhängige Netzwerke, die sich in…

Techblog
Businessman using fingerprint identification to access personal financial data. Idea for E-kyc (electronic know your customer), biometrics security, innovation technology against digital cyber crime.

Smart Contract Access Control beyond Ownable

Mit Smart Contracts programmieren Entwickler das Ethereum-Netzwerk aus weltweit verteilten Computern und Datenspeichern. Aufgrund der starken Verschiedenheit zu normalen Computern braucht man eine eigene Programmiersprache, Solidity, um Smart Contracts zu verfassen.

Dazu muss man wissen: Es gibt nicht die Ethereum-Blockchain sondern mehrere, komplett voneinander unabhängige Netzwerke, die sich in Konfiguration und Zweck unterscheiden: Meistens ist das Mainnet gemeint, wenn von Ethereum gesprochen wird. Daneben gibt es aber auch eine Reihe von Testnetzen wie Ropsten, Kovan oder Rinkeby mit jeweils anderen Mechanismen hinter der Konsensfindung. Natürlich gibt es auch die Möglichkeit, eine private Chain zu betreiben, falls man sich davon mehr Sicherheit oder Privatheit erhofft. Der Fokus liegt im Moment aber klar auf public Chains.

Zugriffskontrolle für die Blockchain

Für Contracts auf einer öffentlichen Blockchain wie dem Mainnet ergeben sich Anforderungen, die wir in klassischen Serveranwendungen, nicht haben. Eine davon sind Zugriffsbeschränkungen: Jeder kann den Speicher eines Contracts lesen und durch das Aufrufen von Funktionen auch verändern. Für Anwendungen, die Werte oder Vermögen verwalten, folgt daraus natürlich sofort, dass eine Zugriffsbeschränkung implementiert werden muss. Eine verbreitete Standardlösung hierfür ist das Ownable Konzept, welches dem Contract einen Besitzer zuordnet und nur diesen berechtigt, markierte Funktionen aufzurufen:

pragma solidity ^0.4.10;

contract Fundstorage {
    address private owner;

    Fundstorage() public {
        owner = msg.sender();
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function withdraw(uint amount) onlyOwner public {
        // ...
    }
}

So ist hier die withdraw() Funktion zwar public, allerdings erfüllt nur der Besitzer die Anforderung, die durch den onlyOwner Modifier gestellt wird. Das bedeutet: Nur er kann die Funktion tatsächlich vollständig ausführen.

Während dies ein solides and bewährtes Mittel der Zugangskontrolle ist, ist es nicht für alle Anwendungen geeignet. So kann zu jedem Zeitpunkt immer nur eine Person der Besitzer sein. Desweiteren hat dieser immer die vollumfängliche Rechte, eine Differenzierung oder Abgrenzung in Rollen oder eine Bindung an einzelne Objekte ist nicht vorgesehen.

RBAC: Fine-grained access control on the example of a Voting App

Das OpenZeppelin-Team widmet sich diesem Problem mit der Role-based Access Control (RBAC) Library. Diese besteht aus der low-level Solidity-Library Roles.sol und dem Solitity Contract RBAC.sol. In folgendem Beispiel verwenden wir beide, um einen Smart Contract für Abstimmungen zu implementieren. Der Smart Contract soll es einer privilegierten Gruppe von Administratoren erlauben, Abstimmungen zu erstellen; außerdem können sie den Abstimmungen Stimmberechtigte hinzuzufügen. Die definierten Stimmberechtigten, und nur diese, können dann ihre Zustimmung oder Ablehnung zu einem Thema ausdrücken indem sie ihr Stimmrecht verwenden.

Durch Vererbung von RBAC.sol erhält unser Contract automatisch die Werkzeuge, um unsere kritischen Funktionen addPoll() und addVoter()zu schützen. Nur noch die Mitglieder der Rolle “admin” sind berechtigt, diese auszuführen. Gleichzeitig können sie dieses Recht jederzeit an weitere Nutzer weitergeben – und auch wieder entziehen.

Für einzelne Abstimmungen selbst wäre dies aber nicht ausreichend, da der oder die Stimmberechtigte einer Abstimmung automatisch an allen Abstimmungen teilnehmen könnte. Deswegen benutzen wir die Roles.sol Library, um Rechte an ein einzelnes Objekt, hier eine Abstimmung, zu binden. Wir fügen dem Poll-Konstrukt eine weitere Variable voters hinzu, die die Stimmberechtigten speichert. Diese Form der Einbettung geht aus einer Limitierung der Solidity-Programmiersprache hervor: Libraries können keinerlei State halten. Ihre Funktionen sind also mathematisch “pure” und haben außer dem In-/Output keine Seiteneffekte. Wir sind also für die Speicherung des Zustands selbst verantwortlich.

Mit der Funktion addVoter() geben Admins Nutzern ein Stimmrecht, welches beim eigentlichen Abgeben der Stimme (vote()) überprüft wird. Dafür schreiben wir einen eigenen Modifier onlyVoters(). Er überprüft, ob der Absender der Transaktion an dieser Abstimmung teilnehmen darf. Modifier sind kleine, wiederverwendbare Code-Schnipsel, die häufig benötigte Funktionalität implementieren. Statt also immer wieder den gleichen Code zu schreiben, wird eine Funktion mit dem Modifier “dekoriert” und dann automatisch vor dem ersten Statement der Funktion ausgeführt.

Als kleinen Bonus können wir so auch sehr leicht mehrfaches Abstimmen verhindern: Die Stimmberechtigung wird nach erfolgreicher Stimmabgabe einfach wieder entzogen.

Zusammen mit den import Befehlen, welche die Libraries einbinden, sieht unser Contract dann wie folgt aus:

pragma solidity ^0.4.10;

import 'zeppelin-solidity/contracts/ownership/rbac/RBAC.sol';
import 'zeppelin-solidity/contracts/ownership/rbac/Roles.sol';

contract PollContract is RBAC {
    using Roles for Roles.Role;

    struct Poll {
        string question;
        uint yes;
        uint no;
        Roles.Role voters;
    }
    Poll[] private polls;

    function PollContract() RBAC() public { }

    /* Inherited from RBAC.sol
    string public constant ROLE_ADMIN = "admin";
    function adminAddRole(address addr, string roleName) onlyAdmin public {
        addRole(addr, roleName);
    }
    */

    function addPoll(string question) onlyAdmin public {
        polls.push(Poll(question, 0, 0, Roles.Role()));
    }

    function addVoter(uint pollID, address voter) onlyAdmin public {
        polls[pollID].voters.add(voter);
    }

    modifier onlyVoter(Poll storage poll) {
        poll.voters.check(msg.sender);
        _;
    }

    function vote(uint pollID, bool yes) onlyVoter(polls[pollID]) public {
        if (yes) {
            polls[pollID].yes++;
        } else {
            polls[pollID].no++;
        }
        polls[idx].voters.remove(msg.sender);
    }
}

Sichere Smart Contracts: erster Schritt von vielen

Wir haben an einer Abstimmungs-App gezeigt, wie leicht wir fortgeschrittene Zugangsbeschränkungen in Smart Contracts mit externen Libraries wie dem OpenZeppelin RBAC Modul implementieren. Mit relativ geringem Codeaufwand (und daher geringem Risiko, selbst Fehler zu machen) ergibt sich folgende Rechte-Matrix:

PersonaddAdmin()createPoll()/addVoter()vote(poll1)vote(poll2)
AdminsXX
Voterpoll1XXX
Voterpoll2XXX
UnregistriertXXXX

Das OpenZeppelin Team hat Smart-Contract-Entwicklern hiermit ein weiteres exzellentes Werkzeug gegeben, um die Sicherheit ihrer Contracts zu verbessern.

Auch wenn das ein Schritt in die richtige Richtung ist, erreicht man damit noch nicht den Standard an Funktionalität wie er in klassischen Systemen zu finden ist. So wird der temporale Aspekt eines Rechts noch ganz außer Acht gelassen. Einschränkungen wie Gültigkeit “nicht bevor Y” und “nur bis Zeitpunkt X”, wie sie aus X.509 Zertifikaten (HTTPS/TLS) bekannt sind, fehlen noch. Ebenfalls denkbar wäre eine hierarchische Rollenordnung in der (Sub-)Rechte anderen Rechten untergeordnet und automatisch vergeben oder entzogen werden können.


Über den Autor

Maximilian Pudelko