create-sia-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +179 -0
  3. package/package.json +29 -0
  4. package/template/CLAUDE.md +160 -0
  5. package/template/README.md +102 -0
  6. package/template/_gitignore +5 -0
  7. package/template/biome.json +40 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +30 -0
  10. package/template/rust/README.md +16 -0
  11. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
  12. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
  13. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
  14. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
  15. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
  16. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
  17. package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
  18. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
  19. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
  20. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
  21. package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
  22. package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
  23. package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
  24. package/template/rust/sia-sdk-rs/LICENSE +21 -0
  25. package/template/rust/sia-sdk-rs/README.md +30 -0
  26. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
  27. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
  28. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
  29. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
  30. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
  31. package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
  32. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
  33. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
  34. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
  35. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
  36. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
  37. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
  38. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
  39. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
  40. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
  41. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
  42. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
  43. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
  44. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
  45. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
  46. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
  47. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
  48. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
  49. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
  50. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
  51. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
  52. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
  53. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
  54. package/template/rust/sia-sdk-rs/knope.toml +54 -0
  55. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
  56. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
  57. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
  58. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
  59. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
  60. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
  61. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
  62. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
  63. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
  64. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
  65. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
  66. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
  67. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
  68. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
  69. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
  70. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
  71. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
  72. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
  73. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
  74. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
  75. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
  76. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
  77. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
  78. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
  88. package/template/scripts/setup-rust.js +29 -0
  89. package/template/src/App.tsx +13 -0
  90. package/template/src/components/DevNote.tsx +21 -0
  91. package/template/src/components/auth/ApproveScreen.tsx +84 -0
  92. package/template/src/components/auth/AuthFlow.tsx +77 -0
  93. package/template/src/components/auth/ConnectScreen.tsx +214 -0
  94. package/template/src/components/auth/LoadingScreen.tsx +8 -0
  95. package/template/src/components/auth/RecoveryScreen.tsx +182 -0
  96. package/template/src/components/upload/UploadZone.tsx +314 -0
  97. package/template/src/index.css +9 -0
  98. package/template/src/lib/constants.ts +8 -0
  99. package/template/src/lib/format.ts +35 -0
  100. package/template/src/lib/hex.ts +13 -0
  101. package/template/src/lib/sdk.ts +25 -0
  102. package/template/src/lib/wasm-env.ts +5 -0
  103. package/template/src/main.tsx +12 -0
  104. package/template/src/stores/auth.ts +86 -0
  105. package/template/tsconfig.app.json +31 -0
  106. package/template/tsconfig.json +7 -0
  107. package/template/tsconfig.node.json +26 -0
  108. package/template/vite.config.ts +18 -0
  109. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
  110. package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
  111. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  112. package/template/wasm/indexd_wasm/package.json +31 -0
@@ -0,0 +1,367 @@
1
+ use super::Error as EncodingError;
2
+ use bytes::{Bytes, BytesMut};
3
+ use chrono::{DateTime, Duration, Utc};
4
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
5
+
6
+ pub trait AsyncEncoder {
7
+ type Error: From<EncodingError>;
8
+ fn encode_buf(&mut self, buf: &[u8]) -> impl Future<Output = Result<(), Self::Error>>;
9
+ }
10
+
11
+ pub trait AsyncDecoder {
12
+ type Error: From<EncodingError>;
13
+ fn decode_buf(&mut self, buf: &mut [u8]) -> impl Future<Output = Result<(), Self::Error>>;
14
+ }
15
+
16
+ pub trait AsyncSiaEncodable {
17
+ fn encode_async<E: AsyncEncoder>(
18
+ &self,
19
+ w: &mut E,
20
+ ) -> impl Future<Output = Result<(), E::Error>>;
21
+ }
22
+
23
+ pub trait AsyncSiaDecodable: Sized {
24
+ fn decode_async<D: AsyncDecoder>(r: &mut D) -> impl Future<Output = Result<Self, D::Error>>;
25
+ }
26
+
27
+ impl<T: AsyncWriteExt + Unpin> AsyncEncoder for T {
28
+ type Error = EncodingError;
29
+ async fn encode_buf(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
30
+ self.write_all(buf).await?;
31
+ Ok(())
32
+ }
33
+ }
34
+
35
+ impl<T: AsyncReadExt + Unpin> AsyncDecoder for T {
36
+ type Error = EncodingError;
37
+ async fn decode_buf(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
38
+ self.read_exact(buf).await?;
39
+ Ok(())
40
+ }
41
+ }
42
+
43
+ impl AsyncSiaEncodable for u8 {
44
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
45
+ w.encode_buf(&[*self]).await
46
+ }
47
+ }
48
+
49
+ impl AsyncSiaDecodable for u8 {
50
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
51
+ let mut buf = [0; 1];
52
+ r.decode_buf(&mut buf).await?;
53
+ Ok(buf[0])
54
+ }
55
+ }
56
+
57
+ impl AsyncSiaEncodable for bool {
58
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
59
+ (*self as u8).encode_async(w).await
60
+ }
61
+ }
62
+
63
+ impl AsyncSiaDecodable for bool {
64
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
65
+ let v = u8::decode_async(r).await?;
66
+ match v {
67
+ 0 => Ok(false),
68
+ 1 => Ok(true),
69
+ _ => Err(EncodingError::InvalidValue("requires 0 or 1".into()).into()),
70
+ }
71
+ }
72
+ }
73
+
74
+ impl AsyncSiaEncodable for DateTime<Utc> {
75
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
76
+ self.timestamp().encode_async(w).await
77
+ }
78
+ }
79
+
80
+ impl AsyncSiaDecodable for DateTime<Utc> {
81
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
82
+ let timestamp = i64::decode_async(r).await?;
83
+ Ok(DateTime::from_timestamp_secs(timestamp).ok_or_else(|| {
84
+ EncodingError::InvalidValue(format!("invalid timestamp: {timestamp}"))
85
+ })?)
86
+ }
87
+ }
88
+
89
+ impl AsyncSiaEncodable for Duration {
90
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
91
+ self.num_nanoseconds()
92
+ .ok_or_else(|| EncodingError::InvalidValue("duration too large".into()))?
93
+ .encode_async(w)
94
+ .await
95
+ }
96
+ }
97
+
98
+ impl AsyncSiaDecodable for Duration {
99
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
100
+ let ns = u64::decode_async(r).await?;
101
+ if ns > i64::MAX as u64 {
102
+ return Err(EncodingError::InvalidValue(format!(
103
+ "duration {ns} must be less than {}",
104
+ i64::MAX
105
+ ))
106
+ .into());
107
+ }
108
+ Ok(Duration::nanoseconds(ns as i64))
109
+ }
110
+ }
111
+
112
+ impl AsyncSiaEncodable for Bytes {
113
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
114
+ self.len().encode_async(w).await?;
115
+ w.encode_buf(self).await
116
+ }
117
+ }
118
+
119
+ impl AsyncSiaDecodable for Bytes {
120
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
121
+ let len = usize::decode_async(r).await?;
122
+ let mut buf = BytesMut::zeroed(len);
123
+ r.decode_buf(&mut buf).await?;
124
+ Ok(buf.freeze())
125
+ }
126
+ }
127
+
128
+ impl<T: AsyncSiaEncodable> AsyncSiaEncodable for [T] {
129
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
130
+ self.len().encode_async(w).await?;
131
+ for item in self {
132
+ item.encode_async(w).await?;
133
+ }
134
+ Ok(())
135
+ }
136
+ }
137
+
138
+ impl<T: AsyncSiaEncodable> AsyncSiaEncodable for Option<T> {
139
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
140
+ match self {
141
+ Some(value) => {
142
+ 1u8.encode_async(w).await?;
143
+ value.encode_async(w).await?;
144
+ }
145
+ None => 0u8.encode_async(w).await?,
146
+ }
147
+ Ok(())
148
+ }
149
+ }
150
+
151
+ impl<T: AsyncSiaDecodable> AsyncSiaDecodable for Option<T> {
152
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
153
+ match bool::decode_async(r).await? {
154
+ true => Ok(Some(T::decode_async(r).await?)),
155
+ false => Ok(None),
156
+ }
157
+ }
158
+ }
159
+
160
+ impl<T> AsyncSiaEncodable for Vec<T>
161
+ where
162
+ T: AsyncSiaEncodable,
163
+ {
164
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
165
+ self.len().encode_async(w).await?;
166
+ for item in self {
167
+ item.encode_async(w).await?;
168
+ }
169
+ Ok(())
170
+ }
171
+ }
172
+
173
+ impl<T> AsyncSiaDecodable for Vec<T>
174
+ where
175
+ T: AsyncSiaDecodable,
176
+ {
177
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
178
+ let mut vec = Vec::new();
179
+ // note: the vec is not pre-allocated
180
+ // to prevent abuse by sending a large len
181
+ for _ in 0..usize::decode_async(r).await? {
182
+ vec.push(T::decode_async(r).await?);
183
+ }
184
+ Ok(vec)
185
+ }
186
+ }
187
+
188
+ impl AsyncSiaEncodable for String {
189
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
190
+ let bytes = self.as_bytes();
191
+ bytes.encode_async(w).await?;
192
+ Ok(())
193
+ }
194
+ }
195
+
196
+ impl AsyncSiaDecodable for String {
197
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
198
+ let bytes = Vec::<u8>::decode_async(r).await?;
199
+ String::from_utf8(bytes).map_err(|e| EncodingError::InvalidValue(e.to_string()).into())
200
+ }
201
+ }
202
+
203
+ impl<const N: usize> AsyncSiaEncodable for [u8; N] {
204
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
205
+ w.encode_buf(self).await
206
+ }
207
+ }
208
+
209
+ impl<const N: usize> AsyncSiaDecodable for [u8; N] {
210
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
211
+ let mut arr = [0u8; N];
212
+ r.decode_buf(&mut arr).await?;
213
+ Ok(arr)
214
+ }
215
+ }
216
+
217
+ macro_rules! impl_sia_numeric {
218
+ ($($t:ty),*) => {
219
+ $(
220
+ impl AsyncSiaEncodable for $t {
221
+ async fn encode_async<E: AsyncEncoder>(&self, w: &mut E) -> Result<(), E::Error> {
222
+ w.encode_buf(&(*self as u64).to_le_bytes()).await
223
+ }
224
+ }
225
+
226
+ impl AsyncSiaDecodable for $t {
227
+ async fn decode_async<D: AsyncDecoder>(r: &mut D) -> Result<Self, D::Error> {
228
+ let mut buf = [0u8; 8];
229
+ r.decode_buf(&mut buf).await?;
230
+ Ok(u64::from_le_bytes(buf) as Self)
231
+ }
232
+ }
233
+ )*
234
+ }
235
+ }
236
+
237
+ impl_sia_numeric!(u16, u32, usize, i16, i32, i64, u64);
238
+
239
+ #[cfg(test)]
240
+ mod tests {
241
+ use std::fmt::Debug;
242
+
243
+ use super::*;
244
+
245
+ async fn test_roundtrip<T: AsyncSiaEncodable + AsyncSiaDecodable + Debug + PartialEq>(
246
+ value: T,
247
+ expected_bytes: Vec<u8>,
248
+ ) {
249
+ let mut encoded_bytes = Vec::new();
250
+ value
251
+ .encode_async(&mut encoded_bytes)
252
+ .await
253
+ .unwrap_or_else(|e| panic!("failed to encode: {e:?}"));
254
+
255
+ assert_eq!(
256
+ encoded_bytes, expected_bytes,
257
+ "encoding mismatch for {value:?}"
258
+ );
259
+
260
+ let mut bytes = &expected_bytes[..];
261
+ let decoded = T::decode_async(&mut bytes)
262
+ .await
263
+ .unwrap_or_else(|e| panic!("failed to decode: {e:?}"));
264
+ assert_eq!(decoded, value, "decoding mismatch for {value:?}");
265
+
266
+ assert_eq!(bytes.len(), 0, "leftover bytes for {value:?}");
267
+ }
268
+
269
+ #[tokio::test]
270
+ async fn test_numerics() {
271
+ test_roundtrip(1u8, vec![1]).await;
272
+ test_roundtrip(2u16, vec![2, 0, 0, 0, 0, 0, 0, 0]).await;
273
+ test_roundtrip(3u32, vec![3, 0, 0, 0, 0, 0, 0, 0]).await;
274
+ test_roundtrip(4u64, vec![4, 0, 0, 0, 0, 0, 0, 0]).await;
275
+ test_roundtrip(5usize, vec![5, 0, 0, 0, 0, 0, 0, 0]).await;
276
+ test_roundtrip(-1i16, vec![255, 255, 255, 255, 255, 255, 255, 255]).await;
277
+ test_roundtrip(-2i32, vec![254, 255, 255, 255, 255, 255, 255, 255]).await;
278
+ test_roundtrip(-3i64, vec![253, 255, 255, 255, 255, 255, 255, 255]).await;
279
+ }
280
+
281
+ #[tokio::test]
282
+ async fn test_strings() {
283
+ test_roundtrip(
284
+ "hello".to_string(),
285
+ vec![
286
+ 5, 0, 0, 0, 0, 0, 0, 0, // length prefix
287
+ 104, 101, 108, 108, 111, // "hello"
288
+ ],
289
+ )
290
+ .await;
291
+ test_roundtrip(
292
+ "".to_string(),
293
+ vec![0, 0, 0, 0, 0, 0, 0, 0], // empty string length
294
+ )
295
+ .await;
296
+ }
297
+
298
+ #[tokio::test]
299
+ async fn test_fixed_arrays() {
300
+ test_roundtrip([1u8, 2u8, 3u8], vec![1, 2, 3]).await;
301
+ test_roundtrip([0u8; 4], vec![0, 0, 0, 0]).await;
302
+ }
303
+
304
+ #[tokio::test]
305
+ async fn test_vectors() {
306
+ test_roundtrip(
307
+ vec![1u8, 2u8, 3u8],
308
+ vec![
309
+ 3, 0, 0, 0, 0, 0, 0, 0, // length prefix
310
+ 1, 2, 3, // values
311
+ ],
312
+ )
313
+ .await;
314
+ test_roundtrip(
315
+ vec![100u64, 200u64],
316
+ vec![
317
+ 2, 0, 0, 0, 0, 0, 0, 0, // length prefix
318
+ 100, 0, 0, 0, 0, 0, 0, 0, // 100u64
319
+ 200, 0, 0, 0, 0, 0, 0, 0, // 200u64
320
+ ],
321
+ )
322
+ .await;
323
+ test_roundtrip(
324
+ vec!["a".to_string(), "bc".to_string()],
325
+ vec![
326
+ 2, 0, 0, 0, 0, 0, 0, 0, // vector length
327
+ 1, 0, 0, 0, 0, 0, 0, 0, // first string length
328
+ 97, // "a"
329
+ 2, 0, 0, 0, 0, 0, 0, 0, // second string length
330
+ 98, 99, // "bc"
331
+ ],
332
+ )
333
+ .await;
334
+ }
335
+
336
+ #[tokio::test]
337
+ async fn test_nested() {
338
+ test_roundtrip(
339
+ vec![vec![1u8, 2u8], vec![3u8, 4u8]],
340
+ vec![
341
+ 2, 0, 0, 0, 0, 0, 0, 0, // outer vec length
342
+ 2, 0, 0, 0, 0, 0, 0, 0, // first inner vec length
343
+ 1, 2, // first inner vec contents
344
+ 2, 0, 0, 0, 0, 0, 0, 0, // second inner vec length
345
+ 3, 4, // second inner vec contents
346
+ ],
347
+ )
348
+ .await;
349
+ }
350
+
351
+ #[tokio::test]
352
+ async fn test_bytes() {
353
+ test_roundtrip(
354
+ Bytes::from("hello"),
355
+ vec![
356
+ 5, 0, 0, 0, 0, 0, 0, 0, // length prefix
357
+ 104, 101, 108, 108, 111, // "hello"
358
+ ],
359
+ )
360
+ .await;
361
+ test_roundtrip(
362
+ Bytes::from(""),
363
+ vec![0, 0, 0, 0, 0, 0, 0, 0], // empty string length
364
+ )
365
+ .await;
366
+ }
367
+ }
@@ -0,0 +1,6 @@
1
+ use crate::encoding::Error;
2
+
3
+ mod v2;
4
+
5
+ pub use sia_derive::{AsyncSiaDecode, AsyncSiaEncode};
6
+ pub use v2::{AsyncDecoder, AsyncEncoder, AsyncSiaDecodable, AsyncSiaEncodable};
@@ -0,0 +1,303 @@
1
+ use crate::encoding::{SiaDecodable, SiaEncodable};
2
+ use base64::Engine;
3
+ use base64::prelude::BASE64_STANDARD;
4
+ use chacha20::XChaCha20;
5
+ use chacha20::cipher::inout::InOutBuf;
6
+ use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherError, StreamCipherSeek};
7
+ use rayon::prelude::*;
8
+ use serde::{Deserialize, Serialize};
9
+ use sia_derive::{SiaDecode, SiaEncode};
10
+ use tokio::io::{AsyncRead, AsyncWrite};
11
+ use zeroize::ZeroizeOnDrop;
12
+
13
+ #[derive(SiaEncode, SiaDecode, Clone, Debug, ZeroizeOnDrop, PartialEq)]
14
+ pub struct EncryptionKey([u8; 32]);
15
+
16
+ impl From<[u8; 32]> for EncryptionKey {
17
+ fn from(value: [u8; 32]) -> Self {
18
+ Self(value)
19
+ }
20
+ }
21
+
22
+ impl TryFrom<&[u8]> for EncryptionKey {
23
+ type Error = &'static str;
24
+
25
+ fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
26
+ if value.len() != 32 {
27
+ return Err("invalid key length");
28
+ }
29
+ let mut key = [0u8; 32];
30
+ key.copy_from_slice(value);
31
+ Ok(Self(key))
32
+ }
33
+ }
34
+
35
+ impl AsRef<[u8; 32]> for EncryptionKey {
36
+ fn as_ref(&self) -> &[u8; 32] {
37
+ &self.0
38
+ }
39
+ }
40
+
41
+ impl Serialize for EncryptionKey {
42
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
43
+ where
44
+ S: serde::Serializer,
45
+ {
46
+ let s = BASE64_STANDARD.encode(self.0);
47
+ serializer.serialize_str(&s)
48
+ }
49
+ }
50
+
51
+ impl<'de> Deserialize<'de> for EncryptionKey {
52
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
53
+ where
54
+ D: serde::Deserializer<'de>,
55
+ {
56
+ let s = String::deserialize(deserializer)?;
57
+ let bytes = BASE64_STANDARD
58
+ .decode(s.as_bytes())
59
+ .map_err(serde::de::Error::custom)?;
60
+ EncryptionKey::try_from(bytes.as_slice()).map_err(serde::de::Error::custom)
61
+ }
62
+ }
63
+
64
+ /// encrypts the provided shards using XChaCha20. To decrypt the shards, call
65
+ /// this function again with the same key.
66
+ /// NOTE: don't reuse the same key for the same set of shards as it will
67
+ /// compromise the security of the encryption. Always use a freshly generated
68
+ /// key.
69
+ pub fn encrypt_shards(
70
+ key: &EncryptionKey,
71
+ shard_start: u8,
72
+ offset: usize,
73
+ shards: &mut Vec<Vec<u8>>,
74
+ ) {
75
+ shards.par_iter_mut().enumerate().for_each(|(i, shard)| {
76
+ encrypt_shard(key, shard_start + i as u8, offset, shard);
77
+ });
78
+ }
79
+
80
+ /// Encrypts a single shard using XChaCha20. To decrypt the shard, call this
81
+ /// function again with the same key.
82
+ /// NOTE: don't reuse the same key for the same shard as it will compromise the
83
+ /// security of the encryption. Always use a freshly generated key.
84
+ ///
85
+ /// For performance reasons, prefer using `encrypt_shards` when encrypting
86
+ /// multiple shards.
87
+ pub fn encrypt_shard(key: &EncryptionKey, index: u8, offset: usize, shard: &mut [u8]) {
88
+ let mut nonce: [u8; 24] = [0u8; 24]; // XChaCha20 nonce size
89
+ nonce[0] = index;
90
+ let mut cipher = XChaCha20::new(key.as_ref().into(), &nonce.into());
91
+ cipher.seek(offset);
92
+ cipher.apply_keystream(shard);
93
+ }
94
+
95
+ pub struct CipherReader<R: AsyncRead> {
96
+ inner: R,
97
+ cipher: Chacha20Cipher,
98
+ }
99
+
100
+ impl<R: AsyncRead> CipherReader<R> {
101
+ pub fn new(inner: R, key: EncryptionKey, offset: usize) -> Self {
102
+ Self {
103
+ inner,
104
+ cipher: Chacha20Cipher::new(key, offset as u64),
105
+ }
106
+ }
107
+ }
108
+
109
+ impl<R: AsyncRead + Unpin> AsyncRead for CipherReader<R> {
110
+ fn poll_read(
111
+ mut self: std::pin::Pin<&mut Self>,
112
+ cx: &mut std::task::Context<'_>,
113
+ buf: &mut tokio::io::ReadBuf<'_>,
114
+ ) -> std::task::Poll<std::io::Result<()>> {
115
+ let initial_filled = buf.filled().len();
116
+ let poll = std::pin::Pin::new(&mut self.inner).poll_read(cx, buf);
117
+
118
+ // apply the cipher to the newly read bytes
119
+ self.cipher
120
+ .apply_keystream(&mut buf.filled_mut()[initial_filled..]);
121
+ poll
122
+ }
123
+ }
124
+
125
+ pub struct CipherWriter<W: AsyncWrite> {
126
+ inner: W,
127
+ cipher: Chacha20Cipher,
128
+ buf: Vec<u8>,
129
+ }
130
+
131
+ impl<W: AsyncWrite> CipherWriter<W> {
132
+ pub fn new(inner: W, key: EncryptionKey, offset: usize) -> Self {
133
+ Self {
134
+ inner,
135
+ cipher: Chacha20Cipher::new(key, offset as u64),
136
+ buf: Vec::new(),
137
+ }
138
+ }
139
+ }
140
+
141
+ impl<W: AsyncWrite + Unpin> AsyncWrite for CipherWriter<W> {
142
+ fn poll_write(
143
+ self: std::pin::Pin<&mut Self>,
144
+ cx: &mut std::task::Context<'_>,
145
+ buf: &[u8],
146
+ ) -> std::task::Poll<std::io::Result<usize>> {
147
+ let this = self.get_mut();
148
+ this.buf.resize(buf.len(), 0);
149
+ this.buf.copy_from_slice(buf);
150
+ this.cipher.apply_keystream(&mut this.buf);
151
+ std::pin::Pin::new(&mut this.inner).poll_write(cx, &this.buf)
152
+ }
153
+
154
+ fn poll_flush(
155
+ mut self: std::pin::Pin<&mut Self>,
156
+ cx: &mut std::task::Context<'_>,
157
+ ) -> std::task::Poll<std::io::Result<()>> {
158
+ std::pin::Pin::new(&mut self.inner).poll_flush(cx)
159
+ }
160
+
161
+ fn poll_shutdown(
162
+ mut self: std::pin::Pin<&mut Self>,
163
+ cx: &mut std::task::Context<'_>,
164
+ ) -> std::task::Poll<std::io::Result<()>> {
165
+ std::pin::Pin::new(&mut self.inner).poll_shutdown(cx)
166
+ }
167
+ }
168
+
169
+ struct Chacha20Cipher {
170
+ inner: XChaCha20,
171
+ key: EncryptionKey,
172
+ nonce: [u8; 24],
173
+ offset: u64,
174
+ }
175
+
176
+ impl Chacha20Cipher {
177
+ const MAX_BYTES_PER_NONCE: u64 = u32::MAX as u64 * 64;
178
+
179
+ fn nonce_for_offset(offset: u64) -> [u8; 24] {
180
+ let mut nonce: [u8; 24] = [0u8; 24];
181
+ nonce[16..24].copy_from_slice(&(offset / Self::MAX_BYTES_PER_NONCE).to_le_bytes());
182
+ nonce
183
+ }
184
+
185
+ pub fn new(key: EncryptionKey, offset: u64) -> Self {
186
+ let nonce = Self::nonce_for_offset(offset);
187
+ let mut cipher = XChaCha20::new(key.as_ref().into(), &nonce.into());
188
+ cipher.seek(offset % Self::MAX_BYTES_PER_NONCE);
189
+ Self {
190
+ inner: cipher,
191
+ key,
192
+ nonce,
193
+ offset,
194
+ }
195
+ }
196
+ }
197
+
198
+ impl StreamCipher for Chacha20Cipher {
199
+ fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> {
200
+ // we handle nonce rotation, so we can always process more data.
201
+ Ok(())
202
+ }
203
+
204
+ fn unchecked_apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) {
205
+ let remaining_keystream =
206
+ Self::MAX_BYTES_PER_NONCE - (self.offset % Self::MAX_BYTES_PER_NONCE);
207
+
208
+ if buf.len() as u64 <= remaining_keystream {
209
+ self.offset += buf.len() as u64;
210
+ self.inner.apply_keystream_inout(buf);
211
+ return;
212
+ }
213
+
214
+ // we can't process the entire buffer with the current nonce, so we need
215
+ // to split it
216
+ let (first, second) = buf.split_at(remaining_keystream as usize);
217
+
218
+ // the first part can be processed with the current nonce
219
+ self.offset += first.len() as u64;
220
+ self.inner.apply_keystream_inout(first);
221
+
222
+ // update nonce and reinitialize cipher
223
+ self.nonce = Self::nonce_for_offset(self.offset);
224
+ self.inner = XChaCha20::new(self.key.as_ref().into(), &self.nonce.into());
225
+
226
+ // encrypt the second part
227
+ self.offset += second.len() as u64;
228
+ self.inner.apply_keystream_inout(second);
229
+ }
230
+
231
+ fn unchecked_write_keystream(&mut self, buf: &mut [u8]) {
232
+ buf.fill(0);
233
+ self.unchecked_apply_keystream(buf);
234
+ }
235
+ }
236
+
237
+ #[cfg(test)]
238
+ mod test {
239
+ use rand::Rng;
240
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
241
+
242
+ use crate::rhp::SECTOR_SIZE;
243
+
244
+ use super::*;
245
+
246
+ #[test]
247
+ fn test_encrypt_sector_roundtrip() {
248
+ let key = EncryptionKey::from([1u8; 32]);
249
+
250
+ let mut sector = vec![0u8; SECTOR_SIZE];
251
+ rand::rng().fill_bytes(&mut sector);
252
+
253
+ let original = sector.clone();
254
+ encrypt_shard(&key, 0, 0, &mut sector);
255
+ assert_ne!(sector, original);
256
+ encrypt_shard(&key, 0, 0, &mut sector);
257
+ assert_eq!(sector, original);
258
+ }
259
+
260
+ #[test]
261
+ fn test_encrypt_shards() {
262
+ let key = EncryptionKey::from([1u8; 32]);
263
+ let mut shards = vec![vec![1, 2, 3], vec![4, 5, 6]];
264
+
265
+ // encrypt
266
+ encrypt_shards(&key, 0, 0, &mut shards);
267
+ assert_eq!(shards[0], vec![136, 154, 188]);
268
+ assert_eq!(shards[1], vec![70, 216, 180]);
269
+
270
+ // decrypt
271
+ encrypt_shards(&key, 0, 0, &mut shards);
272
+ assert_eq!(shards[0], vec![1, 2, 3]);
273
+ assert_eq!(shards[1], vec![4, 5, 6]);
274
+
275
+ // encrypt with offset
276
+ encrypt_shards(&key, 0, 100, &mut shards);
277
+ assert_eq!(shards[0], vec![6, 194, 192]);
278
+ assert_eq!(shards[1], vec![236, 188, 165]);
279
+
280
+ // decrypt with offset
281
+ encrypt_shards(&key, 0, 100, &mut shards);
282
+ assert_eq!(shards[0], vec![1, 2, 3]);
283
+ assert_eq!(shards[1], vec![4, 5, 6]);
284
+ }
285
+
286
+ #[tokio::test]
287
+ async fn test_cipher_reader_writer() {
288
+ let key = EncryptionKey::from([1u8; 32]);
289
+ let data = b"lorem ipsum dolor sit amet, consectetur adipiscing elit";
290
+
291
+ for offset in [0, 10, u32::MAX as usize * 64 - 10, u32::MAX as usize * 64] {
292
+ let mut reader = CipherReader::new(data.as_ref(), key.clone(), offset);
293
+ let mut cipher_text = vec![0u8; data.len()];
294
+ reader.read_exact(&mut cipher_text).await.unwrap();
295
+ assert_ne!(cipher_text, data);
296
+
297
+ let mut writer = CipherWriter::new(Vec::new(), key.clone(), offset);
298
+ writer.write_all(&cipher_text).await.unwrap();
299
+ let plaintext = writer.inner;
300
+ assert_eq!(plaintext, data);
301
+ }
302
+ }
303
+ }