diff --git a/.gitignore b/.gitignore index 6397790..71be575 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ at-advent.db-shm at-advent.db-wal /web/node_modules .DS_Store -node_modules \ No newline at end of file +node_modules +/local diff --git a/Cargo.lock b/Cargo.lock index d9bf96e..105e85c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -169,6 +191,20 @@ dependencies = [ "web-time", ] +[[package]] +name = "atrium-crypto" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03dd32c12479c334544ff2a0066d591530e9738edafead49a92e137f0d360267" +dependencies = [ + "ecdsa", + "generic-array", + "k256", + "multibase", + "p256", + "thiserror 1.0.69", +] + [[package]] name = "atrium-identity" version = "0.1.8" @@ -214,6 +250,26 @@ dependencies = [ "trait-variant", ] +[[package]] +name = "atrium-repo" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02714b0216d58f67f2980894c2a8febb589e8f55088ad9c0ac6a1a0fc28cf068" +dependencies = [ + "async-stream", + "atrium-api", + "futures", + "ipld-core", + "serde", + "serde_bytes", + "serde_ipld_dagcbor", + "sha2", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "unsigned-varint", +] + [[package]] name = "atrium-xrpc" version = "0.12.4" @@ -447,6 +503,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cbor4ii" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.2.30" @@ -817,6 +882,7 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", + "spki", ] [[package]] @@ -840,6 +906,7 @@ dependencies = [ "ff", "generic-array", "group", + "pkcs8", "rand_core 0.6.4", "sec1", "subtle", @@ -1066,6 +1133,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1667,6 +1735,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "langtag" version = "0.3.4" @@ -2293,7 +2373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2313,7 +2393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2327,9 +2407,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.3", ] @@ -2613,6 +2693,7 @@ dependencies = [ "base16ct", "der", "generic-array", + "pkcs8", "subtle", "zeroize", ] @@ -2688,6 +2769,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_ipld_dagcbor" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" +dependencies = [ + "cbor4ii", + "ipld-core", + "scopeguard", + "serde", +] + [[package]] name = "serde_json" version = "1.0.141" @@ -2766,8 +2859,10 @@ dependencies = [ "async-trait", "atrium-api", "atrium-common", + "atrium-crypto", "atrium-identity", "atrium-oauth", + "atrium-repo", "atrium-xrpc-client", "axum", "bb8", @@ -2782,6 +2877,7 @@ dependencies = [ "serde_json", "sqlx", "thiserror 1.0.69", + "tokio", ] [[package]] @@ -3299,6 +3395,7 @@ checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -3553,6 +3650,10 @@ name = "unsigned-varint" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" +dependencies = [ + "futures-io", + "futures-util", +] [[package]] name = "url" diff --git a/Cargo.toml b/Cargo.toml index ec00f5f..738ec1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,12 @@ resolver = "2" [workspace.dependencies] axum = "0.8.4" atrium-common = "0.1.3" +atrium-crypto = "0.1.3" atrium-api = "0.25.7" atrium-identity = "0.1.8" atrium-xrpc-client = "0.5.15" atrium-oauth = "0.1.6" +atrium-repo = "0.1.7" chrono = { version = "0.4", features = ["serde", "now"] } hickory-resolver = "0.24.1" dotenv = "0.15.0" @@ -25,4 +27,4 @@ bb8-redis = "0.24.0" redis = "0.32.4" tokio = { version = "1.46.1", features = ["full"] } markdown = "1.0.0" -rust-embed = { version = "8.7.2", features = ["include-exclude"] } \ No newline at end of file +rust-embed = { version = "8.7.2", features = ["include-exclude"] } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b4825a..e585908 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -6,9 +6,11 @@ edition = "2024" [dependencies] axum.workspace = true atrium-common.workspace = true +atrium-crypto.workspace = true atrium-api.workspace = true atrium-identity.workspace = true atrium-oauth.workspace = true +atrium-repo.workspace = true bb8.workspace = true bb8-redis.workspace = true hickory-resolver.workspace = true @@ -23,3 +25,4 @@ rand = "0.9.2" handlebars = { version = "6.3.2" } async-trait = "0.1.88" atrium-xrpc-client.workspace = true +tokio.workspace = true diff --git a/shared/src/advent/challenges/day_three.rs b/shared/src/advent/challenges/day_three/mod.rs similarity index 94% rename from shared/src/advent/challenges/day_three.rs rename to shared/src/advent/challenges/day_three/mod.rs index 2cfbb66..ea590b0 100644 --- a/shared/src/advent/challenges/day_three.rs +++ b/shared/src/advent/challenges/day_three/mod.rs @@ -1,3 +1,7 @@ +pub mod repo; + +pub use repo::manufacture_car; + use crate::OAuthAgentType; use crate::advent::day::Day; use crate::advent::{AdventChallenge, AdventError, ChallengeCheckResponse}; diff --git a/shared/src/advent/challenges/day_three/repo.rs b/shared/src/advent/challenges/day_three/repo.rs new file mode 100644 index 0000000..1ea1dd4 --- /dev/null +++ b/shared/src/advent/challenges/day_three/repo.rs @@ -0,0 +1,77 @@ +use std::io::Cursor; + +use atrium_api::types::string::Did; +use atrium_crypto::keypair::P256Keypair; +use atrium_repo::{Repository, blockstore::{CarStore, MemoryBlockStore}}; +use serde::Serialize; + +/// secret record type +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChallengeRecord { + pub created_at: String, + pub verification_code: String, + pub message: String, +} + +/// make a single-record atproto car +/// +/// returns CAR bytes +pub async fn manufacture_car( + did: Did, + collection: &str, + rkey: &str, + record: ChallengeRecord, +) -> Result, Box> { + + // throwaway (fake key hello) + let keypair = P256Keypair::import(&[1u8; 32])?; + + let mut mem = MemoryBlockStore::new(); + let builder = Repository::create(&mut mem, did).await?; + let sig = keypair.sign(&builder.bytes())?; + let mut repo = builder.finalize(sig).await?; + + // add record + let key = format!("{collection}/{rkey}"); + let (cb, _cid) = repo.add_raw(&key, record).await?; + let sig = keypair.sign(&cb.bytes())?; + cb.finalize(sig).await?; + + // to the car! + let mut car_bytes = Vec::new(); + { + let cursor = Cursor::new(&mut car_bytes); // something CarStore can use + let mut car = CarStore::create_with_roots(cursor, [repo.root()]).await?; + repo.export_into(&mut car).await?; + } + + Ok(car_bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_generate_car() { + let car_bytes = manufacture_car( + "did:plc:somebodyyyy".parse().unwrap(), + "codes.advent.supersecret.verification", + "1337", + ChallengeRecord { + created_at: "2026-01-01T12:00:00Z".into(), + verification_code: "ABCDE-FGHIJ".to_string(), + message: "what did the muffin say to the other muffin?".into(), + }, + ) + .await + .expect("build a car"); + + assert!( + car_bytes.len() > 100, + "car too small to be real: {} bytes", + car_bytes.len() + ); + } +}