-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
backend: On-chain SIWE message parsing
- Loading branch information
Showing
6 changed files
with
505 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
// https://github.com/pipermerriam/ethereum-datetime/blob/master/contracts/DateTime.sol | ||
contract DateTime { | ||
/* | ||
* Date and Time utilities for ethereum contracts | ||
* | ||
*/ | ||
struct _DateTime { | ||
uint16 year; | ||
uint8 month; | ||
uint8 day; | ||
uint8 hour; | ||
uint8 minute; | ||
uint8 second; | ||
uint8 weekday; | ||
} | ||
|
||
uint constant DAY_IN_SECONDS = 86400; | ||
uint constant YEAR_IN_SECONDS = 31536000; | ||
uint constant LEAP_YEAR_IN_SECONDS = 31622400; | ||
|
||
uint constant HOUR_IN_SECONDS = 3600; | ||
uint constant MINUTE_IN_SECONDS = 60; | ||
|
||
uint16 constant ORIGIN_YEAR = 1970; | ||
|
||
function isLeapYear(uint16 year) public pure returns (bool) { | ||
if (year % 4 != 0) { | ||
return false; | ||
} | ||
if (year % 100 != 0) { | ||
return true; | ||
} | ||
if (year % 400 != 0) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
function leapYearsBefore(uint year) public pure returns (uint) { | ||
year -= 1; | ||
return year / 4 - year / 100 + year / 400; | ||
} | ||
|
||
function getDaysInMonth(uint8 month, uint16 year) public pure returns (uint8) { | ||
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { | ||
return 31; | ||
} | ||
else if (month == 4 || month == 6 || month == 9 || month == 11) { | ||
return 30; | ||
} | ||
else if (isLeapYear(year)) { | ||
return 29; | ||
} | ||
else { | ||
return 28; | ||
} | ||
} | ||
|
||
function parseTimestamp(uint timestamp) internal pure returns (_DateTime memory dt) { | ||
uint secondsAccountedFor = 0; | ||
uint buf; | ||
uint8 i; | ||
|
||
// Year | ||
dt.year = getYear(timestamp); | ||
buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR); | ||
|
||
secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf; | ||
secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf); | ||
|
||
// Month | ||
uint secondsInMonth; | ||
for (i = 1; i <= 12; i++) { | ||
secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year); | ||
if (secondsInMonth + secondsAccountedFor > timestamp) { | ||
dt.month = i; | ||
break; | ||
} | ||
secondsAccountedFor += secondsInMonth; | ||
} | ||
|
||
// Day | ||
for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) { | ||
if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) { | ||
dt.day = i; | ||
break; | ||
} | ||
secondsAccountedFor += DAY_IN_SECONDS; | ||
} | ||
|
||
// Hour | ||
dt.hour = getHour(timestamp); | ||
|
||
// Minute | ||
dt.minute = getMinute(timestamp); | ||
|
||
// Second | ||
dt.second = getSecond(timestamp); | ||
|
||
// Day of week. | ||
dt.weekday = getWeekday(timestamp); | ||
} | ||
|
||
function getYear(uint timestamp) public pure returns (uint16) { | ||
uint secondsAccountedFor = 0; | ||
uint16 year; | ||
uint numLeapYears; | ||
|
||
// Year | ||
year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS); | ||
numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR); | ||
|
||
secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears; | ||
secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears); | ||
|
||
while (secondsAccountedFor > timestamp) { | ||
if (isLeapYear(uint16(year - 1))) { | ||
secondsAccountedFor -= LEAP_YEAR_IN_SECONDS; | ||
} | ||
else { | ||
secondsAccountedFor -= YEAR_IN_SECONDS; | ||
} | ||
year -= 1; | ||
} | ||
return year; | ||
} | ||
|
||
function getMonth(uint timestamp) public pure returns (uint8) { | ||
return parseTimestamp(timestamp).month; | ||
} | ||
|
||
function getDay(uint timestamp) public pure returns (uint8) { | ||
return parseTimestamp(timestamp).day; | ||
} | ||
|
||
function getHour(uint timestamp) public pure returns (uint8) { | ||
return uint8((timestamp / 60 / 60) % 24); | ||
} | ||
|
||
function getMinute(uint timestamp) public pure returns (uint8) { | ||
return uint8((timestamp / 60) % 60); | ||
} | ||
|
||
function getSecond(uint timestamp) public pure returns (uint8) { | ||
return uint8(timestamp % 60); | ||
} | ||
|
||
function getWeekday(uint timestamp) public pure returns (uint8) { | ||
return uint8((timestamp / DAY_IN_SECONDS + 4) % 7); | ||
} | ||
|
||
function toTimestamp(uint16 year, uint8 month, uint8 day) public pure returns (uint timestamp) { | ||
return toTimestamp(year, month, day, 0, 0, 0); | ||
} | ||
|
||
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) public pure returns (uint timestamp) { | ||
return toTimestamp(year, month, day, hour, 0, 0); | ||
} | ||
|
||
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) public pure returns (uint timestamp) { | ||
return toTimestamp(year, month, day, hour, minute, 0); | ||
} | ||
|
||
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) public pure returns (uint timestamp) { | ||
uint16 i; | ||
|
||
// Year | ||
for (i = ORIGIN_YEAR; i < year; i++) { | ||
if (isLeapYear(i)) { | ||
timestamp += LEAP_YEAR_IN_SECONDS; | ||
} | ||
else { | ||
timestamp += YEAR_IN_SECONDS; | ||
} | ||
} | ||
|
||
// Month | ||
uint8[12] memory monthDayCounts; | ||
monthDayCounts[0] = 31; | ||
if (isLeapYear(year)) { | ||
monthDayCounts[1] = 29; | ||
} | ||
else { | ||
monthDayCounts[1] = 28; | ||
} | ||
monthDayCounts[2] = 31; | ||
monthDayCounts[3] = 30; | ||
monthDayCounts[4] = 31; | ||
monthDayCounts[5] = 30; | ||
monthDayCounts[6] = 31; | ||
monthDayCounts[7] = 31; | ||
monthDayCounts[8] = 30; | ||
monthDayCounts[9] = 31; | ||
monthDayCounts[10] = 30; | ||
monthDayCounts[11] = 31; | ||
|
||
for (i = 1; i < month; i++) { | ||
timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1]; | ||
} | ||
|
||
// Day | ||
timestamp += DAY_IN_SECONDS * (day - 1); | ||
|
||
// Hour | ||
timestamp += HOUR_IN_SECONDS * (hour); | ||
|
||
// Minute | ||
timestamp += MINUTE_IN_SECONDS * (minute); | ||
|
||
// Second | ||
timestamp += second; | ||
|
||
return timestamp; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
import "hardhat/console.sol"; | ||
|
||
struct Sig { | ||
uint8 v; | ||
bytes32 r; | ||
bytes32 s; | ||
} | ||
import "./SiweAuth.sol"; | ||
|
||
contract MessageBox { | ||
contract MessageBox is SiweAuth { | ||
string private _message; | ||
address public author; | ||
|
||
function toAsciiStringAddr(address x) internal pure returns (string memory) { | ||
bytes memory s = new bytes(40); | ||
for (uint i = 0; i < 20; i++) { | ||
bytes1 b = bytes1(uint8(uint(uint160(x)) / (2**(8*(19 - i))))); | ||
bytes1 hi = bytes1(uint8(b) / 16); | ||
bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); | ||
s[2*i] = char(hi); | ||
s[2*i+1] = char(lo); | ||
} | ||
return string(s); | ||
} | ||
|
||
function char(bytes1 b) internal pure returns (bytes1 c) { | ||
if (uint8(b) < 10) return bytes1(uint8(b) + 0x30); | ||
else return bytes1(uint8(b) + 0x57); | ||
} | ||
|
||
|
||
function getSiweMsg() external view returns (bytes memory) { | ||
string memory domain="demo-starter"; | ||
string memory uri="http://localhost:5173"; | ||
string memory version="1"; | ||
string memory chainId="0x5afd"; | ||
string memory nonce="1"; | ||
string memory issuedAt="2021-09-30T16:25:24Z"; | ||
|
||
// TODO: contract address needs to be hex case-sensitive checksummed. | ||
bytes memory siweMsg = abi.encodePacked(domain, " wants you to sign in with your Ethereum account:\n0x", toAsciiStringAddr(address(this)), "\n\n\n\nURI: ", uri, "\nVersion: ",version,"\nChain ID: ", chainId, "\nNonce: ", nonce, "\nIssued At: ", issuedAt); | ||
return siweMsg; | ||
} | ||
|
||
modifier _authorOnly(Sig calldata auth) { | ||
bytes memory eip191msg = abi.encodePacked("\x19Ethereum Signed Message:\n", "203", this.getSiweMsg()); | ||
address addr = ecrecover(keccak256(eip191msg), auth.v, auth.r, auth.s); | ||
if (addr != author) { | ||
modifier _authorOnly(bytes calldata bearer) { | ||
if (authMsgSender(bearer) != author) { | ||
revert("not allowed"); | ||
} | ||
_; | ||
} | ||
|
||
constructor(string memory domain) SiweAuth(domain) { | ||
} | ||
|
||
function setMessage(string calldata in_message) external { | ||
_message = in_message; | ||
author = msg.sender; | ||
} | ||
|
||
function message(Sig calldata auth) external view _authorOnly(auth) returns (string memory) { | ||
function message(bytes calldata bearer) external view _authorOnly(bearer) returns (string memory) { | ||
return _message; | ||
} | ||
} |
Oops, something went wrong.