~starkingdoms/starkingdoms

eabc84c8e290163d25ea21f153ae811bf09fc23a — core 11 hours ago 6f94947 core/netcode-round-three
feat: add basic save file authentication infrastructure
M Cargo.lock => Cargo.lock +179 -3
@@ 373,6 373,15 @@ dependencies = [
]

[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
 "critical-section",
]

[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 466,6 475,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"

[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"

[[package]]
name = "bevy"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1210,7 1225,7 @@ dependencies = [
 "portable-atomic",
 "portable-atomic-util",
 "serde",
 "spin",
 "spin 0.10.0",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-time 1.1.0",


@@ 1539,7 1554,7 @@ dependencies = [
 "crossbeam-queue",
 "derive_more",
 "futures-lite",
 "heapless",
 "heapless 0.9.2",
 "pin-project",
]



@@ 2162,6 2177,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"

[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"

[[package]]
name = "const_format"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2475,6 2496,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"

[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
 "cfg-if",
 "cpufeatures 0.2.17",
 "curve25519-dalek-derive",
 "digest",
 "fiat-crypto",
 "rustc_version",
 "subtle",
 "zeroize",
]

[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "darling"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2516,6 2564,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"

[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
 "const-oid",
 "zeroize",
]

[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2783,12 2841,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fdab65db9274e0168143841eb8f864a0a21f8b1b8d2ba6812bbe6024346e99e"

[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
 "pkcs8",
 "serde",
 "signature",
]

[[package]]
name = "ed25519-dalek"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
 "curve25519-dalek",
 "ed25519",
 "serde",
 "sha2",
 "subtle",
 "zeroize",
]

[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"

[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"

[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"

[[package]]
name = "ena"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2924,6 3019,12 @@ dependencies = [
]

[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"

[[package]]
name = "file-id"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3465,6 3566,15 @@ dependencies = [

[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
 "byteorder",
]

[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"


@@ 3502,11 3612,25 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"

[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
 "atomic-polyfill",
 "hash32 0.2.1",
 "rustc_version",
 "serde",
 "spin 0.9.8",
 "stable_deref_trait",
]

[[package]]
name = "heapless"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
dependencies = [
 "hash32",
 "hash32 0.3.1",
 "portable-atomic",
 "stable_deref_trait",
]


@@ 4931,6 5055,16 @@ dependencies = [
]

[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
 "der",
 "spki",
]

[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5030,6 5164,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
 "cobs",
 "embedded-io 0.4.0",
 "embedded-io 0.6.1",
 "heapless 0.7.17",
 "postcard-derive",
 "serde",
]


@@ 5916,6 6053,15 @@ dependencies = [
]

[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
 "rand_core 0.6.4",
]

[[package]]
name = "simba"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6071,6 6217,15 @@ dependencies = [

[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
 "lock_api",
]

[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"


@@ 6088,6 6243,16 @@ dependencies = [
]

[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
 "base64ct",
 "der",
]

[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6132,6 6297,17 @@ dependencies = [
]

[[package]]
name = "starkingdoms-common"
version = "0.1.0"
dependencies = [
 "base64",
 "ed25519-dalek",
 "postcard",
 "serde",
 "thiserror 2.0.18",
]

[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

A crates/common/Cargo.toml => crates/common/Cargo.toml +11 -0
@@ 0,0 1,11 @@
[package]
name = "starkingdoms-common"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
postcard = { version = "1", features = ["alloc"] }
ed25519-dalek = { version = "2", features = ["serde"] }
base64 = { version = "0.22" }
thiserror = "2"
\ No newline at end of file

A crates/common/src/lib.rs => crates/common/src/lib.rs +16 -0
@@ 0,0 1,16 @@
pub mod save;

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

A crates/common/src/save/authentication.rs => crates/common/src/save/authentication.rs +37 -0
@@ 0,0 1,37 @@
use base64::Engine;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use crate::save::error::SaveError;

#[derive(Serialize, Deserialize)]
pub struct SignedSavefile {
    pub authenticating_server: AuthenticatingServer,
    pub save_data: crate::save::Savefile,
    pub signature: Signature
}

#[derive(Serialize, Deserialize)]
pub struct AuthenticatingServer {
    pub server: String,
    pub server_build_id: String,
    pub server_public_key: VerifyingKey,
}

impl SignedSavefile {
    pub fn encoded_save_data(&self) -> Result<Vec<u8>, SaveError> {
        Ok(postcard::to_allocvec(&self.save_data)?)
    }
    pub fn is_valid(&self) -> Result<bool, SaveError> {
        let encoded_savefile = self.encoded_save_data()?;
        Ok(self.authenticating_server.server_public_key.verify(&encoded_savefile, &self.signature).is_ok())
    }
}

pub fn encode_public_key(public_key: &VerifyingKey) -> String {
    base64::engine::general_purpose::URL_SAFE.encode(public_key)
}
pub fn decode_public_key(public_key: &str) -> Result<VerifyingKey, SaveError> {
    let bytes = base64::engine::general_purpose::URL_SAFE.decode(public_key)?;
    let key: [u8; 32] = bytes.try_into().ok().ok_or(SaveError::InvalidKeyLength(public_key.len()))?;
    VerifyingKey::from_bytes(&key).ok().ok_or(SaveError::Ed25519Error)
}
\ No newline at end of file

A crates/common/src/save/error.rs => crates/common/src/save/error.rs +13 -0
@@ 0,0 1,13 @@
use thiserror::Error;

#[derive(Clone, Debug, Error)]
pub enum SaveError {
    #[error("postcard ser/de error: {0}")]
    PostcardError(#[from] postcard::Error),
    #[error("invalid base64: {0}")]
    Base64(#[from] base64::DecodeError),
    #[error("incorrect key length: should be 32, got {0}")]
    InvalidKeyLength(usize),
    #[error("ed25519 error")]
    Ed25519Error
}
\ No newline at end of file

A crates/common/src/save/mod.rs => crates/common/src/save/mod.rs +26 -0
@@ 0,0 1,26 @@
use serde::{Deserialize, Serialize};

pub mod authentication;
pub mod v1;
pub mod error;

#[derive(Serialize, Deserialize)]
pub enum Savefile {
    V1(v1::Savefile),
}

pub trait SaveContainer {
    type LatestFormat;

    fn as_latest(&self) -> &Self::LatestFormat;
}

impl SaveContainer for Savefile {
    type LatestFormat = v1::Savefile;

    fn as_latest(&self) -> &Self::LatestFormat {
        match self {
            Savefile::V1(s) => s,
        }
    }
}
\ No newline at end of file

A crates/common/src/save/v1.rs => crates/common/src/save/v1.rs +6 -0
@@ 0,0 1,6 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Savefile {

}
\ No newline at end of file