Add decentralized cryptographic identity

This commit is contained in:
Alexander Sellite 2026-07-29 22:18:14 -04:00
parent 0b5b78a553
commit fa996a6735
27 changed files with 1733 additions and 58 deletions

View file

@ -1,11 +1,13 @@
class_name NetworkProtocol
extends RefCounted
const PROTOCOL_VERSION: int = 2
const PROTOCOL_VERSION: int = 3
const GAME_BUILD: String = "prealpha"
const MAX_DISPLAY_NAME_LENGTH: int = 24
const MAX_PROFILE_ID_LENGTH: int = 96
const MAX_NONCE_LENGTH: int = 96
const MAX_PUBLIC_KEY_LENGTH: int = 8192
const MAX_SIGNATURE_LENGTH: int = 2048
# ENet channels: 0 reliable lifecycle, 1 movement input, 2 movement
# snapshots, 3 fishing input, 4 fishing snapshots, 5 reliable sales,
# 6 reliable shop transactions, 7 reliable item/equipment lifecycle,
@ -23,17 +25,56 @@ enum RejectionCode {
PROTOCOL_MISMATCH,
SERVER_FULL,
DUPLICATE_PROFILE,
DUPLICATE_IDENTITY,
INVALID_IDENTITY_PROOF,
AUTHENTICATION_TIMEOUT,
SERVER_SHUTTING_DOWN,
UNSUPPORTED_CLIENT,
}
static func make_identity_hello(
public_key: String,
fingerprint: String,
client_nonce: String,
attempt_id: String,
) -> Dictionary:
return {
"protocol_version": PROTOCOL_VERSION,
"public_key": public_key,
"fingerprint": fingerprint,
"client_nonce": client_nonce,
"attempt_id": attempt_id,
"capability_flags": PackedStringArray(["identity_v1"]),
}
static func validate_identity_hello(data: Variant) -> bool:
if typeof(data) != TYPE_DICTIONARY:
return false
var value: Dictionary = data
return (
typeof(value.get("protocol_version")) == TYPE_INT
and typeof(value.get("public_key")) == TYPE_STRING
and str(value["public_key"]).to_utf8_buffer().size() <= MAX_PUBLIC_KEY_LENGTH
and NetworkIdentityCrypto.valid_fingerprint(value.get("fingerprint"))
and typeof(value.get("client_nonce")) == TYPE_STRING
and str(value["client_nonce"]).length() == 64
and typeof(value.get("attempt_id")) == TYPE_STRING
and str(value["attempt_id"]).length() == 32
and typeof(value.get("capability_flags")) in [
TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY
]
)
static func make_client_hello(
profile_id: String,
display_name: String,
client_nonce: String,
cosmetic_snapshot: Dictionary = {},
identity_fingerprint: String = "",
identity_signature: PackedByteArray = PackedByteArray(),
) -> Dictionary:
return {
"protocol_version": PROTOCOL_VERSION,
@ -43,6 +84,8 @@ static func make_client_hello(
"client_nonce": client_nonce,
"capability_flags": PackedStringArray(),
"cosmetic_snapshot": cosmetic_snapshot,
"identity_fingerprint": identity_fingerprint,
"identity_signature": identity_signature,
}
@ -58,6 +101,8 @@ static func validate_client_hello(data: Variant) -> String:
"client_nonce",
"capability_flags",
"cosmetic_snapshot",
"identity_fingerprint",
"identity_signature",
]:
if not payload.has(key):
return "Handshake is missing %s." % key
@ -78,6 +123,13 @@ static func validate_client_hello(data: Variant) -> String:
return "Capabilities are invalid."
if typeof(payload["cosmetic_snapshot"]) != TYPE_DICTIONARY:
return "Cosmetic snapshot is invalid."
if (
not NetworkIdentityCrypto.valid_fingerprint(
payload["identity_fingerprint"]
)
or typeof(payload["identity_signature"]) != TYPE_PACKED_BYTE_ARRAY
):
return "Profile identity proof is invalid."
var profile_id: String = payload["local_profile_id"]
var display_name: String = payload["display_name"]
var nonce: String = payload["client_nonce"]
@ -93,6 +145,23 @@ static func validate_client_hello(data: Variant) -> String:
return ""
static func client_profile_fields(data: Dictionary) -> Array:
var appearance: Dictionary = data.get("cosmetic_snapshot", {})
return [
str(data.get("client_nonce", "")),
str(data.get("identity_fingerprint", "")),
str(data.get("local_profile_id", "")),
str(data.get("display_name", "")),
str(appearance.get("species", "")),
str(appearance.get("fur_pattern", "")),
str(appearance.get("ears", "")),
str(appearance.get("eyes", "")),
str(appearance.get("nose", "")),
str(appearance.get("mouth", "")),
str(appearance.get("tail", "")),
]
static func make_server_hello(
accepted: bool,
rejection_code: RejectionCode,
@ -120,6 +189,7 @@ static func make_server_hello(
"chat_v1",
"mail_v1",
"profile_v1",
"identity_v1",
]),
}
@ -131,7 +201,11 @@ static func rejection_text(code: int) -> String:
RejectionCode.SERVER_FULL:
return "The server is full."
RejectionCode.DUPLICATE_PROFILE:
return "This local profile is already connected."
return "This legacy local profile is already connected."
RejectionCode.DUPLICATE_IDENTITY:
return "This player identity is already connected."
RejectionCode.INVALID_IDENTITY_PROOF:
return "Player identity authentication failed."
RejectionCode.AUTHENTICATION_TIMEOUT:
return "The server did not finish authentication."
RejectionCode.SERVER_SHUTTING_DOWN: