11// SPDX-License-Identifier: Apache-2.0
22pragma solidity ^ 0.8.20 ;
33
4- import {Strings} from "@openzeppelin/contracts/utils/Strings.sol " ;
5-
64import {SignatureRSV, A13e} from "@oasisprotocol/sapphire-contracts/contracts/auth/A13e.sol " ;
7- import {
8- ParsedSiweMessage,
9- SiweParser
10- } from "@oasisprotocol/sapphire-contracts/contracts/SiweParser.sol " ;
115import {Sapphire} from "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol " ;
6+ import {Subcall} from "@oasisprotocol/sapphire-contracts/contracts/Subcall.sol " ;
127
138/// @title AuthToken structure for SIWE-based authentication
149struct AuthToken {
@@ -31,29 +26,28 @@ struct AuthToken {
3126 * The authentication logic (login/authMsgSender) is only supported on Sapphire chains.
3227 */
3328contract AccountingSiweAuth is A13e {
34- string internal _domain;
3529 bytes32 private _authTokenEncKey;
36- uint256 private constant DEFAULT_VALIDITY = 24 hours ;
30+ bytes21 private _roflAppId ;
3731
3832 error SiweAuth_UnsupportedChain ();
39- error SiweAuth_ChainIdMismatch ();
40- error SiweAuth_DomainMismatch ();
41- error SiweAuth_AddressMismatch ();
42- error SiweAuth_NotBeforeInFuture ();
4333 error SiweAuth_Expired ();
34+ error SiweAuth_NotAuthorizedRofl ();
35+ error SiweAuth_LoginDisabled ();
4436
45- constructor (string memory inDomain ) {
46- _domain = inDomain ;
37+ constructor (bytes21 inRoflAppId ) {
38+ _roflAppId = inRoflAppId ;
4739
48- if ( _isSapphireChainId ( block . chainid )) {
49- _authTokenEncKey = bytes32 (Sapphire. randomBytes ( 32 , "" ));
50- } else {
40+ // TODO: Remove non-Sapphire fallback when Sapphire localnet e2e tests are available.
41+ // This allows deployment on Hardhat/local networks for unit testing.
42+ if ( ! _isSapphireChainId ( block . chainid )) {
5143 // Deterministic key for non-Sapphire local test networks (e.g., Hardhat).
5244 // Authentication is not expected to be used off-Sapphire.
45+ // On Sapphire, the ROFL service will set the key via setAuthTokenEncKey().
5346 _authTokenEncKey = bytes32 (uint256 (1 ));
5447 }
5548 }
5649
50+ // TODO: Remove when Sapphire localnet e2e tests are available.
5751 function _isSapphireChainId (uint256 chainId ) private pure returns (bool ) {
5852 return chainId == 0x5afe || chainId == 0x5aff || chainId == 0x5afd ;
5953 }
@@ -64,73 +58,30 @@ contract AccountingSiweAuth is A13e {
6458 }
6559 }
6660
67- function login (string calldata siweMsg , SignatureRSV calldata sig )
61+ /// @notice Set the AuthToken encryption key. Can only be called by the authorized ROFL app.
62+ /// @dev The ROFL app ID is set at deployment. Only that app can call this function.
63+ /// @param newKey The 32-byte Deoxys-II encryption key.
64+ function setAuthTokenEncKey (bytes32 newKey ) external {
65+ if (Subcall.getRoflAppId () != _roflAppId) {
66+ revert SiweAuth_NotAuthorizedRofl ();
67+ }
68+ _authTokenEncKey = newKey;
69+ }
70+
71+ /// @notice Login is disabled - use the REST API instead.
72+ /// @dev This function is kept for A13e interface compatibility but always reverts.
73+ /// The REST service generates and encrypts AuthTokens directly.
74+ function login (string calldata , SignatureRSV calldata )
6875 external
69- view
76+ pure
7077 override
7178 returns (bytes memory )
7279 {
73- _requireSapphire ();
74-
75- AuthToken memory b;
76-
77- // Derive the user's address from the signature.
78- bytes memory eip191msg = abi.encodePacked (
79- "\x19Ethereum Signed Message:\n " ,
80- Strings.toString (bytes (siweMsg).length ),
81- siweMsg
82- );
83- address addr = ecrecover (
84- keccak256 (eip191msg),
85- uint8 (sig.v),
86- sig.r,
87- sig.s
88- );
89- b.userAddr = addr;
90-
91- ParsedSiweMessage memory p = SiweParser.parseSiweMsg (bytes (siweMsg));
92-
93- if (p.chainId != block .chainid ) {
94- revert SiweAuth_ChainIdMismatch ();
95- }
96-
97- if (keccak256 (p.schemeDomain) != keccak256 (bytes (_domain))) {
98- revert SiweAuth_DomainMismatch ();
99- }
100- b.domain = string (p.schemeDomain);
101-
102- if (p.addr != addr) {
103- revert SiweAuth_AddressMismatch ();
104- }
105-
106- if (
107- p.notBefore.length != 0 &&
108- block .timestamp <= SiweParser.timestampFromIso (p.notBefore)
109- ) {
110- revert SiweAuth_NotBeforeInFuture ();
111- }
112-
113- if (p.expirationTime.length != 0 ) {
114- b.validUntil = SiweParser.timestampFromIso (p.expirationTime);
115- } else {
116- b.validUntil = block .timestamp + DEFAULT_VALIDITY;
117- }
118- if (block .timestamp >= b.validUntil) {
119- revert SiweAuth_Expired ();
120- }
121-
122- b.statement = string (p.statement);
123-
124- b.resources = new string [](p.resources.length );
125- for (uint256 i = 0 ; i < p.resources.length ; i++ ) {
126- b.resources[i] = string (p.resources[i]);
127- }
128-
129- return Sapphire.encrypt (_authTokenEncKey, 0 , abi.encode (b), "" );
80+ revert SiweAuth_LoginDisabled ();
13081 }
13182
132- function domain () public view returns (string memory ) {
133- return _domain ;
83+ function roflAppId () public view returns (bytes21 ) {
84+ return _roflAppId ;
13485 }
13586
13687 function authMsgSender (bytes memory token )
@@ -166,10 +117,6 @@ contract AccountingSiweAuth is A13e {
166117 );
167118 AuthToken memory b = abi.decode (authTokenEncoded, (AuthToken));
168119
169- if (keccak256 (bytes (b.domain)) != keccak256 (bytes (_domain))) {
170- revert SiweAuth_DomainMismatch ();
171- }
172-
173120 if (b.validUntil < block .timestamp ) {
174121 revert SiweAuth_Expired ();
175122 }
0 commit comments