Skip to content

Commit

Permalink
backend: On-chain SIWE message parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Jun 28, 2024
1 parent e2f3bcf commit 536f5af
Show file tree
Hide file tree
Showing 6 changed files with 505 additions and 51 deletions.
218 changes: 218 additions & 0 deletions backend/contracts/DateTime.sol
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;
}
}
51 changes: 8 additions & 43 deletions backend/contracts/MessageBox.sol
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;
}
}
Loading

0 comments on commit 536f5af

Please sign in to comment.