chore: restructure concord crate (#50)
Reviewed-on: #50
This commit was merged in pull request #50.
This commit is contained in:
Generated
+119
-81
@@ -301,9 +301,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.47"
|
version = "0.4.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef217a77a86a6e3dab9a5b3c81dc445b603fe743a90c1cb10a2f2144628d8cfa"
|
checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-codecs",
|
"compression-codecs",
|
||||||
"compression-core",
|
"compression-core",
|
||||||
@@ -437,7 +437,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -903,7 +903,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1008,9 +1008,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
@@ -1244,7 +1244,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui_util",
|
"gpui_util",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1286,9 +1286,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compression-codecs"
|
name = "compression-codecs"
|
||||||
version = "0.4.42"
|
version = "0.4.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "257c7085cbb71be72d8fb97edff08b03d86d5d9f2222b9cc34a6bec87093bc10"
|
checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"compression-core",
|
"compression-core",
|
||||||
@@ -1793,7 +1793,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1881,7 +1881,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.2",
|
"redox_users 0.5.3",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1911,7 +1911,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2352,7 +2352,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2469,7 +2469,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2706,7 +2706,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -2735,6 +2735,8 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"lyon",
|
"lyon",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-video",
|
||||||
"parking",
|
"parking",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
@@ -2788,7 +2790,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_apple"
|
name = "gpui_apple"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"block",
|
"block",
|
||||||
@@ -2812,7 +2814,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_linux"
|
name = "gpui_linux"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
@@ -2858,13 +2860,12 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macos"
|
name = "gpui_macos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_macos",
|
"accesskit_macos",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-task",
|
"async-task",
|
||||||
"block",
|
|
||||||
"block2 0.6.2",
|
"block2 0.6.2",
|
||||||
"cocoa 0.26.0",
|
"cocoa 0.26.0",
|
||||||
"collections",
|
"collections",
|
||||||
@@ -2884,11 +2885,11 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mach2",
|
"mach2",
|
||||||
"media",
|
|
||||||
"metal",
|
|
||||||
"objc",
|
"objc",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit 0.3.2",
|
"objc2-app-kit 0.3.2",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-core-media",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"objc2-screen-capture-kit",
|
"objc2-screen-capture-kit",
|
||||||
"objc2-user-notifications",
|
"objc2-user-notifications",
|
||||||
@@ -2905,7 +2906,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2916,7 +2917,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_platform"
|
name = "gpui_platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2929,7 +2930,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_shared_string"
|
name = "gpui_shared_string"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2939,7 +2940,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2950,7 +2951,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_util"
|
name = "gpui_util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"log",
|
"log",
|
||||||
@@ -2960,7 +2961,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_web"
|
name = "gpui_web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
@@ -2988,7 +2989,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_wgpu"
|
name = "gpui_wgpu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -3014,7 +3015,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_windows"
|
name = "gpui_windows"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_windows",
|
"accesskit_windows",
|
||||||
@@ -3289,7 +3290,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -3309,7 +3310,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rustls",
|
"rustls",
|
||||||
@@ -4085,20 +4086,6 @@ dependencies = [
|
|||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "media"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bindgen",
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"core-video",
|
|
||||||
"foreign-types",
|
|
||||||
"metal",
|
|
||||||
"objc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
@@ -4225,7 +4212,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4677,6 +4664,28 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-audio"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||||
|
dependencies = [
|
||||||
|
"dispatch2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-audio-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-data"
|
name = "objc2-core-data"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -4735,6 +4744,34 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-media"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-audio",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-video",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-video"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-io-surface",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-encode"
|
name = "objc2-encode"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -4834,8 +4871,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394"
|
checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.6.2",
|
"block2 0.6.2",
|
||||||
|
"dispatch2",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
|
"objc2-core-media",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5078,7 +5117,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5829,11 +5868,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.17",
|
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
]
|
]
|
||||||
@@ -5855,13 +5893,13 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
@@ -5943,7 +5981,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -6086,9 +6124,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -6244,7 +6282,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "scheduler"
|
name = "scheduler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-task",
|
"async-task",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
@@ -6280,7 +6318,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_derive_internals",
|
"serde_derive_internals",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6432,7 +6470,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6443,7 +6481,7 @@ checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6477,7 +6515,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6841,7 +6879,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless 0.9.3",
|
"heapless 0.9.3",
|
||||||
"log",
|
"log",
|
||||||
@@ -6969,9 +7007,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.5"
|
version = "3.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7004,7 +7042,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7159,7 +7197,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7298,7 +7336,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7697,9 +7735,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "ab72a15cf68d77cb0987d3684aa8a45c5ef827e8cb49ee2f30bfd7ba2feb519f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
@@ -7801,9 +7839,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ureq-proto"
|
name = "ureq-proto"
|
||||||
version = "0.6.3"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b0809a01d1ca5a51ca70db32bb2a19157582a526505ef3c19e3b343a59aa5ad"
|
checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"http",
|
"http",
|
||||||
@@ -7872,7 +7910,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -8053,7 +8091,7 @@ dependencies = [
|
|||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9223,7 +9261,7 @@ checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9295,7 +9333,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
"zvariant",
|
"zvariant",
|
||||||
"zvariant_utils",
|
"zvariant_utils",
|
||||||
@@ -9483,7 +9521,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9537,7 +9575,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9549,7 +9587,7 @@ checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "zlog"
|
name = "zlog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -9566,7 +9604,7 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing"
|
name = "ztracing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -9577,7 +9615,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing_macro"
|
name = "ztracing_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#739fdbef762f7e514d62e1fa650c1b7cac5bbff4"
|
source = "git+https://github.com/zed-industries/zed#4b47ceb9d328035bde5ef99009e674d9fee07fb7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
@@ -9628,7 +9666,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"zvariant_utils",
|
"zvariant_utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9641,7 +9679,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"syn 3.0.5",
|
"syn 3.0.6",
|
||||||
"winnow 1.0.4",
|
"winnow 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ use anyhow::Result;
|
|||||||
use data_encoding::HEXLOWER;
|
use data_encoding::HEXLOWER;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
use crate::edition::{
|
use crate::cord01::{
|
||||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
|
||||||
};
|
|
||||||
use crate::stream::{
|
|
||||||
KIND_WRAP, OpenedStream, SealForm, StreamError, build_rumor_ms, build_seal, open_wrap,
|
KIND_WRAP, OpenedStream, SealForm, StreamError, build_rumor_ms, build_seal, open_wrap,
|
||||||
wrap_seal,
|
wrap_seal,
|
||||||
};
|
};
|
||||||
|
use crate::cord04::{
|
||||||
|
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||||
|
};
|
||||||
use crate::{GroupKey, decode_hex_32};
|
use crate::{GroupKey, decode_hex_32};
|
||||||
|
|
||||||
pub const KIND_JOIN_LEAVE: u16 = 3306;
|
pub const KIND_JOIN_LEAVE: u16 = 3306;
|
||||||
@@ -532,8 +532,8 @@ fn pubkey(hex: &str, name: &'static str) -> Result<PublicKey, GuestbookError> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::cord01::build_rumor_secs;
|
||||||
use crate::derive::guestbook_group_key;
|
use crate::derive::guestbook_group_key;
|
||||||
use crate::stream::build_rumor_secs;
|
|
||||||
use crate::{CommunityId, Epoch};
|
use crate::{CommunityId, Epoch};
|
||||||
|
|
||||||
const ROOT: [u8; 32] = [0x5au8; 32];
|
const ROOT: [u8; 32] = [0x5au8; 32];
|
||||||
@@ -5,8 +5,8 @@ use std::fmt;
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::invite::{ChannelGrant, CommunityInvite};
|
use crate::cord01::{self, NIP44_MAX_PLAINTEXT};
|
||||||
use crate::stream::{self, NIP44_MAX_PLAINTEXT};
|
use crate::cord05::{ChannelGrant, CommunityInvite};
|
||||||
use crate::{CommunityId, Epoch, Extra};
|
use crate::{CommunityId, Epoch, Extra};
|
||||||
|
|
||||||
pub const KIND_COMMUNITY_LIST: u16 = 13302;
|
pub const KIND_COMMUNITY_LIST: u16 = 13302;
|
||||||
@@ -42,8 +42,8 @@ impl fmt::Display for ListError {
|
|||||||
|
|
||||||
impl std::error::Error for ListError {}
|
impl std::error::Error for ListError {}
|
||||||
|
|
||||||
impl From<stream::StreamError> for ListError {
|
impl From<cord01::StreamError> for ListError {
|
||||||
fn from(error: stream::StreamError) -> Self {
|
fn from(error: cord01::StreamError) -> Self {
|
||||||
ListError::Crypto(error.to_string())
|
ListError::Crypto(error.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ pub fn build_list_event(keys: &Keys, list: &CommunityList) -> Result<Event, List
|
|||||||
list.fits()?;
|
list.fits()?;
|
||||||
|
|
||||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||||
let content = stream::seal_to_self(keys, json.as_bytes())?;
|
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||||
|
|
||||||
EventBuilder::new(Kind::Custom(KIND_COMMUNITY_LIST), content)
|
EventBuilder::new(Kind::Custom(KIND_COMMUNITY_LIST), content)
|
||||||
.finalize(keys)
|
.finalize(keys)
|
||||||
@@ -199,7 +199,7 @@ pub fn parse_list_event(keys: &Keys, event: &Event) -> Result<CommunityList, Lis
|
|||||||
return Err(ListError::Kind(event.kind.as_u16()));
|
return Err(ListError::Kind(event.kind.as_u16()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = stream::open_to_self(keys, &event.content)?;
|
let json = cord01::open_to_self(keys, &event.content)?;
|
||||||
|
|
||||||
serde_json::from_slice(&json).map_err(json_error)
|
serde_json::from_slice(&json).map_err(json_error)
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
|
pub mod guestbook;
|
||||||
|
pub mod list;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, Timestamp, UnsignedEvent};
|
use nostr_sdk::prelude::{Event, Keys, PublicKey, Timestamp, UnsignedEvent};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::cord01::{KIND_WRAP, SealForm, build_seal, open_wrap_at, wrap_seal_with};
|
||||||
|
use crate::cord04::roles::{
|
||||||
|
AuthorityEdition, CommunityRoles, Grant, MAX_BANLIST, Permissions, Role, Roster, citation_ok,
|
||||||
|
fold_roster,
|
||||||
|
};
|
||||||
|
use crate::cord04::{
|
||||||
|
AuthorityCitation, EditionFields, EditionMeta, EntityHead, Floors, ParsedEdition,
|
||||||
|
build_edition, fold_head, parse_edition, vsk,
|
||||||
|
};
|
||||||
use crate::derive::{
|
use crate::derive::{
|
||||||
banlist_locator, community_id_of, control_group_key, control_signer_group_key, grant_locator,
|
banlist_locator, community_id_of, control_group_key, control_signer_group_key, grant_locator,
|
||||||
invite_links_locator, pins_locator, verify_community_id,
|
invite_links_locator, pins_locator, verify_community_id,
|
||||||
};
|
};
|
||||||
use crate::edition::{
|
|
||||||
AuthorityCitation, EditionFields, EditionMeta, EntityHead, Floors, ParsedEdition,
|
|
||||||
build_edition, fold_head, parse_edition, vsk,
|
|
||||||
};
|
|
||||||
use crate::roles::{
|
|
||||||
AuthorityEdition, CommunityRoles, Grant, MAX_BANLIST, Permissions, Role, Roster, citation_ok,
|
|
||||||
fold_roster,
|
|
||||||
};
|
|
||||||
use crate::stream::{KIND_WRAP, SealForm, build_seal, open_wrap_at, wrap_seal_with};
|
|
||||||
use crate::{ChannelId, CommunityId, Epoch, Extra, GroupKey, random_32};
|
use crate::{ChannelId, CommunityId, Epoch, Extra, GroupKey, random_32};
|
||||||
|
|
||||||
pub const MAX_NAME_BYTES: usize = 64;
|
pub const MAX_NAME_BYTES: usize = 64;
|
||||||
@@ -731,12 +734,13 @@ mod tests {
|
|||||||
use nostr_memory::MemoryDatabase;
|
use nostr_memory::MemoryDatabase;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::chat::{self, build_message, seal_rumor};
|
use crate::cord03::{self, build_message, seal_rumor};
|
||||||
|
use crate::cord04::fold;
|
||||||
|
use crate::cord04::pins;
|
||||||
|
use crate::cord04::roles::{Grant, MAX_BANLIST, MAX_ROLES_PER_MEMBER, Role, RoleScope};
|
||||||
use crate::derive::{channel_group_key, grant_locator};
|
use crate::derive::{channel_group_key, grant_locator};
|
||||||
use crate::edition::fold;
|
|
||||||
use crate::roles::{Grant, MAX_BANLIST, MAX_ROLES_PER_MEMBER, Role, RoleScope};
|
|
||||||
use crate::store::{CommunityState, load_state, save_state};
|
use crate::store::{CommunityState, load_state, save_state};
|
||||||
use crate::{Extra, RoleId, pins};
|
use crate::{Extra, RoleId};
|
||||||
|
|
||||||
const AT: u64 = 1_700_000_000;
|
const AT: u64 = 1_700_000_000;
|
||||||
|
|
||||||
@@ -1102,7 +1106,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let (wrap, _) = seal_rumor(&rumor, &group, &author, false).expect("seals");
|
let (wrap, _) = seal_rumor(&rumor, &group, &author, false).expect("seals");
|
||||||
let opened = chat::open(&wrap, &group, &channel, ROOT_EPOCH)
|
let opened = cord03::open(&wrap, &group, &channel, ROOT_EPOCH)
|
||||||
.expect("opens")
|
.expect("opens")
|
||||||
.0;
|
.0;
|
||||||
let entry = pins::build_entry(&opened, &group, &channel).expect("pins");
|
let entry = pins::build_entry(&opened, &group, &channel).expect("pins");
|
||||||
@@ -5,15 +5,15 @@ use std::fmt;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
use crate::derive::channel_group_key;
|
use crate::cord01::{
|
||||||
use crate::edition::{
|
|
||||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
|
||||||
};
|
|
||||||
use crate::stream::{
|
|
||||||
KIND_WRAP, KIND_WRAP_EPHEMERAL, OpenedStream, SealForm, StreamError, build_rumor_ms,
|
KIND_WRAP, KIND_WRAP_EPHEMERAL, OpenedStream, SealForm, StreamError, build_rumor_ms,
|
||||||
build_seal, channel_binding_tags, check_channel_binding, open_wrap, resolve_ms_strict,
|
build_seal, channel_binding_tags, check_channel_binding, open_wrap, resolve_ms_strict,
|
||||||
wrap_seal,
|
wrap_seal,
|
||||||
};
|
};
|
||||||
|
use crate::cord04::{
|
||||||
|
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||||
|
};
|
||||||
|
use crate::derive::channel_group_key;
|
||||||
use crate::{ChannelId, Epoch, GroupKey, decode_hex_32};
|
use crate::{ChannelId, Epoch, GroupKey, decode_hex_32};
|
||||||
|
|
||||||
pub const KIND_MESSAGE: u16 = 9;
|
pub const KIND_MESSAGE: u16 = 9;
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
pub mod pins;
|
||||||
|
pub mod roles;
|
||||||
|
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -7,8 +10,8 @@ use nostr_sdk::prelude::{EventId, PublicKey, Tag, UnsignedEvent};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
use crate::cord01::build_rumor_secs;
|
||||||
use crate::decode_hex_32;
|
use crate::decode_hex_32;
|
||||||
use crate::stream::build_rumor_secs;
|
|
||||||
|
|
||||||
pub const KIND_CONTROL: u16 = 3308;
|
pub const KIND_CONTROL: u16 = 3308;
|
||||||
|
|
||||||
@@ -9,9 +9,9 @@ use nostr_sdk::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
use crate::chat::{ChatAction, ChatRumor, KIND_COMMENT, KIND_EDIT, KIND_MESSAGE};
|
use crate::cord01::{self, OpenedStream, SealForm, resolve_ms_strict};
|
||||||
use crate::edition::canonical_decimal;
|
use crate::cord03::{ChatAction, ChatRumor, KIND_COMMENT, KIND_EDIT, KIND_MESSAGE};
|
||||||
use crate::stream::{self, OpenedStream, SealForm, resolve_ms_strict};
|
use crate::cord04::canonical_decimal;
|
||||||
use crate::{ChannelId, Epoch, Extra, GroupKey, decode_hex_lower};
|
use crate::{ChannelId, Epoch, Extra, GroupKey, decode_hex_lower};
|
||||||
|
|
||||||
pub const PIN_MAX_ENTRIES: usize = 25;
|
pub const PIN_MAX_ENTRIES: usize = 25;
|
||||||
@@ -315,7 +315,7 @@ fn disclosed_keys(opened: &OpenedStream, group: &GroupKey) -> Result<MessageKeys
|
|||||||
pub fn verify_entry(entry: &PinEntry, channel: &ChannelId) -> Option<VerifiedPin> {
|
pub fn verify_entry(entry: &PinEntry, channel: &ChannelId) -> Option<VerifiedPin> {
|
||||||
let seal = &entry.seal;
|
let seal = &entry.seal;
|
||||||
|
|
||||||
if seal.kind.as_u16() != stream::KIND_SEAL_ENCRYPTED || seal.verify().is_err() {
|
if seal.kind.as_u16() != cord01::KIND_SEAL_ENCRYPTED || seal.verify().is_err() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ fn verify_edit_bundle(
|
|||||||
let seal = &bundle.seal;
|
let seal = &bundle.seal;
|
||||||
|
|
||||||
// Checkable before any crypto: nobody else may revise another member's words.
|
// Checkable before any crypto: nobody else may revise another member's words.
|
||||||
if seal.kind.as_u16() != stream::KIND_SEAL_ENCRYPTED || seal.pubkey != *original_author {
|
if seal.kind.as_u16() != cord01::KIND_SEAL_ENCRYPTED || seal.pubkey != *original_author {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +456,7 @@ fn serialize_sealed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inner = encode_form(entries)?;
|
let inner = encode_form(entries)?;
|
||||||
let sealed = stream::seal_bytes(group.conversation(), inner.as_bytes())
|
let sealed = cord01::seal_bytes(group.conversation(), inner.as_bytes())
|
||||||
.map_err(|error| PinError::Seal(error.to_string()))?;
|
.map_err(|error| PinError::Seal(error.to_string()))?;
|
||||||
let content = serde_json::json!({ "epoch": epoch.to_string(), "sealed": sealed }).to_string();
|
let content = serde_json::json!({ "epoch": epoch.to_string(), "sealed": sealed }).to_string();
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ pub fn read_list(content: &str, unseal: impl Fn(Epoch) -> Option<GroupKey>) -> R
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(inner) = stream::open_bytes(group.conversation(), sealed) else {
|
let Ok(inner) = cord01::open_bytes(group.conversation(), sealed) else {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -554,7 +554,7 @@ mod tests {
|
|||||||
use nostr::nips::nip44::v2::{self, ConversationKey};
|
use nostr::nips::nip44::v2::{self, ConversationKey};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::chat::{ChatRumor, build_delete, build_edit, build_message, open, seal_rumor};
|
use crate::cord03::{ChatRumor, build_delete, build_edit, build_message, open, seal_rumor};
|
||||||
use crate::derive::channel_group_key;
|
use crate::derive::channel_group_key;
|
||||||
|
|
||||||
const AT_MS: u64 = 1_700_000_000_000;
|
const AT_MS: u64 = 1_700_000_000_000;
|
||||||
@@ -657,7 +657,7 @@ mod tests {
|
|||||||
assert!(verify_entry(&forged, &channel()).is_none());
|
assert!(verify_entry(&forged, &channel()).is_none());
|
||||||
|
|
||||||
// A rumor carrying a claimed id that is not its own is refused.
|
// A rumor carrying a claimed id that is not its own is refused.
|
||||||
let plaintext = stream::open_bytes(&conversation(), &opened.seal.content).expect("opens");
|
let plaintext = cord01::open_bytes(&conversation(), &opened.seal.content).expect("opens");
|
||||||
let mut value: serde_json::Value = serde_json::from_slice(&plaintext).expect("json");
|
let mut value: serde_json::Value = serde_json::from_slice(&plaintext).expect("json");
|
||||||
value["id"] = serde_json::Value::String("00".repeat(32));
|
value["id"] = serde_json::Value::String("00".repeat(32));
|
||||||
|
|
||||||
@@ -668,7 +668,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("encrypts");
|
.expect("encrypts");
|
||||||
let content = BASE64.encode(&raw);
|
let content = BASE64.encode(&raw);
|
||||||
let seal = EventBuilder::new(Kind::Custom(stream::KIND_SEAL_ENCRYPTED), &content)
|
let seal = EventBuilder::new(Kind::Custom(cord01::KIND_SEAL_ENCRYPTED), &content)
|
||||||
.custom_created_at(opened.seal.created_at)
|
.custom_created_at(opened.seal.created_at)
|
||||||
.finalize(&author)
|
.finalize(&author)
|
||||||
.expect("signs");
|
.expect("signs");
|
||||||
@@ -4,10 +4,10 @@ use anyhow::{Result, bail};
|
|||||||
use nostr_sdk::prelude::PublicKey;
|
use nostr_sdk::prelude::PublicKey;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::derive::{banlist_locator, grant_locator};
|
use crate::cord04::{
|
||||||
use crate::edition::{
|
|
||||||
AuthorityCitation, EditionMeta, EntityHead, Floors, ParsedEdition, fold_head, vsk,
|
AuthorityCitation, EditionMeta, EntityHead, Floors, ParsedEdition, fold_head, vsk,
|
||||||
};
|
};
|
||||||
|
use crate::derive::{banlist_locator, grant_locator};
|
||||||
use crate::{ChannelId, CommunityId, Extra, RoleId, decode_hex_32};
|
use crate::{ChannelId, CommunityId, Extra, RoleId, decode_hex_32};
|
||||||
|
|
||||||
pub const MAX_ROLES_PER_COMMUNITY: usize = 100;
|
pub const MAX_ROLES_PER_COMMUNITY: usize = 100;
|
||||||
@@ -759,7 +759,7 @@ mod tests {
|
|||||||
use nostr_sdk::prelude::Keys;
|
use nostr_sdk::prelude::Keys;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::edition::{EditionFields, build_edition, parse_edition};
|
use crate::cord04::{EditionFields, build_edition, parse_edition};
|
||||||
|
|
||||||
const COMMUNITY: [u8; 32] = [0xc0; 32];
|
const COMMUNITY: [u8; 32] = [0xc0; 32];
|
||||||
const AT: u64 = 1_700_000_000;
|
const AT: u64 = 1_700_000_000;
|
||||||
@@ -10,11 +10,11 @@ use nostr::nips::nip59::{GiftWrapBuilder, UnwrappedGift};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::control::{ImageRef, MAX_RELAYS};
|
use crate::cord01::{self, NIP44_MAX_PLAINTEXT, StreamError};
|
||||||
|
use crate::cord02::list::{canonical, union};
|
||||||
|
use crate::cord02::{ImageRef, MAX_RELAYS};
|
||||||
|
use crate::cord04::{TAG_SUBKIND, vsk};
|
||||||
use crate::derive::{TOKEN_LEN, verify_community_id};
|
use crate::derive::{TOKEN_LEN, verify_community_id};
|
||||||
use crate::edition::{TAG_SUBKIND, vsk};
|
|
||||||
use crate::list::{canonical, union};
|
|
||||||
use crate::stream::{self, NIP44_MAX_PLAINTEXT, StreamError};
|
|
||||||
use crate::{ChannelId, CommunityId, Epoch, Extra, decode_hex_32};
|
use crate::{ChannelId, CommunityId, Epoch, Extra, decode_hex_32};
|
||||||
|
|
||||||
pub const KIND_BUNDLE: u16 = 33301;
|
pub const KIND_BUNDLE: u16 = 33301;
|
||||||
@@ -594,7 +594,7 @@ pub fn build_invite_list(keys: &Keys, list: &InviteList) -> Result<Event, Invite
|
|||||||
list.fits()?;
|
list.fits()?;
|
||||||
|
|
||||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||||
let content = stream::seal_to_self(keys, json.as_bytes())?;
|
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||||
|
|
||||||
EventBuilder::new(Kind::Custom(KIND_INVITE_LIST), content)
|
EventBuilder::new(Kind::Custom(KIND_INVITE_LIST), content)
|
||||||
.finalize(keys)
|
.finalize(keys)
|
||||||
@@ -606,7 +606,7 @@ pub fn parse_invite_list(keys: &Keys, event: &Event) -> Result<InviteList, Invit
|
|||||||
return Err(InviteError::Kind(event.kind.as_u16()));
|
return Err(InviteError::Kind(event.kind.as_u16()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = stream::open_to_self(keys, &event.content)?;
|
let json = cord01::open_to_self(keys, &event.content)?;
|
||||||
|
|
||||||
serde_json::from_slice(&json).map_err(json_error)
|
serde_json::from_slice(&json).map_err(json_error)
|
||||||
}
|
}
|
||||||
@@ -626,14 +626,14 @@ fn merge_entry(held: &InviteEntry, incoming: &InviteEntry) -> InviteEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn seal_bundle(bundle_key: &[u8; 32], json: &str) -> Result<String, InviteError> {
|
fn seal_bundle(bundle_key: &[u8; 32], json: &str) -> Result<String, InviteError> {
|
||||||
Ok(stream::seal_bytes(
|
Ok(cord01::seal_bytes(
|
||||||
&ConversationKey::new(*bundle_key),
|
&ConversationKey::new(*bundle_key),
|
||||||
json.as_bytes(),
|
json.as_bytes(),
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_bundle(bundle_key: &[u8; 32], content: &str) -> Result<String, InviteError> {
|
fn open_bundle(bundle_key: &[u8; 32], content: &str) -> Result<String, InviteError> {
|
||||||
let plaintext = stream::open_bytes(&ConversationKey::new(*bundle_key), content)?;
|
let plaintext = cord01::open_bytes(&ConversationKey::new(*bundle_key), content)?;
|
||||||
|
|
||||||
String::from_utf8(plaintext).map_err(|_| InviteError::BadFragment("bundle is not utf8"))
|
String::from_utf8(plaintext).map_err(|_| InviteError::BadFragment("bundle is not utf8"))
|
||||||
}
|
}
|
||||||
@@ -941,7 +941,7 @@ mod tests {
|
|||||||
assert!(unwrap_direct_invite(&wrap, &stranger).is_err());
|
assert!(unwrap_direct_invite(&wrap, &stranger).is_err());
|
||||||
|
|
||||||
// ...and a wrap that opens to some other kind is not an invite.
|
// ...and a wrap that opens to some other kind is not an invite.
|
||||||
let rumor = EventBuilder::new(Kind::Custom(crate::chat::KIND_MESSAGE), "hello")
|
let rumor = EventBuilder::new(Kind::Custom(crate::cord03::KIND_MESSAGE), "hello")
|
||||||
.finalize_unsigned(recipient.public_key());
|
.finalize_unsigned(recipient.public_key());
|
||||||
let wrap = GiftWrapBuilder::new(recipient.public_key(), rumor)
|
let wrap = GiftWrapBuilder::new(recipient.public_key(), rumor)
|
||||||
.finalize(&recipient)
|
.finalize(&recipient)
|
||||||
@@ -7,17 +7,17 @@ use nostr::nips::nip44::v2::ConversationKey;
|
|||||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, SecretKey, Tag, Timestamp, UnsignedEvent};
|
use nostr_sdk::prelude::{Event, Keys, PublicKey, SecretKey, Tag, Timestamp, UnsignedEvent};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::control::CommunityIdentity;
|
use crate::cord01::{self, KIND_SEAL_PLAINTEXT, OpenedStream, SealForm, StreamError};
|
||||||
|
use crate::cord02::CommunityIdentity;
|
||||||
|
use crate::cord04::roles::CommunityRoles;
|
||||||
|
use crate::cord04::{
|
||||||
|
AuthorityCitation, KIND_CONTROL, TAG_SUBKIND, canonical_decimal, citation_from, citation_tag,
|
||||||
|
vsk,
|
||||||
|
};
|
||||||
use crate::derive::{
|
use crate::derive::{
|
||||||
base_rekey_group_key, channel_rekey_group_key, control_group_key, control_signer_group_key,
|
base_rekey_group_key, channel_rekey_group_key, control_group_key, control_signer_group_key,
|
||||||
dissolved_group_key, epoch_key_commitment, recipient_locator,
|
dissolved_group_key, epoch_key_commitment, recipient_locator,
|
||||||
};
|
};
|
||||||
use crate::edition::{
|
|
||||||
AuthorityCitation, KIND_CONTROL, TAG_SUBKIND, canonical_decimal, citation_from, citation_tag,
|
|
||||||
vsk,
|
|
||||||
};
|
|
||||||
use crate::roles::CommunityRoles;
|
|
||||||
use crate::stream::{self, KIND_SEAL_PLAINTEXT, OpenedStream, SealForm, StreamError};
|
|
||||||
use crate::{ChannelId, CommunityId, Epoch, GroupKey, random_32};
|
use crate::{ChannelId, CommunityId, Epoch, GroupKey, random_32};
|
||||||
|
|
||||||
pub const KIND_REKEY: u16 = 3303;
|
pub const KIND_REKEY: u16 = 3303;
|
||||||
@@ -325,7 +325,7 @@ pub fn open_blob(
|
|||||||
) -> Result<KeyDelivery, RekeyError> {
|
) -> Result<KeyDelivery, RekeyError> {
|
||||||
let conversation =
|
let conversation =
|
||||||
ConversationKey::derive(recipient.secret_key(), rotator).map_err(crypto_error)?;
|
ConversationKey::derive(recipient.secret_key(), rotator).map_err(crypto_error)?;
|
||||||
let plaintext = stream::open_bytes(&conversation, &blob.wrapped)?;
|
let plaintext = cord01::open_bytes(&conversation, &blob.wrapped)?;
|
||||||
|
|
||||||
parse_blob_plaintext(&plaintext, scope, epoch, community_id)
|
parse_blob_plaintext(&plaintext, scope, epoch, community_id)
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,7 @@ fn seal_to(
|
|||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
) -> Result<String, RekeyError> {
|
) -> Result<String, RekeyError> {
|
||||||
let conversation = ConversationKey::derive(secret, recipient).map_err(crypto_error)?;
|
let conversation = ConversationKey::derive(secret, recipient).map_err(crypto_error)?;
|
||||||
Ok(stream::seal_bytes(&conversation, plaintext)?)
|
Ok(cord01::seal_bytes(&conversation, plaintext)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -539,7 +539,7 @@ pub fn compact(
|
|||||||
let mut wraps = Vec::with_capacity(seals.len());
|
let mut wraps = Vec::with_capacity(seals.len());
|
||||||
|
|
||||||
for seal in seals {
|
for seal in seals {
|
||||||
wraps.push(stream::rewrap_seal(seal, read, signer, at)?.0);
|
wraps.push(cord01::rewrap_seal(seal, read, signer, at)?.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(wraps)
|
Ok(wraps)
|
||||||
@@ -592,7 +592,7 @@ pub fn build_rekey_rumor(
|
|||||||
tags.push(Tag::custom(TAG_SEVER, ["1"]));
|
tags.push(Tag::custom(TAG_SEVER, ["1"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(stream::build_rumor_secs(
|
Ok(cord01::build_rumor_secs(
|
||||||
KIND_REKEY, rotator, &content, tags, at_secs,
|
KIND_REKEY, rotator, &content, tags, at_secs,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -633,11 +633,11 @@ pub fn build_rekey_chunks(
|
|||||||
at_secs,
|
at_secs,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let seal = stream::build_seal(&rumor, SealForm::Encrypted, group, rotator)?;
|
let seal = cord01::build_seal(&rumor, SealForm::Encrypted, group, rotator)?;
|
||||||
let (wrap, _) = stream::wrap_seal(
|
let (wrap, _) = cord01::wrap_seal(
|
||||||
&seal,
|
&seal,
|
||||||
group,
|
group,
|
||||||
stream::KIND_WRAP,
|
cord01::KIND_WRAP,
|
||||||
Timestamp::from_secs(at_secs),
|
Timestamp::from_secs(at_secs),
|
||||||
&[],
|
&[],
|
||||||
)?;
|
)?;
|
||||||
@@ -704,7 +704,7 @@ pub fn parse_rekey_chunk(opened: &OpenedStream) -> Result<RekeyChunk, RekeyError
|
|||||||
prev_commit,
|
prev_commit,
|
||||||
chunk: parse_chunk(rumor)?,
|
chunk: parse_chunk(rumor)?,
|
||||||
blobs,
|
blobs,
|
||||||
citation: tag(rumor, crate::edition::TAG_CITATION)?.and_then(citation_from),
|
citation: tag(rumor, crate::cord04::TAG_CITATION)?.and_then(citation_from),
|
||||||
severed,
|
severed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -719,7 +719,7 @@ pub fn dissolved_tombstone_rumor(
|
|||||||
community_id: &CommunityId,
|
community_id: &CommunityId,
|
||||||
at_secs: u64,
|
at_secs: u64,
|
||||||
) -> UnsignedEvent {
|
) -> UnsignedEvent {
|
||||||
stream::build_rumor_secs(
|
cord01::build_rumor_secs(
|
||||||
KIND_CONTROL,
|
KIND_CONTROL,
|
||||||
owner,
|
owner,
|
||||||
"",
|
"",
|
||||||
@@ -738,11 +738,11 @@ pub fn seal_dissolved(
|
|||||||
at_secs: u64,
|
at_secs: u64,
|
||||||
) -> Result<Event, RekeyError> {
|
) -> Result<Event, RekeyError> {
|
||||||
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
||||||
let seal = stream::build_seal(rumor, SealForm::Plaintext, &group, owner)?;
|
let seal = cord01::build_seal(rumor, SealForm::Plaintext, &group, owner)?;
|
||||||
let (wrap, _) = stream::wrap_seal(
|
let (wrap, _) = cord01::wrap_seal(
|
||||||
&seal,
|
&seal,
|
||||||
&group,
|
&group,
|
||||||
stream::KIND_WRAP,
|
cord01::KIND_WRAP,
|
||||||
Timestamp::from_secs(at_secs),
|
Timestamp::from_secs(at_secs),
|
||||||
&[],
|
&[],
|
||||||
)?;
|
)?;
|
||||||
@@ -756,7 +756,7 @@ pub fn open_dissolved(
|
|||||||
community_id: &CommunityId,
|
community_id: &CommunityId,
|
||||||
) -> Result<DissolvedTombstone, RekeyError> {
|
) -> Result<DissolvedTombstone, RekeyError> {
|
||||||
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
||||||
let opened = stream::open_wrap(wrap, &group)?;
|
let opened = cord01::open_wrap(wrap, &group)?;
|
||||||
|
|
||||||
if !is_tombstone(&opened.rumor, community_id) {
|
if !is_tombstone(&opened.rumor, community_id) {
|
||||||
return Err(RekeyError::NotADissolution);
|
return Err(RekeyError::NotADissolution);
|
||||||
@@ -859,13 +859,13 @@ mod tests {
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::control::{
|
use crate::cord01::KIND_WRAP;
|
||||||
|
use crate::cord02::{
|
||||||
CommunityMetadata, ControlWriter, Edition, ROOT_EPOCH, fold_control, genesis, open_edition,
|
CommunityMetadata, ControlWriter, Edition, ROOT_EPOCH, fold_control, genesis, open_edition,
|
||||||
};
|
};
|
||||||
|
use crate::cord04::roles::{Grant, Permissions, Role, RoleScope};
|
||||||
|
use crate::cord04::{EditionFields, Floors, build_edition};
|
||||||
use crate::derive::{community_id_of, grant_locator};
|
use crate::derive::{community_id_of, grant_locator};
|
||||||
use crate::edition::{EditionFields, Floors, build_edition};
|
|
||||||
use crate::roles::{Grant, Permissions, Role, RoleScope};
|
|
||||||
use crate::stream::KIND_WRAP;
|
|
||||||
use crate::{Extra, RoleId};
|
use crate::{Extra, RoleId};
|
||||||
|
|
||||||
const AT: u64 = 1_700_000_000;
|
const AT: u64 = 1_700_000_000;
|
||||||
@@ -1074,7 +1074,7 @@ mod tests {
|
|||||||
.expect("builds");
|
.expect("builds");
|
||||||
assert_eq!(chunks.len(), 1);
|
assert_eq!(chunks.len(), 1);
|
||||||
|
|
||||||
let opened = stream::open_wrap(&chunks[0], &group).expect("opens");
|
let opened = cord01::open_wrap(&chunks[0], &group).expect("opens");
|
||||||
let chunk = parse_rekey_chunk(&opened).expect("parses");
|
let chunk = parse_rekey_chunk(&opened).expect("parses");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
chunk.rotator,
|
chunk.rotator,
|
||||||
@@ -1486,7 +1486,7 @@ mod tests {
|
|||||||
at_secs: AT,
|
at_secs: AT,
|
||||||
});
|
});
|
||||||
let seal =
|
let seal =
|
||||||
stream::build_seal(&rumor, SealForm::Plaintext, &prior_read, &owner).expect("seals");
|
cord01::build_seal(&rumor, SealForm::Plaintext, &prior_read, &owner).expect("seals");
|
||||||
|
|
||||||
let refounding = plan_refounding(Epoch(1)).expect("plans");
|
let refounding = plan_refounding(Epoch(1)).expect("plans");
|
||||||
let read = refounding.read(&community_id).expect("derives");
|
let read = refounding.read(&community_id).expect("derives");
|
||||||
@@ -1497,7 +1497,7 @@ mod tests {
|
|||||||
compact(std::slice::from_ref(&seal), &read, &signer, AT + 1).expect("compacts");
|
compact(std::slice::from_ref(&seal), &read, &signer, AT + 1).expect("compacts");
|
||||||
assert_eq!(compacted.len(), 1);
|
assert_eq!(compacted.len(), 1);
|
||||||
|
|
||||||
let reopened = stream::open_wrap_at(&compacted[0], &signer.pk(), read.conversation(), true)
|
let reopened = cord01::open_wrap_at(&compacted[0], &signer.pk(), read.conversation(), true)
|
||||||
.expect("opens");
|
.expect("opens");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reopened.seal.sig, seal.sig,
|
reopened.seal.sig, seal.sig,
|
||||||
@@ -1508,7 +1508,7 @@ mod tests {
|
|||||||
|
|
||||||
// Only a plaintext seal can be carried forward.
|
// Only a plaintext seal can be carried forward.
|
||||||
let encrypted =
|
let encrypted =
|
||||||
stream::build_seal(&rumor, SealForm::Encrypted, &prior_read, &owner).expect("seals");
|
cord01::build_seal(&rumor, SealForm::Encrypted, &prior_read, &owner).expect("seals");
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
compact(&[encrypted], &read, &signer, AT + 1),
|
compact(&[encrypted], &read, &signer, AT + 1),
|
||||||
Err(RekeyError::Stream(StreamError::NotRewrappable))
|
Err(RekeyError::Stream(StreamError::NotRewrappable))
|
||||||
@@ -1550,7 +1550,7 @@ mod tests {
|
|||||||
// The spec's all-zero `eid` is refused: it would let one owner's genuine
|
// The spec's all-zero `eid` is refused: it would let one owner's genuine
|
||||||
// tombstone be re-wrapped at another of their communities and kill it.
|
// tombstone be re-wrapped at another of their communities and kill it.
|
||||||
let zeroed = seal_dissolved(
|
let zeroed = seal_dissolved(
|
||||||
&stream::build_rumor_secs(
|
&cord01::build_rumor_secs(
|
||||||
KIND_CONTROL,
|
KIND_CONTROL,
|
||||||
owner.public_key(),
|
owner.public_key(),
|
||||||
"",
|
"",
|
||||||
@@ -1580,10 +1580,10 @@ mod tests {
|
|||||||
owner: owner.public_key(),
|
owner: owner.public_key(),
|
||||||
owner_salt: other_salt,
|
owner_salt: other_salt,
|
||||||
};
|
};
|
||||||
let seal = stream::open_wrap(&wrap, &dissolved_group_key(&community_id).expect("derives"))
|
let seal = cord01::open_wrap(&wrap, &dissolved_group_key(&community_id).expect("derives"))
|
||||||
.expect("opens")
|
.expect("opens")
|
||||||
.seal;
|
.seal;
|
||||||
let replayed = stream::wrap_seal(
|
let replayed = cord01::wrap_seal(
|
||||||
&seal,
|
&seal,
|
||||||
&dissolved_group_key(&other_id).expect("derives"),
|
&dissolved_group_key(&other_id).expect("derives"),
|
||||||
KIND_WRAP,
|
KIND_WRAP,
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//! One module per CORD document. CORD-07 (audio/video) is unimplemented, and
|
||||||
|
//! CORD-08's timer rides the Chat and Control planes it edits rather than owning a file.
|
||||||
|
|
||||||
|
pub mod cord01;
|
||||||
|
pub mod cord02;
|
||||||
|
pub mod cord03;
|
||||||
|
pub mod cord04;
|
||||||
|
pub mod cord05;
|
||||||
|
pub mod cord06;
|
||||||
+9
-143
@@ -1,146 +1,12 @@
|
|||||||
pub mod chat;
|
mod cords;
|
||||||
pub mod control;
|
mod types;
|
||||||
pub mod derive;
|
mod utils;
|
||||||
pub mod edition;
|
|
||||||
pub mod guestbook;
|
|
||||||
pub mod invite;
|
|
||||||
pub mod list;
|
|
||||||
pub mod pins;
|
|
||||||
pub mod rekey;
|
|
||||||
pub mod roles;
|
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub mod stream;
|
|
||||||
|
|
||||||
use std::fmt;
|
pub use cords::{cord01, cord02, cord03, cord04, cord05, cord06};
|
||||||
use std::str::FromStr;
|
pub use types::{ChannelId, CommunityId, Epoch, RoleId};
|
||||||
|
pub use utils::derive::{self, GroupKey};
|
||||||
|
|
||||||
use anyhow::{Result, anyhow, bail};
|
pub(crate) use types::Extra;
|
||||||
use data_encoding::HEXLOWER;
|
pub(crate) use utils::{decode_hex_32, decode_hex_lower, fill_random, random_32};
|
||||||
pub use derive::GroupKey;
|
|
||||||
use rand::TryRng as _;
|
|
||||||
use rand::rngs::SysRng;
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
|
|
||||||
/// Unknown fields a content struct does not model, so a republish cannot wipe them.
|
|
||||||
pub(crate) type Extra = serde_json::Map<String, serde_json::Value>;
|
|
||||||
|
|
||||||
macro_rules! hex_id {
|
|
||||||
($(#[$meta:meta])* $name:ident) => {
|
|
||||||
$(#[$meta])*
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct $name([u8; 32]);
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
|
||||||
Self(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_hex(&self) -> String {
|
|
||||||
HEXLOWER.encode(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<[u8; 32]> for $name {
|
|
||||||
fn from(bytes: [u8; 32]) -> Self {
|
|
||||||
Self(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for $name {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str(&self.to_hex())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for $name {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}({})", stringify!($name), self.to_hex())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for $name {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self> {
|
|
||||||
Ok(Self(decode_hex_32(value)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for $name {
|
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
serializer.serialize_str(&self.to_hex())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for $name {
|
|
||||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
let value = String::deserialize(deserializer)?;
|
|
||||||
value.parse().map_err(serde::de::Error::custom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
hex_id! {
|
|
||||||
/// A self-certifying commitment to the owner's key, never on the wire.
|
|
||||||
CommunityId
|
|
||||||
}
|
|
||||||
|
|
||||||
hex_id! {
|
|
||||||
ChannelId
|
|
||||||
}
|
|
||||||
|
|
||||||
hex_id! {
|
|
||||||
/// Both a Role's entity coordinate and the field it repeats in its own content.
|
|
||||||
RoleId
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A key-rotation counter; it bumps only on a Rekey that removes somebody.
|
|
||||||
#[derive(
|
|
||||||
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
pub struct Epoch(pub u64);
|
|
||||||
|
|
||||||
impl fmt::Display for Epoch {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uppercase and other non-canonical spellings are rejected.
|
|
||||||
pub(crate) fn decode_hex_32(value: &str) -> Result<[u8; 32]> {
|
|
||||||
decode_hex_lower::<32>(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn decode_hex_lower<const N: usize>(value: &str) -> Result<[u8; N]> {
|
|
||||||
let bytes = HEXLOWER
|
|
||||||
.decode(value.as_bytes())
|
|
||||||
.map_err(|error| anyhow!("invalid hex: {error}"))?;
|
|
||||||
|
|
||||||
let decoded: [u8; N] = bytes
|
|
||||||
.as_slice()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| anyhow!("expected {N} bytes, got {}", bytes.len()))?;
|
|
||||||
|
|
||||||
if HEXLOWER.encode(&decoded) != value {
|
|
||||||
bail!("hex must be lowercase and canonical");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn fill_random(bytes: &mut [u8]) -> Result<()> {
|
|
||||||
SysRng
|
|
||||||
.try_fill_bytes(bytes)
|
|
||||||
.map_err(|error| anyhow!("os rng: {error}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn random_32() -> Result<[u8; 32]> {
|
|
||||||
let mut bytes = [0u8; 32];
|
|
||||||
fill_random(&mut bytes)?;
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|||||||
+11
-10
@@ -6,13 +6,13 @@ use anyhow::{Result, anyhow};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::chat::{self, ChatRumor, plane_keys};
|
use crate::cord01::{KIND_WRAP_EPHEMERAL, OpenedStream};
|
||||||
use crate::control::{
|
use crate::cord02::{
|
||||||
ChannelMetadata, CommunityGenesis, CommunityMetadata, ControlFold, ROOT_EPOCH,
|
ChannelMetadata, CommunityGenesis, CommunityMetadata, ControlFold, ROOT_EPOCH,
|
||||||
};
|
};
|
||||||
|
use crate::cord03::{self, ChatRumor, plane_keys};
|
||||||
|
use crate::cord04::{EntityHead, Floors, ParsedEdition, vsk};
|
||||||
use crate::derive::control_signer_group_key;
|
use crate::derive::control_signer_group_key;
|
||||||
use crate::edition::{EntityHead, Floors, ParsedEdition, vsk};
|
|
||||||
use crate::stream::{KIND_WRAP_EPHEMERAL, OpenedStream};
|
|
||||||
use crate::{ChannelId, CommunityId, Epoch, GroupKey};
|
use crate::{ChannelId, CommunityId, Epoch, GroupKey};
|
||||||
|
|
||||||
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
|
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
|
||||||
@@ -31,7 +31,8 @@ pub async fn cache_rumor(
|
|||||||
channel: &ChannelId,
|
channel: &ChannelId,
|
||||||
opened: &OpenedStream,
|
opened: &OpenedStream,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if chat::expiration_of(&opened.rumor)?.is_some_and(|expiration| expiration <= Timestamp::now())
|
if cord03::expiration_of(&opened.rumor)?
|
||||||
|
.is_some_and(|expiration| expiration <= Timestamp::now())
|
||||||
{
|
{
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ pub async fn purge_expired(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(Some(expiration)) = chat::expiration_of(&rumor) else {
|
let Ok(Some(expiration)) = cord03::expiration_of(&rumor) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -374,7 +375,7 @@ fn advance(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok((opened, rumor)) = chat::open(wrap, group, channel, *epoch) else {
|
let Ok((opened, rumor)) = cord03::open(wrap, group, channel, *epoch) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -419,11 +420,11 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Epoch;
|
use crate::Epoch;
|
||||||
use crate::chat::{build_message, seal_rumor};
|
use crate::cord01::{
|
||||||
use crate::derive::channel_group_key;
|
|
||||||
use crate::stream::{
|
|
||||||
KIND_WRAP, SealForm, build_rumor_ms, build_seal, channel_binding_tags, open_wrap, wrap_seal,
|
KIND_WRAP, SealForm, build_rumor_ms, build_seal, channel_binding_tags, open_wrap, wrap_seal,
|
||||||
};
|
};
|
||||||
|
use crate::cord03::{build_message, seal_rumor};
|
||||||
|
use crate::derive::channel_group_key;
|
||||||
|
|
||||||
const SECRET: [u8; 32] = [0x07u8; 32];
|
const SECRET: [u8; 32] = [0x07u8; 32];
|
||||||
const NEXT_SECRET: [u8; 32] = [0x11u8; 32];
|
const NEXT_SECRET: [u8; 32] = [0x11u8; 32];
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use data_encoding::HEXLOWER;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::decode_hex_32;
|
||||||
|
|
||||||
|
/// Unknown fields a content struct does not model, so a republish cannot wipe them.
|
||||||
|
pub(crate) type Extra = serde_json::Map<String, serde_json::Value>;
|
||||||
|
|
||||||
|
macro_rules! hex_id {
|
||||||
|
($(#[$meta:meta])* $name:ident) => {
|
||||||
|
$(#[$meta])*
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct $name([u8; 32]);
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_hex(&self) -> String {
|
||||||
|
HEXLOWER.encode(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for $name {
|
||||||
|
fn from(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}({})", stringify!($name), self.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for $name {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self> {
|
||||||
|
Ok(Self(decode_hex_32(value)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for $name {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(&self.to_hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for $name {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let value = String::deserialize(deserializer)?;
|
||||||
|
value.parse().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hex_id! {
|
||||||
|
/// A self-certifying commitment to the owner's key, never on the wire.
|
||||||
|
CommunityId
|
||||||
|
}
|
||||||
|
|
||||||
|
hex_id! {
|
||||||
|
ChannelId
|
||||||
|
}
|
||||||
|
|
||||||
|
hex_id! {
|
||||||
|
/// Both a Role's entity coordinate and the field it repeats in its own content.
|
||||||
|
RoleId
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A key-rotation counter; it bumps only on a Rekey that removes somebody.
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct Epoch(pub u64);
|
||||||
|
|
||||||
|
impl fmt::Display for Epoch {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
pub mod derive;
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow, bail};
|
||||||
|
use data_encoding::HEXLOWER;
|
||||||
|
use rand::TryRng as _;
|
||||||
|
use rand::rngs::SysRng;
|
||||||
|
|
||||||
|
/// Uppercase and other non-canonical spellings are rejected.
|
||||||
|
pub(crate) fn decode_hex_32(value: &str) -> Result<[u8; 32]> {
|
||||||
|
decode_hex_lower::<32>(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decode_hex_lower<const N: usize>(value: &str) -> Result<[u8; N]> {
|
||||||
|
let bytes = HEXLOWER
|
||||||
|
.decode(value.as_bytes())
|
||||||
|
.map_err(|error| anyhow!("invalid hex: {error}"))?;
|
||||||
|
|
||||||
|
let decoded: [u8; N] = bytes
|
||||||
|
.as_slice()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| anyhow!("expected {N} bytes, got {}", bytes.len()))?;
|
||||||
|
|
||||||
|
if HEXLOWER.encode(&decoded) != value {
|
||||||
|
bail!("hex must be lowercase and canonical");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fill_random(bytes: &mut [u8]) -> Result<()> {
|
||||||
|
SysRng
|
||||||
|
.try_fill_bytes(bytes)
|
||||||
|
.map_err(|error| anyhow!("os rng: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn random_32() -> Result<[u8; 32]> {
|
||||||
|
let mut bytes = [0u8; 32];
|
||||||
|
fill_random(&mut bytes)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
+69
-60
@@ -13,32 +13,41 @@ folded independently by every client.
|
|||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
Files follow the CORD documents. The frozen derivations of Appendix A and the id
|
||||||
|
vocabulary are shared substrate — every document calls them — so they live outside
|
||||||
|
`cords` in `utils::derive` (re-exported as `derive`) and `types`.
|
||||||
|
|
||||||
| Module | Owns |
|
| Module | Owns |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `cord01` | Private Streams: the seal/wrap envelope and the NIP-44 helpers |
|
||||||
|
| `cord02` | Communities: identity, epochs, metadata, the Control Plane fold and writer |
|
||||||
|
| `cord02::guestbook` | Joins, leaves, kicks, snapshots, the member list |
|
||||||
|
| `cord02::list` | The Community List (a member's own memberships, across devices) |
|
||||||
|
| `cord03` | Channels: Channel metadata and the Chat Plane |
|
||||||
|
| `cord04` | Roles: chained editions, parse/hash/fold, the roster, permissions, the banlist |
|
||||||
|
| `cord04::pins` | Pin Lists, and the key disclosure a keyless reader verifies |
|
||||||
|
| `cord05` | Invite bundles, links, the Direct Invite, the Invite List |
|
||||||
|
| `cord06` | Key rotations, refounding, compaction, dissolution |
|
||||||
| `derive` | Every frozen HKDF derivation and coordinate |
|
| `derive` | Every frozen HKDF derivation and coordinate |
|
||||||
| `stream` | The CORD-01 envelope: seal, wrap, open, and the NIP-44 helpers |
|
|
||||||
| `edition` | Chained, versioned editions: parse, hash, fold, floors |
|
|
||||||
| `roles` | Permissions, roles, grants, the banlist, the authority fixpoint |
|
|
||||||
| `control` | The Control Plane: genesis, the fold, the writer, metadata |
|
|
||||||
| `chat` | The Chat Plane: message/reaction/edit/delete builders and the fold |
|
|
||||||
| `guestbook` | Joins, leaves, kicks, snapshots, the member list |
|
|
||||||
| `invite` | Invite bundles, links, the Direct Invite, the Invite List |
|
|
||||||
| `list` | The Community List (a member's own memberships, across devices) |
|
|
||||||
| `rekey` | Key rotations, refounding, compaction, dissolution |
|
|
||||||
| `pins` | Pin Lists, and the key disclosure a keyless reader verifies |
|
|
||||||
| `store` | Local rumor cache, the community state document, relay paging |
|
| `store` | Local rumor cache, the community state document, relay paging |
|
||||||
|
|
||||||
|
`CommunityId`, `ChannelId`, `RoleId`, `Epoch` and `Extra` (crate-internal) come from
|
||||||
|
the private `types` module and are re-exported at the crate root.
|
||||||
|
|
||||||
|
CORD-07 (audio/video) is unimplemented. CORD-08's timer has no file of its own: it
|
||||||
|
lives in the metadata it reads (`cord02`) and the fold it filters (`cord03`).
|
||||||
|
|
||||||
Read `CommunityId` as "this community", `ChannelId` as "this channel", `Epoch` as
|
Read `CommunityId` as "this community", `ChannelId` as "this channel", `Epoch` as
|
||||||
"which key generation". Nothing else in the API needs internal state.
|
"which key generation". Nothing else in the API needs internal state.
|
||||||
|
|
||||||
## Creating a community
|
## Creating a community
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::control::{self, CommunityMetadata};
|
use concord::cord02::{self, CommunityMetadata};
|
||||||
use concord::store::{self, CommunityState, save_state};
|
use concord::store::{self, CommunityState, save_state};
|
||||||
|
|
||||||
let metadata = CommunityMetadata { name: "Room".into(), ..Default::default() };
|
let metadata = CommunityMetadata { name: "Room".into(), ..Default::default() };
|
||||||
let minted = control::genesis(&owner_keys, &metadata, now_secs)?;
|
let minted = cord02::genesis(&owner_keys, &metadata, now_secs)?;
|
||||||
|
|
||||||
// minted.identity — community_id, owner, owner_salt (verify() recomputes it)
|
// minted.identity — community_id, owner, owner_salt (verify() recomputes it)
|
||||||
// minted.wraps — the two owner-signed genesis editions, already sealed
|
// minted.wraps — the two owner-signed genesis editions, already sealed
|
||||||
@@ -53,14 +62,14 @@ join:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::derive::{control_group_key, control_signer_group_key};
|
use concord::derive::{control_group_key, control_signer_group_key};
|
||||||
use concord::edition::ParsedEdition;
|
use concord::cord04::ParsedEdition;
|
||||||
|
|
||||||
let read = control_group_key(&minted.community_root, &minted.identity.community_id, Epoch(0))?;
|
let read = control_group_key(&minted.community_root, &minted.identity.community_id, Epoch(0))?;
|
||||||
let signer = control_signer_group_key(&minted.control_root, &minted.identity.community_id, Epoch(0))?;
|
let signer = control_signer_group_key(&minted.control_root, &minted.identity.community_id, Epoch(0))?;
|
||||||
let editions: Vec<ParsedEdition> = minted
|
let editions: Vec<ParsedEdition> = minted
|
||||||
.wraps
|
.wraps
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wrap| control::open_edition(wrap, &read, &signer.pk(), true))
|
.map(|wrap| cord02::open_edition(wrap, &read, &signer.pk(), true))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
let mut state = CommunityState::from_genesis(&minted, &editions, added_at_ms)?;
|
let mut state = CommunityState::from_genesis(&minted, &editions, added_at_ms)?;
|
||||||
@@ -75,12 +84,12 @@ client explicitly — coop's client is a gossip client with no background refres
|
|||||||
An invite link resolves to a bundle:
|
An invite link resolves to a bundle:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::invite::{self, BundleState, invite_bundle_key};
|
use concord::cord05::{self, BundleState, invite_bundle_key};
|
||||||
|
|
||||||
let link = invite::parse_link(url)?; // link_signer, token, bootstrap_relays, naddr
|
let link = cord05::parse_link(url)?; // link_signer, token, bootstrap_relays, naddr
|
||||||
|
|
||||||
// The crate does no I/O: fetch the naddr from the fragment's relays, then:
|
// The crate does no I/O: fetch the naddr from the fragment's relays, then:
|
||||||
let invite = match invite::parse_bundle_event(&event, &link.link_signer, &invite_bundle_key(&link.token))? {
|
let invite = match cord05::parse_bundle_event(&event, &link.link_signer, &invite_bundle_key(&link.token))? {
|
||||||
BundleState::Live(invite) => invite, // validate() already ran
|
BundleState::Live(invite) => invite, // validate() already ran
|
||||||
BundleState::Revoked => return Ok(None), // a tombstone at the coordinate
|
BundleState::Revoked => return Ok(None), // a tombstone at the coordinate
|
||||||
};
|
};
|
||||||
@@ -89,7 +98,7 @@ let invite = match invite::parse_bundle_event(&event, &link.link_signer, &invite
|
|||||||
A Direct Invite arrives as a NIP-59 gift wrap addressed to the member:
|
A Direct Invite arrives as a NIP-59 gift wrap addressed to the member:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let (inviter, invite) = invite::unwrap_direct_invite(&wrap, &my_keys)?;
|
let (inviter, invite) = cord05::unwrap_direct_invite(&wrap, &my_keys)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Either way the invite carries `community_id`, `owner`, `owner_salt`,
|
Either way the invite carries `community_id`, `owner`, `owner_salt`,
|
||||||
@@ -101,26 +110,26 @@ Then publish a join so the member list sees the member before any backfill:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::derive::guestbook_group_key;
|
use concord::derive::guestbook_group_key;
|
||||||
use concord::guestbook;
|
use concord::cord02::guestbook;
|
||||||
|
|
||||||
let guestbook = guestbook_group_key(&invite.community_root, &invite.community_id, invite.root_epoch)?;
|
let guestbook = guestbook_group_key(&invite.community_root, &invite.community_id, invite.root_epoch)?;
|
||||||
let rumor = guestbook::build_join(my_pk, Some((creator_npub, label)), now_ms);
|
let rumor = cord02::guestbook::build_join(my_pk, Some((creator_npub, label)), now_ms);
|
||||||
let (wrap, _) = guestbook::seal_rumor(&rumor, &guestbook, &my_keys)?;
|
let (wrap, _) = cord02::guestbook::seal_rumor(&rumor, &guestbook, &my_keys)?;
|
||||||
client.send_event(&wrap).to(&relays).await?;
|
client.send_event(&wrap).to(&relays).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reading the Control Plane
|
## Reading the Control Plane
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::control::{self, ControlFold};
|
use concord::cord02::{self, ControlFold};
|
||||||
|
|
||||||
let editions: Vec<ParsedEdition> = wraps
|
let editions: Vec<ParsedEdition> = wraps
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|wrap| control::open_edition(wrap, &read, &control_pk, true).ok())
|
.filter_map(|wrap| cord02::open_edition(wrap, &read, &control_pk, true).ok())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let control: ControlFold =
|
let control: ControlFold =
|
||||||
control::fold_control(&owner, &community_id, &editions, &state.floors(), &state.banned);
|
cord02::fold_control(&owner, &community_id, &editions, &state.floors(), &state.banned);
|
||||||
state.apply_fold(&control);
|
state.apply_fold(&control);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,12 +154,12 @@ fold; they are its memory.
|
|||||||
## Sending a message
|
## Sending a message
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::chat::{self, build_message};
|
use concord::cord03::{self, build_message};
|
||||||
use concord::derive::channel_group_key;
|
use concord::derive::channel_group_key;
|
||||||
|
|
||||||
let plane = channel_group_key(&community_root, &channel, epoch)?; // public channel
|
let plane = channel_group_key(&community_root, &channel, epoch)?; // public channel
|
||||||
let rumor = build_message(my_pk, &channel, epoch, text, None, at_ms, timer);
|
let rumor = build_message(my_pk, &channel, epoch, text, None, at_ms, timer);
|
||||||
let (wrap, wrap_key) = chat::seal_rumor(&rumor, &plane, &my_keys, false)?;
|
let (wrap, wrap_key) = cord03::seal_rumor(&rumor, &plane, &my_keys, false)?;
|
||||||
client.send_event(&wrap).to(&relays).await?;
|
client.send_event(&wrap).to(&relays).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -171,7 +180,7 @@ about an existing `EventId` rather than a mutation.
|
|||||||
## Reading a channel
|
## Reading a channel
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::chat::{self, fold, plane_keys};
|
use concord::cord03::{self, fold, plane_keys};
|
||||||
|
|
||||||
let planes = plane_keys(&held, &channel)?; // &[(Epoch, secret)]
|
let planes = plane_keys(&held, &channel)?; // &[(Epoch, secret)]
|
||||||
let mut rumors = Vec::new();
|
let mut rumors = Vec::new();
|
||||||
@@ -180,7 +189,7 @@ for wrap in &wraps {
|
|||||||
let Some((epoch, group)) = planes.iter().find(|(_, group)| group.pk() == wrap.pubkey) else {
|
let Some((epoch, group)) = planes.iter().find(|(_, group)| group.pk() == wrap.pubkey) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Ok((opened, rumor)) = chat::open(wrap, group, &channel, *epoch) else {
|
let Ok((opened, rumor)) = cord03::open(wrap, group, &channel, *epoch) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
store::cache_rumor(database, &channel, &opened).await?;
|
store::cache_rumor(database, &channel, &opened).await?;
|
||||||
@@ -217,11 +226,11 @@ as an inline row only when its author passes
|
|||||||
## Membership
|
## Membership
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let states = guestbook::coalesce(&rumors, now_ms, Some(&refounder_pk), |actor, target, citation| {
|
let states = cord02::guestbook::coalesce(&rumors, now_ms, Some(&refounder_pk), |actor, target, citation| {
|
||||||
citation_ok(&owner, &community_id, actor, citation, &control.roles.floors)
|
citation_ok(&owner, &community_id, actor, citation, &control.roles.floors)
|
||||||
&& control.roles.can_act_on_member(actor, &owner, target, Permissions::KICK)
|
&& control.roles.can_act_on_member(actor, &owner, target, Permissions::KICK)
|
||||||
});
|
});
|
||||||
let members = guestbook::complete_memberlist(&states, &observed, &granted, &control.banned, &BTreeMap::new());
|
let members = cord02::guestbook::complete_memberlist(&states, &observed, &granted, &control.banned, &BTreeMap::new());
|
||||||
```
|
```
|
||||||
|
|
||||||
- `observed` is npub → ms for every author this client has seen publish anything
|
- `observed` is npub → ms for every author this client has seen publish anything
|
||||||
@@ -254,52 +263,52 @@ and pass the head from the current fold, so the chain cannot silently fork.
|
|||||||
Wrappers: `set_community_metadata`, `set_channel_metadata`, `set_role`,
|
Wrappers: `set_community_metadata`, `set_channel_metadata`, `set_role`,
|
||||||
`set_grant`, `set_banlist`, `set_registry`, `set_pin_list`, plus raw `publish`.
|
`set_grant`, `set_banlist`, `set_registry`, `set_pin_list`, plus raw `publish`.
|
||||||
A ban is a `set_banlist` followed by a base rekey; a kick is a `set_grant` with an
|
A ban is a `set_banlist` followed by a base rekey; a kick is a `set_grant` with an
|
||||||
empty `role_ids` followed by `guestbook::build_kick`.
|
empty `role_ids` followed by `cord02::guestbook::build_kick`.
|
||||||
|
|
||||||
## Pins
|
## Pins
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::pins;
|
use concord::cord04::pins;
|
||||||
|
|
||||||
let entry = pins::build_entry(&opened_message, &plane, &channel)?;
|
let entry = cord04::pins::build_entry(&opened_message, &plane, &channel)?;
|
||||||
let head_content = control.pin_content(&community_id, &channel).unwrap_or("");
|
let head_content = control.pin_content(&community_id, &channel).unwrap_or("");
|
||||||
let read = pins::read_list(head_content, |epoch| channel_group_key(&root, &channel, epoch).ok());
|
let read = cord04::pins::read_list(head_content, |epoch| channel_group_key(&root, &channel, epoch).ok());
|
||||||
let content = pins::publishable(&read, channel_is_private, &plane, epoch)?;
|
let content = cord04::pins::publishable(&read, channel_is_private, &plane, epoch)?;
|
||||||
let (wrap, _) = writer.set_pin_list(
|
let (wrap, _) = writer.set_pin_list(
|
||||||
&my_keys, &community_id, &channel, &content, head, citation, now_secs)?;
|
&my_keys, &community_id, &channel, &content, head, citation, now_secs)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Reading is verification: `read_list` decodes either content form (public, or
|
Reading is verification: `read_list` decodes either content form (public, or
|
||||||
sealed under the channel key at the named epoch), and
|
sealed under the channel key at the named epoch), and
|
||||||
`pins::verify_entry(entry, &channel)` returns a `VerifiedPin` with the proven
|
`cord04::pins::verify_entry(entry, &channel)` returns a `VerifiedPin` with the proven
|
||||||
author, words and time — no history and no old keys needed. `read.sealed` means
|
author, words and time — no history and no old keys needed. `read.sealed` means
|
||||||
the list is sealed under an epoch this client never held: show it as unavailable,
|
the list is sealed under an epoch this client never held: show it as unavailable,
|
||||||
and never write from it (`publishable` refuses). `pins::killed_by(&pin, &delete)`
|
and never write from it (`publishable` refuses). `cord04::pins::killed_by(&pin, &delete)`
|
||||||
answers whether a folded kind-5 erases an entry.
|
answers whether a folded kind-5 erases an entry.
|
||||||
|
|
||||||
## Invites
|
## Invites
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::derive::{invite_bundle_key, TOKEN_LEN};
|
use concord::derive::{invite_bundle_key, TOKEN_LEN};
|
||||||
use concord::invite::{self, InviteEntry, InviteTombstone};
|
use concord::cord05::{self, InviteEntry, InviteTombstone};
|
||||||
|
|
||||||
let token: [u8; TOKEN_LEN] = /* 16 bytes from any CSPRNG */;
|
let token: [u8; TOKEN_LEN] = /* 16 bytes from any CSPRNG */;
|
||||||
let bundle_key = invite_bundle_key(&token);
|
let bundle_key = invite_bundle_key(&token);
|
||||||
let link_signer = Keys::generate();
|
let link_signer = Keys::generate();
|
||||||
let bundle = invite::build_bundle_event(&link_signer, &invite, &bundle_key)?;
|
let bundle = cord05::build_bundle_event(&link_signer, &invite, &bundle_key)?;
|
||||||
let url = invite::build_invite_url(BASE, &link_signer.public_key(), &token, &relays)?;
|
let url = cord05::build_invite_url(BASE, &link_signer.public_key(), &token, &relays)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
A link is a coordinate plus a fragment: the naddr fetches the bundle, the token
|
A link is a coordinate plus a fragment: the naddr fetches the bundle, the token
|
||||||
unlocks it, and the fragment names the relays to fetch from.
|
unlocks it, and the fragment names the relays to fetch from.
|
||||||
`invite::stock_relays()` is what a fragment with no relays of its own means.
|
`cord05::stock_relays()` is what a fragment with no relays of its own means.
|
||||||
|
|
||||||
The `link_signer` secret is what lets the creator refresh or retire the link, so
|
The `link_signer` secret is what lets the creator refresh or retire the link, so
|
||||||
keep it against the token in the member's own Invite List — a local document
|
keep it against the token in the member's own Invite List — a local document
|
||||||
encrypted to self, exactly like the Community List:
|
encrypted to self, exactly like the Community List:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut list = invite::parse_invite_list(&my_keys, &event)?;
|
let mut list = cord05::parse_invite_list(&my_keys, &event)?;
|
||||||
list.entries.push(InviteEntry {
|
list.entries.push(InviteEntry {
|
||||||
token: HEXLOWER.encode(&token),
|
token: HEXLOWER.encode(&token),
|
||||||
signer_sk: link_signer.secret_key().to_secret_hex(),
|
signer_sk: link_signer.secret_key().to_secret_hex(),
|
||||||
@@ -310,7 +319,7 @@ list.entries.push(InviteEntry {
|
|||||||
expires_at: None,
|
expires_at: None,
|
||||||
extra: Default::default(),
|
extra: Default::default(),
|
||||||
});
|
});
|
||||||
let event = invite::build_invite_list(&my_keys, &list)?; // kind 13303
|
let event = cord05::build_invite_list(&my_keys, &list)?; // kind 13303
|
||||||
|
|
||||||
// Retiring is a tombstone, never a deletion: it beats a stale copy terminally.
|
// Retiring is a tombstone, never a deletion: it beats a stale copy terminally.
|
||||||
list.tombstones.push(InviteTombstone {
|
list.tombstones.push(InviteTombstone {
|
||||||
@@ -331,10 +340,10 @@ seals one blob per remaining member:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::derive::epoch_key_commitment;
|
use concord::derive::epoch_key_commitment;
|
||||||
use concord::rekey::{self, RekeyScope};
|
use concord::cord06::{self, RekeyScope};
|
||||||
|
|
||||||
let scope = RekeyScope::Channel(channel_id); // or RekeyScope::Base
|
let scope = RekeyScope::Channel(channel_id); // or RekeyScope::Base
|
||||||
let plan = rekey::plan_refounding(Epoch(epoch + 1))?;
|
let plan = cord06::plan_refounding(Epoch(epoch + 1))?;
|
||||||
|
|
||||||
// A base rotation delivers the new control-plane keys beside the root; a channel
|
// A base rotation delivers the new control-plane keys beside the root; a channel
|
||||||
// rotation delivers only that channel's fresh key.
|
// rotation delivers only that channel's fresh key.
|
||||||
@@ -350,12 +359,12 @@ let (control_pk, control_root) = match scope {
|
|||||||
let blobs = members
|
let blobs = members
|
||||||
.iter()
|
.iter()
|
||||||
.map(|member| {
|
.map(|member| {
|
||||||
rekey::build_blob(&my_keys, member, scope, plan.epoch, &new_key, control_pk.as_ref(), control_root)
|
cord06::build_blob(&my_keys, member, scope, plan.epoch, &new_key, control_pk.as_ref(), control_root)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let rekey_group = rekey::rekey_group(scope, &community_root, &community_id, plan.epoch)?;
|
let rekey_group = cord06::rekey_group(scope, &community_root, &community_id, plan.epoch)?;
|
||||||
let wraps = rekey::build_rekey_chunks(
|
let wraps = cord06::build_rekey_chunks(
|
||||||
&my_keys,
|
&my_keys,
|
||||||
&rekey_group,
|
&rekey_group,
|
||||||
scope,
|
scope,
|
||||||
@@ -369,7 +378,7 @@ let wraps = rekey::build_rekey_chunks(
|
|||||||
)?;
|
)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
On the receiving side, `rekey::parse_rekey_chunk(&opened)` per wrap, then
|
On the receiving side, `cord06::parse_rekey_chunk(&opened)` per wrap, then
|
||||||
`collect_rotations(&chunks)`, then `am_i_removed(&rotation, &me)` — which is
|
`collect_rotations(&chunks)`, then `am_i_removed(&rotation, &me)` — which is
|
||||||
`None` until every chunk is held, because an incomplete set is never a removal. A
|
`None` until every chunk is held, because an incomplete set is never a removal. A
|
||||||
member finds their delivery with `find_my_blobs` / `open_blob`, and adopts the key
|
member finds their delivery with `find_my_blobs` / `open_blob`, and adopts the key
|
||||||
@@ -379,11 +388,11 @@ matches the key they already hold. Two concurrent rotations settle on `fork_winn
|
|||||||
Dissolution is owner-only and terminal:
|
Dissolution is owner-only and terminal:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let rumor = rekey::dissolved_tombstone_rumor(owner_pk, &community_id, now_secs);
|
let rumor = cord06::dissolved_tombstone_rumor(owner_pk, &community_id, now_secs);
|
||||||
let wrap = rekey::seal_dissolved(&rumor, &community_id, &my_keys, now_secs)?;
|
let wrap = cord06::seal_dissolved(&rumor, &community_id, &my_keys, now_secs)?;
|
||||||
|
|
||||||
// A receiver seals the community read-only on sight.
|
// A receiver seals the community read-only on sight.
|
||||||
if rekey::verify_dissolved(&wrap, &identity) {
|
if cord06::verify_dissolved(&wrap, &identity) {
|
||||||
state.dissolved = true;
|
state.dissolved = true;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -393,15 +402,15 @@ if rekey::verify_dissolved(&wrap, &identity) {
|
|||||||
A member's own memberships, synced across their devices:
|
A member's own memberships, synced across their devices:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use concord::list;
|
use concord::cord02::list;
|
||||||
|
|
||||||
let material = list::join_material(&invite, staff.then_some(&control_root));
|
let material = cord02::list::join_material(&invite, staff.then_some(&control_root));
|
||||||
let mut mine = list::parse_list_event(&my_keys, &event)?;
|
let mut mine = cord02::list::parse_list_event(&my_keys, &event)?;
|
||||||
mine = list::merge(mine, list::CommunityList {
|
mine = cord02::list::merge(mine, cord02::list::CommunityList {
|
||||||
entries: vec![list::CommunityListEntry { community_id, seed: material.clone(), current: material, added_at: now_ms, extra: Default::default() }],
|
entries: vec![cord02::list::CommunityListEntry { community_id, seed: material.clone(), current: material, added_at: now_ms, extra: Default::default() }],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let event = list::build_list_event(&my_keys, &mine)?; // kind 13302, NIP-44 to self
|
let event = cord02::list::build_list_event(&my_keys, &mine)?; // kind 13302, NIP-44 to self
|
||||||
```
|
```
|
||||||
|
|
||||||
`is_live(&id)` answers joined-versus-left: a tombstone is terminal until a
|
`is_live(&id)` answers joined-versus-left: a tombstone is terminal until a
|
||||||
@@ -430,7 +439,7 @@ impl ConcordRegistry {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Call it after `chat::init` in `desktop/src/main.rs` and `web/src/lib.rs`, and
|
Call it after `cord03::init` in `desktop/src/main.rs` and `web/src/lib.rs`, and
|
||||||
subscribe to `NostrRegistry` for `SignerChanged` so the communities reset with
|
subscribe to `NostrRegistry` for `SignerChanged` so the communities reset with
|
||||||
the account.
|
the account.
|
||||||
|
|
||||||
@@ -458,7 +467,7 @@ self.ingress = Some(cx.background_spawn(async move {
|
|||||||
let Some(plane) = planes.iter().find(|plane| plane.group.pk() == wrap.pubkey) else {
|
let Some(plane) = planes.iter().find(|plane| plane.group.pk() == wrap.pubkey) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let (opened, rumor) = chat::open(wrap, &plane.group, &plane.channel, plane.epoch)?;
|
let (opened, rumor) = cord03::open(wrap, &plane.group, &plane.channel, plane.epoch)?;
|
||||||
store::cache_rumor(database.as_ref(), &plane.channel, &opened).await?;
|
store::cache_rumor(database.as_ref(), &plane.channel, &opened).await?;
|
||||||
signal_tx.send_async(Signal::Chat { channel: plane.channel, rumor }).await?;
|
signal_tx.send_async(Signal::Chat { channel: plane.channel, rumor }).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user