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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +179 -0
- package/package.json +29 -0
- package/template/CLAUDE.md +160 -0
- package/template/README.md +102 -0
- package/template/_gitignore +5 -0
- package/template/biome.json +40 -0
- package/template/index.html +13 -0
- package/template/package.json +30 -0
- package/template/rust/README.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
- package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
- package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
- package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
- package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
- package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
- package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
- package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
- package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
- package/template/rust/sia-sdk-rs/LICENSE +21 -0
- package/template/rust/sia-sdk-rs/README.md +30 -0
- package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
- package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
- package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
- package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
- package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
- package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
- package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
- package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
- package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
- package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
- package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
- package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
- package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
- package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
- package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
- package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
- package/template/rust/sia-sdk-rs/knope.toml +54 -0
- package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
- package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
- package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
- package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
- package/template/scripts/setup-rust.js +29 -0
- package/template/src/App.tsx +13 -0
- package/template/src/components/DevNote.tsx +21 -0
- package/template/src/components/auth/ApproveScreen.tsx +84 -0
- package/template/src/components/auth/AuthFlow.tsx +77 -0
- package/template/src/components/auth/ConnectScreen.tsx +214 -0
- package/template/src/components/auth/LoadingScreen.tsx +8 -0
- package/template/src/components/auth/RecoveryScreen.tsx +182 -0
- package/template/src/components/upload/UploadZone.tsx +314 -0
- package/template/src/index.css +9 -0
- package/template/src/lib/constants.ts +8 -0
- package/template/src/lib/format.ts +35 -0
- package/template/src/lib/hex.ts +13 -0
- package/template/src/lib/sdk.ts +25 -0
- package/template/src/lib/wasm-env.ts +5 -0
- package/template/src/main.tsx +12 -0
- package/template/src/stores/auth.ts +86 -0
- package/template/tsconfig.app.json +31 -0
- package/template/tsconfig.json +7 -0
- package/template/tsconfig.node.json +26 -0
- package/template/vite.config.ts +18 -0
- package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
- package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
- package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
- 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,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
|
+
}
|