From 0c967a5dcba17a5de6ad07fef8f9e29f38e276ea Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 8 Oct 2024 11:09:33 +0200 Subject: [PATCH 01/26] Update eth-demls.md --- vac/raw/eth-demls.md | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index cd3e9c1f..c7cb25bb 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1005,6 +1005,104 @@ version of the tree that allows for the generation of the group key. Another important component is the _authentication service_, which is replaced with SIWE in this specification. +## Considerations regarding authentication + +SIWE presents some centralization issues for random node generation. +Since smart contracts cannot generate random nonces without the need of an oracle, +or can only generate pseudo-random nonces +which would be the result of combining data which could be hacked. + +The following approach aims at mitigating these issues. +It is similar to SIWE, +and would imply the users requesting to log into the system +to create the nonce by themselves, sign it, +and then send it to the smart contract for verification. +This makes the approach easier to implement +and deploy when compared to the above MPC description. + +In order to avoid security issues like replay attacks and DDoS, +one needs to have the following aspects in mind: + +1. Nonce uniqueness: this can be guaranteed using a map to track used nonces for each signature. +This would prevent replay attacks as it provides proofs +that a nonce was used only once by a particular address. +2. Temporal uniqueness: timestamps allow checkings to prevent the reuse of old signatures. +3. Message uniqueness: including the sender’s address in the message hash +ensures that messages are unique even if two users collide in the nonce and the timestamp. +4. Group membership: one could keep a separate mapping +to track who is allowed to authenticate. +5. Admin role: the creator of the smart contract would have an admin role, +allowing the dynamic management of group membership. + +## Pseudocode + +The following pseudocode outlines functions for a smart contract managing group users. +The first two functions handle adding and removing members, +while the core function is `authenticate`. + +Timestamps and nonce tracking are recommended to avoid replay attacks and DDoS attacks. + +### Initial Setup +used_nonces = empty map of address to set of bytes32 +group_members = empty set of addresses +admin = creator_address + +### Function `add_group_member` +Input: member_address + +If the caller is not admin, return an error: +"Only admin can perform this action" +Add member_address to the group_members set + +### Function `remove_group_member` +Input: member_address + +If the caller is not admin, return an error: +"Only admin can perform this action" +Remove member_address from the group_members set + +### Function `authenticate` +Inputs: nonce, timestamp, signature + +Generate the message: +message = KECCAK256(CONCAT(nonce, timestamp, caller_address)) + +Recover the signer from the signature: +signer = RECOVER_SIGNER(message, signature) + +If signer is not the caller_address, return an error: +"Invalid signature" +Check if the timestamp is recent: + +If block.timestamp - timestamp > 5 minutes, return an error: +"Nonce expired" +Check if the nonce has been used: + +If the nonce exists in used_nonces[caller_address], return an error: +"Nonce already used" +Ensure the caller is a group member: + +If caller_address is not in group_members, return an error: +"Not a group member" +Add the nonce to used_nonces[caller_address] + +### Helper Function `recover_signer` +Inputs: message, signature + +Convert message to bytes: +message_bytes = to_bytes(text=message) + +Hash the message using KECCAK-256: +message_hash = keccak(message_bytes) + +Recover the public key from the message hash and signature: +public_key = keys.Signature(signature).recover_public_key_from_msg_hash(message_hash) + +Convert the public key to an Ethereum address: +address = public_key.to_checksum_address() + +Return the address + ## Privacy and Security Considerations - For the information retrieval, the algorithm MUST include a access From f686b60f5acd62806464816d3d073cf321bb00b1 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 8 Oct 2024 11:51:29 +0200 Subject: [PATCH 02/26] Update eth-demls.md --- vac/raw/eth-demls.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index c7cb25bb..11f9a4d2 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1023,7 +1023,7 @@ and deploy when compared to the above MPC description. In order to avoid security issues like replay attacks and DDoS, one needs to have the following aspects in mind: -1. Nonce uniqueness: this can be guaranteed using a map to track used nonces for each signature. +1. Nonce uniqueness: this can be guaranteed tracking used nonces for each signature. This would prevent replay attacks as it provides proofs that a nonce was used only once by a particular address. 2. Temporal uniqueness: timestamps allow checkings to prevent the reuse of old signatures. @@ -1043,11 +1043,13 @@ while the core function is `authenticate`. Timestamps and nonce tracking are recommended to avoid replay attacks and DDoS attacks. ### Initial Setup + used_nonces = empty map of address to set of bytes32 group_members = empty set of addresses admin = creator_address ### Function `add_group_member` + Input: member_address If the caller is not admin, return an error: @@ -1055,6 +1057,7 @@ If the caller is not admin, return an error: Add member_address to the group_members set ### Function `remove_group_member` + Input: member_address If the caller is not admin, return an error: @@ -1062,6 +1065,7 @@ If the caller is not admin, return an error: Remove member_address from the group_members set ### Function `authenticate` + Inputs: nonce, timestamp, signature Generate the message: @@ -1087,6 +1091,7 @@ If caller_address is not in group_members, return an error: Add the nonce to used_nonces[caller_address] ### Helper Function `recover_signer` + Inputs: message, signature Convert message to bytes: From ce1728e724fe13fd9948bfb6236b9bd92532d0ad Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 18 Oct 2024 11:40:06 +0200 Subject: [PATCH 03/26] Update eth-demls.md --- vac/raw/eth-demls.md | 390 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 330 insertions(+), 60 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 11f9a4d2..3053a430 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1036,77 +1036,347 @@ allowing the dynamic management of group membership. ## Pseudocode -The following pseudocode outlines functions for a smart contract managing group users. -The first two functions handle adding and removing members, -while the core function is `authenticate`. +### SIWE Message Structure -Timestamps and nonce tracking are recommended to avoid replay attacks and DDoS attacks. - -### Initial Setup - -used_nonces = empty map of address to set of bytes32 -group_members = empty set of addresses -admin = creator_address - -### Function `add_group_member` - -Input: member_address - -If the caller is not admin, return an error: -"Only admin can perform this action" -Add member_address to the group_members set - -### Function `remove_group_member` - -Input: member_address - -If the caller is not admin, return an error: -"Only admin can perform this action" -Remove member_address from the group_members set - -### Function `authenticate` - -Inputs: nonce, timestamp, signature - -Generate the message: -message = KECCAK256(CONCAT(nonce, timestamp, caller_address)) - -Recover the signer from the signature: -signer = RECOVER_SIGNER(message, signature) - -If signer is not the caller_address, return an error: -"Invalid signature" -Check if the timestamp is recent: +``` +STRUCTURE SIWEMessage: + domain: STRING # The domain requesting the signing + address: ADDRESS # The user's Ethereum address + uri: STRING # The URI of the dApp + chainId: INTEGER # The ID of the blockchain network + issuedAt: TIMESTAMP # When the message was issued + expirationTime: TIMESTAMP # When the message expires + purpose: STRING # New field for unique contextual data +``` -If block.timestamp - timestamp > 5 minutes, return an error: -"Nonce expired" -Check if the nonce has been used: +### Smart Contract functions -If the nonce exists in used_nonces[caller_address], return an error: -"Nonce already used" -Ensure the caller is a group member: +This contract handles authentication, group management, and session management -If caller_address is not in group_members, return an error: -"Not a group member" -Add the nonce to used_nonces[caller_address] +``` +CLASS EthereumStyleAuthWithTimeWindow: + # State variables + groupMembers: MAP # Stores group membership status + admin: ADDRESS # The address of the contract administrator + lastAuthTime: MAP # Tracks last authentication time for each user + sessions: MAP> # Stores session expiration times + sessionHashes: MAP> # Maps session IDs to IPFS hashes +``` +``` + # State variables for failed attempt tracking + failedAttempts: MAP + lastFailedAttemptTime: MAP + + # Constants for time-validity window + CONSTANT MIN_VALIDITY_PERIOD = 30 seconds # Tightened validity window + CONSTANT MAX_VALIDITY_PERIOD = 2 minutes # Adjusted as needed + CONSTANT MIN_AUTH_INTERVAL = 30 seconds # Keep as is or adjust as needed + + # Constants for failed attempt limits + CONSTANT MAX_FAILED_ATTEMPTS = 5 + CONSTANT LOCKOUT_DURATION = 15 minutes +``` +``` + # Constructor: Initializes the contract state + CONSTRUCTOR(): + groupMembers = EMPTY_MAP() + admin = TRANSACTION_SENDER() # Set the contract deployer as admin + groupMembers[admin] = TRUE # Add admin to the group + allowedPurposes = EMPTY_MAP() + allowedPurposes["Authenticate to create session"] = TRUE + EMIT GroupMemberAdded(admin) +``` +``` + # Adds a new member to the group (admin only) + FUNCTION addGroupMember(memberAddress: ADDRESS): + REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") + REQUIRE(groupMembers[memberAddress] != TRUE, "Address is already a group member") + REQUIRE(memberAddress != NULL_ADDRESS, "Invalid address") + groupMembers[memberAddress] = TRUE + EMIT GroupMemberAdded(memberAddress) +``` +``` + # Removes a member from the group (admin only) + FUNCTION removeGroupMember(memberAddress: ADDRESS): + REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") + REQUIRE(memberAddress != admin, "Admin cannot be removed from the group") + REQUIRE(groupMembers[memberAddress] == TRUE, "Address is not a group member") + REQUIRE(memberAddress != NULL_ADDRESS, "Invalid address") + groupMembers[memberAddress] = FALSE + EMIT GroupMemberRemoved(memberAddress) +``` +``` + # Transfers admin rights to a new address + FUNCTION transferAdmin(newAdminAddress: ADDRESS): + REQUIRE(TRANSACTION_SENDER() == admin, "Only current admin can transfer admin rights") + REQUIRE(newAdminAddress != NULL_ADDRESS, "Invalid address") + REQUIRE(groupMembers[newAdminAddress] == TRUE, "New admin must be a group member") + EMIT AdminTransferred(admin, newAdminAddress) + admin = newAdminAddress +``` +``` + # Authenticates a user based on their SIWE message and signature + FUNCTION authenticate( + domain: STRING, + address: ADDRESS, + uri: STRING, + chainId: INTEGER, + issuedAt: TIMESTAMP, + expirationTime: TIMESTAMP, + purpose: STRING, + signature: BYTES + ): + signer = TRANSACTION_SENDER() + currentTime = CURRENT_TIMESTAMP() + + # Check if the user is in a lockout period + IF failedAttempts[signer] >= MAX_FAILED_ATTEMPTS: + lockoutEndTime = lastFailedAttemptTime[signer] + LOCKOUT_DURATION + IF currentTime < lockoutEndTime: + EMIT AuthenticationFailed(signer, "Account locked due to too many failed attempts") + RETURN FALSE + ELSE: + # Reset failed attempts after lockout duration + failedAttempts[signer] = 0 + + # Perform authentication checks + IF groupMembers[signer] != TRUE: + recordFailedAttempt(signer, currentTime, "Not a group member") + RETURN FALSE + + IF currentTime < issuedAt: + recordFailedAttempt(signer, currentTime, "Message not yet valid") + RETURN FALSE + + IF currentTime > expirationTime: + recordFailedAttempt(signer, currentTime, "Message has expired") + RETURN FALSE + + IF expirationTime - issuedAt < MIN_VALIDITY_PERIOD: + recordFailedAttempt(signer, currentTime, "Validity period too short") + RETURN FALSE + + IF expirationTime - issuedAt > MAX_VALIDITY_PERIOD: + recordFailedAttempt(signer, currentTime, "Validity period too long") + RETURN FALSE + + IF chainId != CURRENT_CHAIN_ID(): + recordFailedAttempt(signer, currentTime, "Invalid chain ID") + RETURN FALSE + + IF address != signer: + recordFailedAttempt(signer, currentTime, "Address mismatch") + RETURN FALSE + + IF currentTime - lastAuthTime[signer] < MIN_AUTH_INTERVAL: + recordFailedAttempt(signer, currentTime, "Authentication too frequent") + RETURN FALSE + + # Reconstruct and verify the message + message = SIWEMessage(domain, address, uri, chainId, issuedAt, expirationTime, purpose) + messageHash = HASH_STRUCTURED_DATA(message) + expectedSigner = RECOVER_SIGNER(messageHash, signature) + IF expectedSigner != signer: + recordFailedAttempt(signer, currentTime, "Invalid signature") + RETURN FALSE + + # Verify the purpose or unique contextual data + IF !verifyPurpose(purpose): + recordFailedAttempt(signer, currentTime, "Invalid purpose") + RETURN FALSE + + lastAuthTime[signer] = currentTime # Update last authentication time + failedAttempts[signer] = 0 # Reset failed attempts on success + EMIT AuthenticationSuccessful(signer, purpose) + RETURN TRUE +``` +``` + # Helper function to record failed authentication attempts + FUNCTION recordFailedAttempt(signer: ADDRESS, currentTime: TIMESTAMP, reason: STRING): + failedAttempts[signer] = failedAttempts[signer] + 1 + lastFailedAttemptTime[signer] = currentTime + EMIT AuthenticationFailed(signer, reason) +``` +``` + # Helper function to verify the purpose + FUNCTION verifyPurpose(purpose: STRING) RETURNS (BOOLEAN): + # Implement logic to verify that the purpose is valid and expected + # For example, check that the purpose matches a known action + RETURN purpose == "Authenticate to create session" +``` -### Helper Function `recover_signer` +### Session management functions -Inputs: message, signature +``` + # Creates a new session for an authenticated user + FUNCTION createSession(sessionId: BYTES32, expirationBlock: INTEGER, ipfsHash: STRING): + REQUIRE(groupMembers[TRANSACTION_SENDER()] == TRUE, "Not a group member") + REQUIRE(expirationBlock > CURRENT_BLOCK_NUMBER(), "Expiration block must be in the future") + REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] == NULL, "Session ID already exists for this user") + + sessions[TRANSACTION_SENDER()][sessionId] = expirationBlock + sessionHashes[TRANSACTION_SENDER()][sessionId] = ipfsHash + EMIT SessionCreated(TRANSACTION_SENDER(), sessionId, expirationBlock) +``` +``` + # Retrieves the IPFS hash for a given session ID + FUNCTION getSessionHash(sessionId: BYTES32) VIEW RETURNS (STRING): + expirationBlock = sessions[TRANSACTION_SENDER()][sessionId] + REQUIRE(expirationBlock != NULL, "Session does not exist") + REQUIRE(expirationBlock > CURRENT_BLOCK_NUMBER(), "Session has expired") -Convert message to bytes: -message_bytes = to_bytes(text=message) + RETURN sessionHashes[TRANSACTION_SENDER()][sessionId] +``` +``` + # Extends the expiration time of an existing session + FUNCTION extendSession(sessionId: BYTES32, newExpirationBlock: INTEGER): + REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") + REQUIRE(newExpirationBlock > CURRENT_BLOCK_NUMBER(), "New expiration block must be in the future") + sessions[TRANSACTION_SENDER()][sessionId] = newExpirationBlock + EMIT SessionExtended(TRANSACTION_SENDER(), sessionId, newExpirationBlock) +``` +``` + # Ends a session, removing it from storage + FUNCTION endSession(sessionId: BYTES32): + REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") + DELETE sessions[TRANSACTION_SENDER()][sessionId] + DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] + EMIT SessionEnded(TRANSACTION_SENDER(), sessionId) +``` +``` + # Cleans up expired sessions for the calling user + FUNCTION cleanUpExpiredSessions(): + FOR EACH sessionId IN sessions[TRANSACTION_SENDER()]: + expirationBlock = sessions[TRANSACTION_SENDER()][sessionId] + IF expirationBlock <= CURRENT_BLOCK_NUMBER(): + DELETE sessions[TRANSACTION_SENDER()][sessionId] + DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] + EMIT SessionsCleanedUp(TRANSACTION_SENDER()) +``` -Hash the message using KECCAK-256: -message_hash = keccak(message_bytes) +### Client side functions -Recover the public key from the message hash and signature: -public_key = keys.Signature(signature).recover_public_key_from_msg_hash(message_hash) +``` +# Generates a new SIWE message for authentication +FUNCTION generateSIWEMessage(userAddress: ADDRESS): + domain = "example.com" + uri = "https://example.com/login" + issuedAt = GET_CURRENT_TIMESTAMP() + expirationTime = issuedAt + 1 minute # Tight validity window + chainId = GET_CURRENT_CHAIN_ID() + purpose = "Authenticate to create session" # Unique contextual data + RETURN NEW SIWEMessage( + domain, userAddress, uri, chainId, issuedAt, expirationTime, purpose) +``` +``` +# Signs a SIWE message with the user's private key +FUNCTION signSIWEMessage(message: SIWEMessage, userAddress: ADDRESS): + messageHash = HASH_STRUCTURED_DATA(message) + signature = SIGN_MESSAGE_HASH(messageHash, userAddress) + RETURN signature +``` +``` +# Main authentication function that calls the smart contract +FUNCTION authenticate(): + userAddress = GET_CURRENT_USER_ADDRESS() + message = generateSIWEMessage(userAddress) + signature = signSIWEMessage(message, userAddress) + + authResult = CALL contract.authenticate( + message.domain, + message.address, + message.uri, + message.chainId, + message.issuedAt, + message.expirationTime, + message.purpose, # Pass the purpose to the contract + signature + ) AS userAddress + + IF authResult == TRUE: + sessionId = createAndStoreSession(userAddress) + RETURN "Authentication and session creation successful" + ELSE: + RETURN "Authentication failed" +``` +``` +# Constants or configuration parameters +AVERAGE_BLOCK_TIME = 12 # in seconds +SESSION_DURATION_IN_SECONDS = 3600 # 1 hour +SESSION_DURATION_IN_BLOCKS = ROUND(SESSION_DURATION_IN_SECONDS / AVERAGE_BLOCK_TIME) # 300 blocks +``` +``` +# Creates and stores a new session after successful authentication +FUNCTION createAndStoreSession(userAddress: ADDRESS): + sessionData = { + "userAddress": userAddress, + "loginTime": CURRENT_TIMESTAMP(), + # Add any other relevant session data + } + RANDOM_VALUE = GENERATE_RANDOM_BYTES32() + sessionId = KECCAK256(userAddress + CURRENT_TIMESTAMP() + RANDOM_VALUE) + expirationBlock = GET_CURRENT_BLOCK_NUMBER() + SESSION_DURATION_IN_BLOCKS + encryptedData = encryptSessionData(sessionData, USER_PUBLIC_KEY) + + ipfsHash = IPFS_ADD(encryptedData) + + CALL contract.createSession(sessionId, expirationBlock, ipfsHash) AS userAddress + + STORE_LOCALLY(sessionId) + RETURN sessionId +``` +``` +# Restores a user's session using the stored session ID +FUNCTION restoreUserSession(): + storedSessionId = RETRIEVE_LOCALLY_STORED_SESSION_ID() + IF storedSessionId != NULL: + TRY: + ipfsHash = CALL contract.getSessionHash(storedSessionId) AS userAddress + encryptedData = IPFS_GET(ipfsHash) + sessionData = decryptSessionData(encryptedData, USER_PRIVATE_KEY) + APPLY_SESSION_DATA(sessionData) + RETURN "Session restored successfully" + CATCH: + RETURN "Session expired or invalid" + ELSE: + RETURN "No stored session found" +``` +``` +# Encrypts session data for secure storage +FUNCTION encryptSessionData(sessionData: OBJECT, userPublicKey: PUBLIC_KEY): + encryptedData = ENCRYPT(JSON.stringify(sessionData), userPublicKey) + RETURN encryptedData +``` +``` +# Decrypts session data retrieved from storage +FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): + decryptedData = DECRYPT(encryptedData, userPrivateKey) + RETURN JSON.parse(decryptedData) +``` +``` +# Helper functions which hashes the SIWE message according to EIP-712 standards +FUNCTION HASH_STRUCTURED_DATA(message: SIWEMessage): + RETURN KECCAK256(ENCODED_STRUCTURED_DATA(message)) +``` +``` +# Helper functions which recovers the signer's address from a message hash and signature +FUNCTION RECOVER_SIGNER(messageHash: BYTES32, signature: BYTES): + RETURN ECRECOVER(messageHash, signature) +``` -Convert the public key to an Ethereum address: -address = public_key.to_checksum_address() +### Events -Return the address +``` +EVENT AuthenticationSuccessful(user: ADDRESS, purpose: STRING) +EVENT GroupMemberAdded(member: ADDRESS) +EVENT GroupMemberRemoved(member: ADDRESS) +EVENT AdminTransferred(oldAdmin: ADDRESS, newAdmin: ADDRESS) +EVENT SessionCreated(user: ADDRESS, sessionId: BYTES32, expirationBlock: INTEGER) +EVENT SessionExtended(user: ADDRESS, sessionId: BYTES32, newExpirationBlock: INTEGER) +EVENT SessionEnded(user: ADDRESS, sessionId: BYTES32) +EVENT SessionsCleanedUp(user: ADDRESS) +EVENT AuthenticationFailed(user: ADDRESS, reason: STRING) +``` ## Privacy and Security Considerations From 8c8aeca59f747df5839c20c4003e9fb557344e29 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 18 Oct 2024 11:44:05 +0200 Subject: [PATCH 04/26] Update eth-demls.md --- vac/raw/eth-demls.md | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 3053a430..83a53979 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1038,7 +1038,7 @@ allowing the dynamic management of group membership. ### SIWE Message Structure -``` +```text STRUCTURE SIWEMessage: domain: STRING # The domain requesting the signing address: ADDRESS # The user's Ethereum address @@ -1053,7 +1053,7 @@ STRUCTURE SIWEMessage: This contract handles authentication, group management, and session management -``` +```text CLASS EthereumStyleAuthWithTimeWindow: # State variables groupMembers: MAP # Stores group membership status @@ -1062,7 +1062,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessions: MAP> # Stores session expiration times sessionHashes: MAP> # Maps session IDs to IPFS hashes ``` -``` +```text # State variables for failed attempt tracking failedAttempts: MAP lastFailedAttemptTime: MAP @@ -1076,7 +1076,7 @@ CLASS EthereumStyleAuthWithTimeWindow: CONSTANT MAX_FAILED_ATTEMPTS = 5 CONSTANT LOCKOUT_DURATION = 15 minutes ``` -``` +```text # Constructor: Initializes the contract state CONSTRUCTOR(): groupMembers = EMPTY_MAP() @@ -1086,7 +1086,7 @@ CLASS EthereumStyleAuthWithTimeWindow: allowedPurposes["Authenticate to create session"] = TRUE EMIT GroupMemberAdded(admin) ``` -``` +```text # Adds a new member to the group (admin only) FUNCTION addGroupMember(memberAddress: ADDRESS): REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") @@ -1095,7 +1095,7 @@ CLASS EthereumStyleAuthWithTimeWindow: groupMembers[memberAddress] = TRUE EMIT GroupMemberAdded(memberAddress) ``` -``` +```text # Removes a member from the group (admin only) FUNCTION removeGroupMember(memberAddress: ADDRESS): REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") @@ -1105,7 +1105,7 @@ CLASS EthereumStyleAuthWithTimeWindow: groupMembers[memberAddress] = FALSE EMIT GroupMemberRemoved(memberAddress) ``` -``` +```text # Transfers admin rights to a new address FUNCTION transferAdmin(newAdminAddress: ADDRESS): REQUIRE(TRANSACTION_SENDER() == admin, "Only current admin can transfer admin rights") @@ -1114,7 +1114,7 @@ CLASS EthereumStyleAuthWithTimeWindow: EMIT AdminTransferred(admin, newAdminAddress) admin = newAdminAddress ``` -``` +```text # Authenticates a user based on their SIWE message and signature FUNCTION authenticate( domain: STRING, @@ -1190,14 +1190,14 @@ CLASS EthereumStyleAuthWithTimeWindow: EMIT AuthenticationSuccessful(signer, purpose) RETURN TRUE ``` -``` +```text # Helper function to record failed authentication attempts FUNCTION recordFailedAttempt(signer: ADDRESS, currentTime: TIMESTAMP, reason: STRING): failedAttempts[signer] = failedAttempts[signer] + 1 lastFailedAttemptTime[signer] = currentTime EMIT AuthenticationFailed(signer, reason) ``` -``` +```text # Helper function to verify the purpose FUNCTION verifyPurpose(purpose: STRING) RETURNS (BOOLEAN): # Implement logic to verify that the purpose is valid and expected @@ -1207,7 +1207,7 @@ CLASS EthereumStyleAuthWithTimeWindow: ### Session management functions -``` +```text # Creates a new session for an authenticated user FUNCTION createSession(sessionId: BYTES32, expirationBlock: INTEGER, ipfsHash: STRING): REQUIRE(groupMembers[TRANSACTION_SENDER()] == TRUE, "Not a group member") @@ -1218,7 +1218,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessionHashes[TRANSACTION_SENDER()][sessionId] = ipfsHash EMIT SessionCreated(TRANSACTION_SENDER(), sessionId, expirationBlock) ``` -``` +```text # Retrieves the IPFS hash for a given session ID FUNCTION getSessionHash(sessionId: BYTES32) VIEW RETURNS (STRING): expirationBlock = sessions[TRANSACTION_SENDER()][sessionId] @@ -1227,7 +1227,7 @@ CLASS EthereumStyleAuthWithTimeWindow: RETURN sessionHashes[TRANSACTION_SENDER()][sessionId] ``` -``` +```text # Extends the expiration time of an existing session FUNCTION extendSession(sessionId: BYTES32, newExpirationBlock: INTEGER): REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") @@ -1235,7 +1235,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessions[TRANSACTION_SENDER()][sessionId] = newExpirationBlock EMIT SessionExtended(TRANSACTION_SENDER(), sessionId, newExpirationBlock) ``` -``` +```text # Ends a session, removing it from storage FUNCTION endSession(sessionId: BYTES32): REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") @@ -1243,7 +1243,7 @@ CLASS EthereumStyleAuthWithTimeWindow: DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] EMIT SessionEnded(TRANSACTION_SENDER(), sessionId) ``` -``` +```text # Cleans up expired sessions for the calling user FUNCTION cleanUpExpiredSessions(): FOR EACH sessionId IN sessions[TRANSACTION_SENDER()]: @@ -1256,7 +1256,7 @@ CLASS EthereumStyleAuthWithTimeWindow: ### Client side functions -``` +```text # Generates a new SIWE message for authentication FUNCTION generateSIWEMessage(userAddress: ADDRESS): domain = "example.com" @@ -1268,14 +1268,14 @@ FUNCTION generateSIWEMessage(userAddress: ADDRESS): RETURN NEW SIWEMessage( domain, userAddress, uri, chainId, issuedAt, expirationTime, purpose) ``` -``` +```text # Signs a SIWE message with the user's private key FUNCTION signSIWEMessage(message: SIWEMessage, userAddress: ADDRESS): messageHash = HASH_STRUCTURED_DATA(message) signature = SIGN_MESSAGE_HASH(messageHash, userAddress) RETURN signature ``` -``` +```text # Main authentication function that calls the smart contract FUNCTION authenticate(): userAddress = GET_CURRENT_USER_ADDRESS() @@ -1299,13 +1299,13 @@ FUNCTION authenticate(): ELSE: RETURN "Authentication failed" ``` -``` +```text # Constants or configuration parameters AVERAGE_BLOCK_TIME = 12 # in seconds SESSION_DURATION_IN_SECONDS = 3600 # 1 hour SESSION_DURATION_IN_BLOCKS = ROUND(SESSION_DURATION_IN_SECONDS / AVERAGE_BLOCK_TIME) # 300 blocks ``` -``` +```text # Creates and stores a new session after successful authentication FUNCTION createAndStoreSession(userAddress: ADDRESS): sessionData = { @@ -1325,7 +1325,7 @@ FUNCTION createAndStoreSession(userAddress: ADDRESS): STORE_LOCALLY(sessionId) RETURN sessionId ``` -``` +```text # Restores a user's session using the stored session ID FUNCTION restoreUserSession(): storedSessionId = RETRIEVE_LOCALLY_STORED_SESSION_ID() @@ -1341,24 +1341,24 @@ FUNCTION restoreUserSession(): ELSE: RETURN "No stored session found" ``` -``` +```text # Encrypts session data for secure storage FUNCTION encryptSessionData(sessionData: OBJECT, userPublicKey: PUBLIC_KEY): encryptedData = ENCRYPT(JSON.stringify(sessionData), userPublicKey) RETURN encryptedData ``` -``` +```text # Decrypts session data retrieved from storage FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): decryptedData = DECRYPT(encryptedData, userPrivateKey) RETURN JSON.parse(decryptedData) ``` -``` +```text # Helper functions which hashes the SIWE message according to EIP-712 standards FUNCTION HASH_STRUCTURED_DATA(message: SIWEMessage): RETURN KECCAK256(ENCODED_STRUCTURED_DATA(message)) ``` -``` +```text # Helper functions which recovers the signer's address from a message hash and signature FUNCTION RECOVER_SIGNER(messageHash: BYTES32, signature: BYTES): RETURN ECRECOVER(messageHash, signature) @@ -1366,7 +1366,7 @@ FUNCTION RECOVER_SIGNER(messageHash: BYTES32, signature: BYTES): ### Events -``` +```text EVENT AuthenticationSuccessful(user: ADDRESS, purpose: STRING) EVENT GroupMemberAdded(member: ADDRESS) EVENT GroupMemberRemoved(member: ADDRESS) From 1b9eaaf6f31ee29509a469d3d441bdc0b149bd7f Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 18 Oct 2024 11:46:16 +0200 Subject: [PATCH 05/26] Update eth-demls.md --- vac/raw/eth-demls.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 83a53979..4826e130 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1062,6 +1062,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessions: MAP> # Stores session expiration times sessionHashes: MAP> # Maps session IDs to IPFS hashes ``` + ```text # State variables for failed attempt tracking failedAttempts: MAP @@ -1076,6 +1077,7 @@ CLASS EthereumStyleAuthWithTimeWindow: CONSTANT MAX_FAILED_ATTEMPTS = 5 CONSTANT LOCKOUT_DURATION = 15 minutes ``` + ```text # Constructor: Initializes the contract state CONSTRUCTOR(): @@ -1086,6 +1088,7 @@ CLASS EthereumStyleAuthWithTimeWindow: allowedPurposes["Authenticate to create session"] = TRUE EMIT GroupMemberAdded(admin) ``` + ```text # Adds a new member to the group (admin only) FUNCTION addGroupMember(memberAddress: ADDRESS): @@ -1095,6 +1098,7 @@ CLASS EthereumStyleAuthWithTimeWindow: groupMembers[memberAddress] = TRUE EMIT GroupMemberAdded(memberAddress) ``` + ```text # Removes a member from the group (admin only) FUNCTION removeGroupMember(memberAddress: ADDRESS): @@ -1105,6 +1109,7 @@ CLASS EthereumStyleAuthWithTimeWindow: groupMembers[memberAddress] = FALSE EMIT GroupMemberRemoved(memberAddress) ``` + ```text # Transfers admin rights to a new address FUNCTION transferAdmin(newAdminAddress: ADDRESS): @@ -1114,6 +1119,7 @@ CLASS EthereumStyleAuthWithTimeWindow: EMIT AdminTransferred(admin, newAdminAddress) admin = newAdminAddress ``` + ```text # Authenticates a user based on their SIWE message and signature FUNCTION authenticate( @@ -1190,6 +1196,7 @@ CLASS EthereumStyleAuthWithTimeWindow: EMIT AuthenticationSuccessful(signer, purpose) RETURN TRUE ``` + ```text # Helper function to record failed authentication attempts FUNCTION recordFailedAttempt(signer: ADDRESS, currentTime: TIMESTAMP, reason: STRING): @@ -1197,6 +1204,7 @@ CLASS EthereumStyleAuthWithTimeWindow: lastFailedAttemptTime[signer] = currentTime EMIT AuthenticationFailed(signer, reason) ``` + ```text # Helper function to verify the purpose FUNCTION verifyPurpose(purpose: STRING) RETURNS (BOOLEAN): @@ -1218,6 +1226,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessionHashes[TRANSACTION_SENDER()][sessionId] = ipfsHash EMIT SessionCreated(TRANSACTION_SENDER(), sessionId, expirationBlock) ``` + ```text # Retrieves the IPFS hash for a given session ID FUNCTION getSessionHash(sessionId: BYTES32) VIEW RETURNS (STRING): @@ -1227,6 +1236,7 @@ CLASS EthereumStyleAuthWithTimeWindow: RETURN sessionHashes[TRANSACTION_SENDER()][sessionId] ``` + ```text # Extends the expiration time of an existing session FUNCTION extendSession(sessionId: BYTES32, newExpirationBlock: INTEGER): @@ -1235,6 +1245,7 @@ CLASS EthereumStyleAuthWithTimeWindow: sessions[TRANSACTION_SENDER()][sessionId] = newExpirationBlock EMIT SessionExtended(TRANSACTION_SENDER(), sessionId, newExpirationBlock) ``` + ```text # Ends a session, removing it from storage FUNCTION endSession(sessionId: BYTES32): @@ -1243,6 +1254,7 @@ CLASS EthereumStyleAuthWithTimeWindow: DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] EMIT SessionEnded(TRANSACTION_SENDER(), sessionId) ``` + ```text # Cleans up expired sessions for the calling user FUNCTION cleanUpExpiredSessions(): @@ -1268,6 +1280,7 @@ FUNCTION generateSIWEMessage(userAddress: ADDRESS): RETURN NEW SIWEMessage( domain, userAddress, uri, chainId, issuedAt, expirationTime, purpose) ``` + ```text # Signs a SIWE message with the user's private key FUNCTION signSIWEMessage(message: SIWEMessage, userAddress: ADDRESS): @@ -1275,6 +1288,7 @@ FUNCTION signSIWEMessage(message: SIWEMessage, userAddress: ADDRESS): signature = SIGN_MESSAGE_HASH(messageHash, userAddress) RETURN signature ``` + ```text # Main authentication function that calls the smart contract FUNCTION authenticate(): @@ -1299,12 +1313,14 @@ FUNCTION authenticate(): ELSE: RETURN "Authentication failed" ``` + ```text # Constants or configuration parameters AVERAGE_BLOCK_TIME = 12 # in seconds SESSION_DURATION_IN_SECONDS = 3600 # 1 hour SESSION_DURATION_IN_BLOCKS = ROUND(SESSION_DURATION_IN_SECONDS / AVERAGE_BLOCK_TIME) # 300 blocks ``` + ```text # Creates and stores a new session after successful authentication FUNCTION createAndStoreSession(userAddress: ADDRESS): @@ -1325,6 +1341,7 @@ FUNCTION createAndStoreSession(userAddress: ADDRESS): STORE_LOCALLY(sessionId) RETURN sessionId ``` + ```text # Restores a user's session using the stored session ID FUNCTION restoreUserSession(): @@ -1341,23 +1358,27 @@ FUNCTION restoreUserSession(): ELSE: RETURN "No stored session found" ``` + ```text # Encrypts session data for secure storage FUNCTION encryptSessionData(sessionData: OBJECT, userPublicKey: PUBLIC_KEY): encryptedData = ENCRYPT(JSON.stringify(sessionData), userPublicKey) RETURN encryptedData ``` + ```text # Decrypts session data retrieved from storage FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): decryptedData = DECRYPT(encryptedData, userPrivateKey) RETURN JSON.parse(decryptedData) ``` + ```text # Helper functions which hashes the SIWE message according to EIP-712 standards FUNCTION HASH_STRUCTURED_DATA(message: SIWEMessage): RETURN KECCAK256(ENCODED_STRUCTURED_DATA(message)) ``` + ```text # Helper functions which recovers the signer's address from a message hash and signature FUNCTION RECOVER_SIGNER(messageHash: BYTES32, signature: BYTES): From 4ea16ab1663632146b3dd3276d4a4894d1e34c15 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 18 Oct 2024 11:47:44 +0200 Subject: [PATCH 06/26] Update eth-demls.md --- vac/raw/eth-demls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 4826e130..819bd05a 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1266,7 +1266,7 @@ CLASS EthereumStyleAuthWithTimeWindow: EMIT SessionsCleanedUp(TRANSACTION_SENDER()) ``` -### Client side functions +### Client side functions ```text # Generates a new SIWE message for authentication From b5c4fad55408cd6100ae4affd10498428bd23338 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Mon, 21 Oct 2024 07:48:28 +0200 Subject: [PATCH 07/26] Update eth-demls.md --- vac/raw/eth-demls.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 819bd05a..0e5e49a9 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1206,11 +1206,11 @@ CLASS EthereumStyleAuthWithTimeWindow: ``` ```text - # Helper function to verify the purpose FUNCTION verifyPurpose(purpose: STRING) RETURNS (BOOLEAN): - # Implement logic to verify that the purpose is valid and expected - # For example, check that the purpose matches a known action - RETURN purpose == "Authenticate to create session" + # Define the expected purpose + expectedPurpose = "Authenticate to create session" + # Check if the provided purpose matches the expected purpose + RETURN purpose == expectedPurpose ``` ### Session management functions From de9e6f2a0187ea5112945c449b4d4b8c23f7707f Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Mon, 21 Oct 2024 17:33:39 +0200 Subject: [PATCH 08/26] Update eth-demls.md --- vac/raw/eth-demls.md | 51 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 0e5e49a9..945eab58 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1361,16 +1361,55 @@ FUNCTION restoreUserSession(): ```text # Encrypts session data for secure storage -FUNCTION encryptSessionData(sessionData: OBJECT, userPublicKey: PUBLIC_KEY): - encryptedData = ENCRYPT(JSON.stringify(sessionData), userPublicKey) - RETURN encryptedData + FUNCTION encryptSessionData(sessionData: OBJECT, userPrivateKey: PRIVATE_KEY): + # Derive a symmetric key from the user's private key or generate a new one + symmetricKey = DERIVE_SYMMETRIC_KEY(userPrivateKey) + + # Serialize the session data to a JSON string + serializedData = JSON.stringify(sessionData) + + # Encrypt the serialized data using the symmetric key + encryptedData = SYMMETRIC_ENCRYPT(serializedData, symmetricKey) + RETURN encryptedData ``` ```text # Decrypts session data retrieved from storage -FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): - decryptedData = DECRYPT(encryptedData, userPrivateKey) - RETURN JSON.parse(decryptedData) + FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): + # Derive the symmetric key using the user's private key + symmetricKey = DERIVE_SYMMETRIC_KEY(userPrivateKey) + + # Decrypt the data using the symmetric key + decryptedData = SYMMETRIC_DECRYPT(encryptedData, symmetricKey) + + # Deserialize the JSON string back into an object + sessionData = JSON.parse(decryptedData) + RETURN sessionData +``` + +```text +# Derives a symmetric encryption key from the user's private key + FUNCTION DERIVE_SYMMETRIC_KEY(privateKey: PRIVATE_KEY) RETURNS (SYMMETRIC_KEY): + # Use a key derivation function (KDF) with a salt to derive a symmetric key + salt = FIXED_SALT_OR_USER_SPECIFIC_SALT + symmetricKey = KDF(privateKey, salt) + RETURN symmetricKey +``` + +```text +# Symmetric encryption using AES or a similar algorithm + FUNCTION SYMMETRIC_ENCRYPT(data: STRING, key: SYMMETRIC_KEY) RETURNS (STRING): + # Encrypt the data using the symmetric key + encryptedData = AES_ENCRYPT(data, key) + RETURN encryptedData +``` + +```text +# Symmetric decryption + FUNCTION SYMMETRIC_DECRYPT(encryptedData: STRING, key: SYMMETRIC_KEY) RETURNS (STRING): + # Decrypt the data using the symmetric key + decryptedData = AES_DECRYPT(encryptedData, key) + RETURN decryptedData ``` ```text From 110280142a6418600a3a1cbd4f44aabd1e8bacd1 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 29 Oct 2024 16:43:20 +0100 Subject: [PATCH 09/26] Update eth-secpm.md --- vac/raw/eth-secpm.md | 1945 ++++++++++++++++-------------------------- 1 file changed, 735 insertions(+), 1210 deletions(-) diff --git a/vac/raw/eth-secpm.md b/vac/raw/eth-secpm.md index 5c3a7adb..a5b2af88 100644 --- a/vac/raw/eth-secpm.md +++ b/vac/raw/eth-secpm.md @@ -1,1364 +1,889 @@ --- -title: ETH-SECPM -name: Secure channel setup using Ethereum accounts +title: VAC-DECENTRALIZED-MESSAGING-ETHEREUM +name: Decentralized Key and Session Setup for Secure Messaging over Ethereum status: raw -category: Standards Track -tags: -editor: Ramses Fernandez +category: informational +editor: Ramses Fernandez-Valencia contributors: --- -## Motivation +## Abstract -The need for secure communications has become paramount. -Traditional centralized messaging protocols are susceptible to various security threats, -including unauthorized access, data breaches, and single points of failure. -Therefore a decentralized approach to secure communication becomes increasingly relevant, -offering a robust solution to address these challenges. - -This specification outlines a private messaging service using the -Ethereum blockchain as authentication service. -Rooted in the existing [model](../../waku/standards/application/20/toy-eth-pm.md), -this proposal addresses the deficiencies related -to forward privacy and authentication inherent -in the current framework. -The specification is divided into 3 sections: - -- Private 1-to-1 communications protocol, based on [Signal's double -ratchet](https://signal.org/docs/specifications/doubleratchet/). -- Private group messaging protocol, based on the -[MLS protocol](https://datatracker.ietf.org/doc/rfc9420/). -- Description of an Ethereum-based authentication protocol, based on -[SIWE](https://eips.ethereum.org/EIPS/eip-4361). - -## Private 1-to-1 communications protocol - -### Theory - -The specification is based on the noise protocol framework. -It corresponds to the double ratchet scheme combined with -the X3DH algorithm, which will be used to initialize the former. -We chose to express the protocol in noise to be be able to use -the noise streamlined implementation and proving features. -The X3DH algorithm provides both authentication and forward -secrecy, as stated in the -[X3DH specification](https://signal.org/docs/specifications/x3dh/). - -This protocol will consist of several stages: - -1. Key setting for X3DH: this step will produce -prekey bundles for Bob which will be fed into X3DH. -It will also allow Alice to generate the keys required -to run the X3DH algorithm correctly. -2. Execution of X3DH: This step will output -a common secret key `SK` together with an additional -data vector `AD`. Both will be used in the double -ratchet algorithm initialization. -3. Execution of the double ratchet algorithm -for forward secure, authenticated communications, -using the common secret key `SK`, obtained from X3DH, as a root key. - -The protocol assumes the following requirements: - -- Alice knows Bob’s Ethereum address. -- Bob is willing to participate in the protocol, -and publishes his public key. -- Bob’s ownership of his public key is verifiable, -- Alice wants to send message M to Bob. -- An eavesdropper cannot read M’s content -even if she is storing it or relaying it. - -> The inclusion of this first section devoted to secure 1-to-1 -communications between users is motivated by the fact certain -interactions between existing group members and prospective new -members require secure communication channels. - -### Syntax - -#### Cryptographic suite - -The following cryptographic functions MUST be used: - -- `X488` as Diffie-Hellman function `DH`. -- `SHA256` as KDF. -- `AES256-GCM` as AEAD algorithm. -- `SHA512` as hash function. -- `XEd448` for digital signatures. - -#### X3DH initialization - -This scheme MUST work on the curve curve448. -The X3DH algorithm corresponds to the IX pattern in Noise. - -Bob and Alice MUST define personal key pairs -`(ik_B, IK_B)` and `(ik_A, IK_A)` respectively where: - -- The key `ik` must be kept secret, -- and the key `IK` is public. - -Bob MUST generate new keys using -`(ik_B, IK_B) = GENERATE_KEYPAIR(curve = curve448)`. - -Bob MUST also generate a public key pair -`(spk_B, SPK_B) = GENERATE_KEYPAIR(curve = curve448)`. - -`SPK` is a public key generated and stored at medium-term. -Both signed prekey and the certificate MUST -undergo periodic replacement. -After replacing the key, -Bob keeps the old private key of `SPK` -for some interval, dependant on the implementation. -This allows Bob to decrypt delayed messages. - -Bob MUST sign `SPK` for authentication: -`SigSPK = XEd448(ik, Encode(SPK))` - -A final step requires the definition of -`prekey_bundle = (IK, SPK, SigSPK, OPK_i)` +This document introduces a decentralized group messaging protocol +using Ethereum adresses as identifiers. +It is based in the proposal +[DCGKA](https://eprint.iacr.org/2020/1281) by Weidner et al. +It includes also approximations to overcome limitations related to using PKI and +the multi-device setting. -One-time keys `OPK` MUST be generated as -`(opk_B, OPK_B) = GENERATE_KEYPAIR(curve = curve448)`. +## Motivation -Before sending an initial message to Bob, -Alice MUST generate an AD: `AD = Encode(IK_A) || Encode(IK_B)`. +The need for secure communications has become paramount. +Traditional centralized messaging protocols are susceptible to various security +threats, including unauthorized access, data breaches, and single points of +failure. +Therefore a decentralized approach to secure communication becomes increasingly +relevant, offering a robust solution to address these challenges. + +Secure messaging protocols used should have the following key features: + +1. **Asynchronous Messaging:** Users can send messages even if the recipients +are not online at the moment. + +2. **Resilience to Compromise:** If a user's security is compromised, +the protocol ensures that previous messages remain secure through forward +secrecy (FS). This means that messages sent before the compromise cannot be +decrypted by adversaries. Additionally, the protocol maintains post-compromise +security (PCS) by regularly updating keys, making it difficult for adversaries +to decrypt future communication. + +3. **Dynamic Group Management:** Users can easily add or remove group members +at any time, reflecting the flexible nature of communication within the app. + +In this field, there exists a *trilemma*, similar to what one observes in +blockchain, involving three key aspects: + +1. security, +2. scalability, and +3. decentralization. + +For instance, protocols like the [MLS](https://messaginglayersecurity.rocks) +perform well in terms of scalability and security. +However, they falls short in decentralization. + +Newer studies such as [CoCoa](https://eprint.iacr.org/2022/251) +improve features related to security and scalability, +but they still rely on servers, which may not be fully trusted though they are necessary. + +On the other hand, +older studies like [Causal TreeKEM](https://mattweidner.com/assets/pdf/acs-dissertation.pdf) +exhibit decent scalability (logarithmic) +but lack forward secrecy and have weak post-compromise security (PCS). + +The creators of [DCGKA](https://eprint.iacr.org/2020/1281) introduce a decentralized, +asynchronous secure group messaging protocol that supports dynamic groups. +This protocol operates effectively on various underlying networks +without strict requirements on message ordering or latency. +It can be implemented in peer-to-peer or anonymity networks, +accommodating network partitions, high latency links, and +disconnected operation seamlessly. +Notably, the protocol doesn't rely on servers or +a consensus protocol for its functionality. + +This proposal provides end-to-end encryption with forward secrecy and +post-compromise security, +even when multiple users concurrently modify the group state. + +## Theory + +### Protocol overview + +This protocol makes use of ratchets to provide FS +by encrypting each message with a different key. + +In the figure one can see the ratchet for encrypting a sequence of messages. +The sender requires an initial update secret `I_1`, which is introduced in a PRG. +The PRG will produce two outputs, namely a symmetric key for AEAD encryption, and +a seed for the next ratchet state. +The associated data needed in the AEAD encryption includes the message index `i`. +The ciphertext `c_i` associated to message `m_i` +is then broadcasted to all group members. +The next step requires deleting `I_1`, `k_i` and any old ratchet state. + +After a period of time the sender may replace the ratchet state with new update secrets +`I_2`, `I_3`, and so on. + +To start a post-compromise security update, +a user creates a new random value known as a seed secret and +shares it with every other group member through a secure two-party channel. +Upon receiving the seed secret, +each group member uses it to calculate an update secret for both the sender's ratchet +and their own. +Additionally, the recipient sends an unencrypted acknowledgment to the group +confirming the update. +Every member who receives the acknowledgment updates +not only the ratchet for the original sender but +also the ratchet for the sender of the acknowledgment. +Consequently, after sharing the seed secret through `n - 1` two-party messages and +confirming it with `n - 1` broadcast acknowledgments, +every group member has derived an update secret and updated their ratchet accordingly. + +When removing a group member, +the user who initiates the removal conducts a post-compromise security update +by sending the update secret to all group members except the one being removed. +To add a new group member, +each existing group member shares the necessary state with the new user, +enabling them to derive their future update secrets. + +Since group members may receive messages in various orders, +it's important to ensure that each sender's ratchet is updated consistently +with the same sequence of update secrets at each group member. + +The network protocol used in this scheme ensures that messages from the same sender +are processed in the order they were sent. + +### Components of the protocol + +This protocol relies in 3 components: +authenticated causal broadcast (ACB), +decentralized group membership (DGM) and +2-party secure messaging (2SM). + +#### Authenticated causal broadcast + +A causal order is a partial order relation `<` on messages. +Two messages `m_1` and `m_2` are causally ordered, or +`m_1` causally precedes `m_2` +(denoted by `m_1 < m_2`), if one of the following contiditions hold: + +1. `m_1` and `m_2` were sent by the same group member, and +`m_1` was sent before `m_2`. +2. `m_2` was sent by a group member U, and `m_1` was received and +processed by `U` before sending `m_2`. +3. There exists `m_3` such that `m_1 < m_3` and `m_3 < m_2`. + +Causal broadcast requires that before processing `m`, a group member must +process all preceding messages `{m' | m' < m}`. + +The causal broadcast module used in this protocol authenticates the sender of +each message, as well as its causal ordering metadata, using a digital +signature under the sender’s identity key. +This prevents a passive adversary from impersonating users or affecting +causally ordered delivery. + +#### Decentralized group membership + +This protocol assumes the existence of a decentralized group membership +function (denoted as DGM) that takes a set of membership change messages and +their causal order relantionships, and returns the current set of group +members’ IDs. It needs to be deterministic and depend only on causal order, and +not exact order. + +#### 2-party secure messaging (2SM) + +This protocol makes use of bidirectional 2-party secure messaging schemes, +which consist of 3 algorithms: `2SM-Init`, `2SM-Send` and `2SM-Receive`. + +##### Function 2SM-Init -Alice MUST generate ephemeral key pairs -`(ek, EK) = GENERATE_KEYPAIR(curve = curve448)`. +This function takes two IDs as inputs: +`ID1` representing the local user and `ID2` representing the other party. +It returns an initial protocol state `sigma`. +The 2SM protocol relies on a Public Key Infrastructure (PKI) or +a key server to map these IDs to their corresponding public keys. +In practice, the PKI should incorporate ephemeral prekeys. +This allows users to send messages to a new group member, +even if that member is currently offline. -The function `Encode()` transforms a -curve448 public key into a byte sequence. -This is specified in the [RFC 7748](http://www.ietf.org/rfc/rfc7748.txt) -on elliptic curves for security. +##### Function 2SM-Send + +This function takes a state `sigma` and a plaintext `m` as inputs, and returns +a new state `sigma’` and a ciphertext `c`. + +##### Function 2SM-Receive + +This function takes a state `sigma` and a ciphertext `c`, and +returns a new state `sigma’` and a plaintext `m`. + +This function takes a state `sigma` and a ciphertext `c`, and returns a new +state `sigma’` and a plaintext `m`. -One MUST consider `q = 2^446 - 13818066809895115352007386748515426880336692474882178609894547503885` -for digital signatures with `(XEd448_sign, XEd448_verify)`: +#### Function 2SM Syntax -```text -XEd448_sign((ik, IK), message): - Z = randbytes(64) - r = SHA512(2^456 - 2 || ik || message || Z ) - R = (r * convert_mont(5)) % q - h = SHA512(R || IK || M) - s = (r + h * ik) % q - return (R || s) -``` +The variable `sigma` denotes the state consisting in the variables below: ```text -XEd448_verify(u, message, (R || s)): - if (R.y >= 2^448) or (s >= 2^446): return FALSE - h = (SHA512(R || 156326 || message)) % q - R_check = s * convert_mont(5) - h * 156326 - if R == R_check: return TRUE - return FALSE -``` +sigma.mySks[0] = sk +sigma.nextIndex = 1 +sigma.receivedSk = empty_string +sigma.otherPk = pk`
+sigma.otherPksender = “other” +sigma.otherPkIndex = 0 -```text -convert_mont(u): - u_masked = u % mod 2^448 - inv = ((1 - u_masked)^(2^448 - 2^224 - 3)) % (2^448 - 2^224 - 1) - P.y = ((1 + u_masked) * inv)) % (2^448 - 2^224 - 1) - P.s = 0 - return P ``` -#### Use of X3DH +#### 2SM-Init -This specification combines the double ratchet -with X3DH using the following data as initialization for the former: +On input a key pair `(sk, pk)`, this functions otuputs a state `sigma`. -- The `SK` output from X3DH becomes the `SK` -input of the double ratchet. See section 3.3 of -[Signal Specification](https://signal.org/docs/specifications/doubleratchet/) -for a detailed description. -- The `AD` output from X3DH becomes the `AD` -input of the double ratchet. See sections 3.4 and 3.5 of -[Signal Specification](https://signal.org/docs/specifications/doubleratchet/) -for a detailed description. -- Bob’s signed prekey `SigSPKB` from X3DH is used as Bob’s -initial ratchet public key of the double ratchet. +#### 2SM-Send -X3DH has three phases: +This function encrypts the message `m` using `sigma.otherPk`, which represents +the other party’s current public key. +This key is determined based on the last public key generated for the other +party or the last public key received from the other party, +whichever is more recent. `sigma.otherPkSender` is set to `me` in the former +case and `other` in the latter case. -1. Bob publishes his identity key and prekeys to a server, -a network, or dedicated smart contract. -2. Alice fetches a prekey bundle from the server, -and uses it to send an initial message to Bob. -3. Bob receives and processes Alice's initial message. +Metadata including `otherPkSender` and `otherPkIndex` are included in the +message to indicate which of the recipient’s public keys is being utilized. -Alice MUST perform the following computations: +Additionally, this function generates a new key pair for the local user, +storing the secret key in `sigma.mySks` and sending the public key. +Similarly, it generates a new key pair for the other party, +sending the secret key (encrypted) and storing the public key in +`sigma.otherPk`. ```text -dh1 = DH(IK_A, SPK_B, curve = curve448) -dh2 = DH(EK_A, IK_B, curve = curve448) -dh3 = DH(EK_A, SPK_B) -SK = KDF(dh1 || dh2 || dh3) -``` - -Alice MUST send to Bob a message containing: - -- `IK_A, EK_A`. -- An identifier to Bob's prekeys used. -- A message encrypted with AES256-GCM using `AD` and `SK`. - -Upon reception of the initial message, Bob MUST: - -1. Perform the same computations above with the `DH()` function. -2. Derive `SK` and construct `AD`. -3. Decrypt the initial message encrypted with `AES256-GCM`. -4. If decryption fails, abort the protocol. - -#### Initialization of the double datchet - -In this stage Bob and Alice have generated key pairs -and agreed a shared secret `SK` using X3DH. - -Alice calls `RatchetInitAlice()` defined below: +sigma.mySks[sigma.nextIndex], myNewPk) = PKE-Gen() +(otherNewSk, otherNewPk) = PKE-Gen() +plaintext = (m, otherNewSk, sigma`.nextIndex, myNewPk) +msg = (PKE-Enc(sigma.otherPk, plaintext), sigma.otherPkSender, sigma.otherPkIndex) +sigma.nextIndex++ +(sigma.otherPk, sigma.otherPkSender, sigma.otherPkIndex) = (otherNewPk, "me", empty_string) +return (sigma`, msg) -```text -RatchetInitAlice(SK, IK_B): - state.DHs = GENERATE_KEYPAIR(curve = curve448) - state.DHr = IK_B - state.RK, state.CKs = HKDF(SK, DH(state.DHs, state.DHr)) - state.CKr = None - state.Ns, state.Nr, state.PN = 0 - state.MKSKIPPED = {} ``` -The HKDF function MUST be the proposal by -[Krawczyk and Eronen](http://www.ietf.org/rfc/rfc5869.txt). -In this proposal `chaining_key` and `input_key_material` -MUST be replaced with `SK` and the output of `DH` respectively. +#### 2SM-Receive -Similarly, Bob calls the function `RatchetInitBob()` defined below: +This function utilizes the metadata of the message `c` to determine which +secret key to utilize for decryption, assigning it to `sk`. +If the secret key corresponds to one generated by ourselves, +that secret key along with all keys with lower index are deleted. +This deletion is indicated by `sigma.mySks[≤ keyIndex] = empty_string`. +Subsequently, the new public and secret keys contained in the message are +stored. ```text -RatchetInitBob(SK, (ik_B,IK_B)): - state.DHs = (ik_B, IK_B) - state.Dhr = None - state.RK = SK - state.CKs, state.CKr = None - state.Ns, state.Nr, state.PN = 0 - state.MKSKIPPED = {} -``` - -#### Encryption - -This function performs the symmetric key ratchet. +(ciphertext, keySender, keyIndex) = c +if keySender = "other" then +sk = sigma.mySks[keyIndex] +sigma.mySks[≤ keyIndex] = empty_string +else sk = sigma.receivedSk +(m, sigma.receivedSk, sigma.otherPkIndex, sigma.otherPk) = PKE-Dec(sk, ciphertext) +sigma.otherPkSender = "other" +return (sigma, m) -```text -RatchetEncrypt(state, plaintext, AD): - state.CKs, mk = HMAC-SHA256(state.CKs) - header = HEADER(state.DHs, state.PN, state.Ns) - state.Ns = state.Ns + 1 - return header, AES256-GCM_Enc(mk, plaintext, AD || header) ``` -The `HEADER` function creates a new message header -containing the public key from the key pair output of the `DH`function. -It outputs the previous chain length `pn`, -and the message number `n`. -The returned header object contains ratchet public key -`dh` and integers `pn` and `n`. - -#### Decryption - -The function `RatchetDecrypt()` decrypts incoming messages: +### PKE Syntax -```text -RatchetDecrypt(state, header, ciphertext, AD): - plaintext = TrySkippedMessageKeys(state, header, ciphertext, AD) - if plaintext != None: - return plaintext - if header.dh != state.DHr: - SkipMessageKeys(state, header.pn) - DHRatchet(state, header) - SkipMessageKeys(state, header.n) - state.CKr, mk = HMAC-SHA256(state.CKr) - state.Nr = state.Nr + 1 - return AES256-GCM_Dec(mk, ciphertext, AD || header) -``` +The required PKE that MUST be used is ElGamal with a 2048-bit modulus `p`. -Auxiliary functions follow: +#### Parameters -```text -DHRatchet(state, header): - state.PN = state.Ns - state.Ns = state.Nr = 0 - state.DHr = header.dh - state.RK, state.CKr = HKDF(state.RK, DH(state.DHs, state.DHr)) - state.DHs = GENERATE_KEYPAIR(curve = curve448) - state.RK, state.CKs = HKDF(state.RK, DH(state.DHs, state.DHr)) -``` +The following parameters must be used: ```text -SkipMessageKeys(state, until): - if state.NR + MAX_SKIP < until: - raise Error - if state.CKr != none: - while state.Nr < until: - state.CKr, mk = HMAC-SHA256(state.CKr) - state.MKSKIPPED[state.DHr, state.Nr] = mk - state.Nr = state.Nr + 1 -``` +p = 308920927247127345254346920820166145569 +g = 2 -```text -TrySkippedMessageKey(state, header, ciphertext, AD): - if (header.dh, header.n) in state.MKSKIPPED: - mk = state.MKSKIPPED[header.dh, header.n] - delete state.MKSKIPPED[header.dh, header.n] - return AES256-GCM_Dec(mk, ciphertext, AD || header) - else: return None ``` -## Information retrieval - -### Static data - -Some data, such as the key pairs `(ik, IK)` for Alice and Bob, -MAY NOT be regenerated after a period of time. -Therefore the prekey bundle MAY be stored in long-term -storage solutions, such as a dedicated smart contract -which outputs such a key pair when receiving an Ethereum wallet -address. - -Storing static data is done using a dedicated -smart contract `PublicKeyStorage` which associates -the Ethereum wallet address of a user with his public key. -This mapping is done by `PublicKeyStorage` -using a `publicKeys` function, or a `setPublicKey` function. -This mapping is done if the user passed an authorization process. -A user who wants to retrieve a public key associated -with a specific wallet address calls a function `getPublicKey`. -The user provides the wallet address as the only -input parameter for `getPublicKey`. -The function outputs the associated public key -from the smart contract. - -### Ephemeral data - -Storing ephemeral data on Ethereum MAY be done using -a combination of on-chain and off-chain solutions. -This approach provides an efficient solution to -the problem of storing updatable data in Ethereum. - -1. Ethereum stores a reference or a hash -that points to the off-chain data. -2. Off-chain solutions can include systems like IPFS, -traditional cloud storage solutions, or -decentralized storage networks such as a -[Swarm](https://www.ethswarm.org). - -In any case, the user stores the associated -IPFS hash, URL or reference in Ethereum. - -The fact of a user not updating the ephemeral information -can be understood as Bob not willing to participate in any -communication. - -This applies to `KeyPackage`, -which in the MLS specification are meant -o be stored in a directory provided by the delivery service. -If such an element does not exist, -`KeyPackage` MUST be stored according -to one of the two options outlined above. - -## Private group messaging protocol - -### Theoretical content +#### PKE-KGen -The [Messaging Layer Security](https://datatracker.ietf.org/doc/rfc9420/)(MLS) -protocol aims at providing a group of users with -end-to-end encryption in an authenticated and asynchronous way. -The main security characteristics of the protocol are: -Message confidentiality and authentication, sender authentication, -membership agreement, post-remove -and post-update security, and forward secrecy and -post-compromise security. -The MLS protocol achieves: low-complexity, group integrity, -synchronization and extensibility. - -The extension to group chat described in forthcoming sections is built upon the -[MLS](https://datatracker.ietf.org/doc/rfc9420/) protocol. +Each user `u` MUST do the following: -### Structure - -Each MLS session uses a single cipher suite that specifies the -primitives to be used in group key computations. The cipher suite MUST -use: - -- `X488` as Diffie-Hellman function. -- `SHA256` as KDF. -- `AES256-GCM` as AEAD algorithm. -- `SHA512` as hash function. -- `XEd448` for digital signatures. - -Formats for public keys, signatures and public-key encryption MUST -follow Section 5.1 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Hash-based identifiers - -Some MLS messages refer to other MLS objects by hash. -These identifiers MUST be computed according to Section 5.2 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Credentials - -Each member of a group presents a credential that provides one or more -identities for the -member and associates them with the member's signing key. -The identities and signing key are verified by the Authentication -Service in use for a -group. - -Credentials MUST follow the specifications of section 5.3 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -Below follows the flow diagram for the generation of credentials. -Users MUST generate key pairs by themselves. -![figure1](./images/eth-secpm_credential.png) - -### Message framing - -Handshake and application messages use a common framing structure -providing encryption to -ensure confidentiality within the group, and signing to authenticate -the sender. - -The structure is: - -- `PublicMessage`: represents a message that is only signed, and not -encrypted. -The definition and the encoding/decoding of a `PublicMessage` MUST -follow the specification -in section 6.2 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). -- `PrivateMessage`: represents a signed and encrypted message, with -protections for both the content of the message and related metadata. - -The definition, and the encoding/decoding of a `PrivateMessage` MUST -follow the specification in section 6.3 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -Applications MUST use `PrivateMessage` to encrypt application messages. - -Applications SHOULD use `PrivateMessage` to encode handshake messages. - -Each encrypted MLS message carries a "generation" number which is a -per-sender incrementing counter. -If a group member observes a gap in the generation sequence for a -sender, then they know that they have missed a message from that -sender. - -### Nodes contents - -The nodes of a ratchet tree contain several types of data: - -- Leaf nodes describe individual members. -- Parent nodes describe subgroups. - -Contents of each kind of node, and its structure MUST follow the -indications described in -sections 7.1 and 7.2 of -[RFC9420](https://datatracker.ietf.org/docrfc9420/). - -### Leaf node validation - -`KeyPackage` objects describe the client's capabilities and provides -keys that can be used to add the client to a group. - -The validity of a leaf node needs to be verified at the following -stages: - -- When a leaf node is downloaded in a `KeyPackage`, before it is used -to add the client to the group. -- When a leaf node is received by a group member in an Add, Update, or -Commit message. -- When a client validates a ratchet tree. - -A client MUST verify the validity of a leaf node following the -instructions of section 7.3 in -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Ratchet tree evolution +```text +PKE-KGen(): +a = randint(2, p-2) +pk = (p, g, g^a) +sk = a +return (pk, sk) -Whenever a member initiates an epoch change, they MAY need to refresh -the key pairs of their leaf and of the nodes on their direct path. This -is done to keep forward secrecy and post-compromise security. -The member initiating the epoch change MUST follow this procedure -procedure. -A member updates the nodes along its direct path as follows: +``` -- Blank all the nodes on the direct path from the leaf to the root. -- Generate a fresh HPKE key pair for the leaf. -- Generate a sequence of path secrets, one for each node on the leaf's -filtered direct path. +#### PKE-Enc -It MUST follow the procedure described in section 7.4 of [RFC9420 -(https://datatracker.ietf.org/doc/rfc9420/). +A user `v` encrypting a message `m` for `u` MUST follow these steps: -- Compute the sequence of HPKE key pairs `(node_priv,node_pub)`, one -for each node on the leaf's direct path. +```text +PKE-Enc(pk): +k = randint(2, p-2) +eta = g^k % p +delta = m * (g^a)^k % p +return ((eta, delta)) -It MUST follow the procedure described in section 7.4 of [RFC9420 -(https://datatracker.ietf.org/doc/rfc9420/). +``` -### Views of the tree synchronization +#### PKE-Dec -After generating fresh key material and applying it to update their -local tree state, the generator broadcasts this update to other members -of the group. -This operation MUST be done according to section 7.5 of [RFC9420 -(https://datatracker.ietf.org/doc/rfc9420/). +The user `u` recovers a message `m` from a ciphertext `c` +by performing the following operations: -### Leaf synchronization +```text +PKE-Dec(sk): +mu = eta^(p-1-sk) % p +return ((mu * delta) % p) -Changes to group memberships MUST be represented by adding and removing -leaves of the tree. -This corresponds to increasing or decreasing the depth of the tree, -resulting in the number of leaves being doubled or halved. -These operations MUST be done as described in section 7.7 of [RFC9420 -(https://datatracker.ietf.org/doc/rfc9420/). +``` -### Tree and parent hashing +### DCGKA Syntax -Group members can agree on the cryptographic state of the group by -generating a hash value that represents the contents of the group -ratchet tree and the member’s credentials. -The hash of the tree is the hash of its root node, defined recursively -from the leaves. -Tree hashes summarize the state of a tree at point in time. -The hash of a leaf is the hash of the `LeafNodeHashInput` object. -At the same time, the hash of a parent node including the root, is the -hash of a `ParentNodeHashInput` object. -Parent hashes capture information about how keys in the tree were -populated. +#### Auxiliary functions -Tree and parent hashing MUST follow the directions in Sections 7.8 and -7.9 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). +There exist 6 functions that are auxiliary for the rest of components of the +protocol, namely: -### Key schedule +#### init -Group keys are derived using the `Extract` and `Expand` functions from -the KDF for the group's cipher suite, as well as the functions defined -below: +This function takes an `ID` as input and returns its associated initial state, +denoted by `gamma`: ```text -ExpandWithLabel(Secret, Label, Context, Length) = KDF.Expand(Secret, -KDFLabel, Length) -DeriveSecret(Secret, Label) = ExpandWithLabel(Secret, Label, "", -KDF.Nh) +gamma.myId = ID +gamma.mySeq = 0 +gamma.history = empty +gamma.nextSeed = empty_string +gamma.2sm[·] = empty_string +gamma.memberSecret[·, ·, ·] = empty_string +gamma.ratchet[·] = empty_string +return (gamma) ``` -`KDFLabel` MUST be specified as: +#### encrypt-to -```text -struct { - uint16 length; - opaque label; - opaque context; -} KDFLabel; - -``` - -The fields of `KDFLabel` MUST be: +Upon reception of the recipient’s `ID` and a plaintext, it encrypts a direct +message for another group member. +Should it be the first message for a particular `ID`, +then the `2SM` protocol state is initialized and stored in +`gamma.2sm[recipient.ID]`. +One then uses `2SM_Send` to encrypt the message and store the updated protocol +in `gamma`. ```text -length = Length; -label = "MLS 1.0 " + Label; -context = Context; +if gamma.2sm[recipient_ID] = empty_string then + gamma.2sm[recipient_ID] = 2SM_Init(gamma.myID, recipient_ID) +(gamma.2sm[recipient_ID], ciphertext) = 2SM_Send(gamma.2sm[recipient_ID], plaintext) +return (gamma, ciphertext) ``` -Each member of the group MUST maintaint a `GroupContext` object -summarizing the state of the group. +#### decrypt-from -The sturcture of such object MUST be: +After receiving the sender’s `ID` and a ciphertext, it behaves as the reverse +function of `encrypt-to` and has a similar initialization: ```text -struct { -ProtocolVersion version = mls10; -CipherSuite cipher_suite; -opaque group_id; -uint64 epoch; -opaque tree_hash; -opaque confirmed_trasncript_hash; -Extension extension; -} GroupContext; +if gamma.2sm[sender_ID] = empty_string then +gamma.2sm[sender_ID] = 2SM_Init(gamma.myID, sender_ID) +(gamma.2sm[sender_ID], plaintext) = 2SM_Receive(gamma.2sm[sender_ID], ciphertext) +return (gamma, plaintext) ``` -The use of key scheduling MUST follow the indications in sections 8.1 - -8.7 in [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Secret trees +#### update-ratchet -For the generation of encryption keys and nonces, the key schedule -begins with the `encryption_secret` at the root and derives a tree of -secrets with the same structure as the group's ratchet tree. -Each leaf in the secret tree is associated with the same group member -as the corresponding leaf in the ratchet tree. +This function generates the next update secret `I_update` for the group member +`ID`. +The ratchet state is stored in `gamma.ratchet[ID]`. +It is required to use a HMAC-based key derivation function HKDF to combine the +ratchet state with an input, returning an update secret and a new ratchet +state. -If `N` is a parent node in the secret tree, the secrets of the children -of `N` MUST be defined following section 9 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -#### Encryption keys +```text +(updateSecret, gamma.ratchet[ID]) = HKDF(gamma.ratchet[ID], input) +return (gamma, updateSecret) -MLS encrypts three different types of information: +``` -- Metadata (sender information). -- Handshake messages (Proposal and Commit). -- Application messages. +#### member-view -For handshake and application messages, a sequence of keys is derived -via a sender ratchet. -Each sender has their own sender ratchet, and each step along the -ratchet is called a generation. These procedures MUST follow section -9.1 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). +This function calculates the set of group members +based on the most recent control message sent by the specified user `ID`. +It filters the group membership operations +to include only those observed by the specified `ID`, and +then invokes the DGM function to generate the group membership. -#### Deletion schedule +```text +ops = {m in gamma.history st. m was sent or acknowledged by ID} +return DGM(ops) -All security-sensitive values MUST be deleted as soon as they are -consumed. +``` -A sensitive value S is consumed if: +#### generate-seed -- S was used to encrypt or (successfully) decrypt a message. -- A key, nonce, or secret derived from S has been consumed. +This functions generates a random bit string and +sends it encrypted to each member of the group using the `2SM` mechanism. +It returns the updated protocol state and +the set of direct messages (denoted as `dmsgs`) to send. -The deletion procedure MUST follow the instruction described in section -9.2 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). +```text +gamma.nextSeed = random.randbytes() +dmsgs = empty +for each ID in recipients: +(gamma, msg) = encrypt-to(gamma, ID, gamma.nextSeed) +dmsgs = dmsgs + (ID, msg) +return (gamma, dmsgs) -### Key packages +``` -KeyPackage objects are used to ease the addition of clients to a group -asynchronously. -A KeyPackage object specifies: +### Creation of a group -- Protocol version and cipher suite supported by the client. -- Public keys that can be used to encrypt Welcome messages. -Welcome messages provide new members with the information -to initialize their -state for the epoch in which they were added or in which they want to -add themselves to the group -- The content of the leaf node that should be added to the tree to -represent this client. +A group is generated in a 3 steps procedure: -KeyPackages are intended to be used only once and SHOULD NOT be reused. +1. A user calls the `create` function and broadcasts a control message of type +*create*. +2. Each receiver of the message processes the message and broadcasts an *ack* +control message. +3. Each member processes the *ack* message received. -Clients MAY generate and publish multiple KeyPackages to support -multiple cipher suites. +#### create -The structure of the object MUST be: +This function generates a *create* control message and calls `generate-seed` to +define the set of direct messages that need to be sent. +Then it calls `process-create` to process the control message for this user. +The function `process-create` returns a tuple including an updated state gamma +and an update secret `I`. ```text -struct { -ProtocolVersion version; -CipherSuite cipher_suite; -HPKEPublicKey init_key; -LeafNode leaf_node; -Extension extensions; -/* SignWithLabel(., "KeyPackageTBS", KeyPackageTBS) */ -opaque signature; -} +control = (“create”, gamma.mySeq, IDs) +(gamma, dmsgs) = generate-seed(gamma, IDs) +(gamma, _, _, I, _) = process-create(gamma, gamma.myId, gamma.mySeq, IDs, empty_string) +return (gamma, control, dmsgs, I) ``` +#### process-seed + +This function initially employs `member-view` to identify the users who were +part of the group when the control message was dispatched. +Then, it attempts to acquire the seed secret through the following steps: + +1. If the control message was dispatched by the local user, it uses the most +recent invocation of `generate-seed` stored the seed secret in +`gamma.nextSeed`. +2. If the `control` message was dispatched by another user, and the local user +is among its recipients, the function utilizes `decrypt-from` to decrypt the +direct message that includes the seed secret. +3. Otherwise, it returns an `ack` message without deriving an update secret. + +Afterwards, `process-seed` generates separate member secrets for each group +member from the seed secret by combining the seed secret and +each user ID using HKDF. +The secret for the sender of the message is stored in `senderSecret`, while +those for the other group members are stored in `gamma.memberSecret`. +The sender's member secret is immediately utilized to update their KDF ratchet +and compute their update secret `I_sender` using `update-ratchet`. +If the local user is the sender of the control message, the process is +completed, and the update secret is returned. +However, if the seed secret is received from another user, an `ack` control +message is constructed for broadcast, including the sender ID and sequence +number of the message being acknowledged. + +The final step computes an update secret `I_me` for the local user invoking the +`process-ack` function. + ```text -struct { -ProtocolVersion version; -CipheSuite cipher_suite; -HPKEPublicKey init_key; -LeafNode leaf_node; -Extension extensions; -} +recipients = member-view(gamma, sender) - {sender} +if sender = gamma.myId then seed = gamma.nextSeed; gamma.nextSeed = +empty_string +else if gamma.myId in recipients then (gamma, seed) = decrypt-from(gamma, +sender, dmsg) +else +return (gamma, (ack, ++gamma.mySeq, (sender, seq)), empty_string , +empty_string , empty_string) + +for ID in recipients do gamma.memberSecret[sender, seq, ID] = HKDF(seed, ID) +senderSecret = HKDF(seed, sender) +(gamma, I_sender) = update-ratchet(gamma, sender, senderSecret) +if sender = gamma.myId then return (gamma, empty_string , empty_string , +I_sender, empty_string) +control = (ack, ++gamma.mySeq, (sender, seq)) +members = member-view(gamma, gamma.myId) +forward = empty +for ID in {members - (recipients + {sender})} + s = gamma.memberSecret[sender, seq, gamma.myId] + (gamma, msg) = encrypt-to(gamma, ID, s) + forward = forward + {(ID, msg)} + (gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq, + (sender, seq), empty_string) + return (gamma, control, forward, I_sender, I_me) ``` -`KeyPackage` object MUST be verified when: - -- A `KeyPackage` is downloaded by a group member, before it is used to -add the client to the group. -- When a `KeyPackage` is received by a group member in an `Add` -message. - -Verification MUST be done as follows: - -- Verify that the cipher suite and protocol version of the `KeyPackage` -match those in the `GroupContext`. -- Verify that the `leaf_node` of the `KeyPackage` is valid for a -`KeyPackage`. -- Verify that the signature on the `KeyPackage` is valid. -- Verify that the value of `leaf_node.encryption_key` is different from -the value of the `init_key field`. - -HPKE public keys are opaque values in a format defined by Section 4 of -[RFC9180](https://datatracker.ietf.org/doc/rfc9180/). - -Signature public keys are represented as opaque values in a format -defined by the cipher suite's signature scheme. +#### process-create -### Group creation - -A group is always created with a single member. -Other members are then added to the group using the usual Add/Commit -mechanism. -The creator of a group MUST set: - -- the group ID. -- cipher suite. -- initial extensions for the group. - -If the creator intends to add other members at the time of creation, -then it SHOULD fetch `KeyPackages` for those members, and select a -cipher suite and extensions according to their capabilities. - -The creator MUST use the capabilities information in these -`KeyPackages` to verify that the chosen version and cipher suite is the -best option supported by all members. - -Group IDs SHOULD be constructed so they are unique with high -probability. - -To initialize a group, the creator of the group MUST initialize a one -member group with the following initial values: - -- Ratchet tree: A tree with a single node, a leaf node containing an -HPKE public key and credential for the creator. -- Group ID: A value set by the creator. -- Epoch: `0`. -- Tree hash: The root hash of the above ratchet tree. -- Confirmed transcript hash: The zero-length octet string. -- Epoch secret: A fresh random value of size `KDF.Nh`. -- Extensions: Any values of the creator's choosing. - -The creator MUST also calculate the interim transcript hash: - -- Derive the `confirmation_key` for the epoch according to Section 8 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). -- Compute a `confirmation_tag` over the empty -`confirmed_transcript_hash` using the `confirmation_key` as described -in Section 8.1 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). -- Compute the updated `interim_transcript_hash` from the -`confirmed_transcript_hash` and the `confirmation_tag` as described in -Section 8.2 [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -All members of a group MUST support the cipher suite and protocol -version in use. Additional requirements MAY be imposed by including a -`required_capabilities` extension in the `GroupContext`. +This function is called by the sender and each of the receivers of the `create` +control message. +First, it records the information from the create message in the +`gamma.history+ {op}`, which is used to track group membership changes. Then, +it proceeds to call `process-seed`. ```text -struct { -ExtensionType extension_types; -ProposalType proposal_types; -CredentialType credential_types; -} +op = (”create”, sender, seq, IDs) +gamma.history = gamma.history + {op} +return (process-seed(gamma, sender, seq, dmsg)) ``` -The flow diagram shows the procedure to fetch key material from other -users: -![figure2](./images/eth-secpm_fetching.png) - -Below follows the flow diagram for the creation of a group: -![figure3](./images/eth-secpm_creation.png) +#### process-ack -### Group evolution - -Group membership can change, and existing members can change their keys -in order to achieve post-compromise security. -In MLS, each such change is accomplished by a two-step process: - -- A proposal to make the change is broadcast to the group in a Proposal +This function is called by those group members once they receive an ack message. -- A member of the group or a new member broadcasts a Commit message -that causes one or more proposed changes to enter into effect. +In `process-ack`, `ackID` and `ackSeq` are the sender and sequence number of +the acknowledged message. +Firstly, if the acknowledged message is a group membership operation, it +records the acknowledgement in `gamma.history`. -The group evolves from one cryptographic state to another each time a -Commit message is sent and processed. -These states are called epochs and are uniquely identified among states -of the group by eight-octet epoch values. +Following this, the function retrieves the relevant member secret from +`gamma.memberSecret`, which was previously obtained from the seed secret +contained in the acknowledged message. -Proposals are included in a `FramedContent` by way of a `Proposal` -structure that indicates their type: +Finally, it updates the ratchet for the sender of the `ack` and returns the +resulting update secret. ```text -struct { -ProposalType proposal_type; -select (Proposal.proposal_type) { -case add: Add: -case update: Update; -case remove: Remove; -case psk: PreSharedKey; -case reinit: ReInit; -case external_init: ExternalInit; -case group_context_extensions: GroupContextExtensions; -} +if (ackID, ackSeq) was a create / add / remove then +op = ("ack", sender, seq, ackID, ackSeq) +gamma.history = gamma.history + {op}` +s = gamma.memberSecret[ackID, ackSeq, sender] +gamma.memberSecret[ackID, ackSeq, sender] = empty_string +if (s = empty_string) & (dmsg = empty_string) then return (gamma, empty_string, +empty_string, empty_string, empty_string) +if (s = empty_string) then (gamma, s) = decrypt-from(gamma, sender, dmsg) +(gamma, I) = update-ratchet(gamma, sender, s) +return (gamma, empty_string, empty_string, I, empty_string) ``` -On receiving a `FramedContent` containing a `Proposal`, a client MUST -verify the signature inside `FramedContentAuthData` and that the epoch -field of the enclosing FramedContent is equal to the epoch field of the -current GroupContext object. -If the verification is successful, then the Proposal SHOULD be cached -in such a way that it can be retrieved by hash in a later Commit -message. +The HKDF function MUST follow RFC 5869 using the hash function SHA256. -Proposals are organized as follows: - -- `Add`: requests that a client with a specified KeyPackage be added to -the group. -- `Update`: similar to Add, it replaces the sender's LeafNode in the -tree instead of adding a new leaf to the tree. -- `Remove`: requests that the member with the leaf index removed be -removed from the group. -- `ReInit`: requests to reinitialize the group with different -parameters. -- `ExternalInit`: used by new members that want to join a group by -using an external commit. -- `GroupContentExtensions`: it is used to update the list of extensions -in the GroupContext for the group. - -Proposals structure and semantics MUST follow sections 12.1.1 - 12.1.7 -of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -Any list of commited proposals MUST be validated either by a the group -member who created the commit, or any group member processing such -commit. -The validation MUST be done according to one of the procedures -described in Section 12.2 of -[RFC9420](https://datatracker.ietf.orgdoc/rfc9420/). - -When creating or processing a Commit, a client applies a list of -proposals to the ratchet tree and `GroupContext`. -The client MUST apply the proposals in the list in the order described -in Section 12.3 of [RFC9420](https://datatracker.ietf.org/docrfc9420/). - -Below follows the flow diagram for the addition of a member to a group: -![figure4](./images/eth-secpm_add.png) - -The diagram below shows the procedure to remove a group member: - -![figure5](./images/eth-secpm_remove.png) - -The flow diagram below shows an update procedure: - -![figure6](./images/eth-secpm_update.png) - -### Commit messages - -Commit messages initiate new group epochs. -It informs group members to update their representation of the state of -the group by applying the proposals and advancing the key schedule. - -Each proposal covered by the Commit is included by a `ProposalOrRef` -value. -`ProposalOrRef` identify the proposal to be applied by value or by -reference. -Commits that refer to new Proposals from the committer can be included -by value. -Commits for previously sent proposals from anyone can be sent by -reference. -Proposals sent by reference are specified by including the hash of the -`AuthenticatedContent`. - -Group members that have observed one or more valid proposals within an -epoch MUST send a Commit message before sending application data. -A sender and a receiver of a Commit MUST verify that the committed list -of proposals is valid. -The sender of a Commit SHOULD include all valid proposals received -during the current epoch. - -Functioning of commits MUST follow the instructions of Section 12.4 of -[RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Application messages - -Handshake messages provide an authenticated group key exchange to -clients. -To protect application messages sent among the members of a group, the -`encryption_secret` provided by the key schedule is used to derive a -sequence of nonces and keys for message encryption. - -Each client MUST maintain their local copy of the key schedule for each -epoch during which they are a group member. -They derive new keys, nonces, and secrets as needed. This data MUST be -deleted as soon as they have been used. - -Group members MUST use the AEAD algorithm associated with the -negotiated MLS ciphersuite to encrypt and decrypt Application messages -according to the Message Framing section. -The group identifier and epoch allow a device to know which group -secrets should be used and from which Epoch secret to start computing -other secrets and keys. -Application messages SHOULD be padded to provide resistance against -traffic analysis techniques. -This avoids additional information to be provided to an attacker in -order to guess the length of the encrypted message. -Padding SHOULD be used on messages with zero-valued bytes before AEAD -encryption. - -Functioning of application messages MUST follow the instructions of -Section 15 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - -### Considerations with respect to decentralization - -The MLS protocol assumes the existence on a (central, untrusted) -*delivery service*, whose responsabilites include: - -- Acting as a directory service providing the initial -keying material for clients to use. -- Routing MLS messages among clients. - -The central delivery service can be avoided in protocols using the -publish/gossip approach, such as -[gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub). +### Post-compromise security updates and group member removal -Concerning keys, each node can generate and disseminate their -encryption key among the other nodes, so they can create a local -version of the tree that allows for the generation of the group key. +The functions `update` and `remove` share similarities with `create`: +they both call the function `generate-seed` to encrypt a new seed secret for +each group member. +The distinction lies in the determination of the group members using `member +view`. +In the case of `remove`, the user being removed is excluded from the recipients +of the seed secret. +Additionally, the control message they construct is designated with type +`update` or `remove` respectively. -Another important component is the *authentication service*, which is -replaced with SIWE in this specification. +Likewise, `process-update` and `process-remove` are akin to `process-create`. +The function `process-update` skips the update of `gamma.history`, +whereas `process-remove` includes a removal operation in the history. -## Ethereum-based authentication protocol - -### Introduction - -Sign-in with Ethereum describes how Ethereum accounts authenticate with -off-chain services by signing a standard message format -parameterized by scope, session details, and security mechanisms. -Sign-in with Ethereum (SIWE), which is described in the [EIP 4361 -(https://eips.ethereum.org/EIPS/eip-4361), MUST be the authentication -method required. - -### Pattern - -#### Message format (ABNF) - -A SIWE Message MUST conform with the following Augmented Backus–Naur -Form ([RFC 5234](https://datatracker.ietf.org/doc/html/rfc5234)) -expression. +#### update ```text -sign-in-with-ethereum = - [ scheme "://" ] domain %s" wants you to sign in with your - Ethereum account:" LF address LF - LF - [ statement LF ] - LF - %s"URI: " uri LF - %s"Version: " version LF - %s"Chain ID: " chain-id LF - %s"Nonce: " nonce LF - %s"Issued At: " issued-at - [ LF %s"Expiration Time: " expiration-time ] - [ LF %s"Not Before: " not-before ] - [ LF %s"Request ID: " request-id ] - [ LF %s"Resources:" - resources ] - -scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - ; See RFC 3986 for the fully contextualized - ; definition of "scheme". - -domain = authority - ; From RFC 3986: - ; authority = [ userinfo "@" ] host [ ":" port ] - ; See RFC 3986 for the fully contextualized - ; definition of "authority". - -address = "0x" 40*40HEXDIG - ; Must also conform to captilization - ; checksum encoding specified in EIP-55 - ; where applicable (EOAs). - -statement = *( reserved / unreserved / " " ) - ; See RFC 3986 for the definition - ; of "reserved" and "unreserved". - ; The purpose is to exclude LF (line break). - -uri = URI - ; See RFC 3986 for the definition of "URI". - -version = "1" - -chain-id = 1*DIGIT - ; See EIP-155 for valid CHAIN_IDs. - -nonce = 8*( ALPHA / DIGIT ) - ; See RFC 5234 for the definition - ; of "ALPHA" and "DIGIT". - -issued-at = date-time -expiration-time = date-time -not-before = date-time - ; See RFC 3339 (ISO 8601) for the - ; definition of "date-time". - -request-id = *pchar - ; See RFC 3986 for the definition of "pchar". - -resources = *( LF resource ) - -resource = "- " URI +control = ("update", ++gamma.mySeq, empty_string) +recipients = member-view(gamma, gamma.myId) - {gamma.myId} +(gamma, dmsgs) = generate-seed(gamma, recipients) +(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq, +empty_string, empty_string) +return (gamma, control, dmsgs, I) ``` -This specification defines the following SIWE Message fields that can -be parsed from a SIWE Message by following the rules in ABNF Message -Format: - -- `scheme` OPTIONAL. The URI scheme of the origin of the request. -Its value MUST be a -[RFC 3986](https://datatracker.ietf.org/doc/htmlrfc3986) -URI scheme. - -- `domain` REQUIRED. -The domain that is requesting the signing. -Its value MUST be a [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) -authority. The authority includes an OPTIONAL port. -If the port is not specified, the default -port for the provided scheme is assumed. - -If scheme is not specified, HTTPS is assumed by default. - -- `address` REQUIRED. The Ethereum address performing the signing. -Its value SHOULD be conformant to mixed-case checksum address encoding -specified in ERC-55 where applicable. - -- `statement` OPTIONAL. A human-readable ASCII assertion that the user -will sign which MUST NOT include '\n' (the byte 0x0a). -- `uri` REQUIRED. An -[RFC 3986](https://datatracker.ietf.org/doc/htmlrfc3986) -URI referring to the resource that is the subject of the -signing. - -- `version` REQUIRED. The current version of the SIWE Message, which -MUST be 1 for this specification. - -- `chain-id` REQUIRED. The EIP-155 Chain ID to which the session is -bound, and the network where Contract Accounts MUST be resolved. - -- `nonce` REQUIRED. A random string (minimum 8 alphanumeric characters) -chosen by the relying party and used to prevent replay attacks. - -- `issued-at` REQUIRED. The time when the message was generated, -typically the current time. +#### remove -Its value MUST be an ISO 8601 datetime string. - -- `expiration-time` OPTIONAL. The time when the signed authentication -message is no longer valid. +```text +control = ("remove", ++gamma.mySeq, empty) +recipients = member-view(gamma, gamma.myId) - {ID, gamma.myId} +(gamma, dmsgs) = generate-seed(gamma, recipients) +(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq, ID, +empty_string) +return (gamma, control, dmsgs, I) -Its value MUST be an ISO 8601 datetime string. +``` -- `not-before` OPTIONAL. The time when the signed authentication -message will become valid. +#### process-update -Its value MUST be an ISO 8601 datetime string. +`return process-seed(gamma, sender, seq, dmsg)` -- `request-id` OPTIONAL. An system-specific identifier that MAY be used -to uniquely refer to the sign-in request. +#### process-remove -- `resources` OPTIONAL. A list of information or references to -information the user wishes to have resolved as part of authentication -by the relying party. +```text +op = ("remove", sender, seq, removed) +gamma.history = gamma.history + {op} +return process-seed(gamma, sender, seq, dmsg) -Every resource MUST be a RFC 3986 URI separated by "\n- " where \n is -the byte 0x0a. +``` -#### Signing and Verifying Messages with Ethereum Accounts +### Group member addition -- For Externally Owned Accounts, the verification method specified in -[ERC-191](https://eips.ethereum.org/EIPS/eip-191) -MUST be used. +#### add -- For Contract Accounts, +When adding a new group member, an existing member initiates the process by +invoking the `add` function and providing the ID of the user to be added. +This function prepares a control message marked as `add` for broadcast to the +group. Simultaneously, it creates a welcome message intended for the new member +as a direct message. +This `welcome` message includes the current state of the sender's KDF ratchet, +encrypted using `2SM`, along with the history of group membership operations +conducted so far. - - The verification method specified in -[ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) -SHOULD be used. -Otherwise, the implementer MUST clearly define the -verification method -to attain security and interoperability for both -wallets and relying parties. +```text +control = ("add", ++gamma.mySeq, ID) +(gamma, c) = encrypt-to(gamma, ID, gamma.ratchet[gamma.myId]) +op = ("add", gamma.myId, gamma.mySeq, ID) +welcome = (gamma.history + {op}, c) +(gamma, _, _, I, _) = process-add(gamma, gamma.myId, gamma.mySeq, ID, empty_string) +return (gamma, control, (ID, welcome), I) - - When performing [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) -signature verification, the contract performing the verification MUST -be resolved from the specified `chain-id`. +``` - - Implementers SHOULD take into consideration that [ERC-1271 -(https://eips.ethereum.org/EIPS/eip-1271) implementations are not -required to be pure functions. -They can return different results for the same inputs depending on -blockchain state. -This can affect the security model and session validation rules. +#### process-add + +This function is invoked by both the sender and each recipient of an `add` +message, which includes the new group member. If the local user is the newly +added member, the function proceeds to call `process-welcome` and then exits. +Otherwise, it extends `gamma.history` with the `add` operation. + +Line 5 determines whether the local user was already a group member at the time +the `add` message was sent; this condition is typically true but may be false +if multiple users were added concurrently. + +On lines 6 to 8, the ratchet for the sender of the *add* message is updated +twice. In both calls to `update-ratchet`, a constant string is used as the +ratchet input instead of a random seed secret. + +The value returned by the first ratchet update is stored in +`gamma.memberSecret` as the added user’s initial member secret. The result of +the second ratchet update becomes `I_sender`, the update secret for the sender +of the `add` message. On line 10, if the local user is the sender, the update +secret is returned. + +If the local user is not the sender, an acknowledgment for the `add` message is +required. +Therefore, on line 11, a control message of type `add-ack` is constructed for +broadcast. +Subsequently, in line 12 the current ratchet state is encrypted using `2SM` to +generate a direct message intended for the added user, allowing them to decrypt +subsequent messages sent by the sender. +Finally, in lines 13 to 15, `process-add-ack` is called to calculate the local +user’s update secret (`I_me`), which is then returned along with `I_sender`. -#### Resolving Ethereum Name Service (ENS) Data +```text +if added = gamma.myId then return process-welcome(gamma, sender, seq, dmsg) +op = ("add", sender, seq, added) +gamma.history = gamma.history + {op} +if gamma.myId in member-view(gamma, sender) then + (gamma, s) = update-ratchet(gamma, sender, "welcome") + gamma.memberSecret[sender, seq, added] = s + (gamma, I_sender) = update-ratchet(gamma, sender, "add") + else I_sender = empty_string + if sender = gamma.myId then return (gamma, empty_string, empty_string, + I_sender, empty_string) + control = ("add-ack", ++gamma.mySeq, (sender, seq)) + (gamma, c) = encrypt-to(gamma, added, ratchet[gamma.myId]) + (gamma, _, _, I_me, _) = process-add-ack(gamma, gamma.myId, gamma.mySeq, + (sender, seq), empty_string) + return (gamma, control, {(added, c)}, I_sender, I_me) -- The relying party or wallet MAY additionally perform resolution of -ENS data, as this can improve the user experience by displaying human -friendly information that is related to the `address`. -Resolvable ENS data include: +``` - - The primary ENS name. - - The ENS avatar. - - Any other resolvable resources specified in the ENS documentation. - -- If resolution of ENS data is performed, implementers SHOULD take -precautions to preserve user privacy and consent. -Their `address` could be forwarded to third party services as part of -the resolution process. - -#### Implementer steps: specifying the request origin - -The `domain` and, if present, the `scheme`, in the SIWE Message MUST -correspond to the origin from where the signing request was made. - -#### Implementer steps: verifying a signed message +#### process-add-ack -The SIWE Message MUST be checked for conformance to the ABNF Message -Format and its signature MUST be checked as defined in Signing and -Verifying Messages with Ethereum Accounts. +This function is invoked by both the sender and each recipient of an `add-ack` +message, including the new group member. Upon lines 1–2, the acknowledgment is +added to `gamma.history`, mirroring the process in `process-ack`. +If the current user is the new group member, the `add-ack` message includes the +direct message constructed in `process-add`; this direct message contains the +encrypted ratchet state of the sender of the `add-ack`, then it is decrypted on +lines 3–5. -#### Implementer steps: creating sessions +Upon line 6, a check is performed to check if the local user was already a +group member at the time the `add-ack` was sent. If affirmative, a new update +secret `I` for the sender of the `add-ack` is computed on line 7 by invoking +`update-ratchet` with the constant string `add`. -Sessions MUST be bound to the address and not to further resolved -resources that can change. +In the scenario involving the new member, the ratchet state was recently +initialized on line 5. This ratchet update facilitates all group members, +including the new addition, to derive each member’s update by obtaining any +update secret from before their inclusion. -#### Implementer steps: interpreting and resolving resources +```text +op = ("ack", sender, seq, ackID, ackSeq) +gamma$.history = gamma.history + {op} +if dmsg != empty_string then + (gamma, s) = decrypt-from(gamma, sender, dmsg) + gamma.ratchet[sender] = s +if gamma.myId in member-view(gamma, sender) then + (gamma, I) = update-ratchet(gamma, sender, "add") + return (gamma, empty_string, empty_string, I, empty_string) +else return (gamma, empty_string, empty_string, empty_string, empty_string) -Implementers SHOULD ensure that that URIs in the listed resources are -human-friendly when expressed in plaintext form. +``` -#### Wallet implementer steps: verifying the message format +#### process-welcome + +This function serves as the second step called by a newly added group member. +In this context, `adderHistory` represents the adding user’s copy of +`gamma.history` sent in their welcome message, which is utilized to initialize +the added user’s history. +Here, `c` denotes the ciphertext of the adding user’s ratchet state, which is +decrypted on line 2 using `decrypt-from`. + +Once `gamma.ratchet[sender]` is initialized, `update-ratchet` is invoked twice +on lines 3 to 5 with the constant strings `welcome` and `add` respectively. +These operations mirror the ratchet operations performed by every other group +member in `process-add`. +The outcome of the first `update-ratchet` call becomes the first member secret +for the added user, +while the second call returns `I_sender`, the update secret for the sender of +the add operation. + +Subsequently, the new group member constructs an *ack* control message to +broadcast on line 6 and calls `process-ack` to compute their initial update +secret I_me. The function `process-ack` reads from `gamma.memberSecret` and +passes it to `update-ratchet`. The previous ratchet state for the new member is +the empty string `empty`, as established by `init`, thereby initializing the +new member’s ratchet. +Upon receiving the new member’s `ack`, every other group member initializes +their copy of the new member’s ratchet in a similar manner. + +By the conclusion of `process-welcome`, the new group member has acquired +update secrets for themselves and the user who added them. +The ratchets for other group members are initialized by `process-add-ack`. -The full SIWE message MUST be checked for conformance to the ABNF -defined in ABNF Message Format. +```text +gamma.history = adderHistory +(gamma, gamma.ratchet[sender]) = decrypt-from(gamma, sender, c) +(gamma, s) = update-ratchet(gamma, sender, "welcome") +gamma.memberSecret[sender, seq, gamma.myId] = s +(gamma, I_sender) = update-ratchet(gamma, sender, "add") +control = ("ack", ++gamma.mySeq, (sender, seq)) +(gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq, (sender, +seq), empty_string) +return (gamma, control, empty_string , I_sender, I_me) -Wallet implementers SHOULD warn users if the substring `"wants you to -sign in with your Ethereum account"` appears anywhere in an [ERC-191 -(https://eips.ethereum.org/EIPS/eip-191) message signing request unless -the message fully conforms to the format defined ABNF Message Format. +``` -#### Wallet implementer steps: verifying the request origin - -Wallet implementers MUST prevent phishing attacks by verifying the -origin of the request against the `scheme` and `domain` fields in the -SIWE Message. - -The origin SHOULD be read from a trusted data source such as the -browser window or over WalletConnect -[ERC-1328](https://eips.ethereum.org/EIPS/eip-1328) sessions for -comparison against the -signing message contents. - -Wallet implementers MAY warn instead of rejecting the verification if -the origin is pointing to localhost. - -The following is a RECOMMENDED algorithm for Wallets to conform with -the requirements on request origin verification defined by this -specification. - -The algorithm takes the following input variables: - -- fields from the SIWE message. -- `origin` of the signing request: the origin of the page which -requested the signin via the provider. -- `allowedSchemes`: a list of schemes allowed by the Wallet. -- `defaultScheme`: a scheme to assume when none was provided. Wallet -implementers in the browser SHOULD use https. -- developer mode indication: a setting deciding if certain risks should -be a warning instead of rejection. Can be manually configured or -derived from `origin` being localhost. - -The algorithm is described as follows: - -- If `scheme` was not provided, then assign `defaultScheme` as scheme. -- If `scheme` is not contained in `allowedSchemes`, then the `scheme` -is not expected and the Wallet MUST reject the request. -Wallet implementers in the browser SHOULD limit the list of -allowedSchemes to just 'https' unless a developer mode is activated. -- If `scheme` does not match the scheme of origin, the Wallet SHOULD -reject the request. -Wallet implementers MAY show a warning instead of rejecting the request -if a developer mode is activated. -In that case the Wallet continues processing the request. -- If the `host` part of the `domain` and `origin` do not match, the -Wallet MUST reject the request unless the Wallet is in developer mode. -In developer mode the Wallet MAY show a warning instead and continues -procesing the request. -- If `domain` and `origin` have mismatching subdomains, the Wallet -SHOULD reject the request unless the Wallet is in developer mode. -In developer mode the Wallet MAY show a warning instead and continues -procesing the request. -- Let `port` be the port component of `domain`, and if no port is -contained in domain, assign port the default port specified for the -scheme. -- If `port` is not empty, then the Wallet SHOULD show a warning if the -`port` does not match the port of `origin`. -- If `port` is empty, then the Wallet MAY show a warning if `origin` -contains a specific port. -- Return request origin verification completed. - -#### Wallet implementer steps: creating SIWE interfaces - -Wallet implementers MUST display to the user the following fields from -the SIWE Message request by default and prior to signing, if they are -present: `scheme`, `domain`, `address`, `statement`, and `resources`. -Other present fields MUST also be made available to the user prior to -signing either by default or through an extended interface. - -Wallet implementers displaying a plaintext SIWE Message to the user -SHOULD require the user to scroll to the bottom of the text area prior -to signing. - -Wallet implementers MAY construct a custom SIWE user interface by -parsing the ABNF terms into data elements for use in the interface. -The display rules above still apply to custom interfaces. - -#### Wallet implementer steps: supporting internationalization (i18n) - -After successfully parsing the message into ABNF terms, translation MAY -happen at the UX level per human language. - -## Privacy and Security Considerations - -- The double ratchet "recommends" using AES in CBC mode. Since -encryption must be with an AEAD encryption scheme, we will use AES in -GCM mode instead (supported by Noise). -- For the information retrieval, the algorithm MUST include a access -control mechanisms to restrict who can call the set and get functions. -- One SHOULD include event logs to track changes in public keys. -- The curve vurve448 MUST be chosen due to its higher security level: -224-bit security instead of the 128-bit security provided by X25519. -- It is important that Bob MUST NOT reuse `SPK`. - -## Considerations related to the use of Ethereum addresses - -### With respect to the Authentication Service - -- If users used their Ethereum addresses as identifiers, they MUST -generate their own credentials. -These credentials MUST use the digital signature key pair associated to -the Ethereum address. -- Other users can verify credentials. -- With this approach, there is no need to have a dedicated -Authentication Service responsible for the issuance and verification of -credentials. -- The interaction diagram showing the generation of credentials becomes -obsolete. - -### With respect to the Delivery Service - -- Users MUST generate their own KeyPackage. -- Other users can verify KeyPackages when required. -- A Delivery Service storage system MUST verify KeyPackages before -storing them. -- Interaction diagrams involving the DS do not change. - -## Consideration related to the onchain component of the protocol - -### Assumptions - -- Users have set a secure 1-1 communication channel. -- Each group is managed by a separate smart contract. - -### Addition of members to a group - -#### Alice knows Bob’s Ethereum address - -1. Off-chain - Alice and Bob set a secure communication channel. -2. Alice creates the smart contract associated to the group. This smart -contract MUST include an ACL. -3. Alice adds Bob’s Ethereum address to the ACL. -4. Off-chain - Alice sends a request to join the group to Bob. The -request MUST include the contract’s address: `RequestMLSPayload {"You -are joining the group with smart contract: 0xabcd"}` -5. Off-chain - Bob responds the request with a digitally signed -response. This response includes Bob’s credentials and key package: -`ResponseMLSPayload {sig: signature(ethereum_sk, message_to_sign), -address: ethereum_address, credentials, keypackage}` -6. Off-chain - Alice verifies the signature, using Bob’s `ethereum_pk` -and checks that it corresponds to an address contained in the ACL. -7. Off-chain - Alice sends a welcome message to Bob. -8. Off-chain - Alice SHOULD broadcasts a message announcing the -addition of Bob to other users of the group. -![figure7](./images/eth-secpm_onchain-register-1.png) - -#### Alice does not know Bob’s Ethereum address - -1. Off-chain - Alice and Bob set a secure communication channel. -2. Alice creates the smart contract associated to the group. -This smart contract MUST include an ACL. -3. Off-chain - Alice sends a request to join the group to Bob. The -request MUST include the contract’s address: -`RequestMLSPayload{"You are joining the group -with smart contract: 0xabcd"}` -4. Off-chain - Bob responds the request with a digitally signed -response. This response includes Bob’s credentials, his Ethereum -address and key package: `ResponseMLSPayload {sig: -signature(ethereum_sk, message_to_sign), address: ethereum_address, -credentials, keypackage}` -5. Off-chain - Alice verifies the signature using Bob’s `ethereum_pk`. -6. Upon reception of Bob’s data, Alice registers data with the smart -contract. -7. Off-chain - Alice sends a welcome message to Bob. -8. Off-chain - Alice SHOULD broadcasts a message announcing the -addition of Bob to other users of the group. - -![figure8](./images/eth-secpm_onchain-register-2.png) - -### Considerations regarding smart contracts - -The role of the smart contract includes: - -- Register user information and key packages: -As described in the previous section. -- Updates of key material. - - Users MUST send any update in their key material to the other -users of the group via off-chain messages. - - Upon reception of the new key material, the creator of the -contract MUST update the state of the smart contract. -- Deletion of users. - - Any user can submit a proposal for the removal of a user via -off-chain message. - - This proposal MUST be sent to the creator of the contract. - - The creator of the contract MUST update the ACL, and send -messages to the group for key update. - -![figure9](./images/eth-secpm_onchain-update.png) - -> It is important to note that both -user removal and updates of any kind -have a similar interaction flow. - -- Queries of existing users. - - Any user can query the smart contract to know the state of the -group, including existing users and removed ones. - - This aspect MUST be used when adding new members to verify that -the prospective key package has not been already used. +## Privacy Considerations + +### Dependency on PKI + +The [DCGKA](https://eprint.iacr.org/2020/1281) proposal presents some +limitations highlighted by the authors. +Among these limitations one finds the requirement of a PKI (or a key server) +mapping IDs to public keys. + +One method to overcome this limitation is adapting the protocol SIWE (Sign in +with Ethereum) so a user `u_1` who wants to start a communication with a user +`u_2` can interact with latter’s wallet to request a public key using an +Ethereum address as `ID`. + +#### SIWE + +The [SIWE](https://docs.login.xyz/general-information/siwe-overview) (Sign In +With Ethereum) proposal was a suggested standard for leveraging Ethereum to +authenticate and authorize users on web3 applications. +Its goal is to establish a standardized method for users to sign in to web3 +applications using their Ethereum address and private key, +mirroring the process by which users currently sign in to web2 applications +using their email and password. +Below follows the required steps: + +1. A server generates a unique Nonce for each user intending to sign in. +2. A user initiates a request to connect to a website using their wallet. +3. The user is presented with a distinctive message that includes the Nonce and +details about the website. +4. The user authenticates their identity by signing in with their wallet. +5. Upon successful authentication, the user's identity is confirmed or +approved. +6. The website grants access to data specific to the authenticated user. + +#### Our approach + +The idea in the [DCGKA](https://eprint.iacr.org/2020/1281) setting closely +resembles the procedure outlined in SIWE. Here: + +1. The server corresponds to user D1,who initiates a request (instead of +generating a nonce) to obtain the public key of user D2. +2. Upon receiving the request, the wallet of D2 send the request to the user, +3. User D2 receives the request from the wallet, and decides whether accepts or +rejects. +4. The wallet and responds with a message containing the requested public key +in case of acceptance by D2. + +This message may be signed, allowing D1 to verify that the owner of the +received public key is indeed D2. + +### Multi-device setting + +One may see the set of devices as a group and create a group key for internal +communications. +One may use treeKEM for instance, since it provides interesting properties like +forward secrecy and post-compromise security. +All devices share the same `ID`, which is held by one of them, and from other +user’s point of view, they would look as a single user. + +Using servers, like in the paper +[Multi-Device for Signal](https://eprint.iacr.org/2019/1363), should be +avoided; but this would imply using a particular device as receiver and +broadcaster within the group. +There is an obvious drawback which is having a single device working as a +“server”. Should this device be attacked or without connection, there should be +a mechanism for its revocation and replacement. + +Another approach for communications between devices could be using the keypair +of each device. This could open the door to use UPKE, since keypairs should be +regenerated frequently. + +Each time a device sends a message, either an internal message or an external +message, it needs to replicate and broadcast it to all devices in the group. + +The mechanism for the substitution of misbehaving leader devices follows: + +1. Each device within a group knows the details of other leader devices. This +information may come from metadata in received messages, and is replicated by +the leader device. +2. To replace a leader, the user should select any other device within its +group and use it to send a signed message to all other users. +3. To get the ability to sign messages, this new leader should request the +keypair associated to the ID to the wallet. +4. Once the leader has been changed, it revocates access from DCGKA to the +former leader using the DCGKA protocol. +5. The new leader starts a key update in DCGKA. + +Not all devices in a group should be able to send messages to other users. Only +the leader device should be in charge of sending and receiving messages. +To prevent other devices from sending messages outside their group, a +requirement should be signing each message. The keys associated to the `ID` +should only be in control of the leader device. + +The leader device is in charge of setting the keys involved in the DCGKA. This +information must be replicated within the group to make sure it is updated. + +To detect missing messages or potential misbehavior, messages must include a +counter. + +### Using UPKE + +Managing the group of devices of a user can be done either using a group key +protocol such as treeKEM or using the keypair of each device. +Setting a common key for a group of devices under the control of the same actor +might be excessive, furthermore it may imply some of the problems one can find +in the usual setting of a group of different users; +for example: one of the devices may not participate in the required updating +processes, representing a threat for the group. + +The other approach to managing the group of devices is using each device’s +keypair, but it would require each device updating these materia frequently, +something that may not happens. + +[UPKE](https://eprint.iacr.org/2022/068) is a form of asymetric cryptography +where any user can update any other user’s key pair by running an update +algorithm with (high-entropy) private coins. Any sender can initiate a *key +update* by sending a special update ciphertext. +This ciphertext updates the receiver’s public key and also, once processed by +the receiver, will update their secret key. + +To the best of my knowledge, there exists several efficient constructions both +[UPKE from ElGamal](https://eprint.iacr.org/2019/1189) (based in the DH +assumption) and [UPKE from Lattices]((https://eprint.iacr.org/2023/1400)) +(based in lattices). +None of them have been implemented in a secure messaging protocol, and this +opens the door to some novel research. ## Copyright -Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). +Copyright and related rights waived via +[CC0](https://creativecommons.org/publicdomain/zero/1.0/). ## References -- [Augmented BNF for Syntax Specifications](https://datatracker.ietf.org/doc/html/rfc5234) -- [Gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) -- [HMAC-based Extract-and-Expand Key Derivation Function](https://www.ietf.org/rfc/rfc5869.txt) -- [Hybrid Public Key Encryption](https://datatracker.ietf.org/doc/rfc9180/) -- [Security Analysis and Improvements for the IETF MLS Standard for Group Messaging](https://eprint.iacr.org/2019/1189.pdf) -- [Signed Data Standard](https://eips.ethereum.org/EIPS/eip-191) -- [Sign-In with Ethereum](https://eips.ethereum.org/EIPS/eip-4361) -- [Standard Signature Validation Method for Contracts](https://eips.ethereum.org/EIPS/eip-1271) -- [The Double Ratchet Algorithm](https://signal.org/docs/specifications/doubleratchet/) -- [The Messaging Layer Security Protocol](https://datatracker.ietf.org/doc/rfc9420/) -- [The X3DH Key Agreement Protocol](https://signal.org/docs/specifications/x3dh/) -- [Toy Ethereum Private Messaging Protocol](https://rfc.vac.dev/spec/20/) -- [Uniform Resource Identifier](https://datatracker.ietf.org/doc/html/rfc3986) -- [WalletConnect URI Format](https://eips.ethereum.org/EIPS/eip-1328) +- [DCGKA](https://eprint.iacr.org/2020/1281) +- [MLS](https://messaginglayersecurity.rocks) +- [CoCoa](https://eprint.iacr.org/2022/251) +- [Causal TreeKEM](https://mattweidner.com/assets/pdf/acs-dissertation.pdf) +- [SIWE](https://docs.login.xyz/general-information/siwe-overview) +- [Multi-device for Signal](https://eprint.iacr.org/2019/1363) +- [UPKE](https://eprint.iacr.org/2022/068) +- [UPKE from ElGamal](https://eprint.iacr.org/2019/1189) +- [UPKE from Lattices](https://eprint.iacr.org/2023/1400) From f170ca4ecc6fd399a1bbbaa52a0f15ec4ba99703 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 29 Oct 2024 16:43:48 +0100 Subject: [PATCH 10/26] Delete decentralized-messaging-ethereum.md --- vac/raw/decentralized-messaging-ethereum.md | 889 -------------------- 1 file changed, 889 deletions(-) delete mode 100644 vac/raw/decentralized-messaging-ethereum.md diff --git a/vac/raw/decentralized-messaging-ethereum.md b/vac/raw/decentralized-messaging-ethereum.md deleted file mode 100644 index a5b2af88..00000000 --- a/vac/raw/decentralized-messaging-ethereum.md +++ /dev/null @@ -1,889 +0,0 @@ ---- -title: VAC-DECENTRALIZED-MESSAGING-ETHEREUM -name: Decentralized Key and Session Setup for Secure Messaging over Ethereum -status: raw -category: informational -editor: Ramses Fernandez-Valencia -contributors: ---- - -## Abstract - -This document introduces a decentralized group messaging protocol -using Ethereum adresses as identifiers. -It is based in the proposal -[DCGKA](https://eprint.iacr.org/2020/1281) by Weidner et al. -It includes also approximations to overcome limitations related to using PKI and -the multi-device setting. - -## Motivation - -The need for secure communications has become paramount. -Traditional centralized messaging protocols are susceptible to various security -threats, including unauthorized access, data breaches, and single points of -failure. -Therefore a decentralized approach to secure communication becomes increasingly -relevant, offering a robust solution to address these challenges. - -Secure messaging protocols used should have the following key features: - -1. **Asynchronous Messaging:** Users can send messages even if the recipients -are not online at the moment. - -2. **Resilience to Compromise:** If a user's security is compromised, -the protocol ensures that previous messages remain secure through forward -secrecy (FS). This means that messages sent before the compromise cannot be -decrypted by adversaries. Additionally, the protocol maintains post-compromise -security (PCS) by regularly updating keys, making it difficult for adversaries -to decrypt future communication. - -3. **Dynamic Group Management:** Users can easily add or remove group members -at any time, reflecting the flexible nature of communication within the app. - -In this field, there exists a *trilemma*, similar to what one observes in -blockchain, involving three key aspects: - -1. security, -2. scalability, and -3. decentralization. - -For instance, protocols like the [MLS](https://messaginglayersecurity.rocks) -perform well in terms of scalability and security. -However, they falls short in decentralization. - -Newer studies such as [CoCoa](https://eprint.iacr.org/2022/251) -improve features related to security and scalability, -but they still rely on servers, which may not be fully trusted though they are necessary. - -On the other hand, -older studies like [Causal TreeKEM](https://mattweidner.com/assets/pdf/acs-dissertation.pdf) -exhibit decent scalability (logarithmic) -but lack forward secrecy and have weak post-compromise security (PCS). - -The creators of [DCGKA](https://eprint.iacr.org/2020/1281) introduce a decentralized, -asynchronous secure group messaging protocol that supports dynamic groups. -This protocol operates effectively on various underlying networks -without strict requirements on message ordering or latency. -It can be implemented in peer-to-peer or anonymity networks, -accommodating network partitions, high latency links, and -disconnected operation seamlessly. -Notably, the protocol doesn't rely on servers or -a consensus protocol for its functionality. - -This proposal provides end-to-end encryption with forward secrecy and -post-compromise security, -even when multiple users concurrently modify the group state. - -## Theory - -### Protocol overview - -This protocol makes use of ratchets to provide FS -by encrypting each message with a different key. - -In the figure one can see the ratchet for encrypting a sequence of messages. -The sender requires an initial update secret `I_1`, which is introduced in a PRG. -The PRG will produce two outputs, namely a symmetric key for AEAD encryption, and -a seed for the next ratchet state. -The associated data needed in the AEAD encryption includes the message index `i`. -The ciphertext `c_i` associated to message `m_i` -is then broadcasted to all group members. -The next step requires deleting `I_1`, `k_i` and any old ratchet state. - -After a period of time the sender may replace the ratchet state with new update secrets -`I_2`, `I_3`, and so on. - -To start a post-compromise security update, -a user creates a new random value known as a seed secret and -shares it with every other group member through a secure two-party channel. -Upon receiving the seed secret, -each group member uses it to calculate an update secret for both the sender's ratchet -and their own. -Additionally, the recipient sends an unencrypted acknowledgment to the group -confirming the update. -Every member who receives the acknowledgment updates -not only the ratchet for the original sender but -also the ratchet for the sender of the acknowledgment. -Consequently, after sharing the seed secret through `n - 1` two-party messages and -confirming it with `n - 1` broadcast acknowledgments, -every group member has derived an update secret and updated their ratchet accordingly. - -When removing a group member, -the user who initiates the removal conducts a post-compromise security update -by sending the update secret to all group members except the one being removed. -To add a new group member, -each existing group member shares the necessary state with the new user, -enabling them to derive their future update secrets. - -Since group members may receive messages in various orders, -it's important to ensure that each sender's ratchet is updated consistently -with the same sequence of update secrets at each group member. - -The network protocol used in this scheme ensures that messages from the same sender -are processed in the order they were sent. - -### Components of the protocol - -This protocol relies in 3 components: -authenticated causal broadcast (ACB), -decentralized group membership (DGM) and -2-party secure messaging (2SM). - -#### Authenticated causal broadcast - -A causal order is a partial order relation `<` on messages. -Two messages `m_1` and `m_2` are causally ordered, or -`m_1` causally precedes `m_2` -(denoted by `m_1 < m_2`), if one of the following contiditions hold: - -1. `m_1` and `m_2` were sent by the same group member, and -`m_1` was sent before `m_2`. -2. `m_2` was sent by a group member U, and `m_1` was received and -processed by `U` before sending `m_2`. -3. There exists `m_3` such that `m_1 < m_3` and `m_3 < m_2`. - -Causal broadcast requires that before processing `m`, a group member must -process all preceding messages `{m' | m' < m}`. - -The causal broadcast module used in this protocol authenticates the sender of -each message, as well as its causal ordering metadata, using a digital -signature under the sender’s identity key. -This prevents a passive adversary from impersonating users or affecting -causally ordered delivery. - -#### Decentralized group membership - -This protocol assumes the existence of a decentralized group membership -function (denoted as DGM) that takes a set of membership change messages and -their causal order relantionships, and returns the current set of group -members’ IDs. It needs to be deterministic and depend only on causal order, and -not exact order. - -#### 2-party secure messaging (2SM) - -This protocol makes use of bidirectional 2-party secure messaging schemes, -which consist of 3 algorithms: `2SM-Init`, `2SM-Send` and `2SM-Receive`. - -##### Function 2SM-Init - -This function takes two IDs as inputs: -`ID1` representing the local user and `ID2` representing the other party. -It returns an initial protocol state `sigma`. -The 2SM protocol relies on a Public Key Infrastructure (PKI) or -a key server to map these IDs to their corresponding public keys. -In practice, the PKI should incorporate ephemeral prekeys. -This allows users to send messages to a new group member, -even if that member is currently offline. - -##### Function 2SM-Send - -This function takes a state `sigma` and a plaintext `m` as inputs, and returns -a new state `sigma’` and a ciphertext `c`. - -##### Function 2SM-Receive - -This function takes a state `sigma` and a ciphertext `c`, and -returns a new state `sigma’` and a plaintext `m`. - -This function takes a state `sigma` and a ciphertext `c`, and returns a new -state `sigma’` and a plaintext `m`. - -#### Function 2SM Syntax - -The variable `sigma` denotes the state consisting in the variables below: - -```text -sigma.mySks[0] = sk -sigma.nextIndex = 1 -sigma.receivedSk = empty_string -sigma.otherPk = pk`
-sigma.otherPksender = “other” -sigma.otherPkIndex = 0 - -``` - -#### 2SM-Init - -On input a key pair `(sk, pk)`, this functions otuputs a state `sigma`. - -#### 2SM-Send - -This function encrypts the message `m` using `sigma.otherPk`, which represents -the other party’s current public key. -This key is determined based on the last public key generated for the other -party or the last public key received from the other party, -whichever is more recent. `sigma.otherPkSender` is set to `me` in the former -case and `other` in the latter case. - -Metadata including `otherPkSender` and `otherPkIndex` are included in the -message to indicate which of the recipient’s public keys is being utilized. - -Additionally, this function generates a new key pair for the local user, -storing the secret key in `sigma.mySks` and sending the public key. -Similarly, it generates a new key pair for the other party, -sending the secret key (encrypted) and storing the public key in -`sigma.otherPk`. - -```text -sigma.mySks[sigma.nextIndex], myNewPk) = PKE-Gen() -(otherNewSk, otherNewPk) = PKE-Gen() -plaintext = (m, otherNewSk, sigma`.nextIndex, myNewPk) -msg = (PKE-Enc(sigma.otherPk, plaintext), sigma.otherPkSender, sigma.otherPkIndex) -sigma.nextIndex++ -(sigma.otherPk, sigma.otherPkSender, sigma.otherPkIndex) = (otherNewPk, "me", empty_string) -return (sigma`, msg) - -``` - -#### 2SM-Receive - -This function utilizes the metadata of the message `c` to determine which -secret key to utilize for decryption, assigning it to `sk`. -If the secret key corresponds to one generated by ourselves, -that secret key along with all keys with lower index are deleted. -This deletion is indicated by `sigma.mySks[≤ keyIndex] = empty_string`. -Subsequently, the new public and secret keys contained in the message are -stored. - -```text -(ciphertext, keySender, keyIndex) = c -if keySender = "other" then -sk = sigma.mySks[keyIndex] -sigma.mySks[≤ keyIndex] = empty_string -else sk = sigma.receivedSk -(m, sigma.receivedSk, sigma.otherPkIndex, sigma.otherPk) = PKE-Dec(sk, ciphertext) -sigma.otherPkSender = "other" -return (sigma, m) - -``` - -### PKE Syntax - -The required PKE that MUST be used is ElGamal with a 2048-bit modulus `p`. - -#### Parameters - -The following parameters must be used: - -```text -p = 308920927247127345254346920820166145569 -g = 2 - -``` - -#### PKE-KGen - -Each user `u` MUST do the following: - -```text -PKE-KGen(): -a = randint(2, p-2) -pk = (p, g, g^a) -sk = a -return (pk, sk) - -``` - -#### PKE-Enc - -A user `v` encrypting a message `m` for `u` MUST follow these steps: - -```text -PKE-Enc(pk): -k = randint(2, p-2) -eta = g^k % p -delta = m * (g^a)^k % p -return ((eta, delta)) - -``` - -#### PKE-Dec - -The user `u` recovers a message `m` from a ciphertext `c` -by performing the following operations: - -```text -PKE-Dec(sk): -mu = eta^(p-1-sk) % p -return ((mu * delta) % p) - -``` - -### DCGKA Syntax - -#### Auxiliary functions - -There exist 6 functions that are auxiliary for the rest of components of the -protocol, namely: - -#### init - -This function takes an `ID` as input and returns its associated initial state, -denoted by `gamma`: - -```text -gamma.myId = ID -gamma.mySeq = 0 -gamma.history = empty -gamma.nextSeed = empty_string -gamma.2sm[·] = empty_string -gamma.memberSecret[·, ·, ·] = empty_string -gamma.ratchet[·] = empty_string -return (gamma) - -``` - -#### encrypt-to - -Upon reception of the recipient’s `ID` and a plaintext, it encrypts a direct -message for another group member. -Should it be the first message for a particular `ID`, -then the `2SM` protocol state is initialized and stored in -`gamma.2sm[recipient.ID]`. -One then uses `2SM_Send` to encrypt the message and store the updated protocol -in `gamma`. - -```text -if gamma.2sm[recipient_ID] = empty_string then - gamma.2sm[recipient_ID] = 2SM_Init(gamma.myID, recipient_ID) -(gamma.2sm[recipient_ID], ciphertext) = 2SM_Send(gamma.2sm[recipient_ID], plaintext) -return (gamma, ciphertext) - -``` - -#### decrypt-from - -After receiving the sender’s `ID` and a ciphertext, it behaves as the reverse -function of `encrypt-to` and has a similar initialization: - -```text -if gamma.2sm[sender_ID] = empty_string then -gamma.2sm[sender_ID] = 2SM_Init(gamma.myID, sender_ID) -(gamma.2sm[sender_ID], plaintext) = 2SM_Receive(gamma.2sm[sender_ID], ciphertext) -return (gamma, plaintext) - -``` - -#### update-ratchet - -This function generates the next update secret `I_update` for the group member -`ID`. -The ratchet state is stored in `gamma.ratchet[ID]`. -It is required to use a HMAC-based key derivation function HKDF to combine the -ratchet state with an input, returning an update secret and a new ratchet -state. - -```text -(updateSecret, gamma.ratchet[ID]) = HKDF(gamma.ratchet[ID], input) -return (gamma, updateSecret) - -``` - -#### member-view - -This function calculates the set of group members -based on the most recent control message sent by the specified user `ID`. -It filters the group membership operations -to include only those observed by the specified `ID`, and -then invokes the DGM function to generate the group membership. - -```text -ops = {m in gamma.history st. m was sent or acknowledged by ID} -return DGM(ops) - -``` - -#### generate-seed - -This functions generates a random bit string and -sends it encrypted to each member of the group using the `2SM` mechanism. -It returns the updated protocol state and -the set of direct messages (denoted as `dmsgs`) to send. - -```text -gamma.nextSeed = random.randbytes() -dmsgs = empty -for each ID in recipients: -(gamma, msg) = encrypt-to(gamma, ID, gamma.nextSeed) -dmsgs = dmsgs + (ID, msg) -return (gamma, dmsgs) - -``` - -### Creation of a group - -A group is generated in a 3 steps procedure: - -1. A user calls the `create` function and broadcasts a control message of type -*create*. -2. Each receiver of the message processes the message and broadcasts an *ack* -control message. -3. Each member processes the *ack* message received. - -#### create - -This function generates a *create* control message and calls `generate-seed` to -define the set of direct messages that need to be sent. -Then it calls `process-create` to process the control message for this user. -The function `process-create` returns a tuple including an updated state gamma -and an update secret `I`. - -```text -control = (“create”, gamma.mySeq, IDs) -(gamma, dmsgs) = generate-seed(gamma, IDs) -(gamma, _, _, I, _) = process-create(gamma, gamma.myId, gamma.mySeq, IDs, empty_string) -return (gamma, control, dmsgs, I) - -``` - -#### process-seed - -This function initially employs `member-view` to identify the users who were -part of the group when the control message was dispatched. -Then, it attempts to acquire the seed secret through the following steps: - -1. If the control message was dispatched by the local user, it uses the most -recent invocation of `generate-seed` stored the seed secret in -`gamma.nextSeed`. -2. If the `control` message was dispatched by another user, and the local user -is among its recipients, the function utilizes `decrypt-from` to decrypt the -direct message that includes the seed secret. -3. Otherwise, it returns an `ack` message without deriving an update secret. - -Afterwards, `process-seed` generates separate member secrets for each group -member from the seed secret by combining the seed secret and -each user ID using HKDF. -The secret for the sender of the message is stored in `senderSecret`, while -those for the other group members are stored in `gamma.memberSecret`. -The sender's member secret is immediately utilized to update their KDF ratchet -and compute their update secret `I_sender` using `update-ratchet`. -If the local user is the sender of the control message, the process is -completed, and the update secret is returned. -However, if the seed secret is received from another user, an `ack` control -message is constructed for broadcast, including the sender ID and sequence -number of the message being acknowledged. - -The final step computes an update secret `I_me` for the local user invoking the -`process-ack` function. - -```text -recipients = member-view(gamma, sender) - {sender} -if sender = gamma.myId then seed = gamma.nextSeed; gamma.nextSeed = -empty_string -else if gamma.myId in recipients then (gamma, seed) = decrypt-from(gamma, -sender, dmsg) -else -return (gamma, (ack, ++gamma.mySeq, (sender, seq)), empty_string , -empty_string , empty_string) - -for ID in recipients do gamma.memberSecret[sender, seq, ID] = HKDF(seed, ID) -senderSecret = HKDF(seed, sender) -(gamma, I_sender) = update-ratchet(gamma, sender, senderSecret) -if sender = gamma.myId then return (gamma, empty_string , empty_string , -I_sender, empty_string) -control = (ack, ++gamma.mySeq, (sender, seq)) -members = member-view(gamma, gamma.myId) -forward = empty -for ID in {members - (recipients + {sender})} - s = gamma.memberSecret[sender, seq, gamma.myId] - (gamma, msg) = encrypt-to(gamma, ID, s) - forward = forward + {(ID, msg)} - (gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq, - (sender, seq), empty_string) - return (gamma, control, forward, I_sender, I_me) - -``` - -#### process-create - -This function is called by the sender and each of the receivers of the `create` -control message. -First, it records the information from the create message in the -`gamma.history+ {op}`, which is used to track group membership changes. Then, -it proceeds to call `process-seed`. - -```text -op = (”create”, sender, seq, IDs) -gamma.history = gamma.history + {op} -return (process-seed(gamma, sender, seq, dmsg)) - -``` - -#### process-ack - -This function is called by those group members once they receive an ack -message. -In `process-ack`, `ackID` and `ackSeq` are the sender and sequence number of -the acknowledged message. -Firstly, if the acknowledged message is a group membership operation, it -records the acknowledgement in `gamma.history`. - -Following this, the function retrieves the relevant member secret from -`gamma.memberSecret`, which was previously obtained from the seed secret -contained in the acknowledged message. - -Finally, it updates the ratchet for the sender of the `ack` and returns the -resulting update secret. - -```text -if (ackID, ackSeq) was a create / add / remove then -op = ("ack", sender, seq, ackID, ackSeq) -gamma.history = gamma.history + {op}` -s = gamma.memberSecret[ackID, ackSeq, sender] -gamma.memberSecret[ackID, ackSeq, sender] = empty_string -if (s = empty_string) & (dmsg = empty_string) then return (gamma, empty_string, -empty_string, empty_string, empty_string) -if (s = empty_string) then (gamma, s) = decrypt-from(gamma, sender, dmsg) -(gamma, I) = update-ratchet(gamma, sender, s) -return (gamma, empty_string, empty_string, I, empty_string) - -``` - -The HKDF function MUST follow RFC 5869 using the hash function SHA256. - -### Post-compromise security updates and group member removal - -The functions `update` and `remove` share similarities with `create`: -they both call the function `generate-seed` to encrypt a new seed secret for -each group member. -The distinction lies in the determination of the group members using `member -view`. -In the case of `remove`, the user being removed is excluded from the recipients -of the seed secret. -Additionally, the control message they construct is designated with type -`update` or `remove` respectively. - -Likewise, `process-update` and `process-remove` are akin to `process-create`. -The function `process-update` skips the update of `gamma.history`, -whereas `process-remove` includes a removal operation in the history. - -#### update - -```text -control = ("update", ++gamma.mySeq, empty_string) -recipients = member-view(gamma, gamma.myId) - {gamma.myId} -(gamma, dmsgs) = generate-seed(gamma, recipients) -(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq, -empty_string, empty_string) -return (gamma, control, dmsgs, I) - -``` - -#### remove - -```text -control = ("remove", ++gamma.mySeq, empty) -recipients = member-view(gamma, gamma.myId) - {ID, gamma.myId} -(gamma, dmsgs) = generate-seed(gamma, recipients) -(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq, ID, -empty_string) -return (gamma, control, dmsgs, I) - -``` - -#### process-update - -`return process-seed(gamma, sender, seq, dmsg)` - -#### process-remove - -```text -op = ("remove", sender, seq, removed) -gamma.history = gamma.history + {op} -return process-seed(gamma, sender, seq, dmsg) - -``` - -### Group member addition - -#### add - -When adding a new group member, an existing member initiates the process by -invoking the `add` function and providing the ID of the user to be added. -This function prepares a control message marked as `add` for broadcast to the -group. Simultaneously, it creates a welcome message intended for the new member -as a direct message. -This `welcome` message includes the current state of the sender's KDF ratchet, -encrypted using `2SM`, along with the history of group membership operations -conducted so far. - -```text -control = ("add", ++gamma.mySeq, ID) -(gamma, c) = encrypt-to(gamma, ID, gamma.ratchet[gamma.myId]) -op = ("add", gamma.myId, gamma.mySeq, ID) -welcome = (gamma.history + {op}, c) -(gamma, _, _, I, _) = process-add(gamma, gamma.myId, gamma.mySeq, ID, empty_string) -return (gamma, control, (ID, welcome), I) - -``` - -#### process-add - -This function is invoked by both the sender and each recipient of an `add` -message, which includes the new group member. If the local user is the newly -added member, the function proceeds to call `process-welcome` and then exits. -Otherwise, it extends `gamma.history` with the `add` operation. - -Line 5 determines whether the local user was already a group member at the time -the `add` message was sent; this condition is typically true but may be false -if multiple users were added concurrently. - -On lines 6 to 8, the ratchet for the sender of the *add* message is updated -twice. In both calls to `update-ratchet`, a constant string is used as the -ratchet input instead of a random seed secret. - -The value returned by the first ratchet update is stored in -`gamma.memberSecret` as the added user’s initial member secret. The result of -the second ratchet update becomes `I_sender`, the update secret for the sender -of the `add` message. On line 10, if the local user is the sender, the update -secret is returned. - -If the local user is not the sender, an acknowledgment for the `add` message is -required. -Therefore, on line 11, a control message of type `add-ack` is constructed for -broadcast. -Subsequently, in line 12 the current ratchet state is encrypted using `2SM` to -generate a direct message intended for the added user, allowing them to decrypt -subsequent messages sent by the sender. -Finally, in lines 13 to 15, `process-add-ack` is called to calculate the local -user’s update secret (`I_me`), which is then returned along with `I_sender`. - -```text -if added = gamma.myId then return process-welcome(gamma, sender, seq, dmsg) -op = ("add", sender, seq, added) -gamma.history = gamma.history + {op} -if gamma.myId in member-view(gamma, sender) then - (gamma, s) = update-ratchet(gamma, sender, "welcome") - gamma.memberSecret[sender, seq, added] = s - (gamma, I_sender) = update-ratchet(gamma, sender, "add") - else I_sender = empty_string - if sender = gamma.myId then return (gamma, empty_string, empty_string, - I_sender, empty_string) - control = ("add-ack", ++gamma.mySeq, (sender, seq)) - (gamma, c) = encrypt-to(gamma, added, ratchet[gamma.myId]) - (gamma, _, _, I_me, _) = process-add-ack(gamma, gamma.myId, gamma.mySeq, - (sender, seq), empty_string) - return (gamma, control, {(added, c)}, I_sender, I_me) - -``` - -#### process-add-ack - -This function is invoked by both the sender and each recipient of an `add-ack` -message, including the new group member. Upon lines 1–2, the acknowledgment is -added to `gamma.history`, mirroring the process in `process-ack`. -If the current user is the new group member, the `add-ack` message includes the -direct message constructed in `process-add`; this direct message contains the -encrypted ratchet state of the sender of the `add-ack`, then it is decrypted on -lines 3–5. - -Upon line 6, a check is performed to check if the local user was already a -group member at the time the `add-ack` was sent. If affirmative, a new update -secret `I` for the sender of the `add-ack` is computed on line 7 by invoking -`update-ratchet` with the constant string `add`. - -In the scenario involving the new member, the ratchet state was recently -initialized on line 5. This ratchet update facilitates all group members, -including the new addition, to derive each member’s update by obtaining any -update secret from before their inclusion. - -```text -op = ("ack", sender, seq, ackID, ackSeq) -gamma$.history = gamma.history + {op} -if dmsg != empty_string then - (gamma, s) = decrypt-from(gamma, sender, dmsg) - gamma.ratchet[sender] = s -if gamma.myId in member-view(gamma, sender) then - (gamma, I) = update-ratchet(gamma, sender, "add") - return (gamma, empty_string, empty_string, I, empty_string) -else return (gamma, empty_string, empty_string, empty_string, empty_string) - -``` - -#### process-welcome - -This function serves as the second step called by a newly added group member. -In this context, `adderHistory` represents the adding user’s copy of -`gamma.history` sent in their welcome message, which is utilized to initialize -the added user’s history. -Here, `c` denotes the ciphertext of the adding user’s ratchet state, which is -decrypted on line 2 using `decrypt-from`. - -Once `gamma.ratchet[sender]` is initialized, `update-ratchet` is invoked twice -on lines 3 to 5 with the constant strings `welcome` and `add` respectively. -These operations mirror the ratchet operations performed by every other group -member in `process-add`. -The outcome of the first `update-ratchet` call becomes the first member secret -for the added user, -while the second call returns `I_sender`, the update secret for the sender of -the add operation. - -Subsequently, the new group member constructs an *ack* control message to -broadcast on line 6 and calls `process-ack` to compute their initial update -secret I_me. The function `process-ack` reads from `gamma.memberSecret` and -passes it to `update-ratchet`. The previous ratchet state for the new member is -the empty string `empty`, as established by `init`, thereby initializing the -new member’s ratchet. -Upon receiving the new member’s `ack`, every other group member initializes -their copy of the new member’s ratchet in a similar manner. - -By the conclusion of `process-welcome`, the new group member has acquired -update secrets for themselves and the user who added them. -The ratchets for other group members are initialized by `process-add-ack`. - -```text -gamma.history = adderHistory -(gamma, gamma.ratchet[sender]) = decrypt-from(gamma, sender, c) -(gamma, s) = update-ratchet(gamma, sender, "welcome") -gamma.memberSecret[sender, seq, gamma.myId] = s -(gamma, I_sender) = update-ratchet(gamma, sender, "add") -control = ("ack", ++gamma.mySeq, (sender, seq)) -(gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq, (sender, -seq), empty_string) -return (gamma, control, empty_string , I_sender, I_me) - -``` - -## Privacy Considerations - -### Dependency on PKI - -The [DCGKA](https://eprint.iacr.org/2020/1281) proposal presents some -limitations highlighted by the authors. -Among these limitations one finds the requirement of a PKI (or a key server) -mapping IDs to public keys. - -One method to overcome this limitation is adapting the protocol SIWE (Sign in -with Ethereum) so a user `u_1` who wants to start a communication with a user -`u_2` can interact with latter’s wallet to request a public key using an -Ethereum address as `ID`. - -#### SIWE - -The [SIWE](https://docs.login.xyz/general-information/siwe-overview) (Sign In -With Ethereum) proposal was a suggested standard for leveraging Ethereum to -authenticate and authorize users on web3 applications. -Its goal is to establish a standardized method for users to sign in to web3 -applications using their Ethereum address and private key, -mirroring the process by which users currently sign in to web2 applications -using their email and password. -Below follows the required steps: - -1. A server generates a unique Nonce for each user intending to sign in. -2. A user initiates a request to connect to a website using their wallet. -3. The user is presented with a distinctive message that includes the Nonce and -details about the website. -4. The user authenticates their identity by signing in with their wallet. -5. Upon successful authentication, the user's identity is confirmed or -approved. -6. The website grants access to data specific to the authenticated user. - -#### Our approach - -The idea in the [DCGKA](https://eprint.iacr.org/2020/1281) setting closely -resembles the procedure outlined in SIWE. Here: - -1. The server corresponds to user D1,who initiates a request (instead of -generating a nonce) to obtain the public key of user D2. -2. Upon receiving the request, the wallet of D2 send the request to the user, -3. User D2 receives the request from the wallet, and decides whether accepts or -rejects. -4. The wallet and responds with a message containing the requested public key -in case of acceptance by D2. - -This message may be signed, allowing D1 to verify that the owner of the -received public key is indeed D2. - -### Multi-device setting - -One may see the set of devices as a group and create a group key for internal -communications. -One may use treeKEM for instance, since it provides interesting properties like -forward secrecy and post-compromise security. -All devices share the same `ID`, which is held by one of them, and from other -user’s point of view, they would look as a single user. - -Using servers, like in the paper -[Multi-Device for Signal](https://eprint.iacr.org/2019/1363), should be -avoided; but this would imply using a particular device as receiver and -broadcaster within the group. -There is an obvious drawback which is having a single device working as a -“server”. Should this device be attacked or without connection, there should be -a mechanism for its revocation and replacement. - -Another approach for communications between devices could be using the keypair -of each device. This could open the door to use UPKE, since keypairs should be -regenerated frequently. - -Each time a device sends a message, either an internal message or an external -message, it needs to replicate and broadcast it to all devices in the group. - -The mechanism for the substitution of misbehaving leader devices follows: - -1. Each device within a group knows the details of other leader devices. This -information may come from metadata in received messages, and is replicated by -the leader device. -2. To replace a leader, the user should select any other device within its -group and use it to send a signed message to all other users. -3. To get the ability to sign messages, this new leader should request the -keypair associated to the ID to the wallet. -4. Once the leader has been changed, it revocates access from DCGKA to the -former leader using the DCGKA protocol. -5. The new leader starts a key update in DCGKA. - -Not all devices in a group should be able to send messages to other users. Only -the leader device should be in charge of sending and receiving messages. -To prevent other devices from sending messages outside their group, a -requirement should be signing each message. The keys associated to the `ID` -should only be in control of the leader device. - -The leader device is in charge of setting the keys involved in the DCGKA. This -information must be replicated within the group to make sure it is updated. - -To detect missing messages or potential misbehavior, messages must include a -counter. - -### Using UPKE - -Managing the group of devices of a user can be done either using a group key -protocol such as treeKEM or using the keypair of each device. -Setting a common key for a group of devices under the control of the same actor -might be excessive, furthermore it may imply some of the problems one can find -in the usual setting of a group of different users; -for example: one of the devices may not participate in the required updating -processes, representing a threat for the group. - -The other approach to managing the group of devices is using each device’s -keypair, but it would require each device updating these materia frequently, -something that may not happens. - -[UPKE](https://eprint.iacr.org/2022/068) is a form of asymetric cryptography -where any user can update any other user’s key pair by running an update -algorithm with (high-entropy) private coins. Any sender can initiate a *key -update* by sending a special update ciphertext. -This ciphertext updates the receiver’s public key and also, once processed by -the receiver, will update their secret key. - -To the best of my knowledge, there exists several efficient constructions both -[UPKE from ElGamal](https://eprint.iacr.org/2019/1189) (based in the DH -assumption) and [UPKE from Lattices]((https://eprint.iacr.org/2023/1400)) -(based in lattices). -None of them have been implemented in a secure messaging protocol, and this -opens the door to some novel research. - -## Copyright - -Copyright and related rights waived via -[CC0](https://creativecommons.org/publicdomain/zero/1.0/). - -## References - -- [DCGKA](https://eprint.iacr.org/2020/1281) -- [MLS](https://messaginglayersecurity.rocks) -- [CoCoa](https://eprint.iacr.org/2022/251) -- [Causal TreeKEM](https://mattweidner.com/assets/pdf/acs-dissertation.pdf) -- [SIWE](https://docs.login.xyz/general-information/siwe-overview) -- [Multi-device for Signal](https://eprint.iacr.org/2019/1363) -- [UPKE](https://eprint.iacr.org/2022/068) -- [UPKE from ElGamal](https://eprint.iacr.org/2019/1189) -- [UPKE from Lattices](https://eprint.iacr.org/2023/1400) From db758e582d3ffe88deb341c6e3846930164f912a Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:25:26 +0100 Subject: [PATCH 11/26] Create eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 1194 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1194 insertions(+) create mode 100644 vac/raw/eth-simplelogin.md diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md new file mode 100644 index 00000000..4da819cf --- /dev/null +++ b/vac/raw/eth-simplelogin.md @@ -0,0 +1,1194 @@ +--- +title: ETH-SIMPLELOGIN +name: SimpleLogin, a blockchain-based authentication system +status: raw +category: Standards Track +tags: an optional list of tags, not standard +editor: Ramses Fernandez +contributors: +--- + +# Abstract + + This document specifies SimpleLogin, a blockchain-based authentication + system designed to provide secure user registration, session + management, and enhanced security measures including an emergency + lockout mechanism with dual-signature verification. The system + facilitates 90-day sessions with refresh capabilities and supports + batch operations for scalability. + +# 1. Motivation + + With the increasing need for secure authentication mechanisms in + decentralized applications, SimpleLogin provides a blockchain-based + solution that ensures user authentication, session management, and + enhanced security features. It includes an emergency system to handle + lockouts and provides batch operations to enhance scalability. + It uses 90-day sessions with refresh capabilities and enhanced security measures. + +# 2. Terminology + + - **User**: An entity registered within the SimpleLogin system. + + - **Session**: A temporary interaction period between a user and the + system, allowing access to certain functionalities. + + - **Emergency Contact**: A designated user who can manage emergency + lockout procedures for another user. + + - **Nonce**: A number used once to prevent replay attacks in + cryptographic communication. + + - **Signature**: A cryptographic value that proves the authenticity of + a message. + + - **Multicall**: Batch operations that allow multiple actions to be + performed in a single transaction. + + - **IPFS Hash**: A hash used to reference data stored on the + InterPlanetary File System (IPFS). + +# 3. Architecture Overview + + SimpleLogin is a smart contract that resides on a blockchain network, + providing the following key functionalities: + + - **User Registration and Profile Management**: Users can register by + providing a profile hash stored on IPFS and update their profiles as + needed. + + - **Session Management**: Implements session creation, refreshing, and + termination with strict timeouts and inactivity limits. + + - **Security Measures**: Includes reentrancy guards, signature + verification using the secp256k1 curve, and activity tracking. + + - **Emergency System**: Allows users to set an emergency contact who + can enable an emergency lockout in case of security concerns. + + - **Batch Operations**: Supports batch processing of multiple + operations to enhance scalability and reduce transaction costs. + + The contract uses specific time durations for sessions, refresh + windows, inactivity periods, and lockout timeouts to manage user + interactions effectively. + + The system includes a comprehensive set of mappings and data + structures to track user registrations, sessions, nonces, profiles, + emergency contacts, and lockout states. + + The following sections detail the constants, data structures, + functions, and implementation specifics of SimpleLogin. + +# 4. Constants and Parameters + + The contract defines several constants used throughout the system: + + ```solidity + // Duration constants for session management + uint40 public constant SESSION_DURATION = 90 days; // Regular session length + uint40 public constant REFRESH_WINDOW = 14 days; // Time window when refresh is allowed + uint40 public constant MAX_INACTIVITY = 7 days; // Maximum allowed inactivity period + uint40 public constant SIGNIFICANT_INACTIVITY = 1 days; // Threshold for inactivity alerts + uint40 public constant DISABLE_REQUEST_TIMEOUT = 1 days; // Timeout for lockout disable requests + + // Constants for signature validation (secp256k1 curve parameters) + uint256 constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; + uint256 constant SECP256K1_HALF_N = SECP256K1_N / 2; + + // Constants for batch operations + uint256 public constant MAX_BATCH_SIZE = 50; // Maximum number of operations in a single batch + uint256 public constant MIN_BATCH_SIZE = 1; // Minimum number of operations required + + // Maximum number of session refreshes allowed + uint8 public constant MAX_REFRESHES = 5; + ``` + + These constants are critical for managing session durations, + inactivity thresholds, signature validations, and batch operation + constraints. + +# 5. Data Structures + + The contract uses several mappings and a struct to manage state: + + ```solidity + // Session information storing all relevant session data + struct Session { + bool isValid; // Current validity status + uint40 expiresAt; // Expiration timestamp + uint40 lastActivity; // Last activity timestamp + uint8 refreshCount; // Number of times refreshed + address owner; // Session owner address + string dataHash; // IPFS hash of session data + } + + // Main storage mappings + mapping(address => bool) public registered; // Tracks user registration status + mapping(address => uint256) public nonces; // Tracks user nonces for replay protection + mapping(address => string) public profiles; // Stores user profile IPFS hashes + mapping(bytes32 => Session) public sessions; // Maps session IDs to session data + mapping(address => bytes32) private lastSessionSalt; // Stores per-user salt for session ID generation + + // Emergency system storage + mapping(address => address) public emergencyContact; // Maps user to their emergency contact + mapping(address => bool) public emergencyLockout; // Tracks if user is in emergency lockout + mapping(address => bool) public isEmergencyContact; // Tracks addresses that serve as emergency contacts + mapping(address => bytes32) public pendingDisableLockouts; // Tracks pending disable requests + mapping(address => uint40) public disableLockoutTimestamps; // Tracks when each disable request was made + ``` + + The `Session` struct is optimized for storage efficiency, with careful + consideration of data types and struct packing. + +# 6. Function Definitions + + This section details all the functions implemented in the SimpleLogin + contract, including their purpose, parameters, and internal processes. + +## 6.1. Registration and Profile Management + +### 6.1.1. `register` + + **Description**: Allows a new user to register by providing a profile + IPFS hash. + + **Function Definition**: + + ```solidity + function register(string calldata profileHash) external nonReentrant { + require(!registered[msg.sender], "SimpleLogin: already registered"); + require(bytes(profileHash).length > 0, "SimpleLogin: empty profile hash"); + + registered[msg.sender] = true; + profiles[msg.sender] = profileHash; + + emit UserRegistered(msg.sender); + } + ``` + + **Parameters**: + + - `profileHash`: The IPFS hash of the user's profile data. + + **Process**: + + 1. Checks if the caller is already registered. + 2. Validates that the `profileHash` is not empty. + 3. Marks the user as registered and stores the profile hash. + 4. Emits the `UserRegistered` event. + +### 6.1.2. `updateProfile` + + **Description**: Allows a registered user to update their profile. + + **Function Definition**: + + ```solidity + function updateProfile(string calldata newProfileHash) external { + require(registered[msg.sender], "SimpleLogin: not registered"); + require(bytes(newProfileHash).length > 0, "SimpleLogin: empty profile hash"); + require(!emergencyLockout[msg.sender], "SimpleLogin: emergency lockout active"); + + profiles[msg.sender] = newProfileHash; + emit UserProfileUpdated(msg.sender, newProfileHash); + } + ``` + + **Parameters**: + + - `newProfileHash`: The new IPFS hash of the user's profile data. + + **Process**: + + 1. Validates that the caller is registered and not under emergency lockout. + 2. Ensures the `newProfileHash` is not empty. + 3. Updates the user's profile hash. + 4. Emits the `UserProfileUpdated` event. + +## 6.2. Emergency Contact Management + +### 6.2.1. `setEmergencyContact` + + **Description**: Allows a user to designate an emergency contact. + + **Function Definition**: + + ```solidity + function setEmergencyContact(address contact) external { + require(contact != address(0), "SimpleLogin: invalid contact"); + require(contact != msg.sender, "SimpleLogin: cannot be own contact"); + require(registered[contact], "SimpleLogin: contact not registered"); + + if (emergencyContact[msg.sender] != address(0)) { + isEmergencyContact[emergencyContact[msg.sender]] = false; + } + + emergencyContact[msg.sender] = contact; + isEmergencyContact[contact] = true; + + emit EmergencyContactSet(msg.sender, contact); + } + ``` + + **Parameters**: + + - `contact`: The address of the emergency contact. + + **Process**: + + 1. Validates the `contact` address. + 2. Checks that the contact is registered and not the caller themselves. + 3. Updates the emergency contact mappings. + 4. Emits the `EmergencyContactSet` event. + +### 6.2.2. `removeEmergencyContact` + + **Description**: Allows a user to remove their emergency contact. + + **Function Definition**: + + ```solidity + function removeEmergencyContact() external { + require(emergencyContact[msg.sender] != address(0), "SimpleLogin: no contact set"); + require(!emergencyLockout[msg.sender], "SimpleLogin: emergency lockout active"); + + address oldContact = emergencyContact[msg.sender]; + isEmergencyContact[oldContact] = false; + emergencyContact[msg.sender] = address(0); + + emit EmergencyContactSet(msg.sender, address(0)); + } + ``` + + **Process**: + + 1. Checks that the caller has an emergency contact set. + 2. Validates that the caller is not under emergency lockout. + 3. Removes the emergency contact and updates mappings. + 4. Emits the `EmergencyContactSet` event with a null address. + +## 6.3. Emergency Lockout System + +### 6.3.1. `enableEmergencyLockout` + + **Description**: Allows an emergency contact to enable an emergency + lockout for a user. + + **Function Definition**: + + ```solidity + function enableEmergencyLockout(address user) external { + require(emergencyContact[user] == msg.sender, "SimpleLogin: not emergency contact"); + require(!emergencyLockout[user], "SimpleLogin: lockout already active"); + + emergencyLockout[user] = true; + emit EmergencyLockoutEnabled(user, msg.sender); + } + ``` + + **Parameters**: + + - `user`: The address of the user to lock out. + + **Process**: + + 1. Validates that the caller is the emergency contact of the user. + 2. Checks that the user is not already under lockout. + 3. Sets the `emergencyLockout` flag for the user. + 4. Emits the `EmergencyLockoutEnabled` event. + +### 6.3.2. `initiateDisableLockout` + + **Description**: Allows a user under emergency lockout to initiate a + request to disable the lockout. + + **Function Definition**: + + ```solidity + function initiateDisableLockout(bytes calldata userSignature) external { + require(emergencyLockout[msg.sender], "SimpleLogin: no lockout active"); + + bytes32 message = keccak256(abi.encodePacked( + "Disable emergency lockout", + msg.sender, + block.timestamp + )); + + require(verify(message, userSignature), "SimpleLogin: invalid user signature"); + + bytes32 requestId = keccak256(abi.encodePacked(msg.sender, block.timestamp)); + pendingDisableLockouts[msg.sender] = requestId; + disableLockoutTimestamps[msg.sender] = uint40(block.timestamp); + + emit EmergencyLockoutDisableRequested(msg.sender, requestId); + } + ``` + + **Parameters**: + + - `userSignature`: The user's signature authorizing the disable request. + + **Process**: + + 1. Validates that the caller is under emergency lockout. + 2. Creates a message for signature verification. + 3. Verifies the user's signature. + 4. Generates a `requestId` and stores it with a timestamp. + 5. Emits the `EmergencyLockoutDisableRequested` event. + +### 6.3.3. `confirmDisableLockout` + + **Description**: Allows the emergency contact to confirm and disable + the emergency lockout. + + **Function Definition**: + + ```solidity + function confirmDisableLockout( + address user, + bytes32 requestId, + bytes calldata emergencyContactSignature + ) external { + require(emergencyContact[user] == msg.sender, "SimpleLogin: not emergency contact"); + require(pendingDisableLockouts[user] == requestId, "SimpleLogin: invalid or no request"); + + bytes32 message = keccak256(abi.encodePacked( + "Confirm disable lockout", + user, + requestId + )); + + require(verify(message, emergencyContactSignature), "SimpleLogin: invalid contact signature"); + + emergencyLockout[user] = false; + delete pendingDisableLockouts[user]; + delete disableLockoutTimestamps[user]; + + emit EmergencyLockoutDisabled(user, msg.sender); + } + ``` + + **Parameters**: + + - `user`: The address of the user under lockout. + - `requestId`: The identifier of the disable request. + - `emergencyContactSignature`: Signature from the emergency contact. + + **Process**: + + 1. Validates that the caller is the emergency contact of the user. + 2. Checks that there is a valid pending disable request. + 3. Verifies the emergency contact's signature. + 4. Disables the emergency lockout and clears pending requests. + 5. Emits the `EmergencyLockoutDisabled` event. + +### 6.3.4. `cancelDisableLockout` + + **Description**: Allows a user to cancel a pending disable lockout request. + + **Function Definition**: + + ```solidity + function cancelDisableLockout() external { + require(pendingDisableLockouts[msg.sender] != bytes32(0), "SimpleLogin: no pending request"); + delete pendingDisableLockouts[msg.sender]; + delete disableLockoutTimestamps[msg.sender]; + } + ``` + + **Process**: + + 1. Checks that there is a pending disable request. + 2. Deletes the request and timestamp from storage. + +## 6.4. Session Management + +### 6.4.1. `createSession` + + **Description**: Allows a registered user to create a new session. + + **Function Definition**: + + ```solidity + function createSession(bytes calldata signature, string calldata dataHash) + external + nonReentrant + returns (bytes32) + { + require(registered[msg.sender], "SimpleLogin: not registered"); + require(!emergencyLockout[msg.sender], "SimpleLogin: emergency lockout active"); + require(bytes(dataHash).length > 0, "SimpleLogin: empty data hash"); + + bytes32 message = keccak256(abi.encode(msg.sender, nonces[msg.sender])); + require(verify(message, signature), "SimpleLogin: invalid signature"); + + bytes32 sessionId = generateSessionId(msg.sender, nonces[msg.sender]); + + uint40 currentTime = uint40(block.timestamp); + sessions[sessionId] = Session({ + isValid: true, + expiresAt: currentTime + SESSION_DURATION, + lastActivity: currentTime, + refreshCount: 0, + owner: msg.sender, + dataHash: dataHash + }); + + nonces[msg.sender]++; + + emit NewSessionCreated(msg.sender, sessionId, dataHash); + return sessionId; + } + ``` + + **Parameters**: + + - `signature`: The user's signature to verify the session creation request. + - `dataHash`: The IPFS hash of the session data. + + **Process**: + + 1. Validates that the caller is registered and not under emergency lockout. + 2. Checks that `dataHash` is not empty. + 3. Creates a message and verifies the user's signature using the current nonce. + 4. Generates a unique `sessionId`. + 5. Creates a new `Session` struct and stores it. + 6. Increments the user's nonce. + 7. Emits the `NewSessionCreated` event. + +### 6.4.2. `refreshSession` + + **Description**: Allows the session owner to refresh the session's expiration. + + **Function Definition**: + + ```solidity + function refreshSession(bytes32 sessionId) external nonReentrant { + Session storage session = sessions[sessionId]; + + require(session.owner == msg.sender, "SimpleLogin: not session owner"); + require(session.isValid, "SimpleLogin: invalid session"); + require(uint40(block.timestamp) <= session.expiresAt, "SimpleLogin: session expired"); + require(!emergencyLockout[msg.sender], "SimpleLogin: emergency lockout active"); + require( + session.expiresAt - uint40(block.timestamp) <= REFRESH_WINDOW, + "SimpleLogin: too early to refresh" + ); + require(session.refreshCount < MAX_REFRESHES, "SimpleLogin: max refreshes exceeded"); + + uint40 currentTime = uint40(block.timestamp); + session.expiresAt = currentTime + SESSION_DURATION; + session.lastActivity = currentTime; + session.refreshCount++; + + emit SessionRefreshed(sessionId, session.expiresAt); + } + ``` + + **Parameters**: + + - `sessionId`: The identifier of the session to refresh. + + **Process**: + + 1. Retrieves the session and validates ownership. + 2. Checks that the session is valid and not expired. + 3. Ensures it is within the `REFRESH_WINDOW` and `MAX_REFRESHES` has not been exceeded. + 4. Updates `expiresAt`, `lastActivity`, and increments `refreshCount`. + 5. Emits the `SessionRefreshed` event. + +### 6.4.3. `endSession` + + **Description**: Allows a session owner or their emergency contact to invalidate a session. + + **Function Definition**: + + ```solidity + function endSession(bytes32 sessionId) external nonReentrant { + Session storage session = sessions[sessionId]; + + require( + session.owner == msg.sender || emergencyContact[session.owner] == msg.sender, + "SimpleLogin: not authorized" + ); + require(session.isValid, "SimpleLogin: invalid session"); + + session.isValid = false; + session.lastActivity = uint40(block.timestamp); + + emit SessionInvalidated(sessionId); + } + ``` + + **Parameters**: + + - `sessionId`: The identifier of the session to terminate. + + **Process**: + + 1. Validates that the caller is authorized. + 2. Checks that the session is valid. + 3. Sets `isValid` to `false` and updates `lastActivity`. + 4. Emits the `SessionInvalidated` event. + +### 6.4.4. `isSessionValid` + + **Description**: Checks whether a session is currently valid. + + **Function Definition**: + + ```solidity + function isSessionValid(bytes32 sessionId) external view returns (bool) { + Session memory session = sessions[sessionId]; + uint40 currentTime = uint40(block.timestamp); + + return session.isValid && + currentTime <= session.expiresAt && + currentTime <= session.lastActivity + MAX_INACTIVITY && + session.owner == msg.sender && + !emergencyLockout[session.owner]; + } + ``` + + **Parameters**: + + - `sessionId`: The identifier of the session to validate. + + **Process**: + + 1. Retrieves the session data. + 2. Validates session state, expiration, inactivity, ownership, and lockout status. + 3. Returns a boolean indicating validity. + +### 6.4.5. `getSessionData` + + **Description**: Retrieves the session data hash after validating the session. + + **Function Definition**: + + ```solidity + function getSessionData(bytes32 sessionId) external returns (string memory) { + Session storage session = sessions[sessionId]; + uint40 currentTime = uint40(block.timestamp); + + require(session.owner == msg.sender, "SimpleLogin: not session owner"); + require(session.isValid, "SimpleLogin: invalid session"); + require(currentTime <= session.expiresAt, "SimpleLogin: session expired"); + require(!emergencyLockout[session.owner], "SimpleLogin: emergency lockout active"); + require( + currentTime <= session.lastActivity + MAX_INACTIVITY, + "SimpleLogin: session inactive" + ); + + _updateActivity(sessionId); + return session.dataHash; + } + ``` + + **Parameters**: + + - `sessionId`: The identifier of the session. + + **Process**: + + 1. Validates session ownership, validity, expiration, and inactivity. + 2. Updates the session's activity timestamp. + 3. Returns the `dataHash`. + +### 6.4.6. `getSessionDetails` + + **Description**: Retrieves detailed information about a session. + + **Function Definition**: + + ```solidity + function getSessionDetails(bytes32 sessionId) external view returns ( + bool isValid, + uint40 expiresAt, + uint40 lastActivity, + string memory dataHash, + address owner, + uint8 refreshCount + ) { + Session memory session = sessions[sessionId]; + require( + session.owner == msg.sender || + emergencyContact[session.owner] == msg.sender, + "SimpleLogin: not authorized" + ); + + return ( + session.isValid, + session.expiresAt, + session.lastActivity, + session.dataHash, + session.owner, + session.refreshCount + ); + } + ``` + + **Parameters**: + + - `sessionId`: The identifier of the session. + + **Process**: + + 1. Validates that the caller is authorized. + 2. Returns detailed session information. + +### 6.4.7. `_updateActivity` + + **Description**: Internal function to update the session's last activity timestamp. + + **Function Definition**: + + ```solidity + function _updateActivity(bytes32 sessionId) internal { + Session storage session = sessions[sessionId]; + uint40 currentTime = uint40(block.timestamp); + uint40 inactivePeriod = currentTime - session.lastActivity; + session.lastActivity = currentTime; + + emit ActivityUpdated(sessionId, currentTime); + + if(inactivePeriod > SIGNIFICANT_INACTIVITY) { + emit SignificantInactivity(sessionId, inactivePeriod); + } + } + ``` + + **Process**: + + 1. Calculates the inactive period. + 2. Updates the `lastActivity` timestamp. + 3. Emits `ActivityUpdated` and possibly `SignificantInactivity` events. + +## 6.5. Batch Operations (Multicall) + +### 6.5.1. `createMultipleSessions` + + **Description**: Allows creating multiple sessions in a single transaction. + + **Function Definition**: + + ```solidity + function createMultipleSessions( + address[] calldata users, + bytes[] calldata signatures, + string[] calldata dataHashes + ) external nonReentrant returns (bytes32[] memory) { + require( + users.length == signatures.length && + signatures.length == dataHashes.length, + "SimpleLogin: array length mismatch" + ); + + require(users.length >= MIN_BATCH_SIZE, "SimpleLogin: batch too small"); + require(users.length <= MAX_BATCH_SIZE, "SimpleLogin: batch too large"); + + bytes32[] memory sessionIds = new bytes32[](users.length); + + for(uint i = 0; i < users.length; i++) { + require(registered[users[i]], "SimpleLogin: user not registered"); + require(!emergencyLockout[users[i]], "SimpleLogin: emergency lockout active"); + require(bytes(dataHashes[i]).length > 0, "SimpleLogin: empty data hash"); + + bytes32 message = keccak256(abi.encode(users[i], nonces[users[i]])); + bytes memory signature = signatures[i]; + + require( + recoverSigner(message, signature) == users[i], + "SimpleLogin: invalid signature" + ); + + sessionIds[i] = generateSessionId(users[i], nonces[users[i]]); + + uint40 currentTime = uint40(block.timestamp); + sessions[sessionIds[i]] = Session({ + isValid: true, + expiresAt: currentTime + SESSION_DURATION, + lastActivity: currentTime, + refreshCount: 0, + owner: users[i], + dataHash: dataHashes[i] + }); + + nonces[users[i]]++; + + emit NewSessionCreated(users[i], sessionIds[i], dataHashes[i]); + } + + return sessionIds; + } + ``` + + **Parameters**: + + - `users`: Array of user addresses. + - `signatures`: Array of signatures for each user. + - `dataHashes`: Array of IPFS hashes for session data. + + **Process**: + + 1. Validates array lengths and batch size. + 2. Iterates over each user to perform session creation steps. + 3. Verifies signatures and increments nonces. + 4. Emits `NewSessionCreated` events. + 5. Returns an array of `sessionIds`. + +### 6.5.2. `endMultipleSessions` + + **Description**: Ends multiple sessions in a single transaction. + + **Function Definition**: + + ```solidity + function endMultipleSessions(bytes32[] calldata sessionIds) external nonReentrant { + require(sessionIds.length >= MIN_BATCH_SIZE, "SimpleLogin: batch too small"); + require(sessionIds.length <= MAX_BATCH_SIZE, "SimpleLogin: batch too large"); + + for(uint i = 0; i < sessionIds.length; i++) { + Session storage session = sessions[sessionIds[i]]; + + require( + session.owner == msg.sender || emergencyContact[session.owner] == msg.sender, + "SimpleLogin: not authorized" + ); + require(session.isValid, "SimpleLogin: invalid session"); + + session.isValid = false; + session.lastActivity = uint40(block.timestamp); + + emit SessionInvalidated(sessionIds[i]); + } + } + ``` + + **Parameters**: + + - `sessionIds`: Array of session identifiers to terminate. + + **Process**: + + 1. Validates batch size. + 2. Iterates over each `sessionId`. + 3. Validates authorization and session validity. + 4. Sets `isValid` to `false` and updates `lastActivity`. + 5. Emits `SessionInvalidated` events. + +### 6.5.3. `updateMultipleProfiles` + + **Description**: Updates profiles for multiple users in a single transaction. + + **Function Definition**: + + ```solidity + function updateMultipleProfiles( + address[] calldata users, + string[] calldata newProfileHashes + ) external nonReentrant { + require( + users.length == newProfileHashes.length, + "SimpleLogin: array length mismatch" + ); + + require(users.length >= MIN_BATCH_SIZE, "SimpleLogin: batch too small"); + require(users.length <= MAX_BATCH_SIZE, "SimpleLogin: batch too large"); + + for(uint i = 0; i < users.length; i++) { + require(users[i] == msg.sender, "SimpleLogin: not authorized"); + require(registered[users[i]], "SimpleLogin: not registered"); + require(!emergencyLockout[users[i]], "SimpleLogin: emergency lockout active"); + require(bytes(newProfileHashes[i]).length > 0, "SimpleLogin: empty profile hash"); + + profiles[users[i]] = newProfileHashes[i]; + + emit UserProfileUpdated(users[i], newProfileHashes[i]); + } + } + ``` + + **Parameters**: + + - `users`: Array of user addresses. + - `newProfileHashes`: Array of new IPFS profile hashes. + + **Process**: + + 1. Validates array lengths and batch size. + 2. Iterates over each user. + 3. Validates authorization and status. + 4. Updates profiles and emits events. + +### 6.5.4. `enableMultipleEmergencyLockouts` + + **Description**: Allows an emergency contact to enable lockouts for multiple users. + + **Function Definition**: + + ```solidity + function enableMultipleEmergencyLockouts( + address[] calldata users + ) external nonReentrant { + require(users.length >= MIN_BATCH_SIZE, "SimpleLogin: batch too small"); + require(users.length <= MAX_BATCH_SIZE, "SimpleLogin: batch too large"); + + for(uint i = 0; i < users.length; i++) { + require( + emergencyContact[users[i]] == msg.sender, + "SimpleLogin: not emergency contact" + ); + require(!emergencyLockout[users[i]], "SimpleLogin: lockout already active"); + + emergencyLockout[users[i]] = true; + + emit EmergencyLockoutEnabled(users[i], msg.sender); + } + } + ``` + + **Parameters**: + + - `users`: Array of user addresses to lock out. + + **Process**: + + 1. Validates batch size. + 2. Iterates over each user. + 3. Validates that the caller is the emergency contact. + 4. Sets `emergencyLockout` to `true`. + 5. Emits `EmergencyLockoutEnabled` events. + +## 6.6. Signature Verification + +### 6.6.1. `verify` + + **Description**: Verifies the authenticity of a message signed by a user. + + **Function Definition**: + + ```solidity + function verify(bytes32 message, bytes memory signature) internal view returns (bool) { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); + + if (uint256(r) == 0 || uint256(r) >= SECP256K1_N) return false; + if (uint256(s) == 0 || uint256(s) > SECP256K1_HALF_N) return false; + if (v != 27 && v != 28) return false; + + address recovered = ecrecover( + keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", message) + ), + v, + r, + s + ); + + return recovered == msg.sender; + } + ``` + + **Parameters**: + + - `message`: The hashed message that was signed. + - `signature`: The signature bytes. + + **Process**: + + 1. Splits the signature into `r`, `s`, and `v` components. + 2. Validates `r`, `s`, and `v` according to secp256k1 standards. + 3. Recovers the signer's address. + 4. Compares the recovered address with `msg.sender`. + +### 6.6.2. `splitSignature` + + **Description**: Splits a signature into its components. + + **Function Definition**: + + ```solidity + function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { + require(sig.length == 65, "SimpleLogin: invalid signature length"); + + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := byte(0, mload(add(sig, 96))) + } + + if (v < 27) v += 27; + + return (r, s, v); + } + ``` + + **Parameters**: + + - `sig`: The signature bytes. + + **Process**: + + 1. Validates the length of the signature. + 2. Extracts `r`, `s`, and `v` components. + 3. Adjusts `v` if necessary. + +### 6.6.3. `recoverSigner` + + **Description**: Helper function for signature recovery in batch operations. + + **Function Definition**: + + ```solidity + function recoverSigner( + bytes32 message, + bytes memory signature + ) internal pure returns (address) { + bytes32 prefixedMessage = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", message) + ); + + (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); + + require(uint256(s) <= SECP256K1_HALF_N, "SimpleLogin: invalid signature 's' value"); + require(v == 27 || v == 28, "SimpleLogin: invalid signature 'v' value"); + + address recovered = ecrecover(prefixedMessage, v, r, s); + require(recovered != address(0), "SimpleLogin: invalid signature"); + + return recovered; + } + ``` + + **Parameters**: + + - `message`: The hashed message. + - `signature`: The signature bytes. + + **Process**: + + 1. Creates a prefixed message. + 2. Splits the signature. + 3. Validates `s` and `v` values. + 4. Recovers the signer's address. + +## 6.7. View Functions + +### 6.7.1. `getProfile` + + **Description**: Retrieves the profile hash of a registered user. + + **Function Definition**: + + ```solidity + function getProfile(address user) external view returns (string memory) { + require(registered[user], "SimpleLogin: user not registered"); + return profiles[user]; + } + ``` + + **Parameters**: + + - `user`: The address of the user. + + **Process**: + + 1. Validates that the user is registered. + 2. Returns the profile hash. + +### 6.7.2. `getUserNonce` + + **Description**: Retrieves the current nonce of a user. + + **Function Definition**: + + ```solidity + function getUserNonce(address user) external view returns (uint256) { + return nonces[user]; + } + ``` + + **Parameters**: + + - `user`: The address of the user. + + **Process**: + + 1. Returns the user's nonce. + +### 6.7.3. `isDisableRequestValid` + + **Description**: Checks if a pending disable lockout request is still valid. + + **Function Definition**: + + ```solidity + function isDisableRequestValid(address user) external view returns (bool) { + bytes32 requestId = pendingDisableLockouts[user]; + if (requestId == bytes32(0)) return false; + + uint40 requestTimestamp = disableLockoutTimestamps[user]; + uint40 currentTime = uint40(block.timestamp); + return currentTime <= requestTimestamp + DISABLE_REQUEST_TIMEOUT; + } + ``` + + **Parameters**: + + - `user`: The address of the user. + + **Process**: + + 1. Checks if there is a pending request. + 2. Validates the request based on the timestamp and timeout. + +7. Implementation Details + +## 7.1. Reentrancy Guard + + The contract uses a `nonReentrant` modifier to prevent reentrant calls + to functions that modify critical state. + + **Modifier Definition**: + + ```solidity + modifier nonReentrant() { + require(!_locked, "SimpleLogin: reentrant call"); + _locked = true; + _; + _locked = false; + } + ``` + + **Process**: + + 1. Checks if the `_locked` state is `false`. + 2. Sets `_locked` to `true` before function execution. + 3. Resets `_locked` to `false` after execution. + +## 7.2. Session ID Generation + + **Function**: `generateSessionId` + + **Description**: Generates a unique session identifier using entropy + from the blockchain and user-specific data. + + **Function Definition**: + + ```solidity + function generateSessionId(address user, uint256 nonce) private returns (bytes32) { + bytes32 blockEntropy = blockhash(block.number - 1); + + bytes32 prevSalt = lastSessionSalt[user]; + if (prevSalt == bytes32(0)) { + prevSalt = keccak256( + abi.encodePacked( + block.timestamp, + block.prevrandao, + block.coinbase, + user + ) + ); + } + + bytes32 newSalt = keccak256( + abi.encodePacked( + prevSalt, + blockEntropy, + block.timestamp, + block.prevrandao + ) + ); + + lastSessionSalt[user] = newSalt; + + return keccak256( + abi.encodePacked( + user, + nonce, + block.timestamp, + blockEntropy, + newSalt, + block.prevrandao, + block.coinbase + ) + ); + } + ``` + + **Process**: + + 1. Retrieves entropy from the blockchain. + 2. Uses a per-user salt updated with each session creation. + 3. Combines user address, nonce, timestamps, and entropy to generate a unique `sessionId`. + +## 7.3. Nonce Management + + Nonces are used to prevent replay attacks during signature verification. + + - Each user has an associated nonce stored in the `nonces` mapping. + - Nonces increment with each session creation. + +## 7.4. Event Logging + + The contract emits events to facilitate monitoring and logging of key actions: + + - `UserRegistered(address indexed user);` + - `NewSessionCreated(address indexed user, bytes32 indexed sessionId, string dataHash);` + - `SessionInvalidated(bytes32 indexed sessionId);` + - `SessionRefreshed(bytes32 indexed sessionId, uint40 newExpiry);` + - `EmergencyContactSet(address indexed user, address indexed contact);` + - `EmergencyLockoutEnabled(address indexed user, address indexed triggeredBy);` + - `EmergencyLockoutDisabled(address indexed user, address indexed emergencyContact);` + - `EmergencyLockoutDisableRequested(address indexed user, bytes32 requestId);` + - `SignificantInactivity(bytes32 indexed sessionId, uint40 inactivePeriod);` + - `ActivityUpdated(bytes32 indexed sessionId, uint40 timestamp);` + - `UserProfileUpdated(address indexed user, string newProfileHash);` + +# 8. Security Considerations + + - **Replay Protection**: Uses nonces to prevent replay attacks during + session creation and emergency procedures. + + - **Signature Validation**: Ensures that signatures conform to + secp256k1 standards and are correctly verified. + + - **Emergency Lockout**: Provides a mechanism for users to secure their + accounts in case of compromise, with dual-signature verification for + disabling lockouts. + + - **Reentrancy Guard**: Protects against reentrancy attacks by using a + `nonReentrant` modifier on critical functions. + + - **Session Inactivity**: Monitors inactivity and invalidates sessions + that exceed `MAX_INACTIVITY`. + + - **Entropy Sources**: Uses blockchain-provided entropy sources like + `blockhash` and `block.prevrandao` for generating session IDs, but + acknowledges the limitations and potential predictability. + + - **Access Control**: Validates that only authorized users can perform + certain actions, such as ending sessions or updating profiles. + + - **Batch Operations Risks**: Ensures that batch operations do not + exceed predefined sizes to prevent denial-of-service through large + transactions. + + - **Time Casting**: Safely casts block timestamps to `uint40` to + prevent overflows, given that `uint40` can represent dates up to the + year 36812. + +# 9. References + + - **[EIP-155]**: Ethereum Improvement Proposal 155 - Simple replay + attack protection. + + - **[SECP256K1]**: Standards for Efficient Cryptography Group, + "Recommended Elliptic Curve Domain Parameters", 2010. + + - **[Solidity]**: Solidity Documentation - + + + - **[IPFS]**: InterPlanetary File System - + \ No newline at end of file From 68f53f511fc6a51c732a82c2a2aceb98fdbdbbdb Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:31:08 +0100 Subject: [PATCH 12/26] Update eth-demls.md --- vac/raw/eth-demls.md | 434 ++----------------------------------------- 1 file changed, 12 insertions(+), 422 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 945eab58..945684cc 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1012,431 +1012,21 @@ Since smart contracts cannot generate random nonces without the need of an oracl or can only generate pseudo-random nonces which would be the result of combining data which could be hacked. -The following approach aims at mitigating these issues. -It is similar to SIWE, -and would imply the users requesting to log into the system -to create the nonce by themselves, sign it, -and then send it to the smart contract for verification. -This makes the approach easier to implement -and deploy when compared to the above MPC description. - -In order to avoid security issues like replay attacks and DDoS, -one needs to have the following aspects in mind: - -1. Nonce uniqueness: this can be guaranteed tracking used nonces for each signature. -This would prevent replay attacks as it provides proofs -that a nonce was used only once by a particular address. -2. Temporal uniqueness: timestamps allow checkings to prevent the reuse of old signatures. -3. Message uniqueness: including the sender’s address in the message hash -ensures that messages are unique even if two users collide in the nonce and the timestamp. -4. Group membership: one could keep a separate mapping -to track who is allowed to authenticate. -5. Admin role: the creator of the smart contract would have an admin role, -allowing the dynamic management of group membership. - -## Pseudocode - -### SIWE Message Structure +While SIWE provides a robust foundation for Ethereum-based authentication, +it presents certain challenges in a decentralized context, +particularly regarding nonce generation through smart contracts. +The need for oracles or the reliance on deterministic pseudo-random nonces +derived from on-chain data introduces potential security vulnerabilities. +Additionally, implementing SIWE in a fully decentralized environment +requires careful consideration of centralization risks and extra infrastructure. -```text -STRUCTURE SIWEMessage: - domain: STRING # The domain requesting the signing - address: ADDRESS # The user's Ethereum address - uri: STRING # The URI of the dApp - chainId: INTEGER # The ID of the blockchain network - issuedAt: TIMESTAMP # When the message was issued - expirationTime: TIMESTAMP # When the message expires - purpose: STRING # New field for unique contextual data -``` - -### Smart Contract functions - -This contract handles authentication, group management, and session management - -```text -CLASS EthereumStyleAuthWithTimeWindow: - # State variables - groupMembers: MAP # Stores group membership status - admin: ADDRESS # The address of the contract administrator - lastAuthTime: MAP # Tracks last authentication time for each user - sessions: MAP> # Stores session expiration times - sessionHashes: MAP> # Maps session IDs to IPFS hashes -``` - -```text - # State variables for failed attempt tracking - failedAttempts: MAP - lastFailedAttemptTime: MAP - - # Constants for time-validity window - CONSTANT MIN_VALIDITY_PERIOD = 30 seconds # Tightened validity window - CONSTANT MAX_VALIDITY_PERIOD = 2 minutes # Adjusted as needed - CONSTANT MIN_AUTH_INTERVAL = 30 seconds # Keep as is or adjust as needed - - # Constants for failed attempt limits - CONSTANT MAX_FAILED_ATTEMPTS = 5 - CONSTANT LOCKOUT_DURATION = 15 minutes -``` - -```text - # Constructor: Initializes the contract state - CONSTRUCTOR(): - groupMembers = EMPTY_MAP() - admin = TRANSACTION_SENDER() # Set the contract deployer as admin - groupMembers[admin] = TRUE # Add admin to the group - allowedPurposes = EMPTY_MAP() - allowedPurposes["Authenticate to create session"] = TRUE - EMIT GroupMemberAdded(admin) -``` - -```text - # Adds a new member to the group (admin only) - FUNCTION addGroupMember(memberAddress: ADDRESS): - REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") - REQUIRE(groupMembers[memberAddress] != TRUE, "Address is already a group member") - REQUIRE(memberAddress != NULL_ADDRESS, "Invalid address") - groupMembers[memberAddress] = TRUE - EMIT GroupMemberAdded(memberAddress) -``` - -```text - # Removes a member from the group (admin only) - FUNCTION removeGroupMember(memberAddress: ADDRESS): - REQUIRE(TRANSACTION_SENDER() == admin, "Only admin can perform this action") - REQUIRE(memberAddress != admin, "Admin cannot be removed from the group") - REQUIRE(groupMembers[memberAddress] == TRUE, "Address is not a group member") - REQUIRE(memberAddress != NULL_ADDRESS, "Invalid address") - groupMembers[memberAddress] = FALSE - EMIT GroupMemberRemoved(memberAddress) -``` - -```text - # Transfers admin rights to a new address - FUNCTION transferAdmin(newAdminAddress: ADDRESS): - REQUIRE(TRANSACTION_SENDER() == admin, "Only current admin can transfer admin rights") - REQUIRE(newAdminAddress != NULL_ADDRESS, "Invalid address") - REQUIRE(groupMembers[newAdminAddress] == TRUE, "New admin must be a group member") - EMIT AdminTransferred(admin, newAdminAddress) - admin = newAdminAddress -``` - -```text - # Authenticates a user based on their SIWE message and signature - FUNCTION authenticate( - domain: STRING, - address: ADDRESS, - uri: STRING, - chainId: INTEGER, - issuedAt: TIMESTAMP, - expirationTime: TIMESTAMP, - purpose: STRING, - signature: BYTES - ): - signer = TRANSACTION_SENDER() - currentTime = CURRENT_TIMESTAMP() - - # Check if the user is in a lockout period - IF failedAttempts[signer] >= MAX_FAILED_ATTEMPTS: - lockoutEndTime = lastFailedAttemptTime[signer] + LOCKOUT_DURATION - IF currentTime < lockoutEndTime: - EMIT AuthenticationFailed(signer, "Account locked due to too many failed attempts") - RETURN FALSE - ELSE: - # Reset failed attempts after lockout duration - failedAttempts[signer] = 0 - - # Perform authentication checks - IF groupMembers[signer] != TRUE: - recordFailedAttempt(signer, currentTime, "Not a group member") - RETURN FALSE - - IF currentTime < issuedAt: - recordFailedAttempt(signer, currentTime, "Message not yet valid") - RETURN FALSE - - IF currentTime > expirationTime: - recordFailedAttempt(signer, currentTime, "Message has expired") - RETURN FALSE - - IF expirationTime - issuedAt < MIN_VALIDITY_PERIOD: - recordFailedAttempt(signer, currentTime, "Validity period too short") - RETURN FALSE - - IF expirationTime - issuedAt > MAX_VALIDITY_PERIOD: - recordFailedAttempt(signer, currentTime, "Validity period too long") - RETURN FALSE - - IF chainId != CURRENT_CHAIN_ID(): - recordFailedAttempt(signer, currentTime, "Invalid chain ID") - RETURN FALSE - - IF address != signer: - recordFailedAttempt(signer, currentTime, "Address mismatch") - RETURN FALSE - - IF currentTime - lastAuthTime[signer] < MIN_AUTH_INTERVAL: - recordFailedAttempt(signer, currentTime, "Authentication too frequent") - RETURN FALSE - - # Reconstruct and verify the message - message = SIWEMessage(domain, address, uri, chainId, issuedAt, expirationTime, purpose) - messageHash = HASH_STRUCTURED_DATA(message) - expectedSigner = RECOVER_SIGNER(messageHash, signature) - IF expectedSigner != signer: - recordFailedAttempt(signer, currentTime, "Invalid signature") - RETURN FALSE - - # Verify the purpose or unique contextual data - IF !verifyPurpose(purpose): - recordFailedAttempt(signer, currentTime, "Invalid purpose") - RETURN FALSE - - lastAuthTime[signer] = currentTime # Update last authentication time - failedAttempts[signer] = 0 # Reset failed attempts on success - EMIT AuthenticationSuccessful(signer, purpose) - RETURN TRUE -``` - -```text - # Helper function to record failed authentication attempts - FUNCTION recordFailedAttempt(signer: ADDRESS, currentTime: TIMESTAMP, reason: STRING): - failedAttempts[signer] = failedAttempts[signer] + 1 - lastFailedAttemptTime[signer] = currentTime - EMIT AuthenticationFailed(signer, reason) -``` - -```text - FUNCTION verifyPurpose(purpose: STRING) RETURNS (BOOLEAN): - # Define the expected purpose - expectedPurpose = "Authenticate to create session" - # Check if the provided purpose matches the expected purpose - RETURN purpose == expectedPurpose -``` - -### Session management functions - -```text - # Creates a new session for an authenticated user - FUNCTION createSession(sessionId: BYTES32, expirationBlock: INTEGER, ipfsHash: STRING): - REQUIRE(groupMembers[TRANSACTION_SENDER()] == TRUE, "Not a group member") - REQUIRE(expirationBlock > CURRENT_BLOCK_NUMBER(), "Expiration block must be in the future") - REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] == NULL, "Session ID already exists for this user") - - sessions[TRANSACTION_SENDER()][sessionId] = expirationBlock - sessionHashes[TRANSACTION_SENDER()][sessionId] = ipfsHash - EMIT SessionCreated(TRANSACTION_SENDER(), sessionId, expirationBlock) -``` - -```text - # Retrieves the IPFS hash for a given session ID - FUNCTION getSessionHash(sessionId: BYTES32) VIEW RETURNS (STRING): - expirationBlock = sessions[TRANSACTION_SENDER()][sessionId] - REQUIRE(expirationBlock != NULL, "Session does not exist") - REQUIRE(expirationBlock > CURRENT_BLOCK_NUMBER(), "Session has expired") - - RETURN sessionHashes[TRANSACTION_SENDER()][sessionId] -``` - -```text - # Extends the expiration time of an existing session - FUNCTION extendSession(sessionId: BYTES32, newExpirationBlock: INTEGER): - REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") - REQUIRE(newExpirationBlock > CURRENT_BLOCK_NUMBER(), "New expiration block must be in the future") - sessions[TRANSACTION_SENDER()][sessionId] = newExpirationBlock - EMIT SessionExtended(TRANSACTION_SENDER(), sessionId, newExpirationBlock) -``` - -```text - # Ends a session, removing it from storage - FUNCTION endSession(sessionId: BYTES32): - REQUIRE(sessions[TRANSACTION_SENDER()][sessionId] != NULL, "Session does not exist") - DELETE sessions[TRANSACTION_SENDER()][sessionId] - DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] - EMIT SessionEnded(TRANSACTION_SENDER(), sessionId) -``` +[SimpleLogin](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md) provides a blockchain-based authentication system +specifically designed for decentralized applications. -```text - # Cleans up expired sessions for the calling user - FUNCTION cleanUpExpiredSessions(): - FOR EACH sessionId IN sessions[TRANSACTION_SENDER()]: - expirationBlock = sessions[TRANSACTION_SENDER()][sessionId] - IF expirationBlock <= CURRENT_BLOCK_NUMBER(): - DELETE sessions[TRANSACTION_SENDER()][sessionId] - DELETE sessionHashes[TRANSACTION_SENDER()][sessionId] - EMIT SessionsCleanedUp(TRANSACTION_SENDER()) -``` - -### Client side functions - -```text -# Generates a new SIWE message for authentication -FUNCTION generateSIWEMessage(userAddress: ADDRESS): - domain = "example.com" - uri = "https://example.com/login" - issuedAt = GET_CURRENT_TIMESTAMP() - expirationTime = issuedAt + 1 minute # Tight validity window - chainId = GET_CURRENT_CHAIN_ID() - purpose = "Authenticate to create session" # Unique contextual data - RETURN NEW SIWEMessage( - domain, userAddress, uri, chainId, issuedAt, expirationTime, purpose) -``` - -```text -# Signs a SIWE message with the user's private key -FUNCTION signSIWEMessage(message: SIWEMessage, userAddress: ADDRESS): - messageHash = HASH_STRUCTURED_DATA(message) - signature = SIGN_MESSAGE_HASH(messageHash, userAddress) - RETURN signature -``` - -```text -# Main authentication function that calls the smart contract -FUNCTION authenticate(): - userAddress = GET_CURRENT_USER_ADDRESS() - message = generateSIWEMessage(userAddress) - signature = signSIWEMessage(message, userAddress) - - authResult = CALL contract.authenticate( - message.domain, - message.address, - message.uri, - message.chainId, - message.issuedAt, - message.expirationTime, - message.purpose, # Pass the purpose to the contract - signature - ) AS userAddress - - IF authResult == TRUE: - sessionId = createAndStoreSession(userAddress) - RETURN "Authentication and session creation successful" - ELSE: - RETURN "Authentication failed" -``` +SimpleLogin manages user registration, session handling, +and emergency security measures. -```text -# Constants or configuration parameters -AVERAGE_BLOCK_TIME = 12 # in seconds -SESSION_DURATION_IN_SECONDS = 3600 # 1 hour -SESSION_DURATION_IN_BLOCKS = ROUND(SESSION_DURATION_IN_SECONDS / AVERAGE_BLOCK_TIME) # 300 blocks -``` - -```text -# Creates and stores a new session after successful authentication -FUNCTION createAndStoreSession(userAddress: ADDRESS): - sessionData = { - "userAddress": userAddress, - "loginTime": CURRENT_TIMESTAMP(), - # Add any other relevant session data - } - RANDOM_VALUE = GENERATE_RANDOM_BYTES32() - sessionId = KECCAK256(userAddress + CURRENT_TIMESTAMP() + RANDOM_VALUE) - expirationBlock = GET_CURRENT_BLOCK_NUMBER() + SESSION_DURATION_IN_BLOCKS - encryptedData = encryptSessionData(sessionData, USER_PUBLIC_KEY) - - ipfsHash = IPFS_ADD(encryptedData) - - CALL contract.createSession(sessionId, expirationBlock, ipfsHash) AS userAddress - - STORE_LOCALLY(sessionId) - RETURN sessionId -``` - -```text -# Restores a user's session using the stored session ID -FUNCTION restoreUserSession(): - storedSessionId = RETRIEVE_LOCALLY_STORED_SESSION_ID() - IF storedSessionId != NULL: - TRY: - ipfsHash = CALL contract.getSessionHash(storedSessionId) AS userAddress - encryptedData = IPFS_GET(ipfsHash) - sessionData = decryptSessionData(encryptedData, USER_PRIVATE_KEY) - APPLY_SESSION_DATA(sessionData) - RETURN "Session restored successfully" - CATCH: - RETURN "Session expired or invalid" - ELSE: - RETURN "No stored session found" -``` - -```text -# Encrypts session data for secure storage - FUNCTION encryptSessionData(sessionData: OBJECT, userPrivateKey: PRIVATE_KEY): - # Derive a symmetric key from the user's private key or generate a new one - symmetricKey = DERIVE_SYMMETRIC_KEY(userPrivateKey) - - # Serialize the session data to a JSON string - serializedData = JSON.stringify(sessionData) - - # Encrypt the serialized data using the symmetric key - encryptedData = SYMMETRIC_ENCRYPT(serializedData, symmetricKey) - RETURN encryptedData -``` - -```text -# Decrypts session data retrieved from storage - FUNCTION decryptSessionData(encryptedData: STRING, userPrivateKey: PRIVATE_KEY): - # Derive the symmetric key using the user's private key - symmetricKey = DERIVE_SYMMETRIC_KEY(userPrivateKey) - - # Decrypt the data using the symmetric key - decryptedData = SYMMETRIC_DECRYPT(encryptedData, symmetricKey) - - # Deserialize the JSON string back into an object - sessionData = JSON.parse(decryptedData) - RETURN sessionData -``` - -```text -# Derives a symmetric encryption key from the user's private key - FUNCTION DERIVE_SYMMETRIC_KEY(privateKey: PRIVATE_KEY) RETURNS (SYMMETRIC_KEY): - # Use a key derivation function (KDF) with a salt to derive a symmetric key - salt = FIXED_SALT_OR_USER_SPECIFIC_SALT - symmetricKey = KDF(privateKey, salt) - RETURN symmetricKey -``` - -```text -# Symmetric encryption using AES or a similar algorithm - FUNCTION SYMMETRIC_ENCRYPT(data: STRING, key: SYMMETRIC_KEY) RETURNS (STRING): - # Encrypt the data using the symmetric key - encryptedData = AES_ENCRYPT(data, key) - RETURN encryptedData -``` - -```text -# Symmetric decryption - FUNCTION SYMMETRIC_DECRYPT(encryptedData: STRING, key: SYMMETRIC_KEY) RETURNS (STRING): - # Decrypt the data using the symmetric key - decryptedData = AES_DECRYPT(encryptedData, key) - RETURN decryptedData -``` - -```text -# Helper functions which hashes the SIWE message according to EIP-712 standards -FUNCTION HASH_STRUCTURED_DATA(message: SIWEMessage): - RETURN KECCAK256(ENCODED_STRUCTURED_DATA(message)) -``` - -```text -# Helper functions which recovers the signer's address from a message hash and signature -FUNCTION RECOVER_SIGNER(messageHash: BYTES32, signature: BYTES): - RETURN ECRECOVER(messageHash, signature) -``` - -### Events - -```text -EVENT AuthenticationSuccessful(user: ADDRESS, purpose: STRING) -EVENT GroupMemberAdded(member: ADDRESS) -EVENT GroupMemberRemoved(member: ADDRESS) -EVENT AdminTransferred(oldAdmin: ADDRESS, newAdmin: ADDRESS) -EVENT SessionCreated(user: ADDRESS, sessionId: BYTES32, expirationBlock: INTEGER) -EVENT SessionExtended(user: ADDRESS, sessionId: BYTES32, newExpirationBlock: INTEGER) -EVENT SessionEnded(user: ADDRESS, sessionId: BYTES32) -EVENT SessionsCleanedUp(user: ADDRESS) -EVENT AuthenticationFailed(user: ADDRESS, reason: STRING) -``` +User registration and authentication MUST be implemented following the specification described [here](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md). ## Privacy and Security Considerations From 22cb88c46560440207ddce40fd31cacd586549f5 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:45:09 +0100 Subject: [PATCH 13/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 460 ++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 232 deletions(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index 4da819cf..c9773b97 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -8,7 +8,7 @@ editor: Ramses Fernandez contributors: --- -# Abstract +## Abstract This document specifies SimpleLogin, a blockchain-based authentication system designed to provide secure user registration, session @@ -17,7 +17,7 @@ contributors: facilitates 90-day sessions with refresh capabilities and supports batch operations for scalability. -# 1. Motivation +## Motivation With the increasing need for secure authentication mechanisms in decentralized applications, SimpleLogin provides a blockchain-based @@ -26,61 +26,61 @@ contributors: lockouts and provides batch operations to enhance scalability. It uses 90-day sessions with refresh capabilities and enhanced security measures. -# 2. Terminology +## Terminology - - **User**: An entity registered within the SimpleLogin system. +- **User**: An entity registered within the SimpleLogin system. - - **Session**: A temporary interaction period between a user and the - system, allowing access to certain functionalities. +- **Session**: A temporary interaction period between a user and the system, +allowing access to certain functionalities. - - **Emergency Contact**: A designated user who can manage emergency - lockout procedures for another user. +- **Emergency Contact**: A designated user who can manage +emergency lockout procedures for another user. - - **Nonce**: A number used once to prevent replay attacks in - cryptographic communication. +- **Nonce**: A number used once to prevent replay attacks in +cryptographic communication. - - **Signature**: A cryptographic value that proves the authenticity of - a message. +- **Signature**: A cryptographic value that proves the authenticity of +a message. - - **Multicall**: Batch operations that allow multiple actions to be - performed in a single transaction. +- **Multicall**: Batch operations that allow multiple actions to be +performed in a single transaction. - - **IPFS Hash**: A hash used to reference data stored on the - InterPlanetary File System (IPFS). +- **IPFS Hash**: A hash used to reference data stored on the +InterPlanetary File System (IPFS). -# 3. Architecture Overview +## Architecture Overview SimpleLogin is a smart contract that resides on a blockchain network, providing the following key functionalities: - - **User Registration and Profile Management**: Users can register by - providing a profile hash stored on IPFS and update their profiles as - needed. +- **User Registration and Profile Management**: Users can register by +providing a profile hash stored on IPFS and update their profiles as +needed. - - **Session Management**: Implements session creation, refreshing, and - termination with strict timeouts and inactivity limits. +- **Session Management**: Implements session creation, refreshing, and +termination with strict timeouts and inactivity limits. - - **Security Measures**: Includes reentrancy guards, signature - verification using the secp256k1 curve, and activity tracking. +- **Security Measures**: Includes reentrancy guards, signature +verification using the secp256k1 curve, and activity tracking. - - **Emergency System**: Allows users to set an emergency contact who - can enable an emergency lockout in case of security concerns. +- **Emergency System**: Allows users to set an emergency contact who +can enable an emergency lockout in case of security concerns. - - **Batch Operations**: Supports batch processing of multiple - operations to enhance scalability and reduce transaction costs. +- **Batch Operations**: Supports batch processing of multiple +operations to enhance scalability and reduce transaction costs. - The contract uses specific time durations for sessions, refresh - windows, inactivity periods, and lockout timeouts to manage user - interactions effectively. +The contract uses specific time durations for sessions, refresh +windows, inactivity periods, and lockout timeouts to manage user +interactions effectively. - The system includes a comprehensive set of mappings and data - structures to track user registrations, sessions, nonces, profiles, - emergency contacts, and lockout states. +The system includes a comprehensive set of mappings and data +structures to track user registrations, sessions, nonces, profiles, +emergency contacts, and lockout states. - The following sections detail the constants, data structures, - functions, and implementation specifics of SimpleLogin. +The following sections detail the constants, data structures, +functions, and implementation specifics of SimpleLogin. -# 4. Constants and Parameters +## Constants and Parameters The contract defines several constants used throughout the system: @@ -108,7 +108,7 @@ contributors: inactivity thresholds, signature validations, and batch operation constraints. -# 5. Data Structures +## Data Structures The contract uses several mappings and a struct to manage state: @@ -141,14 +141,14 @@ contributors: The `Session` struct is optimized for storage efficiency, with careful consideration of data types and struct packing. -# 6. Function Definitions +## Function Definitions This section details all the functions implemented in the SimpleLogin contract, including their purpose, parameters, and internal processes. -## 6.1. Registration and Profile Management +### Registration and Profile Management -### 6.1.1. `register` +#### `register` **Description**: Allows a new user to register by providing a profile IPFS hash. @@ -169,16 +169,16 @@ contributors: **Parameters**: - - `profileHash`: The IPFS hash of the user's profile data. +- `profileHash`: The IPFS hash of the user's profile data. **Process**: - 1. Checks if the caller is already registered. - 2. Validates that the `profileHash` is not empty. - 3. Marks the user as registered and stores the profile hash. - 4. Emits the `UserRegistered` event. +1. Checks if the caller is already registered. +2. Validates that the `profileHash` is not empty. +3. Marks the user as registered and stores the profile hash. +4. Emits the `UserRegistered` event. -### 6.1.2. `updateProfile` +#### `updateProfile` **Description**: Allows a registered user to update their profile. @@ -201,14 +201,14 @@ contributors: **Process**: - 1. Validates that the caller is registered and not under emergency lockout. - 2. Ensures the `newProfileHash` is not empty. - 3. Updates the user's profile hash. - 4. Emits the `UserProfileUpdated` event. +1. Validates that the caller is registered and not under emergency lockout. +2. Ensures the `newProfileHash` is not empty. +3. Updates the user's profile hash. +4. Emits the `UserProfileUpdated` event. -## 6.2. Emergency Contact Management +### Emergency Contact Management -### 6.2.1. `setEmergencyContact` +#### `setEmergencyContact` **Description**: Allows a user to designate an emergency contact. @@ -237,12 +237,12 @@ contributors: **Process**: - 1. Validates the `contact` address. - 2. Checks that the contact is registered and not the caller themselves. - 3. Updates the emergency contact mappings. - 4. Emits the `EmergencyContactSet` event. +1. Validates the `contact` address. +2. Checks that the contact is registered and not the caller themselves. +3. Updates the emergency contact mappings. +4. Emits the `EmergencyContactSet` event. -### 6.2.2. `removeEmergencyContact` +#### `removeEmergencyContact` **Description**: Allows a user to remove their emergency contact. @@ -263,14 +263,14 @@ contributors: **Process**: - 1. Checks that the caller has an emergency contact set. - 2. Validates that the caller is not under emergency lockout. - 3. Removes the emergency contact and updates mappings. - 4. Emits the `EmergencyContactSet` event with a null address. +1. Checks that the caller has an emergency contact set. +2. Validates that the caller is not under emergency lockout. +3. Removes the emergency contact and updates mappings. +4. Emits the `EmergencyContactSet` event with a null address. -## 6.3. Emergency Lockout System +### Emergency Lockout System -### 6.3.1. `enableEmergencyLockout` +#### `enableEmergencyLockout` **Description**: Allows an emergency contact to enable an emergency lockout for a user. @@ -293,12 +293,12 @@ contributors: **Process**: - 1. Validates that the caller is the emergency contact of the user. - 2. Checks that the user is not already under lockout. - 3. Sets the `emergencyLockout` flag for the user. - 4. Emits the `EmergencyLockoutEnabled` event. +1. Validates that the caller is the emergency contact of the user. +2. Checks that the user is not already under lockout. +3. Sets the `emergencyLockout` flag for the user. +4. Emits the `EmergencyLockoutEnabled` event. -### 6.3.2. `initiateDisableLockout` +#### `initiateDisableLockout` **Description**: Allows a user under emergency lockout to initiate a request to disable the lockout. @@ -331,13 +331,13 @@ contributors: **Process**: - 1. Validates that the caller is under emergency lockout. - 2. Creates a message for signature verification. - 3. Verifies the user's signature. - 4. Generates a `requestId` and stores it with a timestamp. - 5. Emits the `EmergencyLockoutDisableRequested` event. +1. Validates that the caller is under emergency lockout. +2. Creates a message for signature verification. +3. Verifies the user's signature. +4. Generates a `requestId` and stores it with a timestamp. +5. Emits the `EmergencyLockoutDisableRequested` event. -### 6.3.3. `confirmDisableLockout` +#### `confirmDisableLockout` **Description**: Allows the emergency contact to confirm and disable the emergency lockout. @@ -371,19 +371,19 @@ contributors: **Parameters**: - - `user`: The address of the user under lockout. - - `requestId`: The identifier of the disable request. - - `emergencyContactSignature`: Signature from the emergency contact. +- `user`: The address of the user under lockout. +- `requestId`: The identifier of the disable request. +- `emergencyContactSignature`: Signature from the emergency contact. **Process**: - 1. Validates that the caller is the emergency contact of the user. - 2. Checks that there is a valid pending disable request. - 3. Verifies the emergency contact's signature. - 4. Disables the emergency lockout and clears pending requests. - 5. Emits the `EmergencyLockoutDisabled` event. +1. Validates that the caller is the emergency contact of the user. +2. Checks that there is a valid pending disable request. +3. Verifies the emergency contact's signature. +4. Disables the emergency lockout and clears pending requests. +5. Emits the `EmergencyLockoutDisabled` event. -### 6.3.4. `cancelDisableLockout` +#### `cancelDisableLockout` **Description**: Allows a user to cancel a pending disable lockout request. @@ -399,12 +399,12 @@ contributors: **Process**: - 1. Checks that there is a pending disable request. - 2. Deletes the request and timestamp from storage. +1. Checks that there is a pending disable request. +2. Deletes the request and timestamp from storage. -## 6.4. Session Management +### Session Management -### 6.4.1. `createSession` +#### `createSession` **Description**: Allows a registered user to create a new session. @@ -449,15 +449,15 @@ contributors: **Process**: - 1. Validates that the caller is registered and not under emergency lockout. - 2. Checks that `dataHash` is not empty. - 3. Creates a message and verifies the user's signature using the current nonce. - 4. Generates a unique `sessionId`. - 5. Creates a new `Session` struct and stores it. - 6. Increments the user's nonce. - 7. Emits the `NewSessionCreated` event. +1. Validates that the caller is registered and not under emergency lockout. +2. Checks that `dataHash` is not empty. +3. Creates a message and verifies the user's signature using the current nonce. +4. Generates a unique `sessionId`. +5. Creates a new `Session` struct and stores it. +6. Increments the user's nonce. +7. Emits the `NewSessionCreated` event. -### 6.4.2. `refreshSession` +#### `refreshSession` **Description**: Allows the session owner to refresh the session's expiration. @@ -492,13 +492,13 @@ contributors: **Process**: - 1. Retrieves the session and validates ownership. - 2. Checks that the session is valid and not expired. - 3. Ensures it is within the `REFRESH_WINDOW` and `MAX_REFRESHES` has not been exceeded. - 4. Updates `expiresAt`, `lastActivity`, and increments `refreshCount`. - 5. Emits the `SessionRefreshed` event. +1. Retrieves the session and validates ownership. +2. Checks that the session is valid and not expired. +3. Ensures it is within the `REFRESH_WINDOW` and `MAX_REFRESHES` has not been exceeded. +4. Updates `expiresAt`, `lastActivity`, and increments `refreshCount`. +5. Emits the `SessionRefreshed` event. -### 6.4.3. `endSession` +#### `endSession` **Description**: Allows a session owner or their emergency contact to invalidate a session. @@ -527,12 +527,12 @@ contributors: **Process**: - 1. Validates that the caller is authorized. - 2. Checks that the session is valid. - 3. Sets `isValid` to `false` and updates `lastActivity`. - 4. Emits the `SessionInvalidated` event. +1. Validates that the caller is authorized. +2. Checks that the session is valid. +3. Sets `isValid` to `false` and updates `lastActivity`. +4. Emits the `SessionInvalidated` event. -### 6.4.4. `isSessionValid` +#### `isSessionValid` **Description**: Checks whether a session is currently valid. @@ -557,11 +557,11 @@ contributors: **Process**: - 1. Retrieves the session data. - 2. Validates session state, expiration, inactivity, ownership, and lockout status. - 3. Returns a boolean indicating validity. +1. Retrieves the session data. +2. Validates session state, expiration, inactivity, ownership, and lockout status. +3. Returns a boolean indicating validity. -### 6.4.5. `getSessionData` +#### `getSessionData` **Description**: Retrieves the session data hash after validating the session. @@ -592,11 +592,11 @@ contributors: **Process**: - 1. Validates session ownership, validity, expiration, and inactivity. - 2. Updates the session's activity timestamp. - 3. Returns the `dataHash`. +1. Validates session ownership, validity, expiration, and inactivity. +2. Updates the session's activity timestamp. +3. Returns the `dataHash`. -### 6.4.6. `getSessionDetails` +#### `getSessionDetails` **Description**: Retrieves detailed information about a session. @@ -635,10 +635,10 @@ contributors: **Process**: - 1. Validates that the caller is authorized. - 2. Returns detailed session information. +1. Validates that the caller is authorized. +2. Returns detailed session information. -### 6.4.7. `_updateActivity` +#### `_updateActivity` **Description**: Internal function to update the session's last activity timestamp. @@ -661,13 +661,13 @@ contributors: **Process**: - 1. Calculates the inactive period. - 2. Updates the `lastActivity` timestamp. - 3. Emits `ActivityUpdated` and possibly `SignificantInactivity` events. +1. Calculates the inactive period. +2. Updates the `lastActivity` timestamp. +3. Emits `ActivityUpdated` and possibly `SignificantInactivity` events. -## 6.5. Batch Operations (Multicall) +### Batch Operations (Multicall) -### 6.5.1. `createMultipleSessions` +#### `createMultipleSessions` **Description**: Allows creating multiple sessions in a single transaction. @@ -732,13 +732,13 @@ contributors: **Process**: - 1. Validates array lengths and batch size. - 2. Iterates over each user to perform session creation steps. - 3. Verifies signatures and increments nonces. - 4. Emits `NewSessionCreated` events. - 5. Returns an array of `sessionIds`. +1. Validates array lengths and batch size. +2. Iterates over each user to perform session creation steps. +3. Verifies signatures and increments nonces. +4. Emits `NewSessionCreated` events. +5. Returns an array of `sessionIds`. -### 6.5.2. `endMultipleSessions` +#### `endMultipleSessions` **Description**: Ends multiple sessions in a single transaction. @@ -772,13 +772,13 @@ contributors: **Process**: - 1. Validates batch size. - 2. Iterates over each `sessionId`. - 3. Validates authorization and session validity. - 4. Sets `isValid` to `false` and updates `lastActivity`. - 5. Emits `SessionInvalidated` events. +1. Validates batch size. +2. Iterates over each `sessionId`. +3. Validates authorization and session validity. +4. Sets `isValid` to `false` and updates `lastActivity`. +5. Emits `SessionInvalidated` events. -### 6.5.3. `updateMultipleProfiles` +#### `updateMultipleProfiles` **Description**: Updates profiles for multiple users in a single transaction. @@ -817,12 +817,12 @@ contributors: **Process**: - 1. Validates array lengths and batch size. - 2. Iterates over each user. - 3. Validates authorization and status. - 4. Updates profiles and emits events. +1. Validates array lengths and batch size. +2. Iterates over each user. +3. Validates authorization and status. +4. Updates profiles and emits events. -### 6.5.4. `enableMultipleEmergencyLockouts` +#### `enableMultipleEmergencyLockouts` **Description**: Allows an emergency contact to enable lockouts for multiple users. @@ -855,15 +855,15 @@ contributors: **Process**: - 1. Validates batch size. - 2. Iterates over each user. - 3. Validates that the caller is the emergency contact. - 4. Sets `emergencyLockout` to `true`. - 5. Emits `EmergencyLockoutEnabled` events. +1. Validates batch size. +2. Iterates over each user. +3. Validates that the caller is the emergency contact. +4. Sets `emergencyLockout` to `true`. +5. Emits `EmergencyLockoutEnabled` events. -## 6.6. Signature Verification +### Signature Verification -### 6.6.1. `verify` +#### `verify` **Description**: Verifies the authenticity of a message signed by a user. @@ -892,17 +892,17 @@ contributors: **Parameters**: - - `message`: The hashed message that was signed. - - `signature`: The signature bytes. +- `message`: The hashed message that was signed. +- `signature`: The signature bytes. **Process**: - 1. Splits the signature into `r`, `s`, and `v` components. - 2. Validates `r`, `s`, and `v` according to secp256k1 standards. - 3. Recovers the signer's address. - 4. Compares the recovered address with `msg.sender`. +1. Splits the signature into `r`, `s`, and `v` components. +2. Validates `r`, `s`, and `v` according to secp256k1 standards. +3. Recovers the signer's address. +4. Compares the recovered address with `msg.sender`. -### 6.6.2. `splitSignature` +#### `splitSignature` **Description**: Splits a signature into its components. @@ -926,15 +926,15 @@ contributors: **Parameters**: - - `sig`: The signature bytes. +- `sig`: The signature bytes. **Process**: - 1. Validates the length of the signature. - 2. Extracts `r`, `s`, and `v` components. - 3. Adjusts `v` if necessary. +1. Validates the length of the signature. +2. Extracts `r`, `s`, and `v` components. +3. Adjusts `v` if necessary. -### 6.6.3. `recoverSigner` +#### `recoverSigner` **Description**: Helper function for signature recovery in batch operations. @@ -963,19 +963,19 @@ contributors: **Parameters**: - - `message`: The hashed message. - - `signature`: The signature bytes. +- `message`: The hashed message. +- `signature`: The signature bytes. **Process**: - 1. Creates a prefixed message. - 2. Splits the signature. - 3. Validates `s` and `v` values. - 4. Recovers the signer's address. +1. Creates a prefixed message. +2. Splits the signature. +3. Validates `s` and `v` values. +4. Recovers the signer's address. -## 6.7. View Functions +### View Functions -### 6.7.1. `getProfile` +#### `getProfile` **Description**: Retrieves the profile hash of a registered user. @@ -990,14 +990,14 @@ contributors: **Parameters**: - - `user`: The address of the user. +- `user`: The address of the user. **Process**: - 1. Validates that the user is registered. - 2. Returns the profile hash. +1. Validates that the user is registered. +2. Returns the profile hash. -### 6.7.2. `getUserNonce` +#### `getUserNonce` **Description**: Retrieves the current nonce of a user. @@ -1011,13 +1011,13 @@ contributors: **Parameters**: - - `user`: The address of the user. +- `user`: The address of the user. **Process**: - 1. Returns the user's nonce. +1. Returns the user's nonce. -### 6.7.3. `isDisableRequestValid` +#### `isDisableRequestValid` **Description**: Checks if a pending disable lockout request is still valid. @@ -1036,16 +1036,16 @@ contributors: **Parameters**: - - `user`: The address of the user. +- `user`: The address of the user. **Process**: - 1. Checks if there is a pending request. - 2. Validates the request based on the timestamp and timeout. +1. Checks if there is a pending request. +2. Validates the request based on the timestamp and timeout. -7. Implementation Details +## Implementation Details -## 7.1. Reentrancy Guard +### Reentrancy Guard The contract uses a `nonReentrant` modifier to prevent reentrant calls to functions that modify critical state. @@ -1063,11 +1063,11 @@ contributors: **Process**: - 1. Checks if the `_locked` state is `false`. - 2. Sets `_locked` to `true` before function execution. - 3. Resets `_locked` to `false` after execution. +1. Checks if the `_locked` state is `false`. +2. Sets `_locked` to `true` before function execution. +3. Resets `_locked` to `false` after execution. -## 7.2. Session ID Generation +### Session ID Generation **Function**: `generateSessionId` @@ -1119,76 +1119,72 @@ contributors: **Process**: - 1. Retrieves entropy from the blockchain. - 2. Uses a per-user salt updated with each session creation. - 3. Combines user address, nonce, timestamps, and entropy to generate a unique `sessionId`. +1. Retrieves entropy from the blockchain. +2. Uses a per-user salt updated with each session creation. +3. Combines user address, nonce, timestamps, and entropy to generate a unique `sessionId`. -## 7.3. Nonce Management +### Nonce Management Nonces are used to prevent replay attacks during signature verification. - Each user has an associated nonce stored in the `nonces` mapping. - Nonces increment with each session creation. -## 7.4. Event Logging +### Event Logging The contract emits events to facilitate monitoring and logging of key actions: - - `UserRegistered(address indexed user);` - - `NewSessionCreated(address indexed user, bytes32 indexed sessionId, string dataHash);` - - `SessionInvalidated(bytes32 indexed sessionId);` - - `SessionRefreshed(bytes32 indexed sessionId, uint40 newExpiry);` - - `EmergencyContactSet(address indexed user, address indexed contact);` - - `EmergencyLockoutEnabled(address indexed user, address indexed triggeredBy);` - - `EmergencyLockoutDisabled(address indexed user, address indexed emergencyContact);` - - `EmergencyLockoutDisableRequested(address indexed user, bytes32 requestId);` - - `SignificantInactivity(bytes32 indexed sessionId, uint40 inactivePeriod);` - - `ActivityUpdated(bytes32 indexed sessionId, uint40 timestamp);` - - `UserProfileUpdated(address indexed user, string newProfileHash);` +- `UserRegistered(address indexed user);` +- `NewSessionCreated(address indexed user, bytes32 indexed sessionId, string dataHash);` +- `SessionInvalidated(bytes32 indexed sessionId);` +- `SessionRefreshed(bytes32 indexed sessionId, uint40 newExpiry);` +- `EmergencyContactSet(address indexed user, address indexed contact);` +- `EmergencyLockoutEnabled(address indexed user, address indexed triggeredBy);` +- `EmergencyLockoutDisabled(address indexed user, address indexed emergencyContact);` +- `EmergencyLockoutDisableRequested(address indexed user, bytes32 requestId);` +- `SignificantInactivity(bytes32 indexed sessionId, uint40 inactivePeriod);` +- `ActivityUpdated(bytes32 indexed sessionId, uint40 timestamp);` +- `UserProfileUpdated(address indexed user, string newProfileHash);` -# 8. Security Considerations +## Security Considerations - - **Replay Protection**: Uses nonces to prevent replay attacks during - session creation and emergency procedures. +- **Replay Protection**: Uses nonces to prevent replay attacks during +session creation and emergency procedures. - - **Signature Validation**: Ensures that signatures conform to - secp256k1 standards and are correctly verified. +- **Signature Validation**: Ensures that signatures conform to +secp256k1 standards and are correctly verified. - - **Emergency Lockout**: Provides a mechanism for users to secure their - accounts in case of compromise, with dual-signature verification for - disabling lockouts. +- **Emergency Lockout**: Provides a mechanism for users to secure their +accounts in case of compromise, with dual-signature verification for +disabling lockouts. - - **Reentrancy Guard**: Protects against reentrancy attacks by using a - `nonReentrant` modifier on critical functions. +- **Reentrancy Guard**: Protects against reentrancy attacks by using a +`nonReentrant` modifier on critical functions. - - **Session Inactivity**: Monitors inactivity and invalidates sessions - that exceed `MAX_INACTIVITY`. +- **Session Inactivity**: Monitors inactivity and invalidates sessions +that exceed `MAX_INACTIVITY`. - - **Entropy Sources**: Uses blockchain-provided entropy sources like - `blockhash` and `block.prevrandao` for generating session IDs, but - acknowledges the limitations and potential predictability. +- **Entropy Sources**: Uses blockchain-provided entropy sources like +`blockhash` and `block.prevrandao` for generating session IDs, but +acknowledges the limitations and potential predictability. - - **Access Control**: Validates that only authorized users can perform - certain actions, such as ending sessions or updating profiles. +- **Access Control**: Validates that only authorized users can perform +certain actions, such as ending sessions or updating profiles. - - **Batch Operations Risks**: Ensures that batch operations do not - exceed predefined sizes to prevent denial-of-service through large - transactions. +- **Batch Operations Risks**: Ensures that batch operations do not +exceed predefined sizes to prevent denial-of-service through large +transactions. - - **Time Casting**: Safely casts block timestamps to `uint40` to - prevent overflows, given that `uint40` can represent dates up to the - year 36812. +## References -# 9. References +- **[EIP-155]**: Ethereum Improvement Proposal 155 - Simple replay +attack protection. - - **[EIP-155]**: Ethereum Improvement Proposal 155 - Simple replay - attack protection. +- **[SECP256K1]**: Standards for Efficient Cryptography Group, +"Recommended Elliptic Curve Domain Parameters", 2010. - - **[SECP256K1]**: Standards for Efficient Cryptography Group, - "Recommended Elliptic Curve Domain Parameters", 2010. +- **[Solidity]**: Solidity Documentation - + - - **[Solidity]**: Solidity Documentation - - - - - **[IPFS]**: InterPlanetary File System - - \ No newline at end of file +- **[IPFS]**: InterPlanetary File System - + \ No newline at end of file From 0adc698c785fc75e6c4fd4e366c5c94bf8282082 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:47:43 +0100 Subject: [PATCH 14/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index c9773b97..2b701453 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -3,7 +3,7 @@ title: ETH-SIMPLELOGIN name: SimpleLogin, a blockchain-based authentication system status: raw category: Standards Track -tags: an optional list of tags, not standard +tags: editor: Ramses Fernandez contributors: --- From a70e94aff445fb9086525c761d7d95f9e5a95f3b Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:55:23 +0100 Subject: [PATCH 15/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 52 ++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index 2b701453..a0096bec 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -22,7 +22,7 @@ contributors: With the increasing need for secure authentication mechanisms in decentralized applications, SimpleLogin provides a blockchain-based solution that ensures user authentication, session management, and - enhanced security features. It includes an emergency system to handle + enhanced security features. It includes an emergency system to handle lockouts and provides batch operations to enhance scalability. It uses 90-day sessions with refresh capabilities and enhanced security measures. @@ -197,7 +197,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `newProfileHash`: The new IPFS hash of the user's profile data. +- `newProfileHash`: The new IPFS hash of the user's profile data. **Process**: @@ -233,7 +233,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `contact`: The address of the emergency contact. +- `contact`: The address of the emergency contact. **Process**: @@ -289,7 +289,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `user`: The address of the user to lock out. +- `user`: The address of the user to lock out. **Process**: @@ -327,7 +327,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `userSignature`: The user's signature authorizing the disable request. +- `userSignature`: The user's signature authorizing the disable request. **Process**: @@ -444,8 +444,8 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `signature`: The user's signature to verify the session creation request. - - `dataHash`: The IPFS hash of the session data. +- `signature`: The user's signature to verify the session creation request. +- `dataHash`: The IPFS hash of the session data. **Process**: @@ -488,7 +488,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionId`: The identifier of the session to refresh. +- `sessionId`: The identifier of the session to refresh. **Process**: @@ -523,7 +523,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionId`: The identifier of the session to terminate. +- `sessionId`: The identifier of the session to terminate. **Process**: @@ -553,7 +553,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionId`: The identifier of the session to validate. +- `sessionId`: The identifier of the session to validate. **Process**: @@ -588,7 +588,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionId`: The identifier of the session. +- `sessionId`: The identifier of the session. **Process**: @@ -631,7 +631,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionId`: The identifier of the session. +- `sessionId`: The identifier of the session. **Process**: @@ -726,9 +726,9 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `users`: Array of user addresses. - - `signatures`: Array of signatures for each user. - - `dataHashes`: Array of IPFS hashes for session data. +- `users`: Array of user addresses. +- `signatures`: Array of signatures for each user. +- `dataHashes`: Array of IPFS hashes for session data. **Process**: @@ -768,7 +768,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `sessionIds`: Array of session identifiers to terminate. +- `sessionIds`: Array of session identifiers to terminate. **Process**: @@ -812,8 +812,8 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `users`: Array of user addresses. - - `newProfileHashes`: Array of new IPFS profile hashes. +- `users`: Array of user addresses. +- `newProfileHashes`: Array of new IPFS profile hashes. **Process**: @@ -851,7 +851,7 @@ functions, and implementation specifics of SimpleLogin. **Parameters**: - - `users`: Array of user addresses to lock out. +- `users`: Array of user addresses to lock out. **Process**: @@ -1127,8 +1127,8 @@ functions, and implementation specifics of SimpleLogin. Nonces are used to prevent replay attacks during signature verification. - - Each user has an associated nonce stored in the `nonces` mapping. - - Nonces increment with each session creation. +- Each user has an associated nonce stored in the `nonces` mapping. +- Nonces increment with each session creation. ### Event Logging @@ -1177,14 +1177,12 @@ transactions. ## References -- **[EIP-155]**: Ethereum Improvement Proposal 155 - Simple replay +- EIP-155: Ethereum Improvement Proposal 155 - Simple replay attack protection. -- **[SECP256K1]**: Standards for Efficient Cryptography Group, +- SECP256K1: Standards for Efficient Cryptography Group, "Recommended Elliptic Curve Domain Parameters", 2010. -- **[Solidity]**: Solidity Documentation - - +- [Solidity documentation](https://docs.soliditylang.org/) -- **[IPFS]**: InterPlanetary File System - - \ No newline at end of file +- [IPFS documentation](https://ipfs.io/) \ No newline at end of file From 57a79b33cbc3e4bfeb806a332e68d98b439015ea Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 15:58:23 +0100 Subject: [PATCH 16/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index a0096bec..78396995 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -500,7 +500,8 @@ functions, and implementation specifics of SimpleLogin. #### `endSession` - **Description**: Allows a session owner or their emergency contact to invalidate a session. + **Description**: Allows a session owner or their emergency contact +to invalidate a session. **Function Definition**: @@ -1183,6 +1184,4 @@ attack protection. - SECP256K1: Standards for Efficient Cryptography Group, "Recommended Elliptic Curve Domain Parameters", 2010. -- [Solidity documentation](https://docs.soliditylang.org/) - - [IPFS documentation](https://ipfs.io/) \ No newline at end of file From 7ee097ffdf3dd78f6de543ff939e5b28db071b21 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 16:00:32 +0100 Subject: [PATCH 17/26] Update eth-demls.md --- vac/raw/eth-demls.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 945684cc..c6de878d 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1020,13 +1020,17 @@ derived from on-chain data introduces potential security vulnerabilities. Additionally, implementing SIWE in a fully decentralized environment requires careful consideration of centralization risks and extra infrastructure. -[SimpleLogin](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md) provides a blockchain-based authentication system +[SimpleLogin] +(https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md) +provides a blockchain-based authentication system specifically designed for decentralized applications. SimpleLogin manages user registration, session handling, and emergency security measures. -User registration and authentication MUST be implemented following the specification described [here](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md). +User registration and authentication MUST be implemented following the specification described +[here] +(https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md). ## Privacy and Security Considerations From 0fc13411b9813f992ff16074cd0249ff405ee53f Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 16:00:36 +0100 Subject: [PATCH 18/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index 78396995..6f2f4cde 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -1182,6 +1182,4 @@ transactions. attack protection. - SECP256K1: Standards for Efficient Cryptography Group, -"Recommended Elliptic Curve Domain Parameters", 2010. - -- [IPFS documentation](https://ipfs.io/) \ No newline at end of file +"Recommended Elliptic Curve Domain Parameters", 2010. \ No newline at end of file From 29be0ef518b2d6d671dda1a42a6e9fda3e88dfb6 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 16:04:35 +0100 Subject: [PATCH 19/26] Update eth-demls.md --- vac/raw/eth-demls.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index c6de878d..ccaead5d 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1020,17 +1020,15 @@ derived from on-chain data introduces potential security vulnerabilities. Additionally, implementing SIWE in a fully decentralized environment requires careful consideration of centralization risks and extra infrastructure. -[SimpleLogin] -(https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md) +[SimpleLogin](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md) provides a blockchain-based authentication system specifically designed for decentralized applications. SimpleLogin manages user registration, session handling, and emergency security measures. -User registration and authentication MUST be implemented following the specification described -[here] -(https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md). +User registration and authentication MUST be implemented following +the specification described [here](https://github.com/vacp2p/rfc-index/blob/eth-secpm-alt_authentication/vac/raw/eth-simplelogin.md). ## Privacy and Security Considerations From cd5709e3d7582f8ca8a7b3d98becc7865e9bf917 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Wed, 30 Oct 2024 16:04:37 +0100 Subject: [PATCH 20/26] Update eth-simplelogin.md --- vac/raw/eth-simplelogin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vac/raw/eth-simplelogin.md b/vac/raw/eth-simplelogin.md index 6f2f4cde..b1c329d3 100644 --- a/vac/raw/eth-simplelogin.md +++ b/vac/raw/eth-simplelogin.md @@ -1182,4 +1182,4 @@ transactions. attack protection. - SECP256K1: Standards for Efficient Cryptography Group, -"Recommended Elliptic Curve Domain Parameters", 2010. \ No newline at end of file +"Recommended Elliptic Curve Domain Parameters", 2010. From 8ca811182f35606662668018136de28848a828a0 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Thu, 31 Oct 2024 16:37:53 +0100 Subject: [PATCH 21/26] Update eth-demls.md --- vac/raw/eth-demls.md | 459 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 429 insertions(+), 30 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index ccaead5d..30fa34b3 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -642,36 +642,435 @@ Section 15 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). - Users have set a secure 1-1 communication channel. - Each group is managed by a separate smart contract. -### Addition of members to a group - -1. On-chain: Alice creates a Smart Contract with ACL. -2. Off-chain: Alice sends the contract address -and an invitation message to Bob over the secure channel. -3. Off-chain: Bob sends a signed response -confirming his Ethereum address and agreement to join. -4. Off-chain: Alice verifies the signature using the public key of Bob. -5. On-chain: Alice adds Bob’s address to the ACL. -6. Off-chain: Alice sends a welcome message to Bob. -7. Off-chain: Alice sends a broadcast message to all group members, -notifying them the addition of Bob. - -![figure8](./images/eth-secpm_onchain-register-2.png) - -### Updates in groups - -Removal requests and update requests are considered the same operation. -One assumes Alice is the creator of the contract. -They MUST be processed as follows: - -1. Off-chain: Bob creates a new update request. -2. Off-chain: Bob sends the update request to Alice. -3. Off-chain: Alice verifies the request. -4. On-chain: If the verification is successfull, -Alice sends it to the smart contract for registration. -5. Off-chain: Alice sends a broadcast message -communicating the update to all users. - -![figure9](./images/eth-secpm_onchain-update.png) +### Group Management Contract + +To ease decentralized and secure group management, the `GroupManager` smart contract is used. This contract provides functionalities for: + +- **Group Creation**: Users can create new groups, specifying group names and types (OPEN or CLOSED). +- **Membership Management**: Admins can add or remove members, and users can join OPEN groups directly. +- **Role Assignment**: Roles (MEMBER, ADMIN) can be assigned to group members, with appropriate access control. +- **Ownership Transfer**: Group owners can transfer ownership to another member. +- **Access Control**: The contract enforces access control through modifiers that check roles, ownership, and session validity. + +The `GroupManager` relies on the **`SimpleLogin`** contract for authentication, ensuring that only authenticated users with valid sessions can interact with the group management functionalities. + +#### Contract Specification + +```solidity +// SimpleLogin interface provides authentication services through sessions +interface ISimpleLogin { + // Verifies if a session is still valid + function isSessionValid(bytes32 sessionId) external view returns (bool); + // Returns the owner address of a session + function sessions(bytes32 sessionId) external view returns (address owner); + // Checks if an address is registered in the system + function registered(address user) external view returns (bool); +} + +contract GroupManager { + // Reentrancy guard state variable to prevent recursive calls + bool private _locked; + + // Authentication service reference + ISimpleLogin private immutable simpleLogin; + + // Tracks total number of groups for ID assignment + uint256 public groupCount; + + // Group Types define joining permissions + enum GroupType { + OPEN, // Anyone can join + CLOSED // Admin invitation required + } + + // Roles define member permissions within groups + enum Role { + NONE, // Not a member + MEMBER, // Basic access + ADMIN // Full management access + } + + // Group structure contains all group-related data + struct Group { + uint256 id; // Unique identifier + string name; // Display name + address owner; // Owner address + GroupType groupType; // Open/Closed status + uint256 memberCount; // Total number of members + mapping(address => Role) members; // Member roles + mapping(uint256 => address) memberList; // List of member addresses for iteration + } + + // Maps group IDs to Group structs + mapping(uint256 => Group) public groups; + + // Events for tracking state changes + event GroupCreated( + uint256 indexed groupId, + string name, + GroupType groupType, + address indexed owner + ); + + event MemberJoined( + uint256 indexed groupId, + address indexed member, + Role role + ); + + event MemberRemoved( + uint256 indexed groupId, + address indexed member + ); + + event OwnershipTransferred( + uint256 indexed groupId, + address indexed oldOwner, + address indexed newOwner + ); + + event RoleAssigned( + uint256 indexed groupId, + address indexed member, + Role role + ); + + // Guard against recursive calls to protected functions + modifier nonReentrant() { + require(!_locked, "ReentrancyGuard: reentrant call"); + _locked = true; + _; + _locked = false; + } + + // Ensures session is valid and belongs to caller + modifier validSession(bytes32 sessionId) { + require(simpleLogin.isSessionValid(sessionId), "Invalid session"); + require(simpleLogin.sessions(sessionId) == msg.sender, "Unauthorized"); + _; + } + + // Ensures group exists + modifier groupExists(uint256 groupId) { + require(groups[groupId].owner != address(0), "Group does not exist"); + _; + } + + // Ensures caller is group admin + modifier onlyAdmin(uint256 groupId) { + require(groups[groupId].members[msg.sender] == Role.ADMIN, "Not an admin"); + _; + } + + // Ensures caller is group owner + modifier onlyOwner(uint256 groupId) { + require(groups[groupId].owner == msg.sender, "Not group owner"); + _; + } + + // Prevents removing the last admin + modifier maintainAdmin(uint256 groupId, address member) { + _; + Group storage group = groups[groupId]; + bool hasAdmin = false; + + // Check if at least one admin remains + for (uint i = 0; i < group.memberCount; i++) { + address memberAddress = group.memberList[i]; + if (group.members[memberAddress] == Role.ADMIN) { + hasAdmin = true; + break; + } + } + require(hasAdmin, "Cannot remove last admin"); + } + + // Prevents admins from modifying other admins' roles + modifier adminHierarchy(uint256 groupId, address member) { + Group storage group = groups[groupId]; + require( + group.members[member] != Role.ADMIN || + msg.sender == group.owner, + "Only owner can modify admin roles" + ); + _; + } + + // Initialize contract with authentication service + constructor(address simpleLoginAddress) { + require(simpleLoginAddress != address(0), "Invalid SimpleLogin address"); + simpleLogin = ISimpleLogin(simpleLoginAddress); + } + + // Creates a new group with the caller as owner and admin + function createGroup( + bytes32 sessionId, + string calldata groupName, + GroupType groupType + ) + external + validSession(sessionId) + nonReentrant + { + // Validate group name + require(bytes(groupName).length > 0, "Empty group name"); + require(bytes(groupName).length <= 100, "Group name too long"); + + // Create new group with incremented ID + groupCount++; + Group storage newGroup = groups[groupCount]; + + // Initialize group properties + newGroup.id = groupCount; + newGroup.name = groupName; + newGroup.owner = msg.sender; + newGroup.groupType = groupType; + newGroup.memberCount = 1; + + // Add owner as first member with admin role + newGroup.members[msg.sender] = Role.ADMIN; + newGroup.memberList[0] = msg.sender; + + emit GroupCreated(groupCount, groupName, groupType, msg.sender); + } + + // Allows users to join OPEN groups directly + function joinGroup( + bytes32 sessionId, + uint256 groupId + ) + external + validSession(sessionId) + groupExists(groupId) + nonReentrant + { + Group storage group = groups[groupId]; + + // Verify group is open and user not already member + require(group.groupType == GroupType.OPEN, "Cannot join closed group"); + require(group.members[msg.sender] == Role.NONE, "Already a member"); + + // Add new member + group.members[msg.sender] = Role.MEMBER; + group.memberList[group.memberCount] = msg.sender; + group.memberCount++; + + emit MemberJoined(groupId, msg.sender, Role.MEMBER); + } + + // Allows admin to add members to any group type + function addMember( + bytes32 sessionId, + uint256 groupId, + address newMember + ) + external + validSession(sessionId) + groupExists(groupId) + onlyAdmin(groupId) + nonReentrant + { + Group storage group = groups[groupId]; + + // Validate new member + require(newMember != address(0), "Invalid member address"); + require(group.members[newMember] == Role.NONE, "Already a member"); + require(simpleLogin.registered(newMember), "User not registered"); + + // Add new member + group.members[newMember] = Role.MEMBER; + group.memberList[group.memberCount] = newMember; + group.memberCount++; + + emit MemberJoined(groupId, newMember, Role.MEMBER); + } + + // Allows admin to remove any member except owner + function removeMember( + bytes32 sessionId, + uint256 groupId, + address member + ) + external + validSession(sessionId) + groupExists(groupId) + onlyAdmin(groupId) + maintainAdmin(groupId, member) + adminHierarchy(groupId, member) + nonReentrant + { + Group storage group = groups[groupId]; + + // Validate member + require(member != group.owner, "Cannot remove owner"); + require(group.members[member] != Role.NONE, "Not a member"); + + // Find and remove member from list + for (uint i = 0; i < group.memberCount; i++) { + if (group.memberList[i] == member) { + // Move last member to this position if not last + if (i != group.memberCount - 1) { + group.memberList[i] = group.memberList[group.memberCount - 1]; + } + delete group.memberList[group.memberCount - 1]; + group.memberCount--; + break; + } + } + + // Remove member role + delete group.members[member]; + + emit MemberRemoved(groupId, member); + } + + // Transfers group ownership to another member + function transferOwnership( + bytes32 sessionId, + uint256 groupId, + address newOwner + ) + external + validSession(sessionId) + groupExists(groupId) + onlyOwner(groupId) + nonReentrant + { + Group storage group = groups[groupId]; + + // Validate new owner + require(newOwner != address(0), "Invalid owner address"); + require(group.members[newOwner] != Role.NONE, "New owner must be member"); + require(newOwner != msg.sender, "Already owner"); + + // Store old owner's role + Role oldOwnerRole = group.members[msg.sender]; + + // Update ownership and roles + group.owner = newOwner; + group.members[newOwner] = Role.ADMIN; + + // Keep admin role for old owner if they had it + if (oldOwnerRole != Role.ADMIN) { + group.members[msg.sender] = Role.MEMBER; + } + + emit OwnershipTransferred(groupId, msg.sender, newOwner); + } + + // Assigns role to member, with admin hierarchy checks + function assignRole( + bytes32 sessionId, + uint256 groupId, + address member, + Role role + ) + external + validSession(sessionId) + groupExists(groupId) + onlyAdmin(groupId) + maintainAdmin(groupId, member) + adminHierarchy(groupId, member) + nonReentrant + { + Group storage group = groups[groupId]; + + // Validate member and role + require(member != address(0), "Invalid member address"); + require(group.members[member] != Role.NONE, "Not a member"); + require(role != Role.NONE, "Cannot assign NONE role"); + require(member != group.owner, "Cannot modify owner role"); + + // Only owner can assign admin role + if (role == Role.ADMIN) { + require(msg.sender == group.owner, "Only owner can assign admin role"); + } + + group.members[member] = role; + + emit RoleAssigned(groupId, member, role); + } + + // Helper function to check if address is group member + function isMember( + uint256 groupId, + address user + ) + external + view + groupExists(groupId) + returns (bool) + { + return groups[groupId].members[user] != Role.NONE; + } + + // Helper function to get member role + function getMemberRole( + uint256 groupId, + address member + ) + external + view + groupExists(groupId) + returns (Role) + { + return groups[groupId].members[member]; + } + + // Helper function to get member count + function getMemberCount( + uint256 groupId + ) + external + view + groupExists(groupId) + returns (uint256) + { + return groups[groupId].memberCount; + } + + // Helper function to get member at specific index + function getMemberAt( + uint256 groupId, + uint256 index + ) + external + view + groupExists(groupId) + returns (address) + { + require(index < groups[groupId].memberCount, "Index out of bounds"); + return groups[groupId].memberList[index]; + } +} +``` + +### Addition of Members to a Group + +The process of adding members to a group is facilitated by the `GroupManager` contract: + +1. **On-chain**: Alice (the group owner) creates a group using the `createGroup` function of `GroupManager`. +2. **Off-chain**: Alice sends the contract address and an invitation to Bob over a secure channel. +3. **Off-chain**: Bob responds, and Alice verifies his authentication via `SimpleLogin`. +4. **On-chain**: Alice uses `addMember` to add Bob to the group. +5. **Off-chain**: Alice notifies Bob and other group members about the addition. + +### Updates in Groups + +Group updates, such as member removal or role changes, are managed through the `GroupManager` contract: + +1. **Off-chain**: A member initiates an update request. +2. **Off-chain**: The request is sent to the group admin or owner. +3. **Off-chain**: The admin verifies the request and the member's authentication. +4. **On-chain**: The admin calls the appropriate function (`removeMember`, `assignRole`) in `GroupManager`. +5. **Off-chain**: The group is notified of the update. + +### Security Considerations + +- **Reentrancy Guard**: The `nonReentrant` modifier prevents reentrant calls to critical functions. +- **Access Control**: Modifiers like `onlyAdmin`, `onlyOwner`, and `validSession` ensure that only authorized users can perform specific actions. +- **Session Authentication**: Integration with `SimpleLogin` ensures that users have valid sessions before interacting with the contract. +- **Preventing Unauthorized Role Changes**: The contract enforces hierarchy rules, where only owners can modify admin roles, and admins cannot alter other admins' roles without proper authorization. ## Ethereum-based authentication protocol From d19f687d117374da6494c2ddb9d7e0b8eedf2534 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 1 Nov 2024 12:37:06 +0100 Subject: [PATCH 22/26] Update eth-demls.md --- vac/raw/eth-demls.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 30fa34b3..63ac4843 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -644,15 +644,22 @@ Section 15 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). ### Group Management Contract -To ease decentralized and secure group management, the `GroupManager` smart contract is used. This contract provides functionalities for: - -- **Group Creation**: Users can create new groups, specifying group names and types (OPEN or CLOSED). -- **Membership Management**: Admins can add or remove members, and users can join OPEN groups directly. -- **Role Assignment**: Roles (MEMBER, ADMIN) can be assigned to group members, with appropriate access control. +To ease decentralized and secure group management, the `GroupManager` smart contract is used. +This contract provides functionalities for: + +- **Group Creation**: Users can create new groups, specifying group names and types +(OPEN or CLOSED). +- **Membership Management**: Admins can add or remove members, +and users can join OPEN groups directly. +- **Role Assignment**: Roles (MEMBER, ADMIN) can be assigned to group members, +with appropriate access control. - **Ownership Transfer**: Group owners can transfer ownership to another member. -- **Access Control**: The contract enforces access control through modifiers that check roles, ownership, and session validity. +- **Access Control**: The contract enforces access control through modifiers that check roles, +ownership, and session validity. -The `GroupManager` relies on the **`SimpleLogin`** contract for authentication, ensuring that only authenticated users with valid sessions can interact with the group management functionalities. +The `GroupManager` relies on the **`SimpleLogin`** contract for authentication, +ensuring that only authenticated users with valid sessions can interact +with the group management functionalities. #### Contract Specification From eb3c012043e496ad2b646ef38bfe85a5c6c1c8cc Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 1 Nov 2024 12:40:14 +0100 Subject: [PATCH 23/26] Update eth-demls.md --- vac/raw/eth-demls.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 63ac4843..d9e2d914 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -644,7 +644,7 @@ Section 15 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). ### Group Management Contract -To ease decentralized and secure group management, the `GroupManager` smart contract is used. +To ease decentralized and secure group management, we set the `GroupManager` smart contract. This contract provides functionalities for: - **Group Creation**: Users can create new groups, specifying group names and types @@ -654,8 +654,8 @@ and users can join OPEN groups directly. - **Role Assignment**: Roles (MEMBER, ADMIN) can be assigned to group members, with appropriate access control. - **Ownership Transfer**: Group owners can transfer ownership to another member. -- **Access Control**: The contract enforces access control through modifiers that check roles, -ownership, and session validity. +- **Access Control**: The contract enforces access control through modifiers +that check roles, ownership, and session validity. The `GroupManager` relies on the **`SimpleLogin`** contract for authentication, ensuring that only authenticated users with valid sessions can interact @@ -1056,28 +1056,37 @@ contract GroupManager { The process of adding members to a group is facilitated by the `GroupManager` contract: -1. **On-chain**: Alice (the group owner) creates a group using the `createGroup` function of `GroupManager`. -2. **Off-chain**: Alice sends the contract address and an invitation to Bob over a secure channel. +1. **On-chain**: Alice (group owner) creates a group using the `createGroup` function +of `GroupManager`. +2. **Off-chain**: Alice sends the contract address and an invitation to Bob +over a secure channel. 3. **Off-chain**: Bob responds, and Alice verifies his authentication via `SimpleLogin`. 4. **On-chain**: Alice uses `addMember` to add Bob to the group. 5. **Off-chain**: Alice notifies Bob and other group members about the addition. ### Updates in Groups -Group updates, such as member removal or role changes, are managed through the `GroupManager` contract: +Group updates, such as member removal or role changes, +are managed through the `GroupManager`: 1. **Off-chain**: A member initiates an update request. 2. **Off-chain**: The request is sent to the group admin or owner. 3. **Off-chain**: The admin verifies the request and the member's authentication. -4. **On-chain**: The admin calls the appropriate function (`removeMember`, `assignRole`) in `GroupManager`. +4. **On-chain**: The admin calls the appropriate function +(`removeMember`, `assignRole`) in `GroupManager`. 5. **Off-chain**: The group is notified of the update. ### Security Considerations -- **Reentrancy Guard**: The `nonReentrant` modifier prevents reentrant calls to critical functions. -- **Access Control**: Modifiers like `onlyAdmin`, `onlyOwner`, and `validSession` ensure that only authorized users can perform specific actions. -- **Session Authentication**: Integration with `SimpleLogin` ensures that users have valid sessions before interacting with the contract. -- **Preventing Unauthorized Role Changes**: The contract enforces hierarchy rules, where only owners can modify admin roles, and admins cannot alter other admins' roles without proper authorization. +- **Reentrancy Guard**: The `nonReentrant` modifier prevents +reentrant calls to critical functions. +- **Access Control**: Modifiers like `onlyAdmin`, `onlyOwner`, +and `validSession` ensure that only authorized users can perform specific actions. +- **Session Authentication**: Integration with `SimpleLogin` ensures +that users have valid sessions before interacting with the contract. +- **Preventing Unauthorized Role Changes**: The contract enforces hierarchy rules, +where only owners can modify admin roles, and admins cannot alter other +admins' roles without proper authorization. ## Ethereum-based authentication protocol From 297f1b618fc6b716c0fdcb58585ddfe74607b76b Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Fri, 1 Nov 2024 12:41:22 +0100 Subject: [PATCH 24/26] Update eth-demls.md --- vac/raw/eth-demls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index d9e2d914..1768f76f 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -644,7 +644,7 @@ Section 15 of [RFC9420](https://datatracker.ietf.org/doc/rfc9420/). ### Group Management Contract -To ease decentralized and secure group management, we set the `GroupManager` smart contract. +To ease decentralized group management, we set the `GroupManager` smart contract. This contract provides functionalities for: - **Group Creation**: Users can create new groups, specifying group names and types From 019708a34d0871bbaba294502cea359fdc74b1c3 Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 5 Nov 2024 11:46:02 +0100 Subject: [PATCH 25/26] Update eth-demls.md --- vac/raw/eth-demls.md | 97 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 1768f76f..156b47bd 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1069,25 +1069,104 @@ over a secure channel. Group updates, such as member removal or role changes, are managed through the `GroupManager`: -1. **Off-chain**: A member initiates an update request. -2. **Off-chain**: The request is sent to the group admin or owner. -3. **Off-chain**: The admin verifies the request and the member's authentication. -4. **On-chain**: The admin calls the appropriate function +1. **Off-chain**: a member initiates an update request. +2. **Off-chain**: the request is sent to the group admin or owner. +3. **Off-chain**: the admin verifies the request and the member's authentication. +4. **On-chain**: the admin calls the appropriate function (`removeMember`, `assignRole`) in `GroupManager`. -5. **Off-chain**: The group is notified of the update. +5. **Off-chain**: the group is notified of the update. ### Security Considerations -- **Reentrancy Guard**: The `nonReentrant` modifier prevents +- **Reentrancy guard**: the `nonReentrant` modifier prevents reentrant calls to critical functions. -- **Access Control**: Modifiers like `onlyAdmin`, `onlyOwner`, +- **Access control**: modifiers like `onlyAdmin`, `onlyOwner`, and `validSession` ensure that only authorized users can perform specific actions. -- **Session Authentication**: Integration with `SimpleLogin` ensures +- **Session authentication**: integration with `SimpleLogin` ensures that users have valid sessions before interacting with the contract. -- **Preventing Unauthorized Role Changes**: The contract enforces hierarchy rules, +- **Preventing unauthorized role changes**: the contract enforces hierarchy rules, where only owners can modify admin roles, and admins cannot alter other admins' roles without proper authorization. +### Interaction between `GroupManager` and `SimpleLogin` + +#### Core Integration + +The integration between `GroupManager` and `SimpleLogin` +is established through a clean interface and immutable contract reference. +This ensures secure authentication and session management +while maintaining separation of concerns. + +```solidity +// GroupManager's interface to SimpleLogin +interface ISimpleLogin { + function isSessionValid(bytes32 sessionId) external view returns (bool); + function sessions(bytes32 sessionId) external view returns (address owner); + function registered(address user) external view returns (bool); +} + +// Integration setup in GroupManager +contract GroupManager { + ISimpleLogin private immutable simpleLogin; + + constructor(address simpleLoginAddress) { + require(simpleLoginAddress != address(0), "Invalid SimpleLogin address"); + simpleLogin = ISimpleLogin(simpleLoginAddress); + } +} +``` + +#### Interactions + +- Session validation: every operation in `GroupManager` requires a valid session from `SimpleLogin`. +The `validSession` modifier ensures both session validity and ownership, +creating a secure bridge between authentication and group operations. + +```solidity +modifier validSession(bytes32 sessionId) { + require(simpleLogin.isSessionValid(sessionId), "Invalid session"); + require(simpleLogin.sessions(sessionId) == msg.sender, "Unauthorized"); + _; +} +``` + +- Registration verification: before adding new members to a group, +`GroupManager` verifies their registration status with `SimpleLogin`. +This ensures that only registered users can become group members. + +```solidity +function addMember(...) { + // ... + require(simpleLogin.registered(newMember), "User not registered"); + // ... +} +``` + +#### Interaction flow example + +The following flow shows the typical interaction pattern between users, +`GroupManager`, and `SimpleLogin`, +showing how authentication and group operations are integrated. + +```text +1. User Authentication: + - User creates session through SimpleLogin + - Receives sessionId for future operations + +2. Group Operations: + - User includes sessionId in GroupManager calls + - GroupManager verifies session with SimpleLogin + - GroupManager checks message sender matches session owner + - Operation proceeds if validation successful + +3. Member Management: + - Admin adds new member + - GroupManager verifies admin's session + - GroupManager checks new member's registration + - Member addition proceeds if all checks pass +``` + + ## Ethereum-based authentication protocol ### Introduction From 9e82ea818a8de4e588ba542a9c43eb0444fb445e Mon Sep 17 00:00:00 2001 From: ramsesfv Date: Tue, 5 Nov 2024 11:53:30 +0100 Subject: [PATCH 26/26] Update eth-demls.md --- vac/raw/eth-demls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vac/raw/eth-demls.md b/vac/raw/eth-demls.md index 156b47bd..69e4dbc1 100644 --- a/vac/raw/eth-demls.md +++ b/vac/raw/eth-demls.md @@ -1118,7 +1118,8 @@ contract GroupManager { #### Interactions -- Session validation: every operation in `GroupManager` requires a valid session from `SimpleLogin`. +- Session validation: every operation in `GroupManager` +requires a valid session from `SimpleLogin`. The `validSession` modifier ensures both session validity and ownership, creating a secure bridge between authentication and group operations. @@ -1166,7 +1167,6 @@ showing how authentication and group operations are integrated. - Member addition proceeds if all checks pass ``` - ## Ethereum-based authentication protocol ### Introduction